appview-less bluesky client
24
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 89 lines 2.9 kB view raw
1import { type ActorIdentifier, type Did, type RecordKey } from '@atcute/lexicons/syntax'; 2import { type AtpClient } from './client.svelte'; 3import { AppBskyFeedGenerator } from '@atcute/bluesky'; 4import { img } from '$lib/cdn'; 5import type { Blob as AtprotoBlob } from '@atcute/lexicons'; 6 7export type FeedGenerator = { 8 uri: string; 9 displayName: string; 10 description?: string; 11 avatar?: string; 12 did: string; 13}; 14 15export function parseFeedUri(uri: string): { repo: ActorIdentifier; rkey: RecordKey } | null { 16 if (uri.startsWith('at://')) { 17 const match = uri.match(/^at:\/\/([^/]+)\/app\.bsky\.feed\.generator\/([^/]+)$/); 18 if (!match) return null; 19 return { repo: match[1] as ActorIdentifier, rkey: match[2] as RecordKey }; 20 } else if (uri.startsWith('https://') || uri.startsWith('http://')) { 21 const match = uri.match(/^https?:\/\/(?:[^/]+)\/profile\/([^/]+)\/feed\/([^/]+)$/); 22 if (!match) return null; 23 return { repo: match[1] as ActorIdentifier, rkey: match[2] as RecordKey }; 24 } 25 return null; 26} 27 28export async function fetchFeedGenerator(client: AtpClient, uri: string): Promise<FeedGenerator | null> { 29 const parsed = parseFeedUri(uri); 30 if (!parsed) return null; 31 32 try { 33 const response = await client.getRecord(AppBskyFeedGenerator.mainSchema, parsed.repo, parsed.rkey); 34 if (!response.ok) return null; 35 36 const value = response.value.record; 37 const did = response.value.uri.split('/')[2] as Did; 38 const avatar = value.avatar ? img('avatar_thumbnail', did, (value.avatar as AtprotoBlob<string>).ref.$link, 'webp') : undefined; 39 40 return { 41 uri: response.value.uri, 42 displayName: value.displayName, 43 description: value.description, 44 did: value.did, 45 avatar, 46 }; 47 } catch { 48 return null; 49 } 50} 51 52export type FeedSkeletonItem = { 53 post: string; 54 reason?: { $type: string; repost?: string }; 55}; 56 57export type FeedSkeleton = { 58 feed: FeedSkeletonItem[]; 59 cursor?: string; 60}; 61 62export async function fetchFeedSkeleton( 63 client: AtpClient, 64 feedUri: string, 65 feedServiceDid: string, 66 cursor?: string, 67 limit: number = 25 68): Promise<FeedSkeleton | null> { 69 const auth = client.user; 70 if (!auth) return null; 71 72 const cursorParam = cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''; 73 const url = `/xrpc/app.bsky.feed.getFeedSkeleton?feed=${encodeURIComponent(feedUri)}&limit=${limit}${cursorParam}`; 74 75 try { 76 const response = await auth.atcute.handler(url, { 77 method: 'GET', 78 headers: { 79 'atproto-proxy': `${feedServiceDid}#bsky_fg` 80 } 81 }); 82 83 if (!response.ok) return null; 84 return (await response.json()) as FeedSkeleton; 85 } catch { 86 return null; 87 } 88} 89