feat(twitch): wire tmi.js IRC bot and connect to court API #87
Reference in New Issue
Block a user
Delete Branch "feat/twitch-bot"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
commands.tsparser intoTwitchBot.parseCommand(was a stub returning null) — addssrc/twitch/bot.test.tswith 5 unit testsconnectIRC()using tmi.js: createstmi.Client, listens to chat messages, resolves active session ID per-message, and callsforwardCommandforwardCommand()private method routing!press→/press,!present→/present,!vote/!sentence→/votewith correct body shapesserver.tsstartup (noop when env vars absent, logs "Twitch bot disabled: missing credentials")src/twitch/tmi.d.tsminimal type declarations (tmi.js 1.8.5 ships no TS types)forwardCommandpath/body correctness using a mockedglobalThis.fetchTest Plan
npm test— 179 pass, 0 failnpm run lintpasses!press 2in chat →POST /api/court/sessions/:id/presspressVotesupdates after!pressCloses #77
🤖 Generated with Claude Code
Pull request overview
This PR adds a Twitch chat bot integration that parses
!press/!present/!vote/!sentencecommands and forwards them to the court API, and wires bot startup into the server lifecycle.Changes:
tmi.jsTypeScript declaration file and implemented real IRC connectivity viatmi.js.TwitchBot.parseCommand, added unit tests for parsing and API routing.getActiveSessionId()resolver.Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 5 comments.
tmi.jsusage in the bot.fetch.Comments suppressed due to low confidence (1)
src/server.ts:1057
createServerAppintroduces process-wide side effects for unit/integration tests (many tests callcreateServerApp), and the returneddispose()currently does not stop/disconnect the bot. This can lead to unexpected outbound Twitch connections on developer machines with Twitch env vars set and can leak resources across tests. Consider moving bot startup tobootstrap()(afterlisten()), or gating it behind an explicit flag, and make suredispose()stops/destroys the global bot instance.💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
@@ -1007,6 +1008,34 @@ export async function createServerApp(replay,getActiveSessionIdcallsstore.listSessions()on every chat message. In the Postgres-backed store this loads all sessions and then fetches turns for each session, which is expensive and can become a bottleneck under active chat. Consider adding a cheaper way to resolve the running session ID (e.g., a targeted query/filter in the store, caching the running session ID on phase/session_started events, or an in-memory pointer updated by the orchestrator).createServerAppcallstwitchBot.start(), butTwitchBot.start()currently returns immediately because it checksif (!this.config || !this.isActive) return;andisActivestarts asfalse. As a result the bot never connects even when credentials are present. Fix the start guard (should start when not already active) and ensureisActiveis set appropriately during startup.@@ -0,0 +88,4 @@else process.env.TWITCH_CLIENT_SECRET = origClientSecret;globalThis.fetch = originalFetch;});This test overwrites
process.env.TWITCH_*variables and then deletes them inafter(). If the developer (or another test file) had these env vars set beforehand, they will be lost for the remainder of the test process. Save the previous values inbefore()and restore them inafter()instead of unconditional deletion.tmi.jsidentityusernameis set tothis.config.channel. Twitch IRC authentication typically requires the bot account's username (which may differ from the channel being joined). Consider adding a separatebotUsername/identityUsernameconfig field (env var) and use that here to avoid login failures when the bot is a different account.The IRC
messagehandler ignores theselfargument from tmi.js and will process the bot's own messages if it ever sends chat output, which can create feedback loops. Also, the handler isasyncwithout a surrounding try/catch; ifgetActiveSessionId()throws (or any awaited call rejects outsideforwardCommand's fetch try/catch), this can become an unhandled rejection. Include theselfparameter and return early whenself === true, and wrap the handler body in a try/catch with logging.@copilot open a new pull request to apply changes based on the comments in this thread
@PatrickFanella I've opened a new pull request, #89, to work on those changes. Once the pull request is ready, I'll request review from you.