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.

Handle handle changes, don't thank users for following unless streamer is live, misc. minor syntax tweaks

+47 -20
+19 -20
utils/eventHandler.ts
··· 50 50 break; 51 51 default: 52 52 } 53 + } // Handle handle changes 54 + else if (event.kind === "identity") { 55 + if (!didResolver.getUser(event.did)) return; 56 + const user = didResolver.getUser(event.did)!; 57 + const newHandle = event.identity.handle; 58 + user.handle = newHandle; 53 59 } 54 60 } 55 61 ··· 59 65 // Case: user follows streamer 60 66 if (botInstances.get(record.subject)) { 61 67 const follower = await didResolver.resolve(event.did); 68 + const bot = botInstances.get(record.subject)!; 69 + // Don't send chat message unless streamer is currently live 70 + if (!await bot.streamerIsLive()) return; 62 71 const { text, facets } = new RichtextBuilder().addText( 63 72 "Thank you for the following the streamer, ", 64 73 ).addMention(follower.handle, event.did).addText("!"); 65 - botInstances.get(record.subject)!.sendMessage(text, facets); 66 - } 67 - // Case user follows bot 68 - if ( 74 + bot.sendMessage(text, facets); 75 + } // Case user follows bot 76 + else if ( 69 77 record.subject === atprotoClient.getDid() && 70 78 !botInstances.get(event.did) 71 79 ) { ··· 73 81 await newBot.init(); 74 82 botInstances.set(event.did, newBot); 75 83 } 76 - } 77 - 78 - if (event.commit.operation === "delete") { 84 + } else if (event.commit.operation === "delete") { 79 85 // Case user unfollows bot 80 86 if ( 81 87 botInstances.get(event.did) ··· 99 105 const record = event.commit 100 106 .record as AppBskyActorProfile.Main; 101 107 profile.actorProfile = record; 102 - didResolver.setUser(event.did, profile); 103 108 } 104 109 // Case deleted profile 105 110 if (event.commit.operation === "delete") { 106 111 profile.actorProfile = undefined; 107 - didResolver.setUser(event.did, profile); 108 112 } 109 113 } 110 114 ··· 152 156 const record = event.commit 153 157 .record as PlaceStreamChatProfile.Main; 154 158 profile.chatProfile = record; 155 - didResolver.setUser(event.did, profile); 156 - } 157 - // Case deleted profile 158 - if (event.commit.operation === "delete") { 159 + } // Case deleted profile 160 + else if (event.commit.operation === "delete") { 159 161 profile.chatProfile = undefined; 160 - didResolver.setUser(event.did, profile); 161 162 } 162 163 } 163 164 ··· 210 211 record, 211 212 `at://${event.did}/${event.commit.collection}/${event.commit.rkey}`, 212 213 ); 213 - } 214 - // Case deleted command 215 - if (event.commit.operation === "delete") { 214 + } // Case deleted command 215 + else if (event.commit.operation === "delete") { 216 216 if (!botInstances.get(event.did)) return; 217 217 const bot = botInstances.get(event.did); 218 218 const trigger = bot!.getCommandHandler() ··· 239 239 if (event.commit.operation === "update") { 240 240 bot!.getGreeted().delete(record.user); 241 241 } 242 - } 243 - // Case deleted shoutout 244 - if (event.commit.operation === "delete") { 242 + } // Case deleted shoutout 243 + else if (event.commit.operation === "delete") { 245 244 if (!botInstances.get(event.did)) return; 246 245 const bot = botInstances.get(event.did); 247 246 const did = bot!.getShoutoutByAtUri(uri);
+28
utils/streamplaceBot.ts
··· 8 8 import { 9 9 OnlineTimtinkersBotShoutout, 10 10 PlaceStreamChatMessage, 11 + PlaceStreamLivestream, 11 12 PlaceStreamRichtextFacet, 12 13 } from "./lexicons/index.ts"; 13 14 import { buildRichtext } from "./richtextUtils.ts"; ··· 211 212 this.shoutouts.clear(); 212 213 this.shoutoutsByAtUri.clear(); 213 214 await this.loadShoutouts(); 215 + } 216 + 217 + // Check if streamer is live 218 + async streamerIsLive(): Promise<boolean> { 219 + const streamer = await didResolver.resolve(this.streamerDid); 220 + try { 221 + const { records } = await listRecords(streamer.pdsEndpoint, { 222 + repo: this.getStreamerDid(), 223 + collection: "place.stream.livestream", 224 + }); 225 + if (!records || records.length < 1) return false; 226 + const record = records[0].value as PlaceStreamLivestream.Main; 227 + if (!record.lastSeenAt) return false; 228 + const lastSeen = new Date(record.lastSeenAt).getTime(); 229 + const now = Date.now(); 230 + const idleTimeoutMs = 231 + record.idleTimeoutSeconds && record.idleTimeoutSeconds > 0 232 + ? record.idleTimeoutSeconds * 1000 233 + : 300 * 1000; 234 + return (now - lastSeen) <= idleTimeoutMs; 235 + } catch (error) { 236 + console.error( 237 + "Error checking if streamer is live:", 238 + error, 239 + ); 240 + } 241 + return false; 214 242 } 215 243 216 244 // Getters, Setters (alphabetically)