send on transitions

This commit is contained in:
Theo Browne
2026-02-22 21:50:01 -08:00
parent 79f9dab7fb
commit ccaa86b4a6
2 changed files with 78 additions and 3 deletions

View File

@@ -272,7 +272,7 @@ export async function runGame(
runs: number, runs: number,
state: GameState, state: GameState,
rerender: () => void, rerender: () => void,
onViewerVotingStart?: () => void, onViewerVotingStart?: (round: RoundState) => void,
) { ) {
let startRound = 1; let startRound = 1;
const lastCompletedRound = state.completed.at(-1); const lastCompletedRound = state.completed.at(-1);
@@ -403,7 +403,7 @@ export async function runGame(
round.viewerVotesA = 0; round.viewerVotesA = 0;
round.viewerVotesB = 0; round.viewerVotesB = 0;
round.viewerVotingEndsAt = Date.now() + 30_000; round.viewerVotingEndsAt = Date.now() + 30_000;
onViewerVotingStart?.(); onViewerVotingStart?.(round);
rerender(); rerender();
await Promise.all([ await Promise.all([

View File

@@ -105,10 +105,18 @@ const FOSSABOT_CHANNEL_LOGIN = (
process.env.FOSSABOT_CHANNEL_LOGIN ?? "quipslop" process.env.FOSSABOT_CHANNEL_LOGIN ?? "quipslop"
).trim().toLowerCase(); ).trim().toLowerCase();
const FOSSABOT_VOTE_SECRET = process.env.FOSSABOT_VOTE_SECRET ?? ""; const FOSSABOT_VOTE_SECRET = process.env.FOSSABOT_VOTE_SECRET ?? "";
const FOSSABOT_CHAT_CHANNEL_ID = (
process.env.FOSSABOT_CHAT_CHANNEL_ID ?? "813591620327550976"
).trim();
const FOSSABOT_SESSION_TOKEN = (process.env.FOSSABOT_SESSION_TOKEN ?? "").trim();
const FOSSABOT_VALIDATE_TIMEOUT_MS = parsePositiveInt( const FOSSABOT_VALIDATE_TIMEOUT_MS = parsePositiveInt(
process.env.FOSSABOT_VALIDATE_TIMEOUT_MS, process.env.FOSSABOT_VALIDATE_TIMEOUT_MS,
1_500, 1_500,
); );
const FOSSABOT_SEND_CHAT_TIMEOUT_MS = parsePositiveInt(
process.env.FOSSABOT_SEND_CHAT_TIMEOUT_MS,
3_000,
);
const VIEWER_VOTE_BROADCAST_DEBOUNCE_MS = parsePositiveInt( const VIEWER_VOTE_BROADCAST_DEBOUNCE_MS = parsePositiveInt(
process.env.VIEWER_VOTE_BROADCAST_DEBOUNCE_MS, process.env.VIEWER_VOTE_BROADCAST_DEBOUNCE_MS,
250, 250,
@@ -315,6 +323,69 @@ async function validateFossabotRequest(validateUrl: string): Promise<boolean> {
} }
} }
async function sendFossabotChatMessage(messageText: string): Promise<void> {
if (!FOSSABOT_SESSION_TOKEN) {
log(
"WARN",
"fossabot:chat",
"Skipped chat message because FOSSABOT_SESSION_TOKEN is not configured",
);
return;
}
if (!FOSSABOT_CHAT_CHANNEL_ID) {
log(
"WARN",
"fossabot:chat",
"Skipped chat message because FOSSABOT_CHAT_CHANNEL_ID is not configured",
);
return;
}
const controller = new AbortController();
const timeout = setTimeout(
() => controller.abort(),
FOSSABOT_SEND_CHAT_TIMEOUT_MS,
);
try {
const url = `https://api.fossabot.com/v2/channels/${FOSSABOT_CHAT_CHANNEL_ID}/bot/send_chat_message`;
const res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${FOSSABOT_SESSION_TOKEN}`,
},
body: JSON.stringify({ messageText }),
signal: controller.signal,
});
if (!res.ok) {
const body = await res.text().catch(() => "");
log("WARN", "fossabot:chat", "Fossabot send_chat_message failed", {
status: res.status,
body: body.slice(0, 250),
});
return;
}
const response = (await res.json().catch(() => null)) as
| { transactionId?: unknown }
| null;
log("INFO", "fossabot:chat", "Sent voting prompt to Twitch chat", {
transactionId:
response && typeof response.transactionId === "string"
? response.transactionId
: undefined,
});
} catch (error) {
log("WARN", "fossabot:chat", "Failed to send chat message", {
error: error instanceof Error ? error.message : String(error),
});
} finally {
clearTimeout(timeout);
}
}
function applyViewerVote(voterId: string, side: ViewerVoteSide): boolean { function applyViewerVote(voterId: string, side: ViewerVoteSide): boolean {
const round = gameState.active; const round = gameState.active;
if (!round || round.phase !== "voting") return false; if (!round || round.phase !== "voting") return false;
@@ -857,8 +928,12 @@ log("INFO", "server", `Web server started on port ${server.port}`, {
// ── Start game ────────────────────────────────────────────────────────────── // ── Start game ──────────────────────────────────────────────────────────────
runGame(runs, gameState, broadcast, () => { runGame(runs, gameState, broadcast, (round) => {
viewerVoters.clear(); viewerVoters.clear();
const [modelA, modelB] = round.contestants;
const messageText = `1 in chat for ${modelA.name}, 2 in chat for ${modelB.name}`;
void sendFossabotChatMessage(messageText);
}).then(() => { }).then(() => {
console.log(`\n✅ Game complete! Log: ${LOG_FILE}`); console.log(`\n✅ Game complete! Log: ${LOG_FILE}`);
}); });