tg-bot-ts/src/index.ts

103 lines
No EOL
3.6 KiB
TypeScript

import { Client, GatewayIntentBits, TextChannel, REST, Routes } from "discord.js";
import { Config } from "@systems/config";
import { postPoll, polls, lockPoll, updatePollMessage } from "@systems/poll";
import { handleInteraction } from "@handlers/interactions";
import { buildTgCommand } from "@commands/tg";
import { buildTgConfigCommand } from "@commands/tgConfig";
import { TGSlot } from "@src/types";
import { persist } from "@systems/pollPersistence"
import { buildTgAdminCommand } from "@commands/tgAdmin";
import { Scheduler } from "@systems/scheduler";
import { Runtime } from "@systems/runtime";
const TOKEN = process.env.DISCORD_TOKEN!;
const CLIENT_ID = process.env.CLIENT_ID!;
const GUILD_ID = process.env.GUILD_ID!;
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
});
async function registerCommands(): Promise<void> {
const rest = new REST({ version: "10" }).setToken(TOKEN);
await rest.put(Routes.applicationGuildCommands(CLIENT_ID, GUILD_ID), {
body: [
buildTgCommand().toJSON(),
buildTgConfigCommand().toJSON(),
buildTgAdminCommand().toJSON(),
],
});
console.log("Slash commands registered.");
}
async function onPollOpen(slot: TGSlot): Promise<void> {
const channelId = Config.get({ section: "channels", key: "poll" });
const channel = await client.channels.fetch(channelId) as any;
if (!channel) return console.error("Poll channel not found.");
await postPoll(channel, slot);
}
// Fires at tgHour exactly (e.g. 20:00) — voting closes, lockedYesKeys snapshotted
async function onPollLock(slot: TGSlot): Promise<void> {
const state = polls.get(slot.tgHour);
if (!state || state.locked) return;
lockPoll(slot.tgHour);
const channelId = Config.get({ section: "channels", key: "poll" });
const channel = await client.channels.fetch(channelId) as any;
if (!channel) return;
// Buttons disabled, no submit button yet — that comes at close
await updatePollMessage(channel, slot.tgHour);
console.log(`[${new Date().toISOString()}] Poll locked for ${slot.tgHour}:00.`);
}
// Fires at tgHour + closesAfter (e.g. 20:35) — TG ended, reveal Submit Score
async function onPollClose(slot: TGSlot): Promise<void> {
const state = polls.get(slot.tgHour);
if (!state) return;
const channelId = Config.get({ section: "channels", key: "poll" });
const channel = await client.channels.fetch(channelId) as any;
if (!channel) return;
await updatePollMessage(channel, slot.tgHour, undefined, true); // showSubmit = true
console.log(`[${new Date().toISOString()}] Poll closed for ${slot.tgHour}:00.`);
}
client.on("interactionCreate", handleInteraction);
client.once("clientReady", async () => {
console.log(`Logged in as ${client.user!.tag}`);
await Runtime.start();
const restored = persist.load();
if (restored) {
for (const [slot, state] of restored) polls.set(slot, state);
// Re-render all restored poll messages
const channelId = Config.get({ section: "channels", key: "poll" });
const channel = await client.channels.fetch(channelId) as any;
for (const slot of polls.keys()) {
const state = polls.get(slot)!;
await updatePollMessage(channel, slot, undefined, state.locked && state.confirmed === null);
}
console.log("Poll state restored and messages re-rendered.");
}
const guild = await client.guilds.fetch(GUILD_ID);
await guild.members.fetch();
console.log(`Member cache warmed: ${guild.members.cache.size} members`);
if (process.argv.includes("--register")) {
await registerCommands();
}
Scheduler.schedule(client, onPollOpen, onPollLock, onPollClose);
console.log("Bot ready.");
});
client.login(TOKEN);