your personal website on atproto - mirror blento.app
at qr-codes 324 lines 6.8 kB view raw
1import type { Did, Handle } from '@atcute/lexicons'; 2import { user } from './auth.svelte'; 3import { 4 CompositeDidDocumentResolver, 5 CompositeHandleResolver, 6 DohJsonHandleResolver, 7 PlcDidDocumentResolver, 8 WebDidDocumentResolver, 9 WellKnownHandleResolver 10} from '@atcute/identity-resolver'; 11import { Client, simpleFetchHandler } from '@atcute/client'; 12import type { AppBskyActorDefs } from '@atcute/bluesky'; 13 14export type Collection = `${string}.${string}.${string}`; 15 16export function parseUri(uri: string) { 17 const [did, collection, rkey] = uri.replace('at://', '').split('/'); 18 return { did, collection, rkey } as { 19 collection: `${string}.${string}.${string}`; 20 rkey: string; 21 did: Did; 22 }; 23} 24 25export async function resolveHandle({ handle }: { handle: Handle }) { 26 const handleResolver = new CompositeHandleResolver({ 27 methods: { 28 dns: new DohJsonHandleResolver({ dohUrl: 'https://mozilla.cloudflare-dns.com/dns-query' }), 29 http: new WellKnownHandleResolver() 30 } 31 }); 32 33 const data = await handleResolver.resolve(handle); 34 return data; 35} 36 37const didResolver = new CompositeDidDocumentResolver({ 38 methods: { 39 plc: new PlcDidDocumentResolver(), 40 web: new WebDidDocumentResolver() 41 } 42}); 43 44export async function getPDS(did: Did) { 45 const doc = await didResolver.resolve(did as `did:plc:${string}` | `did:web:${string}`); 46 if (!doc.service) throw new Error('No PDS found'); 47 for (const service of doc.service) { 48 if (service.id === '#atproto_pds') { 49 return service.serviceEndpoint.toString(); 50 } 51 } 52} 53 54export async function getDetailedProfile(data?: { did?: Did; client?: Client }) { 55 data ??= {}; 56 data.did ??= user.did; 57 58 if (!data.did) throw new Error('Error getting detailed profile: no did'); 59 60 data.client ??= new Client({ 61 handler: simpleFetchHandler({ service: 'https://public.api.bsky.app' }) 62 }); 63 64 const response = await data.client.get('app.bsky.actor.getProfile', { 65 params: { actor: data.did } 66 }); 67 68 if (!response.ok) return; 69 70 return response.data; 71} 72 73export async function getAuthorFeed(data?: { 74 did?: Did; 75 client?: Client; 76 filter?: string; 77 limit?: number; 78}) { 79 data ??= {}; 80 data.did ??= user.did; 81 82 if (!data.did) throw new Error('Error getting detailed profile: no did'); 83 84 data.client ??= new Client({ 85 handler: simpleFetchHandler({ service: 'https://public.api.bsky.app' }) 86 }); 87 88 const response = await data.client.get('app.bsky.feed.getAuthorFeed', { 89 params: { actor: data.did, filter: data.filter ?? 'posts_with_media', limit: data.limit || 100 } 90 }); 91 92 if (!response.ok) return; 93 94 return response.data; 95} 96 97export async function getClient({ did }: { did: Did }) { 98 const pds = await getPDS(did); 99 if (!pds) throw new Error('PDS not found'); 100 101 const client = new Client({ 102 handler: simpleFetchHandler({ service: pds }) 103 }); 104 105 return client; 106} 107 108export async function listRecords({ 109 did, 110 collection, 111 cursor, 112 limit = 0, 113 client 114}: { 115 did?: Did; 116 collection: `${string}.${string}.${string}`; 117 cursor?: string; 118 limit?: number; 119 client?: Client; 120}) { 121 did ??= user.did; 122 if (!collection) { 123 throw new Error('Missing parameters for listRecords'); 124 } 125 if (!did) { 126 throw new Error('Missing did for getRecord'); 127 } 128 129 client ??= await getClient({ did }); 130 131 const allRecords = []; 132 133 let currentCursor = cursor; 134 do { 135 const response = await client.get('com.atproto.repo.listRecords', { 136 params: { 137 repo: did, 138 collection, 139 limit: limit || 100, 140 cursor: currentCursor 141 } 142 }); 143 144 if (!response.ok) { 145 return allRecords; 146 } 147 148 allRecords.push(...response.data.records); 149 currentCursor = response.data.cursor; 150 } while (currentCursor && (!limit || allRecords.length < limit)); 151 152 return allRecords; 153} 154 155export async function getRecord({ 156 did, 157 collection, 158 rkey, 159 client 160}: { 161 did?: Did; 162 collection: Collection; 163 rkey?: string; 164 client?: Client; 165}) { 166 did ??= user.did; 167 rkey ??= 'self'; 168 169 if (!collection) { 170 throw new Error('Missing parameters for getRecord'); 171 } 172 if (!did) { 173 throw new Error('Missing did for getRecord'); 174 } 175 176 client ??= await getClient({ did }); 177 178 const record = await client.get('com.atproto.repo.getRecord', { 179 params: { 180 repo: did, 181 collection, 182 rkey 183 } 184 }); 185 186 return JSON.parse(JSON.stringify(record.data)); 187} 188 189export async function putRecord({ 190 collection, 191 rkey, 192 record 193}: { 194 collection: Collection; 195 rkey: string; 196 record: Record<string, unknown>; 197}) { 198 if (!user.client || !user.did) throw new Error('No rpc or did'); 199 200 const response = await user.client.post('com.atproto.repo.putRecord', { 201 input: { 202 collection, 203 repo: user.did, 204 rkey, 205 record: { 206 ...record 207 } 208 } 209 }); 210 211 return response; 212} 213 214export async function deleteRecord({ collection, rkey }: { collection: Collection; rkey: string }) { 215 if (!user.client || !user.did) throw new Error('No profile or rpc or did'); 216 217 const response = await user.client.post('com.atproto.repo.deleteRecord', { 218 input: { 219 collection, 220 repo: user.did, 221 rkey 222 } 223 }); 224 225 return response.ok; 226} 227 228export async function uploadBlob({ blob }: { blob: Blob }) { 229 if (!user.did || !user.client) throw new Error("Can't upload blob: Not logged in"); 230 231 const blobResponse = await user.client.post('com.atproto.repo.uploadBlob', { 232 input: blob, 233 data: { 234 repo: user.did 235 } 236 }); 237 238 if (!blobResponse?.ok) { 239 return; 240 } 241 242 const blobInfo = blobResponse?.data.blob as { 243 $type: 'blob'; 244 ref: { 245 $link: string; 246 }; 247 mimeType: string; 248 size: number; 249 }; 250 251 return blobInfo; 252} 253 254export async function describeRepo({ client, did }: { client?: Client; did?: Did }) { 255 did ??= user.did; 256 if (!did) { 257 throw new Error('Error describeRepo: No did'); 258 } 259 client ??= await getClient({ did }); 260 261 const repo = await client.get('com.atproto.repo.describeRepo', { 262 params: { 263 repo: did 264 } 265 }); 266 if (!repo.ok) return; 267 268 return repo.data; 269} 270 271export async function getBlobURL({ 272 did, 273 blob 274}: { 275 did: Did; 276 blob: { 277 $type: 'blob'; 278 ref: { 279 $link: string; 280 }; 281 }; 282}) { 283 const pds = await getPDS(did); 284 return `${pds}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${blob.ref.$link}`; 285} 286 287export function getImageBlobUrl({ 288 did, 289 blob 290}: { 291 did: string; 292 blob: { 293 $type: 'blob'; 294 ref: { 295 $link: string; 296 }; 297 }; 298}) { 299 if (!did || !blob?.ref?.$link) return ''; 300 return `https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${blob.ref.$link}@webp`; 301} 302 303export async function searchActorsTypeahead( 304 q: string, 305 limit: number = 10, 306 host?: string 307): Promise<{ actors: AppBskyActorDefs.ProfileViewBasic[]; q: string }> { 308 host ??= 'https://public.api.bsky.app'; 309 310 const client = new Client({ 311 handler: simpleFetchHandler({ service: host }) 312 }); 313 314 const response = await client.get('app.bsky.actor.searchActorsTypeahead', { 315 params: { 316 q, 317 limit 318 } 319 }); 320 321 if (!response.ok) return { actors: [], q }; 322 323 return { actors: response.data.actors, q }; 324}