import { REST, Routes, Collection, Client, ChatInputCommandInteraction, GatewayIntentBits } from 'discord.js'; import fs from 'fs'; import path from 'path'; import { SlashCommand } from '../utils/types'; import { env } from '../config/env'; import { logger } from '../utils/logger'; import { AdminService } from './adminService'; import { StatuspageService } from './statuspageService'; import { settingsStore } from '../config/state'; import { ModuleKey } from './moduleService'; export class CommandHandler { private commands = new Collection(); private moduleMap: Record = { // Tickets ticket: 'ticketsEnabled', ticketpanel: 'ticketsEnabled', transcript: 'ticketsEnabled', close: 'ticketsEnabled', claim: 'ticketsEnabled', // Music play: 'musicEnabled', skip: 'musicEnabled', stop: 'musicEnabled', pause: 'musicEnabled', resume: 'musicEnabled', loop: 'musicEnabled', queue: 'musicEnabled', // Level rank: 'levelingEnabled', // Statuspage status: 'statuspageEnabled', // Birthday birthday: 'birthdayEnabled', // Events event: 'eventsEnabled', events: 'eventsEnabled' }; constructor(private client: Client, private admin?: AdminService, private statuspage?: StatuspageService) {} public async loadCommands() { const commandsPath = path.join(process.cwd(), 'src', 'commands'); const commandFiles = this.getCommandFiles(commandsPath); for (const file of commandFiles) { const mod = await import(file); const command: SlashCommand = mod.default; if (command?.data && command?.execute) { this.commands.set(command.data.name, command); logger.info(`Loaded command ${command.data.name}`); } } } private getCommandFiles(dir: string): string[] { const entries = fs.readdirSync(dir, { withFileTypes: true }); const files: string[] = []; for (const entry of entries) { const res = path.resolve(dir, entry.name); if (entry.isDirectory()) { files.push(...this.getCommandFiles(res)); } else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.js'))) { files.push(res); } } return files; } public async registerSlashCommands() { const rest = new REST({ version: '10' }).setToken(env.token); const body = this.commands.map((command) => command.data.toJSON()); const guilds = env.guildIds.length ? env.guildIds : []; if (guilds.length) { for (const gid of guilds) { try { await rest.put(Routes.applicationGuildCommands(env.clientId, gid), { body }); logger.info(`Registered ${body.length} slash commands for guild ${gid}`); } catch (err) { logger.error(`Failed to register slash commands for guild ${gid}`, err); } } } else { try { await rest.put(Routes.applicationCommands(env.clientId), { body }); logger.info(`Registered ${body.length} global slash commands`); } catch (err) { logger.error('Failed to register global slash commands', err); } } } public async registerGuildCommands(guildId: string) { const rest = new REST({ version: '10' }).setToken(env.token); const body = this.commands.map((command) => command.data.toJSON()); try { await rest.put(Routes.applicationGuildCommands(env.clientId, guildId), { body }); logger.info(`Registered ${body.length} slash commands for guild ${guildId}`); } catch (err) { logger.error(`Failed to register commands for guild ${guildId}`, err); } } public async handleInteraction(interaction: ChatInputCommandInteraction) { const command = this.commands.get(interaction.commandName); if (!command) return; if (command.guildOnly && !interaction.inGuild()) { await interaction.reply({ content: 'Dieser Befehl funktioniert nur auf Servern.', ephemeral: true }); return; } if (interaction.inGuild()) { const moduleKey = this.moduleMap[interaction.commandName]; if (moduleKey) { const cfg = settingsStore.get(interaction.guildId!); const enabled = cfg?.[moduleKey]; if (enabled === false) { await interaction.reply({ content: 'Dieses Modul ist fuer diese Guild deaktiviert.', ephemeral: true }); return; } } } try { this.admin?.trackCommand(interaction.guildId); if (interaction.guildId) this.admin?.trackGuildEvent(interaction.guildId, 'commands'); await command.execute(interaction, this.client); } catch (err) { logger.error(`Command ${interaction.commandName} failed`, err); if (interaction.deferred || interaction.replied) { await interaction.followUp({ content: 'Es ist ein Fehler aufgetreten.', ephemeral: true }); } else { await interaction.reply({ content: 'Es ist ein Fehler aufgetreten.', ephemeral: true }); } } } }