Notification subscriptions
This commit is contained in:
425
src/Bot.js
425
src/Bot.js
@@ -5,204 +5,261 @@ 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.client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.MessageContent,
|
||||
],
|
||||
});
|
||||
|
||||
this.playerService = new PlayerService();
|
||||
this.commandHandler = new CommandHandler(this.playerService);
|
||||
this.matchCommands = new MatchCommands(this.playerService);
|
||||
this.notificationService = new NotificationService(this);
|
||||
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;
|
||||
}
|
||||
|
||||
this.supabaseService = new SupabaseService();
|
||||
if (this.supabaseService.supabase) {
|
||||
this.subscriptionCommands = new SubscriptionCommands(this.supabaseService);
|
||||
} else {
|
||||
console.warn('SupabaseService not initialized. Subscription commands will be unavailable.');
|
||||
this.subscriptionCommands = null;
|
||||
}
|
||||
|
||||
this.setupEventHandlers();
|
||||
}
|
||||
|
||||
setupEventHandlers() {
|
||||
this.client.on('error', this.handleError.bind(this));
|
||||
this.client.once('ready', this.handleReady.bind(this));
|
||||
this.client.on('interactionCreate', this.handleInteraction.bind(this));
|
||||
}
|
||||
|
||||
// In the start method, add Supabase connection test:
|
||||
async start(token) {
|
||||
console.log("Starting bot...");
|
||||
try {
|
||||
// Test Supabase connection first
|
||||
if (this.supabaseService) {
|
||||
const connected = await this.supabaseService.testConnection();
|
||||
if (!connected) {
|
||||
console.warn('Supabase connection failed. Subscription features will be disabled.');
|
||||
this.subscriptionCommands = null;
|
||||
}
|
||||
}
|
||||
|
||||
await this.client.login(token);
|
||||
console.log("Login successful");
|
||||
|
||||
// Start notification service after bot is logged in
|
||||
const port = process.env.NOTIFICATION_PORT || 3000;
|
||||
await this.notificationService.start(port);
|
||||
console.log(`Notification service started on port ${port}`);
|
||||
} catch (error) {
|
||||
console.error("Startup failed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async stop() {
|
||||
console.log("Stopping bot...");
|
||||
try {
|
||||
await this.notificationService.stop();
|
||||
await this.client.destroy();
|
||||
console.log("Bot stopped successfully");
|
||||
} catch (error) {
|
||||
console.error("Error stopping bot:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
handleError(error) {
|
||||
console.error("Discord client error:", error);
|
||||
}
|
||||
|
||||
handleReady() {
|
||||
console.log(`Logged in as ${this.client.user.tag}!`);
|
||||
}
|
||||
|
||||
async handleInteraction(interaction) {
|
||||
try {
|
||||
if (interaction.isCommand()) {
|
||||
await this.handleCommand(interaction);
|
||||
} else if (interaction.isButton()) {
|
||||
await this.handleButton(interaction);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error handling interaction:", error);
|
||||
await this.handleInteractionError(interaction, error);
|
||||
}
|
||||
}
|
||||
|
||||
async handleCommand(interaction) {
|
||||
try {
|
||||
// Defer the reply immediately for all commands
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
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":
|
||||
if (this.subscriptionCommands) {
|
||||
try {
|
||||
// Defer the reply immediately
|
||||
await this.safeReply(interaction, "Processing your request...");
|
||||
|
||||
switch (interaction.commandName) {
|
||||
case "subscribe":
|
||||
await this.subscriptionCommands.handleSubscribe(interaction);
|
||||
} else {
|
||||
await interaction.editReply("Subscription commands are not available.");
|
||||
}
|
||||
break;
|
||||
case "unsubscribe":
|
||||
if (this.subscriptionCommands) {
|
||||
break;
|
||||
case "unsubscribe":
|
||||
await this.subscriptionCommands.handleUnsubscribe(interaction);
|
||||
} else {
|
||||
await interaction.editReply("Subscription commands are not available.");
|
||||
}
|
||||
break;
|
||||
case "listsubscriptions":
|
||||
if (this.subscriptionCommands) {
|
||||
break;
|
||||
case "list_subscriptions":
|
||||
await this.subscriptionCommands.handleListSubscriptions(interaction);
|
||||
} else {
|
||||
await interaction.editReply("Subscription commands are not available.");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
await interaction.editReply("Unknown command");
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('Subscription command error:', error);
|
||||
await this.safeReply(interaction, 'An error occurred while processing your request.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Command error:', error);
|
||||
await this.handleCommandError(interaction, error);
|
||||
}
|
||||
}
|
||||
async handleButton(interaction) {
|
||||
const [action, matchId] = interaction.customId.split("_match_");
|
||||
|
||||
async handleButton(interaction) {
|
||||
const [action, matchId] = interaction.customId.split("_match_");
|
||||
try {
|
||||
this.logger.debug('Processing button interaction', {
|
||||
action,
|
||||
matchId,
|
||||
user: interaction.user.tag
|
||||
});
|
||||
|
||||
try {
|
||||
switch (action) {
|
||||
case "accept":
|
||||
await this.matchCommands.handleMatchAccept(interaction, matchId);
|
||||
break;
|
||||
case "view":
|
||||
await this.matchCommands.handleViewDetails(interaction, matchId);
|
||||
break;
|
||||
default:
|
||||
await interaction.reply({
|
||||
content: "Unknown button action",
|
||||
ephemeral: true,
|
||||
});
|
||||
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);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error handling button:", error);
|
||||
await this.handleInteractionError(interaction, error);
|
||||
}
|
||||
}
|
||||
|
||||
async handleCommandError(interaction, error) {
|
||||
console.error("Command error:", error);
|
||||
|
||||
if (error.code === 10062) {
|
||||
console.log("Interaction expired");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const message = "An error occurred while processing your command.";
|
||||
if (interaction.deferred) {
|
||||
await interaction.editReply({ content: message });
|
||||
} else if (!interaction.replied) {
|
||||
await interaction.reply({ content: message, ephemeral: true });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error while sending error message:", e);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async handleInteractionError(interaction, error) {
|
||||
console.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) {
|
||||
await interaction.editReply({
|
||||
content: "An error occurred while processing your request.",
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error while sending error message:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Bot;
|
||||
Reference in New Issue
Block a user