Skip to content

Commit

Permalink
Merge pull request #360 from aternosorg/slashcommands
Browse files Browse the repository at this point in the history
Add slash commands
  • Loading branch information
JulianVennen authored Sep 8, 2021
2 parents dee2678 + 1322291 commit bc9f61a
Show file tree
Hide file tree
Showing 29 changed files with 688 additions and 223 deletions.
4 changes: 4 additions & 0 deletions example.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,9 @@
"client_email": "[email protected]",
"private_key": "google service account private key"
}
},
"debug": {
"enabled": false,
"guild": ""
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "modbot",
"version": "1.2.0",
"version": "1.3.0",
"description": "Discord Bot for the Aternos Discord server",
"main": "index.js",
"scripts": {
Expand Down
49 changes: 46 additions & 3 deletions src/Bot.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
const {Client} = require('discord.js');
const {Client, Collection} = require('discord.js');
const Database = require('./Database');
const util = require('./util');
const fs = require('fs').promises;
const config = require('../config.json');
const Monitor = require('./Monitor');
const CommandManager = require('./CommandManager');
const SlashCommand = require('./SlashCommand');

class Bot {
static instance = new Bot();
Expand Down Expand Up @@ -58,8 +60,11 @@ class Bot {
await this.#client.login(config.auth_token);
await this.#monitor.info('Logged into Discord');

await this._loadChecks();
await this._loadFeatures();
await Promise.all([
this._loadChecks(),
this._loadFeatures(),
this._loadSlashCommands()
]);
}

async _loadChecks(){
Expand Down Expand Up @@ -115,6 +120,44 @@ class Bot {
});
}
}

async _loadSlashCommands() {
console.log('Loading slash commands!');
/**
* @type {Collection<String, SlashCommand>}
*/
const commands = new Collection();
for (const command of CommandManager.getCommandClasses()
.filter(command => command.supportsSlashCommands)) {
commands.set(command.names[0], new SlashCommand(command));
}

if (config.debug?.enabled) {
const guild = await this.#client.guilds.fetch(config.debug.guild);
await guild.commands.set(Array.from(commands.values()));
}

const commandManager = this.#client.application.commands;
await commandManager.fetch();

for (const registeredCommand of commandManager.cache.values()) {
if (commands.has(registeredCommand.name)) {
const newCommand = commands.get(registeredCommand.name);
commands.delete(registeredCommand.name);
if (newCommand.matchesDefinition(registeredCommand))
continue;
await registeredCommand.edit(newCommand);
}
else {
await registeredCommand.delete();
}
}

for (const command of commands.values()) {
await commandManager.create(command);
}
console.log('Slash commands loaded!');
}
}

module.exports = Bot;
100 changes: 75 additions & 25 deletions src/Command.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ const {
MessageActionRow,
MessageButton,
MessageAttachment,
ApplicationCommandOptionData,
CommandInteractionOptionResolver,
CommandInteractionOption,
} = require('discord.js');
const Database = require('./Database');
const defaultPrefix = require('../config.json').prefix;
const icons = require('./icons');
const CommandSource = require('./CommandSource');

