Compare commits

..

3 Commits

Author SHA1 Message Date
ethanf
6673965c3e fix: compare exception id to id instead of member to user 2024-03-27 00:28:55 -05:00
ethanf
9feac9211f chore: improve config naming 2024-03-26 23:56:43 -05:00
ethanf
2bed3c07e9 refactor: break relevant commands into files 2024-03-26 23:38:49 -05:00
10 changed files with 281 additions and 345 deletions

25
commands/clear.js Normal file
View File

@ -0,0 +1,25 @@
import { SlashCommandBuilder } from "discord.js";
import { client } from "..";
import { ROLE_ID_RUNNER } from "../config.json";
const data = new SlashCommandBuilder()
.setName("clear")
.setDescription("Clears bot messages in this channel");
const permissions = [ROLE_ID_RUNNER];
const execute = async (interaction) => {
await interaction.reply("Clearing messages...");
interaction.channel.messages
.fetch({ limit: 250 })
.then((messages) => {
messages.forEach((message) => {
if (message.author.id === client.user.id) {
message.delete();
}
});
})
.catch(console.error);
};
export { data, permissions, execute };

71
commands/end.js Normal file
View File

@ -0,0 +1,71 @@
import { SlashCommandBuilder } from "discord.js";
import { findChannelfromCache } from "../utils.js";
import { ROLE_ID_RUNNER, VOICE_ID_PICKING, VOICE_ID_BLU, VOICE_ID_RED } from "../config.json";
const data = new SlashCommandBuilder()
.setName("end")
.setDescription("Moves the team channel members back to the picking channel");
const permissions = [ROLE_ID_RUNNER];
const execute = async (interaction) => {
await interaction.reply("Moving members...");
// get voice channels
const picking = findChannelfromCache(
interaction.guild.channels.cache,
"picking",
VOICE_ID_PICKING
);
const blu = findChannelfromCache(
interaction.guild.channels.cache,
"blu",
VOICE_ID_BLU
);
const red = findChannelfromCache(
interaction.guild.channels.cache,
"red",
VOICE_ID_RED
);
if (!picking || !blu || !red) {
return console.error("Could not find all channels for /end");
}
// get members in voice channel
let members = blu.members.concat(red.members);
if (members.size === 0) {
return await interaction.followUp("Found no members in blu or red");
}
let eCount = 0;
// move members to picking
const moveToPicking = async (member) => {
try {
await member.voice.setChannel(picking);
} catch (error) {
console.error(error);
eCount++;
}
};
const moveAllToPicking = async () => {
return Promise.all(
Array.from(members, async ([memberId, member]) => {
await moveToPicking(member);
})
);
};
moveAllToPicking().then(() =>
interaction.followUp(
`Moved members in blu and red${
eCount > 0 ? ` (error moving ${eCount} members)` : ""
}`
)
);
};
const synonyms = ["resetteams"];
export { data, permissions, execute, synonyms };

View File

