const { Client, GatewayIntentBits } = require("discord.js"); const CommandHandler = require("./commands/CommandHandler.js"); const PlayerService = require("./services/PlayerService.js"); const NotificationService = require("./services/NotificationService.js"); const MatchCommands = require("./commands/MatchCommands.js"); const SupabaseService = require("./services/SupabaseService.js"); const SubscriptionCommands = require("./commands/SubscriptionCommands.js"); const CooldownManager = require("./utils/CooldownManager.js"); const Logger = require("./utils/Logger.js"); class Bot { constructor() { this.logger = new Logger('Bot'); this.cooldownManager = new CooldownManager(); this.client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, ], }); this.logger.info('Initializing bot...'); this.initializeServices(); this.setupEventHandlers(); } initializeServices() { this.logger.debug('Initializing services...'); this.playerService = new PlayerService(); this.commandHandler = new CommandHandler(this.playerService); this.matchCommands = new MatchCommands(this.playerService); this.notificationService = new NotificationService(this); this.initializeSupabase(); } initializeSupabase() { this.logger.debug('Initializing Supabase service...'); this.supabaseService = new SupabaseService(); if (this.supabaseService.supabase) { this.subscriptionCommands = new SubscriptionCommands(this.supabaseService); this.logger.info('Supabase service initialized successfully'); } else { this.logger.warn('Supabase initialization failed. Subscription commands unavailable.'); this.subscriptionCommands = null; } } setupEventHandlers() { this.logger.debug('Setting up event handlers...'); this.client.on('error', this.handleError.bind(this)); this.client.once('ready', this.handleReady.bind(this)); this.client.on('interactionCreate', this.handleInteraction.bind(this)); } async start(token) { this.logger.info('Starting bot...'); try { await this.client.login(token); this.logger.info('Login successful'); const port = process.env.NOTIFICATION_PORT || 3000; await this.notificationService.start(port); this.logger.info(`Notification service started on port ${port}`); } catch (error) { this.logger.error('Startup failed:', error); throw error; } } async stop() { this.logger.info('Stopping bot...'); try { await this.notificationService.stop(); await this.client.destroy(); this.logger.info('Bot stopped successfully'); } catch (error) { this.logger.error('Error stopping bot:', error); throw error; } } handleError(error) { this.logger.error('Discord client error:', error); } handleReady() { this.logger.info(`Logged in as ${this.client.user.tag}`); } handleInteraction(interaction) { this.logger.debug('Received interaction', { type: interaction.type, commandName: interaction.commandName, user: interaction.user.tag, guild: interaction.guild?.name, channel: interaction.channel?.name }); if (interaction.isCommand()) { this.handleCommand(interaction) .catch(error => this.handleInteractionError(interaction, error)); } else if (interaction.isButton()) { this.handleButton(interaction) .catch(error => this.handleInteractionError(interaction, error)); } } async handleCommand(interaction) { try { // Check cooldown const cooldownTime = this.cooldownManager.checkCooldown(interaction); if (cooldownTime > 0) { await interaction.reply({ content: `Please wait ${cooldownTime.toFixed(1)} more seconds before using this command again.`, ephemeral: true }); return; } this.logger.debug('Processing command', { command: interaction.commandName, user: interaction.user.tag }); await this.processCommand(interaction); } catch (error) { this.logger.error('Command error:', error); await this.handleCommandError(interaction, error); } } async processCommand(interaction) { switch (interaction.commandName) { case "ping": await this.commandHandler.handlePing(interaction); break; case "finduser": await this.commandHandler.handleFindUser(interaction); break; case "matchhistory": await this.commandHandler.handleMatchHistory(interaction); break; case "subscribe": case "unsubscribe": case "list_subscriptions": await this.handleSubscriptionCommand(interaction); break; default: await interaction.reply({ content: "Unknown command", ephemeral: true }); } } async handleSubscriptionCommand(interaction) { if (!this.subscriptionCommands) { await this.safeReply(interaction, "Subscription commands are not available."); return; } try { // Defer the reply immediately await this.safeReply(interaction, "Processing your request..."); switch (interaction.commandName) { case "subscribe": await this.subscriptionCommands.handleSubscribe(interaction); break; case "unsubscribe": await this.subscriptionCommands.handleUnsubscribe(interaction); break; case "list_subscriptions": await this.subscriptionCommands.handleListSubscriptions(interaction); break; } } catch (error) { this.logger.error('Subscription command error:', error); await this.safeReply(interaction, 'An error occurred while processing your request.'); } } async handleButton(interaction) { const [action, matchId] = interaction.customId.split("_match_"); try { this.logger.debug('Processing button interaction', { action, matchId, user: interaction.user.tag }); switch (action) { case "accept": await this.matchCommands.handleMatchAccept(interaction, matchId); break; case "view": await this.matchCommands.handleViewDetails(interaction, matchId); break; default: this.logger.warn('Unknown button action', { action }); await interaction.reply({ content: "Unknown button action", ephemeral: true, }); } } catch (error) { this.logger.error("Error handling button:", error); await this.handleInteractionError(interaction, error); } } async handleCommandError(interaction, error) { this.logger.error('Command error:', error); try { if (!interaction.replied && !interaction.deferred) { await interaction.reply({ content: 'An error occurred while processing your command.', ephemeral: true }); } else if (interaction.deferred && !interaction.replied) { await interaction.editReply('An error occurred while processing your command.'); } } catch (e) { this.logger.error('Error sending error message:', e); } } async safeReply(interaction, content, options = {}) { try { if (!interaction.deferred && !interaction.replied) { await interaction.reply({ content, ephemeral: true, ...options }); } else if (interaction.deferred && !interaction.replied) { await interaction.editReply({ content, ...options }); } } catch (error) { this.logger.error('Error in safeReply:', error); } } async handleInteractionError(interaction, error) { this.logger.error('Interaction error:', error); try { if (!interaction.replied && !interaction.deferred) { await interaction.reply({ content: 'An error occurred while processing your request.', ephemeral: true }); } else if (interaction.deferred && !interaction.replied) { await interaction.editReply({ content: 'An error occurred while processing your request.' }); } } catch (e) { this.logger.error('Error sending error message:', e); } } } module.exports = Bot;