269 lines
9.5 KiB
TypeScript
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);
|
|
});
|