Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 165 lines 4.2 kB view raw
1import { 2 type $Typed, 3 type AppBskyActorDefs, 4 type AppBskyGraphGetStarterPack, 5 type BskyAgent, 6 type ComAtprotoRepoApplyWrites, 7 type Facet, 8} from '@atproto/api' 9import {msg} from '@lingui/core/macro' 10import {useLingui} from '@lingui/react' 11import {useMutation} from '@tanstack/react-query' 12 13import {until} from '#/lib/async/until' 14import {sanitizeDisplayName} from '#/lib/strings/display-names' 15import {sanitizeHandle} from '#/lib/strings/handles' 16import {enforceLen} from '#/lib/strings/helpers' 17import {useAgent} from '#/state/session' 18import type * as bsky from '#/types/bsky' 19 20export const createStarterPackList = async ({ 21 name, 22 description, 23 descriptionFacets, 24 profiles, 25 agent, 26}: { 27 name: string 28 description?: string 29 descriptionFacets?: Facet[] 30 profiles: bsky.profile.AnyProfileView[] 31 agent: BskyAgent 32}): Promise<{uri: string; cid: string}> => { 33 if (profiles.length === 0) throw new Error('No profiles given') 34 35 const list = await agent.app.bsky.graph.list.create( 36 {repo: agent.session!.did}, 37 { 38 name, 39 description, 40 descriptionFacets, 41 avatar: undefined, 42 createdAt: new Date().toISOString(), 43 purpose: 'app.bsky.graph.defs#referencelist', 44 }, 45 ) 46 if (!list) throw new Error('List creation failed') 47 await agent.com.atproto.repo.applyWrites({ 48 repo: agent.session!.did, 49 writes: profiles.map(p => createListItem({did: p.did, listUri: list.uri})), 50 }) 51 52 return list 53} 54 55export function useGenerateStarterPackMutation({ 56 onSuccess, 57 onError, 58}: { 59 onSuccess: ({uri, cid}: {uri: string; cid: string}) => void 60 onError: (e: Error) => void 61}) { 62 const {_} = useLingui() 63 const agent = useAgent() 64 65 return useMutation<{uri: string; cid: string}, Error, void>({ 66 mutationFn: async () => { 67 let profile: AppBskyActorDefs.ProfileViewDetailed | undefined 68 let profiles: AppBskyActorDefs.ProfileView[] | undefined 69 70 await Promise.all([ 71 (async () => { 72 profile = ( 73 await agent.app.bsky.actor.getProfile({ 74 actor: agent.session!.did, 75 }) 76 ).data 77 })(), 78 (async () => { 79 profiles = ( 80 await agent.app.bsky.actor.searchActors({ 81 q: encodeURIComponent('*'), 82 limit: 49, 83 }) 84 ).data.actors.filter(p => p.viewer?.following) 85 })(), 86 ]) 87 88 if (!profile || !profiles) { 89 throw new Error('ERROR_DATA') 90 } 91 92 // We include ourselves when we make the list 93 if (profiles.length < 7) { 94 throw new Error('NOT_ENOUGH_FOLLOWERS') 95 } 96 97 const displayName = enforceLen( 98 profile.displayName 99 ? sanitizeDisplayName(profile.displayName) 100 : `@${sanitizeHandle(profile.handle)}`, 101 25, 102 true, 103 ) 104 const starterPackName = _(msg`${displayName}'s Starter Pack`) 105 106 const list = await createStarterPackList({ 107 name: starterPackName, 108 profiles, 109 agent, 110 }) 111 112 return await agent.app.bsky.graph.starterpack.create( 113 { 114 repo: agent.session!.did, 115 }, 116 { 117 name: starterPackName, 118 list: list.uri, 119 createdAt: new Date().toISOString(), 120 }, 121 ) 122 }, 123 onSuccess: async data => { 124 await whenAppViewReady(agent, data.uri, v => { 125 return typeof v?.data.starterPack.uri === 'string' 126 }) 127 onSuccess(data) 128 }, 129 onError: error => { 130 onError(error) 131 }, 132 }) 133} 134 135function createListItem({ 136 did, 137 listUri, 138}: { 139 did: string 140 listUri: string 141}): $Typed<ComAtprotoRepoApplyWrites.Create> { 142 return { 143 $type: 'com.atproto.repo.applyWrites#create', 144 collection: 'app.bsky.graph.listitem', 145 value: { 146 $type: 'app.bsky.graph.listitem', 147 subject: did, 148 list: listUri, 149 createdAt: new Date().toISOString(), 150 }, 151 } 152} 153 154async function whenAppViewReady( 155 agent: BskyAgent, 156 uri: string, 157 fn: (res?: AppBskyGraphGetStarterPack.Response) => boolean, 158) { 159 await until( 160 5, // 5 tries 161 1e3, // 1s delay between tries 162 fn, 163 () => agent.app.bsky.graph.getStarterPack({starterPack: uri}), 164 ) 165}