this repo has no description
2
fork

Configure Feed

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

at master 132 lines 4.1 kB view raw
1import heading from "./pds-heading.txt" with { type: "text" }; 2import { 3 didDocSchema, 4 getRecordSchema, 5 listRecordsSchema, 6 listReposSchema, 7 profileSelfSchema, 8 statusphereSchema, 9 tealFmStatusSchema, 10} from "../schemas.ts"; 11import { stagger } from "../utils.ts"; 12 13const PDS = Deno.env.get("PDS") ?? "http://pi:8000"; 14const PLC_DIRECTORY = Deno.env.get("PLC_DIRECTORY") ?? "https://plc.directory"; 15 16async function renderUser(did: string): Promise<string> { 17 const handle = fetch( 18 did.startsWith("did:plc") 19 ? `${PLC_DIRECTORY}/${did}` 20 : `https://${did.replace("did:web:", "")}/.well-known/did.json`, 21 ) 22 .then((res) => res.json()) 23 .then((res) => didDocSchema.safeParse(res).data) 24 .then((doc) => 25 doc 26 ? (doc.alsoKnownAs 27 .filter((x: string) => x.startsWith("at://"))[0] 28 ?.replace("at://", "") ?? "handle.invalid") 29 : "handle.invalid", 30 ) 31 .catch(() => "handle.invalid"); 32 33 const displayName: Promise<string | undefined> = fetch( 34 `${PDS}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=app.bsky.actor.profile&rkey=self`, 35 ) 36 .then((res) => res.json()) 37 .then((data) => getRecordSchema(profileSelfSchema).safeParse(data).data) 38 .then((data) => (data ? data.value.displayName : undefined)) 39 .catch(() => undefined); 40 41 const statusphere: Promise<string | undefined> = fetch( 42 `${PDS}/xrpc/com.atproto.repo.listRecords?repo=${did}&collection=xyz.statusphere.status`, 43 ) 44 .then((res) => res.json()) 45 .then((data) => listRecordsSchema(statusphereSchema).safeParse(data).data) 46 .then((data) => 47 data && data.records.length > 0 48 ? data.records[0].value.status 49 : undefined, 50 ) 51 .catch(() => undefined); 52 53 const nowPlaying = fetch( 54 `${PDS}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=fm.teal.alpha.actor.status&rkey=self`, 55 ) 56 .then((res) => res.json()) 57 .then((data) => getRecordSchema(tealFmStatusSchema).safeParse(data).data) 58 .then((data) => 59 data 60 ? data.value.item.trackName + 61 ((data.value.item.artists.length > 0 62 ? ` - ${data.value.item.artists[0].artistName}` 63 : "") + 64 (data.value.item.artists.length > 1 65 ? `${data.value.item.artists.length - 1} more artist${data.value.item.artists.length > 1 ? "s" : ""}` 66 : "")) 67 : undefined, 68 ) 69 .catch(() => undefined); 70 71 return ( 72 "- " + 73 [ 74 (await statusphere) 75 ? `${await displayName} (${await statusphere})` 76 : await displayName, 77 78 `@${await handle}`, 79 `at://${did}`, 80 await nowPlaying, 81 ] 82 .filter((x) => x) 83 .join("\n ") 84 ); 85} 86 87export default function (): Response { 88 // get upstream pds status 89 const status = fetch(`${PDS}/xrpc/_health`) 90 .then(async (res) => `${res.status} ${res.statusText} ${await res.text()}`) 91 .catch((e) => 92 e instanceof Error 93 ? `ERR: ${e.name}\n\n${e.message}\n${e.cause}\n${e.stack}` 94 : `Err\n\n${e}`, 95 ); 96 97 const users = fetch(`${PDS}/xrpc/com.atproto.sync.listRepos`) 98 .then((res) => res.json()) 99 .catch(() => undefined) 100 .then((data) => listReposSchema.safeParse(data).data) 101 .then((data) => 102 data ? data.repos.filter((x) => x.active).map((x) => x.did) : undefined, 103 ) 104 .then(async (users) => 105 users 106 ? await Promise.all(users.map((did) => renderUser(did))).then((x) => 107 x.join("\n\n"), 108 ) 109 : undefined, 110 ); 111 112 const body = new ReadableStream({ 113 async start(controller: ReadableStreamDefaultController<string>) { 114 controller.enqueue(heading); 115 controller.enqueue(await status); 116 controller.enqueue("\n\n"); 117 controller.enqueue(await users); 118 controller.close(); 119 }, 120 }); 121 122 return new Response( 123 body.pipeThrough(stagger()).pipeThrough(new TextEncoderStream()), 124 { 125 headers: { 126 "Content-Type": "text/text; charset=utf-8", 127 "Access-Control-Allow-Origin": "*", 128 Link: "</styles.css>; rel=stylesheet", 129 }, 130 }, 131 ); 132}