564 lines
16 KiB
JavaScript
564 lines
16 KiB
JavaScript
import fs from "fs";
|
|
import path from "path";
|
|
import { Client, GatewayIntentBits, Partials } from "discord.js";
|
|
const {
|
|
TOKEN,
|
|
GUILD_ID,
|
|
COMMAND_CHANNEL_ID,
|
|
ADDUP_ID,
|
|
PICKING_ID,
|
|
BLU_ID,
|
|
RED_ID,
|
|
FK_ID,
|
|
CAPTAIN_ID,
|
|
} = require("./config.json");
|
|
|
|
let whitelistStr = require("./config.json").RANKING_WHITELIST;
|
|
|
|
const client = new Client({
|
|
intents: [
|
|
GatewayIntentBits.Guilds,
|
|
GatewayIntentBits.GuildMembers,
|
|
GatewayIntentBits.GuildPresences,
|
|
GatewayIntentBits.GuildVoiceStates,
|
|
GatewayIntentBits.DirectMessages,
|
|
GatewayIntentBits.MessageContent,
|
|
],
|
|
partials: [Partials.Channel, Partials.Message],
|
|
});
|
|
|
|
const backticks = "```";
|
|
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
|
|
);
|
|
};
|
|
|
|
client.on("ready", () => {
|
|
console.log(`Logged in as ${client.user.tag}!`);
|
|
console.log(
|
|
`Deleting old messages from this bot in command channel: ${COMMAND_CHANNEL_ID}...`
|
|
);
|
|
let channel = client.channels.cache.get(COMMAND_CHANNEL_ID);
|
|
if (!channel) return console.error("(ready) 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);
|
|
});
|
|
|
|
// send message on error
|
|
client.on("error", (error) => {
|
|
console.error(error);
|
|
let channel = client.channels.cache.get(COMMAND_CHANNEL_ID);
|
|
if (!channel) return console.error("Can't find command channel");
|
|
client.channels.cache
|
|
.get(COMMAND_CHANNEL_ID)
|
|
.send(`Error: ${error.message}`)
|
|
.catch(console.error);
|
|
});
|
|
|
|
client.on("interactionCreate", async (interaction) => {
|
|
const command = interaction.commandName;
|
|
if (!interaction.isChatInputCommand()) 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 (interaction.channelId !== COMMAND_CHANNEL_ID) {
|
|
await interaction.reply({
|
|
content: "Wrong channel, or you lack the required permissions",
|
|
ephemeral: true,
|
|
});
|
|
return;
|
|
}
|
|
|
|
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 addup = interaction.guild.channels.cache.find(
|
|
(channel) => channel.name === "add-up" || channel.id === ADDUP_ID
|
|
);
|
|
if (!addup) return console.error("Can't find channel 'add-up'!");
|
|
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 (command !== "resetteams") members = members.concat(addup.members);
|
|
if (members.size === 0) {
|
|
return await interaction.followUp(
|
|
`Found no members in ${
|
|
command === "resetteams" ? "blu" : "addup, blu,"
|
|
} or red`
|
|
);
|
|
}
|
|
|
|
// move members to addup
|
|
let idx = 0,
|
|
eCount = 0;
|
|
|
|
const moveToAddup = async (member) => {
|
|
try {
|
|
await member.voice.setChannel(picking);
|
|
} catch (error) {
|
|
console.error(error);
|
|
eCount++;
|
|
}
|
|
};
|
|
|
|
const moveAllToAddup = async () => {
|
|
return Promise.all(
|
|
Array.from(members, async ([memberId, member]) => {
|
|
await moveToAddup(member);
|
|
})
|
|
);
|
|
};
|
|
|
|
moveAllToAddup().then(() =>
|
|
interaction.followUp(
|
|
`Moved members in ${
|
|
command === "resetteams" ? "blu" : "addup, blu,"
|
|
} and red${eCount > 0 ? ` (error moving ${eCount} members)` : ""}`
|
|
)
|
|
);
|
|
}
|
|
|
|
if (command === "fk" || command === "fatkid") {
|
|
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 === "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);
|
|
}
|
|
});
|
|
|
|
/*
|
|
* DM commands
|
|
* - setrank: saves a player's rank
|
|
* - getrank: prints a player's rank
|
|
* - rankings: prints all players' ranks
|
|
* - whitelist: adds an admin to the whitelist to use DM commands
|
|
* - getwhitelist: prints the whitelist
|
|
* - clearwhitelist: clears the whitelist completely, security measure
|
|
*/
|
|
client.on("messageCreate", async (message) => {
|
|
if (message.author.bot || message.guild) return;
|
|
|
|
// check if user is whitelisted
|
|
if (!whitelistStr.includes(message.author.id)) {
|
|
return;
|
|
}
|
|
|
|
if (message.content.toLowerCase().includes("hello penguin")) {
|
|
await message.reply(`Hello ${message.author.username}`);
|
|
return;
|
|
}
|
|
|
|
const pickupGuild = client.guilds.cache.get(GUILD_ID);
|
|
if (!pickupGuild) {
|
|
await message.reply("Could not find guild");
|
|
return;
|
|
}
|
|
|
|
const args = message.content.toLowerCase().split(" ");
|
|
|
|
if (args[0] === "setrank") {
|
|
if (args.length < 3) {
|
|
await message.reply(
|
|
"Invalid number of arguments. usage: `setrank <player name or id> <rank (0-5)>`"
|
|
);
|
|
return;
|
|
}
|
|
|
|
const player = findPlayer(pickupGuild, args[1]);
|
|
if (!player) {
|
|
await message.reply(
|
|
`Could not find player ${args[1]}. If this issue persists, try copy/pasting the player's Discord handle or user ID`
|
|
);
|
|
return;
|
|
}
|
|
|
|
const playerId = player.id;
|
|
const applicableName = getApplicableName(player);
|
|
|
|
const rank = parseInt(args[2]);
|
|
if (isNaN(rank) || rank < 0 || rank > 5) {
|
|
await message.reply(
|
|
`Invalid rank ${args[2]}. Supply an integer between 0 and 5`
|
|
);
|
|
return;
|
|
}
|
|
|
|
// create rankings.json if it doesn't exist
|
|
const rankingsPath = path.join(__dirname, "rankings.json");
|
|
if (!fs.existsSync(rankingsPath)) {
|
|
fs.writeFileSync(rankingsPath, "{}");
|
|
}
|
|
|
|
// rankings.json is a map of player's id to rank
|
|
const rankings = JSON.parse(fs.readFileSync(rankingsPath));
|
|
rankings[playerId] = rank;
|
|
|
|
console.log(
|
|
`Setting rank of ${applicableName}, ${playerId} to ${rank} at ${rankingsPath}...`
|
|
);
|
|
new Promise((resolve, reject) => {
|
|
fs.writeFile(rankingsPath, JSON.stringify(rankings), (err) => {
|
|
if (err) reject(err);
|
|
else resolve();
|
|
});
|
|
}).then((res, err) => {
|
|
if (err) {
|
|
console.error(err);
|
|
message.reply(`Error setting rank: ${err.message}`);
|
|
} else {
|
|
message.reply(`Set rank of ${applicableName} to ${rank}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (args[0] === "getrank") {
|
|
if (args.length < 2) {
|
|
await message.reply(
|
|
"Invalid number of arguments. Usage: `getrank <player name or id>`"
|
|
);
|
|
return;
|
|
}
|
|
|
|
const player = findPlayer(pickupGuild, args[1]);
|
|
if (!player) {
|
|
await message.reply(
|
|
`Could not find player ${args[1]}. If this issue persists, try copy/pasting the player's Discord handle or user ID`
|
|
);
|
|
return;
|
|
}
|
|
|
|
const playerId = player.id;
|
|
const applicableName = getApplicableName(player);
|
|
|
|
try {
|
|
console.log(
|
|
`Getting rank of ${applicableName}, ${playerId} at ${rankingsPath}...`
|
|
);
|
|
if (!fs.existsSync(rankingsPath)) {
|
|
fs.writeFileSync(rankingsPath, "{}");
|
|
}
|
|
const rankings = JSON.parse(fs.readFileSync(rankingsPath));
|
|
await message.reply(
|
|
`${backticks}${applicableName} - ${"*".repeat(
|
|
rankings[playerId]
|
|
)}${backticks}`
|
|
);
|
|
} catch (error) {
|
|
console.error(error);
|
|
await message.reply(`Error getting rank: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
if (args[0] === "rankings") {
|
|
await message.reply("Getting rankings...");
|
|
try {
|
|
console.log(`Getting rankings at ${rankingsPath}...`);
|
|
if (!fs.existsSync(rankingsPath)) {
|
|
fs.writeFileSync(rankingsPath, "{}");
|
|
}
|
|
const rankings = JSON.parse(fs.readFileSync(rankingsPath));
|
|
let players = [];
|
|
for (const [playerId, rank] of Object.entries(rankings)) {
|
|
if (rank > 0) {
|
|
const player = await pickupGuild.members.fetch(playerId);
|
|
const applicableName = getApplicableName(player);
|
|
players.push({ name: applicableName, rank });
|
|
}
|
|
}
|
|
|
|
// sort by rank, then name
|
|
players.sort((a, b) => {
|
|
if (a.rank === b.rank) {
|
|
return a.name.localeCompare(b.name);
|
|
}
|
|
return b.rank - a.rank;
|
|
});
|
|
|
|
// build string
|
|
let str = backticks;
|
|
const maxNameLength = Math.max(...players.map((p) => p.name.length));
|
|
for (const { name, rank } of players) {
|
|
str += `${name.padEnd(maxNameLength, " ")} - ${"*".repeat(rank)}\n`;
|
|
}
|
|
str += backticks;
|
|
if (str === backticks + backticks) str = "No rankings found";
|
|
await message.reply(str);
|
|
} catch (error) {
|
|
console.error(error);
|
|
await message.reply(`Error getting rankings: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
if (args[0] === "whitelist") {
|
|
// add user to config.json whitelist
|
|
if (args.length < 2) {
|
|
await message.reply(
|
|
"Invalid number of arguments. Usage: `whitelist <name>`"
|
|
);
|
|
return;
|
|
}
|
|
|
|
const name = args[1];
|
|
const player = findPlayer(pickupGuild, name);
|
|
if (!player) {
|
|
await message.reply(
|
|
`Could not find player ${name}. If this issue persists, try copy/pasting the player's Discord handle or user ID`
|
|
);
|
|
return;
|
|
}
|
|
|
|
const playerId = player.id;
|
|
const applicableName = getApplicableName(player);
|
|
|
|
if (whitelistStr.includes(playerId)) {
|
|
await message.reply(
|
|
`User ${applicableName} (${playerId}) is already whitelisted`
|
|
);
|
|
return;
|
|
}
|
|
|
|
const configPath = path.join(__dirname, "config.json");
|
|
const config = JSON.parse(fs.readFileSync(configPath));
|
|
whitelistStr += `,${playerId}`;
|
|
config.RANKING_WHITELIST = whitelistStr;
|
|
new Promise((resolve, reject) => {
|
|
fs.writeFile(configPath, JSON.stringify(config), (err) => {
|
|
if (err) reject(err);
|
|
else resolve();
|
|
});
|
|
}).then((res, err) => {
|
|
if (err) {
|
|
console.error(err);
|
|
message.reply(`Error whitelisting user: ${err.message}`);
|
|
} else {
|
|
message.reply(
|
|
`Whitelisted user ${applicableName} (${playerId}). If this was done in error, please contact an admin`
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (args[0] === "getwhitelist") {
|
|
let str = `${backticks}${whitelistStr}${backticks}${backticks}`;
|
|
const whitelistIds = whitelistStr.split(",");
|
|
for (const id of whitelistIds) {
|
|
const player = findPlayer(pickupGuild, id);
|
|
if (player) {
|
|
str += `\n${getApplicableName(player)}`;
|
|
}
|
|
}
|
|
str += `${backticks}`;
|
|
await message.reply(str);
|
|
}
|
|
|
|
if (args[0] === "clearwhitelist") {
|
|
if (args[1] !== "confirm") {
|
|
await message.reply(
|
|
"This command will clear the whitelist and prevent further DM commands. This will require manual intervention to undo. To confirm, use `clearwhitelist confirm`"
|
|
);
|
|
return;
|
|
} else {
|
|
const configPath = path.join(__dirname, "config.json");
|
|
const config = JSON.parse(fs.readFileSync(configPath));
|
|
whitelistStr = "";
|
|
config.RANKING_WHITELIST = whitelistStr;
|
|
new Promise((resolve, reject) => {
|
|
fs.writeFile(configPath, JSON.stringify(config), (err) => {
|
|
if (err) reject(err);
|
|
else resolve();
|
|
});
|
|
}).then((res, err) => {
|
|
if (err) {
|
|
console.error(err);
|
|
message.reply(`Error clearing whitelist: ${err.message}`);
|
|
} else {
|
|
message.reply("Whitelist cleared, please alert an admin");
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
client.login(TOKEN);
|