From 26a5edeb4f94d11001bf9a60ee84bd36504b50ea Mon Sep 17 00:00:00 2001 From: VinceC <33974776+VinceC3@users.noreply.github.com> Date: Thu, 28 Nov 2024 04:47:58 -0600 Subject: [PATCH] all commands and buttons are working --- deploy-commands.js | 7 +- src/Bot.js | 85 +++----- src/commands/CommandHandler.js | 242 +++++++++++++++------- src/commands/SubscriptionCommands.js | 77 ++++--- src/services/PlayerService.js | 31 ++- src/services/ServerRegistrationService.js | 102 ++++++--- src/utils/embedBuilders.js | 232 +++++++++++++++++---- 7 files changed, 530 insertions(+), 246 deletions(-) diff --git a/deploy-commands.js b/deploy-commands.js index 6ff2ff4..687ff38 100644 --- a/deploy-commands.js +++ b/deploy-commands.js @@ -35,6 +35,7 @@ const commands = [ new SlashCommandBuilder() .setName("subscribe") .setDescription("Subscribe to game notifications") + .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) .addStringOption((option) => option .setName("game") @@ -60,6 +61,7 @@ const commands = [ new SlashCommandBuilder() .setName("unsubscribe") .setDescription("Unsubscribe from game notifications") + .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) .addStringOption((option) => option .setName("game") @@ -82,10 +84,11 @@ const commands = [ .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), new SlashCommandBuilder() .setName("list_subscriptions") - .setDescription("List all game subscriptions for this server"), + .setDescription("List all game subscriptions for this server") + .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), ]; -const rest = new REST({ version: "10" }).setToken(process.env.BOT_TOKEN); +const rest = new REST({ version: "10" }).setToken(process.env.DISCORD_TOKEN); (async () => { try { diff --git a/src/Bot.js b/src/Bot.js index cddf99e..5e02baf 100644 --- a/src/Bot.js +++ b/src/Bot.js @@ -22,16 +22,16 @@ class Bot { // Initialize services this.playerService = new PlayerService(logger); this.serverRegistrationService = new ServerRegistrationService(supabase, logger); + this.subscriptionCommands = new SubscriptionCommands(supabase, logger); // Initialize command handlers this.commandHandler = new CommandHandler( this.playerService, this.supabase, this.logger, - this.serverRegistrationService + this.serverRegistrationService, + this.subscriptionCommands ); - - this.subscriptionCommands = new SubscriptionCommands(supabase, logger); // Setup event handlers this.client.on('ready', () => { @@ -62,66 +62,24 @@ class Bot { return; } - // Set the lock before any async operations this.processingLock.set(lockKey, true); try { - // Special handling for register_server command - if (interaction.isCommand() && interaction.commandName === 'register_server') { - // Send initial response immediately - await interaction.reply({ - content: 'Attempting to register server...', - ephemeral: true - }); + if (interaction.isCommand()) { + // Send immediate acknowledgment + await interaction.deferReply({ ephemeral: true }); - const guildId = interaction.guildId; - const serverName = interaction.guild.name; - - this.logger.debug('Starting server registration', { - guildId, - serverName, + // Process command asynchronously + this.logger.debug('Processing command', { + command: interaction.commandName, + guild: interaction.guild?.name, timestamp: new Date().toISOString() }); - try { - // Attempt database operation first - const result = await this.serverRegistrationService.registerServer(guildId, serverName); - - this.logger.info('Server registration database operation completed', { - status: result.status, - guildId, - serverId: result.server.id, - timestamp: new Date().toISOString() - }); - - // Update the response with the result - const message = result.status === 'exists' - ? '⚠️ This Discord server is already registered with BattleBot.' - : '✅ Server successfully registered with BattleBot! You can now use subscription commands.'; - - await interaction.editReply({ - content: message, - ephemeral: true - }); - - } catch (dbError) { - this.logger.error('Server registration failed:', { - error: dbError.message, - stack: dbError.stack, - guildId, - timestamp: new Date().toISOString() - }); - - await interaction.editReply({ - content: '❌ Failed to register server. Please try again.', - ephemeral: true - }); - } - } else if (interaction.isCommand()) { - // Handle all other commands through the command handler await this.commandHandler.handleCommand(interaction); } else if (interaction.isButton()) { - // Handle button interactions + // For buttons, we'll still use deferUpdate to show the loading state + await interaction.deferUpdate(); await this.commandHandler.handleButton(interaction); } } catch (error) { @@ -129,16 +87,21 @@ class Bot { error: error.message, stack: error.stack, command: interaction.commandName, - guild: interaction.guild?.name, - timestamp: new Date().toISOString() + guild: interaction.guild?.name }); + + try { + if (!interaction.replied) { + await interaction.editReply({ + content: '❌ An error occurred while processing your request.', + ephemeral: true + }); + } + } catch (replyError) { + this.logger.error('Failed to send error response:', replyError); + } } finally { this.processingLock.delete(lockKey); - this.logger.debug('Interaction processing completed', { - id: lockKey, - command: interaction.commandName, - timestamp: new Date().toISOString() - }); } } } diff --git a/src/commands/CommandHandler.js b/src/commands/CommandHandler.js index 4a2a851..ae3c0cd 100644 --- a/src/commands/CommandHandler.js +++ b/src/commands/CommandHandler.js @@ -1,9 +1,13 @@ +const { ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); +const EmbedBuilders = require('../utils/EmbedBuilders'); + class CommandHandler { - constructor(playerService, supabase, logger, serverRegistrationService) { + constructor(playerService, supabase, logger, serverRegistrationService, subscriptionCommands) { this.playerService = playerService; this.supabase = supabase; this.logger = logger; this.serverRegistrationService = serverRegistrationService; + this.subscriptionCommands = subscriptionCommands; } async handleCommand(interaction) { @@ -21,50 +25,66 @@ class CommandHandler { 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; default: - await interaction.editReply({ content: 'Unknown command', ephemeral: true }); + await interaction.editReply({ + content: '❌ Unknown command', + ephemeral: true + }); } } catch (error) { - // Only log the error, let Bot class handle the response this.logger.error('Command handling error:', { command: interaction.commandName, error: error.message, stack: error.stack }); - throw error; // Re-throw to let Bot handle it + + 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) { - if (!interaction.deferred) { - this.logger.warn('Interaction not deferred, cannot proceed', { - guildId: interaction.guildId - }); - return; - } - - // Get the server details first - const guildId = interaction.guildId; - const serverName = interaction.guild.name; - - this.logger.debug('Starting server registration', { - guildId, - serverName, - timestamp: new Date().toISOString() - }); - try { - // Log before database operation - this.logger.debug('Calling ServerRegistrationService.registerServer', { + // 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 first + // Attempt the database operation const result = await this.serverRegistrationService.registerServer(guildId, serverName); - // Log the result immediately + // Log the result this.logger.debug('Server registration result received', { status: result.status, serverId: result.server?.id, @@ -72,74 +92,146 @@ class CommandHandler { timestamp: new Date().toISOString() }); - // Only after successful database operation, send the response - try { - const message = result.status === 'exists' - ? 'This server is already registered!' - : 'Server successfully registered! You can now use subscription commands.'; - - 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 (replyError) { - // Log that we couldn't send the response but the registration was successful - this.logger.warn('Could not send response, but server registration was successful', { - error: replyError.message, - guildId, - serverId: result.server.id, - 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.'; } - return result; + 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) { - // Log the error but let Bot.js handle the error response - this.logger.error('Failed to register server:', { + this.logger.error('Error in handleRegisterServer:', { error: error.message, stack: error.stack, - guildId, - serverName, + 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'); + + 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.', + ephemeral: true + }); + return; + } + + const playerData = typeof userData.player_data === 'string' + ? JSON.parse(userData.player_data) + : userData.player_data; + + const embed = EmbedBuilders.createUserEmbed(playerData, gameFilter, this.playerService); + const row = this.createActionRow(playerData.username); + + await interaction.editReply({ + embeds: [embed], + components: [row], + ephemeral: true + }); + } catch (error) { + this.logger.error('Error in handleFindUser:', { + error: error.message, + username: interaction.options.getString('username'), timestamp: new Date().toISOString() }); throw error; } } - async handleButton(interaction) { - const [action, ...params] = interaction.customId.split(':'); - - switch (action) { - case 'refresh': - await this.handleFindUser(interaction, params[0]); - break; - default: + 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: 'Unknown button interaction', + content: '❌ User not found or an error occurred while fetching data.', ephemeral: true }); + 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], + ephemeral: true + }); + } catch (error) { + this.logger.error('Error in handleMatchHistory:', { + error: error.message, + username: interaction.options.getString('username'), + timestamp: new Date().toISOString() + }); + throw error; } } - // Helper methods for creating embeds and action rows remain unchanged - createUserEmbed(playerData, gameFilter) { - // ... (keep existing implementation) - } - - createMatchHistoryEmbed(playerData, matches) { - // ... (keep existing implementation) - } - createActionRow(username) { - // ... (keep existing implementation) + return new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel('🔵 View Profile') + .setStyle(ButtonStyle.Link) + .setURL(`https://www.vrbattles.gg/profile/${username}`), + new ButtonBuilder() + .setLabel('🟡 Join Main Discord') + .setStyle(ButtonStyle.Link) + .setURL('https://discord.gg/j3DKVATPGQ') + ); } } diff --git a/src/commands/SubscriptionCommands.js b/src/commands/SubscriptionCommands.js index d118525..8f2e26c 100644 --- a/src/commands/SubscriptionCommands.js +++ b/src/commands/SubscriptionCommands.js @@ -232,57 +232,72 @@ class SubscriptionCommands { async handleListSubscriptions(interaction) { try { - // Defer the reply first - await interaction.deferReply({ ephemeral: true }); - const guildId = interaction.guildId; this.logger.debug("Fetching subscriptions", { guildId }); const subscriptions = await this.getSubscriptions(guildId); + if (!subscriptions || subscriptions.length === 0) { - await interaction.editReply("This server has no game subscriptions."); + await interaction.editReply({ + content: "📝 This server has no game subscriptions.", + ephemeral: true + }); return; } - const embed = this.buildSubscriptionsEmbed(subscriptions); - await interaction.editReply({ embeds: [embed] }); + const embed = new EmbedBuilder() + .setColor("#0099ff") + .setTitle("🎮 Game Subscriptions") + .setDescription("Current game notification subscriptions for this server:") + .addFields( + subscriptions.map((sub) => ({ + name: `${sub.game_name || 'Unknown Game'}`, + value: sub.notification_channel_id ? `<#${sub.notification_channel_id}>` : 'No channel set', + inline: true, + })) + ) + .setTimestamp() + .setFooter({ text: "VRBattles Match System" }); + + await interaction.editReply({ + embeds: [embed], + ephemeral: true + }); } catch (error) { this.logger.error("Error in handleListSubscriptions:", { - error, + error: error.message, stack: error.stack, - message: error.message, + guildId: interaction.guildId }); - try { - if (!interaction.replied && !interaction.deferred) { - await interaction.reply({ - content: "An error occurred while fetching subscriptions.", - ephemeral: true, - }); - } else if (interaction.deferred) { - await interaction.editReply( - "An error occurred while fetching subscriptions." - ); - } - } catch (e) { - this.logger.error("Error sending error message:", e); - } + await interaction.editReply({ + content: "❌ An error occurred while fetching subscriptions.", + ephemeral: true + }); } } - // Also update the getSubscriptions method to remove the interaction parameter async getSubscriptions(guildId) { - const { data: subscriptions, error } = await this.supabase - .from("active_subscriptions") - .select("*") - .eq("discord_server_id", guildId); + try { + const { data: subscriptions, error } = await this.supabase + .from("active_subscriptions") + .select("*") + .eq("discord_server_id", guildId); - if (error) { - this.logger.error("Error fetching subscriptions", { error }); + if (error) { + this.logger.error("Error fetching subscriptions", { error }); + return null; + } + + return subscriptions; + } catch (error) { + this.logger.error("Error in getSubscriptions:", { + error: error.message, + stack: error.stack, + guildId + }); return null; } - - return subscriptions; } // Helper Methods diff --git a/src/services/PlayerService.js b/src/services/PlayerService.js index 2c7b805..98e6ffd 100644 --- a/src/services/PlayerService.js +++ b/src/services/PlayerService.js @@ -8,15 +8,34 @@ class PlayerService { async findUserByUsername(username) { try { - const response = await axios.get( - `${this.baseUrl}/api/get_player_data_by_username/${encodeURIComponent(username)}`, - { - timeout: 5000 + console.log(`Fetching data for username: ${username}`); + const url = `${this.baseUrl}/api/get_player_data_by_username/${encodeURIComponent(username)}`; + console.log(`API URL: ${url}`); + + const response = await axios.get(url, { + timeout: 5000 + }); + + console.log('API Response:', JSON.stringify(response.data, null, 2)); + + if (response.data && response.data.success) { + // Parse player_data if it's a string + if (typeof response.data.player_data === 'string') { + try { + response.data.player_data = JSON.parse(response.data.player_data); + } catch (parseError) { + console.error('Error parsing player_data:', parseError); + } } - ); + } + return response.data; } catch (error) { - console.error('Error fetching user data:', error); + console.error('Error fetching user data:', { + message: error.message, + response: error.response?.data, + status: error.response?.status + }); return null; } } diff --git a/src/services/ServerRegistrationService.js b/src/services/ServerRegistrationService.js index 8cdfcf4..9f613c3 100644 --- a/src/services/ServerRegistrationService.js +++ b/src/services/ServerRegistrationService.js @@ -2,12 +2,18 @@ class ServerRegistrationService { constructor(supabase, logger) { this.supabase = supabase; this.logger = logger; - // Log when service is instantiated this.logger.info('ServerRegistrationService initialized'); } async registerServer(guildId, serverName) { - // Add entry point log + // Input validation + if (!guildId || typeof guildId !== 'string') { + throw new Error('Invalid guildId provided'); + } + if (!serverName || typeof serverName !== 'string') { + throw new Error('Invalid serverName provided'); + } + this.logger.info('registerServer method called', { guildId, serverName, @@ -15,58 +21,72 @@ class ServerRegistrationService { }); try { - this.logger.debug('Starting server registration process', { - guildId, - serverName, - timestamp: new Date().toISOString() - }); - + // Check if server exists const { data: existingServer, error: checkError } = await this.supabase .from('servers') .select('*') .eq('discord_server_id', guildId) .single(); - // Log the database query result this.logger.debug('Database query result', { hasExistingServer: !!existingServer, hasError: !!checkError, errorCode: checkError?.code }); + // If server exists, update the name if it changed if (existingServer) { - this.logger.info('Server already exists', { - serverId: existingServer.id, - guildId - }); + if (existingServer.server_name !== serverName) { + const { data: updatedServer, error: updateError } = await this.supabase + .from('servers') + .update({ server_name: serverName }) + .eq('id', existingServer.id) + .select() + .single(); + + if (updateError) { + this.logger.error('Failed to update server name', { error: updateError }); + throw updateError; + } + + this.logger.info('Server name updated', { + serverId: updatedServer.id, + oldName: existingServer.server_name, + newName: serverName + }); + return { status: 'updated', server: updatedServer }; + } + return { status: 'exists', server: existingServer }; } - if (!existingServer || checkError?.code === 'PGRST116') { - this.logger.debug('Attempting to create new server'); - const { data: newServer, error: insertError } = await this.supabase - .from('servers') - .insert([{ - discord_server_id: guildId, - server_name: serverName, - active: true - }]) - .select() - .single(); + // Create new server if it doesn't exist + const { data: newServer, error: insertError } = await this.supabase + .from('servers') + .insert([{ + discord_server_id: guildId, + server_name: serverName, + active: true, + created_at: new Date().toISOString() + }]) + .select() + .single(); - if (insertError) { - this.logger.error('Failed to insert new server', { error: insertError }); - throw insertError; - } - - this.logger.info('New server created successfully', { - serverId: newServer.id, - guildId + if (insertError) { + this.logger.error('Failed to insert new server', { + error: insertError, + details: insertError.details }); - return { status: 'created', server: newServer }; + throw insertError; } - throw checkError; + this.logger.info('New server created successfully', { + serverId: newServer.id, + guildId, + serverName + }); + return { status: 'created', server: newServer }; + } catch (error) { this.logger.error('Error in registerServer:', { error: error.message, @@ -77,6 +97,20 @@ class ServerRegistrationService { throw error; } } + + // Helper method to validate server data + validateServerData(guildId, serverName) { + if (!guildId || typeof guildId !== 'string') { + return { valid: false, error: 'Invalid guildId' }; + } + if (!serverName || typeof serverName !== 'string') { + return { valid: false, error: 'Invalid serverName' }; + } + if (serverName.length > 100) { + return { valid: false, error: 'Server name too long' }; + } + return { valid: true }; + } } module.exports = ServerRegistrationService; \ No newline at end of file diff --git a/src/utils/embedBuilders.js b/src/utils/embedBuilders.js index 65d98eb..5041248 100644 --- a/src/utils/embedBuilders.js +++ b/src/utils/embedBuilders.js @@ -1,41 +1,199 @@ -const { EmbedBuilder } = require('discord.js'); +const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); -function createMatchRequestEmbed(matchData) { - const matchDateTime = new Date(matchData.match_date); - const formattedDateTime = matchDateTime.toLocaleString(); +class EmbedBuilders { + static createUserEmbed(playerData, gameFilter, playerService) { + const profile = playerData.profile || {}; + const embed = new EmbedBuilder() + .setTitle(`User: ${playerData.username || 'Unknown'}`) + .setDescription(profile.bio || 'No bio available') + .setColor('#0099ff'); - return new EmbedBuilder() - .setColor('#00ff00') // Green for new match requests - .setTitle('🎮 New Match Request') - .setDescription(`A new match has been requested for ${matchData.game_name}`) - .addFields( - { - name: 'Game Details', - value: [ - `📋 **Match Type:** ${matchData.match_type}`, - `👥 **Team Size:** ${matchData.team_size}v${matchData.team_size}`, - `🎯 **Class:** ${matchData.match_class}` - ].join('\n'), - inline: false - }, - { - name: 'Match Settings', - value: [ - `🌍 **Region:** ${matchData.region}`, - `📅 **Date & Time:** ${formattedDateTime}`, - ].join('\n'), - inline: false - }, - { - name: 'Status', - value: `📊 **Current Status:** ${matchData.status}`, - inline: false - } - ) - .setTimestamp() - .setFooter({ text: 'VRBattles Match System' }); + // Add thumbnail (avatar) + if (profile.avatar) { + embed.setThumbnail( + `https://www.vrbattles.gg/assets/uploads/profile/${profile.avatar}` + ); + } + + // Add badge image using PlayerService + if (profile.current_level_badge) { + const badgeImageUrl = playerService.getBadgeImageUrl(profile.current_level_badge); + embed.setImage(badgeImageUrl); + } + + // Add profile fields + const profileFields = []; + 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) { + embed.addFields(profileFields); + } + + // Add game stats if available + const games = playerData.stats?.games; + if (games) { + Object.entries(games).forEach(([gameName, gameData]) => { + if (gameFilter && gameName.toLowerCase() !== gameFilter.toLowerCase()) return; + + embed.addFields({ name: gameName, value: '\u200B' }); + Object.entries(gameData).forEach(([mode, stats]) => { + if (mode === 'Overall') return; + + const statFields = []; + if (stats.matches) statFields.push(`Matches: ${stats.matches}`); + if (stats.wins) statFields.push(`Wins: ${stats.wins}`); + if (stats.losses) statFields.push(`Losses: ${stats.losses}`); + if (stats.win_rate) statFields.push(`Win Rate: ${stats.win_rate}`); + + if (statFields.length > 0) { + embed.addFields({ + name: mode, + value: statFields.join(' | '), + inline: true + }); + } + }); + }); + } + + // Add teams if available + const teams = playerData.teams; + if (teams && Object.keys(teams).length > 0) { + const teamList = Object.values(teams) + .map(team => `- **${team.team_name}** (${team.game_name} - ${team.game_mode})`) + .join('\n'); + + embed.addFields( + { name: '\u200B', value: '**Teams**' }, + { name: 'Team List', value: teamList || 'No teams available' } + ); + } + + // Add recent matches if available + const matches = playerData.matches; + if (matches && Object.keys(matches).length > 0) { + const matchList = Object.values(matches) + .sort((a, b) => new Date(b.start_time) - new Date(a.start_time)) + .slice(0, 3) + .map(match => { + const date = new Date(match.start_time).toLocaleDateString(); + const team1 = match.team1_name || 'Team 1'; + const team2 = match.team2_name || 'Team 2'; + const score = match.score || 'N/A'; + return `- **${team1}** vs **${team2}** on ${date} - Result: ${score}`; + }) + .join('\n'); + + embed.addFields( + { name: '\u200B', value: '**Recent Matches**' }, + { name: 'Match History', value: matchList || 'No recent matches' } + ); + } + + return embed; + } + + static createMatchHistoryEmbed(playerData, matches) { + const embed = new EmbedBuilder() + .setTitle(`Match History for ${playerData.username}`) + .setColor('#0099ff') + .setDescription(`Last ${matches.length} matches`); + + if (playerData.profile?.avatar) { + embed.setThumbnail( + `https://www.vrbattles.gg/assets/uploads/profile/${playerData.profile.avatar}` + ); + } + + // Add match stats + const wins = matches.filter(m => { + const isTeam1 = m.team1_name === playerData.username; + return m.winner_id === (isTeam1 ? "1" : "2"); + }).length; + + const winRate = ((wins / matches.length) * 100).toFixed(1); + + embed.addFields( + { name: 'Recent Win Rate', value: `${winRate}%`, inline: true }, + { name: 'Matches Analyzed', value: matches.length.toString(), inline: true }, + { name: 'Recent Wins', value: wins.toString(), inline: true } + ); + + // Add individual matches + matches.forEach((match, index) => { + const date = new Date(match.start_time).toLocaleDateString(); + const isTeam1 = match.team1_name === playerData.username; + const opponent = isTeam1 ? match.team2_name : match.team1_name; + const won = match.winner_id === (isTeam1 ? "1" : "2"); + const score = match.score || '0 - 0'; + const gameMode = match.game_mode ? ` (${match.game_mode})` : ''; + + const matchResult = won ? '✅ Victory' : '❌ Defeat'; + + embed.addFields({ + name: `Match ${index + 1} - ${matchResult}`, + value: `🎮 **${match.game_name}${gameMode}**\n` + + `🆚 vs ${opponent}\n` + + `📊 Score: ${score}\n` + + `📅 ${date}`, + inline: false + }); + }); + + return embed; + } + + static createMatchRequestEmbed(matchData) { + const matchDateTime = new Date(matchData.match_date); + const formattedDateTime = matchDateTime.toLocaleString(); + + return new EmbedBuilder() + .setColor('#00ff00') // Green for new match requests + .setTitle('🎮 New Match Request') + .setDescription(`A new match has been requested for ${matchData.game_name}`) + .addFields( + { + name: 'Game Details', + value: [ + `📋 **Match Type:** ${matchData.match_type}`, + `👥 **Team Size:** ${matchData.team_size}v${matchData.team_size}`, + `🎯 **Class:** ${matchData.match_class}` + ].join('\n'), + inline: false + }, + { + name: 'Match Settings', + value: [ + `🌍 **Region:** ${matchData.region}`, + `📅 **Date & Time:** ${formattedDateTime}`, + ].join('\n'), + inline: false + }, + { + name: 'Status', + value: `📊 **Current Status:** ${matchData.status}`, + inline: false + } + ) + .setTimestamp() + .setFooter({ text: 'VRBattles Match System' }); + } + + static createActionRow(username, playerService) { + return new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel('🔵 View Profile') + .setStyle(ButtonStyle.Link) + .setURL(playerService.getProfileUrl(username)), + new ButtonBuilder() + .setLabel('🟡 Join Main Discord') + .setStyle(ButtonStyle.Link) + .setURL('https://discord.gg/j3DKVATPGQ') + ); + } } -module.exports = { - createMatchRequestEmbed -}; \ No newline at end of file +module.exports = EmbedBuilders; \ No newline at end of file