diff --git a/discordbot/.eslintrc.json b/discordbot/.eslintrc.json index 4cd9b60..918cd1f 100644 --- a/discordbot/.eslintrc.json +++ b/discordbot/.eslintrc.json @@ -5,11 +5,12 @@ "es6": true }, "parserOptions": { - "ecmaVersion": 2021 + "ecmaVersion": 2021, + "sourceType": "module" }, "rules": { "arrow-spacing": ["warn", { "before": true, "after": true }], - "brace-style": ["error", "stroustrup", { "allowSingleLine": true }], + "brace-style": ["error", "1tbs", { "allowSingleLine": true }], "comma-dangle": ["error", "always-multiline"], "comma-spacing": "error", "comma-style": "error", diff --git a/discordbot/commands/ping.js b/discordbot/commands/ping.js new file mode 100644 index 0000000..45e4022 --- /dev/null +++ b/discordbot/commands/ping.js @@ -0,0 +1,14 @@ +const { SlashCommandBuilder } = require('discord.js'); +const { simpleEmbed } = require('../functions/embeds.js'); +const { client } = require('../index.js'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('ping') + .setDescription('Replies with Pong!'), + async execute(interaction) { + const reply = await interaction.reply({ embeds: [simpleEmbed(`Websocket heartbeat: **${client.ws.ping}ms**\n Roundtrip latency: **Pinging...**`)], fetchReply: true, emphemeral: true }); + + interaction.editReply({ embeds: [simpleEmbed(`Websocket heartbeat: **${client.ws.ping}ms**\n Roundtrip latency: **${reply.createdTimestamp - interaction.createdTimestamp}ms**`)] }); + }, +}; diff --git a/discordbot/events/interactionCreate.js b/discordbot/events/interactionCreate.js new file mode 100644 index 0000000..f9d08c2 --- /dev/null +++ b/discordbot/events/interactionCreate.js @@ -0,0 +1,22 @@ +const { Events } = require('discord.js'); + +module.exports = { + name: Events.InteractionCreate, + async execute(interaction) { + if (!interaction.isChatInputCommand()) return; + + const command = interaction.client.commands.get(interaction.commandName); + + if (!command) { + console.error(`No command matching ${interaction.commandName} was found.`); + return; + } + + try { + await command.execute(interaction); + } catch (error) { + console.error(`Error executing ${interaction.commandName}`); + console.error(error); + } + }, +}; diff --git a/discordbot/events/ready.js b/discordbot/events/ready.js new file mode 100644 index 0000000..3d6a1c1 --- /dev/null +++ b/discordbot/events/ready.js @@ -0,0 +1,12 @@ +const { Events } = require('discord.js'); +const deployCommands = require('../functions/deployCommands'); + +module.exports = { + name: Events.ClientReady, + once: true, + execute(client) { + console.log(`Ready! Logged in as ${client.user.tag}`); + + deployCommands(); + }, +}; diff --git a/discordbot/functions/deployCommands.js b/discordbot/functions/deployCommands.js new file mode 100644 index 0000000..710d9b1 --- /dev/null +++ b/discordbot/functions/deployCommands.js @@ -0,0 +1,45 @@ +const { REST, Routes } = require('discord.js'); +const fs = require('node:fs'); +const path = require('node:path'); + +module.exports = function deployCommands() { + const commands = []; + + const commandsPath = path.join(__dirname, '../commands'); + const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); + + for (const file of commandFiles) { + const filePath = path.join(commandsPath, file); + const command = require(filePath); + if ('data' in command && 'execute' in command) { + commands.push(command.data.toJSON()); + } else { + console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); + } + } + + const rest = new REST().setToken(process.env.DISCORD_TOKEN); + + (async () => { + try { + console.log(`Started refreshing ${commands.length} application (/) commands.`); + + let data; + if (process.env.GUILD_ID === 'production') { + data = await rest.put( + Routes.applicationCommands(process.env.DISCORD_APPLICATION_ID), + { body: commands }, + ); + } else { + data = await rest.put( + Routes.applicationGuildCommands(process.env.DISCORD_APPLICATION_ID, process.env.GUILD_ID), + { body: commands }, + ); + } + + console.log(`Successfully reloaded ${data.length} application (/) commands.`); + } catch (error) { + console.error(error); + } + })(); +}; diff --git a/discordbot/functions/embeds.js b/discordbot/functions/embeds.js new file mode 100644 index 0000000..4fbc46e --- /dev/null +++ b/discordbot/functions/embeds.js @@ -0,0 +1,9 @@ +const { EmbedBuilder } = require('discord.js'); + +const simpleEmbed = (value) => { + return new EmbedBuilder() + .setColor(process.env.EMBED_COLOR) + .setDescription(value); +}; + +module.exports = { simpleEmbed }; diff --git a/discordbot/index.js b/discordbot/index.js index 03b7994..2b6f873 100644 --- a/discordbot/index.js +++ b/discordbot/index.js @@ -1,12 +1,41 @@ -const { Client, Events, GatewayIntentBits } = require('discord.js'); +const { Client, GatewayIntentBits, Collection } = require('discord.js'); const dotenv = require('dotenv'); +const fs = require('node:fs'); +const path = require('node:path'); dotenv.config(); -const client = new Client({ intents: [GatewayIntentBits.Guilds] }); -client.once(Events.ClientReady, c => { - console.log(`Ready! Logged in as ${c.user.tag}`); -}); +const client = new Client({ intents: [GatewayIntentBits.Guilds] }); +exports.client = client; + +client.commands = new Collection(); + +const commandsPath = path.join(__dirname, 'commands'); +const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); + +for (const file of commandFiles) { + const filePath = path.join(commandsPath, file); + const command = require(filePath); + + if ('data' in command && 'execute' in command) { + client.commands.set(command.data.name, command); + } else { + console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); + } +} + +const eventsPath = path.join(__dirname, 'events'); +const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js')); + +for (const file of eventFiles) { + const filePath = path.join(eventsPath, file); + const event = require(filePath); + if (event.once) { + client.once(event.name, (...args) => event.execute(...args)); + } else { + client.on(event.name, (...args) => event.execute(...args)); + } +} client.login(process.env.DISCORD_TOKEN);