Files
BattleBot/src/Bot.js
2024-11-24 06:04:35 -06:00

265 lines
9.3 KiB
JavaScript

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;