this repo has no description
2
fork

Configure Feed

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

misc tweaks to knot/pds landers to make more managable code

+112 -93
+90 -78
landing/src/knot/knot.ts
··· 17 17 owner: string, 18 18 pds: string, 19 19 collection: string, 20 - validator: z.ZodType, 21 - cursor?: string 20 + validator: z.ZodType<T>, 21 + cursor?: string, 22 22 ): Promise<{ uri: string; cid: string; value: T }[]> { 23 23 return fetch( 24 - `${pds}/xrpc/com.atproto.repo.listRecords?repo=${owner}&collection=${collection}&cursor=${cursor}` 24 + `${pds}/xrpc/com.atproto.repo.listRecords?repo=${owner}&collection=${collection}&cursor=${cursor}`, 25 25 ) 26 26 .then((res) => res.json()) 27 - 28 - .then( 29 - (res) => 30 - // check that it matches the schema and assume that the schema matches T 31 - listRecordsSchema(validator).safeParse(res).data as { 32 - records: { 33 - uri: string; 34 - cid: string; 35 - value: T; 36 - }[]; 37 - cursor?: string; 38 - } 39 - ) 40 - .then(async (res) => [ 41 - ...res.records, 42 - ...(res.cursor 43 - ? await getAllRecords<T>(owner, pds, collection, validator, res.cursor) 44 - : []), 45 - ]); 27 + .then((res) => listRecordsSchema(validator).safeParse(res).data) 28 + .then(async (res) => 29 + res 30 + ? [ 31 + ...res.records, 32 + ...(res?.cursor 33 + ? await getAllRecords<T>( 34 + owner, 35 + pds, 36 + collection, 37 + validator, 38 + res.cursor, 39 + ) 40 + : []), 41 + ] 42 + : [], 43 + ); 46 44 } 47 45 48 46 export default function (): Response { 49 47 // get upstream knot owner/status 50 48 const owner = fetch(`${KNOT_HOST}/xrpc/sh.tangled.owner`); 51 49 const status = owner.then( 52 - async (res) => `${res.status} ${res.statusText} ${await res.clone().text()}` 50 + async (res) => 51 + `${res.status} ${res.statusText} ${await res.clone().text()}`, 53 52 ); 54 53 55 54 const ownerDid: Promise<string | undefined> = owner ··· 60 59 const ownerPds = ownerDid 61 60 .then((owner) => 62 61 fetch( 63 - `${SLINGSHOT_INSTANCE}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${owner}` 64 - ) 62 + `${SLINGSHOT_INSTANCE}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${owner}`, 63 + ), 65 64 ) 66 65 .then((res) => res.json()) 67 66 .then((x) => slingshotMiniDocSchema.safeParse(x).data?.pds); 68 67 69 - const members = ownerPds 70 - .then((pds) => 71 - ownerDid.then(async (owner) => [ 72 - owner, 73 - ...(await getAllRecords<{ subject: string; domain: string }>( 74 - owner, 75 - pds, 76 - "sh.tangled.knot.member", 77 - z.object({ 78 - subject: z.string(), 79 - domain: z.string(), 80 - }) 81 - ).then((x) => 82 - x 83 - .filter((x) => x.value.domain === KNOT_NAME) 84 - .map((x) => x.value.subject) 85 - )), 86 - ]) 87 - ) 88 - .then((dids) => 89 - Promise.all( 90 - dids.map((did) => 91 - fetch( 92 - `${SLINGSHOT_INSTANCE}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${did}` 68 + const memberDids = Promise.all([ownerDid, ownerPds]).then( 69 + async ([owner, pds]) => [ 70 + owner, 71 + ...(owner && pds 72 + ? await getAllRecords( 73 + owner, 74 + pds, 75 + "sh.tangled.knot.member", 76 + // prettier-ignore 77 + z.object({ subject: z.string(), domain: z.string(), }), 93 78 ) 94 - .then((res) => res.json()) 95 - .then((res) => slingshotMiniDocSchema.safeParse(res).data) 96 - ) 97 - ) 98 - ); 79 + // ensure only members of the current knot are included 80 + .then((member) => 81 + member 82 + .filter((record) => record.value.domain === KNOT_NAME) 83 + .map((record) => record.value.subject), 84 + ) 85 + : []), 86 + ], 87 + ); 99 88 100 - const membersRepr = members.then( 101 - (x) => 102 - " - " + 103 - x 104 - .map((x) => (x.handle !== "handle.invalid" ? x.handle : x.did)) 105 - .join("\n - ") 89 + const members = memberDids 90 + .then((dids) => 91 + dids.map((did) => 92 + // prettier-ignore 93 + fetch(`${SLINGSHOT_INSTANCE}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${did}`) 94 + .then((res) => res.json()) 95 + .then((res) => slingshotMiniDocSchema.safeParse(res).data), 96 + ), 97 + ) 98 + .then((dids) => Promise.all(dids)) 99 + .then((dids) => dids.filter((x) => x !== undefined)); 100 + 101 + const membersRepr = members.then((members) => 102 + members 103 + .map((x) => ` - ${x.handle !== "handle.invalid" ? x.handle : x.did}`) 104 + .join("\n"), 106 105 ); 107 106 108 107 const repos = members.then((members) => 109 108 Promise.all( 110 109 members.map((member) => 111 - getAllRecords<z.infer<typeof tangledRepoSchema>>( 110 + getAllRecords( 112 111 member.did, 113 112 member.pds, 114 113 "sh.tangled.repo", 115 - tangledRepoSchema 114 + tangledRepoSchema, 116 115 ).then((x) => 117 116 x.map((x) => ({ 118 117 ...x.value, 119 118 user: 120 119 member.handle !== "handle.invalid" ? member.handle : member.did, 121 - })) 122 - ) 123 - ) 124 - ).then((x) => x.flat().filter((x) => x.knot === KNOT_NAME)) 120 + })), 121 + ), 122 + ), 123 + ).then((x) => x.flat().filter((x) => x.knot === KNOT_NAME)), 125 124 ); 126 125 127 - const reposRepr = repos.then( 128 - (x) => 129 - " - " + 130 - x 131 - .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) 132 - .map( 133 - (x) => 134 - `${x.user}/${x.name}${x.description ? `\n ${x.description}` : ""}${x.website ? `\n ${x.website}` : ""}` 135 - ) 136 - .join("\n - ") + 137 - "\n" 126 + // const reposRepr = repos.then( 127 + // (x) => 128 + // " - " + 129 + // x 130 + // .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) 131 + // .map( 132 + // (x) => 133 + // `${x.user}/${x.name}${x.description ? `\n ${x.description}` : ""}${x.website ? `\n ${x.website}` : ""}`, 134 + // ) 135 + // .join("\n - ") + 136 + // "\n", 137 + // ); 138 + const reposRepr = repos.then((repo) => 139 + repo 140 + .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) 141 + .map((repo) => 142 + [ 143 + " - ", 144 + `${repo.user}/${repo.name}`, 145 + repo.description ? `\n ${repo.description}` : "", 146 + repo.website ? `\n ${repo.website}` : "", 147 + ].join(""), 148 + ) 149 + .join("\n\n"), 138 150 ); 139 151 140 152 const body = new ReadableStream({ ··· 157 169 "Access-Control-Allow-Origin": "*", 158 170 Link: "</styles.css>; rel=stylesheet", 159 171 }, 160 - } 172 + }, 161 173 ); 162 174 }
+22 -15
landing/src/pds/pds.ts
··· 17 17 const handle = fetch( 18 18 did.startsWith("did:plc") 19 19 ? `${PLC_DIRECTORY}/${did}` 20 - : `https://${did.replace("did:web:", "")}/.well-known/did.json` 20 + : `https://${did.replace("did:web:", "")}/.well-known/did.json`, 21 21 ) 22 22 .then((res) => res.json()) 23 23 .then((res) => didDocSchema.safeParse(res).data) 24 24 .then((doc) => 25 25 doc 26 26 ? (doc.alsoKnownAs 27 - .filter((x) => x.startsWith("at://"))[0] 27 + .filter((x: string) => x.startsWith("at://"))[0] 28 28 ?.replace("at://", "") ?? "handle.invalid") 29 - : "handle.invalid" 29 + : "handle.invalid", 30 30 ) 31 31 .catch(() => "handle.invalid"); 32 32 33 33 const displayName: Promise<string | undefined> = fetch( 34 - `${PDS}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=app.bsky.actor.profile&rkey=self` 34 + `${PDS}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=app.bsky.actor.profile&rkey=self`, 35 35 ) 36 36 .then((res) => res.json()) 37 37 .then((data) => getRecordSchema(profileSelfSchema).safeParse(data).data) ··· 39 39 .catch(() => undefined); 40 40 41 41 const statusphere: Promise<string | undefined> = fetch( 42 - `${PDS}/xrpc/com.atproto.repo.listRecords?repo=${did}&collection=xyz.statusphere.status` 42 + `${PDS}/xrpc/com.atproto.repo.listRecords?repo=${did}&collection=xyz.statusphere.status`, 43 43 ) 44 44 .then((res) => res.json()) 45 45 .then((data) => listRecordsSchema(statusphereSchema).safeParse(data).data) 46 46 .then((data) => 47 - data && data.records.length > 0 ? data.records[0].value.status : undefined 47 + data && data.records.length > 0 48 + ? data.records[0].value.status 49 + : undefined, 48 50 ) 49 51 .catch(() => undefined); 50 52 51 53 const nowPlaying = fetch( 52 - `${PDS}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=fm.teal.alpha.actor.status&rkey=self` 54 + `${PDS}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=fm.teal.alpha.actor.status&rkey=self`, 53 55 ) 54 56 .then((res) => res.json()) 55 57 .then((data) => getRecordSchema(tealFmStatusSchema).safeParse(data).data) ··· 62 64 (data.value.item.artists.length > 1 63 65 ? `${data.value.item.artists.length - 1} more artist${data.value.item.artists.length > 1 ? "s" : ""}` 64 66 : "")) 65 - : undefined 67 + : undefined, 66 68 ) 67 69 .catch(() => undefined); 68 70 ··· 84 86 85 87 export default function (): Response { 86 88 // get upstream pds status 87 - const status = fetch(`${PDS}/xrpc/_health`).then( 88 - async (res) => `${res.status} ${res.statusText} ${await res.text()}` 89 - ); 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 + ); 90 96 91 97 const users = fetch(`${PDS}/xrpc/com.atproto.sync.listRepos`) 92 98 .then((res) => res.json()) 99 + .catch(() => undefined) 93 100 .then((data) => listReposSchema.safeParse(data).data) 94 101 .then((data) => 95 - data ? data.repos.filter((x) => x.active).map((x) => x.did) : undefined 102 + data ? data.repos.filter((x) => x.active).map((x) => x.did) : undefined, 96 103 ) 97 104 .then(async (users) => 98 105 users 99 106 ? await Promise.all(users.map((did) => renderUser(did))).then((x) => 100 - x.join("\n\n") 107 + x.join("\n\n"), 101 108 ) 102 - : undefined 109 + : undefined, 103 110 ); 104 111 105 112 const body = new ReadableStream({ ··· 120 127 "Access-Control-Allow-Origin": "*", 121 128 Link: "</styles.css>; rel=stylesheet", 122 129 }, 123 - } 130 + }, 124 131 ); 125 132 }