/** * Joel Discord Bot - Main Entry Point * * A well-structured Discord bot with clear separation of concerns: * - core/ - Bot client, configuration, logging * - commands/ - Slash command definitions and handling * - events/ - Discord event handlers * - features/ - Feature modules (Joel AI, message logging, etc.) * - services/ - External services (AI providers) * - database/ - Database schema and repositories * - utils/ - Shared utilities */ import { GatewayIntentBits } from "discord.js"; import { BotClient } from "./core/client"; import { config } from "./core/config"; import { createLogger } from "./core/logger"; import { registerEvents } from "./events"; import { logVoiceDependencyHealth, stopSpontaneousMentionsCron } from "./features/joel"; import { buildWebCss, startWebCssWatcher, startWebServer } from "./web"; import { runMigrations } from "./database/migrate"; import type { FSWatcher } from "fs"; const logger = createLogger("Main"); let webCssWatcher: FSWatcher | null = null; const isProduction = Bun.env.NODE_ENV === "production"; // Create the Discord client with required intents const client = new BotClient({ intents: [ GatewayIntentBits.MessageContent, GatewayIntentBits.Guilds, GatewayIntentBits.DirectMessages, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildModeration, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildVoiceStates, ], }); // Register all event handlers registerEvents(client); // Start the bot async function main(): Promise { logger.info("Starting Joel bot..."); try { // Run database migrations await runMigrations(); logVoiceDependencyHealth(); await client.login(config.discord.token); // Start web server after bot is logged in await buildWebCss(); if (!isProduction) { webCssWatcher = startWebCssWatcher(); } await startWebServer(client); } catch (error) { logger.error("Failed to start bot", error); process.exit(1); } } // Handle graceful shutdown process.on("SIGINT", () => { logger.info("Shutting down..."); webCssWatcher?.close(); stopSpontaneousMentionsCron(); client.destroy(); process.exit(0); }); process.on("SIGTERM", () => { logger.info("Shutting down..."); webCssWatcher?.close(); stopSpontaneousMentionsCron(); client.destroy(); process.exit(0); }); main();