A work-in-progress chat bot for Streamplace with chat overlay functionality
2
fork

Configure Feed

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

Backlinks fetching, !linkat command, less optimistic error handling on getRecord

+85 -4
+10 -4
utils/atcuteUtils.ts
··· 202 202 }), 203 203 }); 204 204 205 - const record = await ok( 206 - rpc.get("com.atproto.repo.getRecord", { params: params }), 207 - ); 205 + const record = await rpc.get("com.atproto.repo.getRecord", { 206 + params: params, 207 + }); 208 + if (!record.ok) { 209 + console.log( 210 + `No record of collection ${params.collection} and rkey ${params.rkey} found in ${params.repo}.`, 211 + ); 212 + return undefined; 213 + } 208 214 209 - return record; 215 + return record.data; 210 216 } 211 217 212 218 // com.atproto.repo.listRecords generic wrapper
+39
utils/constellationUtils.ts
··· 1 + // Microcosm backlinks 2 + interface ConstellationResponse { 3 + total: number; 4 + linking_records: Array<{ did: Did; collection: string; rkey: string }>; 5 + cursor: string | null; 6 + } 7 + 8 + export async function getBacklinks( 9 + target: Did, 10 + collection: string, 11 + path: string = ".subject", 12 + limit: number = 100, 13 + cursor?: string, 14 + ): Promise<ConstellationResponse> { 15 + const url = new URL("https://constellation.microcosm.blue/links"); 16 + 17 + url.searchParams.set("target", target); 18 + url.searchParams.set("collection", collection); 19 + url.searchParams.set("path", path); 20 + url.searchParams.set("limit", limit.toString()); 21 + if (cursor) { 22 + url.searchParams.set("cursor", cursor); 23 + } 24 + 25 + try { 26 + const response = await fetch(url.toString()); 27 + 28 + if (!response.ok) { 29 + throw new Error( 30 + `API request failed: ${response.status} ${response.statusText}`, 31 + ); 32 + } 33 + 34 + return await response.json() as ConstellationResponse; 35 + } catch (error) { 36 + console.error("Error fetching backlinks:", error); 37 + throw error; 38 + } 39 + }
+36
utils/streamplaceBot.ts
··· 2 2 import { 3 3 AtprotoClient, 4 4 AtprotoClientConfig, 5 + getRecord, 5 6 listRecords, 6 7 } from "./atcuteUtils.ts"; 7 8 import { didResolver } from "./didResolver.ts"; ··· 260 261 .addMention(`@${huggeeChatter.handle}`, huggeeDid) 261 262 .addText(" a big hug!"); 262 263 264 + await this.sendMessage(text, facets); 265 + }); 266 + 267 + this.registerCommand("linkat", async (_message, _args) => { 268 + const streamer = await this.getUserProfile(this.streamerDid); 269 + const linkat = await getRecord(streamer.pdsEndpoint, { 270 + repo: this.streamerDid, 271 + collection: "blue.linkat.board", 272 + rkey: "self", 273 + }); 274 + if (!linkat) { 275 + const { text, facets } = new RichtextBuilder() 276 + .addText( 277 + "This user has no Linkat board. You can create one at ", 278 + ) 279 + .addLink("https://linkat.blue/", "https://linkat.blue/") 280 + .addText("!"); 281 + 282 + await this.sendMessage(text, facets); 283 + return; 284 + } 285 + 286 + const links = linkat.value.cards as Array< 287 + { url: `${string}:${string}`; text?: string; emoji?: string } 288 + >; 289 + 290 + const richtextBuilder = new RichtextBuilder(); 291 + links.forEach((link, index) => { 292 + richtextBuilder.addLink(link.text || link.url, link.url); 293 + if (index < links.length - 1) { 294 + richtextBuilder.addText(" | "); 295 + } 296 + }); 297 + 298 + const { text, facets } = richtextBuilder; 263 299 await this.sendMessage(text, facets); 264 300 }); 265 301 }