From 933b968fafd253ae2253e7de71a11c7649983c56 Mon Sep 17 00:00:00 2001 From: Anri Date: Sun, 3 Jul 2022 21:09:57 +0200 Subject: [PATCH] Add slash commands support (#20) Reviewed-on: https://git.kennel.ml/ConfrerieDuKassoulait/Botanique/pulls/20 --- package-lock.json | 106 +++++++++++++++++-- package.json | 2 + src/commands/loader.js | 37 +++++++ src/commands/misc/ping.js | 17 +++ src/events/interactions/interactionCreate.js | 13 +++ src/index.js | 17 ++- src/utils/client.js | 6 +- 7 files changed, 186 insertions(+), 12 deletions(-) create mode 100644 src/commands/loader.js create mode 100644 src/commands/misc/ping.js create mode 100644 src/events/interactions/interactionCreate.js diff --git a/package-lock.json b/package-lock.json index c1cfce4..8c1f7d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "0.0.1", "license": "GPL-3.0-only", "dependencies": { + "@discordjs/rest": "^0.5.0", + "discord-api-types": "^0.36.0", "discord.js": "^13.8.1" }, "devDependencies": { @@ -33,6 +35,11 @@ "node": ">=16.9.0" } }, + "node_modules/@discordjs/builders/node_modules/discord-api-types": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.33.5.tgz", + "integrity": "sha512-dvO5M52v7m7Dy96+XUnzXNsQ/0npsYpU6dL205kAtEDueswoz3aU3bh1UMoK4cQmcGtB1YRyLKqp+DXi05lzFg==" + }, "node_modules/@discordjs/collection": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.7.0.tgz", @@ -41,6 +48,27 @@ "node": ">=16.9.0" } }, + "node_modules/@discordjs/rest": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-0.5.0.tgz", + "integrity": "sha512-S4E1YNz1UxgUfMPpMeqzPPkCfXE877zOsvKM5WEmwIhcpz1PQV7lzqlEOuz194UuwOJLLjQFBgQELnQfCX9UfA==", + "dependencies": { + "@discordjs/collection": "^0.7.0", + "@sapphire/async-queue": "^1.3.1", + "@sapphire/snowflake": "^3.2.2", + "discord-api-types": "^0.33.3", + "tslib": "^2.4.0", + "undici": "^5.4.0" + }, + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/@discordjs/rest/node_modules/discord-api-types": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.33.5.tgz", + "integrity": "sha512-dvO5M52v7m7Dy96+XUnzXNsQ/0npsYpU6dL205kAtEDueswoz3aU3bh1UMoK4cQmcGtB1YRyLKqp+DXi05lzFg==" + }, "node_modules/@eslint/eslintrc": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", @@ -99,6 +127,15 @@ "npm": ">=7.0.0" } }, + "node_modules/@sapphire/snowflake": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.2.2.tgz", + "integrity": "sha512-ula2O0kpSZtX9rKXNeQMrHwNd7E4jPDJYUXmEGTFdMRfyfMw+FPyh04oKMjAiDuOi64bYgVkOV3MjK+loImFhQ==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -595,9 +632,9 @@ } }, "node_modules/discord-api-types": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.33.5.tgz", - "integrity": "sha512-dvO5M52v7m7Dy96+XUnzXNsQ/0npsYpU6dL205kAtEDueswoz3aU3bh1UMoK4cQmcGtB1YRyLKqp+DXi05lzFg==" + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.36.0.tgz", + "integrity": "sha512-bazR7FWko6JY4xwoa4Ds4SCRTKGvbzq2ivAuZxiR79RJipU+IXYNvy4tiUt8ixcs2bI04JOQwgHvz491lruBaw==" }, "node_modules/discord.js": { "version": "13.8.1", @@ -619,6 +656,11 @@ "npm": ">=7.0.0" } }, + "node_modules/discord.js/node_modules/discord-api-types": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.33.5.tgz", + "integrity": "sha512-dvO5M52v7m7Dy96+XUnzXNsQ/0npsYpU6dL205kAtEDueswoz3aU3bh1UMoK4cQmcGtB1YRyLKqp+DXi05lzFg==" + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2037,6 +2079,14 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, + "node_modules/undici": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.6.0.tgz", + "integrity": "sha512-mc+8SY1fXubTrdx4CXDkeFFGV8lI3Tq4I/70U1V8Z6g4iscGII0uLO7CPnDt56bXEbvaKwo2T2+VrteWbZiXiQ==", + "engines": { + "node": ">=12.18" + } + }, "node_modules/unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", @@ -2252,6 +2302,13 @@ "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.1", "tslib": "^2.4.0" + }, + "dependencies": { + "discord-api-types": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.33.5.tgz", + "integrity": "sha512-dvO5M52v7m7Dy96+XUnzXNsQ/0npsYpU6dL205kAtEDueswoz3aU3bh1UMoK4cQmcGtB1YRyLKqp+DXi05lzFg==" + } } }, "@discordjs/collection": { @@ -2259,6 +2316,26 @@ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.7.0.tgz", "integrity": "sha512-R5i8Wb8kIcBAFEPLLf7LVBQKBDYUL+ekb23sOgpkpyGT+V4P7V83wTxcsqmX+PbqHt4cEHn053uMWfRqh/Z/nA==" }, + "@discordjs/rest": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-0.5.0.tgz", + "integrity": "sha512-S4E1YNz1UxgUfMPpMeqzPPkCfXE877zOsvKM5WEmwIhcpz1PQV7lzqlEOuz194UuwOJLLjQFBgQELnQfCX9UfA==", + "requires": { + "@discordjs/collection": "^0.7.0", + "@sapphire/async-queue": "^1.3.1", + "@sapphire/snowflake": "^3.2.2", + "discord-api-types": "^0.33.3", + "tslib": "^2.4.0", + "undici": "^5.4.0" + }, + "dependencies": { + "discord-api-types": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.33.5.tgz", + "integrity": "sha512-dvO5M52v7m7Dy96+XUnzXNsQ/0npsYpU6dL205kAtEDueswoz3aU3bh1UMoK4cQmcGtB1YRyLKqp+DXi05lzFg==" + } + } + }, "@eslint/eslintrc": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", @@ -2303,6 +2380,11 @@ "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.4.0.tgz", "integrity": "sha512-uV+vErdfbxCgnjgcwkPDADlyS40I20L57YPy254LKbRNfLCg4/ymy510aNSGhLhq/dpNU0s1fQnTbI2YAetzsA==" }, + "@sapphire/snowflake": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.2.2.tgz", + "integrity": "sha512-ula2O0kpSZtX9rKXNeQMrHwNd7E4jPDJYUXmEGTFdMRfyfMw+FPyh04oKMjAiDuOi64bYgVkOV3MjK+loImFhQ==" + }, "@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -2678,9 +2760,9 @@ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, "discord-api-types": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.33.5.tgz", - "integrity": "sha512-dvO5M52v7m7Dy96+XUnzXNsQ/0npsYpU6dL205kAtEDueswoz3aU3bh1UMoK4cQmcGtB1YRyLKqp+DXi05lzFg==" + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.36.0.tgz", + "integrity": "sha512-bazR7FWko6JY4xwoa4Ds4SCRTKGvbzq2ivAuZxiR79RJipU+IXYNvy4tiUt8ixcs2bI04JOQwgHvz491lruBaw==" }, "discord.js": { "version": "13.8.1", @@ -2696,6 +2778,13 @@ "form-data": "^4.0.0", "node-fetch": "^2.6.1", "ws": "^8.7.0" + }, + "dependencies": { + "discord-api-types": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.33.5.tgz", + "integrity": "sha512-dvO5M52v7m7Dy96+XUnzXNsQ/0npsYpU6dL205kAtEDueswoz3aU3bh1UMoK4cQmcGtB1YRyLKqp+DXi05lzFg==" + } } }, "doctrine": { @@ -3768,6 +3857,11 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, + "undici": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.6.0.tgz", + "integrity": "sha512-mc+8SY1fXubTrdx4CXDkeFFGV8lI3Tq4I/70U1V8Z6g4iscGII0uLO7CPnDt56bXEbvaKwo2T2+VrteWbZiXiQ==" + }, "unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", diff --git a/package.json b/package.json index 7c8a43d..d44ccaa 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "author": "La confrérie du Kassoulait", "license": "GPL-3.0-only", "dependencies": { + "@discordjs/rest": "^0.5.0", + "discord-api-types": "^0.36.0", "discord.js": "^13.8.1" }, "devDependencies": { diff --git a/src/commands/loader.js b/src/commands/loader.js new file mode 100644 index 0000000..b815286 --- /dev/null +++ b/src/commands/loader.js @@ -0,0 +1,37 @@ +import { REST } from '@discordjs/rest'; +import { Routes } from 'discord-api-types/v9'; +import { readdir } from 'fs/promises'; + +export default async client => { + const rest = new REST({ version: '9' }).setToken(client.token); + + const command_categories = (await readdir('./src/commands')) + .filter(element => !element.endsWith('.js')); + + const commands = ( + await Promise.all( + // For each categorie + command_categories.map(async command_category => { + // Retrieve all the commands + const command_files = await readdir(`./src/commands/${command_category}`); + + // Add the command + return Promise.all( + command_files.map(async command_file => { + const command = ( + await import(`../commands/${command_category}/${command_file}`) + ).default; + + client.commands.set(command.data.name, command); + + return command.data.toJSON(); + }), + ); + }), + ) + ).flat(2); + + return await rest.put(Routes.applicationCommands(client.user.id), { + body: commands, + }); +}; diff --git a/src/commands/misc/ping.js b/src/commands/misc/ping.js new file mode 100644 index 0000000..ad4f81a --- /dev/null +++ b/src/commands/misc/ping.js @@ -0,0 +1,17 @@ +import { SlashCommandBuilder } from '@discordjs/builders'; + +export default { + data: new SlashCommandBuilder() + .setName('ping') + .setDescription('Pong!'), + + interaction: async (interaction, client) => { + + const sent = await interaction.reply({ content: 'Pinging...', fetchReply: true }); + + interaction.editReply( + `Roundtrip latency: \ +${sent.createdTimestamp - interaction.createdTimestamp}ms +Websocket heartbeat: ${client.ws.ping}ms.`); + }, +}; diff --git a/src/events/interactions/interactionCreate.js b/src/events/interactions/interactionCreate.js new file mode 100644 index 0000000..bfc98af --- /dev/null +++ b/src/events/interactions/interactionCreate.js @@ -0,0 +1,13 @@ +export default (interaction, client) => { + if (interaction.isCommand()) { + const command = client.commands.get(interaction.commandName); + if (!command) { + return interaction.reply({ + content: 'Désolé la commande n\'existe probablement plus...', + ephemeral: true, + }); + } + + return command.interaction(interaction, client); + } +}; diff --git a/src/index.js b/src/index.js index e38f786..a2f4a4a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,6 @@ import loadClient from './utils/client.js'; import loadEvents from './events/loader.js'; +import loadCommands from './commands/loader.js'; const run = async () => { console.log('Starting Botanique...'); @@ -14,19 +15,25 @@ const run = async () => { .catch(() => { throw 'Client ❌'; }); - console.log('Client ✅'); + if (client) { + console.log('Client ✅'); + } await loadEvents(client) - .then(() => console.log('Events ✅')) + .then(console.log('Events ✅')) .catch(() => { throw 'Events ❌'; }); await client.login(client.config.token_discord); - console.log( - `Botanique "${client.user.username}" ${client.config.version} started!` - ); + await loadCommands(client) + .then(console.log('Commands ✅')) + .catch(() => { + throw 'Commands ❌'; + }); + + console.log(`Botanique "${client.user.username}" ${client.config.version} started!`); }; run().catch(error => console.error(error)); diff --git a/src/utils/client.js b/src/utils/client.js index 4e201c0..446ad35 100644 --- a/src/utils/client.js +++ b/src/utils/client.js @@ -1,4 +1,4 @@ -import { Client, Intents } from 'discord.js'; +import { Client, Collection, Intents } from 'discord.js'; import { readFileSync } from 'fs'; const { version } = JSON.parse(readFileSync('./package.json')); @@ -11,10 +11,14 @@ export default async () => { ], }); + // Store the client configuration client.config = { version: version, token_discord: process.env.TOKEN_DISCORD, }; + // Store the commands available + client.commands = new Collection(); + return client; };