Something went wrong. Try again.
A Bsky-like frontend using the atprotocol natively.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442import { CarBufferReader } from '@ipld/car';import { decode as dagCborDecode } from '@ipld/dag-cbor';import type { CID } from 'multiformats/cid';
const PLC_DIR = 'https://plc.directory';const DEFAULT_TIMEOUT = 8_000;
const DISABLE_CACHE = process.env.DISABLE_CACHE === 'true';const CACHE_TTL = Number(process.env.CACHE_TTL ?? 3600) * 1000;const MAX_CACHE_SIZE = Number(process.env.MAX_CACHE_SIZE ?? 10000);
const HANDLE_RESOLVERS = [ 'https://compressed.cubporn.zip/', 'https://bsky.social/',];
async function fetchJson<T>(url: string, timeoutMs = DEFAULT_TIMEOUT): Promise<T> { const ac = new AbortController(); const t = setTimeout(() => ac.abort(), timeoutMs); try { const r = await fetch(url, { signal: ac.signal }); if (!r.ok) throw new Error(`${r.status}`); return (await r.json()) as T; } finally { clearTimeout(t); }}
async function fetchBuf(url: string, timeoutMs = DEFAULT_TIMEOUT): Promise<{ buffer: Buffer; contentType: string }> { const abortController = new AbortController(); const t = setTimeout(() => abortController.abort(), timeoutMs); try { const response = await fetch(url, { signal: abortController.signal }); if (!response.ok) throw new Error(`${response.status}`); const contentType = response.headers.get('content-type') ?? 'application/octet-stream'; const arrayBuffer = await response.arrayBuffer(); return { buffer: Buffer.from(arrayBuffer), contentType }; } finally { clearTimeout(t); }}
interface DidService { id: string; type: string; serviceEndpoint: string;}interface DidDocument { id?: string; service?: DidService[];}
interface CacheEntry<T> { value: T; expires: number;}
const pdsCache = new Map<string, CacheEntry<string>>();const handleCache = new Map<string, CacheEntry<string>>();const handleToDidCache = new Map<string, CacheEntry<string>>();
function cleanupCache<T>(cache: Map<string, CacheEntry<T>>) { const now = Date.now(); for (const [key, entry] of cache.entries()) { if (now > entry.expires) { cache.delete(key); } }}
function runCleanup() { cleanupCache(pdsCache); cleanupCache(handleCache); cleanupCache(handleToDidCache);}
// Run cleanup every hour (or CACHE_TTL, whichever is smaller, but at least every 5 mins)const CLEANUP_INTERVAL = Math.max(5 * 60 * 1000, Math.min(CACHE_TTL, 3600 * 1000));setInterval(runCleanup, CLEANUP_INTERVAL).unref();
function getFromCache<T>(cache: Map<string, CacheEntry<T>>, key: string): T | undefined { if (DISABLE_CACHE) return undefined; const entry = cache.get(key); if (!entry) return undefined; if (Date.now() > entry.expires) { cache.delete(key); return undefined; } return entry.value;}
function setToCache<T>(cache: Map<string, CacheEntry<T>>, key: string, value: T) { if (DISABLE_CACHE) return; if (cache.size >= MAX_CACHE_SIZE && !cache.has(key)) { const firstKey = cache.keys().next().value; if (firstKey !== undefined) cache.delete(firstKey); } cache.set(key, { value, expires: Date.now() + CACHE_TTL });}
export async function resolveHandle(handle: string): Promise<string> { const lc = handle.toLowerCase(); const cached = getFromCache(handleCache, lc); if (cached) return cached;
for (const resolver of HANDLE_RESOLVERS) { try { const data = await fetchJson<{ did: string }>( `${resolver}xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`, 5_000, ); setToCache(handleCache, lc, data.did); setToCache(handleToDidCache, handle, data.did); return data.did; } catch { /* try next resolver */ } }
throw new Error(`Could not resolve handle: ${handle}`);}
export async function resolvePds(did: string): Promise<string> { const cached = getFromCache(pdsCache, did); if (cached) return cached;
let doc: DidDocument;
if (did.startsWith('did:plc:')) { doc = await fetchJson<DidDocument>(`${PLC_DIR}/${encodeURIComponent(did)}`); } else if (did.startsWith('did:web:')) { const domain = did.slice('did:web:'.length); doc = await fetchJson<DidDocument>(`https://${domain}/.well-known/did.json`); } else { throw new Error(`Unsupported DID method: ${did}`); }
const svc = doc.service?.find( (s) => s.id === '#atproto_pds' || s.type === 'AtprotoPersonalDataServer', ); if (!svc?.serviceEndpoint) throw new Error(`No PDS service found for ${did}`);
const pds = svc.serviceEndpoint.replace(/\/$/, ''); setToCache(pdsCache, did, pds); return pds;}
function cborToJson(value: unknown): unknown { if (value === null || value === undefined) return value;
if (typeof value === 'object' && value !== null && '$bytes' in (value as object) === false && String((value as object).constructor?.name) === 'CID') { return { $link: (value as CID).toString() }; } if (typeof value === 'object' && value !== null && 'toV1' in (value as object) && 'bytes' in (value as object)) { try { return { $link: (value as CID).toString() }; } catch { /* ignored */ } }
if (typeof value === 'bigint') return Number(value);
if (Array.isArray(value)) return value.map(cborToJson);
if (value instanceof Map) { const obj: Record<string, unknown> = {}; for (const [k, v] of value) { obj[String(k)] = cborToJson(v); } return obj; }
if (typeof value === 'object') { const obj: Record<string, unknown> = {}; for (const [k, v] of Object.entries(value as Record<string, unknown>)) { obj[k] = cborToJson(v); } return obj; }
return value;}
const MST_KEYS = new Set(['l', 'e']);const COMMIT_KEYS = new Set(['did', 'rev', 'sig', 'prev', 'data', 'version']);
function isMstOrCommitDecoded(decoded: Map<unknown, unknown> | Record<string, unknown>): boolean { if (decoded instanceof Map) { for (const k of decoded.keys()) { const ks = String(k); if (MST_KEYS.has(ks) || COMMIT_KEYS.has(ks)) return true; } } else { for (const k of Object.keys(decoded)) { if (MST_KEYS.has(k) || COMMIT_KEYS.has(k)) return true; } } return false;}
export async function getRecord<T = Record<string, unknown>>( pdsUrl: string, repo: string, collection: string, rkey: string,): Promise<{ uri: string; cid: string; value: T }> { const url = `${pdsUrl}/xrpc/com.atproto.sync.getRecord?did=${encodeURIComponent(repo)}&collection=${encodeURIComponent(collection)}&rkey=${encodeURIComponent(rkey)}`; const { buffer } = await fetchBuf(url);
const carReader = CarBufferReader.fromBytes(new Uint8Array(buffer));
const roots = carReader.getRoots(); if (roots.length > 0) { const rootBlock = carReader.get(roots[0]); if (rootBlock) { const decoded = dagCborDecode(rootBlock.bytes); // If the root is a map/object, check if it's a record (not MST/commit) if ((decoded instanceof Map) || (typeof decoded === 'object' && decoded !== null && !Array.isArray(decoded))) { if (!isMstOrCommitDecoded(decoded as Map<unknown, unknown> | Record<string, unknown>)) { const value = cborToJson(decoded) as T; const cid = rootBlock.cid.toString(); const uri = `at://${repo}/${collection}/${rkey}`; return { uri, cid, value }; } } } }
const allBlocks = carReader.blocks(); for (let i = allBlocks.length - 1; i >= 0; i--) { const block = allBlocks[i]; const decoded = dagCborDecode(block.bytes); if ((decoded instanceof Map) || (typeof decoded === 'object' && decoded !== null && !Array.isArray(decoded))) { if (!isMstOrCommitDecoded(decoded as Map<unknown, unknown> | Record<string, unknown>)) { const value = cborToJson(decoded) as T; const cid = block.cid.toString(); const uri = `at://${repo}/${collection}/${rkey}`; return { uri, cid, value }; } } }
throw new Error('No record block found in CAR response');}
export async function getBlob( pdsUrl: string, did: string, cid: string,): Promise<{ buffer: Buffer; contentType: string }> { const url = `${pdsUrl}/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(cid)}`; return fetchBuf(url);}
export interface ProfileRecord { displayName?: string; description?: string; avatar?: { ref: { $link: string }; mimeType: string; size: number; $type: string }; banner?: { ref: { $link: string }; mimeType: string; size: number; $type: string };}
export async function getProfileRecord(pdsUrl: string, did: string): Promise<{ cid: string; value: ProfileRecord }> { const rec = await getRecord<ProfileRecord>(pdsUrl, did, 'app.bsky.actor.profile', 'self'); return { cid: rec.cid, value: rec.value };}
export interface ProfileCounts { followersCount?: number; followsCount?: number; postsCount?: number; pronouns?: string;}
const PUBLIC_APPVIEW = 'https://public.api.bsky.app';const countsCache = new Map<string, CacheEntry<ProfileCounts>>();
setInterval(() => { cleanupCache(countsCache);}, CLEANUP_INTERVAL).unref();
export async function getProfileCounts(pdsUrl: string, actor: string): Promise<ProfileCounts> { const key = actor.toLowerCase(); const cached = getFromCache(countsCache, key); if (cached) return cached;
try { const data = await fetchJson<{ followersCount?: number; followsCount?: number; postsCount?: number; pronouns?: string; }>(`${pdsUrl.endsWith(".bsky.network") ? PUBLIC_APPVIEW : pdsUrl}/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(actor)}`, 6_000);
const counts: ProfileCounts = { followersCount: data.followersCount, followsCount: data.followsCount, postsCount: data.postsCount, pronouns: data.pronouns, }; setToCache(countsCache, key, counts); return counts; } catch { return {}; }}
export function formatCount(n: number): string { if (n >= 1_000_000) { const v = n / 1_000_000; return `${v % 1 === 0 ? v.toFixed(0) : v.toFixed(1).replace(/\.0$/, '')}M`; } if (n >= 1_000) { const v = n / 1_000; return `${v % 1 === 0 ? v.toFixed(0) : v.toFixed(1).replace(/\.0$/, '')}K`; } return String(n);}
export interface FeedGeneratorView { uri: string; cid: string; did: string; creator: { did: string; handle: string; displayName?: string; avatar?: string; }; displayName?: string; description?: string; avatar?: string; likeCount?: number; isOnline?: boolean; isValid?: boolean; indexedAt?: string; createdAt?: string;}
export async function getFeedGenerator(feedUri: string): Promise<FeedGeneratorView> { const did = feedUri.split('/')[2]; const pds = await resolvePds(did); const url = `${pds}/xrpc/app.bsky.feed.getFeedGenerator?feed=${encodeURIComponent(feedUri)}`; const data = await fetchJson<{ view: FeedGeneratorView }>(url); return data.view;}
export interface FeedSkeletonItem { post: string; reason?: unknown; feedContext?: string;}
export async function getFeedSkeleton( feedUri: string, limit = 10,): Promise<FeedSkeletonItem[]> { try { const did = feedUri.split('/')[2]; const pds = await resolvePds(did); const url = `${pds}/xrpc/app.bsky.feed.getFeedSkeleton?feed=${encodeURIComponent(feedUri)}&limit=${limit}`; const data = await fetchJson<{ cursor?: string; feed: FeedSkeletonItem[] }>(url); return data.feed ?? []; } catch { return []; }}
export interface HydratedPost { uri: string; cid: string; author: { did: string; handle: string; displayName?: string; avatar?: string; }; record: { text?: string; createdAt?: string; embed?: PostEmbed; facets?: unknown[]; reply?: { root: { uri: string; cid: string }; parent: { uri: string; cid: string } }; }; embed?: unknown; likeCount?: number; repostCount?: number; replyCount?: number; indexedAt?: string;}
export async function getHydratedPosts(uris: string[]): Promise<HydratedPost[]> { if (uris.length === 0) return []; try { const byDid = new Map<string, string[]>(); for (const uri of uris) { const did = uri.split('/')[2]; if (!byDid.has(did)) byDid.set(did, []); byDid.get(did)!.push(uri); }
const allPosts = await Promise.all( Array.from(byDid.entries()).map(async ([did, didUris]) => { const pds = await resolvePds(did); const params = didUris.map((u) => `uris=${encodeURIComponent(u)}`).join('&'); const url = `${pds}/xrpc/app.bsky.feed.getPosts?${params}`; const data = await fetchJson<{ posts: HydratedPost[] }>(url); return data.posts ?? []; }), );
const lookup = new Map<string, HydratedPost>(); for (const posts of allPosts) { for (const post of posts) { lookup.set(post.uri, post); } }
return uris.map((uri) => lookup.get(uri)).filter((p): p is HydratedPost => p !== undefined); } catch { return []; }}
export interface PostRecord { text: string; createdAt: string; embed?: PostEmbed; facets?: unknown[]; reply?: { root: { uri: string; cid: string }; parent: { uri: string; cid: string } };}
export type PostEmbed = | { $type: 'app.bsky.embed.images'; images: PostEmbedImage[] } | { $type: 'app.bsky.embed.video'; video: { ref: { $link: string }; mimeType: string }; thumbnail?: { ref: { $link: string }; mimeType: string }; alt?: string; aspectRatio?: { width: number; height: number }; presentation?: 'default' | 'gif' | string } | { $type: 'app.bsky.embed.external'; external: { uri: string; title: string; description: string; thumb?: { ref: { $link: string }; mimeType: string } } } | { $type: 'app.bsky.embed.record'; record: { uri: string; cid: string } } | { $type: 'app.bsky.embed.recordWithMedia'; record: { uri: string; cid: string }; media: PostEmbed } | { $type: string };
export interface PostEmbedImage { image: { ref: { $link: string }; mimeType: string; size: number }; alt?: string; aspectRatio?: { width: number; height: number };}