/** * Elly Discord Bot * Main entry point */ import { loadConfig, validateConfig, type ConfigValidationResult } from './config/config.ts'; import { REST, Routes } from 'discord.js'; import { EllyClient } from './client/EllyClient.ts'; import { createLogger } from './utils/logger.ts'; // Import commands - Statistics import { bedwarsCommand } from './commands/statistics/bedwars.ts'; import { skywarsCommand } from './commands/statistics/skywars.ts'; import { guildCommand } from './commands/statistics/guild.ts'; import { serverCommand } from './commands/statistics/server.ts'; // Import commands - Utility import { remindCommand } from './commands/utility/remind.ts'; import { awayCommand } from './commands/utility/away.ts'; import { suggestionsCommand } from './commands/suggestions/index.ts'; import { championCommand } from './commands/utility/champion.ts'; import { roleCommand } from './commands/utility/role.ts'; import { qotdCommand } from './commands/qotd/index.ts'; import { applicationsCommand } from './commands/applications/index.ts'; import { staffCommand } from './commands/utility/staff.ts'; // Import commands - Family import { marryCommand } from './commands/family/marry.ts'; import { divorceCommand } from './commands/family/divorce.ts'; import { relationshipCommand } from './commands/family/relationship.ts'; import { adoptCommand } from './commands/family/adopt.ts'; // Import commands - Moderation import { purgeCommand } from './commands/moderation/purge.ts'; import { filterCommand } from './commands/moderation/filter.ts'; // Import commands - Developer import { reloadCommand } from './commands/developer/reload.ts'; import { syncCommand } from './commands/developer/sync.ts'; import { evalCommand } from './commands/developer/eval.ts'; import { debugCommand } from './commands/developer/debug.ts'; import { databaseCommand } from './commands/developer/database.ts'; import { emitCommand } from './commands/developer/emit.ts'; import { shellCommand } from './commands/developer/shell.ts'; import { blacklistCommand } from './commands/developer/blacklist.ts'; // Import events import { messageCreateEvent } from './events/messageCreate.ts'; const logger = createLogger('Main'); async function main(): Promise { console.log(''); console.log('╔═══════════════════════════════════════════════════════════╗'); console.log('║ 🌸 ELLY DISCORD BOT 🌸 ║'); console.log('║ v1.0.0 - TypeScript ║'); console.log('╚═══════════════════════════════════════════════════════════╝'); console.log(''); logger.info('Starting Elly Discord Bot...'); // Load configuration logger.info('Loading configuration from config.toml...'); let config; try { config = await loadConfig('./config.toml'); // Validate configuration const validation = validateConfig(config); if (!validation.valid) { logger.error('✗ Configuration validation failed:'); for (const error of validation.errors) { logger.error(` ✗ [${error.field}] ${error.message}`); } Deno.exit(1); } // Log warnings if (validation.warnings.length > 0) { logger.warn(`⚠ Configuration has ${validation.warnings.length} warning(s):`); for (const warning of validation.warnings) { logger.warn(` ⚠ [${warning.field}] ${warning.message}`); } } logger.info('✓ Configuration loaded and validated'); logger.info(` ├─ Bot Name: ${config.bot.name}`); logger.info(` ├─ Guild: ${config.guild.name} (${config.guild.id})`); logger.info(` ├─ Database: ${config.database.path}`); logger.info(` └─ Owners: ${config.bot.owners?.ids?.length ?? 0} configured`); } catch (error) { logger.error('✗ Failed to load configuration', error); Deno.exit(1); } // Create client logger.info('Initializing Discord client...'); const client = new EllyClient(config); logger.info('✓ Discord client created'); // Register commands logger.info('Registering commands...'); const commands = [ // Statistics { cmd: bedwarsCommand, category: 'Statistics' }, { cmd: skywarsCommand, category: 'Statistics' }, { cmd: guildCommand, category: 'Statistics' }, { cmd: serverCommand, category: 'Statistics' }, // Utility { cmd: remindCommand, category: 'Utility' }, { cmd: awayCommand, category: 'Utility' }, { cmd: suggestionsCommand, category: 'Suggestions' }, { cmd: championCommand, category: 'Utility' }, { cmd: roleCommand, category: 'Utility' }, { cmd: qotdCommand, category: 'QOTD' }, { cmd: applicationsCommand, category: 'Applications' }, { cmd: staffCommand, category: 'Utility' }, // Family { cmd: marryCommand, category: 'Family' }, { cmd: divorceCommand, category: 'Family' }, { cmd: relationshipCommand, category: 'Family' }, { cmd: adoptCommand, category: 'Family' }, // Moderation { cmd: purgeCommand, category: 'Moderation' }, { cmd: filterCommand, category: 'Moderation' }, // Developer { cmd: reloadCommand, category: 'Developer' }, { cmd: syncCommand, category: 'Developer' }, { cmd: evalCommand, category: 'Developer' }, { cmd: debugCommand, category: 'Developer' }, { cmd: databaseCommand, category: 'Developer' }, { cmd: emitCommand, category: 'Developer' }, { cmd: shellCommand, category: 'Developer' }, { cmd: blacklistCommand, category: 'Developer' }, ]; for (const { cmd, category } of commands) { client.registerCommand(cmd); logger.debug(` ├─ /${cmd.data.name} [${category}]`); } logger.info(`✓ Registered ${client.commands.size} commands`); // Register message event for filtering client.on(messageCreateEvent.name, (...args) => messageCreateEvent.execute(...args)); logger.info('✓ Registered message filtering event'); // Initialize client try { await client.initialize(); } catch (error) { logger.error('Failed to initialize client', error); Deno.exit(1); } // Set up interaction handler client.on('interactionCreate', async (interaction) => { if (!interaction.isChatInputCommand()) return; const command = client.commands.get(interaction.commandName); if (!command) return; // Check cooldown const cooldownRemaining = client.isOnCooldown(interaction.user.id, interaction.commandName); if (cooldownRemaining > 0) { await interaction.reply({ content: `Please wait ${cooldownRemaining} seconds before using this command again.`, ephemeral: true, }); return; } // Check permissions if (interaction.guild && interaction.member) { const member = interaction.guild.members.cache.get(interaction.user.id); if (member && !client.permissions.hasPermission(member, command.permission)) { await interaction.reply({ content: client.permissions.formatDeniedMessage(command.permission), ephemeral: true, }); return; } } // Execute command try { await command.execute(interaction); // Set cooldown if (command.cooldown) { client.setCooldown(interaction.user.id, interaction.commandName, command.cooldown); } } catch (error) { // Use the centralized error handler await client.errorHandler.handleCommandError(error, interaction); } }); // Handle shutdown const shutdown = async () => { logger.info('Received shutdown signal'); await client.shutdown(); Deno.exit(0); }; Deno.addSignalListener('SIGINT', shutdown); Deno.addSignalListener('SIGTERM', shutdown); // Login const token = Deno.env.get('DISCORD_TOKEN'); if (!token) { logger.error('DISCORD_TOKEN environment variable is not set'); Deno.exit(1); } try { await client.login(token); } catch (error) { logger.error('Failed to login', error); Deno.exit(1); } // Sync commands after login logger.info('Syncing slash commands...'); try { const startTime = Date.now(); const rest = new REST({ version: '10' }).setToken(token); // Build command data array const commandData = Array.from(client.commands.values()).map((cmd) => cmd.data.toJSON()); // Sync to guild (faster for development) const result = await rest.put( Routes.applicationGuildCommands(client.user!.id, config.guild.id), { body: commandData } ) as unknown[]; const syncTime = Date.now() - startTime; logger.info('✓ Commands synced successfully'); logger.info(` ├─ Commands: ${result.length} synced`); logger.info(` ├─ Guild: ${config.guild.name} (${config.guild.id})`); logger.info(` └─ Time: ${syncTime}ms`); // Log command details const categories = new Map(); for (const { cmd, category } of commands) { if (!categories.has(category)) { categories.set(category, []); } categories.get(category)!.push(cmd.data.name); } logger.debug('Command breakdown by category:'); for (const [category, cmds] of categories) { logger.debug(` ├─ ${category}: ${cmds.join(', ')}`); } } catch (error) { logger.error('✗ Failed to sync commands', error); // Don't exit - bot can still work with existing commands } } // Run main().catch((error) => { logger.error('Unhandled error', error); Deno.exit(1); });