const { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } = require('discord.js'); const EmbedBuilders = require('../utils/embedBuilders'); class CommandHandler { constructor(playerService, supabase, logger, serverRegistrationService, subscriptionCommands) { this.playerService = playerService; this.supabase = supabase; this.logger = logger; this.serverRegistrationService = serverRegistrationService; this.subscriptionCommands = subscriptionCommands; } async handleCommand(interaction) { try { switch (interaction.commandName) { case 'register_server': await this.handleRegisterServer(interaction); break; case 'ping': await interaction.editReply({ content: 'Pong!', ephemeral: true }); break; case 'finduser': await this.handleFindUser(interaction); break; case 'matchhistory': await this.handleMatchHistory(interaction); break; 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; case 'findteam': await this.handleFindTeam(interaction); break; default: await interaction.editReply({ content: '❌ Unknown command', ephemeral: true }); } } catch (error) { this.logger.error('Command handling error:', { command: interaction.commandName, error: error.message, stack: error.stack }); try { await interaction.editReply({ content: '❌ An error occurred while processing your command.', ephemeral: true }); } catch (followUpError) { this.logger.error('Failed to send error response:', { error: followUpError.message, originalError: error.message }); } } } async handleRegisterServer(interaction) { try { // Check for admin permissions if (!interaction.member.permissions.has('ADMINISTRATOR')) { await interaction.editReply({ content: '❌ You need administrator permissions to register this server.', ephemeral: true }); return; } const guildId = interaction.guildId; const serverName = interaction.guild.name; this.logger.debug('Starting server registration', { guildId, serverName, timestamp: new Date().toISOString() }); // Attempt the database operation const result = await this.serverRegistrationService.registerServer(guildId, serverName); // Log the result this.logger.debug('Server registration result received', { status: result.status, serverId: result.server?.id, guildId, timestamp: new Date().toISOString() }); // Prepare response message based on status let message; switch (result.status) { case 'created': message = '✅ Server successfully registered! You can now use subscription commands.'; break; case 'updated': message = '✅ Server information has been updated!'; break; case 'exists': message = '✅ This server is already registered and ready to use subscription commands!'; break; default: message = '❌ An unexpected error occurred during registration.'; } await interaction.editReply({ content: message, ephemeral: true }); // Log the successful operation this.logger.info('Server registration completed', { status: result.status, guildId, serverId: result.server.id, timestamp: new Date().toISOString() }); } catch (error) { this.logger.error('Error in handleRegisterServer:', { error: error.message, stack: error.stack, guildId: interaction.guildId, serverName: interaction.guild?.name }); // Send error message to user const errorMessage = error.message === 'Invalid guildId provided' || error.message === 'Invalid serverName provided' ? '❌ Invalid server information provided.' : '❌ An error occurred while registering the server. Please try again later.'; await interaction.editReply({ content: errorMessage, ephemeral: true }); throw error; } } async handleFindUser(interaction) { try { const username = interaction.options.getString('username'); const gameFilter = interaction.options.getString('game'); // Input validation if (!username || typeof username !== 'string') { await interaction.editReply({ content: '❌ Invalid username provided.' }); return; } // Enhanced sanitization const sanitizedUsername = username .replace(/[^a-zA-Z0-9\s\-_.]/g, '') .trim() .slice(0, 100); if (!sanitizedUsername) { await interaction.editReply({ content: '❌ Username must contain valid characters (letters, numbers, spaces, hyphens, underscores, or periods).' }); return; } // Log sanitized input for monitoring this.logger.debug('User search input:', { original: username, sanitized: sanitizedUsername, game: gameFilter, userId: interaction.user.id, timestamp: new Date().toISOString() }); const userData = await this.playerService.findUserByUsername(sanitizedUsername); if (!userData || !userData.success) { await interaction.editReply({ content: '❌ User not found or an error occurred while fetching data.' }); return; } const playerData = typeof userData.player_data === 'string' ? JSON.parse(userData.player_data) : userData.player_data; // Check if the user is on a team for the specified game const isOnTeam = playerData.teams && Object.values(playerData.teams) .some(team => team.game_name === gameFilter); // Check if the user has played the specified game const hasPlayedGame = playerData.stats?.games && Object.keys(playerData.stats.games) .some(game => game.toLowerCase() === gameFilter.toLowerCase()); if (!hasPlayedGame) { const teamMessage = isOnTeam ? `\nThey are on a team for ${gameFilter} but haven't played any matches yet.` : ''; // Create a basic embed with user info const basicEmbed = new EmbedBuilder() .setTitle(`User: ${playerData.username || 'Unknown'}`) .setDescription(playerData.profile?.bio || 'No bio available') .setColor('#0099ff'); // Add avatar if available if (playerData.profile?.avatar) { basicEmbed.setThumbnail( `https://www.vrbattles.gg/assets/uploads/profile/${playerData.profile.avatar}` ); } // Add badge image if available if (playerData.profile?.current_level_badge) { const badgeImageUrl = this.playerService.getBadgeImageUrl(playerData.profile.current_level_badge); basicEmbed.setImage(badgeImageUrl); } else { basicEmbed.setImage('https://www.vrbattles.gg/assets/images/Qubi/vrwearcubesatad.png'); } // Add basic profile fields const profileFields = []; const profile = playerData.profile || {}; if (profile.country) profileFields.push({ name: 'Country', value: profile.country, inline: true }); if (profile.level) profileFields.push({ name: 'Level', value: profile.level.toString(), inline: true }); if (profile.xp) profileFields.push({ name: 'Total XP', value: profile.xp.toString(), inline: true }); if (profile.prestige) profileFields.push({ name: 'Prestige', value: profile.prestige.toString(), inline: true }); if (profileFields.length > 0) { basicEmbed.addFields(profileFields); } // Add message about no game stats basicEmbed.addFields({ name: `${gameFilter} Status`, value: `❌ No match history found for ${gameFilter}${teamMessage}` }); const row = this.createActionRow(playerData.username); await interaction.editReply({ embeds: [basicEmbed], components: [row] }); return; } const embed = EmbedBuilders.createUserEmbed(playerData, gameFilter, this.playerService); const row = this.createActionRow(playerData.username); await interaction.editReply({ embeds: [embed], components: [row] }); } catch (error) { this.logger.error('Error in handleFindUser:', { error: error.message, username: interaction.options.getString('username'), game: interaction.options.getString('game'), userId: interaction.user.id, timestamp: new Date().toISOString() }); await interaction.editReply({ content: '❌ An error occurred while searching for the user.' }); } } async handleMatchHistory(interaction) { try { const username = interaction.options.getString('username'); const gameFilter = interaction.options.getString('game'); const userData = await this.playerService.findUserByUsername(username); if (!userData || !userData.success) { await interaction.editReply({ content: '❌ User not found or an error occurred while fetching data.', }); return; } const playerData = typeof userData.player_data === 'string' ? JSON.parse(userData.player_data) : userData.player_data; // Extract matches from player data const matches = Object.values(playerData.matches || {}) .filter(match => !gameFilter || match.game_name.toLowerCase() === gameFilter.toLowerCase()) .sort((a, b) => new Date(b.start_time) - new Date(a.start_time)) .slice(0, 10); const embed = EmbedBuilders.createMatchHistoryEmbed(playerData, matches); const row = EmbedBuilders.createActionRow(playerData.username, this.playerService); await interaction.editReply({ embeds: [embed], components: [row], }); } catch (error) { this.logger.error('Error in handleMatchHistory:', { error: error.message, username: interaction.options.getString('username'), timestamp: new Date().toISOString() }); throw error; } } async handleFindTeam(interaction) { try { const teamName = interaction.options.getString('teamname'); const gameFilter = interaction.options.getString('game'); // Input validation if (!teamName || typeof teamName !== 'string') { await interaction.editReply({ content: '❌ Invalid team name provided.', ephemeral: false }); return; } // Enhanced sanitization const sanitizedTeamName = teamName .replace(/[^a-zA-Z0-9\s\-_.]/g, '') .trim() .slice(0, 100); if (!sanitizedTeamName) { await interaction.editReply({ content: '❌ Team name must contain valid characters (letters, numbers, spaces, hyphens, underscores, or periods).', ephemeral: false }); return; } // Log sanitized input for monitoring this.logger.debug('Team search input:', { original: teamName, sanitized: sanitizedTeamName, game: gameFilter, userId: interaction.user.id, timestamp: new Date().toISOString() }); const teamData = await this.playerService.findTeamByName(sanitizedTeamName, gameFilter); if (!teamData || !teamData.success || !teamData.teams || teamData.teams.length === 0) { await interaction.editReply({ content: '❌ No teams found matching your search criteria.', ephemeral: false }); return; } const embeds = EmbedBuilders.createTeamEmbed(teamData.teams); await interaction.editReply({ embeds: embeds, components: [], ephemeral: false }); } catch (error) { this.logger.error('Error in handleFindTeam:', { error: error.message, teamName: interaction.options.getString('teamname'), game: interaction.options.getString('game'), userId: interaction.user.id, timestamp: new Date().toISOString() }); await interaction.editReply({ content: '❌ An error occurred while searching for teams.', ephemeral: false }); } } createActionRow(username) { return new ActionRowBuilder().addComponents( new ButtonBuilder() .setLabel('🔵 View Profile') .setStyle(ButtonStyle.Link) .setURL(`https://www.vrbattles.gg/profile/${username}`) ); } } module.exports = CommandHandler;