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