111 lines
3.9 KiB
TypeScript
111 lines
3.9 KiB
TypeScript
import { Client, Events, GatewayIntentBits, MessageFlags } from "discord.js";
|
|
import { getPayload } from "payload";
|
|
import config from "@payload-config";
|
|
import { botConfig } from "./config";
|
|
import { commands } from "./commands";
|
|
import { services } from "./services";
|
|
import { handleButtonInteraction } from "./events/interactionCreate";
|
|
import { handleTransferInteraction } from "./events/transferInteractions";
|
|
|
|
// Usage: `bun run bot`. Standalone long-running process; connects to Discord
|
|
// and shares the Postgres database with the web app via the Payload config.
|
|
const main = async () => {
|
|
const payload = await getPayload({ config });
|
|
|
|
const client = new Client({
|
|
intents: [GatewayIntentBits.Guilds],
|
|
});
|
|
|
|
let shuttingDown = false;
|
|
const shutdown = async (signal: NodeJS.Signals): Promise<void> => {
|
|
if (shuttingDown) {
|
|
payload.logger.warn(`[Bot] ${signal} received again, forcing exit`);
|
|
process.exit(143);
|
|
}
|
|
shuttingDown = true;
|
|
payload.logger.info(`[Bot] ${signal} received, shutting down gracefully`);
|
|
try {
|
|
if (client.isReady()) {
|
|
await client.destroy();
|
|
}
|
|
payload.logger.info("[Bot] Discord client destroyed");
|
|
process.exit(0);
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
payload.logger.error(`[Bot] Error during shutdown: ${message}`);
|
|
process.exit(1);
|
|
}
|
|
};
|
|
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
|
|
const commandExecutors = new Map(
|
|
commands.map((command) => [command.builder.name, command.execute]),
|
|
);
|
|
|
|
client.once(Events.ClientReady, async (readyClient) => {
|
|
payload.logger.info(`[Bot] Logged in as ${readyClient.user.tag}`);
|
|
const globalCommands = commands.filter((command) => command.scope === "global");
|
|
const guildCommands = commands.filter((command) => command.scope !== "global");
|
|
// Guild-scoped commands are never exposed in DMs — /signup and /link must
|
|
// be global so they actually appear (and run) in a DM with the bot.
|
|
if (globalCommands.length > 0) {
|
|
await readyClient.application.commands.set(
|
|
globalCommands.map((command) => command.builder.toJSON()),
|
|
);
|
|
}
|
|
if (guildCommands.length > 0) {
|
|
await readyClient.application.commands.set(
|
|
guildCommands.map((command) => command.builder.toJSON()),
|
|
botConfig.guildId,
|
|
);
|
|
}
|
|
payload.logger.info(
|
|
`[Bot] Registered ${globalCommands.length} global and ${guildCommands.length} guild command(s)`,
|
|
);
|
|
for (const start of services) {
|
|
await start(readyClient, payload);
|
|
}
|
|
});
|
|
|
|
client.on(Events.InteractionCreate, async (interaction) => {
|
|
if (interaction.isButton() && interaction.customId.startsWith("ptf-att:")) {
|
|
await handleButtonInteraction(interaction, payload);
|
|
return;
|
|
}
|
|
if (
|
|
(interaction.isButton() || interaction.isModalSubmit()) &&
|
|
interaction.customId.startsWith("ptf-xfer:")
|
|
) {
|
|
await handleTransferInteraction(interaction, payload);
|
|
return;
|
|
}
|
|
if (!interaction.isChatInputCommand()) {
|
|
return;
|
|
}
|
|
const execute = commandExecutors.get(interaction.commandName);
|
|
if (!execute) {
|
|
return;
|
|
}
|
|
try {
|
|
await execute(interaction, payload);
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
payload.logger.error(`[Bot] Error executing /${interaction.commandName}: ${message}`);
|
|
if (!interaction.replied && !interaction.deferred) {
|
|
await interaction.reply({
|
|
content: "Something went wrong while running this command.",
|
|
flags: MessageFlags.Ephemeral,
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
await client.login(botConfig.token);
|
|
};
|
|
|
|
main().catch((error) => {
|
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
console.error(`[Bot] Failed to start: ${message}`);
|
|
process.exit(1);
|
|
});
|