Something went wrong. Try again.
A work-in-progress chat bot for Streamplace with chat overlay functionality
Something went wrong. Try again.
4.1 kB · 152 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153import { is, ResourceUri } from "@atcute/lexicons";import { OnlineTimtinkersBotRecurring } from "./lexicons/index.ts";import StreamplaceBot, { IndexedCollection } from "./streamplaceBot.ts";import { MessageCreateEvent } from "./commandHandler.ts";
interface ActiveTimer { trigger: string; intervalSeconds: number; minMessages: number; intervalId: ReturnType<typeof setInterval>; messagesSinceFire: number;}
export class RecurringCommandHandler { private bot: StreamplaceBot; // Keyed by URI, indexed by trigger string. private recurring = new IndexedCollection<string, ActiveTimer>( (t) => t.trigger, ); private loaded = false;
constructor(bot: StreamplaceBot) { this.bot = bot; }
init(): void { if (this.loaded) return; // Recurring records are populated by the repo walk in StreamplaceBot // before init() is called, via ingestRecord. this.loaded = true; console.log(`Loaded ${this.recurring.size} recurring commands`); }
/** * Called by the repo walk for each `online.timtinkers.bot.recurring` record. * Delegates to handleUpsert so startup and live-update paths stay in sync. */ ingestRecord( uri: ResourceUri, record: OnlineTimtinkersBotRecurring.Main, ): void { this.handleUpsert(uri, record); }
private scheduleTimer( uri: ResourceUri, record: OnlineTimtinkersBotRecurring.Main, ): void { // Clear any existing timer for this URI first (handles updates) this.clearTimer(uri);
const trigger = record.trigger.toLowerCase(); const intervalMs = record.intervalSeconds ? record.intervalSeconds * 1000 : 60 * 1000;
const intervalId = setInterval(async () => { const timer = this.recurring.getByUri(uri); if (!timer) return; if (!this.bot.streamerIsLive()) return;
// Enforce minimum message threshold if (timer.messagesSinceFire < timer.minMessages) return;
const command = this.bot.getCommandHandler().getCommand(trigger); if (!command) return;
// Recurring commands bypass permission/cooldown gates const syntheticEvent = this.makeSyntheticEvent(); try { await command.execute(syntheticEvent, [], this.bot); timer.messagesSinceFire = 0; } catch (error) { console.error( `Error firing recurring command "${trigger}":`, error, ); } }, intervalMs);
this.recurring.set(uri, { trigger, intervalSeconds: record.intervalSeconds || 60, minMessages: record.minMessages || 0, intervalId, messagesSinceFire: 0, }); }
private clearTimer(uri: ResourceUri): void { const existing = this.recurring.delete(uri); if (existing) clearInterval(existing.intervalId); }
// Called by StreamplaceBot.processMessage on every incoming message incrementMessageCount(): void { for (const timer of this.recurring.values()) { timer.messagesSinceFire++; } }
// Called from EventHandler for live create/update handleUpsert( uri: ResourceUri, record: OnlineTimtinkersBotRecurring.Main, ): void { if (!is(OnlineTimtinkersBotRecurring.mainSchema, record)) return; this.scheduleTimer(uri, record); console.log( `Recurring command "${record.trigger}" scheduled (${record.intervalSeconds}s, min ${record.minMessages} msgs)`, ); }
// Called from EventHandler for live delete handleDelete(uri: ResourceUri): void { const timer = this.recurring.delete(uri); if (timer) { clearInterval(timer.intervalId); console.log(`Recurring command "${timer.trigger}" removed`); } }
destroy(): void { for (const timer of this.recurring.values()) { clearInterval(timer.intervalId); } this.recurring.clear(); }
// Recurring commands fire from the bot itself — no real chatter event exists. // Commands that reference event.did will get the bot's own DID here. private makeSyntheticEvent(): MessageCreateEvent { return { did: this.bot.getBotDid()!, time_us: 0, kind: "commit", commit: { rev: "", operation: "create", collection: "place.stream.chat.message", rkey: "", record: { $type: "place.stream.chat.message", text: "", streamer: this.bot.getStreamerDid(), createdAt: "", }, cid: "", }, }; }}