Retro Bulletin Board Systems on atproto. Web app and TUI. lazy mirror of alyraffauf/atbbs atbbs.xyz
forums python tui atproto bbs
3
fork

Configure Feed

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

at master 103 lines 3.0 kB view raw
1/** Load a sysop's bans + hides, hydrated with identities and post previews. */ 2 3import { getRecordByUri, listRecords, resolveIdentitiesBatch } from "./atproto"; 4import { BAN, HIDE } from "./lexicon"; 5import { parseAtUri } from "./util"; 6import { is } from "@atcute/lexicons/validations"; 7import { mainSchema as banSchema } from "../lexicons/types/xyz/atbbs/ban"; 8import { mainSchema as hideSchema } from "../lexicons/types/xyz/atbbs/hide"; 9import type { XyzAtbbsBan, XyzAtbbsHide } from "../lexicons"; 10 11export interface HiddenInfo { 12 uri: string; 13 handle: string; 14 title: string; 15 body: string; 16} 17 18export interface SysopModeration { 19 banRkeys: Record<string, string>; 20 bannedHandles: Record<string, string>; 21 hideRkeys: Record<string, string>; 22 hidden: HiddenInfo[]; 23} 24 25function buildRkeyMap<T>( 26 records: { uri: string; value: Record<string, unknown> }[], 27 schema: Parameters<typeof is>[0], 28 getKey: (value: T) => string, 29): Record<string, string> { 30 const map: Record<string, string> = {}; 31 for (const record of records) { 32 if (!is(schema, record.value)) continue; 33 map[getKey(record.value as unknown as T)] = parseAtUri(record.uri).rkey; 34 } 35 return map; 36} 37 38async function hydrateHiddenPosts(uris: string[]): Promise<HiddenInfo[]> { 39 if (uris.length === 0) return []; 40 41 const dids = [...new Set(uris.map((uri) => parseAtUri(uri).did))]; 42 43 const [identities, records] = await Promise.all([ 44 resolveIdentitiesBatch(dids), 45 Promise.allSettled(uris.map(getRecordByUri)), 46 ]); 47 48 return uris.map((uri, index) => { 49 const did = parseAtUri(uri).did; 50 const handle = identities[did]?.handle ?? did; 51 const result = records[index]; 52 if (result.status === "fulfilled") { 53 const value = result.value.value as unknown as { 54 title?: string; 55 body?: string; 56 }; 57 return { 58 uri, 59 handle, 60 title: value.title ?? "", 61 body: (value.body ?? "").substring(0, 100), 62 }; 63 } 64 return { uri, handle, title: "", body: uri }; 65 }); 66} 67 68export async function fetchSysopModeration( 69 pdsUrl: string, 70 did: string, 71): Promise<SysopModeration> { 72 const [banRecs, hideRecs] = await Promise.all([ 73 listRecords(pdsUrl, did, BAN), 74 listRecords(pdsUrl, did, HIDE), 75 ]); 76 77 const banRkeys = buildRkeyMap<XyzAtbbsBan.Main>( 78 banRecs, 79 banSchema, 80 (ban) => ban.did, 81 ); 82 const hideRkeys = buildRkeyMap<XyzAtbbsHide.Main>( 83 hideRecs, 84 hideSchema, 85 (hide) => hide.uri, 86 ); 87 88 const bannedDids = Object.keys(banRkeys); 89 let bannedHandles: Record<string, string> = {}; 90 if (bannedDids.length) { 91 try { 92 const authors = await resolveIdentitiesBatch(bannedDids); 93 for (const did of bannedDids) 94 bannedHandles[did] = authors[did]?.handle ?? did; 95 } catch { 96 for (const did of bannedDids) bannedHandles[did] = did; 97 } 98 } 99 100 const hidden = await hydrateHiddenPosts(Object.keys(hideRkeys)); 101 102 return { banRkeys, bannedHandles, hideRkeys, hidden }; 103}