398 lines
15 KiB
JavaScript
398 lines
15 KiB
JavaScript
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; |