#!/bin/bash
# Create directories
mkdir -p your-bot/commands/admin
mkdir -p your-bot/commands/general
mkdir -p your-bot/commands/match
mkdir -p your-bot/commands/warning
# Create data.json file
cat <<EOL > your-bot/data.json
{
"matches": {},
"matchCounter": 1,
"playersELO": {},
"playerWarnings": {},
"karma": {},
"playerStats": {}
}
EOL
# Create .env file
cat <<EOL > your-bot/.env
DISCORD_TOKEN=your-token-here
DISCORD_PREFIX=!
QUEUE_CHANNEL_ID=your-queue-channel-id
MATCH_CATEGORY_ID=your-match-category-id
MATCH_LOG_CHANNEL_ID=your-match-log-channel-id
EOL
# Create package.json file
cat <<EOL > your-bot/package.json
{
"name": "your-bot",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"discord.js": "^14.0.0",
"dotenv": "^16.0.0"
},
"type": "module"
}
EOL
# Create index.js file
cat <<EOL > your-bot/index.js
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';
import { loadCommands } from './loadCommands.js';
import { registerCommands } from './registerCommands.js';
dotenv.config();
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const dataFilePath = path.join(__dirname, 'data.json');
console.log(\`Data file path: \${dataFilePath}\`);
let data;
let matches = {};
let matchCounter = 1;
let playersELO = {};
let playerWarnings = {};
let playerStats = {};
let karma = {};
let queue = [];
const maps = ['BW', 'SB', 'PORT', 'COMP', 'ANK', 'MEX', 'EE'];
const saveMatchData = () => {
const dataToSave = { matches, matchCounter, playersELO, playerWarnings, karma, playerStats };
fs.writeFileSync(dataFilePath, JSON.stringify(dataToSave, null, 2), 'utf-8');
};
const loadMatchData = () => {
try {
const fileContents = fs.readFileSync(dataFilePath, 'utf8');
const data = JSON.parse(fileContents);
matches = data.matches || {};
matchCounter = data.matchCounter || 1;
playersELO = data.playersELO || {};
playerWarnings = data.playerWarnings || {};
karma = data.karma || {};
playerStats = data.playerStats || {};
} catch (error) {
console.error(\`Error reading file at path: \${dataFilePath}\`);
console.error(error);
}
};
loadMatchData();
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();
registerCommands(client);
});
client.on('messageCreate', async (message) => {
if (!message.content.startsWith(process.env.DISCORD_PREFIX) || message.author.bot) return;
const args = message.content.slice(process.env.DISCORD_PREFIX.length).trim().split(/ +/);
const command = args.shift().toLowerCase();
try {
await loadCommands(command, message, args);
} 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')
.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' });
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 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;
const member = await interaction.guild.members.fetch(user.id);
if (!queue.includes(user.id) && member.roles.cache.some(role => ['Pug Approved', 'Admin', 'Score Reporter'].includes(role.name))) {
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 or do not have permission to join 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: {},
chosenMap: randomMap,
textChannelId: matchText.id
};
saveMatchData();
console.log(\`Match \${matchCounter} created with map \${randomMap}\`);
await updateQueueMessage();
await updateMatchMessage(matchCounter);
setTimeout(async () => {
if (!matches[matchCounter]) return;
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);
}, 3 * 60 * 1000);
matchCounter++;
} catch (error) {
console.error('Error creating match:', error);
}
};
const updateMatchMessage = async (matchNumber) => {
try {
const match = matches[matchNumber];
const matchText = client.channels.cache.get(match.textChannelId);
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;
}
if (!match.remaining.includes(playerId)) {
await interaction.reply({ content: 'Invalid player ID for picking.', ephemeral: true });
return;
}
const team = match.pickingOrder[match.currentPick];
match[team].push(playerId);
match.remaining = match.remaining.filter(id => id !== playerId);
match.currentPick++;
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('_');
const userId = interaction.user.id;
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].includes(userId)) {
await interaction.reply({ content: 'You have already voted for this map.', ephemeral: true });
return;
}
for (const m in match.mapVotes) {
match.mapVotes[m] = match.mapVotes[m].filter(id => id !== userId);
}
if (!match.mapVotes[map]) match.mapVotes[map] = [];
match.mapVotes[map].push(userId);
const mostVotedMap = Object.entries(match.mapVotes).reduce((max, entry) => entry[1].length > max[1].length ? entry : max, ['', []])[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;
};
client.login(process.env.DISCORD_TOKEN);
EOL
# Create command files
# Admin Commands
cat <<EOL > your-bot/commands/admin/clearQueue.js
export 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.');
}
};
EOL
cat <<EOL > your-bot/commands/admin/giveElo.js
export 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.');
}
};
EOL
cat <<EOL > your-bot/commands/admin/resetElo.js
export 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.');
}
};
EOL
cat <<EOL > your-bot/commands/admin/resetWarnings.js
export 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.');
}
};
EOL
# General Commands
cat <<EOL > your-bot/commands/general/profile.js
export 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] });
};
EOL
cat <<EOL > your-bot/commands/general/leaderboard.js
export 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')
.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.');
}
};
EOL
cat <<EOL > your-bot/commands/general/elo.js
export 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.');
}
};
EOL
# Match Commands
cat <<EOL > your-bot/commands/match/cancelMatch.js
export const cancelMatchCommand = async (message, args) => {
const roleIds = ['1075756565023445022', '1258877265870192673']; // Array of role IDs
const hasAdminPermission = message.member.permissions.has(PermissionsBitField.Flags.Administrator);
const hasRequiredRole = roleIds.some(roleId => message.member.roles.cache.has(roleId));
const hasPermission = hasAdminPermission || hasRequiredRole; // Check for permissions
let matchId = Object.keys(matches).find(id => matches[id].textChannelId === message.channel.id);
if (!matchId && hasPermission) {
matchId = args[0];
}
if (!matchId || !matches[matchId]) {
try {
await message.reply('Invalid match ID or no active match found.');
} catch (error) {
console.error('Error sending reply:', error);
}
return;
}
if (!hasPermission) { // Check if user has the necessary permissions
try {
await message.reply('You do not have permission to cancel matches.');
} catch (error) {
console.error('Error sending reply:', error);
}
return;
}
try {
const match = matches[matchId];
delete matches[matchId];
saveMatchData();
const guild = message.guild;
const matchCategory = await guild.channels.fetch(process.env.MATCH_CATEGORY_ID);
// Fetch channels from cache or fetch from API if not cached
const team1Voice = guild.channels.cache.get(match.team1VoiceChannelId) || await guild.channels.fetch(match.team1VoiceChannelId);
const team2Voice = guild.channels.cache.get(match.team2VoiceChannelId) || await guild.channels.fetch(match.team2VoiceChannelId);
const matchText = guild.channels.cache.get(match.textChannelId) || await guild.channels.fetch(match.textChannelId);
if (team1Voice) await team1Voice.delete();
if (team2Voice) await team2Voice.delete();
if (matchText) await matchText.delete();
await message.reply(\`Match \${matchId} has been canceled.\`);
} catch (error) {
console.error('Error canceling match:', error);
try {
await message.reply('There was an error canceling the match.');
} catch (replyError) {
console.error('Error sending error reply:', replyError);
}
}
};
EOL
cat <<EOL > your-bot/commands/match/needSub.js
export 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.');
}
};
EOL
cat <<EOL > your-bot/commands/match/pick.js
export 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.');
}
};
EOL
cat <<EOL > your-bot/commands/match/pt.js
export 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(' ');
let matchId = Object.keys(matches).find(id => matches[id].textChannelId === message.channel.id);
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.');
}
};
EOL
cat <<EOL > your-bot/commands/match/report.js
export const reportCommand = async (message, args) => {
const roleIds = ['1075756565023445022', '1258877265870192673']; // Array of role IDs
const hasAdminPermission = message.member.permissions.has(PermissionsBitField.Flags.Administrator);
const hasRequiredRole = roleIds.some(roleId => message.member.roles.cache.has(roleId));
const hasPermission = hasAdminPermission || hasRequiredRole; // Check for permissions
let matchId = Object.keys(matches).find(id => matches[id].textChannelId === message.channel.id);
if (!matchId && hasPermission) {
matchId = args[0];
}
if (!matchId || !matches[matchId]) {
return message.reply('Invalid match ID or no active match found.');
}
try {
const winningTeamNumber = hasPermission ? args[1] : args[0];
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];
saveMatchData();
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.');
}
};
EOL
cat <<EOL > your-bot/commands/match/replace.js
export const replaceCommand = async (message, args) => {
const roleIds = ['1075756565023445022', '1258877265870192673']; // Array of role IDs
const hasAdminPermission = message.member.permissions.has(PermissionsBitField.Flags.Administrator);
const hasRequiredRole = roleIds.some(roleId => message.member.roles.cache.has(roleId));
const hasPermission = hasAdminPermission || hasRequiredRole; // Check for permissions
let matchId = Object.keys(matches).find(id => matches[id].textChannelId === message.channel.id);
if (!matchId && hasPermission) {
matchId = args[2];
}
if (!matchId || !matches[matchId]) {
return message.reply('Invalid match ID or no active match found.');
}
if (!hasPermission) { // Check if user has the necessary permissions
return message.reply('You do not have permission to replace players.');
}
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('>', '').replace('!', '');
const newPlayerId = args[1].replace('<@', '').replace('>', '').replace('!', '');
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.');
}
};
EOL
cat <<EOL > your-bot/commands/match/suspendUser.js
export const suspendUserCommand = async (message, args) => {
try {
const userId = args[0].replace('<@', '').replace('>', '');
const duration = parseInt(args[1]);
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.');
}
};
EOL
cat <<EOL > your-bot/commands/match/unSuspendUser.js
export 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.');
}
};
EOL
# Warning Commands
cat <<EOL > your-bot/commands/warning/warn.js
export 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 {
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';
if (!playerWarnings[userId]) {
playerWarnings[userId] = { count: 0, reasons: [] };
}
playerWarnings[userId].count += 1;
playerWarnings[userId].reasons.push(reason);
saveData();
const embed = new EmbedBuilder()
.setColor('#ff4500')
.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')
.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.');
}
};
EOL
cat <<EOL > your-bot/commands/warning/warns.js
export 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')
.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')
.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.');
}
};
EOL
# Load and register commands
cat <<EOL > your-bot/loadCommands.js
import { clearQueueCommand } from './commands/admin/clearQueue.js';
import { giveEloCommand } from './commands/admin/giveElo.js';
import { resetEloCommand } from './commands/admin/resetElo.js';
import { warningResetCommand } from './commands/admin/resetWarnings.js';
import { profileCommand } from './commands/general/profile.js';
import { leaderboardCommand } from './commands/general/leaderboard.js';
import { eloCommand } from './commands/general/elo.js';
import { cancelMatchCommand } from './commands/match/cancelMatch.js';
import { needSubCommand } from './commands/match/needSub.js';
import { pickCommand } from './commands/match/pick.js';
import { ptCommand } from './commands/match/pt.js';
import { reportCommand } from './commands/match/report.js';
import { replaceCommand } from './commands/match/replace.js';
import { suspendUserCommand } from './commands/match/suspendUser.js';
import { unSuspendUserCommand } from './commands/match/unSuspendUser.js';
import { warnCommand } from './commands/warning/warn.js';
import { warnsCommand } from './commands/warning/warns.js';
const commandMap = {
'clearqueue': clearQueueCommand,
'giveelo': giveEloCommand,
'resetelo': resetEloCommand,
'resetwarnings': warningResetCommand,
'profile': profileCommand,
'leaderboard': leaderboardCommand,
'elo': eloCommand,
'cancelmatch': cancelMatchCommand,
'needsub': needSubCommand,
'pick': pickCommand,
'pt': ptCommand,
'report': reportCommand,
'replace': replaceCommand,
'suspenduser': suspendUserCommand,
'unsuspenduser': unSuspendUserCommand,
'warn': warnCommand,
'warns': warnsCommand
};
export const loadCommands = async (command, message, args) => {
if (commandMap[command]) {
await commandMap[command](message, args);
} else {
message.reply('Unknown command.');
}
};
EOL
cat <<EOL > your-bot/registerCommands.js
export const registerCommands = (client) => {
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'));
};
EOL
# Install dependencies
npm install
EOL
echo "Setup completed successfully. You can now navigate to the 'your-bot' directory, add your .env values, and run 'npm start' to start your bot."