class Command {
/**
Expand Down Expand Up @@ -62,11 +66,24 @@ class Command {
*/
static botPerms = [];

/**
* does this command support slash commands
* @type {boolean}
*/
static supportsSlashCommands = false;

/**
* @type {Message}
* @deprecated
*/
message;

/**
* command source
* @type {CommandSource}
*/
source;

/**
* @type {Database}
*/
Expand All @@ -79,6 +96,7 @@ class Command {

/**
* arguments passed to the command
* @deprecated get options from the {@link options OptionResolver}
* @type {String[]}
*/
args;
Expand Down Expand Up @@ -117,56 +135,88 @@ class Command {
*/
response;

/**
* @type {CommandInteractionOptionResolver}
*/
options;

/**
* call this command
* @param {Message} message
* @param {Database} database
* @param {Client} bot
* @param {CommandSource} source
* @param {Database} database
* @param {Client} bot
* @param {String} name
* @param {String} prefix
*/
constructor(message, database, bot, name, prefix) {
this.message = message;
constructor(source, database, bot, name, prefix) {
this.database = database;
this.bot = bot;
this.args = util.split(message.content.substring(prefix.length + name.length),' ');
this.name = name;
this.prefix = prefix;
this.source = source;

if (source.isInteraction) {
this.options = source.getOptions();
}
else {
this.message = source.getRaw();
const args = util.split(source.getRaw().content.substring(prefix.length + name.length), ' ');
this.args = args;
this.options = new CommandInteractionOptionResolver(bot, this.parseOptions(args));
}
}

/**
* get slash command definition
* @return {ApplicationCommandOptionData[]}
*/
static getOptions() {
return [];
}

async _loadConfigs() {
this.guildConfig = await GuildConfig.get(this.message.guild.id);
this.channelConfig = await ChannelConfig.get(this.message.channel.id);
this.userConfig = await UserConfig.get(this.message.author.id);
this.guildConfig = await GuildConfig.get(this.source.getGuild().id);
this.channelConfig = await ChannelConfig.get(this.source.getChannel().id);
this.userConfig = await UserConfig.get(this.source.getUser().id);
}

/**
* Can this user run this command?
* @return {boolean|String[]}
* @return {boolean|PermissionFlags[]}
*/
userHasPerms() {
if (this.constructor.modCommand && this.guildConfig.isMod(this.message.member))
if (this.constructor.modCommand && this.guildConfig.isMod(this.source.getMember()))
return true;
const missingPerms = [];
for (const perm of this.constructor.userPerms) {
if (!this.message.member.permissions.has(perm)) missingPerms.push(perm);
if (!this.source.getMember().permissions.has(perm)) missingPerms.push(perm);
}
return missingPerms.length ? missingPerms : true;
}

/**
* Can the bot run this command
* @return {boolean|PermissionFlags}
* @return {boolean|PermissionFlags[]}
*/
botHasPerms() {
const botMember = this.message.guild.members.resolve(this.bot.user);
const botMember = this.source.getGuild().members.resolve(this.bot.user);
const missingPerms = [];
for (const perm of this.constructor.botPerms) {
if (!botMember.permissions.has(perm)) missingPerms.push(perm);
}
return missingPerms.length ? missingPerms : true;
}

/**
* parse options from a message
* @param {String[]} args arguments
* @return {CommandInteractionOption[]}
*/
// eslint-disable-next-line no-unused-vars
parseOptions(args) {
return [];
}

/**
* execute the command
* @return {Promise<void>}
Expand All @@ -175,17 +225,17 @@ class Command {

/**
* Generate a usage embed
* @param {Message} message
* @param {CommandSource} source
* @param {String} cmd
* @param {GuildConfig} [guildConfig]
* @return {MessageEmbed}
*/
static async getUsage(message, cmd, guildConfig) {
if (!guildConfig) guildConfig = await GuildConfig.get(message.guild.id);
static async getUsage(source, cmd, guildConfig) {
if (!guildConfig) guildConfig = await GuildConfig.get(source.getGuild().id);
const prefix = guildConfig.prefix || defaultPrefix;
const embed = new MessageEmbed()
.setAuthor(`Help for ${cmd} | Prefix: ${prefix}`)
.setFooter(`Command executed by ${util.escapeFormatting(message.author.tag)}`)
.setFooter(`Command executed by ${util.escapeFormatting(source.getUser().tag)}`)
.addFields(
/** @type {any} */ { name: 'Usage', value: `\`${prefix}${cmd} ${this.usage}\``, inline: true},
/** @type {any} */ { name: 'Description', value: this.description, inline: true},
Expand Down Expand Up @@ -215,7 +265,7 @@ class Command {
* @return {Promise<void>}
*/
async sendUsage() {
await this.reply(await this.constructor.getUsage(this.message,this.name , this.guildConfig));
await this.reply(await this.constructor.getUsage(this.source,this.name , this.guildConfig));
}

/**
Expand Down Expand Up @@ -260,13 +310,13 @@ class Command {
}
}

if (this.userConfig.deleteCommands) {
this.response = await this.message.channel.send(options);
if (!this.source.isInteraction && this.userConfig.deleteCommands) {
this.response = await this.source.getChannel().send(options);
}
else {
options.failIfNotExists ??= false;
options.allowedMentions ??= {repliedUser: false};
this.response = await this.message.reply(options);
this.response = await this.source.reply(options);
}
}

Expand All @@ -285,7 +335,7 @@ class Command {

const reactions = message.createReactionCollector( {
filter: async (reaction, user) => {
if (user.id === this.message.author.id && [icons.left,icons.right].includes(reaction.emoji.name)) {
if (user.id === this.source.getUser().id && [icons.left,icons.right].includes(reaction.emoji.name)) {
return true;
}
else {
Expand Down Expand Up @@ -347,12 +397,12 @@ class Command {
.setStyle('SUCCESS')
);
/** @type {Message} */
this.response = await this.message.channel.send({content: text, components: [buttons]});
this.response = await this.source.getChannel().send({content: text, components: [buttons]});
try {
const component = await this.response.awaitMessageComponent({
max: 1, time: options.time, errors: ['time'],
filter: async (interaction) => {
if (interaction.user.id !== this.message.author.id) {
if (interaction.user.id !== this.source.getUser().id) {
await interaction.reply({
ephemeral: true,
content: 'Only the message author can do this.'
Expand Down
Loading

0 comments on commit bc9f61a

Please sign in to comment.