diff --git a/deploy-commands.js b/deploy-commands.js index 49d2d21..5e40933 100644 --- a/deploy-commands.js +++ b/deploy-commands.js @@ -28,6 +28,27 @@ const commands = [ option.setName('game') .setDescription('Filter by game') .setRequired(false)), + new SlashCommandBuilder() + .setName('subscribe') + .setDescription('Subscribe to notifications for a game') + .addStringOption(option => + option.setName('game') + .setDescription('The game to subscribe to') + .setRequired(true)) + .addChannelOption(option => + option.setName('channel') + .setDescription('The channel to receive notifications (default: current channel)') + .setRequired(false)), + new SlashCommandBuilder() + .setName('unsubscribe') + .setDescription('Unsubscribe from notifications for a game') + .addStringOption(option => + option.setName('game') + .setDescription('The game to unsubscribe from') + .setRequired(true)), + new SlashCommandBuilder() + .setName('listsubscriptions') + .setDescription('List all active subscriptions for this server'), ]; const rest = new REST({ version: '10' }).setToken(process.env.BOT_TOKEN); diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index c175f0f..bfe060c 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -170,6 +170,80 @@ "npm": ">=7.0.0" } }, + "node_modules/@supabase/auth-js": { + "version": "2.65.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.65.1.tgz", + "integrity": "sha512-IA7i2Xq2SWNCNMKxwmPlHafBQda0qtnFr8QnyyBr+KaSxoXXqEzFCnQ1dGTy6bsZjVBgXu++o3qrDypTspaAPw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.3.tgz", + "integrity": "sha512-sOLXy+mWRyu4LLv1onYydq+10mNRQ4rzqQxNhbrKLTLTcdcmS9hbWif0bGz/NavmiQfPs4ZcmQJp4WqOXlR4AQ==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.16.3.tgz", + "integrity": "sha512-HI6dsbW68AKlOPofUjDTaosiDBCtW4XAm0D18pPwxoW3zKOE2Ru13Z69Wuys9fd6iTpfDViNco5sgrtnP0666A==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.10.7", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.10.7.tgz", + "integrity": "sha512-OLI0hiSAqQSqRpGMTUwoIWo51eUivSYlaNBgxsXZE7PSoWh12wPRdVt0psUMaUzEonSB85K21wGc7W5jHnT6uA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.14.2" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz", + "integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.46.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.46.1.tgz", + "integrity": "sha512-HiBpd8stf7M6+tlr+/82L8b2QmCjAD8ex9YdSAKU+whB/SHXXJdus1dGlqiH9Umy9ePUuxaYmVkGd9BcvBnNvg==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.65.1", + "@supabase/functions-js": "2.4.3", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.16.3", + "@supabase/realtime-js": "2.10.7", + "@supabase/storage-js": "2.7.1" + } + }, "node_modules/@types/node": { "version": "22.5.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz", @@ -179,6 +253,12 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/phoenix": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.5.tgz", + "integrity": "sha512-xegpDuR+z0UqG9fwHqNoy3rI7JDlvaPh2TY47Fl80oq6g+hXT+c/LEuE43X48clZ6lOfANl5WrPur9fYO1RJ/w==", + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.5.12", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", @@ -1061,6 +1141,12 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-mixer": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", @@ -1128,6 +1214,22 @@ "node": ">= 0.8" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", diff --git a/package-lock.json b/package-lock.json index 6948ec9..1bae346 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@supabase/supabase-js": "^2.46.1", "axios": "^1.6.0", "discord.js": "^14.16.2", "dotenv": "^16.4.5", @@ -182,6 +183,80 @@ "npm": ">=7.0.0" } }, + "node_modules/@supabase/auth-js": { + "version": "2.65.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.65.1.tgz", + "integrity": "sha512-IA7i2Xq2SWNCNMKxwmPlHafBQda0qtnFr8QnyyBr+KaSxoXXqEzFCnQ1dGTy6bsZjVBgXu++o3qrDypTspaAPw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.3.tgz", + "integrity": "sha512-sOLXy+mWRyu4LLv1onYydq+10mNRQ4rzqQxNhbrKLTLTcdcmS9hbWif0bGz/NavmiQfPs4ZcmQJp4WqOXlR4AQ==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.16.3.tgz", + "integrity": "sha512-HI6dsbW68AKlOPofUjDTaosiDBCtW4XAm0D18pPwxoW3zKOE2Ru13Z69Wuys9fd6iTpfDViNco5sgrtnP0666A==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.10.7", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.10.7.tgz", + "integrity": "sha512-OLI0hiSAqQSqRpGMTUwoIWo51eUivSYlaNBgxsXZE7PSoWh12wPRdVt0psUMaUzEonSB85K21wGc7W5jHnT6uA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.14.2" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz", + "integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.46.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.46.1.tgz", + "integrity": "sha512-HiBpd8stf7M6+tlr+/82L8b2QmCjAD8ex9YdSAKU+whB/SHXXJdus1dGlqiH9Umy9ePUuxaYmVkGd9BcvBnNvg==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.65.1", + "@supabase/functions-js": "2.4.3", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.16.3", + "@supabase/realtime-js": "2.10.7", + "@supabase/storage-js": "2.7.1" + } + }, "node_modules/@types/node": { "version": "22.5.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz", @@ -191,6 +266,12 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/phoenix": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.5.tgz", + "integrity": "sha512-xegpDuR+z0UqG9fwHqNoy3rI7JDlvaPh2TY47Fl80oq6g+hXT+c/LEuE43X48clZ6lOfANl5WrPur9fYO1RJ/w==", + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.5.12", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", @@ -1073,6 +1154,12 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-mixer": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", @@ -1140,6 +1227,22 @@ "node": ">= 0.8" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", diff --git a/package.json b/package.json index 5ab442d..91b80e8 100644 --- a/package.json +++ b/package.json @@ -4,16 +4,14 @@ "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "test:webhook": "NODE_ENV=test node src/test/testWebhook.js" - - }, "keywords": [], "author": "", "license": "ISC", "description": "", "dependencies": { + "@supabase/supabase-js": "^2.46.1", "axios": "^1.6.0", "discord.js": "^14.16.2", "dotenv": "^16.4.5", diff --git a/src/Bot.js b/src/Bot.js index e145865..4b01d39 100644 --- a/src/Bot.js +++ b/src/Bot.js @@ -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); } } } diff --git a/src/commands/SubscriptionCommands.js b/src/commands/SubscriptionCommands.js new file mode 100644 index 0000000..81da935 --- /dev/null +++ b/src/commands/SubscriptionCommands.js @@ -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; \ No newline at end of file diff --git a/src/services/NotificationService.js b/src/services/NotificationService.js index a59fa75..cabbaa7 100644 --- a/src/services/NotificationService.js +++ b/src/services/NotificationService.js @@ -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' }); diff --git a/src/services/SupabaseService.js b/src/services/SupabaseService.js new file mode 100644 index 0000000..f8a40d2 --- /dev/null +++ b/src/services/SupabaseService.js @@ -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; \ No newline at end of file