subscribe update

This commit is contained in:
VinceC
2024-11-24 03:15:04 -06:00
parent 12722b6fd4
commit 1cee965d00
8 changed files with 447 additions and 72 deletions

View File

@@ -1,8 +1,10 @@
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 { 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");
class Bot {
constructor() {
@@ -13,12 +15,20 @@ class Bot {
GatewayIntentBits.MessageContent,
],
});
this.playerService = new PlayerService();
this.commandHandler = new CommandHandler(this.playerService);
this.matchCommands = new MatchCommands(this.playerService);
this.notificationService = new NotificationService(this);
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();
}
@@ -29,35 +39,35 @@ class Bot {
}
async start(token) {
console.log('Starting bot...');
console.log("Starting bot...");
try {
await this.client.login(token);
console.log('Login successful');
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);
console.error("Startup failed:", error);
throw error;
}
}
async stop() {
console.log('Stopping bot...');
console.log("Stopping bot...");
try {
await this.notificationService.stop();
await this.client.destroy();
console.log('Bot stopped successfully');
console.log("Bot stopped successfully");
} catch (error) {
console.error('Error stopping bot:', error);
console.error("Error stopping bot:", error);
throw error;
}
}
handleError(error) {
console.error('Discord client error:', error);
console.error("Discord client error:", error);
}
handleReady() {
@@ -72,94 +82,115 @@ class Bot {
await this.handleButton(interaction);
}
} catch (error) {
console.error('Error handling interaction:', error);
this.handleInteractionError(interaction, 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':
case "ping":
await this.commandHandler.handlePing(interaction);
break;
case 'finduser':
case "finduser":
await this.commandHandler.handleFindUser(interaction);
break;
case 'matchhistory':
case "matchhistory":
await this.commandHandler.handleMatchHistory(interaction);
break;
case "subscribe":
if (this.subscriptionCommands) {
await this.subscriptionCommands.handleSubscribe(interaction);
} else {
await interaction.editReply("Subscription commands are not available.");
}
break;
case "unsubscribe":
if (this.subscriptionCommands) {
await this.subscriptionCommands.handleUnsubscribe(interaction);
} else {
await interaction.editReply("Subscription commands are not available.");
}
break;
case "listsubscriptions":
if (this.subscriptionCommands) {
await this.subscriptionCommands.handleListSubscriptions(interaction);
} else {
await interaction.editReply("Subscription commands are not available.");
}
break;
default:
await interaction.reply({
content: 'Unknown command',
ephemeral: true
});
await interaction.editReply("Unknown command");
}
} catch (error) {
console.error('Command error:', error);
await this.handleCommandError(interaction, error);
}
}
async handleButton(interaction) {
const [action, matchId] = interaction.customId.split('_match_');
const [action, matchId] = interaction.customId.split("_match_");
try {
switch (action) {
case 'accept':
case "accept":
await this.matchCommands.handleMatchAccept(interaction, matchId);
break;
case 'view':
case "view":
await this.matchCommands.handleViewDetails(interaction, matchId);
break;
default:
await interaction.reply({
content: 'Unknown button action',
ephemeral: true
content: "Unknown button action",
ephemeral: true,
});
}
} catch (error) {
console.error('Error handling button:', error);
console.error("Error handling button:", error);
await this.handleInteractionError(interaction, error);
}
}
async handleCommandError(interaction, error) {
console.error('Command error:', error);
console.error("Command error:", error);
if (error.code === 10062) {
console.log('Interaction expired');
console.log("Interaction expired");
return;
}
try {
const message = 'An error occurred while processing your command.';
const message = "An error occurred while processing your command.";
if (interaction.deferred) {
await interaction.editReply({ content: message, ephemeral: true });
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);
console.error("Error while sending error message:", e);
}
}
async handleInteractionError(interaction, error) {
console.error('Interaction error:', 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
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.',
ephemeral: true
content: "An error occurred while processing your request.",
});
}
} catch (e) {
console.error('Error while sending error message:', e);
console.error("Error while sending error message:", e);
}
}
}

View File

