diff --git a/.env.example b/.env.example index da34dfd0..643c55aa 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,7 @@ OWNERS= LOVER_USER_ID= XIAO_PREFIX= INVITE= +TEST_GUILD_ID= REPORT_CHANNEL_ID= JOIN_LEAVE_CHANNEL_ID= COMMAND_CHANNEL_ID= diff --git a/README.md b/README.md index 0aac044c..2f127e96 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Just read `LICENSE.md`. Give credit if you use any part of this monster, thanks. * `LOVER_USER_ID` is the Discord user ID of your significant other. This rigs certain commands, like `ship`. Optional for loners. * `XIAO_PREFIX` is the prefix the bot should have. * `INVITE` is an invite link for the bot's support server. Optional. +* `TEST_GUILD_ID` is a guild ID for the server you want to deploy test slash commands to. * `REPORT_CHANNEL_ID` is a channel ID for the bot to send reports to. Optional, by default it'll send the owners a DM. * `JOIN_LEAVE_CHANNEL_ID` is a channel ID for the bot to send server join/leave information to. Optional. * `COMMAND_CHANNEL_ID` is a channel ID for the bot to send command usage data to. Optional. diff --git a/Xiao.js b/Xiao.js index 414e6dae..34f68016 100644 --- a/Xiao.js +++ b/Xiao.js @@ -63,9 +63,19 @@ client.registry ]) .registerCommandsIn(path.join(__dirname, 'commands')); +client.slashRegistry.registerCommandsIn(path.join(__dirname, 'slash-commands')); + client.on('ready', async () => { client.logger.info(`[READY] Logged in as ${client.user.tag}! ID: ${client.user.id}`); + // Register Slash Commands in Test Server + try { + const slashCmds = await client.slashRegistry.uploadTestCommands(); + client.logger.info(`[SLASH] Registered ${slashCmds.length} slash commands for testing!`); + } catch (err) { + client.logger.error(`[SLASH] Failed to register slash commands for testing:\n${err.stack}`); + } + // Make temp directories const tmpFolderExists = await checkFileExists(path.join(__dirname, 'tmp')); if (!tmpFolderExists) await mkdir(path.join(__dirname, 'tmp')); diff --git a/framework/Client.js b/framework/Client.js index 89cce100..4b5697e7 100644 --- a/framework/Client.js +++ b/framework/Client.js @@ -4,6 +4,7 @@ const fs = require('fs'); const path = require('path'); const { stripIndents } = require('common-tags'); const Registry = require('./Registry'); +const SlashRegistry = require('./slash/SlashRegistry'); const Dispatcher = require('./Dispatcher'); require('./Extensions'); @@ -16,6 +17,7 @@ module.exports = class CommandClient extends Client { this.owner = typeof options.owner === 'string' ? [options.owner] : options.owner; this.invite = options.invite || null; this.registry = new Registry(this); + this.slashRegistry = new SlashRegistry(this); this.dispatcher = new Dispatcher(this); this.games = new Collection(); this.blacklist = { user: [], guild: [] }; @@ -23,6 +25,7 @@ module.exports = class CommandClient extends Client { this.once('ready', this.onceReady); this.on('messageCreate', this.onMessage); + this.on('interactionCreate', this.onInteractionCreate); } isOwner(user) { @@ -149,6 +152,25 @@ module.exports = class CommandClient extends Client { } } + async onInteractionCreate(interaction) { + if (!interaction.isChatInputCommand()) return; + + const command = this.slashRegistry.commands.get(interaction.commandName); + if (!command) return; + + try { + const result = await command.run(interaction); + this.emit('commandRun', command, result, interaction); + } catch (err) { + this.emit('commandError', interaction, err); + 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 }); + } + } + } + importBlacklist() { const read = fs.readFileSync(path.join(__dirname, '..', 'blacklist.json'), { encoding: 'utf8' }); const file = JSON.parse(read); diff --git a/framework/slash/SlashCommand.js b/framework/slash/SlashCommand.js new file mode 100644 index 00000000..16291b20 --- /dev/null +++ b/framework/slash/SlashCommand.js @@ -0,0 +1,14 @@ +module.exports = class SlashCommand { + constructor(client, options) { + Object.defineProperty(this, 'client', { value: client }); + + this.name = options.name.toLowerCase(); + this.description = options.description; + this.credit = options.credit || []; + this.credit.push({ + name: 'Dragon Fire', + url: 'https://github.com/dragonfire535', + reason: 'Code' + }); + } +}; diff --git a/framework/slash/SlashRegistry.js b/framework/slash/SlashRegistry.js new file mode 100644 index 00000000..f355c1c0 --- /dev/null +++ b/framework/slash/SlashRegistry.js @@ -0,0 +1,45 @@ +const { SlashCommandBuilder, Routes } = require('discord.js'); +const { Collection } = require('@discordjs/collection'); +const fs = require('fs'); +const path = require('path'); +const { TEST_GUILD_ID } = process.env; + +module.exports = class SlashRegistry { + constructor(client) { + Object.defineProperty(this, 'client', { value: client }); + + this.commands = new Collection(); + } + + registerCommand(command) { + const slashCmd = new SlashCommandBuilder() + .setName(command.name) + .setDescription(command.description); + this.commands.set(command.name, { command, data: slashCmd }); + return this; + } + + registerCommandsIn(dir) { + const commands = fs.readdirSync(dir); + for (const command of commands) { + if (!command.endsWith('.js')) continue; + const Required = require(path.join(dir, command)); + this.registerCommand(new Required(this.client)); + } + return this; + } + + uploadTestCommands() { + return this.client.rest.put( + Routes.applicationGuildCommands(this.client.id, TEST_GUILD_ID), + { body: this.commands.map(cmd => cmd.data.toJSON()) } + ); + } + + uploadGlobalCommands() { + return this.client.rest.put( + Routes.applicationCommands(this.client.id), + { body: commands } + ); + } +}; diff --git a/slash-commands/lenny.js b/slash-commands/lenny.js new file mode 100644 index 00000000..76ae8913 --- /dev/null +++ b/slash-commands/lenny.js @@ -0,0 +1,14 @@ +const SlashCommand = require('../framework/slash/SlashCommand'); + +module.exports = class LennyCommand extends SlashCommand { + constructor(client) { + super(client, { + name: 'lenny', + description: 'Responds with a lenny face.' + }); + } + + run(interaction) { + return interaction.reply('( ͡° ͜ʖ ͡°)'); + } +};