@ -1,5 +1,6 @@
import { SlashCommandBuilder } from "discord.js"; import { SlashCommandBuilder } from "discord.js";
import { RUNNER_ROLE_ID, ADDUP_ID, PICKING_ID } from "../config.json"; import { findChannelfromCache } from "../utils";
import { ROLE_ID_RUNNER, VOICE_ID_ADDUP, VOICE_ID_PICKING } from "../config.json";
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
@ -56,19 +57,26 @@ const data = new SlashCommandBuilder()
.setDescription("Player to protect from sitting out, intended for medic") .setDescription("Player to protect from sitting out, intended for medic")
); );
const permissions = [RUNNER_ROLE_ID]; const permissions = [ROLE_ID_RUNNER];
const execute = async (interaction) => { const execute = async (interaction) => {
// pull users in addup into picking, then randomly select other users in picking to be sat out until there are 18 in picking // pull users in addup into picking, then randomly select other users in picking to be sat out until there are 18 in picking
await interaction.reply("Randomly choosing fatkids from picking..."); await interaction.reply("Randomly choosing fatkids from picking...");
// get voice channels // get voice channels
const addup = interaction.guild.channels.cache.find( const addup = findChannelfromCache(
(channel) => channel.name === "add-up" || channel.id === ADDUP_ID interaction.guild.channels.cache,
"add-up",
VOICE_ID_ADDUP
); );
const picking = interaction.guild.channels.cache.find( const picking = findChannelfromCache(
(channel) => channel.name === "picking" || channel.id === PICKING_ID interaction.guild.channels.cache,
"picking",
VOICE_ID_PICKING
); );
if (!addup || !picking) {
return console.error("Could not find all channels for /fk");
}
// get members in voice channel // get members in voice channel
const addupPlayers = []; const addupPlayers = [];
@ -115,7 +123,7 @@ const execute = async (interaction) => {
let exceptions = []; let exceptions = [];
for (const option of ["exception1", "exception2"]) { for (const option of ["exception1", "exception2"]) {
const exception = interaction.options.getUser(option); const exception = interaction.options.getUser(option);
if (exception) exceptions.push(exception); if (exception) exceptions.push(exception.id);
} }
if (addupPlayers.length > targetPlayerCount - exceptions.length) { if (addupPlayers.length > targetPlayerCount - exceptions.length) {
exceptions = []; exceptions = [];
@ -140,7 +148,7 @@ const execute = async (interaction) => {
fkAttempts++; fkAttempts++;
const idx = Math.floor(Math.random() * pickingPlayers.length); const idx = Math.floor(Math.random() * pickingPlayers.length);
let fk = pickingPlayers[idx]; let fk = pickingPlayers[idx];
if (exceptions.includes(fk)) { if (exceptions.includes(fk.id)) {
continue; continue;
} }
if (protectedFk.includes(fk.id)) { if (protectedFk.includes(fk.id)) {

66
commands/fklist.js Normal file
View File

@ -0,0 +1,66 @@
import { SlashCommandBuilder } from "discord.js";
import { findChannelfromCache, findPlayer, getApplicableName } from "../utils";
import { ROLE_ID_RUNNER, VOICE_ID_PICKING, VOICE_ID_FK } from "../config.json";
// not really used since fks are now determined randomly
const data = new SlashCommandBuilder()
.setName("fklist")
.setDescription("Pulls addup channel members into fk channel and lists them");
const permissions = [ROLE_ID_RUNNER];
const synonyms = ["listfk"];
const execute = async (interaction) => {
await interaction.reply("Moving fatkids...");
// get voice channels
const picking = findChannelfromCache(
interaction.guild.channels.cache,
"picking",
VOICE_ID_PICKING
);
const fk = findChannelfromCache(
interaction.guild.channels.cache,
"fatkid",
VOICE_ID_FK
);
if (!picking || !fk) {
return console.error("Could not find all channels for /fklist");
}
// get members in voice channel
const members = picking.members;
if (members.size === 0) {
return await interaction.followUp("Found no members in picking");
}
let names = [],
eCount = 0;
const logFk = async (member) => {
try {
await member.voice.setChannel(fk);
names.push(getApplicableName(member));
} catch (error) {
console.error(error);
eCount++;
}
};
const logAllFks = async () => {
return Promise.all(
Array.from(members, async ([memberId, member]) => {
await logFk(member);
})
);
};
logAllFks().then(() =>
interaction.followUp(
`Fatkids: ${names.join(', ')}${eCount > 0 ? ` (error moving ${eCount} members)` : ""}`
)
);
};
export { data, permissions, execute, synonyms };

14
commands/hello.js Normal file
View File

@ -0,0 +1,14 @@
import { SlashCommandBuilder } from "discord.js";
import { ROLE_ID_RUNNER } from "../config.json";
const data = new SlashCommandBuilder()
.setName("hello")
.setDescription("Replies 'world'");
const permissions = [ROLE_ID_RUNNER];
const execute = async (interaction) => {
await interaction.reply("world");
}
export { data, permissions, execute };

View File

@ -1,5 +1,5 @@
import { SlashCommandBuilder } from "discord.js"; import { SlashCommandBuilder } from "discord.js";
import { PLAYER_ROLE_ID } from "../config.json"; import { ROLE_ID_PLAYER } from "../config.json";
const data = new SlashCommandBuilder() const data = new SlashCommandBuilder()
.setName("roll") .setName("roll")
@ -12,7 +12,7 @@ const data = new SlashCommandBuilder()
.setMinValue(1) .setMinValue(1)
); );
const permissions = [PLAYER_ROLE_ID]; const permissions = [ROLE_ID_PLAYER];
const execute = async (interaction) => { const execute = async (interaction) => {
const max = interaction.options.getInteger("max"); const max = interaction.options.getInteger("max");

View File

@ -1,15 +1,15 @@
{ {
"TOKEN": "redacted", "TOKEN": "redacted",
"CLIENT_ID": "1177748390092742767",
"GUILD_ID": "1175646363707523132", "GUILD_ID": "1175646363707523132",
"PLAYER_ROLE_ID": "1175646999140384778", "USER_ID_CLIENT": "1177748390092742767",
"RUNNER_ROLE_ID": "1175646976235282452", "USER_ID_DEBUG": "233036215610245120",
"COMMAND_CHANNEL_ID": "1177469184192565288", "ROLE_ID_PLAYER": "1175646999140384778",
"ADDUP_ID": "1175649994674544721", "ROLE_ID_RUNNER": "1175646976235282452",
"PICKING_ID": "1175649967935856680", "TEXT_ID_COMMAND": "1177469184192565288",
"BLU_ID": "1175649793943552010", "VOICE_ID_ADDUP": "1175649994674544721",
"RED_ID": "1175649777648680971", "VOICE_ID_PICKING": "1175649967935856680",
"FK_ID": "1176396207183106078", "VOICE_ID_BLU": "1175649793943552010",
"CAPTAIN_ID": "1178475124563919018", "VOICE_ID_RED": "1175649777648680971",
"VOICE_ID_FK": "1176396207183106078",
"RANKING_WHITELIST": "233036215610245120" "RANKING_WHITELIST": "233036215610245120"
} }

268
index.js
View File

@ -1,21 +1,20 @@
import fs from "fs"; import fs from "fs";
import path, { parse } from "path"; import path from "path";
import { Client, Collection, GatewayIntentBits, Partials } from "discord.js"; import { Client, Collection, GatewayIntentBits, Partials } from "discord.js";
import { getApplicableName, findPlayer } from "./utils.js";
const { const {
TOKEN, TOKEN,
GUILD_ID, GUILD_ID,
RUNNER_ROLE_ID, USER_ID_DEBUG,
COMMAND_CHANNEL_ID, TEXT_ID_COMMAND,
PICKING_ID, VOICE_ID_PICKING,
BLU_ID, VOICE_ID_BLU,
RED_ID, VOICE_ID_RED,
FK_ID,
CAPTAIN_ID,
} = require("./config.json"); } = require("./config.json");
let whitelistStr = require("./config.json").RANKING_WHITELIST; let whitelistStr = require("./config.json").RANKING_WHITELIST;
const client = new Client({ export const client = new Client({
intents: [ intents: [
GatewayIntentBits.Guilds, GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMembers,
@ -36,6 +35,11 @@ for (const file of commandFiles) {
const command = require(`./commands/${file}`); const command = require(`./commands/${file}`);
if ("data" in command && "execute" in command) { if ("data" in command && "execute" in command) {
client.fileCommands.set(command.data.name, command); client.fileCommands.set(command.data.name, command);
if (command.synonyms) {
for (const synonym of command.synonyms) {
client.fileCommands.set(synonym, command);
}
}
} else { } else {
console.error(`${__filename}: Invalid command file: ${file}`); console.error(`${__filename}: Invalid command file: ${file}`);
} }
@ -44,43 +48,6 @@ for (const file of commandFiles) {
const backticks = "```"; const backticks = "```";
const rankingsPath = path.join(__dirname, "rankings.json"); const rankingsPath = path.join(__dirname, "rankings.json");
const matchString = (str, search) => {
if (!str || !search) return false;
return str.toLowerCase().includes(search.toLowerCase());
};
const findPlayer = (guild, searchName) => {
// search display name
let player = guild.members.cache.find((member) =>
matchString(member.displayName, searchName)
);
if (!player) {
// search global name
player = guild.members.cache.find((member) =>
matchString(member.user.globalName, searchName)
);
}
if (!player) {
// search username
player = guild.members.cache.find((member) =>
matchString(member.user.username, searchName)
);
}
if (!player) {
// match id
player = guild.members.cache.find((member) => member.id === searchName);
}
return player;
};
const getApplicableName = (player) => {
return (
player.displayName ||
player.user.globalName ||
player.user.username ||
player.id
);
};
const avgDiff = (arr) => { const avgDiff = (arr) => {
// average absolute difference between all pairs of rank properties in a given array // average absolute difference between all pairs of rank properties in a given array
@ -207,9 +174,9 @@ const balanceArrays = (a1, a2) => {
client.on("ready", () => { client.on("ready", () => {
console.log(`Logged in as ${client.user.tag}!`); console.log(`Logged in as ${client.user.tag}!`);
console.log( console.log(
`Deleting old messages from this bot in command channel: ${COMMAND_CHANNEL_ID}...` `Deleting old messages from this bot in command channel: ${TEXT_ID_COMMAND}...`
); );
let channel = client.channels.cache.get(COMMAND_CHANNEL_ID); let channel = client.channels.cache.get(TEXT_ID_COMMAND);
if (!channel) return console.error("(ready) Can't find command channel"); if (!channel) return console.error("(ready) Can't find command channel");
channel.messages channel.messages
.fetch({ limit: 100 }) .fetch({ limit: 100 })
@ -226,11 +193,11 @@ client.on("ready", () => {
// send message on error // send message on error
client.on("error", (error) => { client.on("error", (error) => {
console.error(error); console.error(error);
let channel = client.channels.cache.get(COMMAND_CHANNEL_ID); let channel = client.channels.cache.get(TEXT_ID_COMMAND);
if (!channel) return console.error("Can't find command channel"); if (!channel) return console.error("Can't find command channel");
client.channels.cache client.channels.cache
.get(COMMAND_CHANNEL_ID) .get(TEXT_ID_COMMAND)
.send("Internal error") .send(`Internal error: ${error.message}`)
.catch(console.error); .catch(console.error);
}); });
@ -244,7 +211,7 @@ client.on("interactionCreate", async (interaction) => {
if (fileCommand.permissions) { if (fileCommand.permissions) {
const member = interaction.member; const member = interaction.member;
for (const permission of fileCommand.permissions) { for (const permission of fileCommand.permissions) {
if (!member.roles.cache.has(permission)) { if (!member.roles.cache.has(permission) && member.id !== USER_ID_DEBUG) {
await interaction.reply({ await interaction.reply({
content: "You lack the required permissions", content: "You lack the required permissions",
ephemeral: true, ephemeral: true,
@ -261,8 +228,8 @@ client.on("interactionCreate", async (interaction) => {
ephemeral: true, ephemeral: true,
}); });
} }
} else if (interaction.channelId !== COMMAND_CHANNEL_ID) { } else if (interaction.channelId !== TEXT_ID_COMMAND) {
//let isRunner = await interaction.member.roles.cache.has(RUNNER_ROLE_ID); //let isRunner = await interaction.member.roles.cache.has(ROLE_ID_RUNNER);
//if (!isRunner) { //if (!isRunner) {
await interaction.reply({ await interaction.reply({
content: "Wrong channel, or you lack the required permissions", content: "Wrong channel, or you lack the required permissions",
@ -271,191 +238,20 @@ client.on("interactionCreate", async (interaction) => {
return; return;
} }
if (
command === "scout" ||
command === "soldier" ||
command === "pyro" ||
command === "demoman" ||
command === "demo" ||
command === "heavy" ||
command === "engineer" ||
command === "engi" ||
command === "medic" ||
command === "sniper" ||
command === "spy"
) {
// get voice channels
const picking = interaction.guild.channels.cache.find(
(channel) => channel.name === "picking" || channel.id === PICKING_ID
);
if (!picking) return console.error("Can't find channel 'picking'!");
const captain = interaction.guild.channels.cache.find(
(channel) => channel.name === "captains" || channel.id === CAPTAIN_ID
);
if (!captain) return console.error("Can't find channel 'captains'!");
// make sure user is in picking or captain channel
if (
!picking.members.has(interaction.user.id) &&
!captain.members.has(interaction.user.id)
) {
await interaction.reply({
content: "Must be in picking or captains channel to use this command",
ephemeral: true,
});
return;
} else {
await interaction.reply({
content: "Checking picking channel...",
ephemeral: true,
});
// set role name
let roleName = command;
if (command === "demoman") roleName = "demo";
if (command === "engi") roleName = "engineer";
// check each member in picking channel for role
let str = `In picking (${roleName}):`;
for (const member of picking.members.values()) {
if (member.roles.cache.find((role) => role.name === roleName)) {
if (str !== `In picking (${roleName}):`) str += ",";
str += " " + getApplicableName(member);
}
}
if (str === `In picking (${roleName}):`)
str = `None found ¯\\_(ツ)_/¯ (${roleName})`;
// respond
return await interaction.followUp(str);
}
}
if (command === "hello") {
await interaction.reply("world");
}
if (
command === "topicking" ||
command === "end" ||
command === "resetteams"
) {
await interaction.reply("Moving members...");
// get voice channels
const blu = interaction.guild.channels.cache.find(
(channel) => channel.name === "blu" || channel.id === BLU_ID
);
if (!blu) return console.error("Can't find channel 'blu'!");
const red = interaction.guild.channels.cache.find(
(channel) => channel.name === "red" || channel.id === RED_ID
);
if (!red) return console.error("Can't find channel 'red'!");
const picking = interaction.guild.channels.cache.find(
(channel) => channel.name === "picking" || channel.id === PICKING_ID
);
if (!picking) return console.error("Can't find channel 'picking'!");
// get members in voice channel
let members = blu.members.concat(red.members);
if (members.size === 0) {
return await interaction.followUp("Found no members in blu or red");
}
let eCount = 0;
// move members to picking
const moveToPicking = async (member) => {
try {
await member.voice.setChannel(picking);
} catch (error) {
console.error(error);
eCount++;
}
};
const moveAllToPicking = async () => {
return Promise.all(
Array.from(members, async ([memberId, member]) => {
await moveToPicking(member);
})
);
};
moveAllToPicking().then(() =>
interaction.followUp(
`Moved members in blu and red${
eCount > 0 ? ` (error moving ${eCount} members)` : ""
}`
)
);
}
if (command === "fklist" || command === "listfk") {
// moves players in picking to fatkid channel, for use in captain pugs
await interaction.reply("Moving fatkids...");
// get voice channels
const picking = interaction.guild.channels.cache.find(
(channel) => channel.name === "picking" || channel.id === PICKING_ID
);
if (!picking) return console.error("Can't find channel 'add-up'!");
const fk = interaction.guild.channels.cache.find(
(channel) => channel.name === "fatkid" || channel.id === FK_ID
);
if (!fk) return console.error("Can't find channel 'fatkid'!");
// get members in voice channel
const members = picking.members;
if (members.size === 0) {
return await interaction.followUp("Found no members in picking");
}
let str = "",
eCount = 0;
const logFk = async (member) => {
try {
await member.voice.setChannel(fk);
if (str.length > 0) str += ", ";
str += getApplicableName(member);
} catch (error) {
console.error(error);
eCount++;
}
};
const logAllFks = async () => {
return Promise.all(
Array.from(members, async ([memberId, member]) => {
await logFk(member);
})
);
};
logAllFks().then(() =>
interaction.followUp(
`Fatkids: ${str}${
eCount > 0 ? ` (error moving ${eCount} members)` : ""
}`
)
);
}
if (command === "pick" || command === "autocaptain") { if (command === "pick" || command === "autocaptain") {
await interaction.reply("Picking teams..."); await interaction.reply("Picking teams...");
// get voice channels // get voice channels
const picking = interaction.guild.channels.cache.find( const picking = interaction.guild.channels.cache.find(
(channel) => channel.name === "picking" || channel.id === PICKING_ID (channel) => channel.name === "picking" || channel.id === VOICE_ID_PICKING
); );
if (!picking) return console.error("Can't find channel 'picking'!"); if (!picking) return console.error("Can't find channel 'picking'!");
const blu = interaction.guild.channels.cache.find( const blu = interaction.guild.channels.cache.find(
(channel) => channel.name === "blu" || channel.id === BLU_ID (channel) => channel.name === "blu" || channel.id === VOICE_ID_BLU
); );
if (!blu) return console.error("Can't find channel 'blu'!"); if (!blu) return console.error("Can't find channel 'blu'!");
const red = interaction.guild.channels.cache.find( const red = interaction.guild.channels.cache.find(
(channel) => channel.name === "red" || channel.id === RED_ID (channel) => channel.name === "red" || channel.id === VOICE_ID_RED
); );
if (!red) return console.error("Can't find channel 'red'!"); if (!red) return console.error("Can't find channel 'red'!");
@ -608,22 +404,6 @@ client.on("interactionCreate", async (interaction) => {
}` }`
); );
} }
if (command === "clear" || command === "bclear") {
await interaction.reply("Clearing messages...");
let channel = client.channels.cache.get(COMMAND_CHANNEL_ID);
if (!channel) return console.error("Can't find command channel");
channel.messages
.fetch({ limit: 100 })
.then((messages) => {
messages.forEach((message) => {
if (message.author.id === client.user.id) {
message.delete();
}
});
})
.catch(console.error);
}
}); });
/* /*

View File

@ -5,86 +5,6 @@ const path = require("path");
// set static commands // set static commands
const commands = [ const commands = [
{
name: "hello",
description: "Replies 'world'",
},
{
name: "topicking",
description: "Pulls team and addup channel members into picking channel",
},
{
name: "end",
description: "Pulls team and addup channel members into picking channel",
},
{
name: "resetteams",
description: "Pulls team channel members into picking channel",
},
/*{
name: "testfk",
description: "debug fk",
},*/
{
name: "fklist",
description: "Pulls addup channel members into fk channel and lists them",
},
{
name: "listfk",
description: "Pulls addup channel members into fk channel and lists them",
},
{
name: "clear",
description: "Clears bot messages in command channel",
},
{
name: "bclear",
description: "Clears bot messages in command channel",
},
{
name: "scout",
description: "Lists picking channel members with scout role",
},
{
name: "soldier",
description: "Lists picking channel members with soldier role",
},
{
name: "pyro",
description: "Lists picking channel members with pyro role",
},
{
name: "demo",
description: "Lists picking channel members with demo role",
},
{
name: "demoman",
description: "Alias of /demo",
},
{
name: "heavy",
description: "Lists picking channel members with heavy role",
},
{
name: "engineer",
description: "Lists picking channel members with engineer role",
},
{
name: "engi",
description: "Alias of /engineer",
},
{
name: "medic",
description: "Lists picking channel members with medic role",
},
{
name: "sniper",
description: "Lists picking channel members with sniper role",
},
{
name: "spy",
description: "Lists picking channel members with spy role",
},
{ {
name: "pick", name: "pick",
description: "Automatically picks teams", description: "Automatically picks teams",
@ -102,7 +22,16 @@ const commandFiles = fs.readdirSync(commandsPath);
for (const file of commandFiles) { for (const file of commandFiles) {
const command = require(`./commands/${file}`); const command = require(`./commands/${file}`);
if ("data" in command && "execute" in command) { if ("data" in command && "execute" in command) {
commands.push(command.data.toJSON()); let commandData = command.data;
commands.push(commandData);
if (command.synonyms) {
for (const synonym of command.synonyms) {
commands.push({
...commandData,
name: synonym,
});
}
}
} else { } else {
console.error(`${__filename}: Invalid command file: ${file}`); console.error(`${__filename}: Invalid command file: ${file}`);
} }

43
utils.js Normal file
View File

@ -0,0 +1,43 @@
export const findChannelfromCache = (channelCache, name, id) => {
return channelCache.find(
(channel) => channel.name === name || channel.id === id
);
}
export const matchString = (str, search) => {
if (!str || !search) return false;
return str.toLowerCase().includes(search.toLowerCase());
};
export const findPlayer = (guild, searchName) => {
// search display name
let player = guild.members.cache.find((member) =>
matchString(member.displayName, searchName)
);
if (!player) {
// search global name
player = guild.members.cache.find((member) =>
matchString(member.user.globalName, searchName)
);
}
if (!player) {
// search username
player = guild.members.cache.find((member) =>
matchString(member.user.username, searchName)
);
}
if (!player) {
// match id
player = guild.members.cache.find((member) => member.id === searchName);
}
return player;
};
export const getApplicableName = (player) => {
return (
player.displayName ||
player.user.globalName ||
player.user.username ||
player.id
);
};