@@ -0,0 +1,62 @@
class SubscriptionCommands {
constructor(supabaseService) {
this.supabaseService = supabaseService;
}
async handleSubscribe(interaction) {
const gameName = interaction.options.getString('game');
const channel = interaction.options.getChannel('channel') || interaction.channel;
try {
// Defer the reply immediately
await interaction.deferReply({ ephemeral: true });
// Perform the subscription operation
await this.supabaseService.addSubscription(interaction.guildId, gameName, channel.id);
// Edit the deferred reply with the success message
await interaction.editReply(`Successfully subscribed to ${gameName} notifications in ${channel}.`);
} catch (error) {
console.error('Error subscribing:', error);
// If we failed to defer earlier, try to reply now
if (!interaction.deferred && !interaction.replied) {
await interaction.reply({ content: 'An error occurred while subscribing. Please try again.', ephemeral: true }).catch(console.error);
} else {
// If we successfully deferred earlier, edit the reply
await interaction.editReply('An error occurred while subscribing. Please try again.').catch(console.error);
}
}
}
async handleUnsubscribe(interaction) {
const gameName = interaction.options.getString('game');
try {
await this.supabaseService.removeSubscription(interaction.guildId, gameName);
await interaction.reply(`Successfully unsubscribed from ${gameName} notifications.`);
} catch (error) {
console.error('Error unsubscribing:', error);
await interaction.reply('An error occurred while unsubscribing. Please try again.');
}
}
async handleListSubscriptions(interaction) {
try {
const subscriptions = await this.supabaseService.getSubscriptions(interaction.guildId);
if (subscriptions.length === 0) {
await interaction.reply('This server has no active subscriptions.');
} else {
const subscriptionList = subscriptions.map(sub =>
`${sub.game_name} in <#${sub.channel_id}>`
).join('\n');
await interaction.reply(`Active subscriptions:\n${subscriptionList}`);
}
} catch (error) {
console.error('Error listing subscriptions:', error);
await interaction.reply('An error occurred while fetching subscriptions. Please try again.');
}
}
}
module.exports = SubscriptionCommands;

View File

@@ -5,12 +5,13 @@ const { createMatchRequestEmbed } = require('../utils/embedBuilders');
const { createMatchActionRow } = require('../utils/componentBuilders');
class NotificationService {
constructor(bot) {
this.bot = bot;
this.app = express();
this.app.use(express.json());
this.setupRoutes();
}
constructor(bot, supabaseService) {
this.bot = bot;
this.supabaseService = supabaseService;
this.app = express();
this.app.use(express.json());
this.setupRoutes();
}
setupRoutes() {
this.app.post('/api/match-notification', this.handleMatchNotification.bind(this));
@@ -49,26 +50,25 @@ class NotificationService {
return res.status(400).json({ error: 'Invalid match data' });
}
const notificationChannelId = process.env.NOTIFICATION_CHANNEL_ID;
if (!notificationChannelId) {
throw new Error('Notification channel ID not configured');
const subscriptions = await this.supabaseService.getSubscriptions();
const relevantSubscriptions = subscriptions.filter(sub => sub.game_name === matchData.game_name);
for (const subscription of relevantSubscriptions) {
try {
const channel = await this.bot.client.channels.fetch(subscription.channel_id);
const embed = createMatchRequestEmbed(matchData);
const actionRow = createMatchActionRow(matchData.game_id);
await channel.send({
embeds: [embed],
components: [actionRow]
});
} catch (error) {
console.error(`Error sending notification to channel ${subscription.channel_id}:`, error);
}
}
try {
const channel = await this.bot.client.channels.fetch(notificationChannelId);
const embed = createMatchRequestEmbed(matchData);
const actionRow = createMatchActionRow(matchData.game_id);
await channel.send({
embeds: [embed],
components: [actionRow]
});
res.json({ success: true });
} catch (error) {
console.error('Error fetching or sending to notification channel:', error);
throw new Error('Failed to send match notification');
}
res.json({ success: true });
} catch (error) {
console.error('Error handling match notification:', error);
res.status(500).json({ error: 'Internal server error' });

View File

@@ -0,0 +1,58 @@
const { createClient } = require('@supabase/supabase-js');
class SupabaseService {
constructor() {
const supabaseUrl = process.env.SUPABASE_URL;
const supabaseKey = process.env.SUPABASE_KEY;
if (!supabaseUrl || !supabaseKey) {
console.error('Supabase URL or key is missing. Please check your .env file.');
this.supabase = null;
} else {
this.supabase = createClient(supabaseUrl, supabaseKey);
}
}
async getSubscriptions(guildId) {
if (!this.supabase) {
console.error('Supabase client is not initialized.');
return [];
}
const { data, error } = await this.supabase
.from('subscriptions')
.select('*')
.eq('guild_id', guildId);
if (error) throw error;
return data;
}
async addSubscription(guildId, gameName, channelId) {
if (!this.supabase) {
console.error('Supabase client is not initialized.');
return null;
}
const { data, error } = await this.supabase
.from('subscriptions')
.insert({ guild_id: guildId, game_name: gameName, channel_id: channelId });
if (error) throw error;
return data;
}
async removeSubscription(guildId, gameName) {
if (!this.supabase) {
console.error('Supabase client is not initialized.');
return null;
}
const { data, error } = await this.supabase
.from('subscriptions')
.delete()
.match({ guild_id: guildId, game_name: gameName });
if (error) throw error;
return data;
}
}
module.exports = SupabaseService;