flybrain/services/bridge/src/chat.ts
acamilo 660c3cf00d
Some checks failed
ci / node 22 (test + typecheck) (push) Has been cancelled
ci / rust stable (cargo test --workspace --release) (push) Has been cancelled
ci / infra/tests/lint.sh (push) Has been cancelled
ci / playwright apps/stage (allowed to fail) (push) Has been cancelled
flybrain v0.4.0: public tree (history retained privately)
2026-09-21 15:09:46 +00:00

43 lines
1.7 KiB
TypeScript

/**
* Chat sending: the `ChatSender` interface (so tests never import twurple), the template-only
* `send()` gate, and the real Twurple-backed implementation.
*
* Each bridge deployment targets exactly one channel (`BridgeConfig.channel`), so `ChatSender`
* takes only the message text — there is nothing else to route to.
*/
import type { ApiClient } from '@twurple/api';
import { renderTemplate, type TemplateId, type TemplateParams } from './templates';
export interface ChatSender {
sendMessage(text: string): Promise<void>;
}
/** The only function allowed to put a rendered template on the wire. See `src/templates.ts`. */
export type Send = <T extends TemplateId>(id: T, params: TemplateParams<T>) => Promise<void>;
/** Build a `send()` bound to a `ChatSender`. Every command/event handler gets one of these. */
export function createSend(sender: ChatSender): Send {
return async function send<T extends TemplateId>(id: T, params: TemplateParams<T>): Promise<void> {
const text = renderTemplate(id, params);
await sender.sendMessage(text);
};
}
/**
* Sends chat messages as the bot account, in the broadcaster's channel, using Twurple's
* `asUser` context override (`docs/design/stage-bridge.md` B1: the bot token carries
* `user:write:chat` + `user:bot`, the broadcaster authorizes it with `channel:bot`).
*/
export class TwurpleChatSender implements ChatSender {
constructor(
private readonly apiClient: ApiClient,
private readonly botUserId: string,
private readonly broadcasterUserId: string,
) {}
async sendMessage(text: string): Promise<void> {
await this.apiClient.asUser(this.botUserId, async (ctx) => {
await ctx.chat.sendChatMessage(this.broadcasterUserId, text);
});
}
}