import { type ActorIdentifier, type Did, type RecordKey } from '@atcute/lexicons/syntax';
import { type AtpClient } from './client.svelte';
import { AppBskyFeedGenerator } from '@atcute/bluesky';
import { img } from '$lib/cdn';
import type { Blob as AtprotoBlob } from '@atcute/lexicons';
export type FeedGenerator = {
uri: string;
displayName: string;
description?: string;
avatar?: string;
did: string;
};
export function parseFeedUri(uri: string): { repo: ActorIdentifier; rkey: RecordKey } | null {
if (uri.startsWith('at://')) {
const match = uri.match(/^at:\/\/([^/]+)\/app\.bsky\.feed\.generator\/([^/]+)$/);
if (!match) return null;
return { repo: match[1] as ActorIdentifier, rkey: match[2] as RecordKey };
} else if (uri.startsWith('https://') || uri.startsWith('http://')) {
const match = uri.match(/^https?:\/\/(?:[^/]+)\/profile\/([^/]+)\/feed\/([^/]+)$/);
if (!match) return null;
return { repo: match[1] as ActorIdentifier, rkey: match[2] as RecordKey };
}
return null;
}
export async function fetchFeedGenerator(
client: AtpClient,
uri: string
): Promise<FeedGenerator | null> {
const parsed = parseFeedUri(uri);
if (!parsed) return null;
try {
const response = await client.getRecord(
AppBskyFeedGenerator.mainSchema,
parsed.repo,
parsed.rkey
);
if (!response.ok) return null;
const value = response.value.record;
const did = response.value.uri.split('/')[2] as Did;
const avatar = value.avatar
? img('avatar_thumbnail', did, (value.avatar as AtprotoBlob<string>).ref.$link, 'webp')
: undefined;
return {
uri: response.value.uri,
displayName: value.displayName,
description: value.description,
did: value.did,
avatar
};
} catch {
return null;
}
}
export type FeedSkeletonItem = {
post: string;
reason?: { $type: string; repost?: string };
};
export type FeedSkeleton = {
feed: FeedSkeletonItem[];
cursor?: string;
};
export async function fetchFeedSkeleton(
client: AtpClient,
feedUri: string,
feedServiceDid: string,
cursor?: string,
limit: number = 25
): Promise<FeedSkeleton | null> {
const auth = client.user;
if (!auth) return null;
const cursorParam = cursor ? `&cursor=${encodeURIComponent(cursor)}` : '';
const url = `/xrpc/app.bsky.feed.getFeedSkeleton?feed=${encodeURIComponent(feedUri)}&limit=${limit}${cursorParam}`;
try {
const response = await auth.atcute.handler(url, {
method: 'GET',
headers: {
'atproto-proxy': `${feedServiceDid}#bsky_fg`
}
});
if (!response.ok) return null;
return (await response.json()) as FeedSkeleton;
} catch {
return null;
}
}