Files
EllyDiscordBot/src/index.ts

269 lines
9.5 KiB
TypeScript

/**
* 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<void> {
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<string, string[]>();
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);
});