Preview:
import { Client, GatewayIntentBits, Partials, PermissionsBitField, ChannelType, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import dotenv from 'dotenv';

dotenv.config();

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const prefix = process.env.DISCORD_PREFIX || '!';
const dataFilePath = new URL('data.json', import.meta.url).pathname;
let { playersELO = {}, playerWarnings = {}, karma = {}, playerStats = {} } = JSON.parse(fs.readFileSync(dataFilePath, 'utf-8') || "{}");

let queue = [];
let matchCounter = 1;
let matches = {};

const maps = ['BW', 'SB', 'PORT', 'COMP', 'ANK', 'MEX', 'EE'];

const client = new Client({
  intents: [
    GatewayIntentBits.Guilds,
    GatewayIntentBits.GuildMessages,
    GatewayIntentBits.MessageContent,
    GatewayIntentBits.GuildMembers,
    GatewayIntentBits.GuildVoiceStates,
    GatewayIntentBits.GuildPresences,
    GatewayIntentBits.GuildMessageReactions
  ],
  partials: [Partials.Channel, Partials.Message, Partials.Reaction]
});

client.once('ready', async () => {
  console.log(`Bot is ready! Logged in as ${client.user.tag}`);
  await setupQueueMessage();
});

client.on('messageCreate', async (message) => {
  if (!message.content.startsWith(prefix) || message.author.bot) return;
  const args = message.content.slice(prefix.length).trim().split(/ +/);
  const command = args.shift().toLowerCase();

  try {
    switch (command) {
      case 'report': await reportCommand(message, args); break;
      case 'clearqueue': await clearQueueCommand(message); break;
      case 'resetelo': await resetEloCommand(message); break;
      case 'giveelo': await giveEloCommand(message, args); break;
      case 'setmatchlogchannel': await setMatchLogChannelCommand(message, args); break;
      case 'profile': await profileCommand(message, args); break;
      case 'lb': case 'leaderboard': await leaderboardCommand(message); break;
      case 'elo': await eloCommand(message, args); break;
      case 'pick': await pickCommand(message, args); break;
      case 'needsub': await needSubCommand(message, args); break;
      case 'replace': await replaceCommand(message, args); break;
      case 'pt': await ptCommand(message, args); break;
      case 'cooldown': await suspendUserCommand(message, args); break;
      case 'removecd': await unSuspendUserCommand(message, args); break;
      case 'cancel': await cancelMatchCommand(message, args); break;
      case 'warn': await warnCommand(message, args); break;
      case 'warns': await warnsCommand(message, args); break;
      case 'warningsreset': await warningResetCommand(message); break;
    }
  } catch (error) {
    console.error(`Error executing command ${command}:`, error);
    message.reply('There was an error executing that command.');
  }
});

client.on('interactionCreate', async interaction => {
  try {
    if (interaction.isButton()) {
      await handleButtonInteraction(interaction);
    } else if (interaction.isCommand()) {
      const command = interaction.commandName;
      switch (command) {
        case 'setupreaction':
          await setupQueueMessage(interaction);
          break;
        case 'eloreset':
          await eloresetCommand(interaction);
          break;
        case 'warningreset':
          await warningresetCommand(interaction);
          break;
        case 'leaderboard':
          await leaderboardCommand(interaction);
          break;
        default:
          await interaction.reply({ content: 'Unknown command interaction.', ephemeral: true });
          break;
      }
    }
  } catch (error) {
    console.error('Error handling interaction:', error);
    if (interaction.isCommand() || interaction.isButton()) {
      await interaction.reply({ content: 'There was an error handling this interaction.', ephemeral: true });
    }
  }
});


const setupQueueMessage = async () => {
  try {
    const channel = await client.channels.fetch(process.env.QUEUE_CHANNEL_ID);
    const embed = new EmbedBuilder()
      .setColor('#00ff26') // Set a custom color for the embed
      .setTitle('🎮 CSL West Competitive Queue 🎮')
      .setDescription('Use the buttons below to join or leave the queue.\n\n**Current Queue:**\nNo one in the queue (0/10)')
      .setFooter({ text: 'Powered by Maverick', iconURL: 'https://i.imgur.com/wTF3RuJ.png' }); // Set an icon for the footer if you have one

    const row = new ActionRowBuilder()
      .addComponents(
        new ButtonBuilder()
          .setCustomId('join_queue')
          .setLabel('Join Queue')
          .setStyle(ButtonStyle.Success)
          .setEmoji('✅'), // Add an emoji to the button
        new ButtonBuilder()
          .setCustomId('leave_queue')
          .setLabel('Leave Queue')
          .setStyle(ButtonStyle.Danger)
          .setEmoji('❌') // Add an emoji to the button
      );

    await channel.send({ embeds: [embed], components: [row] });
  } catch (error) {
    console.error('Error setting up queue message:', error);
  }
};

const joinQueue = async (interaction) => {
  try {
    await interaction.deferReply({ ephemeral: true });

    const user = interaction.user;
    if (!queue.includes(user.id)) {
      queue.push(user.id);
      await updateQueueMessage();
      if (queue.length === 10) {
        await createMatch(interaction.guild);
      }
      await interaction.editReply({ content: 'You have joined the queue!' });
    } else {
      await interaction.editReply({ content: 'You are already in the queue!' });
    }
  } catch (error) {
    console.error('Error joining queue:', error);
    if (!interaction.replied) {
      await interaction.editReply({ content: 'There was an error joining the queue.' });
    }
  }
};

const leaveQueue = async (interaction) => {
  try {
    await interaction.deferReply({ ephemeral: true });

    const user = interaction.user;
    const index = queue.indexOf(user.id);
    if (index > -1) {
      queue.splice(index, 1);
      await updateQueueMessage();
      await interaction.editReply({ content: 'You have left the queue!' });
    } else {
      await interaction.editReply({ content: 'You are not in the queue!' });
    }
  } catch (error) {
    console.error('Error leaving queue:', error);
    if (!interaction.replied) {
      await interaction.editReply({ content: 'There was an error leaving the queue.' });
    }
  }
};

const updateQueueMessage = async () => {
  try {
    const channel = await client.channels.fetch(process.env.QUEUE_CHANNEL_ID);
    const messages = await channel.messages.fetch({ limit: 10 });
    const queueMessage = messages.find(msg => msg.embeds[0]?.title === '🎮 CSL West Competitive Queue 🎮');

    if (queueMessage) {
      const description = queue.length > 0 
        ? `${queue.map(id => `<@${id}>`).join('\n')}\n\n**(${queue.length}/10)**`
        : 'No one in the queue (0/10)';

      const embed = new EmbedBuilder()
        .setColor('#00ff26')
        .setTitle('🎮 CSL West Competitive Queue 🎮')
        .setDescription(`Use the buttons below to join or leave the queue.\n\n**Current Queue:**\n${description}`)
        .setFooter({ text: 'Powered by Maverick', iconURL: 'https://i.imgur.com/wTF3RuJ.png' });

      const row = new ActionRowBuilder()
        .addComponents(
          new ButtonBuilder()
            .setCustomId('join_queue')
            .setLabel('Join Queue')
            .setStyle(ButtonStyle.Success)
            .setEmoji('✅'),
          new ButtonBuilder()
            .setCustomId('leave_queue')
            .setLabel('Leave Queue')
            .setStyle(ButtonStyle.Danger)
            .setEmoji('❌')
        );

      await queueMessage.edit({ embeds: [embed], components: [row] });
    }
  } catch (error) {
    console.error('Error updating queue message:', error);
  }
};

const createMatch = async (guild) => {
  try {
    const matchCategory = guild.channels.cache.get(process.env.MATCH_CATEGORY_ID);
    const team1Voice = await guild.channels.create({
      name: `Match ${matchCounter} - Team 1`,
      type: ChannelType.GuildVoice,
      parent: matchCategory,
      userLimit: 6,
      permissionOverwrites: [
        {
          id: guild.roles.everyone.id,
          allow: [PermissionsBitField.Flags.Connect],
        },
      ],
    });
    const team2Voice = await guild.channels.create({
      name: `Match ${matchCounter} - Team 2`,
      type: ChannelType.GuildVoice,
      parent: matchCategory,
      userLimit: 6,
      permissionOverwrites: [
        {
          id: guild.roles.everyone.id,
          allow: [PermissionsBitField.Flags.Connect],
        },
      ],
    });
    const matchText = await guild.channels.create({
      name: `match-${matchCounter}`,
      type: ChannelType.GuildText,
      parent: matchCategory,
      permissionOverwrites: [
        {
          id: guild.roles.everyone.id,
          allow: [PermissionsBitField.Flags.ViewChannel, PermissionsBitField.Flags.SendMessages],
        },
      ],
    });

    const team1Captain = queue.shift();
    const team2Captain = queue.shift();
    const remainingPlayers = queue.splice(0, 8);
    const randomMap = maps[Math.floor(Math.random() * maps.length)];
    matches[matchCounter] = {
      team1: [team1Captain],
      team2: [team2Captain],
      remaining: remainingPlayers,
      pickingOrder: ['team1', 'team2', 'team2', 'team1', 'team1', 'team2', 'team1'],
      currentPick: 0,
      mapVotes: {}, // Ensure mapVotes is initialized
      chosenMap: randomMap
    };

    console.log(`Match ${matchCounter} created with map ${randomMap}`);

    await updateQueueMessage();
    await updateMatchMessage(matchCounter);

    // Start voting timer
    setTimeout(async () => {
      if (!matches[matchCounter]) return; // Match might have ended or been cancelled
      const mostVotedMap = Object.entries(matches[matchCounter].mapVotes)
        .reduce((max, entry) => entry[1] > max[1] ? entry : max, ['', 0])[0];
      if (mostVotedMap) {
        matches[matchCounter].chosenMap = mostVotedMap;
      }
      await updateMatchMessage(matchCounter);
    }, 5 * 60 * 1000); // 5 minutes

    matchCounter++;
  } catch (error) {
    console.error('Error creating match:', error);
  }
};

const updateMatchMessage = async (matchNumber) => {
  try {
    const match = matches[matchNumber];
    const matchText = client.channels.cache.find(channel => channel.name === `match-${matchNumber}`);
    if (!matchText) return;

    const team1 = match.team1.map(id => `<@${id}>`).join('\n') || 'No players yet';
    const team2 = match.team2.map(id => `<@${id}>`).join('\n') || 'No players yet';
    const embed = new EmbedBuilder()
      .setTitle(`🎮 Match ${matchNumber} 🎮`)
      .setColor('#ff4500')
      .setDescription('**Match Details**')
      .addFields(
        { name: '🏆 Team 1 Captain', value: `<@${match.team1[0]}>`, inline: true },
        { name: '🏆 Team 2 Captain', value: `<@${match.team2[0]}>`, inline: true },
        { name: '\u200B', value: '\u200B', inline: true },
        { name: '👥 Team 1', value: team1, inline: true },
        { name: '👥 Team 2', value: team2, inline: true },
        { name: '🗺️ Map', value: `**${match.chosenMap}**`, inline: true }
      )
      .setThumbnail('https://i.imgur.com/xfe96CL.png')
      .setImage('https://i.imgur.com/jyuglDa.png')
      .setFooter({ text: 'Powered by Maverick', iconURL: 'https://i.imgur.com/wTF3RuJ.png' })
      .setTimestamp();

    const playerPickRows = [];
    for (let i = 0; i < match.remaining.length; i += 5) {
      const row = new ActionRowBuilder()
        .addComponents(
          await Promise.all(
            match.remaining.slice(i, i + 5).map(async playerId => {
              const member = await client.guilds.cache.get(matchText.guild.id).members.fetch(playerId);
              const nickname = member.nickname || member.user.username;
              return new ButtonBuilder()
                .setCustomId(`pick_${matchNumber}_${playerId}`)
                .setLabel(`Pick ${nickname}`)
                .setStyle(ButtonStyle.Primary);
            })
          )
        );
      playerPickRows.push(row);
    }

    const mapVoteRows = [];
    for (let i = 0; i < maps.length; i += 5) {
      const row = new ActionRowBuilder()
        .addComponents(
          maps.slice(i, i + 5).map(map => new ButtonBuilder()
            .setCustomId(`map_vote_${matchNumber}_${map}`)
            .setLabel(`${map} (${match.mapVotes[map] || 0})`)
            .setStyle(ButtonStyle.Secondary)
          )
        );
      mapVoteRows.push(row);
    }

    const messages = await matchText.messages.fetch({ limit: 10 });
    const matchMessage = messages.find(msg => msg.embeds[0]?.title === `🎮 Match ${matchNumber} 🎮`);
    if (matchMessage) {
      try {
        await matchMessage.delete();
      } catch (error) {
        console.error(`Failed to delete message: ${error.message}`);
      }
    }
    await matchText.send({ embeds: [embed], components: [...playerPickRows, ...mapVoteRows] });
  } catch (error) {
    console.error('Error updating match message:', error);
  }
};

const handleButtonInteraction = async (interaction) => {
  try {
    const customId = interaction.customId;

    if (customId.startsWith('map_vote_')) {
      await voteMap(interaction);
    } else if (customId.startsWith('join_queue')) {
      await joinQueue(interaction);
    } else if (customId.startsWith('leave_queue')) {
      await leaveQueue(interaction);
    } else if (customId.startsWith('pick_')) {
      await handlePick(interaction);
    } else {
      await interaction.reply({ content: 'Unknown button interaction.', ephemeral: true });
    }
  } catch (error) {
    console.error('Error handling button interaction:', error);
    await interaction.reply({ content: 'There was an error handling this button interaction.', ephemeral: true });
  }
};

const handlePick = async (interaction) => {
  try {
    const [_, matchNumber, playerId] = interaction.customId.split('_');
    const match = matches[matchNumber];

    if (!match) {
      await interaction.reply({ content: `Match ${matchNumber} not found.`, ephemeral: true });
      return;
    }

    const user = interaction.user;
    const currentPickTeam = match.pickingOrder[match.currentPick];
    const isCaptain = (user.id === match.team1[0] && currentPickTeam === 'team1') || 
                      (user.id === match.team2[0] && currentPickTeam === 'team2');

    if (!isCaptain) {
      await interaction.reply({ content: 'Only the respective captain can pick for their team on their turn.', ephemeral: true });
      return;
    }

    const team = match.pickingOrder[match.currentPick];
    if (!team) {
      await interaction.reply({ content: 'Invalid picking order.', ephemeral: true });
      return;
    }

    match[team].push(playerId);
    match.remaining = match.remaining.filter(id => id !== playerId);
    match.currentPick++;

    // Automatically assign the last player to team 2
    if (match.currentPick === match.pickingOrder.length && match.remaining.length === 1) {
      match.team2.push(match.remaining[0]);
      match.remaining = [];
    }

    await updateMatchMessage(matchNumber);
    await interaction.reply({ content: `Player <@${playerId}> has been picked for ${team === 'team1' ? 'Team 1' : 'Team 2'}.`, ephemeral: true });
  } catch (error) {
    console.error('Error handling pick:', error);
    await interaction.reply({ content: 'There was an error handling the pick.', ephemeral: true });
  }
};


const voteMap = async (interaction) => {
  try {
    const [_, matchNumber, map] = interaction.customId.split('_');

    if (!matchNumber || isNaN(matchNumber) || matchNumber < 1) {
      console.error(`Invalid matchNumber: ${matchNumber}`);
      await interaction.reply({ content: 'Invalid match number.', ephemeral: true });
      return;
    }

    const match = matches[matchNumber];
    if (!match) {
      console.error(`Match ${matchNumber} not found.`);
      await interaction.reply({ content: `Match ${matchNumber} not found.`, ephemeral: true });
      return;
    }

    if (!match.mapVotes) match.mapVotes = {};
    if (!match.mapVotes[map]) match.mapVotes[map] = 0;
    match.mapVotes[map]++;

    const mostVotedMap = Object.entries(match.mapVotes).reduce((max, entry) => entry[1] > max[1] ? entry : max, ['', 0])[0];
    match.chosenMap = mostVotedMap;

    await interaction.reply({ content: `Your vote for ${map} has been recorded!`, ephemeral: true });
    await updateMatchMessage(matchNumber);
  } catch (error) {
    console.error('Error voting map:', error);
    await interaction.reply({ content: 'There was an error voting for the map.', ephemeral: true });
  }
};

const findUserMatch = (userId) => {
  for (const [id, match] of Object.entries(matches)) {
      if (match.team1.includes(userId) || match.team2.includes(userId) || match.remaining.includes(userId)) {
          return id;
      }
  }
  return null;
};

const reportCommand = async (message, args) => {
  const roles = [PermissionsBitField.Flags.Administrator, '1075756565023445022', '1258877265870192673'];
  if (!message.member.permissions.has(PermissionsBitField.Flags.Administrator) &&
    !message.member.roles.cache.has(roles[1]) &&
    !message.member.roles.cache.has(roles[2])) {
    return message.reply('You do not have permission to use this command.');
  }
  try {
    const matchId = findUserMatch(message.author.id);
    const winningTeamNumber = args[0];
    if (!matchId || !matches[matchId]) {
      return message.reply('You are not in any active match.');
    }
    const match = matches[matchId];
    const team1 = match.team1;
    const team2 = match.team2;
    let winningTeam, losingTeam;
    if (winningTeamNumber === '1') {
      winningTeam = team1;
      losingTeam = team2;
      updateElo(team1, team2);
      updateStats(team1, team2, match.chosenMap, true);
    } else if (winningTeamNumber === '2') {
      winningTeam = team2;
      losingTeam = team1;
      updateElo(team2, team1);
      updateStats(team2, team1, match.chosenMap, true);
    } else {
      return message.reply('Invalid winning team.');
    }

    await message.reply(`Match ${matchId} has been reported and the channels will be deleted.`);

    const guild = message.guild;
    const matchCategory = await guild.channels.fetch(process.env.MATCH_CATEGORY_ID);
    const team1Voice = matchCategory.children.cache.find(c => c.name === `Match ${matchId} - Team 1`);
    const team2Voice = matchCategory.children.cache.find(c => c.name === `Match ${matchId} - Team 2`);
    const matchText = matchCategory.children.cache.find(c => c.name === `match-${matchId}`);
    if (team1Voice) await team1Voice.delete();
    if (team2Voice) await team2Voice.delete();
    if (matchText) await matchText.delete();

    delete matches[matchId];

    const reporter = message.author;
    logMatch(matchId, winningTeam, losingTeam, `Team ${winningTeamNumber}`, reporter);
  } catch (error) {
    console.error('Error reporting match:', error);
    message.reply('There was an error reporting the match.');
  }
};


const updateStats = (winningTeam, losingTeam, map, isWin) => {
  [...winningTeam, ...losingTeam].forEach(player => {
    if (!playerStats[player]) playerStats[player] = { BW: { wins: 0, losses: 0 }, PORT: { wins: 0, losses: 0 }, ANK: { wins: 0, losses: 0 }, COMP: { wins: 0, losses: 0 }, SB: { wins: 0, losses: 0 }, MEX: { wins: 0, losses: 0 }, EE: { wins: 0, losses: 0 } };

    if (winningTeam.includes(player)) {
      playerStats[player][map].wins++;
    } else {
      playerStats[player][map].losses++;
    }
  });

  saveData();
};



const clearQueueCommand = async (message) => {
  if (!message.member.permissions.has(PermissionsBitField.Flags.Administrator)) {
    return message.reply('You do not have permission to use this command.');
  }

  try {
    queue = [];
    await updateQueueMessage();
    message.reply('The queue has been cleared.');
  } catch (error) {
    console.error('Error clearing queue:', error);
    message.reply('There was an error clearing the queue.');
  }
};

const resetEloCommand = async (message) => {
  if (!message.member.permissions.has(PermissionsBitField.Flags.Administrator)) {
    return message.reply('You do not have permission to use this command.');
  }

  try {
    playersELO = {};
    saveData();
    message.reply('All ELO has been reset.');
  } catch (error) {
    console.error('Error resetting ELO:', error);
    message.reply('There was an error resetting the ELO.');
  }
};

const warningResetCommand = async (message) => {
  if (!message.member.permissions.has(PermissionsBitField.Flags.Administrator)) {
    return message.reply('You do not have permission to use this command.');
  }

  try {
    playerWarnings = {};
    saveData();
    message.reply('All warnings have been reset.');
  } catch (error) {
    console.error('Error resetting warnings:', error);
    message.reply('There was an error resetting the warnings.');
  }
};

const setMatchLogChannelCommand = async (message, args) => {
  if (!message.member.permissions.has(PermissionsBitField.Flags.Administrator)) {
    return message.reply('You do not have permission to use this command.');
  }

  try {
    const channelId = args[0];
    process.env.MATCH_LOG_CHANNEL_ID = channelId;
    message.reply('Match log channel set.');
  } catch (error) {
    console.error('Error setting match log channel:', error);
    message.reply('There was an error setting the match log channel.');
  }
};

const giveEloCommand = async (message, args) => {
  if (!message.member.permissions.has(PermissionsBitField.Flags.Administrator)) {
    return message.reply('You do not have permission to use this command.');
  }

  try {
    const userId = args[0].replace('<@', '').replace('>', '');
    const eloChange = parseInt(args[1], 10);

    if (!playersELO[userId]) playersELO[userId] = 0;
    playersELO[userId] += eloChange;

    saveData();
    message.reply(`ELO for <@${userId}> has been changed by ${eloChange}. New ELO: ${playersELO[userId]}`);
  } catch (error) {
    console.error('Error giving ELO:', error);
    message.reply('There was an error changing the ELO.');
  }
};

const warnCommand = async (message, args) => {
  const roles = [PermissionsBitField.Flags.Administrator, '1075756565023445022'];
  if (!message.member.permissions.has(PermissionsBitField.Flags.Administrator) &&
    !message.member.roles.cache.has(roles[1])) {
    return message.reply('You do not have permission to use this command.');
  }

  try {
    // Check if a user is mentioned and extract the user ID
    if (!args[0] || !args[0].match(/^<@!?(\d+)>$/)) {
      return message.reply('Please mention a valid user to warn.');
    }

    const userId = args[0].replace('<@', '').replace('>', '');
    const reason = args.slice(1).join(' ') || 'No reason provided';

    // Initialize warnings if the user has none
    if (!playerWarnings[userId]) {
      playerWarnings[userId] = { count: 0, reasons: [] };
    }

    // Increment the warning count and add the reason
    playerWarnings[userId].count += 1;
    playerWarnings[userId].reasons.push(reason);
    saveData();

    const embed = new EmbedBuilder()
      .setColor('#ff4500') // Orange-red color suitable for a competitive FPS theme
      .setTitle('User Warned')
      .addFields(
        { name: 'User', value: `<@${userId}>`, inline: true },
        { name: 'Warnings', value: playerWarnings[userId].count.toString(), inline: true },
        { name: 'Reason', value: reason, inline: true }
      )
      .setThumbnail('https://i.imgur.com/wTF3RuJ.png') // Placeholder thumbnail, replace with relevant image
      .setFooter({ text: 'Stay in line, soldier!', iconURL: 'https://i.imgur.com/wTF3RuJ.png' })
      .setTimestamp();

    message.reply({ embeds: [embed] });
  } catch (error) {
    console.error('Error warning user:', error);
    message.reply('There was an error warning the user.');
  }
};


const warnsCommand = async (message, args) => {
  const roles = [PermissionsBitField.Flags.Administrator, '1075756565023445022'];
  if (!message.member.permissions.has(PermissionsBitField.Flags.Administrator) &&
    !message.member.roles.cache.has(roles[1])) {
    return message.reply('You do not have permission to use this command.');
  }
  try {
    const userId = args[0] ? args[0].replace('<@', '').replace('>', '') : message.author.id;
    const userWarnings = playerWarnings[userId] ? playerWarnings[userId].count : 0;
    const reasons = playerWarnings[userId] ? playerWarnings[userId].reasons : [];
    const username = message.mentions.users.first() ? message.mentions.users.first().username : message.author.username;

    const embed = new EmbedBuilder()
      .setColor('#ff4500') // Orange-red color suitable for a competitive FPS theme
      .setTitle(`${username}'s Warnings`)
      .addFields(
        { name: 'Warnings', value: userWarnings.toString(), inline: true },
        { name: 'Reasons', value: reasons.length > 0 ? reasons.join('\n') : 'No warnings', inline: true }
      )
      .setThumbnail('https://i.imgur.com/wTF3RuJ.png') // Placeholder thumbnail, replace with relevant image
      .setFooter({ text: 'Stay in line, soldier!', iconURL: 'https://i.imgur.com/wTF3RuJ.png' })
      .setTimestamp();

    message.reply({ embeds: [embed] });
  } catch (error) {
    console.error('Error fetching warnings:', error);
    message.reply('There was an error fetching the warnings.');
  }
};

const profileCommand = async (message, args) => {
  const userId = args[0] ? args[0].replace('<@', '').replace('>', '') : message.author.id;
  const user = await message.guild.members.fetch(userId);
  const username = user.nickname || user.user.username;
  const elo = playersELO[userId] || 0;
  const userKarma = karma[userId] || 0;
  const stats = playerStats[userId] || {};

  const defaultMapStats = { wins: 0, losses: 0 };
  const maps = {
    BW: stats.BW || defaultMapStats,
    PORT: stats.PORT || defaultMapStats,
    ANK: stats.ANK || defaultMapStats,
    COMP: stats.COMP || defaultMapStats,
    SB: stats.SB || defaultMapStats,
    MEX: stats.MEX || defaultMapStats,
    EE: stats.EE || defaultMapStats
  };

  const totalWins = Object.values(maps).reduce((acc, map) => acc + map.wins, 0);
  const totalLosses = Object.values(maps).reduce((acc, map) => acc + map.losses, 0);

  const embed = new EmbedBuilder()
    .setTitle(`${username}'s Profile`)
    .setThumbnail(user.user.displayAvatarURL({ dynamic: true }))
    .addFields(
      { name: 'ELO', value: elo.toString(), inline: true },
      { name: 'Karma', value: userKarma.toString(), inline: true },
      { name: '\u200B', value: '\u200B', inline: true }
    )
    .addFields(
      { name: 'Map Stats:', value: '\u200B' },
      { name: 'Black Widow', value: `${maps.BW.wins} wins ${maps.BW.losses} losses`, inline: true },
      { name: 'Port', value: `${maps.PORT.wins} wins ${maps.PORT.losses} losses`, inline: true },
      { name: 'Ankara', value: `${maps.ANK.wins} wins ${maps.ANK.losses} losses`, inline: true },
      { name: 'Compound', value: `${maps.COMP.wins} wins ${maps.COMP.losses} losses`, inline: true },
      { name: 'Sub Base', value: `${maps.SB.wins} wins ${maps.SB.losses} losses`, inline: true },
      { name: 'Mexico', value: `${maps.MEX.wins} wins ${maps.MEX.losses} losses`, inline: true },
      { name: 'Eagle Eye', value: `${maps.EE.wins} wins ${maps.EE.losses} losses`, inline: true },
      { name: 'Total Wins', value: totalWins.toString(), inline: true },
      { name: 'Total Losses', value: totalLosses.toString(), inline: true }
    )
    .setColor('#ff4500')
    .setFooter({ text: 'FPS Game Stats', iconURL: 'https://i.imgur.com/wTF3RuJ.png' })
    .setTimestamp();

  message.reply({ embeds: [embed] });
};

const leaderboardCommand = async (message) => {
  try {
    const sortedPlayers = Object.entries(playersELO).sort((a, b) => b[1] - a[1]).slice(0, 25);
    const leaderboard = await Promise.all(
      sortedPlayers.map(async ([userId, elo], index) => {
        const user = await message.guild.members.fetch(userId).catch(() => null);
        return `#${index + 1}  |  ${elo}  |  ${user ? `<@${user.id}>` : `Unknown User`}`;
      })
    );

    if (leaderboard.length === 0) {
      leaderboard.push('No players available');
    }

    const embed = new EmbedBuilder()
      .setColor('#00ff26') // Set a custom color for the embed
      .setTitle('Top 25 Pug Players')
      .addFields({ name: 'Rank | Elo | Player', value: leaderboard.join('\n') })
      .setFooter({ text: 'Use .lb or .leaderboard to see the leaderboard at any time', iconURL: 'https://i.imgur.com/wTF3RuJ.png' })
      .setTimestamp();

    message.reply({ embeds: [embed] });
  } catch (error) {
    console.error('Error fetching leaderboard:', error);
    message.reply('There was an error fetching the leaderboard.');
  }
};

const eloCommand = async (message, args) => {
  try {
    const userId = args[0] ? args[0].replace('<@', '').replace('>', '') : message.author.id;
    const elo = playersELO[userId] || 0;
    message.reply(`<@${userId}> has ${elo} ELO.`);
  } catch (error) {
    console.error('Error fetching ELO:', error);
    message.reply('There was an error fetching the ELO.');
  }
};

const pickCommand = async (message, args) => {
  try {
    if (!args[0] || !args[0].match(/^<@!?(\d+)>$/)) {
      return message.reply('Please mention a valid user to pick.');
    }

    const playerId = args[0].replace('<@', '').replace('>', '');
    const userId = message.author.id;
    const matchId = findUserMatch(userId);

    if (!matchId) {
      return message.reply('You are not part of any active matches.');
    }

    const match = matches[matchId];
    const currentPickIndex = match.team1.length + match.team2.length - 2;

    if (currentPickIndex >= match.pickingOrder.length) {
      return message.reply('All picks have already been made.');
    }

    const pickingTeam = match.pickingOrder[currentPickIndex] === 'team1' ? 'team1' : 'team2';

    if (!match.remaining.includes(playerId)) {
      return message.reply('Invalid player ID for picking.');
    }

    match[pickingTeam].push(playerId);
    match.remaining = match.remaining.filter(id => id !== playerId);

    await updateMatchMessage(matchId);

    message.reply(`Player <@${playerId}> picked for ${pickingTeam === 'team1' ? 'Team 1' : 'Team 2'}.`);
  } catch (error) {
    console.error('Error picking player:', error);
    message.reply('There was an error picking the player.');
  }
};

const needSubCommand = async (message, args) => {
  const roles = ['1255072045574652035', '1075756565023445022', '1258877265870192673'];
  if (!message.member.permissions.has(PermissionsBitField.Flags.Administrator) &&
      !roles.some(role => message.member.roles.cache.has(role))) {
    return message.reply('You do not have permission to use this command.');
  }

  try {
    if (!args[0] || !args[0].match(/^<@!?(\d+)>$/)) {
      return message.reply('Please mention a valid user to sub.');
    }

    const userId = message.author.id;
    const playerId = args[0].replace('<@', '').replace('>', '');
    const matchId = findUserMatch(userId);

    if (!matchId) {
      return message.reply('You are not part of any active matches.');
    }

    const match = matches[matchId];
    const team = match.team1.includes(playerId) ? 'team1' : 'team2';
    const playerIndex = match[team].indexOf(playerId);

    if (playerIndex === -1) {
      return message.reply('Player not found in any team.');
    }

    const subMessage = await message.channel.send(`<@${playerId}> has requested a substitution. React with ❌ within 2 minutes if you are still here.`);
    await subMessage.react('❌');

    const filter = (reaction, user) => reaction.emoji.name === '❌' && user.id === playerId;
    const collector = subMessage.createReactionCollector({ filter, time: 120000 });

    collector.on('collect', () => {
      subMessage.edit(`<@${playerId}> is still active.`);
      collector.stop();
    });

    collector.on('end', async collected => {
      if (collected.size === 0) {
        const newPlayerId = queue.shift();
        if (!newPlayerId) {
          return message.channel.send('No available substitutes in the queue.');
        }

        match[team][playerIndex] = newPlayerId;
        await updateMatchMessage(matchId);

        message.channel.send(`<@${playerId}> has been substituted by <@${newPlayerId}> in ${team === 'team1' ? 'Team 1' : 'Team 2'}.`);
      }
    });
  } catch (error) {
    console.error('Error handling substitution:', error);
    message.reply('There was an error handling the substitution.');
  }
};

const replaceCommand = async (message, args) => {
  const roles = [PermissionsBitField.Flags.Administrator, '1075756565023445022'];
  if (!message.member.permissions.has(PermissionsBitField.Flags.Administrator) &&
    !message.member.roles.cache.has(roles[1])) {
    return message.reply('You do not have permission to use this command.');
  }
  try {
    if (args.length < 2 || !args[0].match(/^<@!?(\d+)>$/) || !args[1].match(/^<@!?(\d+)>$/)) {
      return message.reply('Please mention the old player and the new player in the correct format.');
    }

    const oldPlayerId = args[0].replace('<@', '').replace('>', '');
    const newPlayerId = args[1].replace('<@', '').replace('>', '');
    const userId = message.author.id;
    const matchId = findUserMatch(userId);

    if (!matchId) {
      return message.reply('You are not part of any active matches.');
    }

    const match = matches[matchId];
    const team = match.team1.includes(oldPlayerId) ? 'team1' : match.team2.includes(oldPlayerId) ? 'team2' : null;

    if (!team) {
      return message.reply('Old player is not found in any team.');
    }

    const playerIndex = match[team].indexOf(oldPlayerId);

    if (playerIndex === -1) {
      return message.reply('Old player not found in the team.');
    }

    match[team][playerIndex] = newPlayerId;

    await updateMatchMessage(matchId);

    message.reply(`Player <@${oldPlayerId}> has been replaced by <@${newPlayerId}> in ${team === 'team1' ? 'Team 1' : 'Team 2'}.`);
  } catch (error) {
    console.error('Error replacing player:', error);
    message.reply('There was an error replacing the player.');
  }
};

const ptCommand = async (message, args) => {
  const roles = [PermissionsBitField.Flags.Administrator, '1075756565023445022', '1258877265870192673'];
  if (!message.member.permissions.has(PermissionsBitField.Flags.Administrator) &&
    !message.member.roles.cache.has(roles[1]) &&
    !message.member.roles.cache.has(roles[2])) {
    return message.reply('You do not have permission to use this command.');
  }
  try {
    if (args.length < 1) {
      return message.reply('Please specify the team number (1, 2, or 0) and an optional message.');
    }

    const userId = message.author.id;
    const teamNumber = parseInt(args[0]);

    if (isNaN(teamNumber) || (teamNumber < 0 || teamNumber > 2)) {
      return message.reply('Invalid team number. Use 1 for Team 1, 2 for Team 2, or 0 for both teams.');
    }

    const additionalMessage = args.slice(1).join(' ');

    const matchId = findUserMatch(userId);

    if (!matchId) {
      return message.reply('You are not part of any active matches.');
    }

    const match = matches[matchId];
    let teamMentions = '';

    if (teamNumber === 0) {
      const allTeams = [...match.team1, ...match.team2];
      teamMentions = allTeams.map(id => `<@${id}>`).join(' ');
    } else if (teamNumber === 1) {
      teamMentions = match.team1.map(id => `<@${id}>`).join(' ');
    } else if (teamNumber === 2) {
      teamMentions = match.team2.map(id => `<@${id}>`).join(' ');
    }

    const finalMessage = `${teamMentions} ${additionalMessage}`;

    try {
      const matchCategory = await message.client.channels.fetch(process.env.MATCH_CATEGORY_ID);
      const matchText = matchCategory.children.cache.find(c => c.name === `match-${matchId}`);
      if (!matchText) {
        return message.reply('Match text channel not found.');
      }
      await matchText.send(finalMessage);
      message.reply('Message sent.');
    } catch (error) {
      console.error('Error sending message:', error);
      message.reply('There was an error sending the message. Please try again later.');
    }
  } catch (error) {
    console.error('Error in PT command:', error);
    message.reply('There was an error executing the PT command.');
  }
};

const cancelMatchCommand = async (message) => {
  const roles = [PermissionsBitField.Flags.Administrator, '1075756565023445022', '1258877265870192673'];
  if (!message.member.permissions.has(PermissionsBitField.Flags.Administrator) &&
      !roles.some(role => message.member.roles.cache.has(role))) {
    return message.reply('You do not have permission to use this command.');
  }

  try {
    const userId = message.author.id;
    const matchId = findUserMatch(userId);

    if (!matchId) {
      return message.reply('You are not part of any active matches.');
    }

    const match = matches[matchId];
    delete matches[matchId];

    const guild = message.guild;
    const matchCategory = guild.channels.cache.get(process.env.MATCH_CATEGORY_ID);

    const team1Voice = matchCategory.children.cache.find(c => c.name === `Match ${matchId} - Team 1`);
    const team2Voice = matchCategory.children.cache.find(c => c.name === `Match ${matchId} - Team 2`);
    const matchText = matchCategory.children.cache.find(c => c.name === `match-${matchId}`);

    if (team1Voice) await team1Voice.delete();
    if (team2Voice) await team2Voice.delete();
    if (matchText) await matchText.delete();

    message.reply(`Match ${matchId} has been canceled.`);
  } catch (error) {
    console.error('Error canceling match:', error);
    message.reply('There was an error canceling the match.');
  }
};


const suspendUserCommand = async (message, args) => {
  try {
    const userId = args[0].replace('<@', '').replace('>', '');
    const duration = parseInt(args[1]); // Duration in minutes
    const reason = args.slice(2).join(' ');

    if (!karma[userId]) karma[userId] = {};
    karma[userId].suspendedUntil = Date.now() + duration * 60000;
    karma[userId].reason = reason;
    saveData();

    message.reply(`<@${userId}> has been suspended for ${duration} minutes. Reason: ${reason}`);
  } catch (error) {
    console.error('Error suspending user:', error);
    message.reply('There was an error suspending the user.');
  }
};

const unSuspendUserCommand = async (message, args) => {
  const roles = [PermissionsBitField.Flags.Administrator, '1075756565023445022'];
  if (!message.member.permissions.has(PermissionsBitField.Flags.Administrator) &&
    !message.member.roles.cache.has(roles[1])) {
    return message.reply('You do not have permission to use this command.');
  }
  try {
    const userId = args[0].replace('<@', '').replace('>', '');

    if (karma[userId] && karma[userId].suspendedUntil) {
      delete karma[userId].suspendedUntil;
      delete karma[userId].reason;
      saveData();
      message.reply(`<@${userId}>'s suspension has been lifted.`);
    } else {
      message.reply(`<@${userId}> is not currently suspended.`);
    }
  } catch (error) {
    console.error('Error unsuspending user:', error);
    message.reply('There was an error lifting the suspension.');
  }
};

const saveData = () => {
  const data = { playersELO, playerWarnings, karma, playerStats };
  fs.writeFileSync(dataFilePath, JSON.stringify(data, null, 2), 'utf-8');
};

const logMatch = (matchId, winningTeam, losingTeam, result, reporter) => {
  const logChannel = client.channels.cache.get(process.env.MATCH_LOG_CHANNEL_ID);

  if (!logChannel) {
    console.error('Match log channel not found');
    return;
  }

  const embed = new EmbedBuilder()
    .setTitle(`Match #${matchId} Log`)
    .setColor('#ff4500') // Orange-red color suitable for a competitive FPS theme
    .addFields(
      { name: 'Winners | Team 1', value: winningTeam.map(id => `<@${id}>`).join(' '), inline: true },
      { name: 'Losers | Team 2', value: losingTeam.map(id => `<@${id}>`).join(' '), inline: true },
      { name: 'Reported by', value: `${reporter.username}#${reporter.discriminator} (${reporter.id})` }
    )
    .setFooter({ text: 'Powered by Maverick', iconURL: 'https://i.imgur.com/wTF3RuJ.png' })
    .setTimestamp();

  logChannel.send({ embeds: [embed] });
};

const updateElo = (winningTeam, losingTeam) => {
  winningTeam.forEach(player => {
    if (!playersELO[player]) playersELO[player] = 50;
    playersELO[player] += 6;
  });

  losingTeam.forEach(player => {
    if (!playersELO[player]) playersELO[player] = 50;
    playersELO[player] -= 5;
  });

  saveData();
};

const registerCommands = () => {
  const commands = [
    {
      name: 'setupreaction',
      description: 'Recreates the reaction message to join the queue',
      type: 1,
    },
    {
      name: 'eloreset',
      description: 'Resets all ELO, must be superuser',
      type: 1,
    },
    {
      name: 'warningreset',
      description: 'Resets all warnings, must be admin',
      type: 1,
    },
    {
      name: 'leaderboard',
      description: 'Leaderboard in the stats channel',
      type: 1,
    }
  ];

  const guild = client.guilds.cache.first();
  guild.commands.set(commands).then(() => console.log('Commands registered'));
};

client.login(process.env.DISCORD_TOKEN);
downloadDownload PNG downloadDownload JPEG downloadDownload SVG

Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!

Click to optimize width for Twitter