From c222b0db1d920978d22a574d53a30b397a0dc1ba Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 26 Jan 2024 13:28:02 +0000 Subject: [PATCH 001/133] Initial commit --- .env.example | 2 + .eslintignore | 11 + .eslintrc.json | 12 + .gitignore | 5 + .prettierignore | 11 + .prettierrc | 8 + README.md | 11 + commands/examples/login.example.js | 36 + commands/examples/ping.example.js | 6 + commands/examples/role.example.js | 42 + deploy.js | 71 ++ events/interactionCreate.js | 61 + events/ready.js | 7 + index.js | 81 ++ package-lock.json | 1711 ++++++++++++++++++++++++++++ package.json | 26 + 16 files changed, 2101 insertions(+) create mode 100644 .env.example create mode 100644 .eslintignore create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 README.md create mode 100644 commands/examples/login.example.js create mode 100644 commands/examples/ping.example.js create mode 100644 commands/examples/role.example.js create mode 100644 deploy.js create mode 100644 events/interactionCreate.js create mode 100644 events/ready.js create mode 100644 index.js create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..63d9620 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +TOKEN= +CLIENT= \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..df2ca4a --- /dev/null +++ b/.eslintignore @@ -0,0 +1,11 @@ +.DS_Store +node_modules +/package +.env +.env.* +!.env.example + +# Ignore files for PNPM, NPM and YARN +pnpm-lock.yaml +package-lock.json +yarn.lock diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..85d1353 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "env": { + "node": true, + "es2021": true + }, + "extends": ["eslint:recommended", "prettier"], + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "rules": {} +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..60ee5c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +node_modules +.env +.env.* +!.env.example \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..df2ca4a --- /dev/null +++ b/.prettierignore @@ -0,0 +1,11 @@ +.DS_Store +node_modules +/package +.env +.env.* +!.env.example + +# Ignore files for PNPM, NPM and YARN +pnpm-lock.yaml +package-lock.json +yarn.lock diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..1f1cdd4 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": [], + "overrides": [] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..67c6332 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# A DiscordJS Template + +## Getting started + +- Create a new repository from this template. +- Remove example commands: + - Either permanently delete directory `commands/examples/` + - Or move `commands/examples/` elsewhere, commit deletion, add to [`.gitignore`](.gitignore) and move back in +- Look through, copy and/or modify the [examples](commands/examples/). +- Either read a [guide](https://discordjs.guide/)! +- Or read the [docs](https://discord.js.org/docs)! diff --git a/commands/examples/login.example.js b/commands/examples/login.example.js new file mode 100644 index 0000000..cf37c63 --- /dev/null +++ b/commands/examples/login.example.js @@ -0,0 +1,36 @@ +import { + ActionRowBuilder, + ModalBuilder, + SlashCommandBuilder, + TextInputBuilder, + TextInputStyle +} from 'discord.js'; + +export const data = new SlashCommandBuilder() + .setName('login') + .setDescription('Opens a login pop-up.'); +export async function modalSubmit(interaction) { + await interaction.reply({ content: 'Successfully submitted Form!', ephemeral: true }); +} +export async function execute(interaction) { + const modal = new ModalBuilder().setCustomId('login-modal').setTitle('Login Form'); + + const user = new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId('user') + .setLabel('Enter username:') + .setStyle(TextInputStyle.Short) + .setRequired(true) + ); + const password = new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId('password') + .setLabel('Enter password:') + .setStyle(TextInputStyle.Short) + .setRequired(true) + ); + + modal.addComponents(user, password); + + await interaction.showModal(modal); +} diff --git a/commands/examples/ping.example.js b/commands/examples/ping.example.js new file mode 100644 index 0000000..42bb665 --- /dev/null +++ b/commands/examples/ping.example.js @@ -0,0 +1,6 @@ +import { SlashCommandBuilder } from 'discord.js'; + +export const data = new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!'); +export async function execute(interaction) { + await interaction.reply({ content: 'Pong!', ephemeral: true }); +} diff --git a/commands/examples/role.example.js b/commands/examples/role.example.js new file mode 100644 index 0000000..50cd61e --- /dev/null +++ b/commands/examples/role.example.js @@ -0,0 +1,42 @@ +import { + ActionRowBuilder, + ComponentType, + RoleSelectMenuBuilder, + SlashCommandBuilder +} from 'discord.js'; + +export const data = new SlashCommandBuilder() + .setName('role') + .setDMPermission(false) + .setDescription('Provides a role selector.'); +export async function execute(interaction) { + const roles = await interaction.guild.roles.fetch(); + const choices = roles.filter((r) => r.name.startsWith('test')).map((r) => r.id); + + const button = new RoleSelectMenuBuilder() + .setMinValues(1) + .setMaxValues(25) + .setCustomId('role') + .setDefaultRoles(choices) + .setPlaceholder('Select at least one role.'); + + const row = new ActionRowBuilder().addComponents(button); + + const response = await interaction.reply({ + components: [row], + ephemeral: true + }); + + const collector = response.createMessageComponentCollector({ + componentType: ComponentType.RoleSelect, + time: 120_000 + }); + + collector.on('collect', async (i) => { + const selection = roles + .filter((r) => i.values.includes(r.id)) + .map((r) => r.name) + .join(', '); + await i.reply({ content: `You have selected: "${selection}".`, ephemeral: true }); + }); +} diff --git a/deploy.js b/deploy.js new file mode 100644 index 0000000..d1bc276 --- /dev/null +++ b/deploy.js @@ -0,0 +1,71 @@ +import { REST, Routes } from 'discord.js'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { promisify } from 'util'; +import { config } from 'dotenv'; +import { readdir } from 'fs'; + +config(); +const readdirAsync = promisify(readdir); + +const required = ['data', 'execute']; +const optional = ['autocomplete', 'modalSubmit']; + +// Construct and prepare an instance of the REST module +const rest = new REST().setToken(process.env.TOKEN); + +// and deploy your commands! +const putCommands = async (commands) => { + try { + console.info(`[INFO] Started refreshing ${commands.length} application (/) commands.`); + + // The put method is used to fully refresh all commands in the guild with the current set + const data = await rest.put(Routes.applicationCommands(process.env.CLIENT), { body: commands }); + + console.info(`[INFO] Successfully reloaded ${data.length} application (/) commands.`); + } catch (error) { + // And of course, make sure you catch and log any errors! + console.error('[ERROR] Uncaught error:'); + console.error(error); + } +}; + +// Register commands from sub-directories +const __dirname = dirname(fileURLToPath(import.meta.url)); +const mainPath = join(__dirname, 'commands'); +readdirAsync(mainPath) + .then( + async (categories) => + // For each category directory + await Promise.all( + categories.map(async (category) => { + const catPath = join(mainPath, category); + const files = (await readdirAsync(catPath)).filter((file) => file.endsWith('.js') && !file.endsWith('.example.js')); + // For each command file + return await Promise.all( + files.map(async (current) => { + const filePath = join(catPath, current); + const command = await import(filePath); + // Warn incomplete commands + if (!required.every((name) => name in command)) { + console.error( + `[ERROR] The command at ${filePath} is missing a required "data" or "execute" property.` + ); + return 0; + } + const properties = optional.filter((name) => !(name in command)); + if (properties.length > 0) + properties.forEach((name) => + console.warn( + `[WARNING] The command at ${filePath} is missing a optional "${name}" property.` + ) + ); + // Add command to collection + return command.data.toJSON(); + }) + ); + }) + ) + ) + .then((commands) => commands.reduce((a, i) => a.concat(i), []).filter((e) => e !== 0)) + .then(putCommands); diff --git a/events/interactionCreate.js b/events/interactionCreate.js new file mode 100644 index 0000000..86b465b --- /dev/null +++ b/events/interactionCreate.js @@ -0,0 +1,61 @@ +import { Events } from 'discord.js'; + +const chatInputCommand = async (interaction, command) => { + // Try executing command + try { + console.info(`[INFO] Command ${interaction.commandName} was executed.`); + await command.execute(interaction); + } catch (error) { + console.error(error); + // Follow up/reply with error message + if (interaction.replied || interaction.deferred) + await interaction.followUp({ + content: 'There was an error while executing this command!', + ephemeral: true + }); + else + await interaction.reply({ + content: 'There was an error while executing this command!', + ephemeral: true + }); + } +}; + +const genericExecute = async (interaction, command, name, description) => { + try { + console.info(`[INFO] Command ${interaction.commandName} ${description ?? `used "${name}"`}.`); + await command[name](interaction); + } catch (error) { + console.error(error); + } +}; + +export const name = Events.InteractionCreate; +export async function execute(interaction) { + let command = interaction.client.commands.get(interaction.commandName); + + // Execute slash commands + if (interaction.isChatInputCommand()) { + await chatInputCommand(interaction, command); + return; + } + // Autocomplete input + if (interaction.isAutocomplete() && 'autocomplete' in command) { + await genericExecute(interaction, command, 'autocomplete'); + return; + } + // Modeal submit event + if (interaction.isModalSubmit()) { + const name = interaction.customId.split('-')[0]; + command = interaction.client.commands.get(name); + + await genericExecute(interaction, command, 'modalSubmit', 'submitted a modal'); + return; + } + + // Check if command exists + if (interaction.commandName && !command) { + console.warn(`[WARNING] No command matching ${interaction.commandName} was found.`); + return; + } +} diff --git a/events/ready.js b/events/ready.js new file mode 100644 index 0000000..561e67c --- /dev/null +++ b/events/ready.js @@ -0,0 +1,7 @@ +import { Events } from 'discord.js'; + +export const name = Events.ClientReady; +export const once = true; +export function execute(client) { + console.info(`[INFO] Ready! Logged in as ${client.user.tag}`); +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..7112316 --- /dev/null +++ b/index.js @@ -0,0 +1,81 @@ +import { Client, Collection, GatewayIntentBits } from 'discord.js'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { promisify } from 'util'; +import { config } from 'dotenv'; +import { readdir } from 'fs'; + +config(); +const readdirAsync = promisify(readdir); + +const required = ['data', 'execute']; +const optional = ['autocomplete', 'modalSubmit']; + +const runClient = (commands, events) => { + // Create a new client instance + const client = new Client({ intents: [GatewayIntentBits.Guilds] }); + client.commands = new Collection(); + commands.forEach((c) => client.commands.set(c.data.name, c)); + events.forEach((e) => + e.once + ? client.once(e.name, (...args) => e.execute(...args)) + : client.on(e.name, (...args) => e.execute(...args)) + ); + + // Log in to Discord with your client's token + client.login(process.env.TOKEN); +}; + +// Register commands from sub-directories +const __dirname = dirname(fileURLToPath(import.meta.url)); +const cmdPath = join(__dirname, 'commands'); +const evtPath = join(__dirname, 'events'); +readdirAsync(cmdPath) + .then( + async (categories) => + // For each category directory + await Promise.all( + categories.map(async (category) => { + const catPath = join(cmdPath, category); + const content = await readdirAsync(catPath); + const files = content.filter((file) => file.endsWith('.js') && !file.endsWith('.example.js')); + // For each command file + return await Promise.all( + files.map(async (current) => { + const filePath = join(catPath, current); + const command = await import(filePath); + // Warn incomplete commands + if (!required.every((name) => name in command)) { + console.error( + `[ERROR] The command at ${filePath} is missing a required "data" or "execute" property.` + ); + return 0; + } + const properties = optional.filter((name) => !(name in command)); + if (properties.length > 0) + properties.forEach((name) => + console.warn( + `[WARNING] The command at ${filePath} is missing a optional "${name}" property.` + ) + ); + // Add command to collection + return command; + }) + ); + }) + ) + ) + .then((commands) => commands.reduce((a, i) => a.concat(i), []).filter((e) => e !== 0)) + .then(async (commands) => { + const content = await readdirAsync(evtPath); + const files = content.filter((file) => file.endsWith('.js')); + const events = await Promise.all( + files.map(async (current) => { + const filePath = join(evtPath, current); + const command = await import(filePath); + // Add event to collection + return command; + }) + ); + runClient(commands, events); + }); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9509053 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1711 @@ +{ + "name": "discord-bot", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "discord-bot", + "version": "0.0.1", + "license": "ISC", + "dependencies": { + "discord.js": "^14.14.1", + "dotenv": "^16.3.1", + "proxy-agent": "^6.3.1" + }, + "devDependencies": { + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "prettier": "^3.1.1" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@discordjs/builders": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.7.0.tgz", + "integrity": "sha512-GDtbKMkg433cOZur8Dv6c25EHxduNIBsxeHrsRoIM8+AwmEZ8r0tEpckx/sHwTLwQPOF3e2JWloZh9ofCaMfAw==", + "dependencies": { + "@discordjs/formatters": "^0.3.3", + "@discordjs/util": "^1.0.2", + "@sapphire/shapeshift": "^3.9.3", + "discord-api-types": "0.37.61", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/formatters": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.3.3.tgz", + "integrity": "sha512-wTcI1Q5cps1eSGhl6+6AzzZkBBlVrBdc9IUhJbijRgVjCNIIIZPgqnUj3ntFODsHrdbGU8BEG9XmDQmgEEYn3w==", + "dependencies": { + "discord-api-types": "0.37.61" + }, + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/rest": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.2.0.tgz", + "integrity": "sha512-nXm9wT8oqrYFRMEqTXQx9DUTeEtXUDMmnUKIhZn6O2EeDY9VCdwj23XCPq7fkqMPKdF7ldAfeVKyxxFdbZl59A==", + "dependencies": { + "@discordjs/collection": "^2.0.0", + "@discordjs/util": "^1.0.2", + "@sapphire/async-queue": "^1.5.0", + "@sapphire/snowflake": "^3.5.1", + "@vladfrangu/async_event_emitter": "^2.2.2", + "discord-api-types": "0.37.61", + "magic-bytes.js": "^1.5.0", + "tslib": "^2.6.2", + "undici": "5.27.2" + }, + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.0.0.tgz", + "integrity": "sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@discordjs/util": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.0.2.tgz", + "integrity": "sha512-IRNbimrmfb75GMNEjyznqM1tkI7HrZOf14njX7tCAAUetyZM1Pr8hX/EK2lxBCOgWDRmigbp24fD1hdMfQK5lw==", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/ws": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.0.2.tgz", + "integrity": "sha512-+XI82Rm2hKnFwAySXEep4A7Kfoowt6weO6381jgW+wVdTpMS/56qCvoXyFRY0slcv7c/U8My2PwIB2/wEaAh7Q==", + "dependencies": { + "@discordjs/collection": "^2.0.0", + "@discordjs/rest": "^2.1.0", + "@discordjs/util": "^1.0.2", + "@sapphire/async-queue": "^1.5.0", + "@types/ws": "^8.5.9", + "@vladfrangu/async_event_emitter": "^2.2.2", + "discord-api-types": "0.37.61", + "tslib": "^2.6.2", + "ws": "^8.14.2" + }, + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.0.0.tgz", + "integrity": "sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", + "integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.1.tgz", + "integrity": "sha512-1RdpsmDQR/aWfp8oJzPtn4dNQrbpqSL5PIA0uAB/XwerPXUf994Ug1au1e7uGcD7ei8/F63UDjr5GWps1g/HxQ==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/shapeshift": { + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.4.tgz", + "integrity": "sha512-SiOoCBmm8O7QuadLJnX4V0tAkhC54NIOZJtmvw+5zwnHaiulGkjY02wxCuK8Gf4V540ILmGz+UulC0U8mrOZjg==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/snowflake": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.1.tgz", + "integrity": "sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" + }, + "node_modules/@types/node": { + "version": "20.10.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz", + "integrity": "sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ws": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz", + "integrity": "sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.4.tgz", + "integrity": "sha512-ButUPz9E9cXMLgvAW8aLAKKJJsPu1dY1/l/E8xzLFuysowXygs6GBcyunK9rnGC4zTsnIc2mQo71rGw9U+Ykug==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/basic-ftp": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.3.tgz", + "integrity": "sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.1.tgz", + "integrity": "sha512-MZd3VlchQkp8rdend6vrx7MmVDJzSNTBvghvKjirLkD+WTChA3KUf0jkE68Q4UyctNqI11zZO9/x2Yx+ub5Cvg==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/discord-api-types": { + "version": "0.37.61", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.61.tgz", + "integrity": "sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw==" + }, + "node_modules/discord.js": { + "version": "14.14.1", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.14.1.tgz", + "integrity": "sha512-/hUVzkIerxKHyRKopJy5xejp4MYKDPTszAnpYxzVVv4qJYf+Tkt+jnT2N29PIPschicaEEpXwF2ARrTYHYwQ5w==", + "dependencies": { + "@discordjs/builders": "^1.7.0", + "@discordjs/collection": "1.5.3", + "@discordjs/formatters": "^0.3.3", + "@discordjs/rest": "^2.1.0", + "@discordjs/util": "^1.0.2", + "@discordjs/ws": "^1.0.2", + "@sapphire/snowflake": "3.5.1", + "@types/ws": "8.5.9", + "discord-api-types": "0.37.61", + "fast-deep-equal": "3.1.3", + "lodash.snakecase": "4.1.1", + "tslib": "2.6.2", + "undici": "5.27.2", + "ws": "8.14.2" + }, + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/get-uri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.2.tgz", + "integrity": "sha512-5KLucCJobh8vBY1K07EFV4+cPZH3mrV9YeAruUseCQKHB58SGjjT2l9/eA9LD082IiuMjSlFJEcdJ27TXvbZNw==", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.0", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/magic-bytes.js": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.7.0.tgz", + "integrity": "sha512-YzVU2+/hrjwx8xcgAw+ffNq3jkactpj+f1iSL4LonrFKhvnwDzHSqtFdk/MMRP53y9ScouJ7cKEnqYsJwsHoYA==" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz", + "integrity": "sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz", + "integrity": "sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==", + "dependencies": { + "degenerator": "^5.0.0", + "ip": "^1.1.8", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", + "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-agent": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.1.tgz", + "integrity": "sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", + "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks/node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/ts-mixer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz", + "integrity": "sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==" + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici": { + "version": "5.27.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.27.2.tgz", + "integrity": "sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..961231e --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "discord-bot", + "version": "0.0.1", + "description": "", + "private": true, + "main": "index.js", + "scripts": { + "start": "node .", + "deploy": "node deploy.js", + "lint": "prettier --check . && eslint .", + "format": "prettier --write ." + }, + "author": "", + "license": "ISC", + "dependencies": { + "discord.js": "^14.14.1", + "dotenv": "^16.3.1", + "proxy-agent": "^6.3.1" + }, + "devDependencies": { + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "prettier": "^3.1.1" + }, + "type": "module" +} From 7b04f95444ea032bebac2bbb4bf7f9ac9c260859 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 26 Jan 2024 14:30:35 +0100 Subject: [PATCH 002/133] ignore example commands --- .gitignore | 3 ++- commands/examples/login.example.js | 36 ------------------------- commands/examples/ping.example.js | 6 ----- commands/examples/role.example.js | 42 ------------------------------ 4 files changed, 2 insertions(+), 85 deletions(-) delete mode 100644 commands/examples/login.example.js delete mode 100644 commands/examples/ping.example.js delete mode 100644 commands/examples/role.example.js diff --git a/.gitignore b/.gitignore index 60ee5c8..e993747 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules .env .env.* -!.env.example \ No newline at end of file +!.env.example +commands/examples diff --git a/commands/examples/login.example.js b/commands/examples/login.example.js deleted file mode 100644 index cf37c63..0000000 --- a/commands/examples/login.example.js +++ /dev/null @@ -1,36 +0,0 @@ -import { - ActionRowBuilder, - ModalBuilder, - SlashCommandBuilder, - TextInputBuilder, - TextInputStyle -} from 'discord.js'; - -export const data = new SlashCommandBuilder() - .setName('login') - .setDescription('Opens a login pop-up.'); -export async function modalSubmit(interaction) { - await interaction.reply({ content: 'Successfully submitted Form!', ephemeral: true }); -} -export async function execute(interaction) { - const modal = new ModalBuilder().setCustomId('login-modal').setTitle('Login Form'); - - const user = new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId('user') - .setLabel('Enter username:') - .setStyle(TextInputStyle.Short) - .setRequired(true) - ); - const password = new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId('password') - .setLabel('Enter password:') - .setStyle(TextInputStyle.Short) - .setRequired(true) - ); - - modal.addComponents(user, password); - - await interaction.showModal(modal); -} diff --git a/commands/examples/ping.example.js b/commands/examples/ping.example.js deleted file mode 100644 index 42bb665..0000000 --- a/commands/examples/ping.example.js +++ /dev/null @@ -1,6 +0,0 @@ -import { SlashCommandBuilder } from 'discord.js'; - -export const data = new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!'); -export async function execute(interaction) { - await interaction.reply({ content: 'Pong!', ephemeral: true }); -} diff --git a/commands/examples/role.example.js b/commands/examples/role.example.js deleted file mode 100644 index 50cd61e..0000000 --- a/commands/examples/role.example.js +++ /dev/null @@ -1,42 +0,0 @@ -import { - ActionRowBuilder, - ComponentType, - RoleSelectMenuBuilder, - SlashCommandBuilder -} from 'discord.js'; - -export const data = new SlashCommandBuilder() - .setName('role') - .setDMPermission(false) - .setDescription('Provides a role selector.'); -export async function execute(interaction) { - const roles = await interaction.guild.roles.fetch(); - const choices = roles.filter((r) => r.name.startsWith('test')).map((r) => r.id); - - const button = new RoleSelectMenuBuilder() - .setMinValues(1) - .setMaxValues(25) - .setCustomId('role') - .setDefaultRoles(choices) - .setPlaceholder('Select at least one role.'); - - const row = new ActionRowBuilder().addComponents(button); - - const response = await interaction.reply({ - components: [row], - ephemeral: true - }); - - const collector = response.createMessageComponentCollector({ - componentType: ComponentType.RoleSelect, - time: 120_000 - }); - - collector.on('collect', async (i) => { - const selection = roles - .filter((r) => i.values.includes(r.id)) - .map((r) => r.name) - .join(', '); - await i.reply({ content: `You have selected: "${selection}".`, ephemeral: true }); - }); -} From 3f1495cd07c04cae51d11430d98aa0cfa8e67200 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 26 Jan 2024 14:34:15 +0100 Subject: [PATCH 003/133] bot invite README --- README.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 67c6332..87445d5 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,3 @@ -# A DiscordJS Template +## Bot invite -## Getting started - -- Create a new repository from this template. -- Remove example commands: - - Either permanently delete directory `commands/examples/` - - Or move `commands/examples/` elsewhere, commit deletion, add to [`.gitignore`](.gitignore) and move back in -- Look through, copy and/or modify the [examples](commands/examples/). -- Either read a [guide](https://discordjs.guide/)! -- Or read the [docs](https://discord.js.org/docs)! +link [here](https://discord.com/api/oauth2/authorize?client_id=698979939847766066&permissions=8&scope=applications.commands+bot). From 617edfd0a2435634c8e8d4e044943a8b5f043c49 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 26 Jan 2024 19:29:20 +0100 Subject: [PATCH 004/133] initialize self role command --- commands/admin/self_roles.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 commands/admin/self_roles.js diff --git a/commands/admin/self_roles.js b/commands/admin/self_roles.js new file mode 100644 index 0000000..e2367f6 --- /dev/null +++ b/commands/admin/self_roles.js @@ -0,0 +1,9 @@ +import { SlashCommandBuilder } from 'discord.js'; + +export const data = new SlashCommandBuilder() + .setName('self_roles') + .setDescription('Manages reactions for self roles.'); +export async function execute(interaction) { + await interaction + .reply('Unimplemented!'); +} From e67a846fa41b40e2c44a8ea643bb170bfa87a888 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 26 Jan 2024 20:23:19 +0100 Subject: [PATCH 005/133] better naming convention --- commands/admin/{self_roles.js => selfRoles.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename commands/admin/{self_roles.js => selfRoles.js} (100%) diff --git a/commands/admin/self_roles.js b/commands/admin/selfRoles.js similarity index 100% rename from commands/admin/self_roles.js rename to commands/admin/selfRoles.js From 713135a0219a357ad273a0a0cc82bdfee7795e15 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 26 Jan 2024 20:23:45 +0100 Subject: [PATCH 006/133] initialize reaction events --- events/reactionAdd.js | 6 ++++++ events/reactionRemove.js | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 events/reactionAdd.js create mode 100644 events/reactionRemove.js diff --git a/events/reactionAdd.js b/events/reactionAdd.js new file mode 100644 index 0000000..c45c021 --- /dev/null +++ b/events/reactionAdd.js @@ -0,0 +1,6 @@ +import { Events } from 'discord.js'; + +export const name = Events.MessageReactionAdd; +export async function execute(messageReaction, user) { + console.log(messageReaction, user); +} diff --git a/events/reactionRemove.js b/events/reactionRemove.js new file mode 100644 index 0000000..9fc1eaa --- /dev/null +++ b/events/reactionRemove.js @@ -0,0 +1,6 @@ +import { Events } from 'discord.js'; + +export const name = Events.MessageReactionRemove; +export async function execute(messageReaction, user) { + console.log(messageReaction, user); +} From 0edf46856a94bbaee73cd377cc04c01ca530f178 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 28 Jan 2024 18:33:03 +0100 Subject: [PATCH 007/133] detect users adding/removing reactions --- events/reactionAdd.js | 6 ++++-- events/reactionRemove.js | 6 ++++-- index.js | 10 +++++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/events/reactionAdd.js b/events/reactionAdd.js index c45c021..35fc162 100644 --- a/events/reactionAdd.js +++ b/events/reactionAdd.js @@ -1,6 +1,8 @@ import { Events } from 'discord.js'; export const name = Events.MessageReactionAdd; -export async function execute(messageReaction, user) { - console.log(messageReaction, user); +export async function execute(reaction, user) { + const uname = user.username; + const rname = reaction._emoji.name; + console.debug(`[DEBUG] User '${uname}' reacted with emoji '${rname}'.`); } diff --git a/events/reactionRemove.js b/events/reactionRemove.js index 9fc1eaa..58d4c52 100644 --- a/events/reactionRemove.js +++ b/events/reactionRemove.js @@ -1,6 +1,8 @@ import { Events } from 'discord.js'; export const name = Events.MessageReactionRemove; -export async function execute(messageReaction, user) { - console.log(messageReaction, user); +export async function execute(reaction, user) { + const uname = user.username; + const rname = reaction._emoji.name; + console.debug(`[DEBUG] User '${uname}' removed reaction of emoji '${rname}'.`); } diff --git a/index.js b/index.js index 7112316..8b7aee2 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,7 @@ import { fileURLToPath } from 'url'; import { promisify } from 'util'; import { config } from 'dotenv'; import { readdir } from 'fs'; +import { Partials } from 'discord.js'; config(); const readdirAsync = promisify(readdir); @@ -13,7 +14,14 @@ const optional = ['autocomplete', 'modalSubmit']; const runClient = (commands, events) => { // Create a new client instance - const client = new Client({ intents: [GatewayIntentBits.Guilds] }); + const client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.GuildMessageReactions + ], + partials: [Partials.Message, Partials.Reaction] + }); client.commands = new Collection(); commands.forEach((c) => client.commands.set(c.data.name, c)); events.forEach((e) => From 7fe865ad98197b52e59cb226b41c5a3c980b3186 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 28 Jan 2024 18:33:22 +0100 Subject: [PATCH 008/133] detect users joining/leaving voice channels --- events/voiceStateUpdate.js | 16 ++++++++++++++++ index.js | 1 + 2 files changed, 17 insertions(+) create mode 100644 events/voiceStateUpdate.js diff --git a/events/voiceStateUpdate.js b/events/voiceStateUpdate.js new file mode 100644 index 0000000..3473ee0 --- /dev/null +++ b/events/voiceStateUpdate.js @@ -0,0 +1,16 @@ +import { Events } from 'discord.js'; + +export const name = Events.VoiceStateUpdate; +export async function execute(oldState, newState) { + console.debug('[DEBUG] Voice State Update'); + + const change = (!!oldState.channel) ^ (!!newState.channel); + if (!change) return; + + const guild = newState.guild.name; + const member = newState.member.user.username; + const state = newState.channel ? 'joined' : 'left'; + const channel = (oldState.channel ?? newState.channel).name; + + console.debug(`[DEBUG] User '${member}' ${state} channel '${channel}' in guild '${guild}'.`); +} diff --git a/index.js b/index.js index 8b7aee2..a08a3bf 100644 --- a/index.js +++ b/index.js @@ -18,6 +18,7 @@ const runClient = (commands, events) => { intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, + GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMessageReactions ], partials: [Partials.Message, Partials.Reaction] From db551f7e5b480e701cdb365ee2a234c85181560a Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 28 Jan 2024 18:34:35 +0100 Subject: [PATCH 009/133] use slashcommand examples --- commands/utility/login.js | 41 +++++++++++++++++++++++++++ commands/utility/roleSelector.js | 48 ++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 commands/utility/login.js create mode 100644 commands/utility/roleSelector.js diff --git a/commands/utility/login.js b/commands/utility/login.js new file mode 100644 index 0000000..d9a73fb --- /dev/null +++ b/commands/utility/login.js @@ -0,0 +1,41 @@ +import { + ActionRowBuilder, + ModalBuilder, + SlashCommandBuilder, + TextInputBuilder, + TextInputStyle +} from 'discord.js'; + +export const data = new SlashCommandBuilder() + .setName('login') + .setDescription('Opens a login pop-up.'); +export async function modalSubmit(interaction) { + await interaction.reply({ + content: 'Successfully submitted Form!', + ephemeral: true + }); +} +export async function execute(interaction) { + const modal = new ModalBuilder() + .setCustomId('login-modal') + .setTitle('Login Form'); + + const user = new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId('user') + .setLabel('Enter username:') + .setStyle(TextInputStyle.Short) + .setRequired(true) + ); + const password = new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId('password') + .setLabel('Enter password:') + .setStyle(TextInputStyle.Short) + .setRequired(true) + ); + + modal.addComponents(user, password); + + await interaction.showModal(modal); +} diff --git a/commands/utility/roleSelector.js b/commands/utility/roleSelector.js new file mode 100644 index 0000000..2812bcf --- /dev/null +++ b/commands/utility/roleSelector.js @@ -0,0 +1,48 @@ +import { + ActionRowBuilder, + ComponentType, + RoleSelectMenuBuilder, + SlashCommandBuilder +} from 'discord.js'; + +export const data = new SlashCommandBuilder() + .setName('role_selector') + .setDMPermission(false) + .setDescription('Provides a role selector.'); +export async function execute(interaction) { + const roles = await interaction.guild.roles.fetch(); + const choices = roles + .filter((r) => r.name.startsWith('test')) + .map((r) => r.id); + + const button = new RoleSelectMenuBuilder() + .setMinValues(1) + .setMaxValues(25) + .setCustomId('role') + .setDefaultRoles(choices) + .setPlaceholder('Select at least one role.'); + + const row = new ActionRowBuilder() + .addComponents(button); + + const response = await interaction.reply({ + components: [row], + ephemeral: true + }); + + const collector = response.createMessageComponentCollector({ + componentType: ComponentType.RoleSelect, + time: 120_000 + }); + + collector.on('collect', async (i) => { + const selection = roles + .filter((r) => i.values.includes(r.id)) + .map((r) => r.name) + .join(', '); + await i.reply({ + content: `You have selected: "${selection}".`, + ephemeral: true + }); + }); +} From 8a619b53e4f0ccc48a387e45790b1981c63cd49b Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 29 Jan 2024 00:30:33 +0100 Subject: [PATCH 010/133] initialize sequalize with sqlite setup --- .env.example | 6 +- database.js | 15 + package-lock.json | 1312 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 4 +- 4 files changed, 1315 insertions(+), 22 deletions(-) create mode 100644 database.js diff --git a/.env.example b/.env.example index 63d9620..a1bc242 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,6 @@ TOKEN= -CLIENT= \ No newline at end of file +CLIENT= +DB_PWD= +DB_NAME= +DB_HOST= +DB_USER= diff --git a/database.js b/database.js new file mode 100644 index 0000000..4efe98f --- /dev/null +++ b/database.js @@ -0,0 +1,15 @@ +import { Sequelize } from 'sequelize'; +import { config } from 'dotenv'; +config(); + +const { DB_NAME, DB_USER, DB_PWD, DB_HOST } = process.env; +const sequelize = new Sequelize(DB_NAME, DB_USER, DB_PWD, { + storage: `${DB_NAME}.sqlite`, + dialect: 'sqlite', + logging: false, + host: DB_HOST, +}); + +sequelize.sync(); + +export { sequelize }; diff --git a/package-lock.json b/package-lock.json index 9509053..14d32f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,9 @@ "dependencies": { "discord.js": "^14.14.1", "dotenv": "^16.3.1", - "proxy-agent": "^6.3.1" + "proxy-agent": "^6.3.1", + "sequelize": "^6.35.2", + "sqlite3": "^5.1.7" }, "devDependencies": { "eslint": "^8.56.0", @@ -190,6 +192,12 @@ "node": ">=14" } }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "optional": true + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -258,6 +266,30 @@ "node": ">= 8" } }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@sapphire/async-queue": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.1.tgz", @@ -289,11 +321,33 @@ "npm": ">=7.0.0" } }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" + }, "node_modules/@types/node": { "version": "20.10.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz", @@ -302,6 +356,11 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/validator": { + "version": "13.11.8", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.8.tgz", + "integrity": "sha512-c/hzNDBh7eRF+KbCf+OoZxKbnkpaK/cKp9iLQWqB7muXtM+MtL9SUUH8vCFcLn6dH1Qm05jiexK0ofWY7TfOhQ==" + }, "node_modules/@types/ws": { "version": "8.5.9", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz", @@ -325,6 +384,12 @@ "npm": ">=7.0.0" } }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, "node_modules/acorn": { "version": "8.11.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", @@ -357,6 +422,31 @@ "node": ">= 14" } }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "optional": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "optional": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -377,7 +467,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -397,6 +487,25 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -418,7 +527,26 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "devOptional": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/basic-ftp": { "version": "5.0.3", @@ -428,16 +556,98 @@ "node": ">=10.0.0" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "devOptional": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -463,6 +673,23 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -481,11 +708,26 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "devOptional": true + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "optional": true }, "node_modules/cross-spawn": { "version": "7.0.3", @@ -525,6 +767,28 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -544,6 +808,20 @@ "node": ">= 14" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "optional": true + }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, "node_modules/discord-api-types": { "version": "0.37.61", "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.61.tgz", @@ -596,6 +874,49 @@ "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, + "node_modules/dottie": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz", + "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "optional": true + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "optional": true + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -792,6 +1113,14 @@ "node": ">=0.10.0" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -830,6 +1159,11 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -866,6 +1200,11 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, "node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -879,11 +1218,41 @@ "node": ">=6 <7 || >=8" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "devOptional": true + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } }, "node_modules/get-uri": { "version": "6.0.2", @@ -899,11 +1268,16 @@ "node": ">= 14" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, + "devOptional": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -966,6 +1340,18 @@ "node": ">=8" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "optional": true + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "optional": true + }, "node_modules/http-proxy-agent": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", @@ -990,6 +1376,46 @@ "node": ">= 14" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", @@ -1019,16 +1445,39 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "optional": true + }, + "node_modules/inflection": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz", + "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==", + "engines": [ + "node >= 0.4.0" + ] + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, + "devOptional": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -1037,8 +1486,12 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/ip": { "version": "1.1.8", @@ -1054,6 +1507,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -1066,6 +1528,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "optional": true + }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -1079,7 +1547,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "devOptional": true }, "node_modules/js-yaml": { "version": "4.1.0", @@ -1185,11 +1653,114 @@ "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.7.0.tgz", "integrity": "sha512-YzVU2+/hrjwx8xcgAw+ffNq3jkactpj+f1iSL4LonrFKhvnwDzHSqtFdk/MMRP53y9ScouJ7cKEnqYsJwsHoYA==" }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen/node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1197,17 +1768,162 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.44", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.44.tgz", + "integrity": "sha512-nv3YpzI/8lkQn0U6RkLd+f0W/zy/JnoR5/EyPz/dNkPTBjA2jNLCVxaiQ8QpeLymhSZvX0wCL5s27NQWdOPwAw==", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/netmask": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", @@ -1216,11 +1932,83 @@ "node": ">= 0.4.0" } }, + "node_modules/node-abi": { + "version": "3.54.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.54.0.tgz", + "integrity": "sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz", + "integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==", + "engines": { + "node": "^16 || ^18 || >= 20" + } + }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -1272,6 +2060,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pac-proxy-agent": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz", @@ -1328,7 +2131,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -1342,6 +2145,36 @@ "node": ">=8" } }, + "node_modules/pg-connection-string": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", + "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==" + }, + "node_modules/prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -1366,6 +2199,25 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/proxy-agent": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.1.tgz", @@ -1389,6 +2241,15 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -1418,6 +2279,41 @@ } ] }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -1427,6 +2323,20 @@ "node": ">=4" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-as-promised": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.4.tgz", + "integrity": "sha512-XgmCoxKWkDofwH8WddD0w85ZfqYz+ZHlr5yo+3YUCfycWawU56T5ckWXsScsj5B8tqUcIG67DxXByo3VUgiAdA==" + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -1441,7 +2351,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, + "devOptional": true, "dependencies": { "glob": "^7.1.3" }, @@ -1475,6 +2385,131 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "optional": true + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sequelize": { + "version": "6.35.2", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.35.2.tgz", + "integrity": "sha512-EdzLaw2kK4/aOnWQ7ed/qh3B6/g+1DvmeXr66RwbcqSm/+QRS9X0LDI5INBibsy4eNJHWIRPo3+QK0zL+IPBHg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/sequelize" + } + ], + "dependencies": { + "@types/debug": "^4.1.8", + "@types/validator": "^13.7.17", + "debug": "^4.3.4", + "dottie": "^2.0.6", + "inflection": "^1.13.4", + "lodash": "^4.17.21", + "moment": "^2.29.4", + "moment-timezone": "^0.5.43", + "pg-connection-string": "^2.6.1", + "retry-as-promised": "^7.0.4", + "semver": "^7.5.4", + "sequelize-pool": "^7.1.0", + "toposort-class": "^1.0.1", + "uuid": "^8.3.2", + "validator": "^13.9.0", + "wkx": "^0.5.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependenciesMeta": { + "ibm_db": { + "optional": true + }, + "mariadb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-hstore": { + "optional": true + }, + "snowflake-sdk": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } + } + }, + "node_modules/sequelize-pool": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz", + "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "optional": true + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -1496,6 +2531,55 @@ "node": ">=8" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "optional": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -1545,11 +2629,68 @@ "node": ">=0.10.0" } }, + "node_modules/sqlite3": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", + "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -1581,12 +2722,72 @@ "node": ">=8" } }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==" + }, "node_modules/ts-mixer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz", @@ -1597,6 +2798,17 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -1637,6 +2849,24 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -1654,11 +2884,32 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "dependencies": { "isexe": "^2.0.0" }, @@ -1669,11 +2920,27 @@ "node": ">= 8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wkx": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", + "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { "version": "8.14.2", @@ -1695,6 +2962,11 @@ } } }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 961231e..e7b8f61 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,9 @@ "dependencies": { "discord.js": "^14.14.1", "dotenv": "^16.3.1", - "proxy-agent": "^6.3.1" + "proxy-agent": "^6.3.1", + "sequelize": "^6.35.2", + "sqlite3": "^5.1.7" }, "devDependencies": { "eslint": "^8.56.0", From c19ef05c8266a4ec6c2eb4161d4e0cb736e6de5e Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 29 Jan 2024 00:31:40 +0100 Subject: [PATCH 011/133] added message to database --- database.js | 4 +++- models/message.js | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 models/message.js diff --git a/database.js b/database.js index 4efe98f..eb1be67 100644 --- a/database.js +++ b/database.js @@ -1,3 +1,4 @@ +import defineMessage from './models/message.js'; import { Sequelize } from 'sequelize'; import { config } from 'dotenv'; config(); @@ -9,7 +10,8 @@ const sequelize = new Sequelize(DB_NAME, DB_USER, DB_PWD, { logging: false, host: DB_HOST, }); +const Message = defineMessage(sequelize); sequelize.sync(); -export { sequelize }; +export { sequelize, Message }; diff --git a/models/message.js b/models/message.js new file mode 100644 index 0000000..928d5e1 --- /dev/null +++ b/models/message.js @@ -0,0 +1,15 @@ +import { DataTypes } from "sequelize"; + +export default function(sequelize) { + return sequelize.define('Message', { + id: { + type: DataTypes.STRING, + primaryKey: true, + }, + roleEmojiPair: { + deferrable: Deferrable.INITIALLY_IMMEDIATE, + model: 'RoleEmojiPair', + key: 'id', + }, + }); +} From 56a2c697dc722b2058b0f42343555a6fd5e638d3 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 29 Jan 2024 00:32:13 +0100 Subject: [PATCH 012/133] added role-emoji-pair to database --- database.js | 4 +++- models/roleEmojiPair.js | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 models/roleEmojiPair.js diff --git a/database.js b/database.js index eb1be67..607751b 100644 --- a/database.js +++ b/database.js @@ -1,3 +1,4 @@ +import defineRoleEmojiPair from './models/roleEmojiPair.js'; import defineMessage from './models/message.js'; import { Sequelize } from 'sequelize'; import { config } from 'dotenv'; @@ -10,8 +11,9 @@ const sequelize = new Sequelize(DB_NAME, DB_USER, DB_PWD, { logging: false, host: DB_HOST, }); +const RoleEmojiPair = defineRoleEmojiPair(sequelize); const Message = defineMessage(sequelize); sequelize.sync(); -export { sequelize, Message }; +export { sequelize, RoleEmojiPair, Message }; diff --git a/models/roleEmojiPair.js b/models/roleEmojiPair.js new file mode 100644 index 0000000..7bfe3f1 --- /dev/null +++ b/models/roleEmojiPair.js @@ -0,0 +1,17 @@ +import { DataTypes } from "sequelize"; + +export default function(sequelize) { + return sequelize.define('RoleEmojiPair', { + id: { + defaultValue: DataTypes.UUIDV4, + type: DataTypes.UUID, + primaryKey: true, + }, + role: { + type: DataTypes.STRING, + }, + emoji: { + type: DataTypes.STRING, + } + }); +} From 1f5f5621c3ff963e6eb7f477bd69b6a127ba0ab0 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 29 Jan 2024 00:32:32 +0100 Subject: [PATCH 013/133] added voice channels to database --- database.js | 4 +++- models/voiceChannel.js | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 models/voiceChannel.js diff --git a/database.js b/database.js index 607751b..ab1b792 100644 --- a/database.js +++ b/database.js @@ -1,4 +1,5 @@ import defineRoleEmojiPair from './models/roleEmojiPair.js'; +import defineVoiceChannel from './models/voiceChannel.js'; import defineMessage from './models/message.js'; import { Sequelize } from 'sequelize'; import { config } from 'dotenv'; @@ -12,8 +13,9 @@ const sequelize = new Sequelize(DB_NAME, DB_USER, DB_PWD, { host: DB_HOST, }); const RoleEmojiPair = defineRoleEmojiPair(sequelize); +const VoiceChannel = defineVoiceChannel(sequelize); const Message = defineMessage(sequelize); sequelize.sync(); -export { sequelize, RoleEmojiPair, Message }; +export { sequelize, RoleEmojiPair, VoiceChannel, Message }; diff --git a/models/voiceChannel.js b/models/voiceChannel.js new file mode 100644 index 0000000..6fb9f41 --- /dev/null +++ b/models/voiceChannel.js @@ -0,0 +1,10 @@ +import { DataTypes } from "sequelize"; + +export default function(sequelize) { + return sequelize.define('VoiceChannel', { + id: { + type: DataTypes.STRING, + primaryKey: true, + }, + }); +} From e41afe0adaeb5915b6fb2cb8f39a892d975c55ef Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 29 Jan 2024 02:10:16 +0100 Subject: [PATCH 014/133] implement self roles command --- commands/admin/selfRoles.js | 9 ------- commands/admin/self_roles.js | 50 ++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 9 deletions(-) delete mode 100644 commands/admin/selfRoles.js create mode 100644 commands/admin/self_roles.js diff --git a/commands/admin/selfRoles.js b/commands/admin/selfRoles.js deleted file mode 100644 index e2367f6..0000000 --- a/commands/admin/selfRoles.js +++ /dev/null @@ -1,9 +0,0 @@ -import { SlashCommandBuilder } from 'discord.js'; - -export const data = new SlashCommandBuilder() - .setName('self_roles') - .setDescription('Manages reactions for self roles.'); -export async function execute(interaction) { - await interaction - .reply('Unimplemented!'); -} diff --git a/commands/admin/self_roles.js b/commands/admin/self_roles.js new file mode 100644 index 0000000..4932bd7 --- /dev/null +++ b/commands/admin/self_roles.js @@ -0,0 +1,50 @@ +import { SlashCommandBuilder } from 'discord.js'; + +export const data = new SlashCommandBuilder() + .setName('self_roles') + .setDescription('Manages reactions for self roles.') + .addSubcommand(subcommand => + subcommand + .setName('create') + .setDescription('Creates new message in channel.') + .addStringOption(option => + option + .setRequired(true) + .setName('text') + .setDescription('The text to be displayed in the message.'))) + .addSubcommand(subcommand => + subcommand + .setName('add') + .setDescription('Adds functionality to an existing message.') + .addStringOption(option => + option + .setName('id') + .setRequired(true) + .setDescription('The ID to reference the message to be used.'))); +export async function execute(interaction) { + const channel = interaction.channel; + let message = null; + + switch (interaction.options.getSubcommand()) { + case 'create': + // Create message with text input + const text = interaction.options.getString('text'); + message = await channel.send(text); + // Reply and delete to acknowledge command + interaction.deferReply(); + interaction.deleteReply(); + break; + case 'add': + // Get message by id + const id = interaction.options.getString('id'); + message = await channel.messages.fetch(id); + // Reply to acknowledge command + await interaction.reply({ + content: 'Successfully fetched message!', + ephemeral: true, + }); + break; + } + + console.debug(`[DEBUG] Message ID: '${message.id}'.`); +} From 7126d3486609d7497f3be5500c46ba2dc651d874 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 29 Jan 2024 02:35:29 +0100 Subject: [PATCH 015/133] prepare role-emoji-pair adding --- commands/admin/self_roles.js | 65 ++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/commands/admin/self_roles.js b/commands/admin/self_roles.js index 4932bd7..49fa8a0 100644 --- a/commands/admin/self_roles.js +++ b/commands/admin/self_roles.js @@ -14,37 +14,80 @@ export const data = new SlashCommandBuilder() .setDescription('The text to be displayed in the message.'))) .addSubcommand(subcommand => subcommand - .setName('add') - .setDescription('Adds functionality to an existing message.') + .setName('register') + .setDescription('Registers an existing message.') .addStringOption(option => option .setName('id') .setRequired(true) - .setDescription('The ID to reference the message to be used.'))); + .setDescription('The ID to reference the message to be used.'))) + .addSubcommand(subcommand => + subcommand + .setName('add') + .setDescription('Add a role-emoji-pair to a message.') + .addStringOption(option => + option + .setName('id') + .setRequired(true) + .setDescription('The ID to reference the message to be used.')) + .addRoleOption(option => + option + .setName('role') + .setRequired(true) + .setDescription('The role be assigned to.')) + .addStringOption(option => + option + .setName('emoji') + .setRequired(true) + .setDescription('The emoji to be reacted with.'))); export async function execute(interaction) { const channel = interaction.channel; - let message = null; + let createNew = false; + let id = interaction.options.getString('id'); switch (interaction.options.getSubcommand()) { case 'create': // Create message with text input const text = interaction.options.getString('text'); - message = await channel.send(text); + id = (await channel.send(text)).id; // Reply and delete to acknowledge command interaction.deferReply(); interaction.deleteReply(); + // Flag to create new database entry + createNew = true; + break; + case 'register': + try { + // Get message by id + await channel.messages.fetch(id); + // Reply successfully to acknowledge command + await interaction.reply({ + content: 'Successfully fetched message!', + ephemeral: true, + }); + // Flag to create new database entry + createNew = true; + } catch (_) { + // Reply failed to acknowledge command + await interaction.reply({ + content: 'Failed to fetch message!', + ephemeral: true, + }); + } break; case 'add': - // Get message by id - const id = interaction.options.getString('id'); - message = await channel.messages.fetch(id); - // Reply to acknowledge command + const role = interaction.options.getRole('role'); + const emoji = interaction.options.getString('emoji'); + + // Reply successfully to acknowledge command await interaction.reply({ - content: 'Successfully fetched message!', + content: 'Added new entry for self roles!', ephemeral: true, }); + console.debug(`[DEBUG] Added new entry to get role with ID '${role.id}' using '${emoji}'.`); break; } - console.debug(`[DEBUG] Message ID: '${message.id}'.`); + if (createNew) + console.debug(`[DEBUG] New self roles on message with ID: '${id}'.`); } From 7468dca986495826c93084a1335bf22c090ce2d2 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 29 Jan 2024 14:58:51 +0100 Subject: [PATCH 016/133] rename model files to fit convention --- models/{message.js => messages.js} | 0 models/{roleEmojiPair.js => roleEmojiPairs.js} | 0 models/{voiceChannel.js => voiceChannels.js} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename models/{message.js => messages.js} (100%) rename models/{roleEmojiPair.js => roleEmojiPairs.js} (100%) rename models/{voiceChannel.js => voiceChannels.js} (100%) diff --git a/models/message.js b/models/messages.js similarity index 100% rename from models/message.js rename to models/messages.js diff --git a/models/roleEmojiPair.js b/models/roleEmojiPairs.js similarity index 100% rename from models/roleEmojiPair.js rename to models/roleEmojiPairs.js diff --git a/models/voiceChannel.js b/models/voiceChannels.js similarity index 100% rename from models/voiceChannel.js rename to models/voiceChannels.js From ae8562c53632f95c90e28a2b23f3548659ef0345 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 29 Jan 2024 14:59:36 +0100 Subject: [PATCH 017/133] rename model code to fit convention --- database.js | 6 +++--- models/messages.js | 7 +------ models/roleEmojiPairs.js | 12 ++++++++++-- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/database.js b/database.js index ab1b792..8c90ba0 100644 --- a/database.js +++ b/database.js @@ -1,6 +1,6 @@ -import defineRoleEmojiPair from './models/roleEmojiPair.js'; -import defineVoiceChannel from './models/voiceChannel.js'; -import defineMessage from './models/message.js'; +import defineRoleEmojiPair from './models/roleEmojiPairs.js'; +import defineVoiceChannel from './models/voiceChannels.js'; +import defineMessage from './models/messages.js'; import { Sequelize } from 'sequelize'; import { config } from 'dotenv'; config(); diff --git a/models/messages.js b/models/messages.js index 928d5e1..45f1340 100644 --- a/models/messages.js +++ b/models/messages.js @@ -1,15 +1,10 @@ import { DataTypes } from "sequelize"; export default function(sequelize) { - return sequelize.define('Message', { + return sequelize.define('Messages', { id: { type: DataTypes.STRING, primaryKey: true, }, - roleEmojiPair: { - deferrable: Deferrable.INITIALLY_IMMEDIATE, - model: 'RoleEmojiPair', - key: 'id', - }, }); } diff --git a/models/roleEmojiPairs.js b/models/roleEmojiPairs.js index 7bfe3f1..7814483 100644 --- a/models/roleEmojiPairs.js +++ b/models/roleEmojiPairs.js @@ -1,12 +1,20 @@ -import { DataTypes } from "sequelize"; +import { DataTypes, Deferrable } from "sequelize"; export default function(sequelize) { - return sequelize.define('RoleEmojiPair', { + return sequelize.define('RoleEmojiPairs', { id: { defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID, primaryKey: true, }, + message: { + type: DataTypes.STRING, + references: { + deferrable: Deferrable.INITIALLY_IMMEDIATE, + model: 'Messages', + key: 'id', + }, + }, role: { type: DataTypes.STRING, }, From 7026ed56a880361f4583dda3bde5304270a31620 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 29 Jan 2024 14:59:52 +0100 Subject: [PATCH 018/133] clean up: using unprotected sqlite database --- .env.example | 3 --- .gitignore | 1 + database.js | 5 ++--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index a1bc242..b36af2c 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,3 @@ TOKEN= CLIENT= -DB_PWD= DB_NAME= -DB_HOST= -DB_USER= diff --git a/.gitignore b/.gitignore index e993747..4d3c332 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules .env.* !.env.example commands/examples +*.sqlite diff --git a/database.js b/database.js index 8c90ba0..0c39ee5 100644 --- a/database.js +++ b/database.js @@ -5,12 +5,11 @@ import { Sequelize } from 'sequelize'; import { config } from 'dotenv'; config(); -const { DB_NAME, DB_USER, DB_PWD, DB_HOST } = process.env; -const sequelize = new Sequelize(DB_NAME, DB_USER, DB_PWD, { +const { DB_NAME } = process.env; +const sequelize = new Sequelize({ storage: `${DB_NAME}.sqlite`, dialect: 'sqlite', logging: false, - host: DB_HOST, }); const RoleEmojiPair = defineRoleEmojiPair(sequelize); const VoiceChannel = defineVoiceChannel(sequelize); From 04b259cf567c39a36eba4916905bda07f0c47c30 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 29 Jan 2024 15:00:30 +0100 Subject: [PATCH 019/133] implement database access for self roles --- commands/admin/self_roles.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/commands/admin/self_roles.js b/commands/admin/self_roles.js index 49fa8a0..a84703d 100644 --- a/commands/admin/self_roles.js +++ b/commands/admin/self_roles.js @@ -1,4 +1,5 @@ import { SlashCommandBuilder } from 'discord.js'; +import { Message, RoleEmojiPair } from './../../database.js'; export const data = new SlashCommandBuilder() .setName('self_roles') @@ -84,10 +85,15 @@ export async function execute(interaction) { content: 'Added new entry for self roles!', ephemeral: true, }); + + RoleEmojiPair.create({ message: id, role: role.id, emoji }); + console.debug(`[DEBUG] Added new entry to get role with ID '${role.id}' using '${emoji}'.`); break; } - if (createNew) + if (createNew) { console.debug(`[DEBUG] New self roles on message with ID: '${id}'.`); + Message.create({ id }); + } } From cd44a4717a7187d116c599bc418206e3b2646a5f Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 4 Feb 2024 23:40:07 +0100 Subject: [PATCH 020/133] clean up: refactor by splitting into functions --- commands/admin/self_roles.js | 151 +++++++++++++++++++++++++---------- 1 file changed, 111 insertions(+), 40 deletions(-) diff --git a/commands/admin/self_roles.js b/commands/admin/self_roles.js index a84703d..85e5fbc 100644 --- a/commands/admin/self_roles.js +++ b/commands/admin/self_roles.js @@ -1,6 +1,107 @@ +import { Op } from 'sequelize'; import { SlashCommandBuilder } from 'discord.js'; import { Message, RoleEmojiPair } from './../../database.js'; +const createSelfRoles = async (interaction) => { + const { options, channel } = interaction; + + // Create message with text input + const text = options.getString('text'); + const id = (await channel.send(text)).id; + + // Reply and delete to acknowledge command + interaction.deferReply(); + interaction.deleteReply(); + + return id; +}; + +const registerSelfRoles = async (interaction) => { + const { options, channel } = interaction; + const id = options.getString('id'); + const response = { + success: false, + msgID: null, + }; + + try { + // Get message by id + await channel.messages.fetch(id); + + // Reply successfully to acknowledge command + await interaction.reply({ + content: 'Successfully fetched message!', + ephemeral: true, + }); + + response.success = true; + response.msgID = id; + + return response; + } catch (_) { + // Reply failed to acknowledge command + await interaction.reply({ + content: 'Failed to fetch message!', + ephemeral: true, + }); + } + return response; +}; + +const addSelfRoles = async (interaction) => { + const { options, channel } = interaction; + const id = options.getString('id'); + + let step = 'fetch'; + try { + // Get message by id + const message = await channel.messages.fetch(id); + + // Get user arguments + const role = options.getRole('role'); + const emoji = options.getString('emoji'); + + step = 'save data from'; + // Try finding existing entry + const rep = await RoleEmojiPair.findOne({ + where: { + [Op.or]: [ + { + message: id, + role: role.id + }, { + message: id, + emoji + } + ] + } + }); + if (rep !== null) throw new Error(); + + // Create database entry for pair + RoleEmojiPair.create({ message: id, role: role.id, emoji }); + + step = 'react to'; + // React with emoji to message + await message.react(emoji); + + // Reply successfully to acknowledge command + await interaction.reply({ + content: 'Added new entry for self roles!', + ephemeral: true, + }); + + console.info(`[INFO] Added new entry to get role with ID '${role.id}' using '${emoji}'.`); + } catch (_) { + // Reply failed to acknowledge command + await interaction.reply({ + content: `Failed to ${step} message!`, + ephemeral: true, + }); + } + +} + export const data = new SlashCommandBuilder() .setName('self_roles') .setDescription('Manages reactions for self roles.') @@ -42,58 +143,28 @@ export const data = new SlashCommandBuilder() .setRequired(true) .setDescription('The emoji to be reacted with.'))); export async function execute(interaction) { - const channel = interaction.channel; + const { options } = interaction; - let createNew = false; - let id = interaction.options.getString('id'); - switch (interaction.options.getSubcommand()) { + let createNew = false, id; + switch (options.getSubcommand()) { case 'create': - // Create message with text input - const text = interaction.options.getString('text'); - id = (await channel.send(text)).id; - // Reply and delete to acknowledge command - interaction.deferReply(); - interaction.deleteReply(); + id = await createSelfRoles(interaction); // Flag to create new database entry createNew = true; break; case 'register': - try { - // Get message by id - await channel.messages.fetch(id); - // Reply successfully to acknowledge command - await interaction.reply({ - content: 'Successfully fetched message!', - ephemeral: true, - }); - // Flag to create new database entry - createNew = true; - } catch (_) { - // Reply failed to acknowledge command - await interaction.reply({ - content: 'Failed to fetch message!', - ephemeral: true, - }); - } + const { success, msgID } = await registerSelfRoles(interaction); + id = msgID ?? id; + // Flag to create new database entry + createNew = success; break; case 'add': - const role = interaction.options.getRole('role'); - const emoji = interaction.options.getString('emoji'); - - // Reply successfully to acknowledge command - await interaction.reply({ - content: 'Added new entry for self roles!', - ephemeral: true, - }); - - RoleEmojiPair.create({ message: id, role: role.id, emoji }); - - console.debug(`[DEBUG] Added new entry to get role with ID '${role.id}' using '${emoji}'.`); + await addSelfRoles(interaction); break; } if (createNew) { - console.debug(`[DEBUG] New self roles on message with ID: '${id}'.`); + console.info(`[INFO] New self roles on message with ID: '${id}'.`); Message.create({ id }); } } From 3d89bd8e6555071d99d7afdc8a534e0861732ee3 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 4 Feb 2024 23:41:04 +0100 Subject: [PATCH 021/133] ignore own reactions --- events/reactionAdd.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/events/reactionAdd.js b/events/reactionAdd.js index 35fc162..c0b8de3 100644 --- a/events/reactionAdd.js +++ b/events/reactionAdd.js @@ -1,7 +1,10 @@ import { Events } from 'discord.js'; +import { config } from 'dotenv'; +config(); export const name = Events.MessageReactionAdd; export async function execute(reaction, user) { + if (user.id === process.env.CLIENT) return; const uname = user.username; const rname = reaction._emoji.name; console.debug(`[DEBUG] User '${uname}' reacted with emoji '${rname}'.`); From caa23d6cb2d6295dce21fa92842fab8c53dec7d3 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 4 Feb 2024 23:52:08 +0100 Subject: [PATCH 022/133] generalise command execution for interaction create --- events/interactionCreate.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/events/interactionCreate.js b/events/interactionCreate.js index 86b465b..4afa97a 100644 --- a/events/interactionCreate.js +++ b/events/interactionCreate.js @@ -1,6 +1,6 @@ import { Events } from 'discord.js'; -const chatInputCommand = async (interaction, command) => { +const executeCommand = async (interaction, command) => { // Try executing command try { console.info(`[INFO] Command ${interaction.commandName} was executed.`); @@ -34,9 +34,9 @@ export const name = Events.InteractionCreate; export async function execute(interaction) { let command = interaction.client.commands.get(interaction.commandName); - // Execute slash commands - if (interaction.isChatInputCommand()) { - await chatInputCommand(interaction, command); + // Execute slash- and context-menu-commands + if (interaction.isChatInputCommand() || interaction.isMessageContextMenuCommand()) { + await executeCommand(interaction, command); return; } // Autocomplete input @@ -44,7 +44,7 @@ export async function execute(interaction) { await genericExecute(interaction, command, 'autocomplete'); return; } - // Modeal submit event + // Modal submit event if (interaction.isModalSubmit()) { const name = interaction.customId.split('-')[0]; command = interaction.client.commands.get(name); From 7cd7d68d3b04bc839bd6c00d778611f30f87fb33 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 4 Feb 2024 23:52:36 +0100 Subject: [PATCH 023/133] clean up: delete empty line --- commands/admin/self_roles.js | 1 - 1 file changed, 1 deletion(-) diff --git a/commands/admin/self_roles.js b/commands/admin/self_roles.js index 85e5fbc..73dd62c 100644 --- a/commands/admin/self_roles.js +++ b/commands/admin/self_roles.js @@ -99,7 +99,6 @@ const addSelfRoles = async (interaction) => { ephemeral: true, }); } - } export const data = new SlashCommandBuilder() From 124e5afe5a049664618fa159f7113c12267424aa Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 00:16:06 +0100 Subject: [PATCH 024/133] refactor: renamed to differentiate command types --- .../{self_roles.js => slash_self_roles.js} | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) rename commands/admin/{self_roles.js => slash_self_roles.js} (92%) diff --git a/commands/admin/self_roles.js b/commands/admin/slash_self_roles.js similarity index 92% rename from commands/admin/self_roles.js rename to commands/admin/slash_self_roles.js index 73dd62c..1494912 100644 --- a/commands/admin/self_roles.js +++ b/commands/admin/slash_self_roles.js @@ -38,7 +38,9 @@ const registerSelfRoles = async (interaction) => { response.msgID = id; return response; - } catch (_) { + } catch (error) { + console.error(error); + // Reply failed to acknowledge command await interaction.reply({ content: 'Failed to fetch message!', @@ -92,7 +94,9 @@ const addSelfRoles = async (interaction) => { }); console.info(`[INFO] Added new entry to get role with ID '${role.id}' using '${emoji}'.`); - } catch (_) { + } catch (error) { + console.error(error); + // Reply failed to acknowledge command await interaction.reply({ content: `Failed to ${step} message!`, @@ -163,7 +167,19 @@ export async function execute(interaction) { } if (createNew) { + try { + // Create database entry + Message.create({ id }); + } catch (error) { + console.error(error); + + // Reply failed to acknowledge command + await interaction.followUp({ + content: 'Failed to save data from message!', + ephemeral: true, + }); + } + console.info(`[INFO] New self roles on message with ID: '${id}'.`); - Message.create({ id }); } } From d7409ed0f72db39c077ff059a757e3425362cf51 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 00:16:52 +0100 Subject: [PATCH 025/133] implemented context menu command for registering self roles --- commands/admin/ctx_self_roles.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 commands/admin/ctx_self_roles.js diff --git a/commands/admin/ctx_self_roles.js b/commands/admin/ctx_self_roles.js new file mode 100644 index 0000000..f24f160 --- /dev/null +++ b/commands/admin/ctx_self_roles.js @@ -0,0 +1,29 @@ +import { Message } from '../../database.js'; +import { ApplicationCommandType, ContextMenuCommandBuilder } from 'discord.js'; + +export const data = new ContextMenuCommandBuilder() + .setName('Register self roles') + .setType(ApplicationCommandType.Message); +export async function execute(interaction) { + const id = interaction.targetMessage.id; + + try { + // Create database entry + Message.create({ id }); + + // Reply successfully to acknowledge command + await interaction.reply({ + content: 'Successfully saved data from message!', + ephemeral: true, + }); + + console.info(`[INFO] New self roles on message with ID: '${id}'.`); + } catch (error) { + console.error(error); + // Reply failed to acknowledge command + await interaction.reply({ + content: 'Failed to save data from message!', + ephemeral: true, + }); + } +} From afa958b5571407ae2b8cf89594f14de1e3009f6d Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 02:56:18 +0100 Subject: [PATCH 026/133] implement reaction add/remove events --- events/reactionAdd.js | 40 ++++++++++++++++++++++++++++++++++++---- events/reactionRemove.js | 37 ++++++++++++++++++++++++++++++++++--- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/events/reactionAdd.js b/events/reactionAdd.js index c0b8de3..6207c40 100644 --- a/events/reactionAdd.js +++ b/events/reactionAdd.js @@ -1,11 +1,43 @@ -import { Events } from 'discord.js'; import { config } from 'dotenv'; +import { Events } from 'discord.js'; +import { Message, RoleEmojiPair } from '../database.js'; config(); export const name = Events.MessageReactionAdd; export async function execute(reaction, user) { if (user.id === process.env.CLIENT) return; - const uname = user.username; - const rname = reaction._emoji.name; - console.debug(`[DEBUG] User '${uname}' reacted with emoji '${rname}'.`); + + // Get message + const msgID = reaction.message.id; + const message = await Message.findOne({ + where: { + id: msgID, + } + }); + // Ignore if unregistered + if (message === null) return; + + // Get emoji + const emoji = reaction.emoji.toString(); + const rep = await RoleEmojiPair.findOne({ + where: { + message: msgID, + emoji, + } + }); + // Deny if unregistered + if (rep === null) { + // Remove reaction and quit + await reaction.remove(); + return; + } + + // Fetch role from guild + const guild = reaction.message.guild; + const role = await guild.roles.fetch(rep.role); + if (role === null) return; + + // Add role to user + await guild.members.addRole({ role, user }); + console.info(`[INFO] Added role with id '${role.id}' to user '${user.username}'.`); } diff --git a/events/reactionRemove.js b/events/reactionRemove.js index 58d4c52..2888bf9 100644 --- a/events/reactionRemove.js +++ b/events/reactionRemove.js @@ -1,8 +1,39 @@ +import { config } from 'dotenv'; import { Events } from 'discord.js'; +import { Message, RoleEmojiPair } from '../database.js'; +config(); export const name = Events.MessageReactionRemove; export async function execute(reaction, user) { - const uname = user.username; - const rname = reaction._emoji.name; - console.debug(`[DEBUG] User '${uname}' removed reaction of emoji '${rname}'.`); + if (user.id === process.env.CLIENT) return; + + // Get message + const msgID = reaction.message.id; + const message = await Message.findOne({ + where: { + id: msgID, + } + }); + // Ignore if unregistered + if (message === null) return; + + // Get emoji + const emoji = reaction.emoji.toString(); + const rep = await RoleEmojiPair.findOne({ + where: { + message: msgID, + emoji, + } + }); + // Deny if unregistered + if (rep === null) return; + + // Fetch role from guild + const guild = reaction.message.guild; + const role = await guild.roles.fetch(rep.role); + if (role === null) return; + + // Add role to user + await guild.members.removeRole({ role, user }); + console.info(`[INFO] Removed role with id '${role.id}' from user '${user.username}'.`); } From 1e716d5f5e7a4f994d4bdf015e849645da99d721 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 02:58:08 +0100 Subject: [PATCH 027/133] replace emoji name in identifier --- commands/admin/slash_self_roles.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/commands/admin/slash_self_roles.js b/commands/admin/slash_self_roles.js index 1494912..243ec79 100644 --- a/commands/admin/slash_self_roles.js +++ b/commands/admin/slash_self_roles.js @@ -61,7 +61,9 @@ const addSelfRoles = async (interaction) => { // Get user arguments const role = options.getRole('role'); - const emoji = options.getString('emoji'); + const emoji = options + .getString('emoji') + .replace(/:.*?:/, ':_:'); step = 'save data from'; // Try finding existing entry From 3fa0bd9c1fc6a80f93b72a7477d9b688b92757a6 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 03:01:48 +0100 Subject: [PATCH 028/133] refactor: await all asynchronous function calls --- commands/admin/ctx_self_roles.js | 2 +- commands/admin/slash_self_roles.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/commands/admin/ctx_self_roles.js b/commands/admin/ctx_self_roles.js index f24f160..04a8529 100644 --- a/commands/admin/ctx_self_roles.js +++ b/commands/admin/ctx_self_roles.js @@ -9,7 +9,7 @@ export async function execute(interaction) { try { // Create database entry - Message.create({ id }); + await Message.create({ id }); // Reply successfully to acknowledge command await interaction.reply({ diff --git a/commands/admin/slash_self_roles.js b/commands/admin/slash_self_roles.js index 243ec79..c48cf13 100644 --- a/commands/admin/slash_self_roles.js +++ b/commands/admin/slash_self_roles.js @@ -10,8 +10,8 @@ const createSelfRoles = async (interaction) => { const id = (await channel.send(text)).id; // Reply and delete to acknowledge command - interaction.deferReply(); - interaction.deleteReply(); + await interaction.deferReply(); + await interaction.deleteReply(); return id; }; @@ -83,7 +83,7 @@ const addSelfRoles = async (interaction) => { if (rep !== null) throw new Error(); // Create database entry for pair - RoleEmojiPair.create({ message: id, role: role.id, emoji }); + await RoleEmojiPair.create({ message: id, role: role.id, emoji }); step = 'react to'; // React with emoji to message @@ -116,8 +116,8 @@ export const data = new SlashCommandBuilder() .setDescription('Creates new message in channel.') .addStringOption(option => option - .setRequired(true) .setName('text') + .setRequired(true) .setDescription('The text to be displayed in the message.'))) .addSubcommand(subcommand => subcommand @@ -171,7 +171,7 @@ export async function execute(interaction) { if (createNew) { try { // Create database entry - Message.create({ id }); + await Message.create({ id }); } catch (error) { console.error(error); From 42293efb6833537098c9c3a69b03f3c96d965d86 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 03:06:53 +0100 Subject: [PATCH 029/133] refactor: more verbose output --- commands/admin/ctx_self_roles.js | 2 +- commands/admin/slash_self_roles.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/commands/admin/ctx_self_roles.js b/commands/admin/ctx_self_roles.js index 04a8529..0777a5e 100644 --- a/commands/admin/ctx_self_roles.js +++ b/commands/admin/ctx_self_roles.js @@ -13,7 +13,7 @@ export async function execute(interaction) { // Reply successfully to acknowledge command await interaction.reply({ - content: 'Successfully saved data from message!', + content: `Successfully saved data from message! Add roles to it with reference ID '${id}'.`, ephemeral: true, }); diff --git a/commands/admin/slash_self_roles.js b/commands/admin/slash_self_roles.js index c48cf13..c5c746b 100644 --- a/commands/admin/slash_self_roles.js +++ b/commands/admin/slash_self_roles.js @@ -80,7 +80,7 @@ const addSelfRoles = async (interaction) => { ] } }); - if (rep !== null) throw new Error(); + if (rep !== null) throw new Error(`Failed to fetch RoleEmojiPair entry with data {message:${id},role:${role.id},emoji:${emoji}}!`); // Create database entry for pair await RoleEmojiPair.create({ message: id, role: role.id, emoji }); From 531b07e30443c969e2d8ce65957a339647d96dd5 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 03:10:21 +0100 Subject: [PATCH 030/133] clean up: refactor by splitting into functions --- commands/admin/slash_self_roles.js | 40 ++++++++++++++++-------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/commands/admin/slash_self_roles.js b/commands/admin/slash_self_roles.js index c5c746b..1438f40 100644 --- a/commands/admin/slash_self_roles.js +++ b/commands/admin/slash_self_roles.js @@ -50,6 +50,27 @@ const registerSelfRoles = async (interaction) => { return response; }; +const saveMessageData = async (id, role, emoji) => { + // Try finding existing entry + const rep = await RoleEmojiPair.findOne({ + where: { + [Op.or]: [ + { + message: id, + role: role.id + }, { + message: id, + emoji + } + ] + } + }); + if (rep !== null) throw new Error(`Failed to fetch RoleEmojiPair entry with data {message:${id},role:${role.id},emoji:${emoji}}!`); + + // Create database entry for pair + await RoleEmojiPair.create({ message: id, role: role.id, emoji }); +}; + const addSelfRoles = async (interaction) => { const { options, channel } = interaction; const id = options.getString('id'); @@ -66,24 +87,7 @@ const addSelfRoles = async (interaction) => { .replace(/:.*?:/, ':_:'); step = 'save data from'; - // Try finding existing entry - const rep = await RoleEmojiPair.findOne({ - where: { - [Op.or]: [ - { - message: id, - role: role.id - }, { - message: id, - emoji - } - ] - } - }); - if (rep !== null) throw new Error(`Failed to fetch RoleEmojiPair entry with data {message:${id},role:${role.id},emoji:${emoji}}!`); - - // Create database entry for pair - await RoleEmojiPair.create({ message: id, role: role.id, emoji }); + await saveMessageData(id, role, emoji); step = 'react to'; // React with emoji to message From ddcec60be671b51853e9bb4d3b1c1f1a84599b86 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 03:19:30 +0100 Subject: [PATCH 031/133] update bot invite in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 87445d5..7cb1b45 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ ## Bot invite -link [here](https://discord.com/api/oauth2/authorize?client_id=698979939847766066&permissions=8&scope=applications.commands+bot). +link [here](https://discord.com/api/oauth2/authorize?client_id=1203887026282438686&permissions=8&scope=applications.commands+bot). From 474a35a736002ded424d89e0022972f1ec92f75b Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 21:11:59 +0100 Subject: [PATCH 032/133] error handling on reaction roles --- events/reactionAdd.js | 12 +++++++++--- events/reactionRemove.js | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/events/reactionAdd.js b/events/reactionAdd.js index 6207c40..536b61d 100644 --- a/events/reactionAdd.js +++ b/events/reactionAdd.js @@ -37,7 +37,13 @@ export async function execute(reaction, user) { const role = await guild.roles.fetch(rep.role); if (role === null) return; - // Add role to user - await guild.members.addRole({ role, user }); - console.info(`[INFO] Added role with id '${role.id}' to user '${user.username}'.`); + try { + // Add role to user + await guild.members.addRole({ role, user }); + console.info(`[INFO] Added role with id '${role.id}' to user '${user.username}'.`); + } catch (error) { + // Missing permissions + console.error(error); + await user.send('Unable to assign role. Please contact server staff.'); + } } diff --git a/events/reactionRemove.js b/events/reactionRemove.js index 2888bf9..0cc52f1 100644 --- a/events/reactionRemove.js +++ b/events/reactionRemove.js @@ -33,7 +33,13 @@ export async function execute(reaction, user) { const role = await guild.roles.fetch(rep.role); if (role === null) return; - // Add role to user - await guild.members.removeRole({ role, user }); - console.info(`[INFO] Removed role with id '${role.id}' from user '${user.username}'.`); + try { + // Remove role from user + await guild.members.removeRole({ role, user }); + console.info(`[INFO] Removed role with id '${role.id}' from user '${user.username}'.`); + } catch (error) { + // Missing permissions + console.error(error); + await user.send('Unable to retract role. Please contact server staff.'); + } } From c66cff1d218a30cff8baf01bc9eed84a64fd96cb Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 21:19:47 +0100 Subject: [PATCH 033/133] reaction event error feedback --- events/reactionAdd.js | 6 +++++- events/reactionRemove.js | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/events/reactionAdd.js b/events/reactionAdd.js index 536b61d..29cce2a 100644 --- a/events/reactionAdd.js +++ b/events/reactionAdd.js @@ -35,7 +35,11 @@ export async function execute(reaction, user) { // Fetch role from guild const guild = reaction.message.guild; const role = await guild.roles.fetch(rep.role); - if (role === null) return; + // Role not found + if (role === null) { + await user.send('Could not fetch role! Please contact server staff.'); + return; + } try { // Add role to user diff --git a/events/reactionRemove.js b/events/reactionRemove.js index 0cc52f1..f77ef5a 100644 --- a/events/reactionRemove.js +++ b/events/reactionRemove.js @@ -31,7 +31,11 @@ export async function execute(reaction, user) { // Fetch role from guild const guild = reaction.message.guild; const role = await guild.roles.fetch(rep.role); - if (role === null) return; + // Role not found + if (role === null) { + await user.send('Could not fetch role! Please contact server staff.'); + return; + } try { // Remove role from user From d272bdb15e0556d283589f44c8daec9a32a610b8 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 21:24:55 +0100 Subject: [PATCH 034/133] remove example commands --- commands/utility/login.js | 41 --------------------------- commands/utility/roleSelector.js | 48 -------------------------------- 2 files changed, 89 deletions(-) delete mode 100644 commands/utility/login.js delete mode 100644 commands/utility/roleSelector.js diff --git a/commands/utility/login.js b/commands/utility/login.js deleted file mode 100644 index d9a73fb..0000000 --- a/commands/utility/login.js +++ /dev/null @@ -1,41 +0,0 @@ -import { - ActionRowBuilder, - ModalBuilder, - SlashCommandBuilder, - TextInputBuilder, - TextInputStyle -} from 'discord.js'; - -export const data = new SlashCommandBuilder() - .setName('login') - .setDescription('Opens a login pop-up.'); -export async function modalSubmit(interaction) { - await interaction.reply({ - content: 'Successfully submitted Form!', - ephemeral: true - }); -} -export async function execute(interaction) { - const modal = new ModalBuilder() - .setCustomId('login-modal') - .setTitle('Login Form'); - - const user = new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId('user') - .setLabel('Enter username:') - .setStyle(TextInputStyle.Short) - .setRequired(true) - ); - const password = new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId('password') - .setLabel('Enter password:') - .setStyle(TextInputStyle.Short) - .setRequired(true) - ); - - modal.addComponents(user, password); - - await interaction.showModal(modal); -} diff --git a/commands/utility/roleSelector.js b/commands/utility/roleSelector.js deleted file mode 100644 index 2812bcf..0000000 --- a/commands/utility/roleSelector.js +++ /dev/null @@ -1,48 +0,0 @@ -import { - ActionRowBuilder, - ComponentType, - RoleSelectMenuBuilder, - SlashCommandBuilder -} from 'discord.js'; - -export const data = new SlashCommandBuilder() - .setName('role_selector') - .setDMPermission(false) - .setDescription('Provides a role selector.'); -export async function execute(interaction) { - const roles = await interaction.guild.roles.fetch(); - const choices = roles - .filter((r) => r.name.startsWith('test')) - .map((r) => r.id); - - const button = new RoleSelectMenuBuilder() - .setMinValues(1) - .setMaxValues(25) - .setCustomId('role') - .setDefaultRoles(choices) - .setPlaceholder('Select at least one role.'); - - const row = new ActionRowBuilder() - .addComponents(button); - - const response = await interaction.reply({ - components: [row], - ephemeral: true - }); - - const collector = response.createMessageComponentCollector({ - componentType: ComponentType.RoleSelect, - time: 120_000 - }); - - collector.on('collect', async (i) => { - const selection = roles - .filter((r) => i.values.includes(r.id)) - .map((r) => r.name) - .join(', '); - await i.reply({ - content: `You have selected: "${selection}".`, - ephemeral: true - }); - }); -} From 70d183e1ef4bcd7a71b7c786155d4097f851a98e Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 21:26:17 +0100 Subject: [PATCH 035/133] refactor: rename to prepare additional context commands --- commands/admin/{ctx_self_roles.js => ctx_add_self_roles.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename commands/admin/{ctx_self_roles.js => ctx_add_self_roles.js} (100%) diff --git a/commands/admin/ctx_self_roles.js b/commands/admin/ctx_add_self_roles.js similarity index 100% rename from commands/admin/ctx_self_roles.js rename to commands/admin/ctx_add_self_roles.js From 7fa972b4112b47f0ac918a0fd46257fe7f424034 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 21:26:47 +0100 Subject: [PATCH 036/133] deny command access in DMs --- commands/admin/ctx_add_self_roles.js | 1 + commands/admin/slash_self_roles.js | 1 + 2 files changed, 2 insertions(+) diff --git a/commands/admin/ctx_add_self_roles.js b/commands/admin/ctx_add_self_roles.js index 0777a5e..041ebc1 100644 --- a/commands/admin/ctx_add_self_roles.js +++ b/commands/admin/ctx_add_self_roles.js @@ -2,6 +2,7 @@ import { Message } from '../../database.js'; import { ApplicationCommandType, ContextMenuCommandBuilder } from 'discord.js'; export const data = new ContextMenuCommandBuilder() + .setDMPermission(false) .setName('Register self roles') .setType(ApplicationCommandType.Message); export async function execute(interaction) { diff --git a/commands/admin/slash_self_roles.js b/commands/admin/slash_self_roles.js index 1438f40..8a28402 100644 --- a/commands/admin/slash_self_roles.js +++ b/commands/admin/slash_self_roles.js @@ -113,6 +113,7 @@ const addSelfRoles = async (interaction) => { export const data = new SlashCommandBuilder() .setName('self_roles') + .setDMPermission(false) .setDescription('Manages reactions for self roles.') .addSubcommand(subcommand => subcommand From cb56dded3b6323bf372a168f79802fc7022967ec Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 21:27:04 +0100 Subject: [PATCH 037/133] initialize context add pair command --- commands/admin/ctx_register_self_roles.js | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 commands/admin/ctx_register_self_roles.js diff --git a/commands/admin/ctx_register_self_roles.js b/commands/admin/ctx_register_self_roles.js new file mode 100644 index 0000000..041ebc1 --- /dev/null +++ b/commands/admin/ctx_register_self_roles.js @@ -0,0 +1,30 @@ +import { Message } from '../../database.js'; +import { ApplicationCommandType, ContextMenuCommandBuilder } from 'discord.js'; + +export const data = new ContextMenuCommandBuilder() + .setDMPermission(false) + .setName('Register self roles') + .setType(ApplicationCommandType.Message); +export async function execute(interaction) { + const id = interaction.targetMessage.id; + + try { + // Create database entry + await Message.create({ id }); + + // Reply successfully to acknowledge command + await interaction.reply({ + content: `Successfully saved data from message! Add roles to it with reference ID '${id}'.`, + ephemeral: true, + }); + + console.info(`[INFO] New self roles on message with ID: '${id}'.`); + } catch (error) { + console.error(error); + // Reply failed to acknowledge command + await interaction.reply({ + content: 'Failed to save data from message!', + ephemeral: true, + }); + } +} From 4bdd04a881e75cb59896c27723f57a146497f55a Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 22:56:22 +0100 Subject: [PATCH 038/133] fix generic command name getter --- events/interactionCreate.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/events/interactionCreate.js b/events/interactionCreate.js index 4afa97a..991a368 100644 --- a/events/interactionCreate.js +++ b/events/interactionCreate.js @@ -21,9 +21,9 @@ const executeCommand = async (interaction, command) => { } }; -const genericExecute = async (interaction, command, name, description) => { +const genericExecute = async (interaction, command, name, description, cmdName) => { try { - console.info(`[INFO] Command ${interaction.commandName} ${description ?? `used "${name}"`}.`); + console.info(`[INFO] Command ${(cmdName ?? interaction.commandName) ?? 'anonymous'} ${description ?? `used "${name}"`}.`); await command[name](interaction); } catch (error) { console.error(error); @@ -49,7 +49,7 @@ export async function execute(interaction) { const name = interaction.customId.split('-')[0]; command = interaction.client.commands.get(name); - await genericExecute(interaction, command, 'modalSubmit', 'submitted a modal'); + await genericExecute(interaction, command, 'modalSubmit', 'submitted a modal', name); return; } From 095ad012ffa235a0e5a3a8fec99e985575f90535 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 22:57:08 +0100 Subject: [PATCH 039/133] use modal to add self role pair --- commands/admin/ctx_add_self_roles.js | 104 +++++++++++++++++++++++---- 1 file changed, 91 insertions(+), 13 deletions(-) diff --git a/commands/admin/ctx_add_self_roles.js b/commands/admin/ctx_add_self_roles.js index 041ebc1..95d33cc 100644 --- a/commands/admin/ctx_add_self_roles.js +++ b/commands/admin/ctx_add_self_roles.js @@ -1,30 +1,108 @@ -import { Message } from '../../database.js'; -import { ApplicationCommandType, ContextMenuCommandBuilder } from 'discord.js'; +import { TextInputBuilder, TextInputStyle } from 'discord.js'; +import { RoleEmojiPair } from '../../database.js'; +import { + ComponentType, + ActionRowBuilder, + RoleSelectMenuBuilder, + ApplicationCommandType, + ContextMenuCommandBuilder +} from 'discord.js'; +import { ModalBuilder } from 'discord.js'; -export const data = new ContextMenuCommandBuilder() - .setDMPermission(false) - .setName('Register self roles') - .setType(ApplicationCommandType.Message); -export async function execute(interaction) { - const id = interaction.targetMessage.id; +const saveMessageData = async (id, role, emoji) => { + // Try finding existing entry + const rep = await RoleEmojiPair.findOne({ + where: { + [Op.or]: [ + { + message: id, + role: role.id + }, { + message: id, + emoji + } + ] + } + }); + if (rep !== null) throw new Error(`Failed to fetch RoleEmojiPair entry with data {message:${id},role:${role.id},emoji:${emoji}}!`); + // Create database entry for pair + await RoleEmojiPair.create({ message: id, role: role.id, emoji }); +}; + +const addSelfRoles = async (interaction, msgID, emoji, role) => { + const { channel } = interaction; + + let step = 'fetch'; try { - // Create database entry - await Message.create({ id }); + // Get message by id + const message = await channel.messages.fetch(msgID); + + step = 'save data from'; + await saveMessageData(id, role, emoji); + + step = 'react to'; + // React with emoji to message + await message.react(emoji); // Reply successfully to acknowledge command await interaction.reply({ - content: `Successfully saved data from message! Add roles to it with reference ID '${id}'.`, + content: 'Added new entry for self roles!', ephemeral: true, }); - console.info(`[INFO] New self roles on message with ID: '${id}'.`); + console.info(`[INFO] Added new entry to get role with ID '${role.id}' using '${emoji}'.`); } catch (error) { console.error(error); + // Reply failed to acknowledge command await interaction.reply({ - content: 'Failed to save data from message!', + content: `Failed to ${step} message!`, ephemeral: true, }); } } + +export const data = new ContextMenuCommandBuilder() + .setDMPermission(false) + .setName('Add role emoji pair') + .setType(ApplicationCommandType.Message); +export async function modalSubmit(interaction) { + console.log(interaction); + + // await interaction.reply({ + // content: 'Successfully submitted Form!', + // ephemeral: true + // }); +} +export async function execute(interaction) { + const modal = new ModalBuilder() + .setCustomId('Add role emoji pair-pair') + .setTitle('Role Emoji Pair'); + + const roleSelector = new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setLabel('Enter exactly one role ID.') + .setStyle(TextInputStyle.Short) + .setCustomId('role') + .setRequired(true)); + + // const roles = await interaction.guild.roles.fetch(); + // const selection = roles.find((r) => + // roleInteraction.values.includes(r.id) + // ); + + const emojiTextInput = new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setLabel('Enter exactly one emoji.') + .setStyle(TextInputStyle.Short) + .setCustomId('emoji') + .setRequired(true)); + + // const id = interaction.targetMessage.id; + // await addSelfRoles(interaction, id, emoji, role); + + modal.addComponents(roleSelector, emojiTextInput); + + await interaction.showModal(modal); +} From 86091f5eee618dfac2303db1a1925e31684a4fca Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 23:07:19 +0100 Subject: [PATCH 040/133] implement modal submit interaction logic --- commands/admin/ctx_add_self_roles.js | 47 +++++++++++++++++----------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/commands/admin/ctx_add_self_roles.js b/commands/admin/ctx_add_self_roles.js index 95d33cc..95171b4 100644 --- a/commands/admin/ctx_add_self_roles.js +++ b/commands/admin/ctx_add_self_roles.js @@ -1,9 +1,7 @@ import { TextInputBuilder, TextInputStyle } from 'discord.js'; import { RoleEmojiPair } from '../../database.js'; import { - ComponentType, ActionRowBuilder, - RoleSelectMenuBuilder, ApplicationCommandType, ContextMenuCommandBuilder } from 'discord.js'; @@ -68,41 +66,54 @@ export const data = new ContextMenuCommandBuilder() .setName('Add role emoji pair') .setType(ApplicationCommandType.Message); export async function modalSubmit(interaction) { - console.log(interaction); + const { fields, guild } = interaction; + // Get text inputs from modal + const message = fields.getTextInputValue('message'); + const roleID = fields.getTextInputValue('role'); + const emoji = fields.getTextInputValue('emoji'); - // await interaction.reply({ - // content: 'Successfully submitted Form!', - // ephemeral: true - // }); + // Fetch role from guild + const role = await guild.roles.fetch(roleID); + // Role not found + if (role === null) { + await interaction.reply({ + content: 'Could not fetch role! Please contact server staff.', + ephemeral: true, + }); + return; + } + + await addSelfRoles(interaction, message, emoji, role); } export async function execute(interaction) { const modal = new ModalBuilder() .setCustomId('Add role emoji pair-pair') .setTitle('Role Emoji Pair'); - const roleSelector = new ActionRowBuilder().addComponents( + const id = interaction.targetMessage.id; + const message = new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setLabel('The message ID this command is run on.') + .setStyle(TextInputStyle.Short) + .setCustomId('message') + .setRequired(true) + .setValue(id)); + + const role = new ActionRowBuilder().addComponents( new TextInputBuilder() .setLabel('Enter exactly one role ID.') .setStyle(TextInputStyle.Short) .setCustomId('role') .setRequired(true)); - // const roles = await interaction.guild.roles.fetch(); - // const selection = roles.find((r) => - // roleInteraction.values.includes(r.id) - // ); - - const emojiTextInput = new ActionRowBuilder().addComponents( + const emoji = new ActionRowBuilder().addComponents( new TextInputBuilder() .setLabel('Enter exactly one emoji.') .setStyle(TextInputStyle.Short) .setCustomId('emoji') .setRequired(true)); - // const id = interaction.targetMessage.id; - // await addSelfRoles(interaction, id, emoji, role); - - modal.addComponents(roleSelector, emojiTextInput); + modal.addComponents(message, role, emoji); await interaction.showModal(modal); } From 61b8a9a2ecded1ef705c8f9bf795d69e6b435611 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 23:26:49 +0100 Subject: [PATCH 041/133] shared add self role pair function --- commands/admin/ctx_add_self_roles.js | 60 ++-------------------- commands/admin/slash_self_roles.js | 75 ++++------------------------ shared.js | 56 +++++++++++++++++++++ 3 files changed, 69 insertions(+), 122 deletions(-) create mode 100644 shared.js diff --git a/commands/admin/ctx_add_self_roles.js b/commands/admin/ctx_add_self_roles.js index 95171b4..5b6c512 100644 --- a/commands/admin/ctx_add_self_roles.js +++ b/commands/admin/ctx_add_self_roles.js @@ -1,65 +1,11 @@ import { TextInputBuilder, TextInputStyle } from 'discord.js'; -import { RoleEmojiPair } from '../../database.js'; import { + ModalBuilder, ActionRowBuilder, ApplicationCommandType, ContextMenuCommandBuilder } from 'discord.js'; -import { ModalBuilder } from 'discord.js'; - -const saveMessageData = async (id, role, emoji) => { - // Try finding existing entry - const rep = await RoleEmojiPair.findOne({ - where: { - [Op.or]: [ - { - message: id, - role: role.id - }, { - message: id, - emoji - } - ] - } - }); - if (rep !== null) throw new Error(`Failed to fetch RoleEmojiPair entry with data {message:${id},role:${role.id},emoji:${emoji}}!`); - - // Create database entry for pair - await RoleEmojiPair.create({ message: id, role: role.id, emoji }); -}; - -const addSelfRoles = async (interaction, msgID, emoji, role) => { - const { channel } = interaction; - - let step = 'fetch'; - try { - // Get message by id - const message = await channel.messages.fetch(msgID); - - step = 'save data from'; - await saveMessageData(id, role, emoji); - - step = 'react to'; - // React with emoji to message - await message.react(emoji); - - // Reply successfully to acknowledge command - await interaction.reply({ - content: 'Added new entry for self roles!', - ephemeral: true, - }); - - console.info(`[INFO] Added new entry to get role with ID '${role.id}' using '${emoji}'.`); - } catch (error) { - console.error(error); - - // Reply failed to acknowledge command - await interaction.reply({ - content: `Failed to ${step} message!`, - ephemeral: true, - }); - } -} +import { addSelfRoles } from '../../shared.js'; export const data = new ContextMenuCommandBuilder() .setDMPermission(false) @@ -83,7 +29,7 @@ export async function modalSubmit(interaction) { return; } - await addSelfRoles(interaction, message, emoji, role); + await addSelfRoles(interaction, message, role, emoji); } export async function execute(interaction) { const modal = new ModalBuilder() diff --git a/commands/admin/slash_self_roles.js b/commands/admin/slash_self_roles.js index 8a28402..f9b2751 100644 --- a/commands/admin/slash_self_roles.js +++ b/commands/admin/slash_self_roles.js @@ -1,6 +1,7 @@ import { Op } from 'sequelize'; import { SlashCommandBuilder } from 'discord.js'; import { Message, RoleEmojiPair } from './../../database.js'; +import { addSelfRoles } from '../../shared.js'; const createSelfRoles = async (interaction) => { const { options, channel } = interaction; @@ -50,67 +51,6 @@ const registerSelfRoles = async (interaction) => { return response; }; -const saveMessageData = async (id, role, emoji) => { - // Try finding existing entry - const rep = await RoleEmojiPair.findOne({ - where: { - [Op.or]: [ - { - message: id, - role: role.id - }, { - message: id, - emoji - } - ] - } - }); - if (rep !== null) throw new Error(`Failed to fetch RoleEmojiPair entry with data {message:${id},role:${role.id},emoji:${emoji}}!`); - - // Create database entry for pair - await RoleEmojiPair.create({ message: id, role: role.id, emoji }); -}; - -const addSelfRoles = async (interaction) => { - const { options, channel } = interaction; - const id = options.getString('id'); - - let step = 'fetch'; - try { - // Get message by id - const message = await channel.messages.fetch(id); - - // Get user arguments - const role = options.getRole('role'); - const emoji = options - .getString('emoji') - .replace(/:.*?:/, ':_:'); - - step = 'save data from'; - await saveMessageData(id, role, emoji); - - step = 'react to'; - // React with emoji to message - await message.react(emoji); - - // Reply successfully to acknowledge command - await interaction.reply({ - content: 'Added new entry for self roles!', - ephemeral: true, - }); - - console.info(`[INFO] Added new entry to get role with ID '${role.id}' using '${emoji}'.`); - } catch (error) { - console.error(error); - - // Reply failed to acknowledge command - await interaction.reply({ - content: `Failed to ${step} message!`, - ephemeral: true, - }); - } -} - export const data = new SlashCommandBuilder() .setName('self_roles') .setDMPermission(false) @@ -163,13 +103,18 @@ export async function execute(interaction) { createNew = true; break; case 'register': - const { success, msgID } = await registerSelfRoles(interaction); - id = msgID ?? id; + const response = await registerSelfRoles(interaction); + id = response.msgID ?? id; // Flag to create new database entry - createNew = success; + createNew = response.success; break; case 'add': - await addSelfRoles(interaction); + // Get command options + const msgID = options.getString('id'); + const role = options.getRole('role'); + const emoji = options.getString('emoji'); + // Try adding self role pair + await addSelfRoles(interaction, msgID, role, emoji); break; } diff --git a/shared.js b/shared.js new file mode 100644 index 0000000..4a5e067 --- /dev/null +++ b/shared.js @@ -0,0 +1,56 @@ +import { Op } from "sequelize"; +import { RoleEmojiPair } from "./database.js"; + +const saveMessageData = async (id, role, emoji) => { + // Try finding existing entry + const rep = await RoleEmojiPair.findOne({ + where: { + [Op.or]: [ + { + message: id, + role: role.id + }, { + message: id, + emoji + } + ] + } + }); + if (rep !== null) throw new Error(`Failed to fetch RoleEmojiPair entry with data {message:${id},role:${role.id},emoji:${emoji}}!`); + + // Create database entry for pair + await RoleEmojiPair.create({ message: id, role: role.id, emoji }); +}; + +export const addSelfRoles = async (interaction, msgID, role, emoji) => { + const { channel } = interaction; + + let step = 'fetch'; + try { + // Get message by id + const message = await channel.messages.fetch(msgID); + + step = 'save data from'; + await saveMessageData(msgID, role, emoji); + + step = 'react to'; + // React with emoji to message + await message.react(emoji); + + // Reply successfully to acknowledge command + await interaction.reply({ + content: 'Added new entry for self roles!', + ephemeral: true, + }); + + console.info(`[INFO] Added new entry to get role with ID '${role.id}' using '${emoji}'.`); + } catch (error) { + console.error(error); + + // Reply failed to acknowledge command + await interaction.reply({ + content: `Failed to ${step} message!`, + ephemeral: true, + }); + } +} From fdedb50bc5f20e96b99481a7ca9875e079c64228 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Mon, 5 Feb 2024 23:32:59 +0100 Subject: [PATCH 042/133] better error handling --- shared.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/shared.js b/shared.js index 4a5e067..9a8984d 100644 --- a/shared.js +++ b/shared.js @@ -1,7 +1,11 @@ import { Op } from "sequelize"; -import { RoleEmojiPair } from "./database.js"; +import { Message, RoleEmojiPair } from "./database.js"; const saveMessageData = async (id, role, emoji) => { + // Try finding message + const msg = await Message.findOne({ where: { id } }); + if (msg === null) throw new Error(`No message with ID '${id}' could be found!`); + // Try finding existing entry const rep = await RoleEmojiPair.findOne({ where: { @@ -16,7 +20,7 @@ const saveMessageData = async (id, role, emoji) => { ] } }); - if (rep !== null) throw new Error(`Failed to fetch RoleEmojiPair entry with data {message:${id},role:${role.id},emoji:${emoji}}!`); + if (rep !== null) throw new Error(`Existing RoleEmojiPair entry with (partial) data {message:${id},role:${role.id},emoji:${emoji}}!`); // Create database entry for pair await RoleEmojiPair.create({ message: id, role: role.id, emoji }); From 46e938946c70151f484e91444dd9fe4452ff7c57 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Tue, 6 Feb 2024 14:09:04 +0100 Subject: [PATCH 043/133] clean up: fs.promises api and reformat --- index.js | 81 +++++++++++++++++++++++++++----------------------------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/index.js b/index.js index a08a3bf..eb777a6 100644 --- a/index.js +++ b/index.js @@ -1,13 +1,11 @@ import { Client, Collection, GatewayIntentBits } from 'discord.js'; +import { readdir, stat } from 'fs/promises'; +import { Partials } from 'discord.js'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; -import { promisify } from 'util'; import { config } from 'dotenv'; -import { readdir } from 'fs'; -import { Partials } from 'discord.js'; config(); -const readdirAsync = promisify(readdir); const required = ['data', 'execute']; const optional = ['autocomplete', 'modalSubmit']; @@ -39,44 +37,43 @@ const runClient = (commands, events) => { const __dirname = dirname(fileURLToPath(import.meta.url)); const cmdPath = join(__dirname, 'commands'); const evtPath = join(__dirname, 'events'); -readdirAsync(cmdPath) - .then( - async (categories) => - // For each category directory - await Promise.all( - categories.map(async (category) => { - const catPath = join(cmdPath, category); - const content = await readdirAsync(catPath); - const files = content.filter((file) => file.endsWith('.js') && !file.endsWith('.example.js')); - // For each command file - return await Promise.all( - files.map(async (current) => { - const filePath = join(catPath, current); - const command = await import(filePath); - // Warn incomplete commands - if (!required.every((name) => name in command)) { - console.error( - `[ERROR] The command at ${filePath} is missing a required "data" or "execute" property.` - ); - return 0; - } - const properties = optional.filter((name) => !(name in command)); - if (properties.length > 0) - properties.forEach((name) => - console.warn( - `[WARNING] The command at ${filePath} is missing a optional "${name}" property.` - ) - ); - // Add command to collection - return command; - }) - ); - }) - ) - ) - .then((commands) => commands.reduce((a, i) => a.concat(i), []).filter((e) => e !== 0)) - .then(async (commands) => { - const content = await readdirAsync(evtPath); +readdir(cmdPath) + .then(async (categories) => + // For each category directory + await Promise.all( + categories.map(async (category) => { + const catPath = join(cmdPath, category); + const content = await readdir(catPath); + const files = content.filter((file) => file.endsWith('.js') && !file.endsWith('.example.js')); + // For each command file + return await Promise.all( + files.map(async (current) => { + const filePath = join(catPath, current); + const command = await import(filePath); + // Warn incomplete commands + if (!required.every((name) => name in command)) { + console.error( + `[ERROR] The command at ${filePath} is missing a required "data" or "execute" property.` + ); + return 0; + } + const properties = optional.filter((name) => !(name in command)); + if (properties.length > 0) + properties.forEach((name) => + console.warn( + `[WARNING] The command at ${filePath} is missing a optional "${name}" property.` + ) + ); + // Add command to collection + return command; + }) + ); + }) + ) + ).then((commands) => + commands.reduce((a, i) => a.concat(i), []).filter((e) => e !== 0) + ).then(async (commands) => { + const content = await readdir(evtPath); const files = content.filter((file) => file.endsWith('.js')); const events = await Promise.all( files.map(async (current) => { From 4fea72ad88bba03a0e203f9de96c09221ac8eeeb Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Tue, 6 Feb 2024 16:18:06 +0100 Subject: [PATCH 044/133] refactor: autoformat with prettier --- README.md | 2 +- commands/admin/ctx_add_self_roles.js | 11 +- commands/admin/ctx_register_self_roles.js | 4 +- commands/admin/slash_self_roles.js | 60 ++++--- database.js | 40 ++--- deploy.js | 3 +- events/interactionCreate.js | 6 +- events/reactionAdd.js | 4 +- events/reactionRemove.js | 4 +- events/voiceStateUpdate.js | 2 +- index.js | 11 +- models/messages.js | 20 +-- models/roleEmojiPairs.js | 50 +++--- models/voiceChannels.js | 20 +-- shared.js | 202 +++++++++++----------- 15 files changed, 227 insertions(+), 212 deletions(-) diff --git a/README.md b/README.md index 7cb1b45..a37f3da 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ ## Bot invite -link [here](https://discord.com/api/oauth2/authorize?client_id=1203887026282438686&permissions=8&scope=applications.commands+bot). +link [here](https://discord.com/api/oauth2/authorize?client_id=1203887026282438686&permissions=8&scope=applications.commands+bot). diff --git a/commands/admin/ctx_add_self_roles.js b/commands/admin/ctx_add_self_roles.js index 5b6c512..bce7e65 100644 --- a/commands/admin/ctx_add_self_roles.js +++ b/commands/admin/ctx_add_self_roles.js @@ -24,7 +24,7 @@ export async function modalSubmit(interaction) { if (role === null) { await interaction.reply({ content: 'Could not fetch role! Please contact server staff.', - ephemeral: true, + ephemeral: true }); return; } @@ -43,21 +43,24 @@ export async function execute(interaction) { .setStyle(TextInputStyle.Short) .setCustomId('message') .setRequired(true) - .setValue(id)); + .setValue(id) + ); const role = new ActionRowBuilder().addComponents( new TextInputBuilder() .setLabel('Enter exactly one role ID.') .setStyle(TextInputStyle.Short) .setCustomId('role') - .setRequired(true)); + .setRequired(true) + ); const emoji = new ActionRowBuilder().addComponents( new TextInputBuilder() .setLabel('Enter exactly one emoji.') .setStyle(TextInputStyle.Short) .setCustomId('emoji') - .setRequired(true)); + .setRequired(true) + ); modal.addComponents(message, role, emoji); diff --git a/commands/admin/ctx_register_self_roles.js b/commands/admin/ctx_register_self_roles.js index 041ebc1..485216b 100644 --- a/commands/admin/ctx_register_self_roles.js +++ b/commands/admin/ctx_register_self_roles.js @@ -15,7 +15,7 @@ export async function execute(interaction) { // Reply successfully to acknowledge command await interaction.reply({ content: `Successfully saved data from message! Add roles to it with reference ID '${id}'.`, - ephemeral: true, + ephemeral: true }); console.info(`[INFO] New self roles on message with ID: '${id}'.`); @@ -24,7 +24,7 @@ export async function execute(interaction) { // Reply failed to acknowledge command await interaction.reply({ content: 'Failed to save data from message!', - ephemeral: true, + ephemeral: true }); } } diff --git a/commands/admin/slash_self_roles.js b/commands/admin/slash_self_roles.js index f9b2751..1abf70d 100644 --- a/commands/admin/slash_self_roles.js +++ b/commands/admin/slash_self_roles.js @@ -1,6 +1,5 @@ -import { Op } from 'sequelize'; import { SlashCommandBuilder } from 'discord.js'; -import { Message, RoleEmojiPair } from './../../database.js'; +import { Message } from './../../database.js'; import { addSelfRoles } from '../../shared.js'; const createSelfRoles = async (interaction) => { @@ -22,7 +21,7 @@ const registerSelfRoles = async (interaction) => { const id = options.getString('id'); const response = { success: false, - msgID: null, + msgID: null }; try { @@ -32,7 +31,7 @@ const registerSelfRoles = async (interaction) => { // Reply successfully to acknowledge command await interaction.reply({ content: 'Successfully fetched message!', - ephemeral: true, + ephemeral: true }); response.success = true; @@ -45,7 +44,7 @@ const registerSelfRoles = async (interaction) => { // Reply failed to acknowledge command await interaction.reply({ content: 'Failed to fetch message!', - ephemeral: true, + ephemeral: true }); } return response; @@ -55,60 +54,64 @@ export const data = new SlashCommandBuilder() .setName('self_roles') .setDMPermission(false) .setDescription('Manages reactions for self roles.') - .addSubcommand(subcommand => + .addSubcommand((subcommand) => subcommand .setName('create') .setDescription('Creates new message in channel.') - .addStringOption(option => + .addStringOption((option) => option .setName('text') .setRequired(true) - .setDescription('The text to be displayed in the message.'))) - .addSubcommand(subcommand => + .setDescription('The text to be displayed in the message.') + ) + ) + .addSubcommand((subcommand) => subcommand .setName('register') .setDescription('Registers an existing message.') - .addStringOption(option => + .addStringOption((option) => option .setName('id') .setRequired(true) - .setDescription('The ID to reference the message to be used.'))) - .addSubcommand(subcommand => + .setDescription('The ID to reference the message to be used.') + ) + ) + .addSubcommand((subcommand) => subcommand .setName('add') .setDescription('Add a role-emoji-pair to a message.') - .addStringOption(option => + .addStringOption((option) => option .setName('id') .setRequired(true) - .setDescription('The ID to reference the message to be used.')) - .addRoleOption(option => - option - .setName('role') - .setRequired(true) - .setDescription('The role be assigned to.')) - .addStringOption(option => - option - .setName('emoji') - .setRequired(true) - .setDescription('The emoji to be reacted with.'))); + .setDescription('The ID to reference the message to be used.') + ) + .addRoleOption((option) => + option.setName('role').setRequired(true).setDescription('The role be assigned to.') + ) + .addStringOption((option) => + option.setName('emoji').setRequired(true).setDescription('The emoji to be reacted with.') + ) + ); export async function execute(interaction) { const { options } = interaction; - let createNew = false, id; + let createNew = false, + id; switch (options.getSubcommand()) { case 'create': id = await createSelfRoles(interaction); // Flag to create new database entry createNew = true; break; - case 'register': + case 'register': { const response = await registerSelfRoles(interaction); id = response.msgID ?? id; // Flag to create new database entry createNew = response.success; break; - case 'add': + } + case 'add': { // Get command options const msgID = options.getString('id'); const role = options.getRole('role'); @@ -116,6 +119,7 @@ export async function execute(interaction) { // Try adding self role pair await addSelfRoles(interaction, msgID, role, emoji); break; + } } if (createNew) { @@ -128,7 +132,7 @@ export async function execute(interaction) { // Reply failed to acknowledge command await interaction.followUp({ content: 'Failed to save data from message!', - ephemeral: true, + ephemeral: true }); } diff --git a/database.js b/database.js index 0c39ee5..0bcbc0e 100644 --- a/database.js +++ b/database.js @@ -1,20 +1,20 @@ -import defineRoleEmojiPair from './models/roleEmojiPairs.js'; -import defineVoiceChannel from './models/voiceChannels.js'; -import defineMessage from './models/messages.js'; -import { Sequelize } from 'sequelize'; -import { config } from 'dotenv'; -config(); - -const { DB_NAME } = process.env; -const sequelize = new Sequelize({ - storage: `${DB_NAME}.sqlite`, - dialect: 'sqlite', - logging: false, -}); -const RoleEmojiPair = defineRoleEmojiPair(sequelize); -const VoiceChannel = defineVoiceChannel(sequelize); -const Message = defineMessage(sequelize); - -sequelize.sync(); - -export { sequelize, RoleEmojiPair, VoiceChannel, Message }; +import defineRoleEmojiPair from './models/roleEmojiPairs.js'; +import defineVoiceChannel from './models/voiceChannels.js'; +import defineMessage from './models/messages.js'; +import { Sequelize } from 'sequelize'; +import { config } from 'dotenv'; +config(); + +const { DB_NAME } = process.env; +const sequelize = new Sequelize({ + storage: `${DB_NAME}.sqlite`, + dialect: 'sqlite', + logging: false +}); +const RoleEmojiPair = defineRoleEmojiPair(sequelize); +const VoiceChannel = defineVoiceChannel(sequelize); +const Message = defineMessage(sequelize); + +sequelize.sync(); + +export { sequelize, RoleEmojiPair, VoiceChannel, Message }; diff --git a/deploy.js b/deploy.js index 9319cee..458e210 100644 --- a/deploy.js +++ b/deploy.js @@ -34,4 +34,5 @@ getFiles(cmdPath) (await Promise.all(files.map(importAndCheck))) .filter((module) => module !== 0) .map((module) => module.data.toJSON()) - ).then(putCommands); + ) + .then(putCommands); diff --git a/events/interactionCreate.js b/events/interactionCreate.js index 991a368..4771ecb 100644 --- a/events/interactionCreate.js +++ b/events/interactionCreate.js @@ -23,7 +23,11 @@ const executeCommand = async (interaction, command) => { const genericExecute = async (interaction, command, name, description, cmdName) => { try { - console.info(`[INFO] Command ${(cmdName ?? interaction.commandName) ?? 'anonymous'} ${description ?? `used "${name}"`}.`); + console.info( + `[INFO] Command ${cmdName ?? interaction.commandName ?? 'anonymous'} ${ + description ?? `used "${name}"` + }.` + ); await command[name](interaction); } catch (error) { console.error(error); diff --git a/events/reactionAdd.js b/events/reactionAdd.js index 29cce2a..8e6d4e5 100644 --- a/events/reactionAdd.js +++ b/events/reactionAdd.js @@ -11,7 +11,7 @@ export async function execute(reaction, user) { const msgID = reaction.message.id; const message = await Message.findOne({ where: { - id: msgID, + id: msgID } }); // Ignore if unregistered @@ -22,7 +22,7 @@ export async function execute(reaction, user) { const rep = await RoleEmojiPair.findOne({ where: { message: msgID, - emoji, + emoji } }); // Deny if unregistered diff --git a/events/reactionRemove.js b/events/reactionRemove.js index f77ef5a..bd3d56e 100644 --- a/events/reactionRemove.js +++ b/events/reactionRemove.js @@ -11,7 +11,7 @@ export async function execute(reaction, user) { const msgID = reaction.message.id; const message = await Message.findOne({ where: { - id: msgID, + id: msgID } }); // Ignore if unregistered @@ -22,7 +22,7 @@ export async function execute(reaction, user) { const rep = await RoleEmojiPair.findOne({ where: { message: msgID, - emoji, + emoji } }); // Deny if unregistered diff --git a/events/voiceStateUpdate.js b/events/voiceStateUpdate.js index 3473ee0..d698ca5 100644 --- a/events/voiceStateUpdate.js +++ b/events/voiceStateUpdate.js @@ -4,7 +4,7 @@ export const name = Events.VoiceStateUpdate; export async function execute(oldState, newState) { console.debug('[DEBUG] Voice State Update'); - const change = (!!oldState.channel) ^ (!!newState.channel); + const change = !!oldState.channel ^ !!newState.channel; if (!change) return; const guild = newState.guild.name; diff --git a/index.js b/index.js index 65b0944..d39384f 100644 --- a/index.js +++ b/index.js @@ -37,13 +37,10 @@ const evtPath = join(__dirname, 'events'); getFiles(cmdPath) // For each command file .then(async (files) => - (await Promise.all(files.map(importAndCheck))) - .filter((module) => module !== 0) - ).then(async (commands) => { + (await Promise.all(files.map(importAndCheck))).filter((module) => module !== 0) + ) + .then(async (commands) => { const files = await getFiles(evtPath); - const events = await Promise.all( - files.map(async (filePath) => - await import(filePath) - )); + const events = await Promise.all(files.map(async (filePath) => await import(filePath))); runClient(commands, events); }); diff --git a/models/messages.js b/models/messages.js index 45f1340..dae0097 100644 --- a/models/messages.js +++ b/models/messages.js @@ -1,10 +1,10 @@ -import { DataTypes } from "sequelize"; - -export default function(sequelize) { - return sequelize.define('Messages', { - id: { - type: DataTypes.STRING, - primaryKey: true, - }, - }); -} +import { DataTypes } from 'sequelize'; + +export default function (sequelize) { + return sequelize.define('Messages', { + id: { + type: DataTypes.STRING, + primaryKey: true + } + }); +} diff --git a/models/roleEmojiPairs.js b/models/roleEmojiPairs.js index 7814483..9b962c6 100644 --- a/models/roleEmojiPairs.js +++ b/models/roleEmojiPairs.js @@ -1,25 +1,25 @@ -import { DataTypes, Deferrable } from "sequelize"; - -export default function(sequelize) { - return sequelize.define('RoleEmojiPairs', { - id: { - defaultValue: DataTypes.UUIDV4, - type: DataTypes.UUID, - primaryKey: true, - }, - message: { - type: DataTypes.STRING, - references: { - deferrable: Deferrable.INITIALLY_IMMEDIATE, - model: 'Messages', - key: 'id', - }, - }, - role: { - type: DataTypes.STRING, - }, - emoji: { - type: DataTypes.STRING, - } - }); -} +import { DataTypes, Deferrable } from 'sequelize'; + +export default function (sequelize) { + return sequelize.define('RoleEmojiPairs', { + id: { + defaultValue: DataTypes.UUIDV4, + type: DataTypes.UUID, + primaryKey: true + }, + message: { + type: DataTypes.STRING, + references: { + deferrable: Deferrable.INITIALLY_IMMEDIATE, + model: 'Messages', + key: 'id' + } + }, + role: { + type: DataTypes.STRING + }, + emoji: { + type: DataTypes.STRING + } + }); +} diff --git a/models/voiceChannels.js b/models/voiceChannels.js index 6fb9f41..f175404 100644 --- a/models/voiceChannels.js +++ b/models/voiceChannels.js @@ -1,10 +1,10 @@ -import { DataTypes } from "sequelize"; - -export default function(sequelize) { - return sequelize.define('VoiceChannel', { - id: { - type: DataTypes.STRING, - primaryKey: true, - }, - }); -} +import { DataTypes } from 'sequelize'; + +export default function (sequelize) { + return sequelize.define('VoiceChannel', { + id: { + type: DataTypes.STRING, + primaryKey: true + } + }); +} diff --git a/shared.js b/shared.js index 3740d7d..4126019 100644 --- a/shared.js +++ b/shared.js @@ -1,98 +1,104 @@ -import { Op } from "sequelize"; -import { Message, RoleEmojiPair } from "./database.js"; - -const saveMessageData = async (id, role, emoji) => { - // Try finding message - const msg = await Message.findOne({ where: { id } }); - if (msg === null) throw new Error(`No message with ID '${id}' could be found!`); - - // Try finding existing entry - const rep = await RoleEmojiPair.findOne({ - where: { - [Op.or]: [ - { - message: id, - role: role.id - }, { - message: id, - emoji - } - ] - } - }); - if (rep !== null) throw new Error(`Existing RoleEmojiPair entry with (partial) data {message:${id},role:${role.id},emoji:${emoji}}!`); - - // Create database entry for pair - await RoleEmojiPair.create({ message: id, role: role.id, emoji }); -}; - -export const addSelfRoles = async (interaction, msgID, role, emoji) => { - const { channel } = interaction; - - let step = 'fetch'; - try { - // Get message by id - const message = await channel.messages.fetch(msgID); - - step = 'save data from'; - await saveMessageData(msgID, role, emoji); - - step = 'react to'; - // React with emoji to message - await message.react(emoji); - - // Reply successfully to acknowledge command - await interaction.reply({ - content: 'Added new entry for self roles!', - ephemeral: true, - }); - - console.info(`[INFO] Added new entry to get role with ID '${role.id}' using '${emoji}'.`); - } catch (error) { - console.error(error); - - // Reply failed to acknowledge command - await interaction.reply({ - content: `Failed to ${step} message!`, - ephemeral: true, - }); - } -} -import { join } from 'path'; -import { readdir } from 'fs/promises'; - -const required = ['data', 'execute']; -const optional = ['autocomplete', 'modalSubmit']; - -export const getFiles = async (dir) => { - const dirents = await readdir(dir, { withFileTypes: true }); - const files = await Promise.all(dirents.map((dirent) => { - const res = join(dir, dirent.name); - return dirent.isDirectory() ? getFiles(res) : res; - })); - return Array.prototype.concat(...files); -}; - -export const importAndCheck = async (filePath) => { - if (!filePath.endsWith('.js') || filePath.endsWith('.example.js')) { - // Skip this file - return 0; - } - const command = await import(filePath); - // Warn incomplete commands - if (!required.every((name) => name in command)) { - console.error( - `[ERROR] The command at ${filePath} is missing a required "data" or "execute" property.` - ); - return 0; - } - const properties = optional.filter((name) => !(name in command)); - if (properties.length > 0) - properties.forEach((name) => - console.warn( - `[WARNING] The command at ${filePath} is missing an optional "${name}" property.` - ) - ); - // Add command to collection - return command; -}; +import { Op } from 'sequelize'; +import { Message, RoleEmojiPair } from './database.js'; + +const saveMessageData = async (id, role, emoji) => { + // Try finding message + const msg = await Message.findOne({ where: { id } }); + if (msg === null) throw new Error(`No message with ID '${id}' could be found!`); + + // Try finding existing entry + const rep = await RoleEmojiPair.findOne({ + where: { + [Op.or]: [ + { + message: id, + role: role.id + }, + { + message: id, + emoji + } + ] + } + }); + if (rep !== null) + throw new Error( + `Existing RoleEmojiPair entry with (partial) data {message:${id},role:${role.id},emoji:${emoji}}!` + ); + + // Create database entry for pair + await RoleEmojiPair.create({ message: id, role: role.id, emoji }); +}; + +export const addSelfRoles = async (interaction, msgID, role, emoji) => { + const { channel } = interaction; + + let step = 'fetch'; + try { + // Get message by id + const message = await channel.messages.fetch(msgID); + + step = 'save data from'; + await saveMessageData(msgID, role, emoji); + + step = 'react to'; + // React with emoji to message + await message.react(emoji); + + // Reply successfully to acknowledge command + await interaction.reply({ + content: 'Added new entry for self roles!', + ephemeral: true + }); + + console.info(`[INFO] Added new entry to get role with ID '${role.id}' using '${emoji}'.`); + } catch (error) { + console.error(error); + + // Reply failed to acknowledge command + await interaction.reply({ + content: `Failed to ${step} message!`, + ephemeral: true + }); + } +}; +import { join } from 'path'; +import { readdir } from 'fs/promises'; + +const required = ['data', 'execute']; +const optional = ['autocomplete', 'modalSubmit']; + +export const getFiles = async (dir) => { + const dirents = await readdir(dir, { withFileTypes: true }); + const files = await Promise.all( + dirents.map((dirent) => { + const res = join(dir, dirent.name); + return dirent.isDirectory() ? getFiles(res) : res; + }) + ); + return Array.prototype.concat(...files); +}; + +export const importAndCheck = async (filePath) => { + if (!filePath.endsWith('.js') || filePath.endsWith('.example.js')) { + // Skip this file + return 0; + } + const command = await import(filePath); + // Warn incomplete commands + if (!required.every((name) => name in command)) { + console.error( + `[ERROR] The command at ${filePath} is missing a required "data" or "execute" property.` + ); + return 0; + } + const properties = optional.filter((name) => !(name in command)); + if (properties.length > 0) + properties.forEach((name) => + console.warn( + `[WARNING] The command at ${filePath} is missing an optional "${name}" property.` + ) + ); + // Add command to collection + return command; +}; From 105932b5a3b8a0ec9be7641e88d90ae31f08629d Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Tue, 6 Feb 2024 19:12:34 +0100 Subject: [PATCH 045/133] refactor: better file structure --- .../{ctx_add_self_roles.js => self_roles/context/add.js} | 2 +- .../context/register.js} | 2 +- commands/admin/{slash_self_roles.js => self_roles/slash.js} | 4 ++-- events/{ => channels}/voiceStateUpdate.js | 0 events/{ => reactions}/reactionAdd.js | 2 +- events/{ => reactions}/reactionRemove.js | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename commands/admin/{ctx_add_self_roles.js => self_roles/context/add.js} (97%) rename commands/admin/{ctx_register_self_roles.js => self_roles/context/register.js} (94%) rename commands/admin/{slash_self_roles.js => self_roles/slash.js} (97%) rename events/{ => channels}/voiceStateUpdate.js (100%) rename events/{ => reactions}/reactionAdd.js (95%) rename events/{ => reactions}/reactionRemove.js (95%) diff --git a/commands/admin/ctx_add_self_roles.js b/commands/admin/self_roles/context/add.js similarity index 97% rename from commands/admin/ctx_add_self_roles.js rename to commands/admin/self_roles/context/add.js index bce7e65..f45babc 100644 --- a/commands/admin/ctx_add_self_roles.js +++ b/commands/admin/self_roles/context/add.js @@ -5,7 +5,7 @@ import { ApplicationCommandType, ContextMenuCommandBuilder } from 'discord.js'; -import { addSelfRoles } from '../../shared.js'; +import { addSelfRoles } from '../../../../shared.js'; export const data = new ContextMenuCommandBuilder() .setDMPermission(false) diff --git a/commands/admin/ctx_register_self_roles.js b/commands/admin/self_roles/context/register.js similarity index 94% rename from commands/admin/ctx_register_self_roles.js rename to commands/admin/self_roles/context/register.js index 485216b..ece08c0 100644 --- a/commands/admin/ctx_register_self_roles.js +++ b/commands/admin/self_roles/context/register.js @@ -1,4 +1,4 @@ -import { Message } from '../../database.js'; +import { Message } from '../../../../database.js'; import { ApplicationCommandType, ContextMenuCommandBuilder } from 'discord.js'; export const data = new ContextMenuCommandBuilder() diff --git a/commands/admin/slash_self_roles.js b/commands/admin/self_roles/slash.js similarity index 97% rename from commands/admin/slash_self_roles.js rename to commands/admin/self_roles/slash.js index 1abf70d..375b254 100644 --- a/commands/admin/slash_self_roles.js +++ b/commands/admin/self_roles/slash.js @@ -1,6 +1,6 @@ +import { addSelfRoles } from '../../../shared.js'; import { SlashCommandBuilder } from 'discord.js'; -import { Message } from './../../database.js'; -import { addSelfRoles } from '../../shared.js'; +import { Message } from '../../../database.js'; const createSelfRoles = async (interaction) => { const { options, channel } = interaction; diff --git a/events/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js similarity index 100% rename from events/voiceStateUpdate.js rename to events/channels/voiceStateUpdate.js diff --git a/events/reactionAdd.js b/events/reactions/reactionAdd.js similarity index 95% rename from events/reactionAdd.js rename to events/reactions/reactionAdd.js index 8e6d4e5..f907e7f 100644 --- a/events/reactionAdd.js +++ b/events/reactions/reactionAdd.js @@ -1,6 +1,6 @@ import { config } from 'dotenv'; import { Events } from 'discord.js'; -import { Message, RoleEmojiPair } from '../database.js'; +import { Message, RoleEmojiPair } from '../../database.js'; config(); export const name = Events.MessageReactionAdd; diff --git a/events/reactionRemove.js b/events/reactions/reactionRemove.js similarity index 95% rename from events/reactionRemove.js rename to events/reactions/reactionRemove.js index bd3d56e..2c0f2e3 100644 --- a/events/reactionRemove.js +++ b/events/reactions/reactionRemove.js @@ -1,6 +1,6 @@ import { config } from 'dotenv'; import { Events } from 'discord.js'; -import { Message, RoleEmojiPair } from '../database.js'; +import { Message, RoleEmojiPair } from '../../database.js'; config(); export const name = Events.MessageReactionRemove; From 000acd20aa97f0ba36e33d1dd6e810404a382e1a Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Tue, 6 Feb 2024 21:25:47 +0100 Subject: [PATCH 046/133] clean up: format --- events/reactions/reactionAdd.js | 1 + events/reactions/reactionRemove.js | 1 + 2 files changed, 2 insertions(+) diff --git a/events/reactions/reactionAdd.js b/events/reactions/reactionAdd.js index f907e7f..b12a43a 100644 --- a/events/reactions/reactionAdd.js +++ b/events/reactions/reactionAdd.js @@ -1,6 +1,7 @@ import { config } from 'dotenv'; import { Events } from 'discord.js'; import { Message, RoleEmojiPair } from '../../database.js'; + config(); export const name = Events.MessageReactionAdd; diff --git a/events/reactions/reactionRemove.js b/events/reactions/reactionRemove.js index 2c0f2e3..f91dd08 100644 --- a/events/reactions/reactionRemove.js +++ b/events/reactions/reactionRemove.js @@ -1,6 +1,7 @@ import { config } from 'dotenv'; import { Events } from 'discord.js'; import { Message, RoleEmojiPair } from '../../database.js'; + config(); export const name = Events.MessageReactionRemove; From c8e3fc2f2c53add43edd46b2e02d69118412fc51 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Tue, 6 Feb 2024 21:26:29 +0100 Subject: [PATCH 047/133] initialize custom vc slash command --- commands/admin/custom_vc/slash.js | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 commands/admin/custom_vc/slash.js diff --git a/commands/admin/custom_vc/slash.js b/commands/admin/custom_vc/slash.js new file mode 100644 index 0000000..648c1a8 --- /dev/null +++ b/commands/admin/custom_vc/slash.js @@ -0,0 +1,32 @@ +import { SlashCommandBuilder } from 'discord.js'; + +export const data = new SlashCommandBuilder() + .setName('custom_vc') + .setDMPermission(false) + .setDescription('Manages reactions for self roles.') + .addSubcommand((subcommand) => + subcommand + .setName('create') + .setDescription('Creates new voice channel.') + .addStringOption((option) => + option + .setName('name') + .setRequired(true) + .setDescription('The name to use for the voice channel.') + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('register') + .setDescription('Registers an existing voice channel.') + .addStringOption((option) => + option + .setName('id') + .setRequired(true) + .setDescription('The ID to reference the voice channel to be used.') + ) + ); +export async function execute(interaction) { + const { options } = interaction; + // const id = options.getString('id'); +} From a0eb75e4ee0ea8b77a498b8433790c22b11d5c1f Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 7 Feb 2024 17:35:30 +0100 Subject: [PATCH 048/133] basic subcommand handling --- commands/admin/custom_vc/slash.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/commands/admin/custom_vc/slash.js b/commands/admin/custom_vc/slash.js index 648c1a8..33d4a59 100644 --- a/commands/admin/custom_vc/slash.js +++ b/commands/admin/custom_vc/slash.js @@ -28,5 +28,13 @@ export const data = new SlashCommandBuilder() ); export async function execute(interaction) { const { options } = interaction; - // const id = options.getString('id'); + + switch (options.getSubcommand()) { + case 'create': + const name = options.getString('name'); + break; + case 'register': + const id = options.getString('id'); + break; + } } From 1a3e7ac96be2338958f4b05fb392e8d1465c4181 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 7 Feb 2024 17:36:00 +0100 Subject: [PATCH 049/133] basic chanel creation --- events/channels/voiceStateUpdate.js | 32 ++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index d698ca5..2fe381d 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -1,16 +1,28 @@ -import { Events } from 'discord.js'; +import { PermissionsBitField } from 'discord.js'; +import { ChannelType, Events } from 'discord.js'; export const name = Events.VoiceStateUpdate; -export async function execute(oldState, newState) { - console.debug('[DEBUG] Voice State Update'); +export async function execute(_, state) { + if (!state.channel) return; - const change = !!oldState.channel ^ !!newState.channel; - if (!change) return; + const member = state.member; + const name = member.user.username; - const guild = newState.guild.name; - const member = newState.member.user.username; - const state = newState.channel ? 'joined' : 'left'; - const channel = (oldState.channel ?? newState.channel).name; + const channels = state.guild.channels; + const privCh = await channels.create({ + name: `${name}${name.endsWith('s') ? "'" : "'s"} channel`, + type: ChannelType.GuildVoice, + permissionOverwrites: [ + { + id: member.id, + allow: [ + PermissionsBitField.All + ] + } + ] + }); - console.debug(`[DEBUG] User '${member}' ${state} channel '${channel}' in guild '${guild}'.`); + await state.setChannel(privCh); + + console.debug(`[DEBUG] User '${member}' created private channel!`); } From b5079b6f40c0a32da3691c82975a2bd3a4770891 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 7 Feb 2024 17:40:29 +0100 Subject: [PATCH 050/133] basic error handling and comments --- events/channels/voiceStateUpdate.js | 42 +++++++++++++++++------------ 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index 2fe381d..a48b01b 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -1,28 +1,36 @@ -import { PermissionsBitField } from 'discord.js'; -import { ChannelType, Events } from 'discord.js'; +import { ChannelType, Events, PermissionsBitField } from 'discord.js'; export const name = Events.VoiceStateUpdate; export async function execute(_, state) { if (!state.channel) return; + // Extract user data const member = state.member; const name = member.user.username; + // Extract channel data const channels = state.guild.channels; - const privCh = await channels.create({ - name: `${name}${name.endsWith('s') ? "'" : "'s"} channel`, - type: ChannelType.GuildVoice, - permissionOverwrites: [ - { - id: member.id, - allow: [ - PermissionsBitField.All - ] - } - ] - }); + try { + // Create private channel with all permissions + const chName = `${name}${name.endsWith('s') ? "'" : "'s"} channel`; + const privCh = await channels.create({ + name: chName, + type: ChannelType.GuildVoice, + permissionOverwrites: [ + { + id: member.id, + allow: [ + PermissionsBitField.All + ] + } + ] + }); - await state.setChannel(privCh); - - console.debug(`[DEBUG] User '${member}' created private channel!`); + // Move user to private channel + await state.setChannel(privCh); + console.info(`[INFO] User '${name}' created private channel with ID ${privCh.id}.`); + } catch (error) { + console.error(error); + await member.send('Could no create or move to channel! Please contact server staff.'); + } } From d0ec7fbb6c6897807231840d1cce8dc3283ce56b Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 7 Feb 2024 19:30:14 +0100 Subject: [PATCH 051/133] basic custom vc slash command --- commands/admin/custom_vc/slash.js | 50 ++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/commands/admin/custom_vc/slash.js b/commands/admin/custom_vc/slash.js index 33d4a59..57d846c 100644 --- a/commands/admin/custom_vc/slash.js +++ b/commands/admin/custom_vc/slash.js @@ -1,4 +1,5 @@ import { SlashCommandBuilder } from 'discord.js'; +import { VoiceChannel } from '../../../database.js'; export const data = new SlashCommandBuilder() .setName('custom_vc') @@ -27,14 +28,47 @@ export const data = new SlashCommandBuilder() ) ); export async function execute(interaction) { - const { options } = interaction; + const { guild, options } = interaction; - switch (options.getSubcommand()) { - case 'create': - const name = options.getString('name'); - break; - case 'register': - const id = options.getString('id'); - break; + let step; + try { + switch (options.getSubcommand()) { + case 'create': { + // Get channel name from user input + const name = options.getString('name'); + + step = 'create'; + // Create new channel + const channel = await guild.create({ + name, type: ChannelType.GuildVoice + }); + + // Save channel data + step = 'save'; + await VoiceChannel.create({ id: channel.id }); + break; + } + case 'register': { + // Get channel id from user input + const id = options.getString('id'); + + step = 'fetch'; + // Try fetching channel by id + await guild.channels.fetch(id); + + // Save channel data + step = 'save'; + await VoiceChannel.create({ id }); + break; + } + } + } catch (error) { + console.error(error); + + // Reply failed to acknowledge command + await interaction.reply({ + content: `Failed to ${step} message!`, + ephemeral: true + }); } } From d212ca398a4feb7b32f0e2e0c7a726c1a58b8964 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 7 Feb 2024 19:34:18 +0100 Subject: [PATCH 052/133] acknowledge command with reply --- commands/admin/custom_vc/slash.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/commands/admin/custom_vc/slash.js b/commands/admin/custom_vc/slash.js index 57d846c..c640655 100644 --- a/commands/admin/custom_vc/slash.js +++ b/commands/admin/custom_vc/slash.js @@ -46,6 +46,12 @@ export async function execute(interaction) { // Save channel data step = 'save'; await VoiceChannel.create({ id: channel.id }); + + // Reply success to acknowledge command + await interaction.reply({ + content: `Successfully created channel!`, + ephemeral: true + }); break; } case 'register': { @@ -59,6 +65,12 @@ export async function execute(interaction) { // Save channel data step = 'save'; await VoiceChannel.create({ id }); + + // Reply success to acknowledge command + await interaction.reply({ + content: `Successfully registered channel!`, + ephemeral: true + }); break; } } From a29e2074a59a5be4652ff39dbb808047bcc21ae8 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 7 Feb 2024 19:38:51 +0100 Subject: [PATCH 053/133] ignore if channel is unregistered --- events/channels/voiceStateUpdate.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index a48b01b..5ee9841 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -1,9 +1,18 @@ import { ChannelType, Events, PermissionsBitField } from 'discord.js'; +import { VoiceChannel } from '../../database.js'; export const name = Events.VoiceStateUpdate; export async function execute(_, state) { if (!state.channel) return; + // Find channel by id, return if not registered for customs + const channel = await VoiceChannel.findOne({ + where: { + id: state.channel.id + } + }); + if (channel === null) return; + // Extract user data const member = state.member; const name = member.user.username; From f3e2715703f188fe26224b282728c5029fcc2e49 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 7 Feb 2024 20:04:16 +0100 Subject: [PATCH 054/133] use existing private channels --- commands/admin/custom_vc/slash.js | 11 +++-- events/channels/voiceStateUpdate.js | 63 ++++++++++++++++++++--------- models/voiceChannels.js | 10 ++++- 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/commands/admin/custom_vc/slash.js b/commands/admin/custom_vc/slash.js index c640655..a079c69 100644 --- a/commands/admin/custom_vc/slash.js +++ b/commands/admin/custom_vc/slash.js @@ -1,4 +1,4 @@ -import { SlashCommandBuilder } from 'discord.js'; +import { ChannelType, SlashCommandBuilder } from 'discord.js'; import { VoiceChannel } from '../../../database.js'; export const data = new SlashCommandBuilder() @@ -39,13 +39,16 @@ export async function execute(interaction) { step = 'create'; // Create new channel - const channel = await guild.create({ + const channel = await guild.channels.create({ name, type: ChannelType.GuildVoice }); // Save channel data step = 'save'; - await VoiceChannel.create({ id: channel.id }); + await VoiceChannel.create({ + id: channel.id, + create: true + }); // Reply success to acknowledge command await interaction.reply({ @@ -64,7 +67,7 @@ export async function execute(interaction) { // Save channel data step = 'save'; - await VoiceChannel.create({ id }); + await VoiceChannel.create({ id, create: true }); // Reply success to acknowledge command await interaction.reply({ diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index 5ee9841..a640d0c 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -1,45 +1,70 @@ import { ChannelType, Events, PermissionsBitField } from 'discord.js'; import { VoiceChannel } from '../../database.js'; +const getchannel = async (member, channels) => { + // Check database for existing channel + const ownCh = await VoiceChannel.findOne({ + where: { + owner: member.user.id + } + }); + if (ownCh !== null) { + return await channels.fetch(ownCh.id); + } + + // Create private channel with all permissions + const name = member.user.username; + const chName = `${name}${name.endsWith('s') ? "'" : "'s"} channel`; + const privCh = await channels.create({ + name: chName, + type: ChannelType.GuildVoice, + permissionOverwrites: [ + { + id: member.id, + allow: [ + PermissionsBitField.All + ] + } + ] + }); + + // Save newly created channel + await VoiceChannel.create({ + id: privCh.id, + owner: member.user.id + }) + + return privCh; +}; + export const name = Events.VoiceStateUpdate; export async function execute(_, state) { if (!state.channel) return; // Find channel by id, return if not registered for customs - const channel = await VoiceChannel.findOne({ + const createCh = await VoiceChannel.findOne({ where: { - id: state.channel.id + id: state.channel.id, + create: true, } }); - if (channel === null) return; + if (createCh === null) return; // Extract user data const member = state.member; - const name = member.user.username; // Extract channel data const channels = state.guild.channels; + let step = 'create'; try { - // Create private channel with all permissions - const chName = `${name}${name.endsWith('s') ? "'" : "'s"} channel`; - const privCh = await channels.create({ - name: chName, - type: ChannelType.GuildVoice, - permissionOverwrites: [ - { - id: member.id, - allow: [ - PermissionsBitField.All - ] - } - ] - }); + const privCh = await getchannel(member, channels); + step = 'move to'; // Move user to private channel await state.setChannel(privCh); console.info(`[INFO] User '${name}' created private channel with ID ${privCh.id}.`); } catch (error) { console.error(error); - await member.send('Could no create or move to channel! Please contact server staff.'); + await member.send(`Failed to ${step} channel! Please contact server staff.`); } } diff --git a/models/voiceChannels.js b/models/voiceChannels.js index f175404..1e73e57 100644 --- a/models/voiceChannels.js +++ b/models/voiceChannels.js @@ -1,10 +1,18 @@ import { DataTypes } from 'sequelize'; -export default function (sequelize) { +export default function(sequelize) { return sequelize.define('VoiceChannel', { id: { type: DataTypes.STRING, primaryKey: true + }, + create: { + type: DataTypes.BOOLEAN + }, + owner: { + type: DataTypes.STRING, + defaultValue: false, + allowNull: true } }); } From 0ffb68fd96153838b78b7535a0a0bc1d51ee87a0 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 7 Feb 2024 20:26:57 +0100 Subject: [PATCH 055/133] prepare for better event grouping --- events/{reactions => messages}/reactionAdd.js | 0 events/{reactions => messages}/reactionRemove.js | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename events/{reactions => messages}/reactionAdd.js (100%) rename events/{reactions => messages}/reactionRemove.js (100%) diff --git a/events/reactions/reactionAdd.js b/events/messages/reactionAdd.js similarity index 100% rename from events/reactions/reactionAdd.js rename to events/messages/reactionAdd.js diff --git a/events/reactions/reactionRemove.js b/events/messages/reactionRemove.js similarity index 100% rename from events/reactions/reactionRemove.js rename to events/messages/reactionRemove.js From 3339d7a2441cf095e0e9fea51607276cf2e0c9c8 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 7 Feb 2024 20:27:16 +0100 Subject: [PATCH 056/133] initialize deletion events --- events/channels/channelDelete.js | 5 +++++ events/messages/messageDelete.js | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 events/channels/channelDelete.js create mode 100644 events/messages/messageDelete.js diff --git a/events/channels/channelDelete.js b/events/channels/channelDelete.js new file mode 100644 index 0000000..ff32238 --- /dev/null +++ b/events/channels/channelDelete.js @@ -0,0 +1,5 @@ +import { Events } from 'discord.js'; + +export const name = Events.ChannelDelete; +export async function execute(channel) { +} diff --git a/events/messages/messageDelete.js b/events/messages/messageDelete.js new file mode 100644 index 0000000..9272a83 --- /dev/null +++ b/events/messages/messageDelete.js @@ -0,0 +1,5 @@ +import { Events } from 'discord.js'; + +export const name = Events.MessageDelete; +export async function execute(message) { +} From 6ded01b04688b1ca91a015571f4468b67e7b03cd Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 7 Feb 2024 21:02:37 +0100 Subject: [PATCH 057/133] handle message/channel deletion --- database.js | 5 ++++- events/channels/channelDelete.js | 9 +++++++++ events/messages/messageDelete.js | 9 +++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/database.js b/database.js index 0bcbc0e..c4e1222 100644 --- a/database.js +++ b/database.js @@ -11,10 +11,13 @@ const sequelize = new Sequelize({ dialect: 'sqlite', logging: false }); + const RoleEmojiPair = defineRoleEmojiPair(sequelize); + const VoiceChannel = defineVoiceChannel(sequelize); + const Message = defineMessage(sequelize); +Message.hasMany(RoleEmojiPair, { foreignKey: 'message', onDelete: 'CASCADE' }); sequelize.sync(); - export { sequelize, RoleEmojiPair, VoiceChannel, Message }; diff --git a/events/channels/channelDelete.js b/events/channels/channelDelete.js index ff32238..6564da4 100644 --- a/events/channels/channelDelete.js +++ b/events/channels/channelDelete.js @@ -1,5 +1,14 @@ import { Events } from 'discord.js'; +import { VoiceChannel } from '../../database.js'; export const name = Events.ChannelDelete; export async function execute(channel) { + // Delete channel entry once channel is deleted itself + const count = await VoiceChannel.destroy({ + where: { + id: channel.id + } + }); + if (count > 0) + console.info(`[INFO] Custom VC with ID '${channel.id}' was deleted.`); } diff --git a/events/messages/messageDelete.js b/events/messages/messageDelete.js index 9272a83..3975c95 100644 --- a/events/messages/messageDelete.js +++ b/events/messages/messageDelete.js @@ -1,5 +1,14 @@ import { Events } from 'discord.js'; +import { Message } from '../../database.js'; export const name = Events.MessageDelete; export async function execute(message) { + // Delete message entry once message is deleted itself + const count = await Message.destroy({ + where: { + id: message.id + } + }); + if (count > 0) + console.info(`[INFO] Reaction Roles Message with ID '${message.id}' was deleted.`); } From b9407ed52c7f6bde6dc155469e5ed61ba3de4f2a Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 7 Feb 2024 22:02:02 +0100 Subject: [PATCH 058/133] lowercase name for possessive apostroph --- events/channels/voiceStateUpdate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index a640d0c..8b33a89 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -14,7 +14,7 @@ const getchannel = async (member, channels) => { // Create private channel with all permissions const name = member.user.username; - const chName = `${name}${name.endsWith('s') ? "'" : "'s"} channel`; + const chName = `${name}${name.toLowerCase().endsWith('s') ? "'" : "'s"} channel`; const privCh = await channels.create({ name: chName, type: ChannelType.GuildVoice, From 7f94c9ddd3dc1ada883551923064cf592bdb0916 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 7 Feb 2024 22:07:41 +0100 Subject: [PATCH 059/133] version upgrade: fully functioning prototype --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6dad1df..a927082 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-bot", - "version": "0.0.1", + "version": "0.1.0", "description": "", "private": true, "main": "index.js", From f5d8bcc449302de73dc63845d85701fea9f85202 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 7 Feb 2024 23:20:10 +0100 Subject: [PATCH 060/133] bugfix: wrong feedback type --- commands/admin/custom_vc/slash.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands/admin/custom_vc/slash.js b/commands/admin/custom_vc/slash.js index a079c69..6b037ab 100644 --- a/commands/admin/custom_vc/slash.js +++ b/commands/admin/custom_vc/slash.js @@ -82,7 +82,7 @@ export async function execute(interaction) { // Reply failed to acknowledge command await interaction.reply({ - content: `Failed to ${step} message!`, + content: `Failed to ${step} channel!`, ephemeral: true }); } From 8e9ddb1e7bee1ef705256eb182eff7039224d8b5 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Thu, 8 Feb 2024 00:21:53 +0100 Subject: [PATCH 061/133] explicit permission overwrites --- events/channels/voiceStateUpdate.js | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index 8b33a89..af4c852 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -1,6 +1,24 @@ -import { ChannelType, Events, PermissionsBitField } from 'discord.js'; +import { ChannelType, Events, PermissionFlagsBits } from 'discord.js'; import { VoiceChannel } from '../../database.js'; +const vcPermissionOverwrites = [ + PermissionFlagsBits.ManageRoles, + PermissionFlagsBits.ManageChannels, + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.ModerateMembers, + PermissionFlagsBits.SendMessages, + PermissionFlagsBits.SendMessagesInThreads, + PermissionFlagsBits.ManageMessages, + PermissionFlagsBits.ReadMessageHistory, + PermissionFlagsBits.AddReactions, + PermissionFlagsBits.Connect, + PermissionFlagsBits.Speak, + PermissionFlagsBits.MuteMembers, + PermissionFlagsBits.DeafenMembers, + PermissionFlagsBits.MoveMembers, + PermissionFlagsBits.UseVAD +]; + const getchannel = async (member, channels) => { // Check database for existing channel const ownCh = await VoiceChannel.findOne({ @@ -21,9 +39,7 @@ const getchannel = async (member, channels) => { permissionOverwrites: [ { id: member.id, - allow: [ - PermissionsBitField.All - ] + allow: vcPermissionOverwrites } ] }); From ea54565c6af12fd3c523136c5f8342b9c59cdbec Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Thu, 8 Feb 2024 17:50:32 +0100 Subject: [PATCH 062/133] bugfix: case sensitive --- events/channels/voiceStateUpdate.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index af4c852..698a16e 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -19,7 +19,7 @@ const vcPermissionOverwrites = [ PermissionFlagsBits.UseVAD ]; -const getchannel = async (member, channels) => { +const getChannel = async (member, channels) => { // Check database for existing channel const ownCh = await VoiceChannel.findOne({ where: { @@ -73,7 +73,7 @@ export async function execute(_, state) { const channels = state.guild.channels; let step = 'create'; try { - const privCh = await getchannel(member, channels); + const privCh = await getChannel(member, channels); step = 'move to'; // Move user to private channel From 76641b31c70fdba3c993aaec549cf0a8403624a0 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Thu, 8 Feb 2024 18:12:10 +0100 Subject: [PATCH 063/133] delete empty custom vc --- events/channels/voiceStateUpdate.js | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index 698a16e..209a281 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -53,31 +53,47 @@ const getChannel = async (member, channels) => { return privCh; }; +const leftVoiceChat = async (state) => { + const { channel } = state; + + // Isn't this always false? + if (!channel) return; + + // Get active members from channel + const members = Array.from(channel.members); + if (members.length > 0) return; + + // Delete channel from guild + await channel.guild.channels.delete(channel.id); + console.info(`[INFO] Custom VC with ID '${channel.id}' was empty and got deleted.`); +}; + export const name = Events.VoiceStateUpdate; -export async function execute(_, state) { - if (!state.channel) return; +export async function execute(oldState, newState) { + if (!newState.channel) + return await leftVoiceChat(oldState); // Find channel by id, return if not registered for customs const createCh = await VoiceChannel.findOne({ where: { - id: state.channel.id, + id: newState.channel.id, create: true, } }); if (createCh === null) return; // Extract user data - const member = state.member; + const member = newState.member; // Extract channel data - const channels = state.guild.channels; + const channels = newState.guild.channels; let step = 'create'; try { const privCh = await getChannel(member, channels); step = 'move to'; // Move user to private channel - await state.setChannel(privCh); + await newState.setChannel(privCh); console.info(`[INFO] User '${name}' created private channel with ID ${privCh.id}.`); } catch (error) { console.error(error); From 83068ae0859947b995c27a53be7365a3b0803da4 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Thu, 8 Feb 2024 18:24:47 +0100 Subject: [PATCH 064/133] reference channel directly --- commands/admin/custom_vc/slash.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/commands/admin/custom_vc/slash.js b/commands/admin/custom_vc/slash.js index 6b037ab..24df19c 100644 --- a/commands/admin/custom_vc/slash.js +++ b/commands/admin/custom_vc/slash.js @@ -20,11 +20,11 @@ export const data = new SlashCommandBuilder() subcommand .setName('register') .setDescription('Registers an existing voice channel.') - .addStringOption((option) => + .addChannelOption((option) => option - .setName('id') .setRequired(true) - .setDescription('The ID to reference the voice channel to be used.') + .setName('channel') + .setDescription('The voice channel to be used.') ) ); export async function execute(interaction) { @@ -59,11 +59,7 @@ export async function execute(interaction) { } case 'register': { // Get channel id from user input - const id = options.getString('id'); - - step = 'fetch'; - // Try fetching channel by id - await guild.channels.fetch(id); + const id = options.getChannel('channel'); // Save channel data step = 'save'; From 03fb793b6ca19211159877018bcad53743f0efd7 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Thu, 8 Feb 2024 18:43:01 +0100 Subject: [PATCH 065/133] only use voice channels --- commands/admin/custom_vc/slash.js | 1 + 1 file changed, 1 insertion(+) diff --git a/commands/admin/custom_vc/slash.js b/commands/admin/custom_vc/slash.js index 24df19c..e3255d0 100644 --- a/commands/admin/custom_vc/slash.js +++ b/commands/admin/custom_vc/slash.js @@ -24,6 +24,7 @@ export const data = new SlashCommandBuilder() option .setRequired(true) .setName('channel') + .addChannelTypes(ChannelType.GuildVoice) .setDescription('The voice channel to be used.') ) ); From 067702ecc6978e74208cc4abbde9313d55f88d6b Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Thu, 8 Feb 2024 19:03:58 +0100 Subject: [PATCH 066/133] remove vc from custom vc creation --- commands/admin/custom_vc/slash.js | 41 +++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/commands/admin/custom_vc/slash.js b/commands/admin/custom_vc/slash.js index e3255d0..52dbfba 100644 --- a/commands/admin/custom_vc/slash.js +++ b/commands/admin/custom_vc/slash.js @@ -19,7 +19,7 @@ export const data = new SlashCommandBuilder() .addSubcommand((subcommand) => subcommand .setName('register') - .setDescription('Registers an existing voice channel.') + .setDescription('Registers an existing voice channel for custom channel creation.') .addChannelOption((option) => option .setRequired(true) @@ -27,6 +27,18 @@ export const data = new SlashCommandBuilder() .addChannelTypes(ChannelType.GuildVoice) .setDescription('The voice channel to be used.') ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('remove') + .setDescription('Remove a voice channel from custom channel creation.') + .addChannelOption((option) => + option + .setRequired(true) + .setName('channel') + .addChannelTypes(ChannelType.GuildVoice) + .setDescription('The voice channel to be unregistered.') + ) ); export async function execute(interaction) { const { guild, options } = interaction; @@ -60,7 +72,7 @@ export async function execute(interaction) { } case 'register': { // Get channel id from user input - const id = options.getChannel('channel'); + const { id } = options.getChannel('channel'); // Save channel data step = 'save'; @@ -73,6 +85,31 @@ export async function execute(interaction) { }); break; } + case 'remove': { + // Get channel id from user input + const { id } = options.getChannel('channel'); + + // Remove channel from guild + step = 'remove'; + const count = await VoiceChannel.destroy({ + where: { + id, + create: true + } + }); + + // Set reply based on result of deletion + let response = 'Successfully removed'; + if (count === 0) + response = 'Failed to remove'; + + // Reply to acknowledge command + await interaction.reply({ + content: `${response} channel from custom channel creation!`, + ephemeral: true + }); + break; + } } } catch (error) { console.error(error); From 2c7dd261894392b3e28ae15aca2e4372080158b7 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Thu, 8 Feb 2024 19:09:02 +0100 Subject: [PATCH 067/133] verbose console feedback --- commands/admin/custom_vc/slash.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/commands/admin/custom_vc/slash.js b/commands/admin/custom_vc/slash.js index 52dbfba..f825e41 100644 --- a/commands/admin/custom_vc/slash.js +++ b/commands/admin/custom_vc/slash.js @@ -68,6 +68,8 @@ export async function execute(interaction) { content: `Successfully created channel!`, ephemeral: true }); + + console.info(`[INFO] New custom VC created with ID '${channel.id}'.`); break; } case 'register': { @@ -83,6 +85,8 @@ export async function execute(interaction) { content: `Successfully registered channel!`, ephemeral: true }); + + console.info(`[INFO] New custom VC registered using ID '${id}'.`); break; } case 'remove': { @@ -108,6 +112,8 @@ export async function execute(interaction) { content: `${response} channel from custom channel creation!`, ephemeral: true }); + + console.info(`[INFO] Removed custom VC with ID '${id}'.`); break; } } From 81bb307896ba0503565768d77860db6180a1755b Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Thu, 8 Feb 2024 19:09:17 +0100 Subject: [PATCH 068/133] group imports in shared --- shared.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared.js b/shared.js index 4126019..2676949 100644 --- a/shared.js +++ b/shared.js @@ -1,4 +1,6 @@ +import { join } from 'path'; import { Op } from 'sequelize'; +import { readdir } from 'fs/promises'; import { Message, RoleEmojiPair } from './database.js'; const saveMessageData = async (id, role, emoji) => { @@ -62,8 +64,6 @@ export const addSelfRoles = async (interaction, msgID, role, emoji) => { }); } }; -import { join } from 'path'; -import { readdir } from 'fs/promises'; const required = ['data', 'execute']; const optional = ['autocomplete', 'modalSubmit']; From dfe673bebcb310da99ff91a52be8b116c4e24560 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Thu, 8 Feb 2024 19:34:01 +0100 Subject: [PATCH 069/133] remove self roles slashcommand --- commands/admin/self_roles/slash.js | 54 ++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/commands/admin/self_roles/slash.js b/commands/admin/self_roles/slash.js index 375b254..1326ff9 100644 --- a/commands/admin/self_roles/slash.js +++ b/commands/admin/self_roles/slash.js @@ -50,6 +50,44 @@ const registerSelfRoles = async (interaction) => { return response; }; +const removeSelfRoles = async (interaction, msgID) => { + const { channel } = interaction; + + try { + // Try fetching message from channel + await channel.messages.fetch(msgID); + } catch (error) { + console.error(error); + // Reply to acknowledge command + await interaction.reply({ + content: `Failed to fetch message!`, + ephemeral: true + }); + return; + } + + // Try deleting message from database + const count = await Message.destroy({ + where: { + id: msgID + } + }); + + // Set reply based on result of deletion + let response = 'Successfully removed'; + if (count === 0) + response = 'Failed to remove'; + + // Reply to acknowledge command + await interaction.reply({ + content: `${response} self roles from message!`, + ephemeral: true + }); + + console.info(`[INFO] Removed self roles from message with ID '${msgID}'.`); + +}; + export const data = new SlashCommandBuilder() .setName('self_roles') .setDMPermission(false) @@ -92,6 +130,17 @@ export const data = new SlashCommandBuilder() .addStringOption((option) => option.setName('emoji').setRequired(true).setDescription('The emoji to be reacted with.') ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('remove') + .setDescription('Remove self roles from a message.') + .addStringOption((option) => + option + .setName('id') + .setRequired(true) + .setDescription('The ID to reference the message to be removed.') + ) ); export async function execute(interaction) { const { options } = interaction; @@ -120,6 +169,11 @@ export async function execute(interaction) { await addSelfRoles(interaction, msgID, role, emoji); break; } + case 'remove': { + const msgID = options.getString('id'); + await removeSelfRoles(interaction, msgID); + break; + } } if (createNew) { From e69806c1321f8d391b057d7c3febc6765df8275e Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Thu, 8 Feb 2024 19:44:16 +0100 Subject: [PATCH 070/133] remove self roels contextcommand and shared function --- commands/admin/self_roles/context/remove.js | 11 +++++++++++ commands/admin/self_roles/slash.js | 21 +------------------- shared.js | 22 +++++++++++++++++++++ 3 files changed, 34 insertions(+), 20 deletions(-) create mode 100644 commands/admin/self_roles/context/remove.js diff --git a/commands/admin/self_roles/context/remove.js b/commands/admin/self_roles/context/remove.js new file mode 100644 index 0000000..497aa8b --- /dev/null +++ b/commands/admin/self_roles/context/remove.js @@ -0,0 +1,11 @@ +import { removeSelfRoles } from '../../../../shared.js'; +import { ApplicationCommandType, ContextMenuCommandBuilder } from 'discord.js'; + +export const data = new ContextMenuCommandBuilder() + .setDMPermission(false) + .setName('Remove self roles') + .setType(ApplicationCommandType.Message); +export async function execute(interaction) { + const id = interaction.targetMessage.id; + await removeSelfRoles(interaction, id); +} diff --git a/commands/admin/self_roles/slash.js b/commands/admin/self_roles/slash.js index 1326ff9..c539059 100644 --- a/commands/admin/self_roles/slash.js +++ b/commands/admin/self_roles/slash.js @@ -66,26 +66,7 @@ const removeSelfRoles = async (interaction, msgID) => { return; } - // Try deleting message from database - const count = await Message.destroy({ - where: { - id: msgID - } - }); - - // Set reply based on result of deletion - let response = 'Successfully removed'; - if (count === 0) - response = 'Failed to remove'; - - // Reply to acknowledge command - await interaction.reply({ - content: `${response} self roles from message!`, - ephemeral: true - }); - - console.info(`[INFO] Removed self roles from message with ID '${msgID}'.`); - + await removeSelfRoles(interaction, msgID); }; export const data = new SlashCommandBuilder() diff --git a/shared.js b/shared.js index 2676949..1ead0ea 100644 --- a/shared.js +++ b/shared.js @@ -3,6 +3,28 @@ import { Op } from 'sequelize'; import { readdir } from 'fs/promises'; import { Message, RoleEmojiPair } from './database.js'; +export const removeSelfRoles = async (interaction, id) => { + // Try deleting message from database + const count = await Message.destroy({ + where: { + id: id + } + }); + + // Set reply based on result of deletion + let response = 'Successfully removed'; + if (count === 0) + response = 'Failed to remove'; + + // Reply to acknowledge command + await interaction.reply({ + content: `${response} self roles from message!`, + ephemeral: true + }); + + console.info(`[INFO] Removed self roles from message with ID '${id}'.`); +}; + const saveMessageData = async (id, role, emoji) => { // Try finding message const msg = await Message.findOne({ where: { id } }); From cb1662cefb4c04b90db96126a86e4e756b1ec955 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Thu, 8 Feb 2024 20:13:12 +0100 Subject: [PATCH 071/133] bugfix: only delete registered custom channels --- events/channels/voiceStateUpdate.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index 209a281..ae449eb 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -63,6 +63,15 @@ const leftVoiceChat = async (state) => { const members = Array.from(channel.members); if (members.length > 0) return; + // Find channel by id, return if not registered as custom + const custom = await VoiceChannel.findOne({ + where: { + id: channel.id, + create: false + } + }); + if (custom === null) return; + // Delete channel from guild await channel.guild.channels.delete(channel.id); console.info(`[INFO] Custom VC with ID '${channel.id}' was empty and got deleted.`); @@ -73,6 +82,18 @@ export async function execute(oldState, newState) { if (!newState.channel) return await leftVoiceChat(oldState); + console.log( + Array.from( + newState + .channel + .permissionOverwrites + .cache + .values() + ).forEach(overwrite => + console.log(overwrite.allow.toArray()) + ) + ); + // Find channel by id, return if not registered for customs const createCh = await VoiceChannel.findOne({ where: { From dc166dd2862e0e1702bd822930b4645a50bddbee Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Thu, 8 Feb 2024 20:30:50 +0100 Subject: [PATCH 072/133] set minimum required custom vc permissions --- events/channels/voiceStateUpdate.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index ae449eb..51c262b 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -2,21 +2,20 @@ import { ChannelType, Events, PermissionFlagsBits } from 'discord.js'; import { VoiceChannel } from '../../database.js'; const vcPermissionOverwrites = [ - PermissionFlagsBits.ManageRoles, - PermissionFlagsBits.ManageChannels, - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.ModerateMembers, - PermissionFlagsBits.SendMessages, - PermissionFlagsBits.SendMessagesInThreads, - PermissionFlagsBits.ManageMessages, PermissionFlagsBits.ReadMessageHistory, - PermissionFlagsBits.AddReactions, - PermissionFlagsBits.Connect, - PermissionFlagsBits.Speak, - PermissionFlagsBits.MuteMembers, + PermissionFlagsBits.PrioritySpeaker, + PermissionFlagsBits.ManageMessages, + PermissionFlagsBits.ManageChannels, PermissionFlagsBits.DeafenMembers, + PermissionFlagsBits.SendMessages, + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.MuteMembers, PermissionFlagsBits.MoveMembers, - PermissionFlagsBits.UseVAD + PermissionFlagsBits.ManageRoles, + PermissionFlagsBits.Connect, + PermissionFlagsBits.Stream, + PermissionFlagsBits.UseVAD, + PermissionFlagsBits.Speak ]; const getChannel = async (member, channels) => { From 552b0b9c3dd9302b30441377fdec92b246ae0fbe Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Thu, 8 Feb 2024 20:33:10 +0100 Subject: [PATCH 073/133] remove debug log --- events/channels/voiceStateUpdate.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index 51c262b..f14694f 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -81,18 +81,6 @@ export async function execute(oldState, newState) { if (!newState.channel) return await leftVoiceChat(oldState); - console.log( - Array.from( - newState - .channel - .permissionOverwrites - .cache - .values() - ).forEach(overwrite => - console.log(overwrite.allow.toArray()) - ) - ); - // Find channel by id, return if not registered for customs const createCh = await VoiceChannel.findOne({ where: { From 5338b56e4c674114e1f604b88b1bc5b9c53f56ff Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Thu, 8 Feb 2024 20:37:31 +0100 Subject: [PATCH 074/133] bugfix: moved property to correct parent --- models/voiceChannels.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/voiceChannels.js b/models/voiceChannels.js index 1e73e57..db8057b 100644 --- a/models/voiceChannels.js +++ b/models/voiceChannels.js @@ -7,11 +7,11 @@ export default function(sequelize) { primaryKey: true }, create: { - type: DataTypes.BOOLEAN + type: DataTypes.BOOLEAN, + defaultValue: false }, owner: { type: DataTypes.STRING, - defaultValue: false, allowNull: true } }); From 7a9c2441a3d13af32188275094f76bf6cff50f80 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Thu, 8 Feb 2024 21:11:56 +0100 Subject: [PATCH 075/133] reformat: auto format --- commands/admin/custom_vc/slash.js | 258 ++++++++++---------- commands/admin/self_roles/context/remove.js | 22 +- events/channels/channelDelete.js | 27 +- events/channels/voiceStateUpdate.js | 7 +- events/messages/messageDelete.js | 27 +- models/voiceChannels.js | 2 +- shared.js | 3 +- 7 files changed, 171 insertions(+), 175 deletions(-) diff --git a/commands/admin/custom_vc/slash.js b/commands/admin/custom_vc/slash.js index f825e41..8b612c6 100644 --- a/commands/admin/custom_vc/slash.js +++ b/commands/admin/custom_vc/slash.js @@ -1,129 +1,129 @@ -import { ChannelType, SlashCommandBuilder } from 'discord.js'; -import { VoiceChannel } from '../../../database.js'; - -export const data = new SlashCommandBuilder() - .setName('custom_vc') - .setDMPermission(false) - .setDescription('Manages reactions for self roles.') - .addSubcommand((subcommand) => - subcommand - .setName('create') - .setDescription('Creates new voice channel.') - .addStringOption((option) => - option - .setName('name') - .setRequired(true) - .setDescription('The name to use for the voice channel.') - ) - ) - .addSubcommand((subcommand) => - subcommand - .setName('register') - .setDescription('Registers an existing voice channel for custom channel creation.') - .addChannelOption((option) => - option - .setRequired(true) - .setName('channel') - .addChannelTypes(ChannelType.GuildVoice) - .setDescription('The voice channel to be used.') - ) - ) - .addSubcommand((subcommand) => - subcommand - .setName('remove') - .setDescription('Remove a voice channel from custom channel creation.') - .addChannelOption((option) => - option - .setRequired(true) - .setName('channel') - .addChannelTypes(ChannelType.GuildVoice) - .setDescription('The voice channel to be unregistered.') - ) - ); -export async function execute(interaction) { - const { guild, options } = interaction; - - let step; - try { - switch (options.getSubcommand()) { - case 'create': { - // Get channel name from user input - const name = options.getString('name'); - - step = 'create'; - // Create new channel - const channel = await guild.channels.create({ - name, type: ChannelType.GuildVoice - }); - - // Save channel data - step = 'save'; - await VoiceChannel.create({ - id: channel.id, - create: true - }); - - // Reply success to acknowledge command - await interaction.reply({ - content: `Successfully created channel!`, - ephemeral: true - }); - - console.info(`[INFO] New custom VC created with ID '${channel.id}'.`); - break; - } - case 'register': { - // Get channel id from user input - const { id } = options.getChannel('channel'); - - // Save channel data - step = 'save'; - await VoiceChannel.create({ id, create: true }); - - // Reply success to acknowledge command - await interaction.reply({ - content: `Successfully registered channel!`, - ephemeral: true - }); - - console.info(`[INFO] New custom VC registered using ID '${id}'.`); - break; - } - case 'remove': { - // Get channel id from user input - const { id } = options.getChannel('channel'); - - // Remove channel from guild - step = 'remove'; - const count = await VoiceChannel.destroy({ - where: { - id, - create: true - } - }); - - // Set reply based on result of deletion - let response = 'Successfully removed'; - if (count === 0) - response = 'Failed to remove'; - - // Reply to acknowledge command - await interaction.reply({ - content: `${response} channel from custom channel creation!`, - ephemeral: true - }); - - console.info(`[INFO] Removed custom VC with ID '${id}'.`); - break; - } - } - } catch (error) { - console.error(error); - - // Reply failed to acknowledge command - await interaction.reply({ - content: `Failed to ${step} channel!`, - ephemeral: true - }); - } -} +import { ChannelType, SlashCommandBuilder } from 'discord.js'; +import { VoiceChannel } from '../../../database.js'; + +export const data = new SlashCommandBuilder() + .setName('custom_vc') + .setDMPermission(false) + .setDescription('Manages reactions for self roles.') + .addSubcommand((subcommand) => + subcommand + .setName('create') + .setDescription('Creates new voice channel.') + .addStringOption((option) => + option + .setName('name') + .setRequired(true) + .setDescription('The name to use for the voice channel.') + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('register') + .setDescription('Registers an existing voice channel for custom channel creation.') + .addChannelOption((option) => + option + .setRequired(true) + .setName('channel') + .addChannelTypes(ChannelType.GuildVoice) + .setDescription('The voice channel to be used.') + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('remove') + .setDescription('Remove a voice channel from custom channel creation.') + .addChannelOption((option) => + option + .setRequired(true) + .setName('channel') + .addChannelTypes(ChannelType.GuildVoice) + .setDescription('The voice channel to be unregistered.') + ) + ); +export async function execute(interaction) { + const { guild, options } = interaction; + + let step; + try { + switch (options.getSubcommand()) { + case 'create': { + // Get channel name from user input + const name = options.getString('name'); + + step = 'create'; + // Create new channel + const channel = await guild.channels.create({ + name, + type: ChannelType.GuildVoice + }); + + // Save channel data + step = 'save'; + await VoiceChannel.create({ + id: channel.id, + create: true + }); + + // Reply success to acknowledge command + await interaction.reply({ + content: `Successfully created channel!`, + ephemeral: true + }); + + console.info(`[INFO] New custom VC created with ID '${channel.id}'.`); + break; + } + case 'register': { + // Get channel id from user input + const { id } = options.getChannel('channel'); + + // Save channel data + step = 'save'; + await VoiceChannel.create({ id, create: true }); + + // Reply success to acknowledge command + await interaction.reply({ + content: `Successfully registered channel!`, + ephemeral: true + }); + + console.info(`[INFO] New custom VC registered using ID '${id}'.`); + break; + } + case 'remove': { + // Get channel id from user input + const { id } = options.getChannel('channel'); + + // Remove channel from guild + step = 'remove'; + const count = await VoiceChannel.destroy({ + where: { + id, + create: true + } + }); + + // Set reply based on result of deletion + let response = 'Successfully removed'; + if (count === 0) response = 'Failed to remove'; + + // Reply to acknowledge command + await interaction.reply({ + content: `${response} channel from custom channel creation!`, + ephemeral: true + }); + + console.info(`[INFO] Removed custom VC with ID '${id}'.`); + break; + } + } + } catch (error) { + console.error(error); + + // Reply failed to acknowledge command + await interaction.reply({ + content: `Failed to ${step} channel!`, + ephemeral: true + }); + } +} diff --git a/commands/admin/self_roles/context/remove.js b/commands/admin/self_roles/context/remove.js index 497aa8b..75fc6ca 100644 --- a/commands/admin/self_roles/context/remove.js +++ b/commands/admin/self_roles/context/remove.js @@ -1,11 +1,11 @@ -import { removeSelfRoles } from '../../../../shared.js'; -import { ApplicationCommandType, ContextMenuCommandBuilder } from 'discord.js'; - -export const data = new ContextMenuCommandBuilder() - .setDMPermission(false) - .setName('Remove self roles') - .setType(ApplicationCommandType.Message); -export async function execute(interaction) { - const id = interaction.targetMessage.id; - await removeSelfRoles(interaction, id); -} +import { removeSelfRoles } from '../../../../shared.js'; +import { ApplicationCommandType, ContextMenuCommandBuilder } from 'discord.js'; + +export const data = new ContextMenuCommandBuilder() + .setDMPermission(false) + .setName('Remove self roles') + .setType(ApplicationCommandType.Message); +export async function execute(interaction) { + const id = interaction.targetMessage.id; + await removeSelfRoles(interaction, id); +} diff --git a/events/channels/channelDelete.js b/events/channels/channelDelete.js index 6564da4..b3494b5 100644 --- a/events/channels/channelDelete.js +++ b/events/channels/channelDelete.js @@ -1,14 +1,13 @@ -import { Events } from 'discord.js'; -import { VoiceChannel } from '../../database.js'; - -export const name = Events.ChannelDelete; -export async function execute(channel) { - // Delete channel entry once channel is deleted itself - const count = await VoiceChannel.destroy({ - where: { - id: channel.id - } - }); - if (count > 0) - console.info(`[INFO] Custom VC with ID '${channel.id}' was deleted.`); -} +import { Events } from 'discord.js'; +import { VoiceChannel } from '../../database.js'; + +export const name = Events.ChannelDelete; +export async function execute(channel) { + // Delete channel entry once channel is deleted itself + const count = await VoiceChannel.destroy({ + where: { + id: channel.id + } + }); + if (count > 0) console.info(`[INFO] Custom VC with ID '${channel.id}' was deleted.`); +} diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index f14694f..616349d 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -47,7 +47,7 @@ const getChannel = async (member, channels) => { await VoiceChannel.create({ id: privCh.id, owner: member.user.id - }) + }); return privCh; }; @@ -78,14 +78,13 @@ const leftVoiceChat = async (state) => { export const name = Events.VoiceStateUpdate; export async function execute(oldState, newState) { - if (!newState.channel) - return await leftVoiceChat(oldState); + if (!newState.channel) return await leftVoiceChat(oldState); // Find channel by id, return if not registered for customs const createCh = await VoiceChannel.findOne({ where: { id: newState.channel.id, - create: true, + create: true } }); if (createCh === null) return; diff --git a/events/messages/messageDelete.js b/events/messages/messageDelete.js index 3975c95..2f26198 100644 --- a/events/messages/messageDelete.js +++ b/events/messages/messageDelete.js @@ -1,14 +1,13 @@ -import { Events } from 'discord.js'; -import { Message } from '../../database.js'; - -export const name = Events.MessageDelete; -export async function execute(message) { - // Delete message entry once message is deleted itself - const count = await Message.destroy({ - where: { - id: message.id - } - }); - if (count > 0) - console.info(`[INFO] Reaction Roles Message with ID '${message.id}' was deleted.`); -} +import { Events } from 'discord.js'; +import { Message } from '../../database.js'; + +export const name = Events.MessageDelete; +export async function execute(message) { + // Delete message entry once message is deleted itself + const count = await Message.destroy({ + where: { + id: message.id + } + }); + if (count > 0) console.info(`[INFO] Reaction Roles Message with ID '${message.id}' was deleted.`); +} diff --git a/models/voiceChannels.js b/models/voiceChannels.js index db8057b..f1149c7 100644 --- a/models/voiceChannels.js +++ b/models/voiceChannels.js @@ -1,6 +1,6 @@ import { DataTypes } from 'sequelize'; -export default function(sequelize) { +export default function (sequelize) { return sequelize.define('VoiceChannel', { id: { type: DataTypes.STRING, diff --git a/shared.js b/shared.js index 1ead0ea..5a09112 100644 --- a/shared.js +++ b/shared.js @@ -13,8 +13,7 @@ export const removeSelfRoles = async (interaction, id) => { // Set reply based on result of deletion let response = 'Successfully removed'; - if (count === 0) - response = 'Failed to remove'; + if (count === 0) response = 'Failed to remove'; // Reply to acknowledge command await interaction.reply({ From 73de6dbcecb344facb79724ef9141f341ecd84c7 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 9 Feb 2024 16:30:58 +0100 Subject: [PATCH 076/133] bugfix: simple logic patches --- events/channels/channelDelete.js | 6 ++++-- events/channels/voiceStateUpdate.js | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/events/channels/channelDelete.js b/events/channels/channelDelete.js index b3494b5..78e253f 100644 --- a/events/channels/channelDelete.js +++ b/events/channels/channelDelete.js @@ -1,13 +1,15 @@ -import { Events } from 'discord.js'; +import { ChannelType, Events } from 'discord.js'; import { VoiceChannel } from '../../database.js'; export const name = Events.ChannelDelete; export async function execute(channel) { + if (channel.type !== ChannelType.GuildVoice) return; + // Delete channel entry once channel is deleted itself const count = await VoiceChannel.destroy({ where: { id: channel.id } }); - if (count > 0) console.info(`[INFO] Custom VC with ID '${channel.id}' was deleted.`); + if (count > 0) console.info(`[INFO] Custom VC entry with ID '${channel.id}' was destroyed.`); } diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index 616349d..3b6b614 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -11,7 +11,6 @@ const vcPermissionOverwrites = [ PermissionFlagsBits.ViewChannel, PermissionFlagsBits.MuteMembers, PermissionFlagsBits.MoveMembers, - PermissionFlagsBits.ManageRoles, PermissionFlagsBits.Connect, PermissionFlagsBits.Stream, PermissionFlagsBits.UseVAD, @@ -78,7 +77,8 @@ const leftVoiceChat = async (state) => { export const name = Events.VoiceStateUpdate; export async function execute(oldState, newState) { - if (!newState.channel) return await leftVoiceChat(oldState); + await leftVoiceChat(oldState) + if (!newState.channel) return; // Find channel by id, return if not registered for customs const createCh = await VoiceChannel.findOne({ From 3a2073f61f0ac1683e47fcd1be3c8ca344287370 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 9 Feb 2024 19:39:49 +0100 Subject: [PATCH 077/133] edit owned messages to include pair explainations --- shared.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/shared.js b/shared.js index 5a09112..7bf028d 100644 --- a/shared.js +++ b/shared.js @@ -1,8 +1,11 @@ import { join } from 'path'; import { Op } from 'sequelize'; +import { config } from 'dotenv'; import { readdir } from 'fs/promises'; import { Message, RoleEmojiPair } from './database.js'; +config(); + export const removeSelfRoles = async (interaction, id) => { // Try deleting message from database const count = await Message.destroy({ @@ -53,6 +56,23 @@ const saveMessageData = async (id, role, emoji) => { await RoleEmojiPair.create({ message: id, role: role.id, emoji }); }; +const editMessage = async (message, role, emoji) => { + if (message.author.id !== process.env.CLIENT) return; + + // Find out whether to pad message or already present + let padding = '\n'; + const reps = await RoleEmojiPair.findAll({ where: { message: message.id } }); + if (reps.length === 0) padding += '\n'; + + // Get old and build new content of message + const current = message.content; + const next = current + padding + + `React with ${emoji} to receive <@&${role.id}>!`; + + // Set message by editing + await message.edit(next); +}; + export const addSelfRoles = async (interaction, msgID, role, emoji) => { const { channel } = interaction; @@ -61,6 +81,10 @@ export const addSelfRoles = async (interaction, msgID, role, emoji) => { // Get message by id const message = await channel.messages.fetch(msgID); + step = 'edit'; + // Try editing message to explain pair + await editMessage(message, role, emoji); + step = 'save data from'; await saveMessageData(msgID, role, emoji); From 746341d48a9e3deb73a7bb18ce7f17a7d3b4b61e Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 9 Feb 2024 19:42:27 +0100 Subject: [PATCH 078/133] refactor: auto format --- events/channels/voiceStateUpdate.js | 2 +- shared.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index 3b6b614..34f8d08 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -77,7 +77,7 @@ const leftVoiceChat = async (state) => { export const name = Events.VoiceStateUpdate; export async function execute(oldState, newState) { - await leftVoiceChat(oldState) + await leftVoiceChat(oldState); if (!newState.channel) return; // Find channel by id, return if not registered for customs diff --git a/shared.js b/shared.js index 7bf028d..bcec0a2 100644 --- a/shared.js +++ b/shared.js @@ -66,8 +66,7 @@ const editMessage = async (message, role, emoji) => { // Get old and build new content of message const current = message.content; - const next = current + padding + - `React with ${emoji} to receive <@&${role.id}>!`; + const next = current + padding + `React with ${emoji} to receive <@&${role.id}>!`; // Set message by editing await message.edit(next); From 1ebe30f981f8997b51be516be1b11df8dc15f3b6 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 9 Feb 2024 19:55:24 +0100 Subject: [PATCH 079/133] set invite in README with perms for current state --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a37f3da..88f13c8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ ## Bot invite -link [here](https://discord.com/api/oauth2/authorize?client_id=1203887026282438686&permissions=8&scope=applications.commands+bot). +link [here](https://discord.com/api/oauth2/authorize?client_id=1203887026282438686&permissions=275212480336&scope=bot+applications.commands). From 5e43e9ba18e5b8a2d85a2314188cd24151c6eeea Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 9 Feb 2024 19:56:12 +0100 Subject: [PATCH 080/133] version upgrade: first alpha version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a927082..fde8e1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-bot", - "version": "0.1.0", + "version": "0.2.0", "description": "", "private": true, "main": "index.js", From 13497ebf43431a73008685e208cfb864068cf226 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 9 Feb 2024 21:23:40 +0100 Subject: [PATCH 081/133] place explaination in README --- README.md | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 88f13c8..f8509c5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,136 @@ -## Bot invite +# Getting started +Discord utilizes different types of commands to interface with bots. The following explanations aim to explain these types. -link [here](https://discord.com/api/oauth2/authorize?client_id=1203887026282438686&permissions=275212480336&scope=bot+applications.commands). +## Slash Commands +Simple text-based commands. Can be entered in any Text-Based-Channel. + +Prefix message with a "Slash" (``/``) and begin typing the name of the command! This prefix and the styling as a code snippet will be used to highlight Slash Commands in this message. + +For clarification, this will also highlight command options as "Slash Command only" by prefixing them with an underscore (``_``) + +## Context Menu Commands +"One click" commands. Listed under context (right click) menus, under the group "Apps". + +Ideally, clicking on Users or Messages, you can apply any type of command with the press of a button. Comes with the downside of lacking further input capabilities. + +Under the list of names of any given command, there may be a quoted string. These are meant to highlight context menu capabilities for the current command, by showcasing the commands name in the quote. + + + +# Reaction/Self Roles +## Name +``/self_roles`` + +## Description +Enables users to self assign roles by reacting to a message, utilizing a bot for automated tasks + +## Subcommands +### create +Full names: +- ``/self_roles create`` + +Description: +The bot writes a message with the given contents in the current channel, and then registers said message for self roles + +Options: +- _text: String + +### register +Full names: +- ``/self_roles register`` +- "Register self roles" + +Description: +Fetches any existing message and registers it for self roles directly + +Options: +- _id: Discord Message ID + +### remove +Full names: +- ``/self_roles remove`` +- "Remove self roles" + +Description: +Fetches any existing message, looks if it is registered for self roles and then unregisters it + +Options: +- _id: Discord Message ID + +### add +Full names: +- ``/self_roles add`` +- "Add role emoji pair" + +Description: +Adds a new Role-Emoji-Pair to a given message. Reacting to said message with the given emoji yields the given role + +Options: +- _id: Discord Message ID +- role: Discord Role +- emoji: Unicode [or if accessible, a custom] Emoji + +## Automated tasks +Registered Messages will automatically: +- be unregistered during deletion +- be edited in case the bot owns said message. It will explain the given Role-Emoji-Pairs every time a new pair is added. + + +# Custom Voice Chat +## Name +``/custom_vc`` + +## Description +Enables users to create a custom, temporary voice chat with full control over it + +## Subcommands +### create +Full name: +``/custom_vc create`` + +Description: +The bot creates a new voice channel with the given name and registers said channel for custom VC creation + +Options: +- _name: String + +### register +Full names: +- ``/custom_vc register`` + +Description: +Fetches any existing voice channel and registers it for custom VC creation directly + +Options: +- _id: Discord Channel ID + +### remove +Full names: +- ``/custom_vc remove`` + +Description: +Fetches any voice channel, looks if it is registered for custom VC creation and then unregisters it + +Options: +- _id: Discord Channel ID + +## Automated tasks +Registered Voice Channels will automatically: +- be unregistered during deletion +- be deleted once all users left said channel +- grant full control over permissions, only within said channel, to the author + + + +# TO-DO List: +**NOTE:** These lists can and will easily be appended to in the future. Feel free to give any feedback at all and add to the list of wanted features, since this could make for many great opportunities! The bot is being developed just for this one community after all! + +## Planned features +- Automated alerts/reminders +- Removing pairs on reaction/self roles + +## Quirks/Bugs +- Users are currently unable to overwrite permissions of temporarily generated custom VCs + +## Changes +None From 78cdf79fc4fc0d3515b32a7f210784f7dd04c97c Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 9 Feb 2024 21:32:56 +0100 Subject: [PATCH 082/133] refactor: auto format --- README.md | 80 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index f8509c5..506af2e 100644 --- a/README.md +++ b/README.md @@ -1,136 +1,170 @@ # Getting started + Discord utilizes different types of commands to interface with bots. The following explanations aim to explain these types. ## Slash Commands + Simple text-based commands. Can be entered in any Text-Based-Channel. -Prefix message with a "Slash" (``/``) and begin typing the name of the command! This prefix and the styling as a code snippet will be used to highlight Slash Commands in this message. +Prefix message with a "Slash" (`/`) and begin typing the name of the command! This prefix and the styling as a code snippet will be used to highlight Slash Commands in this message. -For clarification, this will also highlight command options as "Slash Command only" by prefixing them with an underscore (``_``) +For clarification, this will also highlight command options as "Slash Command only" by prefixing them with an underscore (`_`) ## Context Menu Commands + "One click" commands. Listed under context (right click) menus, under the group "Apps". Ideally, clicking on Users or Messages, you can apply any type of command with the press of a button. Comes with the downside of lacking further input capabilities. Under the list of names of any given command, there may be a quoted string. These are meant to highlight context menu capabilities for the current command, by showcasing the commands name in the quote. - - # Reaction/Self Roles + ## Name -``/self_roles`` + +`/self_roles` ## Description + Enables users to self assign roles by reacting to a message, utilizing a bot for automated tasks ## Subcommands + ### create + Full names: -- ``/self_roles create`` + +- `/self_roles create` Description: The bot writes a message with the given contents in the current channel, and then registers said message for self roles Options: -- _text: String + +- \_text: String ### register + Full names: -- ``/self_roles register`` + +- `/self_roles register` - "Register self roles" Description: Fetches any existing message and registers it for self roles directly Options: -- _id: Discord Message ID + +- \_id: Discord Message ID ### remove + Full names: -- ``/self_roles remove`` + +- `/self_roles remove` - "Remove self roles" Description: Fetches any existing message, looks if it is registered for self roles and then unregisters it Options: -- _id: Discord Message ID + +- \_id: Discord Message ID ### add + Full names: -- ``/self_roles add`` + +- `/self_roles add` - "Add role emoji pair" Description: Adds a new Role-Emoji-Pair to a given message. Reacting to said message with the given emoji yields the given role Options: -- _id: Discord Message ID + +- \_id: Discord Message ID - role: Discord Role - emoji: Unicode [or if accessible, a custom] Emoji ## Automated tasks + Registered Messages will automatically: + - be unregistered during deletion - be edited in case the bot owns said message. It will explain the given Role-Emoji-Pairs every time a new pair is added. - # Custom Voice Chat + ## Name -``/custom_vc`` + +`/custom_vc` ## Description + Enables users to create a custom, temporary voice chat with full control over it ## Subcommands + ### create + Full name: -``/custom_vc create`` +`/custom_vc create` Description: The bot creates a new voice channel with the given name and registers said channel for custom VC creation Options: -- _name: String + +- \_name: String ### register + Full names: -- ``/custom_vc register`` + +- `/custom_vc register` Description: Fetches any existing voice channel and registers it for custom VC creation directly Options: -- _id: Discord Channel ID + +- \_id: Discord Channel ID ### remove + Full names: -- ``/custom_vc remove`` + +- `/custom_vc remove` Description: Fetches any voice channel, looks if it is registered for custom VC creation and then unregisters it Options: -- _id: Discord Channel ID + +- \_id: Discord Channel ID ## Automated tasks + Registered Voice Channels will automatically: + - be unregistered during deletion - be deleted once all users left said channel - grant full control over permissions, only within said channel, to the author - - # TO-DO List: + **NOTE:** These lists can and will easily be appended to in the future. Feel free to give any feedback at all and add to the list of wanted features, since this could make for many great opportunities! The bot is being developed just for this one community after all! ## Planned features + - Automated alerts/reminders - Removing pairs on reaction/self roles ## Quirks/Bugs + - Users are currently unable to overwrite permissions of temporarily generated custom VCs ## Changes + None From b5440ae98dee994145a628c8be627a38c15ed148 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 9 Feb 2024 21:36:44 +0100 Subject: [PATCH 083/133] unify formatting --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 506af2e..739d7a5 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Full names: - `/self_roles create` Description: + The bot writes a message with the given contents in the current channel, and then registers said message for self roles Options: @@ -51,6 +52,7 @@ Full names: - "Register self roles" Description: + Fetches any existing message and registers it for self roles directly Options: @@ -65,6 +67,7 @@ Full names: - "Remove self roles" Description: + Fetches any existing message, looks if it is registered for self roles and then unregisters it Options: @@ -79,6 +82,7 @@ Full names: - "Add role emoji pair" Description: + Adds a new Role-Emoji-Pair to a given message. Reacting to said message with the given emoji yields the given role Options: @@ -108,8 +112,9 @@ Enables users to create a custom, temporary voice chat with full control over it ### create -Full name: -`/custom_vc create` +Full names: + +- `/custom_vc create` Description: The bot creates a new voice channel with the given name and registers said channel for custom VC creation From 4ab74597b7656b6b9497014549984227a6323c0e Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 9 Feb 2024 21:39:19 +0100 Subject: [PATCH 084/133] better note format --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 739d7a5..cfb4ace 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,9 @@ Registered Voice Channels will automatically: # TO-DO List: -**NOTE:** These lists can and will easily be appended to in the future. Feel free to give any feedback at all and add to the list of wanted features, since this could make for many great opportunities! The bot is being developed just for this one community after all! + +> **NOTE** +> These lists can and will easily be appended to in the future. Any and all feedback is greatly appreciated! ## Planned features From 4e3cfacfa445f77bfd731af3437473a44ec2328d Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 9 Feb 2024 22:41:58 +0100 Subject: [PATCH 085/133] bugfix: default permissions for members to use commands --- commands/admin/custom_vc/slash.js | 3 ++- commands/admin/self_roles/context/add.js | 5 +++-- commands/admin/self_roles/context/register.js | 5 +++-- commands/admin/self_roles/context/remove.js | 5 +++-- commands/admin/self_roles/slash.js | 3 ++- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/commands/admin/custom_vc/slash.js b/commands/admin/custom_vc/slash.js index 8b612c6..36b1b2e 100644 --- a/commands/admin/custom_vc/slash.js +++ b/commands/admin/custom_vc/slash.js @@ -1,10 +1,11 @@ -import { ChannelType, SlashCommandBuilder } from 'discord.js'; +import { ChannelType, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js'; import { VoiceChannel } from '../../../database.js'; export const data = new SlashCommandBuilder() .setName('custom_vc') .setDMPermission(false) .setDescription('Manages reactions for self roles.') + .setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels) .addSubcommand((subcommand) => subcommand .setName('create') diff --git a/commands/admin/self_roles/context/add.js b/commands/admin/self_roles/context/add.js index f45babc..3456acf 100644 --- a/commands/admin/self_roles/context/add.js +++ b/commands/admin/self_roles/context/add.js @@ -1,4 +1,4 @@ -import { TextInputBuilder, TextInputStyle } from 'discord.js'; +import { PermissionFlagsBits, TextInputBuilder, TextInputStyle } from 'discord.js'; import { ModalBuilder, ActionRowBuilder, @@ -10,7 +10,8 @@ import { addSelfRoles } from '../../../../shared.js'; export const data = new ContextMenuCommandBuilder() .setDMPermission(false) .setName('Add role emoji pair') - .setType(ApplicationCommandType.Message); + .setType(ApplicationCommandType.Message) + .setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles); export async function modalSubmit(interaction) { const { fields, guild } = interaction; // Get text inputs from modal diff --git a/commands/admin/self_roles/context/register.js b/commands/admin/self_roles/context/register.js index ece08c0..5612b99 100644 --- a/commands/admin/self_roles/context/register.js +++ b/commands/admin/self_roles/context/register.js @@ -1,10 +1,11 @@ import { Message } from '../../../../database.js'; -import { ApplicationCommandType, ContextMenuCommandBuilder } from 'discord.js'; +import { ApplicationCommandType, ContextMenuCommandBuilder, PermissionFlagsBits } from 'discord.js'; export const data = new ContextMenuCommandBuilder() .setDMPermission(false) .setName('Register self roles') - .setType(ApplicationCommandType.Message); + .setType(ApplicationCommandType.Message) + .setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles); export async function execute(interaction) { const id = interaction.targetMessage.id; diff --git a/commands/admin/self_roles/context/remove.js b/commands/admin/self_roles/context/remove.js index 75fc6ca..c091368 100644 --- a/commands/admin/self_roles/context/remove.js +++ b/commands/admin/self_roles/context/remove.js @@ -1,10 +1,11 @@ import { removeSelfRoles } from '../../../../shared.js'; -import { ApplicationCommandType, ContextMenuCommandBuilder } from 'discord.js'; +import { ApplicationCommandType, ContextMenuCommandBuilder, PermissionFlagsBits } from 'discord.js'; export const data = new ContextMenuCommandBuilder() .setDMPermission(false) .setName('Remove self roles') - .setType(ApplicationCommandType.Message); + .setType(ApplicationCommandType.Message) + .setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles); export async function execute(interaction) { const id = interaction.targetMessage.id; await removeSelfRoles(interaction, id); diff --git a/commands/admin/self_roles/slash.js b/commands/admin/self_roles/slash.js index c539059..e977007 100644 --- a/commands/admin/self_roles/slash.js +++ b/commands/admin/self_roles/slash.js @@ -1,5 +1,5 @@ import { addSelfRoles } from '../../../shared.js'; -import { SlashCommandBuilder } from 'discord.js'; +import { PermissionFlagsBits, SlashCommandBuilder } from 'discord.js'; import { Message } from '../../../database.js'; const createSelfRoles = async (interaction) => { @@ -73,6 +73,7 @@ export const data = new SlashCommandBuilder() .setName('self_roles') .setDMPermission(false) .setDescription('Manages reactions for self roles.') + .setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles) .addSubcommand((subcommand) => subcommand .setName('create') From daef590b83753d3bcd85d78cb38a73ce668e5280 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 9 Feb 2024 22:59:11 +0100 Subject: [PATCH 086/133] bugfix: attach to same parent --- events/channels/voiceStateUpdate.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index 34f8d08..f75a0c1 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -17,7 +17,7 @@ const vcPermissionOverwrites = [ PermissionFlagsBits.Speak ]; -const getChannel = async (member, channels) => { +const getChannel = async (member, guildChs, channel) => { // Check database for existing channel const ownCh = await VoiceChannel.findOne({ where: { @@ -25,14 +25,15 @@ const getChannel = async (member, channels) => { } }); if (ownCh !== null) { - return await channels.fetch(ownCh.id); + return await guildChs.fetch(ownCh.id); } // Create private channel with all permissions const name = member.user.username; const chName = `${name}${name.toLowerCase().endsWith('s') ? "'" : "'s"} channel`; - const privCh = await channels.create({ + const privCh = await guildChs.create({ name: chName, + parent: channel.parent, type: ChannelType.GuildVoice, permissionOverwrites: [ { @@ -77,13 +78,14 @@ const leftVoiceChat = async (state) => { export const name = Events.VoiceStateUpdate; export async function execute(oldState, newState) { + const { channel } = newState await leftVoiceChat(oldState); - if (!newState.channel) return; + if (!channel) return; // Find channel by id, return if not registered for customs const createCh = await VoiceChannel.findOne({ where: { - id: newState.channel.id, + id: channel.id, create: true } }); @@ -96,7 +98,7 @@ export async function execute(oldState, newState) { const channels = newState.guild.channels; let step = 'create'; try { - const privCh = await getChannel(member, channels); + const privCh = await getChannel(member, channels, channel); step = 'move to'; // Move user to private channel From a14298fa6621b96532bcc7ffc71a6a2ad58306e6 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sat, 10 Feb 2024 22:00:17 +0100 Subject: [PATCH 087/133] update packages --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3b9f3b0..8ad1455 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "discord-bot", - "version": "0.0.1", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "discord-bot", - "version": "0.0.1", + "version": "0.2.0", "license": "ISC", "dependencies": { "discord.js": "^14.14.1", From 0edfd41c1324a099ceb56d0af619b23a2a5da1a9 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sat, 10 Feb 2024 22:00:34 +0100 Subject: [PATCH 088/133] initialize JSDoc --- .jsdoc.conf.json | 7 +++++++ package.json | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .jsdoc.conf.json diff --git a/.jsdoc.conf.json b/.jsdoc.conf.json new file mode 100644 index 0000000..e7d5e38 --- /dev/null +++ b/.jsdoc.conf.json @@ -0,0 +1,7 @@ +{ + "source": { + "include": ["."], + "includePattern": ".+\\.js(doc|x)?$", + "excludePattern": "node_modules" + } +} diff --git a/package.json b/package.json index fde8e1f..349bbf7 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "start": "node .", "deploy": "node deploy.js", "lint": "prettier --check . && eslint .", - "format": "prettier --write ." + "format": "prettier --write .", + "jsdoc": "jsdoc -c ./.jsdoc.conf.json -d docs/ -r ." }, "author": "", "license": "ISC", From fe211b70552abd52848d9027a80052dd3cd234f9 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sat, 10 Feb 2024 22:10:51 +0100 Subject: [PATCH 089/133] ignore JSDoc output --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4d3c332..21333c0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ node_modules !.env.example commands/examples *.sqlite +docs From d7bef27f52cb0857a5db9fb3670f42fac99e5f2d Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sat, 10 Feb 2024 22:16:33 +0100 Subject: [PATCH 090/133] better JSDoc config --- .jsdoc.conf.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.jsdoc.conf.json b/.jsdoc.conf.json index e7d5e38..9c02b16 100644 --- a/.jsdoc.conf.json +++ b/.jsdoc.conf.json @@ -1,7 +1,10 @@ { "source": { "include": ["."], - "includePattern": ".+\\.js(doc|x)?$", - "excludePattern": "node_modules" + "exclude": [ + "node_modules", + "commands/examples" + ], + "includePattern": ".+\\.js(doc|x)?$" } } From 14a63d43d1386ed2cc4c6f8273744bd6dede8d6c Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 11 Feb 2024 01:59:57 +0100 Subject: [PATCH 091/133] clean up: auto format --- .jsdoc.conf.json | 17 +++++++---------- README.md | 1 - 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/.jsdoc.conf.json b/.jsdoc.conf.json index 9c02b16..e1be6b8 100644 --- a/.jsdoc.conf.json +++ b/.jsdoc.conf.json @@ -1,10 +1,7 @@ -{ - "source": { - "include": ["."], - "exclude": [ - "node_modules", - "commands/examples" - ], - "includePattern": ".+\\.js(doc|x)?$" - } -} +{ + "source": { + "include": ["."], + "exclude": ["node_modules", "commands/examples"], + "includePattern": ".+\\.js(doc|x)?$" + } +} diff --git a/README.md b/README.md index cfb4ace..a7c2533 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,6 @@ Registered Voice Channels will automatically: # TO-DO List: - > **NOTE** > These lists can and will easily be appended to in the future. Any and all feedback is greatly appreciated! From 04bbb7e376e9f2ff2dc3a5af6beaf073a9b5a9d5 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 11 Feb 2024 02:00:16 +0100 Subject: [PATCH 092/133] update packages --- package-lock.json | 120 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + 2 files changed, 118 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ad1455..26fa17e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "devDependencies": { "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", + "eslint-plugin-jsdoc": "^48.0.6", "prettier": "^3.1.1" } }, @@ -127,6 +128,20 @@ "node": ">=18" } }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.42.0.tgz", + "integrity": "sha512-R1w57YlVA6+YE01wch3GPYn6bCsrOV3YW/5oGGE2tmX6JcL9Nr+b5IikrjMPF+v9CV3ay+obImEdsDhovhJrzw==", + "dev": true, + "dependencies": { + "comment-parser": "1.4.1", + "esquery": "^1.5.0", + "jsdoc-type-pratt-parser": "~4.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -476,6 +491,15 @@ "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "optional": true }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/are-we-there-yet": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", @@ -571,6 +595,18 @@ "ieee754": "^1.1.13" } }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cacache": { "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", @@ -681,6 +717,15 @@ "color-support": "bin.js" } }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -939,6 +984,29 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-jsdoc": { + "version": "48.0.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.0.6.tgz", + "integrity": "sha512-LgwXOX6TWxxFYcbdVe+BJ94Kl/pgjSPYHLzqEdAMXTA1BH9WDx7iJ+9/iDajPF64LtzWX8C1mCfpbMZjJGhAOw==", + "dev": true, + "dependencies": { + "@es-joy/jsdoccomment": "~0.42.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.3.4", + "escape-string-regexp": "^4.0.0", + "esquery": "^1.5.0", + "is-builtin-module": "^3.2.1", + "semver": "^7.6.0", + "spdx-expression-parse": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -1356,6 +1424,21 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1419,6 +1502,15 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -2191,9 +2283,9 @@ "optional": true }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -2390,6 +2482,28 @@ "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", "optional": true }, + "node_modules/spdx-exceptions": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.4.0.tgz", + "integrity": "sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "dev": true + }, "node_modules/sqlite3": { "version": "5.1.7", "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", diff --git a/package.json b/package.json index 349bbf7..af9be62 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "devDependencies": { "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", + "eslint-plugin-jsdoc": "^48.0.6", "prettier": "^3.1.1" }, "type": "module" From 2a33dea919bf95ec7fec32adc0923a1d4ccb1a7f Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 11 Feb 2024 02:00:30 +0100 Subject: [PATCH 093/133] configure eslint to use jsdoc --- .eslintrc.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 85d1353..6499a8f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,5 +8,8 @@ "ecmaVersion": "latest", "sourceType": "module" }, - "rules": {} + "plugins": ["jsdoc"], + "rules": { + "jsdoc/no-undefined-types": 1 + } } From ffa4d43d8fac22c134deb7a190e03ea3ed17f048 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 11 Feb 2024 02:00:47 +0100 Subject: [PATCH 094/133] ignore jsdoc output for prettier and eslint --- .eslintignore | 1 + .prettierignore | 1 + 2 files changed, 2 insertions(+) diff --git a/.eslintignore b/.eslintignore index df2ca4a..f160f46 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,6 +4,7 @@ node_modules .env .env.* !.env.example +docs # Ignore files for PNPM, NPM and YARN pnpm-lock.yaml diff --git a/.prettierignore b/.prettierignore index df2ca4a..f160f46 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,7 @@ node_modules .env .env.* !.env.example +docs # Ignore files for PNPM, NPM and YARN pnpm-lock.yaml From 80d4693c3db171c00b55b841fe01a9496a10066b Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 11 Feb 2024 02:04:12 +0100 Subject: [PATCH 095/133] document code with jsdoc comments --- commands/admin/custom_vc/slash.js | 9 +++- commands/admin/self_roles/context/add.js | 10 ++++- commands/admin/self_roles/context/register.js | 8 +++- commands/admin/self_roles/context/remove.js | 8 +++- commands/admin/self_roles/slash.js | 36 ++++++++++++--- database.js | 1 + deploy.js | 10 +++-- events/channels/channelDelete.js | 3 +- events/channels/voiceStateUpdate.js | 27 ++++++++++- events/interactionCreate.js | 16 +++++++ events/messages/messageDelete.js | 3 +- events/messages/reactionAdd.js | 8 +++- events/messages/reactionRemove.js | 8 +++- events/ready.js | 3 +- index.js | 16 ++++++- models/messages.js | 14 +++++- models/roleEmojiPairs.js | 15 ++++++- models/voiceChannels.js | 14 +++++- shared.js | 45 +++++++++++++++++-- 19 files changed, 222 insertions(+), 32 deletions(-) diff --git a/commands/admin/custom_vc/slash.js b/commands/admin/custom_vc/slash.js index 36b1b2e..5692a92 100644 --- a/commands/admin/custom_vc/slash.js +++ b/commands/admin/custom_vc/slash.js @@ -1,4 +1,9 @@ -import { ChannelType, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js'; +import { + ChannelType, + PermissionFlagsBits, + SlashCommandBuilder, + ChatInputCommandInteraction +} from 'discord.js'; import { VoiceChannel } from '../../../database.js'; export const data = new SlashCommandBuilder() @@ -41,9 +46,11 @@ export const data = new SlashCommandBuilder() .setDescription('The voice channel to be unregistered.') ) ); +/** @param {ChatInputCommandInteraction} interaction */ export async function execute(interaction) { const { guild, options } = interaction; + /** @type {string} */ let step; try { switch (options.getSubcommand()) { diff --git a/commands/admin/self_roles/context/add.js b/commands/admin/self_roles/context/add.js index 3456acf..c5c8966 100644 --- a/commands/admin/self_roles/context/add.js +++ b/commands/admin/self_roles/context/add.js @@ -1,9 +1,13 @@ -import { PermissionFlagsBits, TextInputBuilder, TextInputStyle } from 'discord.js'; import { ModalBuilder, + TextInputStyle, ActionRowBuilder, + TextInputBuilder, + PermissionFlagsBits, + ModalSubmitInteraction, ApplicationCommandType, - ContextMenuCommandBuilder + ContextMenuCommandBuilder, + ContextMenuCommandInteraction } from 'discord.js'; import { addSelfRoles } from '../../../../shared.js'; @@ -12,6 +16,7 @@ export const data = new ContextMenuCommandBuilder() .setName('Add role emoji pair') .setType(ApplicationCommandType.Message) .setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles); +/** @param {ModalSubmitInteraction} interaction */ export async function modalSubmit(interaction) { const { fields, guild } = interaction; // Get text inputs from modal @@ -32,6 +37,7 @@ export async function modalSubmit(interaction) { await addSelfRoles(interaction, message, role, emoji); } +/** @param {ContextMenuCommandInteraction} interaction */ export async function execute(interaction) { const modal = new ModalBuilder() .setCustomId('Add role emoji pair-pair') diff --git a/commands/admin/self_roles/context/register.js b/commands/admin/self_roles/context/register.js index 5612b99..70df442 100644 --- a/commands/admin/self_roles/context/register.js +++ b/commands/admin/self_roles/context/register.js @@ -1,11 +1,17 @@ +import { + ApplicationCommandType, + ContextMenuCommandBuilder, + PermissionFlagsBits, + ContextMenuCommandInteraction +} from 'discord.js'; import { Message } from '../../../../database.js'; -import { ApplicationCommandType, ContextMenuCommandBuilder, PermissionFlagsBits } from 'discord.js'; export const data = new ContextMenuCommandBuilder() .setDMPermission(false) .setName('Register self roles') .setType(ApplicationCommandType.Message) .setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles); +/** @param {ContextMenuCommandInteraction} interaction */ export async function execute(interaction) { const id = interaction.targetMessage.id; diff --git a/commands/admin/self_roles/context/remove.js b/commands/admin/self_roles/context/remove.js index c091368..ee038c4 100644 --- a/commands/admin/self_roles/context/remove.js +++ b/commands/admin/self_roles/context/remove.js @@ -1,11 +1,17 @@ +import { + ApplicationCommandType, + ContextMenuCommandBuilder, + PermissionFlagsBits, + ContextMenuCommandInteraction +} from 'discord.js'; import { removeSelfRoles } from '../../../../shared.js'; -import { ApplicationCommandType, ContextMenuCommandBuilder, PermissionFlagsBits } from 'discord.js'; export const data = new ContextMenuCommandBuilder() .setDMPermission(false) .setName('Remove self roles') .setType(ApplicationCommandType.Message) .setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles); +/** @param {ContextMenuCommandInteraction} interaction */ export async function execute(interaction) { const id = interaction.targetMessage.id; await removeSelfRoles(interaction, id); diff --git a/commands/admin/self_roles/slash.js b/commands/admin/self_roles/slash.js index e977007..3e12b59 100644 --- a/commands/admin/self_roles/slash.js +++ b/commands/admin/self_roles/slash.js @@ -1,7 +1,12 @@ -import { addSelfRoles } from '../../../shared.js'; -import { PermissionFlagsBits, SlashCommandBuilder } from 'discord.js'; +import { PermissionFlagsBits, SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js'; +import { addSelfRoles, removeSelfRoles } from '../../../shared.js'; import { Message } from '../../../database.js'; +/** + * Sends a `Message` in the current channel and registers for self roles. + * @param {ChatInputCommandInteraction} interaction + * @returns {string} + */ const createSelfRoles = async (interaction) => { const { options, channel } = interaction; @@ -16,6 +21,17 @@ const createSelfRoles = async (interaction) => { return id; }; +/** + * @typedef {Object} SelfRoleResponse + * @property {boolean} success Whether or not the operation was successful. + * @property {string|null} msgID A Discord message ID, if successful. + */ + +/** + * Registers a `Message` for self roles. + * @param {ChatInputCommandInteraction} interaction + * @returns {Promise} + */ const registerSelfRoles = async (interaction) => { const { options, channel } = interaction; const id = options.getString('id'); @@ -50,7 +66,12 @@ const registerSelfRoles = async (interaction) => { return response; }; -const removeSelfRoles = async (interaction, msgID) => { +/** + * Main logic of the 'Self Roles remove' slash command to remove the functionality from a `Message`. + * @param {ChatInputCommandInteraction} interaction + * @param {string} msgID A Discord message ID. + */ +const removeReactionRoles = async (interaction, msgID) => { const { channel } = interaction; try { @@ -66,6 +87,7 @@ const removeSelfRoles = async (interaction, msgID) => { return; } + // Call shared method for further logic await removeSelfRoles(interaction, msgID); }; @@ -124,11 +146,13 @@ export const data = new SlashCommandBuilder() .setDescription('The ID to reference the message to be removed.') ) ); +/** @param {ChatInputCommandInteraction} interaction */ export async function execute(interaction) { const { options } = interaction; - let createNew = false, - id; + /** @type {string=} */ + let id; + let createNew = false; switch (options.getSubcommand()) { case 'create': id = await createSelfRoles(interaction); @@ -153,7 +177,7 @@ export async function execute(interaction) { } case 'remove': { const msgID = options.getString('id'); - await removeSelfRoles(interaction, msgID); + await removeReactionRoles(interaction, msgID); break; } } diff --git a/database.js b/database.js index c4e1222..9c13374 100644 --- a/database.js +++ b/database.js @@ -6,6 +6,7 @@ import { config } from 'dotenv'; config(); const { DB_NAME } = process.env; +/** The database instance used as an ORM in this project. */ const sequelize = new Sequelize({ storage: `${DB_NAME}.sqlite`, dialect: 'sqlite', diff --git a/deploy.js b/deploy.js index 458e210..3ad4320 100644 --- a/deploy.js +++ b/deploy.js @@ -3,13 +3,17 @@ import { REST, Routes } from 'discord.js'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import { config } from 'dotenv'; +import Module from 'module'; config(); // Construct and prepare an instance of the REST module const rest = new REST().setToken(process.env.TOKEN); -// and deploy your commands! +/** + * Calls HTTP PUT to register commands in discord. + * @param {Array} commands + */ const putCommands = async (commands) => { try { console.info(`[INFO] Started refreshing ${commands.length} application (/) commands.`); @@ -32,7 +36,7 @@ getFiles(cmdPath) // For each command file .then(async (files) => (await Promise.all(files.map(importAndCheck))) - .filter((module) => module !== 0) - .map((module) => module.data.toJSON()) + .filter(/** @param {(Module|0)} module */ (module) => module !== 0) + .map(/** @param {Module} module */ (module) => module.data.toJSON()) ) .then(putCommands); diff --git a/events/channels/channelDelete.js b/events/channels/channelDelete.js index 78e253f..89eb675 100644 --- a/events/channels/channelDelete.js +++ b/events/channels/channelDelete.js @@ -1,7 +1,8 @@ -import { ChannelType, Events } from 'discord.js'; +import { ChannelType, Events, GuildChannel } from 'discord.js'; import { VoiceChannel } from '../../database.js'; export const name = Events.ChannelDelete; +/** @param {GuildChannel} channel */ export async function execute(channel) { if (channel.type !== ChannelType.GuildVoice) return; diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index f75a0c1..88cfa50 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -1,4 +1,12 @@ -import { ChannelType, Events, PermissionFlagsBits } from 'discord.js'; +import { + ChannelType, + Events, + PermissionFlagsBits, + GuildMember, + GuildChannelManager, + GuildChannel, + VoiceState +} from 'discord.js'; import { VoiceChannel } from '../../database.js'; const vcPermissionOverwrites = [ @@ -17,6 +25,13 @@ const vcPermissionOverwrites = [ PermissionFlagsBits.Speak ]; +/** + * Function that either creates a new custom channel or gets an existing one registered in the database. + * @param {GuildMember} member The member that caused this event. + * @param {GuildChannelManager} guildChs All channels in this guild. + * @param {GuildChannel} channel The channel the member joined for this event to trigger. + * @returns {Promise} The channel, whether it's newly created or not. + */ const getChannel = async (member, guildChs, channel) => { // Check database for existing channel const ownCh = await VoiceChannel.findOne({ @@ -52,6 +67,10 @@ const getChannel = async (member, guildChs, channel) => { return privCh; }; +/** + * Function to delete the voice channel, if and only if the user is currently leaving and it was a custom channel. + * @param {VoiceState} state The previous voice state the user was in. + */ const leftVoiceChat = async (state) => { const { channel } = state; @@ -77,8 +96,12 @@ const leftVoiceChat = async (state) => { }; export const name = Events.VoiceStateUpdate; +/** + * @param {VoiceState} oldState + * @param {VoiceState} newState + */ export async function execute(oldState, newState) { - const { channel } = newState + const { channel } = newState; await leftVoiceChat(oldState); if (!channel) return; diff --git a/events/interactionCreate.js b/events/interactionCreate.js index 4771ecb..cf65eea 100644 --- a/events/interactionCreate.js +++ b/events/interactionCreate.js @@ -1,5 +1,11 @@ import { Events } from 'discord.js'; +import Module from 'module'; +/** + * A more precise execution function specifically to call the main property of a module. + * @param {import('discord.js').Interaction} interaction + * @param {Module} command + */ const executeCommand = async (interaction, command) => { // Try executing command try { @@ -21,6 +27,14 @@ const executeCommand = async (interaction, command) => { } }; +/** + * A generic execution function to call command methods. + * @param {import('discord.js').Interaction} interaction + * @param {Module} command + * @param {string} name + * @param {string=} description + * @param {string=} cmdName + */ const genericExecute = async (interaction, command, name, description, cmdName) => { try { console.info( @@ -35,7 +49,9 @@ const genericExecute = async (interaction, command, name, description, cmdName) }; export const name = Events.InteractionCreate; +/** @param {import('discord.js').Interaction} interaction */ export async function execute(interaction) { + /** @type {Module} */ let command = interaction.client.commands.get(interaction.commandName); // Execute slash- and context-menu-commands diff --git a/events/messages/messageDelete.js b/events/messages/messageDelete.js index 2f26198..4891c01 100644 --- a/events/messages/messageDelete.js +++ b/events/messages/messageDelete.js @@ -1,7 +1,8 @@ -import { Events } from 'discord.js'; import { Message } from '../../database.js'; +import { Events } from 'discord.js'; export const name = Events.MessageDelete; +/** @param {import('discord.js').Message} message */ export async function execute(message) { // Delete message entry once message is deleted itself const count = await Message.destroy({ diff --git a/events/messages/reactionAdd.js b/events/messages/reactionAdd.js index b12a43a..726ab6d 100644 --- a/events/messages/reactionAdd.js +++ b/events/messages/reactionAdd.js @@ -1,10 +1,14 @@ -import { config } from 'dotenv'; -import { Events } from 'discord.js'; +import { Events, MessageReaction, User } from 'discord.js'; import { Message, RoleEmojiPair } from '../../database.js'; +import { config } from 'dotenv'; config(); export const name = Events.MessageReactionAdd; +/** + * @param {MessageReaction} reaction + * @param {User} user + */ export async function execute(reaction, user) { if (user.id === process.env.CLIENT) return; diff --git a/events/messages/reactionRemove.js b/events/messages/reactionRemove.js index f91dd08..c17c3da 100644 --- a/events/messages/reactionRemove.js +++ b/events/messages/reactionRemove.js @@ -1,10 +1,14 @@ -import { config } from 'dotenv'; -import { Events } from 'discord.js'; +import { Events, MessageReaction, User } from 'discord.js'; import { Message, RoleEmojiPair } from '../../database.js'; +import { config } from 'dotenv'; config(); export const name = Events.MessageReactionRemove; +/** + * @param {MessageReaction} reaction + * @param {User} user + */ export async function execute(reaction, user) { if (user.id === process.env.CLIENT) return; diff --git a/events/ready.js b/events/ready.js index 561e67c..a9f4402 100644 --- a/events/ready.js +++ b/events/ready.js @@ -1,7 +1,8 @@ -import { Events } from 'discord.js'; +import { Events, Client } from 'discord.js'; export const name = Events.ClientReady; export const once = true; +/** @param {Client} client */ export function execute(client) { console.info(`[INFO] Ready! Logged in as ${client.user.tag}`); } diff --git a/index.js b/index.js index d39384f..7b7aa55 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,17 @@ -import { Client, Collection, GatewayIntentBits } from 'discord.js'; +import { Client, Collection, GatewayIntentBits, Partials } from 'discord.js'; import { getFiles, importAndCheck } from './shared.js'; -import { Partials } from 'discord.js'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import { config } from 'dotenv'; +import Module from 'module'; config(); +/** + * Main entry point, the bot logs on to discord. + * @param {Array} commands + * @param {Array} events + */ const runClient = (commands, events) => { // Create a new client instance const client = new Client({ @@ -18,8 +23,15 @@ const runClient = (commands, events) => { ], partials: [Partials.Message, Partials.Reaction] }); + + /** + * The commands registered for this client. + * @type {Collection} + */ client.commands = new Collection(); commands.forEach((c) => client.commands.set(c.data.name, c)); + + // Register client events events.forEach((e) => e.once ? client.once(e.name, (...args) => e.execute(...args)) diff --git a/models/messages.js b/models/messages.js index dae0097..fc6f583 100644 --- a/models/messages.js +++ b/models/messages.js @@ -1,5 +1,17 @@ -import { DataTypes } from 'sequelize'; +import { DataTypes, Sequelize } from 'sequelize'; +/** + * @typedef {Object} Message + * @property {string} id A Discord message ID. + * @method hasMany Defines an One-To-Many relationship. + * @param {Object} + */ + +/** + * The definition of the `Message` table in the database. + * @param {Sequelize} sequelize + * @returns {Message} + */ export default function (sequelize) { return sequelize.define('Messages', { id: { diff --git a/models/roleEmojiPairs.js b/models/roleEmojiPairs.js index 9b962c6..3b2541b 100644 --- a/models/roleEmojiPairs.js +++ b/models/roleEmojiPairs.js @@ -1,5 +1,18 @@ -import { DataTypes, Deferrable } from 'sequelize'; +import { DataTypes, Deferrable, Sequelize } from 'sequelize'; +/** + * @typedef {Object} RoleEmojiPair + * @property {string} id A universally unique id, generated by sequelize. + * @property {string} message A Discord message ID as a foreign key reference. + * @property {string} role A Discord role ID. + * @property {string} emoji Either a unicode emoji or a string representation in Discord custom emoji format. + */ + +/** + * The definition of the `RoleEmojiPair` table in the database. + * @param {Sequelize} sequelize + * @returns {RoleEmojiPair} + */ export default function (sequelize) { return sequelize.define('RoleEmojiPairs', { id: { diff --git a/models/voiceChannels.js b/models/voiceChannels.js index f1149c7..c2479a2 100644 --- a/models/voiceChannels.js +++ b/models/voiceChannels.js @@ -1,5 +1,17 @@ -import { DataTypes } from 'sequelize'; +import { DataTypes, Sequelize } from 'sequelize'; +/** + * @typedef {Object} VoiceChannel + * @property {string} id A Discord channel ID. + * @property {boolean} create Whether or not this channel is registered to create customs when joined. + * @property {(string|null)} owner The owner of this channel, if not registered for customs. + */ + +/** + * The definition of the `VoiceChannel` table in the database. + * @param {Sequelize} sequelize + * @returns {VoiceChannel} + */ export default function (sequelize) { return sequelize.define('VoiceChannel', { id: { diff --git a/shared.js b/shared.js index bcec0a2..ceea8a5 100644 --- a/shared.js +++ b/shared.js @@ -1,11 +1,18 @@ -import { join } from 'path'; -import { Op } from 'sequelize'; -import { config } from 'dotenv'; -import { readdir } from 'fs/promises'; +import { ChatInputCommandInteraction, ContextMenuCommandInteraction, Role } from 'discord.js'; import { Message, RoleEmojiPair } from './database.js'; +import { readdir } from 'fs/promises'; +import { config } from 'dotenv'; +import { Op } from 'sequelize'; +import { join } from 'path'; +import Module from 'module'; config(); +/** + * Main logic of the different 'Self Roles remove' commands to remove the functionality from a `Message`. + * @param {(ChatInputCommandInteraction|ContextMenuCommandInteraction)} interaction The interaction related to this command. + * @param {string} id A Discord message ID. + */ export const removeSelfRoles = async (interaction, id) => { // Try deleting message from database const count = await Message.destroy({ @@ -27,6 +34,12 @@ export const removeSelfRoles = async (interaction, id) => { console.info(`[INFO] Removed self roles from message with ID '${id}'.`); }; +/** + * Function to handle saving all the corresponding data for `Message` and `RoleEmojiPair` tables. + * @param {string} id A Discord message ID. + * @param {Role} role + * @param {string} emoji Either a unicode emoji or a string representation in Discord custom emoji format. + */ const saveMessageData = async (id, role, emoji) => { // Try finding message const msg = await Message.findOne({ where: { id } }); @@ -56,6 +69,12 @@ const saveMessageData = async (id, role, emoji) => { await RoleEmojiPair.create({ message: id, role: role.id, emoji }); }; +/** + * Function to handle editing messages in case the message that's getting a new `RoleEmojiPair`, is owned by the bot. + * @param {import('discord.js').Message} message + * @param {Role} role + * @param {string} emoji Either a unicode emoji or a string representation in Discord custom emoji format. + */ const editMessage = async (message, role, emoji) => { if (message.author.id !== process.env.CLIENT) return; @@ -72,6 +91,13 @@ const editMessage = async (message, role, emoji) => { await message.edit(next); }; +/** + * Main logic of the different 'Self Roles add' commands to add a `RoleEmojiPair` to the database and the `Message`. + * @param {(ChatInputCommandInteraction|ContextMenuCommandInteraction)} interaction The interaction related to this command. + * @param {string} msgID A Discord message ID. + * @param {Role} role + * @param {string} emoji Either a unicode emoji or a string representation in Discord custom emoji format. + */ export const addSelfRoles = async (interaction, msgID, role, emoji) => { const { channel } = interaction; @@ -109,9 +135,15 @@ export const addSelfRoles = async (interaction, msgID, role, emoji) => { } }; +// Lists of required and optional attributes of command modules const required = ['data', 'execute']; const optional = ['autocomplete', 'modalSubmit']; +/** + * Recursively scans a directory for all files in it. + * @param {string} dir + * @returns {Array} Array of paths to the files within. + */ export const getFiles = async (dir) => { const dirents = await readdir(dir, { withFileTypes: true }); const files = await Promise.all( @@ -123,6 +155,11 @@ export const getFiles = async (dir) => { return Array.prototype.concat(...files); }; +/** + * Imports and checks a command from a path as a module. + * @param {string} filePath + * @returns {Promise} + */ export const importAndCheck = async (filePath) => { if (!filePath.endsWith('.js') || filePath.endsWith('.example.js')) { // Skip this file From 76526e08b965716e9ae2a4fc4ddc813c645836f6 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 11 Feb 2024 02:39:52 +0100 Subject: [PATCH 096/133] remove old docs on jsdoc script execution --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af9be62..52edc93 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "deploy": "node deploy.js", "lint": "prettier --check . && eslint .", "format": "prettier --write .", - "jsdoc": "jsdoc -c ./.jsdoc.conf.json -d docs/ -r ." + "jsdoc": "rm -rf docs/ && jsdoc -c ./.jsdoc.conf.json -d docs/ -r ." }, "author": "", "license": "ISC", From f919dde7faa505f58496ea7105d1feeb6090cd28 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 11 Feb 2024 02:40:10 +0100 Subject: [PATCH 097/133] clean up: consistant format --- database.js | 1 + 1 file changed, 1 insertion(+) diff --git a/database.js b/database.js index 9c13374..cce1763 100644 --- a/database.js +++ b/database.js @@ -3,6 +3,7 @@ import defineVoiceChannel from './models/voiceChannels.js'; import defineMessage from './models/messages.js'; import { Sequelize } from 'sequelize'; import { config } from 'dotenv'; + config(); const { DB_NAME } = process.env; From b76e772ee25548463531f638cf1ba0aa9d187ae4 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 11 Feb 2024 02:40:26 +0100 Subject: [PATCH 098/133] additional model methods --- models/messages.js | 5 +++-- models/roleEmojiPairs.js | 3 +++ models/voiceChannels.js | 3 +++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/models/messages.js b/models/messages.js index fc6f583..dfbace7 100644 --- a/models/messages.js +++ b/models/messages.js @@ -3,8 +3,9 @@ import { DataTypes, Sequelize } from 'sequelize'; /** * @typedef {Object} Message * @property {string} id A Discord message ID. - * @method hasMany Defines an One-To-Many relationship. - * @param {Object} + * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. + * @property {(conditions: Object) => void} findOne Finds one instance in the database matching the provided condition(-s). + * @property {(conditions: Object) => void} findAll Finds all instances in the database matching the provided condition(-s). */ /** diff --git a/models/roleEmojiPairs.js b/models/roleEmojiPairs.js index 3b2541b..8eb1b70 100644 --- a/models/roleEmojiPairs.js +++ b/models/roleEmojiPairs.js @@ -6,6 +6,9 @@ import { DataTypes, Deferrable, Sequelize } from 'sequelize'; * @property {string} message A Discord message ID as a foreign key reference. * @property {string} role A Discord role ID. * @property {string} emoji Either a unicode emoji or a string representation in Discord custom emoji format. + * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. + * @property {(conditions: Object) => void} findOne Finds one instance in the database matching the provided condition(-s). + * @property {(conditions: Object) => void} findAll Finds all instances in the database matching the provided condition(-s). */ /** diff --git a/models/voiceChannels.js b/models/voiceChannels.js index c2479a2..c2fdc76 100644 --- a/models/voiceChannels.js +++ b/models/voiceChannels.js @@ -5,6 +5,9 @@ import { DataTypes, Sequelize } from 'sequelize'; * @property {string} id A Discord channel ID. * @property {boolean} create Whether or not this channel is registered to create customs when joined. * @property {(string|null)} owner The owner of this channel, if not registered for customs. + * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. + * @property {(conditions: Object) => void} findOne Finds one instance in the database matching the provided condition(-s). + * @property {(conditions: Object) => void} findAll Finds all instances in the database matching the provided condition(-s). */ /** From bdb08c1810796b44c78d5b816c93c259351715f2 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 11 Feb 2024 02:53:28 +0100 Subject: [PATCH 099/133] correct typing on typedef methods --- models/messages.js | 6 +++--- models/roleEmojiPairs.js | 6 +++--- models/voiceChannels.js | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/models/messages.js b/models/messages.js index dfbace7..c783e6f 100644 --- a/models/messages.js +++ b/models/messages.js @@ -4,8 +4,8 @@ import { DataTypes, Sequelize } from 'sequelize'; * @typedef {Object} Message * @property {string} id A Discord message ID. * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. - * @property {(conditions: Object) => void} findOne Finds one instance in the database matching the provided condition(-s). - * @property {(conditions: Object) => void} findAll Finds all instances in the database matching the provided condition(-s). + * @property {(conditions: Object) => Promise} findOne Finds one instance in the database matching the provided condition(-s). + * @property {(conditions: Object) => Promise>} findAll Finds all instances in the database matching the provided condition(-s). */ /** @@ -13,7 +13,7 @@ import { DataTypes, Sequelize } from 'sequelize'; * @param {Sequelize} sequelize * @returns {Message} */ -export default function (sequelize) { +export default function(sequelize) { return sequelize.define('Messages', { id: { type: DataTypes.STRING, diff --git a/models/roleEmojiPairs.js b/models/roleEmojiPairs.js index 8eb1b70..1bf216f 100644 --- a/models/roleEmojiPairs.js +++ b/models/roleEmojiPairs.js @@ -7,8 +7,8 @@ import { DataTypes, Deferrable, Sequelize } from 'sequelize'; * @property {string} role A Discord role ID. * @property {string} emoji Either a unicode emoji or a string representation in Discord custom emoji format. * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. - * @property {(conditions: Object) => void} findOne Finds one instance in the database matching the provided condition(-s). - * @property {(conditions: Object) => void} findAll Finds all instances in the database matching the provided condition(-s). + * @property {(conditions: Object) => Promise} findOne Finds one instance in the database matching the provided condition(-s). + * @property {(conditions: Object) => Promise>} findAll Finds all instances in the database matching the provided condition(-s). */ /** @@ -16,7 +16,7 @@ import { DataTypes, Deferrable, Sequelize } from 'sequelize'; * @param {Sequelize} sequelize * @returns {RoleEmojiPair} */ -export default function (sequelize) { +export default function(sequelize) { return sequelize.define('RoleEmojiPairs', { id: { defaultValue: DataTypes.UUIDV4, diff --git a/models/voiceChannels.js b/models/voiceChannels.js index c2fdc76..9cf3b88 100644 --- a/models/voiceChannels.js +++ b/models/voiceChannels.js @@ -6,8 +6,8 @@ import { DataTypes, Sequelize } from 'sequelize'; * @property {boolean} create Whether or not this channel is registered to create customs when joined. * @property {(string|null)} owner The owner of this channel, if not registered for customs. * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. - * @property {(conditions: Object) => void} findOne Finds one instance in the database matching the provided condition(-s). - * @property {(conditions: Object) => void} findAll Finds all instances in the database matching the provided condition(-s). + * @property {(conditions: Object) => Promise} findOne Finds one instance in the database matching the provided condition(-s). + * @property {(conditions: Object) => Promise>} findAll Finds all instances in the database matching the provided condition(-s). */ /** @@ -15,7 +15,7 @@ import { DataTypes, Sequelize } from 'sequelize'; * @param {Sequelize} sequelize * @returns {VoiceChannel} */ -export default function (sequelize) { +export default function(sequelize) { return sequelize.define('VoiceChannel', { id: { type: DataTypes.STRING, From 9f3bceeade126ead8416c278a007d5aa2dbab159 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 11 Feb 2024 02:55:46 +0100 Subject: [PATCH 100/133] clean up: auto format --- models/messages.js | 2 +- models/roleEmojiPairs.js | 2 +- models/voiceChannels.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/models/messages.js b/models/messages.js index c783e6f..ee23e27 100644 --- a/models/messages.js +++ b/models/messages.js @@ -13,7 +13,7 @@ import { DataTypes, Sequelize } from 'sequelize'; * @param {Sequelize} sequelize * @returns {Message} */ -export default function(sequelize) { +export default function (sequelize) { return sequelize.define('Messages', { id: { type: DataTypes.STRING, diff --git a/models/roleEmojiPairs.js b/models/roleEmojiPairs.js index 1bf216f..be5b04f 100644 --- a/models/roleEmojiPairs.js +++ b/models/roleEmojiPairs.js @@ -16,7 +16,7 @@ import { DataTypes, Deferrable, Sequelize } from 'sequelize'; * @param {Sequelize} sequelize * @returns {RoleEmojiPair} */ -export default function(sequelize) { +export default function (sequelize) { return sequelize.define('RoleEmojiPairs', { id: { defaultValue: DataTypes.UUIDV4, diff --git a/models/voiceChannels.js b/models/voiceChannels.js index 9cf3b88..4bf9f20 100644 --- a/models/voiceChannels.js +++ b/models/voiceChannels.js @@ -15,7 +15,7 @@ import { DataTypes, Sequelize } from 'sequelize'; * @param {Sequelize} sequelize * @returns {VoiceChannel} */ -export default function(sequelize) { +export default function (sequelize) { return sequelize.define('VoiceChannel', { id: { type: DataTypes.STRING, From b99db199d7b1c79cf5e9623625de5db7358fcf6b Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Tue, 13 Feb 2024 20:29:02 +0100 Subject: [PATCH 101/133] fixed jsdoc type error --- commands/admin/self_roles/slash.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands/admin/self_roles/slash.js b/commands/admin/self_roles/slash.js index 3e12b59..3226bd3 100644 --- a/commands/admin/self_roles/slash.js +++ b/commands/admin/self_roles/slash.js @@ -5,7 +5,7 @@ import { Message } from '../../../database.js'; /** * Sends a `Message` in the current channel and registers for self roles. * @param {ChatInputCommandInteraction} interaction - * @returns {string} + * @returns {Promise} */ const createSelfRoles = async (interaction) => { const { options, channel } = interaction; From 5c4192eb4bd61c47ea6d0ef0803c129eb167c09a Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 16 Feb 2024 17:37:53 +0100 Subject: [PATCH 102/133] reply and provide id --- commands/admin/self_roles/slash.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/commands/admin/self_roles/slash.js b/commands/admin/self_roles/slash.js index 3226bd3..2821f51 100644 --- a/commands/admin/self_roles/slash.js +++ b/commands/admin/self_roles/slash.js @@ -14,9 +14,11 @@ const createSelfRoles = async (interaction) => { const text = options.getString('text'); const id = (await channel.send(text)).id; - // Reply and delete to acknowledge command - await interaction.deferReply(); - await interaction.deleteReply(); + // Reply successfully to acknowledge command + await interaction.reply({ + content: `Successfully sent message! Add roles to it with reference ID '${id}'.`, + ephemeral: true + }); return id; }; From 064abe7e24f915c9a12ca5363b43b3ce18e7f25d Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 16 Feb 2024 18:26:00 +0100 Subject: [PATCH 103/133] save emoji identifier without name --- shared.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared.js b/shared.js index ceea8a5..c8264af 100644 --- a/shared.js +++ b/shared.js @@ -66,7 +66,7 @@ const saveMessageData = async (id, role, emoji) => { ); // Create database entry for pair - await RoleEmojiPair.create({ message: id, role: role.id, emoji }); + await RoleEmojiPair.create({ message: id, role: role.id, emoji: emoji.replace(/:(\s*[^:]*\s*):/, ":_:") }); }; /** From ab6ca9edeb0dff29184e957443bb7c84c17fafb7 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Fri, 16 Feb 2024 18:26:25 +0100 Subject: [PATCH 104/133] forced reaction remove error handling --- events/messages/reactionAdd.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/events/messages/reactionAdd.js b/events/messages/reactionAdd.js index 726ab6d..a354455 100644 --- a/events/messages/reactionAdd.js +++ b/events/messages/reactionAdd.js @@ -33,7 +33,13 @@ export async function execute(reaction, user) { // Deny if unregistered if (rep === null) { // Remove reaction and quit - await reaction.remove(); + try { + reaction.remove(); + } catch (error) { + // Missing permissions + console.error(error) + await user.send('Unable to remove reaction. Please contact server staff.'); + } return; } From e74bd83ee6204625107b220af9a53b19b0c7685e Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sat, 24 Feb 2024 18:46:46 +0100 Subject: [PATCH 105/133] catch channel delete error --- events/channels/voiceStateUpdate.js | 37 +++++++++++++++-------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index 88cfa50..700270a 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -102,25 +102,26 @@ export const name = Events.VoiceStateUpdate; */ export async function execute(oldState, newState) { const { channel } = newState; - await leftVoiceChat(oldState); - if (!channel) return; - - // Find channel by id, return if not registered for customs - const createCh = await VoiceChannel.findOne({ - where: { - id: channel.id, - create: true - } - }); - if (createCh === null) return; - - // Extract user data - const member = newState.member; - - // Extract channel data - const channels = newState.guild.channels; - let step = 'create'; + let step = 'delete'; try { + await leftVoiceChat(oldState); + if (!channel) return; + + // Find channel by id, return if not registered for customs + const createCh = await VoiceChannel.findOne({ + where: { + id: channel.id, + create: true + } + }); + if (createCh === null) return; + + // Extract user data + const member = newState.member; + + step = 'create'; + // Extract channel data + const channels = newState.guild.channels; const privCh = await getChannel(member, channels, channel); step = 'move to'; From 7ea28db049b05409e7f1a6a6e0f3839e88fc7ccc Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 28 Feb 2024 20:43:36 +0100 Subject: [PATCH 106/133] more verbose logging --- events/messages/reactionAdd.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/events/messages/reactionAdd.js b/events/messages/reactionAdd.js index a354455..df5b1b0 100644 --- a/events/messages/reactionAdd.js +++ b/events/messages/reactionAdd.js @@ -37,7 +37,7 @@ export async function execute(reaction, user) { reaction.remove(); } catch (error) { // Missing permissions - console.error(error) + console.error(error); await user.send('Unable to remove reaction. Please contact server staff.'); } return; From 0c3c1b5582bc20f02f0ae2f16a52fa4acf87eca0 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 28 Feb 2024 20:48:45 +0100 Subject: [PATCH 107/133] reformat: auto format --- shared.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shared.js b/shared.js index c8264af..f800faa 100644 --- a/shared.js +++ b/shared.js @@ -66,7 +66,11 @@ const saveMessageData = async (id, role, emoji) => { ); // Create database entry for pair - await RoleEmojiPair.create({ message: id, role: role.id, emoji: emoji.replace(/:(\s*[^:]*\s*):/, ":_:") }); + await RoleEmojiPair.create({ + message: id, + role: role.id, + emoji: emoji.replace(/:(\s*[^:]*\s*):/, ':_:') + }); }; /** From 73562b35700d153ba9ae3e2f10f3c40cc2b612b8 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 28 Feb 2024 20:50:34 +0100 Subject: [PATCH 108/133] apply permission overwrites from parent channel --- events/channels/voiceStateUpdate.js | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index 700270a..7d33a76 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -1,7 +1,6 @@ import { ChannelType, Events, - PermissionFlagsBits, GuildMember, GuildChannelManager, GuildChannel, @@ -9,22 +8,6 @@ import { } from 'discord.js'; import { VoiceChannel } from '../../database.js'; -const vcPermissionOverwrites = [ - PermissionFlagsBits.ReadMessageHistory, - PermissionFlagsBits.PrioritySpeaker, - PermissionFlagsBits.ManageMessages, - PermissionFlagsBits.ManageChannels, - PermissionFlagsBits.DeafenMembers, - PermissionFlagsBits.SendMessages, - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.MuteMembers, - PermissionFlagsBits.MoveMembers, - PermissionFlagsBits.Connect, - PermissionFlagsBits.Stream, - PermissionFlagsBits.UseVAD, - PermissionFlagsBits.Speak -]; - /** * Function that either creates a new custom channel or gets an existing one registered in the database. * @param {GuildMember} member The member that caused this event. @@ -46,16 +29,13 @@ const getChannel = async (member, guildChs, channel) => { // Create private channel with all permissions const name = member.user.username; const chName = `${name}${name.toLowerCase().endsWith('s') ? "'" : "'s"} channel`; + // Get permissions from parent + const vcPermOver = channel.parent.permissionOverwrites.cache.get(member.id); const privCh = await guildChs.create({ name: chName, parent: channel.parent, type: ChannelType.GuildVoice, - permissionOverwrites: [ - { - id: member.id, - allow: vcPermissionOverwrites - } - ] + permissionOverwrites: [vcPermOver] }); // Save newly created channel From fa6a9fa2e38edd2afdbb760fcc5cbff171aa9bf0 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 28 Feb 2024 20:51:59 +0100 Subject: [PATCH 109/133] extract member prop early --- events/channels/voiceStateUpdate.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index 7d33a76..e0ce9a3 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -81,7 +81,7 @@ export const name = Events.VoiceStateUpdate; * @param {VoiceState} newState */ export async function execute(oldState, newState) { - const { channel } = newState; + const { channel, member } = newState; let step = 'delete'; try { await leftVoiceChat(oldState); @@ -96,9 +96,6 @@ export async function execute(oldState, newState) { }); if (createCh === null) return; - // Extract user data - const member = newState.member; - step = 'create'; // Extract channel data const channels = newState.guild.channels; From 3cea3694db1f896c4ff81bba71ea710f3cf32fdf Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 28 Feb 2024 21:12:38 +0100 Subject: [PATCH 110/133] clean up: code style --- events/channels/voiceStateUpdate.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index e0ce9a3..5d4b07c 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -22,9 +22,7 @@ const getChannel = async (member, guildChs, channel) => { owner: member.user.id } }); - if (ownCh !== null) { - return await guildChs.fetch(ownCh.id); - } + if (ownCh !== null) return await guildChs.fetch(ownCh.id); // Create private channel with all permissions const name = member.user.username; From 84f754dd5bd2f867f3e1c30dc4fa2fa8a8a39a97 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 28 Feb 2024 21:18:14 +0100 Subject: [PATCH 111/133] clean up: consistant naming --- events/channels/voiceStateUpdate.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index 5d4b07c..3d0ab97 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -19,7 +19,7 @@ const getChannel = async (member, guildChs, channel) => { // Check database for existing channel const ownCh = await VoiceChannel.findOne({ where: { - owner: member.user.id + owner: member.id } }); if (ownCh !== null) return await guildChs.fetch(ownCh.id); @@ -39,7 +39,7 @@ const getChannel = async (member, guildChs, channel) => { // Save newly created channel await VoiceChannel.create({ id: privCh.id, - owner: member.user.id + owner: member.id }); return privCh; From e0c581dcfd5fbf2973acedf8113fe259750eb7b9 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Wed, 28 Feb 2024 21:19:20 +0100 Subject: [PATCH 112/133] get specific permissions from parent element --- events/channels/voiceStateUpdate.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index 3d0ab97..910e8b1 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -4,7 +4,8 @@ import { GuildMember, GuildChannelManager, GuildChannel, - VoiceState + VoiceState, + PermissionOverwrites } from 'discord.js'; import { VoiceChannel } from '../../database.js'; @@ -28,12 +29,19 @@ const getChannel = async (member, guildChs, channel) => { const name = member.user.username; const chName = `${name}${name.toLowerCase().endsWith('s') ? "'" : "'s"} channel`; // Get permissions from parent - const vcPermOver = channel.parent.permissionOverwrites.cache.get(member.id); + /** @type {PermissionOverwrites} */ + const { allow, deny } = channel.parent.permissionOverwrites.cache.get(member.client.user.id); const privCh = await guildChs.create({ name: chName, parent: channel.parent, type: ChannelType.GuildVoice, - permissionOverwrites: [vcPermOver] + permissionOverwrites: [ + { + id: member.id, + allow, + deny + } + ] }); // Save newly created channel From 4aee497fbacceb7b47a6eaa51321e6d116c89972 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sat, 2 Mar 2024 23:49:13 +0100 Subject: [PATCH 113/133] prepare member_roles with template --- commands/admin/member_roles/slash.js | 34 ++++++++++++++++++++++++++++ events/members/guildMemberAdd.js | 7 ++++++ index.js | 1 + 3 files changed, 42 insertions(+) create mode 100644 commands/admin/member_roles/slash.js create mode 100644 events/members/guildMemberAdd.js diff --git a/commands/admin/member_roles/slash.js b/commands/admin/member_roles/slash.js new file mode 100644 index 0000000..0a355d8 --- /dev/null +++ b/commands/admin/member_roles/slash.js @@ -0,0 +1,34 @@ +import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js'; + +export const data = new SlashCommandBuilder() + .setName('member_roles') + .setDMPermission(false) + .setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles) + .setDescription('Assigns roles to new members.') + .addSubcommand((subcommand) => + subcommand + .setName('add') + .setDescription('Registers a role to be assigned to new members.') + .addRoleOption((option) => + option + .setName('role') + .setDescription('The role to assign to new members.') + .setRequired(true) + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('remove') + .setDescription('Unregisters a role from new member assignment.') + .addRoleOption((option) => + option + .setName('role') + .setDescription('The role to unregister from assignmment.') + .setRequired(true) + ) + ); + +/** @param {ChatInputCommandInteraction} interaction */ +export async function execute(interaction) { + const { options } = interaction; +} diff --git a/events/members/guildMemberAdd.js b/events/members/guildMemberAdd.js new file mode 100644 index 0000000..e176e68 --- /dev/null +++ b/events/members/guildMemberAdd.js @@ -0,0 +1,7 @@ +import { Events, GuildMember } from 'discord.js'; + +export const name = Events.GuildMemberAdd; +/** @param {GuildMember} member */ +export function execute(member) { + console.log(member); +} diff --git a/index.js b/index.js index 7b7aa55..2a6103e 100644 --- a/index.js +++ b/index.js @@ -17,6 +17,7 @@ const runClient = (commands, events) => { const client = new Client({ intents: [ GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMessageReactions From 7983eb60f9f9911187b1129f4cd4e760a7ac1912 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sat, 2 Mar 2024 23:49:49 +0100 Subject: [PATCH 114/133] bugfix: JSDoc typing --- shared.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared.js b/shared.js index f800faa..e3c21c4 100644 --- a/shared.js +++ b/shared.js @@ -146,7 +146,7 @@ const optional = ['autocomplete', 'modalSubmit']; /** * Recursively scans a directory for all files in it. * @param {string} dir - * @returns {Array} Array of paths to the files within. + * @returns {Promise>} Array of paths to the files within. */ export const getFiles = async (dir) => { const dirents = await readdir(dir, { withFileTypes: true }); From 5b7862fe7b601e61dca51f7e11147726a37e8dac Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sat, 2 Mar 2024 23:51:18 +0100 Subject: [PATCH 115/133] extend database by saving guilds --- database.js | 12 +++++++++++- models/guilds.js | 23 +++++++++++++++++++++++ models/messages.js | 9 +++++++++ models/roleEmojiPairs.js | 7 ++++++- models/roles.js | 32 ++++++++++++++++++++++++++++++++ models/voiceChannels.js | 11 ++++++++++- 6 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 models/guilds.js create mode 100644 models/roles.js diff --git a/database.js b/database.js index cce1763..7c43f0b 100644 --- a/database.js +++ b/database.js @@ -1,6 +1,8 @@ import defineRoleEmojiPair from './models/roleEmojiPairs.js'; import defineVoiceChannel from './models/voiceChannels.js'; import defineMessage from './models/messages.js'; +import defineGuild from './models/guilds.js'; +import defineRole from './models/roles.js'; import { Sequelize } from 'sequelize'; import { config } from 'dotenv'; @@ -14,6 +16,14 @@ const sequelize = new Sequelize({ logging: false }); +const Guild = defineGuild(sequelize); +Guild.hasMany(VoiceChannel, { foreignKey: 'guild', onDelete: 'CASCADE' }); +Guild.hasMany(Message, { foreignKey: 'guild', onDelete: 'CASCADE' }); +Guild.hasMany(Role, { foreignKey: 'guild', onDelete: 'CASCADE' }); + +const Role = defineRole(sequelize); +Role.hasMany(RoleEmojiPair, { foreignKey: 'role', onDelete: 'CASCADE' }); + const RoleEmojiPair = defineRoleEmojiPair(sequelize); const VoiceChannel = defineVoiceChannel(sequelize); @@ -22,4 +32,4 @@ const Message = defineMessage(sequelize); Message.hasMany(RoleEmojiPair, { foreignKey: 'message', onDelete: 'CASCADE' }); sequelize.sync(); -export { sequelize, RoleEmojiPair, VoiceChannel, Message }; +export { sequelize, Guild, Role, RoleEmojiPair, VoiceChannel, Message }; diff --git a/models/guilds.js b/models/guilds.js new file mode 100644 index 0000000..8460b8f --- /dev/null +++ b/models/guilds.js @@ -0,0 +1,23 @@ +import { DataTypes, Sequelize } from 'sequelize'; + +/** + * @typedef {Object} Guild + * @property {string} id A Discord guild ID. + * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. + * @property {(conditions: Object) => Promise} findOne Finds one instance in the database matching the provided condition(-s). + * @property {(conditions: Object) => Promise>} findAll Finds all instances in the database matching the provided condition(-s). + */ + +/** + * The definition of the `Guild` table in the database. + * @param {Sequelize} sequelize + * @returns {Guild} + */ +export default function (sequelize) { + return sequelize.define('Guilds', { + id: { + type: DataTypes.STRING, + primaryKey: true + } + }); +} diff --git a/models/messages.js b/models/messages.js index ee23e27..c9175ad 100644 --- a/models/messages.js +++ b/models/messages.js @@ -3,6 +3,7 @@ import { DataTypes, Sequelize } from 'sequelize'; /** * @typedef {Object} Message * @property {string} id A Discord message ID. + * @property {string} guild A Discord guild ID as a foreign key reference. * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. * @property {(conditions: Object) => Promise} findOne Finds one instance in the database matching the provided condition(-s). * @property {(conditions: Object) => Promise>} findAll Finds all instances in the database matching the provided condition(-s). @@ -18,6 +19,14 @@ export default function (sequelize) { id: { type: DataTypes.STRING, primaryKey: true + }, + guild: { + type: DataTypes.STRING, + references: { + deferrable: Deferrable.INITIALLY_IMMEDIATE, + model: 'Guilds', + key: 'id' + } } }); } diff --git a/models/roleEmojiPairs.js b/models/roleEmojiPairs.js index be5b04f..11a5946 100644 --- a/models/roleEmojiPairs.js +++ b/models/roleEmojiPairs.js @@ -32,7 +32,12 @@ export default function (sequelize) { } }, role: { - type: DataTypes.STRING + type: DataTypes.STRING, + references: { + deferrable: Deferrable.INITIALLY_IMMEDIATE, + model: 'Roles', + key: 'id' + } }, emoji: { type: DataTypes.STRING diff --git a/models/roles.js b/models/roles.js new file mode 100644 index 0000000..315d1bb --- /dev/null +++ b/models/roles.js @@ -0,0 +1,32 @@ +import { DataTypes, Sequelize } from 'sequelize'; + +/** + * @typedef {Object} Role + * @property {string} id A Discord role ID. + * @property {string} guild A Discord guild ID as a foreign key reference. + * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. + * @property {(conditions: Object) => Promise} findOne Finds one instance in the database matching the provided condition(-s). + * @property {(conditions: Object) => Promise>} findAll Finds all instances in the database matching the provided condition(-s). + */ + +/** + * The definition of the `Role` table in the database. + * @param {Sequelize} sequelize + * @returns {Role} + */ +export default function (sequelize) { + return sequelize.define('Roles', { + id: { + type: DataTypes.STRING, + primaryKey: true + }, + guild: { + type: DataTypes.STRING, + references: { + deferrable: Deferrable.INITIALLY_IMMEDIATE, + model: 'Guilds', + key: 'id' + } + } + }); +} diff --git a/models/voiceChannels.js b/models/voiceChannels.js index 4bf9f20..f18edfe 100644 --- a/models/voiceChannels.js +++ b/models/voiceChannels.js @@ -5,6 +5,7 @@ import { DataTypes, Sequelize } from 'sequelize'; * @property {string} id A Discord channel ID. * @property {boolean} create Whether or not this channel is registered to create customs when joined. * @property {(string|null)} owner The owner of this channel, if not registered for customs. + * @property {string} guild A Discord guild ID as a foreign key reference. * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. * @property {(conditions: Object) => Promise} findOne Finds one instance in the database matching the provided condition(-s). * @property {(conditions: Object) => Promise>} findAll Finds all instances in the database matching the provided condition(-s). @@ -16,7 +17,7 @@ import { DataTypes, Sequelize } from 'sequelize'; * @returns {VoiceChannel} */ export default function (sequelize) { - return sequelize.define('VoiceChannel', { + return sequelize.define('VoiceChannels', { id: { type: DataTypes.STRING, primaryKey: true @@ -28,6 +29,14 @@ export default function (sequelize) { owner: { type: DataTypes.STRING, allowNull: true + }, + guild: { + type: DataTypes.STRING, + references: { + deferrable: Deferrable.INITIALLY_IMMEDIATE, + model: 'Guilds', + key: 'id' + } } }); } From aa3eac70bab80ed6ce3a9bd9418e1acb76a6a387 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 3 Mar 2024 00:41:04 +0100 Subject: [PATCH 116/133] boolean property to note role assignment --- models/roles.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/models/roles.js b/models/roles.js index 315d1bb..25145eb 100644 --- a/models/roles.js +++ b/models/roles.js @@ -3,6 +3,7 @@ import { DataTypes, Sequelize } from 'sequelize'; /** * @typedef {Object} Role * @property {string} id A Discord role ID. + * @property {boolean} assign Whether or not the role should be assigned to new members. * @property {string} guild A Discord guild ID as a foreign key reference. * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. * @property {(conditions: Object) => Promise} findOne Finds one instance in the database matching the provided condition(-s). @@ -20,6 +21,10 @@ export default function (sequelize) { type: DataTypes.STRING, primaryKey: true }, + assign: { + type: DataTypes.BOOLEAN, + defaultValue: false + }, guild: { type: DataTypes.STRING, references: { From 580b54c54989d179bdf19ca358ee6cf40e1d6778 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 3 Mar 2024 00:41:37 +0100 Subject: [PATCH 117/133] implement role assignment slash command --- commands/admin/member_roles/slash.js | 54 ++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/commands/admin/member_roles/slash.js b/commands/admin/member_roles/slash.js index 0a355d8..95cfeb9 100644 --- a/commands/admin/member_roles/slash.js +++ b/commands/admin/member_roles/slash.js @@ -1,4 +1,5 @@ import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js'; +import { Role } from '../../../database.js'; export const data = new SlashCommandBuilder() .setName('member_roles') @@ -31,4 +32,57 @@ export const data = new SlashCommandBuilder() /** @param {ChatInputCommandInteraction} interaction */ export async function execute(interaction) { const { options } = interaction; + + // Get command options + const role = options.getRole('role'); + switch (options.getSubcommand()) { + case 'add': + // Search for role in database + const found = await Role.findOne({ + where: { + id: role.id + } + }); + + // Toggle role assignment if found + if (found) { + found.assign = true; + await found.save(); + // Otherwise create new database entry + } else + await Role.create({ + id: role.id, + assign: true + }); + + // Reply successfully to acknowledge command + await interaction.reply({ + content: 'Successfully registered role.', + ephemeral: true + }); + + console.info(`[INFO] Registered role to be assigned with ID '${role.id}'.`); + break; + case 'remove': + // Remove role from database + const count = await Role.destroy({ + where: { + id: role.id, + assign: true + } + }); + + // Set reply based on result of deletion + let response = 'Successfully removed'; + if (count === 0) response = 'Failed to remove'; + + // Reply to acknowledge command + await interaction.reply({ + content: `${response} role from new member assignment!`, + ephemeral: true + }); + + console.info(`[INFO] Removed role to be assigned with ID '${role.id}'.`); + break; + } } From cfc714611ef24410377b65af17bd9c349e7fce9d Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 3 Mar 2024 00:46:44 +0100 Subject: [PATCH 118/133] implement guild member add event --- events/members/guildMemberAdd.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/events/members/guildMemberAdd.js b/events/members/guildMemberAdd.js index e176e68..8e732dd 100644 --- a/events/members/guildMemberAdd.js +++ b/events/members/guildMemberAdd.js @@ -1,7 +1,20 @@ import { Events, GuildMember } from 'discord.js'; +import { Role } from '../../../database.js'; export const name = Events.GuildMemberAdd; /** @param {GuildMember} member */ -export function execute(member) { - console.log(member); +export async function execute(member) { + // Find roles to be assigned in guild from database + const roles = await Role.findAll({ + where: { + guild: member.guild.id, + assign: true + } + }); + + // Ignore if no none found + if (roles.length === 0) return; + + // Add roles to member + member.roles.add(roles.map((role) => role.id)); } From 2a695f24c6b4bf09256039e8fd562ca7a3eba5ea Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 3 Mar 2024 00:47:17 +0100 Subject: [PATCH 119/133] bugfix: def / init order --- database.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/database.js b/database.js index 7c43f0b..fefc21d 100644 --- a/database.js +++ b/database.js @@ -16,14 +16,6 @@ const sequelize = new Sequelize({ logging: false }); -const Guild = defineGuild(sequelize); -Guild.hasMany(VoiceChannel, { foreignKey: 'guild', onDelete: 'CASCADE' }); -Guild.hasMany(Message, { foreignKey: 'guild', onDelete: 'CASCADE' }); -Guild.hasMany(Role, { foreignKey: 'guild', onDelete: 'CASCADE' }); - -const Role = defineRole(sequelize); -Role.hasMany(RoleEmojiPair, { foreignKey: 'role', onDelete: 'CASCADE' }); - const RoleEmojiPair = defineRoleEmojiPair(sequelize); const VoiceChannel = defineVoiceChannel(sequelize); @@ -31,5 +23,13 @@ const VoiceChannel = defineVoiceChannel(sequelize); const Message = defineMessage(sequelize); Message.hasMany(RoleEmojiPair, { foreignKey: 'message', onDelete: 'CASCADE' }); +const Role = defineRole(sequelize); +Role.hasMany(RoleEmojiPair, { foreignKey: 'role', onDelete: 'CASCADE' }); + +const Guild = defineGuild(sequelize); +Guild.hasMany(VoiceChannel, { foreignKey: 'guild', onDelete: 'CASCADE' }); +Guild.hasMany(Message, { foreignKey: 'guild', onDelete: 'CASCADE' }); +Guild.hasMany(Role, { foreignKey: 'guild', onDelete: 'CASCADE' }); + sequelize.sync(); export { sequelize, Guild, Role, RoleEmojiPair, VoiceChannel, Message }; From aee298f791b29d23b0a09f053ce2e7645e6a5eaf Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 3 Mar 2024 00:49:36 +0100 Subject: [PATCH 120/133] bugfix: import Deferrable --- models/messages.js | 2 +- models/roles.js | 2 +- models/voiceChannels.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/models/messages.js b/models/messages.js index c9175ad..7dec9c6 100644 --- a/models/messages.js +++ b/models/messages.js @@ -1,4 +1,4 @@ -import { DataTypes, Sequelize } from 'sequelize'; +import { DataTypes, Deferrable, Sequelize } from 'sequelize'; /** * @typedef {Object} Message diff --git a/models/roles.js b/models/roles.js index 25145eb..4eb70ef 100644 --- a/models/roles.js +++ b/models/roles.js @@ -1,4 +1,4 @@ -import { DataTypes, Sequelize } from 'sequelize'; +import { DataTypes, Deferrable, Sequelize } from 'sequelize'; /** * @typedef {Object} Role diff --git a/models/voiceChannels.js b/models/voiceChannels.js index f18edfe..84f04f8 100644 --- a/models/voiceChannels.js +++ b/models/voiceChannels.js @@ -1,4 +1,4 @@ -import { DataTypes, Sequelize } from 'sequelize'; +import { DataTypes, Deferrable, Sequelize } from 'sequelize'; /** * @typedef {Object} VoiceChannel From 647dc6211b382559712d7d45bccdc553148393fb Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 3 Mar 2024 00:50:46 +0100 Subject: [PATCH 121/133] bugfix: corrected path --- events/members/guildMemberAdd.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/events/members/guildMemberAdd.js b/events/members/guildMemberAdd.js index 8e732dd..a744467 100644 --- a/events/members/guildMemberAdd.js +++ b/events/members/guildMemberAdd.js @@ -1,5 +1,5 @@ import { Events, GuildMember } from 'discord.js'; -import { Role } from '../../../database.js'; +import { Role } from '../../database.js'; export const name = Events.GuildMemberAdd; /** @param {GuildMember} member */ From 146bdf363e0181a39c5b00911054fdfb16f0b7bc Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 3 Mar 2024 01:07:10 +0100 Subject: [PATCH 122/133] verbose logging --- events/members/guildMemberAdd.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/events/members/guildMemberAdd.js b/events/members/guildMemberAdd.js index a744467..69881c5 100644 --- a/events/members/guildMemberAdd.js +++ b/events/members/guildMemberAdd.js @@ -16,5 +16,7 @@ export async function execute(member) { if (roles.length === 0) return; // Add roles to member - member.roles.add(roles.map((role) => role.id)); + await member.roles.add(roles.map((role) => role.id)); + + console.info(`[INFO] Added ${roles.length} roles to user with ID '${member.user.id}'.`); } From 8d2f647ffd015699dbd7aae3bd03fbf4340ea69f Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 3 Mar 2024 01:44:27 +0100 Subject: [PATCH 123/133] find or create, then save guild on each interaction --- commands/admin/custom_vc/slash.js | 24 +++++++++++++++++++---- commands/admin/member_roles/slash.js | 29 +++++++++++++++++++++------- commands/admin/self_roles/slash.js | 21 ++++++++++++++++++-- events/members/guildMemberAdd.js | 13 +++++++++---- models/guilds.js | 1 + models/messages.js | 1 + models/roleEmojiPairs.js | 1 + models/roles.js | 1 + models/voiceChannels.js | 1 + shared.js | 8 ++++++++ 10 files changed, 83 insertions(+), 17 deletions(-) diff --git a/commands/admin/custom_vc/slash.js b/commands/admin/custom_vc/slash.js index 5692a92..b3fc6b6 100644 --- a/commands/admin/custom_vc/slash.js +++ b/commands/admin/custom_vc/slash.js @@ -4,7 +4,7 @@ import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js'; -import { VoiceChannel } from '../../../database.js'; +import { Guild, VoiceChannel } from '../../../database.js'; export const data = new SlashCommandBuilder() .setName('custom_vc') @@ -52,6 +52,7 @@ export async function execute(interaction) { /** @type {string} */ let step; + const guildData = { id: guild.id }; try { switch (options.getSubcommand()) { case 'create': { @@ -65,10 +66,16 @@ export async function execute(interaction) { type: ChannelType.GuildVoice }); - // Save channel data step = 'save'; + // Create guild if not exists + await Guild.findOrCreate({ + where: guildData, + defaults: guildData + }); + // Save channel data await VoiceChannel.create({ id: channel.id, + guild: guild.id, create: true }); @@ -85,9 +92,18 @@ export async function execute(interaction) { // Get channel id from user input const { id } = options.getChannel('channel'); - // Save channel data step = 'save'; - await VoiceChannel.create({ id, create: true }); + // Create guild if not exists + await Guild.findOrCreate({ + where: guildData, + defaults: guildData + }); + // Save channel data + await VoiceChannel.create({ + id, + guild: guild.id, + create: true + }); // Reply success to acknowledge command await interaction.reply({ diff --git a/commands/admin/member_roles/slash.js b/commands/admin/member_roles/slash.js index 95cfeb9..181e108 100644 --- a/commands/admin/member_roles/slash.js +++ b/commands/admin/member_roles/slash.js @@ -1,5 +1,25 @@ import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js'; -import { Role } from '../../../database.js'; +import { Role, Guild } from '../../../database.js'; + +/** + * @param {Guild} guild + * @param {Role} role + */ +const registerRole = async (guild, role) => { + // Check if guild exists in database, otherwise create it + const guildData = { id: guild.id }; + await Guild.findOrCreate({ + where: guildData, + defaults: guildData + }); + + // Register role in database + await Role.create({ + guild: guild.id, + id: role.id, + assign: true + }); +}; export const data = new SlashCommandBuilder() .setName('member_roles') @@ -49,12 +69,7 @@ export async function execute(interaction) { found.assign = true; await found.save(); // Otherwise create new database entry - } else - await Role.create({ - id: role.id, - assign: true - }); - + } else await registerRole(interaction.guild, role); // Reply successfully to acknowledge command await interaction.reply({ content: 'Successfully registered role.', diff --git a/commands/admin/self_roles/slash.js b/commands/admin/self_roles/slash.js index 2821f51..560967c 100644 --- a/commands/admin/self_roles/slash.js +++ b/commands/admin/self_roles/slash.js @@ -1,6 +1,6 @@ import { PermissionFlagsBits, SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js'; import { addSelfRoles, removeSelfRoles } from '../../../shared.js'; -import { Message } from '../../../database.js'; +import { Guild, Message } from '../../../database.js'; /** * Sends a `Message` in the current channel and registers for self roles. @@ -46,6 +46,13 @@ const registerSelfRoles = async (interaction) => { // Get message by id await channel.messages.fetch(id); + // Check if message is already registered + const found = await Message.findOne({ + where: { id } + }); + + if (found) throw new Error('Message already registered!'); + // Reply successfully to acknowledge command await interaction.reply({ content: 'Successfully fetched message!', @@ -186,8 +193,18 @@ export async function execute(interaction) { if (createNew) { try { + // Create guild if not exists + const guildData = { id: interaction.guild.id }; + await Guild.findOrCreate({ + where: guildData, + defaults: guildData + }); + // Create database entry - await Message.create({ id }); + await Message.create({ + id, + guild: interaction.guild.id + }); } catch (error) { console.error(error); diff --git a/events/members/guildMemberAdd.js b/events/members/guildMemberAdd.js index 69881c5..70aba56 100644 --- a/events/members/guildMemberAdd.js +++ b/events/members/guildMemberAdd.js @@ -15,8 +15,13 @@ export async function execute(member) { // Ignore if no none found if (roles.length === 0) return; - // Add roles to member - await member.roles.add(roles.map((role) => role.id)); - - console.info(`[INFO] Added ${roles.length} roles to user with ID '${member.user.id}'.`); + try { + // Add roles to member + await member.roles.add(roles.map((role) => role.id)); + } catch (error) { + // Missing permissions + console.error(error); + await member.user.send('Could not assign roles. Please contact server staff.'); + } + console.info(`[INFO] Added ${roles.length} roles to new member with ID '${member.user.id}'.`); } diff --git a/models/guilds.js b/models/guilds.js index 8460b8f..c537fbf 100644 --- a/models/guilds.js +++ b/models/guilds.js @@ -6,6 +6,7 @@ import { DataTypes, Sequelize } from 'sequelize'; * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. * @property {(conditions: Object) => Promise} findOne Finds one instance in the database matching the provided condition(-s). * @property {(conditions: Object) => Promise>} findAll Finds all instances in the database matching the provided condition(-s). + * @property {(conditions: Object) => Promise} findOrCreate Finds or creates an instance in the database matching the provided condition(-s) or default values. */ /** diff --git a/models/messages.js b/models/messages.js index 7dec9c6..ef3ac26 100644 --- a/models/messages.js +++ b/models/messages.js @@ -7,6 +7,7 @@ import { DataTypes, Deferrable, Sequelize } from 'sequelize'; * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. * @property {(conditions: Object) => Promise} findOne Finds one instance in the database matching the provided condition(-s). * @property {(conditions: Object) => Promise>} findAll Finds all instances in the database matching the provided condition(-s). + * @property {(conditions: Object) => Promise} findOrCreate Finds or creates an instance in the database matching the provided condition(-s) or default values. */ /** diff --git a/models/roleEmojiPairs.js b/models/roleEmojiPairs.js index 11a5946..51ee5c9 100644 --- a/models/roleEmojiPairs.js +++ b/models/roleEmojiPairs.js @@ -9,6 +9,7 @@ import { DataTypes, Deferrable, Sequelize } from 'sequelize'; * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. * @property {(conditions: Object) => Promise} findOne Finds one instance in the database matching the provided condition(-s). * @property {(conditions: Object) => Promise>} findAll Finds all instances in the database matching the provided condition(-s). + * @property {(conditions: Object) => Promise} findOrCreate Finds or creates an instance in the database matching the provided condition(-s) or default values. */ /** diff --git a/models/roles.js b/models/roles.js index 4eb70ef..fec00d9 100644 --- a/models/roles.js +++ b/models/roles.js @@ -8,6 +8,7 @@ import { DataTypes, Deferrable, Sequelize } from 'sequelize'; * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. * @property {(conditions: Object) => Promise} findOne Finds one instance in the database matching the provided condition(-s). * @property {(conditions: Object) => Promise>} findAll Finds all instances in the database matching the provided condition(-s). + * @property {(conditions: Object) => Promise} findOrCreate Finds or creates an instance in the database matching the provided condition(-s) or default values. */ /** diff --git a/models/voiceChannels.js b/models/voiceChannels.js index 84f04f8..f7f29de 100644 --- a/models/voiceChannels.js +++ b/models/voiceChannels.js @@ -9,6 +9,7 @@ import { DataTypes, Deferrable, Sequelize } from 'sequelize'; * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. * @property {(conditions: Object) => Promise} findOne Finds one instance in the database matching the provided condition(-s). * @property {(conditions: Object) => Promise>} findAll Finds all instances in the database matching the provided condition(-s). + * @property {(conditions: Object) => Promise} findOrCreate Finds or creates an instance in the database matching the provided condition(-s) or default values. */ /** diff --git a/shared.js b/shared.js index e3c21c4..ad45a61 100644 --- a/shared.js +++ b/shared.js @@ -65,10 +65,18 @@ const saveMessageData = async (id, role, emoji) => { `Existing RoleEmojiPair entry with (partial) data {message:${id},role:${role.id},emoji:${emoji}}!` ); + // Create guild if not exists + const guildData = { id: role.guild.id }; + await Guild.findOrCreate({ + where: guildData, + defaults: guildData + }); + // Create database entry for pair await RoleEmojiPair.create({ message: id, role: role.id, + guild: guildData.id, emoji: emoji.replace(/:(\s*[^:]*\s*):/, ':_:') }); }; From 3563e5be62dd96a83f8c77eb2d093e7e182f0e65 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 3 Mar 2024 17:08:47 +0100 Subject: [PATCH 124/133] download & install instructions --- README.md | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a7c2533..f308847 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,34 @@ -# Getting started +# Setup + +## Requirements + +- NodeJS (LTS; including NPM): https://nodejs.org/en/download/ +- Git: https://git-scm.com/downloads + +## Download + +Download the current source code [here](https://git.baipyr.us/Baipyrus/DiscordJS-Example/archive/main.zip). + +Or clone the repository manually: +```bash +git clone https://git.baipyr.us/Baipyrus/DiscordJS-Example.git +``` + +## Installation + +Install the required dependencies: +```bash +npm install +``` + +## Running + +Start the bot with: +```bash +npm run start +``` + +# Usage Discord utilizes different types of commands to interface with bots. The following explanations aim to explain these types. @@ -117,6 +147,7 @@ Full names: - `/custom_vc create` Description: + The bot creates a new voice channel with the given name and registers said channel for custom VC creation Options: @@ -130,11 +161,12 @@ Full names: - `/custom_vc register` Description: + Fetches any existing voice channel and registers it for custom VC creation directly Options: -- \_id: Discord Channel ID +- \_channel: Discord Channel ### remove @@ -143,11 +175,12 @@ Full names: - `/custom_vc remove` Description: + Fetches any voice channel, looks if it is registered for custom VC creation and then unregisters it Options: -- \_id: Discord Channel ID +- \_channel: Discord Channel ## Automated tasks From fe139004bf1a9b3dddaf7acf1125dcaaeec6f99d Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sun, 3 Mar 2024 17:13:59 +0100 Subject: [PATCH 125/133] bufix: added missing import --- shared.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared.js b/shared.js index ad45a61..8f75789 100644 --- a/shared.js +++ b/shared.js @@ -1,5 +1,5 @@ import { ChatInputCommandInteraction, ContextMenuCommandInteraction, Role } from 'discord.js'; -import { Message, RoleEmojiPair } from './database.js'; +import { Message, RoleEmojiPair, Guild } from './database.js'; import { readdir } from 'fs/promises'; import { config } from 'dotenv'; import { Op } from 'sequelize'; From c7aad7190e32df2fdbb919ac9f7fea3dafa6b9e6 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sat, 23 Mar 2024 22:13:38 +0100 Subject: [PATCH 126/133] added new member role explaination to README --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index f308847..7c4b852 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,50 @@ Registered Voice Channels will automatically: - be deleted once all users left said channel - grant full control over permissions, only within said channel, to the author +# New member roles + +## Name + +`/member_roles` + +## Description + +Registers roles to be assigned to new members + +## Subcommands + +### add + +Full names: + +- `/member_roles add` + +Description: + +Adds a new Role to be assigned to new members + +Options: + +- role: Discord Role + +### remove + +Full names: + +- `/member_roles remove` + +Description: + +Removes a Role from being assigned to new members + +Options: + +- role: Discord Role + +## Automated tasks + +Registered roles will automatically be assigned to new members + # TO-DO List: > **NOTE** From b8adefd0936fde80d72e70e3445d4b7a9a62de39 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sat, 23 Mar 2024 22:43:21 +0100 Subject: [PATCH 127/133] implement working version of custom channel permissions --- events/channels/voiceStateUpdate.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index 910e8b1..5118b42 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -5,7 +5,7 @@ import { GuildChannelManager, GuildChannel, VoiceState, - PermissionOverwrites + PermissionsBitField } from 'discord.js'; import { VoiceChannel } from '../../database.js'; @@ -28,20 +28,10 @@ const getChannel = async (member, guildChs, channel) => { // Create private channel with all permissions const name = member.user.username; const chName = `${name}${name.toLowerCase().endsWith('s') ? "'" : "'s"} channel`; - // Get permissions from parent - /** @type {PermissionOverwrites} */ - const { allow, deny } = channel.parent.permissionOverwrites.cache.get(member.client.user.id); const privCh = await guildChs.create({ name: chName, parent: channel.parent, - type: ChannelType.GuildVoice, - permissionOverwrites: [ - { - id: member.id, - allow, - deny - } - ] + type: ChannelType.GuildVoice }); // Save newly created channel @@ -107,6 +97,19 @@ export async function execute(oldState, newState) { const channels = newState.guild.channels; const privCh = await getChannel(member, channels, channel); + step = 'edit permissions for'; + // Edit permissionOverwrites on channel for user + await privCh.permissionOverwrites.set([ + { + id: member.id, + allow: [ + PermissionsBitField.Flags.ViewChannel, + PermissionsBitField.Flags.ManageChannels, + PermissionsBitField.Flags.ManageRoles + ] + } + ]); + step = 'move to'; // Move user to private channel await newState.setChannel(privCh); From bba161792678565022715a0eb2208d029f8e5f7c Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sat, 23 Mar 2024 22:45:10 +0100 Subject: [PATCH 128/133] bugfix: logging user id --- events/channels/voiceStateUpdate.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/events/channels/voiceStateUpdate.js b/events/channels/voiceStateUpdate.js index 5118b42..83a0590 100644 --- a/events/channels/voiceStateUpdate.js +++ b/events/channels/voiceStateUpdate.js @@ -113,7 +113,9 @@ export async function execute(oldState, newState) { step = 'move to'; // Move user to private channel await newState.setChannel(privCh); - console.info(`[INFO] User '${name}' created private channel with ID ${privCh.id}.`); + console.info( + `[INFO] User with ID '${member.id}' created private channel with ID ${privCh.id}.` + ); } catch (error) { console.error(error); await member.send(`Failed to ${step} channel! Please contact server staff.`); From 0d80761e2c9debce6b392fc1a49395017ac948e1 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sat, 23 Mar 2024 22:47:24 +0100 Subject: [PATCH 129/133] refactor: auto format --- README.md | 3 + commands/admin/member_roles/slash.js | 206 +++++++++++++-------------- events/members/guildMemberAdd.js | 54 +++---- models/guilds.js | 48 +++---- models/roles.js | 76 +++++----- 5 files changed, 195 insertions(+), 192 deletions(-) diff --git a/README.md b/README.md index 7c4b852..e706ab3 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Download the current source code [here](https://git.baipyr.us/Baipyrus/DiscordJS-Example/archive/main.zip). Or clone the repository manually: + ```bash git clone https://git.baipyr.us/Baipyrus/DiscordJS-Example.git ``` @@ -17,6 +18,7 @@ git clone https://git.baipyr.us/Baipyrus/DiscordJS-Example.git ## Installation Install the required dependencies: + ```bash npm install ``` @@ -24,6 +26,7 @@ npm install ## Running Start the bot with: + ```bash npm run start ``` diff --git a/commands/admin/member_roles/slash.js b/commands/admin/member_roles/slash.js index 181e108..e0adbb7 100644 --- a/commands/admin/member_roles/slash.js +++ b/commands/admin/member_roles/slash.js @@ -1,103 +1,103 @@ -import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js'; -import { Role, Guild } from '../../../database.js'; - -/** - * @param {Guild} guild - * @param {Role} role - */ -const registerRole = async (guild, role) => { - // Check if guild exists in database, otherwise create it - const guildData = { id: guild.id }; - await Guild.findOrCreate({ - where: guildData, - defaults: guildData - }); - - // Register role in database - await Role.create({ - guild: guild.id, - id: role.id, - assign: true - }); -}; - -export const data = new SlashCommandBuilder() - .setName('member_roles') - .setDMPermission(false) - .setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles) - .setDescription('Assigns roles to new members.') - .addSubcommand((subcommand) => - subcommand - .setName('add') - .setDescription('Registers a role to be assigned to new members.') - .addRoleOption((option) => - option - .setName('role') - .setDescription('The role to assign to new members.') - .setRequired(true) - ) - ) - .addSubcommand((subcommand) => - subcommand - .setName('remove') - .setDescription('Unregisters a role from new member assignment.') - .addRoleOption((option) => - option - .setName('role') - .setDescription('The role to unregister from assignmment.') - .setRequired(true) - ) - ); - -/** @param {ChatInputCommandInteraction} interaction */ -export async function execute(interaction) { - const { options } = interaction; - - // Get command options - const role = options.getRole('role'); - switch (options.getSubcommand()) { - case 'add': - // Search for role in database - const found = await Role.findOne({ - where: { - id: role.id - } - }); - - // Toggle role assignment if found - if (found) { - found.assign = true; - await found.save(); - // Otherwise create new database entry - } else await registerRole(interaction.guild, role); - // Reply successfully to acknowledge command - await interaction.reply({ - content: 'Successfully registered role.', - ephemeral: true - }); - - console.info(`[INFO] Registered role to be assigned with ID '${role.id}'.`); - break; - case 'remove': - // Remove role from database - const count = await Role.destroy({ - where: { - id: role.id, - assign: true - } - }); - - // Set reply based on result of deletion - let response = 'Successfully removed'; - if (count === 0) response = 'Failed to remove'; - - // Reply to acknowledge command - await interaction.reply({ - content: `${response} role from new member assignment!`, - ephemeral: true - }); - - console.info(`[INFO] Removed role to be assigned with ID '${role.id}'.`); - break; - } -} +import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js'; +import { Role, Guild } from '../../../database.js'; + +/** + * @param {Guild} guild + * @param {Role} role + */ +const registerRole = async (guild, role) => { + // Check if guild exists in database, otherwise create it + const guildData = { id: guild.id }; + await Guild.findOrCreate({ + where: guildData, + defaults: guildData + }); + + // Register role in database + await Role.create({ + guild: guild.id, + id: role.id, + assign: true + }); +}; + +export const data = new SlashCommandBuilder() + .setName('member_roles') + .setDMPermission(false) + .setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles) + .setDescription('Assigns roles to new members.') + .addSubcommand((subcommand) => + subcommand + .setName('add') + .setDescription('Registers a role to be assigned to new members.') + .addRoleOption((option) => + option + .setName('role') + .setDescription('The role to assign to new members.') + .setRequired(true) + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('remove') + .setDescription('Unregisters a role from new member assignment.') + .addRoleOption((option) => + option + .setName('role') + .setDescription('The role to unregister from assignmment.') + .setRequired(true) + ) + ); + +/** @param {ChatInputCommandInteraction} interaction */ +export async function execute(interaction) { + const { options } = interaction; + + // Get command options + const role = options.getRole('role'); + switch (options.getSubcommand()) { + case 'add': + // Search for role in database + const found = await Role.findOne({ + where: { + id: role.id + } + }); + + // Toggle role assignment if found + if (found) { + found.assign = true; + await found.save(); + // Otherwise create new database entry + } else await registerRole(interaction.guild, role); + // Reply successfully to acknowledge command + await interaction.reply({ + content: 'Successfully registered role.', + ephemeral: true + }); + + console.info(`[INFO] Registered role to be assigned with ID '${role.id}'.`); + break; + case 'remove': + // Remove role from database + const count = await Role.destroy({ + where: { + id: role.id, + assign: true + } + }); + + // Set reply based on result of deletion + let response = 'Successfully removed'; + if (count === 0) response = 'Failed to remove'; + + // Reply to acknowledge command + await interaction.reply({ + content: `${response} role from new member assignment!`, + ephemeral: true + }); + + console.info(`[INFO] Removed role to be assigned with ID '${role.id}'.`); + break; + } +} diff --git a/events/members/guildMemberAdd.js b/events/members/guildMemberAdd.js index 70aba56..55722c0 100644 --- a/events/members/guildMemberAdd.js +++ b/events/members/guildMemberAdd.js @@ -1,27 +1,27 @@ -import { Events, GuildMember } from 'discord.js'; -import { Role } from '../../database.js'; - -export const name = Events.GuildMemberAdd; -/** @param {GuildMember} member */ -export async function execute(member) { - // Find roles to be assigned in guild from database - const roles = await Role.findAll({ - where: { - guild: member.guild.id, - assign: true - } - }); - - // Ignore if no none found - if (roles.length === 0) return; - - try { - // Add roles to member - await member.roles.add(roles.map((role) => role.id)); - } catch (error) { - // Missing permissions - console.error(error); - await member.user.send('Could not assign roles. Please contact server staff.'); - } - console.info(`[INFO] Added ${roles.length} roles to new member with ID '${member.user.id}'.`); -} +import { Events, GuildMember } from 'discord.js'; +import { Role } from '../../database.js'; + +export const name = Events.GuildMemberAdd; +/** @param {GuildMember} member */ +export async function execute(member) { + // Find roles to be assigned in guild from database + const roles = await Role.findAll({ + where: { + guild: member.guild.id, + assign: true + } + }); + + // Ignore if no none found + if (roles.length === 0) return; + + try { + // Add roles to member + await member.roles.add(roles.map((role) => role.id)); + } catch (error) { + // Missing permissions + console.error(error); + await member.user.send('Could not assign roles. Please contact server staff.'); + } + console.info(`[INFO] Added ${roles.length} roles to new member with ID '${member.user.id}'.`); +} diff --git a/models/guilds.js b/models/guilds.js index c537fbf..08e17e9 100644 --- a/models/guilds.js +++ b/models/guilds.js @@ -1,24 +1,24 @@ -import { DataTypes, Sequelize } from 'sequelize'; - -/** - * @typedef {Object} Guild - * @property {string} id A Discord guild ID. - * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. - * @property {(conditions: Object) => Promise} findOne Finds one instance in the database matching the provided condition(-s). - * @property {(conditions: Object) => Promise>} findAll Finds all instances in the database matching the provided condition(-s). - * @property {(conditions: Object) => Promise} findOrCreate Finds or creates an instance in the database matching the provided condition(-s) or default values. - */ - -/** - * The definition of the `Guild` table in the database. - * @param {Sequelize} sequelize - * @returns {Guild} - */ -export default function (sequelize) { - return sequelize.define('Guilds', { - id: { - type: DataTypes.STRING, - primaryKey: true - } - }); -} +import { DataTypes, Sequelize } from 'sequelize'; + +/** + * @typedef {Object} Guild + * @property {string} id A Discord guild ID. + * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. + * @property {(conditions: Object) => Promise} findOne Finds one instance in the database matching the provided condition(-s). + * @property {(conditions: Object) => Promise>} findAll Finds all instances in the database matching the provided condition(-s). + * @property {(conditions: Object) => Promise} findOrCreate Finds or creates an instance in the database matching the provided condition(-s) or default values. + */ + +/** + * The definition of the `Guild` table in the database. + * @param {Sequelize} sequelize + * @returns {Guild} + */ +export default function (sequelize) { + return sequelize.define('Guilds', { + id: { + type: DataTypes.STRING, + primaryKey: true + } + }); +} diff --git a/models/roles.js b/models/roles.js index fec00d9..5f26749 100644 --- a/models/roles.js +++ b/models/roles.js @@ -1,38 +1,38 @@ -import { DataTypes, Deferrable, Sequelize } from 'sequelize'; - -/** - * @typedef {Object} Role - * @property {string} id A Discord role ID. - * @property {boolean} assign Whether or not the role should be assigned to new members. - * @property {string} guild A Discord guild ID as a foreign key reference. - * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. - * @property {(conditions: Object) => Promise} findOne Finds one instance in the database matching the provided condition(-s). - * @property {(conditions: Object) => Promise>} findAll Finds all instances in the database matching the provided condition(-s). - * @property {(conditions: Object) => Promise} findOrCreate Finds or creates an instance in the database matching the provided condition(-s) or default values. - */ - -/** - * The definition of the `Role` table in the database. - * @param {Sequelize} sequelize - * @returns {Role} - */ -export default function (sequelize) { - return sequelize.define('Roles', { - id: { - type: DataTypes.STRING, - primaryKey: true - }, - assign: { - type: DataTypes.BOOLEAN, - defaultValue: false - }, - guild: { - type: DataTypes.STRING, - references: { - deferrable: Deferrable.INITIALLY_IMMEDIATE, - model: 'Guilds', - key: 'id' - } - } - }); -} +import { DataTypes, Deferrable, Sequelize } from 'sequelize'; + +/** + * @typedef {Object} Role + * @property {string} id A Discord role ID. + * @property {boolean} assign Whether or not the role should be assigned to new members. + * @property {string} guild A Discord guild ID as a foreign key reference. + * @property {(model: Object) => void} hasMany Defines an One-To-Many relationship. + * @property {(conditions: Object) => Promise} findOne Finds one instance in the database matching the provided condition(-s). + * @property {(conditions: Object) => Promise>} findAll Finds all instances in the database matching the provided condition(-s). + * @property {(conditions: Object) => Promise} findOrCreate Finds or creates an instance in the database matching the provided condition(-s) or default values. + */ + +/** + * The definition of the `Role` table in the database. + * @param {Sequelize} sequelize + * @returns {Role} + */ +export default function (sequelize) { + return sequelize.define('Roles', { + id: { + type: DataTypes.STRING, + primaryKey: true + }, + assign: { + type: DataTypes.BOOLEAN, + defaultValue: false + }, + guild: { + type: DataTypes.STRING, + references: { + deferrable: Deferrable.INITIALLY_IMMEDIATE, + model: 'Guilds', + key: 'id' + } + } + }); +} From 4653af6f9003b23f9269d81e412389318c0e978c Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sat, 23 Mar 2024 22:49:42 +0100 Subject: [PATCH 130/133] bugfix: missing import for JSDoc --- commands/admin/member_roles/slash.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands/admin/member_roles/slash.js b/commands/admin/member_roles/slash.js index e0adbb7..6c1c737 100644 --- a/commands/admin/member_roles/slash.js +++ b/commands/admin/member_roles/slash.js @@ -1,4 +1,4 @@ -import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js'; +import { SlashCommandBuilder, PermissionFlagsBits, ChatInputCommandInteraction } from 'discord.js'; import { Role, Guild } from '../../../database.js'; /** From 54dcfddd1a98bcb01d5eb9551f23950ace2abe80 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sat, 23 Mar 2024 22:49:57 +0100 Subject: [PATCH 131/133] bugfix(linting): case block scope for variable usage --- commands/admin/member_roles/slash.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/commands/admin/member_roles/slash.js b/commands/admin/member_roles/slash.js index 6c1c737..90edb41 100644 --- a/commands/admin/member_roles/slash.js +++ b/commands/admin/member_roles/slash.js @@ -56,7 +56,7 @@ export async function execute(interaction) { // Get command options const role = options.getRole('role'); switch (options.getSubcommand()) { - case 'add': + case 'add': { // Search for role in database const found = await Role.findOne({ where: { @@ -78,7 +78,8 @@ export async function execute(interaction) { console.info(`[INFO] Registered role to be assigned with ID '${role.id}'.`); break; - case 'remove': + } + case 'remove': { // Remove role from database const count = await Role.destroy({ where: { @@ -99,5 +100,6 @@ export async function execute(interaction) { console.info(`[INFO] Removed role to be assigned with ID '${role.id}'.`); break; + } } } From 1253c1acf4fd9db793e9dc5cd86381b8f24360a6 Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sat, 23 Mar 2024 22:55:57 +0100 Subject: [PATCH 132/133] remove permissionOverwrite bug in custom_vc from README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e706ab3..6023049 100644 --- a/README.md +++ b/README.md @@ -249,7 +249,7 @@ Registered roles will automatically be assigned to new members ## Quirks/Bugs -- Users are currently unable to overwrite permissions of temporarily generated custom VCs +None ## Changes From 94b079779751bc69ad829dce1eefc388e567fd1d Mon Sep 17 00:00:00 2001 From: Baipyrus Date: Sat, 23 Mar 2024 22:57:12 +0100 Subject: [PATCH 133/133] version upgrade: alpha release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 52edc93..539865e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-bot", - "version": "0.2.0", + "version": "0.3.0", "description": "", "private": true, "main": "index.js",