mirror of
https://github.com/Hessenuk/DiscordTickets.git
synced 2025-09-03 00:41:27 +03:00
Improved plugin manager and added to command manager
This commit is contained in:
@@ -4,17 +4,19 @@ const { Client, GuildMember, Guild, Channel } = require('discord.js');
|
||||
const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
const { path } = require('../../utils/fs');
|
||||
const { createMessage, flags } = require('../../utils/discord');
|
||||
const Plugin = require('../plugins/plugin');
|
||||
|
||||
/**
|
||||
* A command
|
||||
*/
|
||||
module.exports = class Command {
|
||||
/**
|
||||
* A command option choice
|
||||
* @typedef CommandOptionChoice
|
||||
* A command option choice
|
||||
* @typedef CommandOptionChoice
|
||||
* @property {string} name - Choice name (1-100)
|
||||
* @property {(string|number)} value - choice value
|
||||
*/
|
||||
*/
|
||||
|
||||
/**
|
||||
* A command option
|
||||
@@ -33,9 +35,11 @@ module.exports = class Command {
|
||||
* @param {Object} data - Command data
|
||||
* @param {string} data.name - The name of the command (3-32)
|
||||
* @param {string} data.description - The description of the command (1-100)
|
||||
* @param {boolean} staff_only - Only allow staff to use this command?
|
||||
* @param {string[]} permissions - Array of permissions needed for a user to use this command
|
||||
* @param {CommandOption[]} data.options - The command options, max of 10
|
||||
*/
|
||||
constructor(client, data) {
|
||||
constructor(client, data) {
|
||||
|
||||
/** The Discord Client */
|
||||
this.client = client;
|
||||
@@ -59,18 +63,40 @@ module.exports = class Command {
|
||||
*/
|
||||
this.description = data.description;
|
||||
|
||||
/**
|
||||
* Only allow staff to use this command?
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.staff_only = data.staff_only;
|
||||
|
||||
/**
|
||||
* Array of permissions needed for a user to use this command
|
||||
* @type {string[]}
|
||||
*/
|
||||
this.permissions = data.permissions;
|
||||
|
||||
/**
|
||||
* The command options
|
||||
* @type {CommandOption[]}
|
||||
*/
|
||||
this.options = data.options;
|
||||
|
||||
/** True if command is internal, false if it is from a plugin */
|
||||
/**
|
||||
* True if command is internal, false if it is from a plugin
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.internal = data.internal;
|
||||
|
||||
|
||||
if (!this.internal) {
|
||||
/**
|
||||
* The plugin this command belongs to, if any
|
||||
* @type {(undefined|Plugin)}
|
||||
*/
|
||||
this.plugin = this.client.plugins.plugins.find(p => p.commands?.includes(this.name));
|
||||
}
|
||||
|
||||
this.manager.check(data); // validate
|
||||
|
||||
|
||||
try {
|
||||
this.manager.register(this); // register the command
|
||||
} catch (e) {
|
||||
@@ -92,15 +118,83 @@ module.exports = class Command {
|
||||
* @property {(undefined|ApplicationCommandInteractionDataOption[])} options - Present if the option is a subcommand/subcommand group
|
||||
*/
|
||||
|
||||
/**
|
||||
* [Interaction](https://discord.com/developers/docs/interactions/slash-commands#interaction) object
|
||||
* @typedef {Object} Interaction
|
||||
* @property {string} interaction.id - ID of the interaction
|
||||
* @property {number} interaction.type - Type of interaction
|
||||
* @property {ApplicationCommandInteractionData} interaction.data - Interaction data
|
||||
* @property {Guild} interaction.guild- The guild object
|
||||
* @property {Channel} interaction.channel- The channel object
|
||||
* @property {GuildMember} interaction.member - The member object
|
||||
* @property {string} interaction.token - The token used to respond to the interaction
|
||||
*/
|
||||
|
||||
/**
|
||||
* The code to be executed when a command is invoked
|
||||
* @abstract
|
||||
* @param {Object} data - Object containing data about the command invocation
|
||||
* @param {(undefined|ApplicationCommandInteractionDataOption[])} data.args - Command arguments
|
||||
* @param {Object} data.args - Command arguments
|
||||
* @param {Channel} data.channel- The channel object
|
||||
* @param {Guild} data.guild- The guild object
|
||||
* @param {GuildMember} data.member - The member object
|
||||
* @param {string} data.token - The token used to respond to the interaction
|
||||
* @param {Interaction} interaction - Interaction object
|
||||
*/
|
||||
async execute(data) {}
|
||||
async execute(data, interaction) { }
|
||||
|
||||
/**
|
||||
* Defer the response to respond later
|
||||
* @param {Interaction} interaction - Interaction object
|
||||
* @param {boolean} secret - Ephemeral message? **NOTE: EMBEDS AND ATTACHMENTS DO NOT RENDER IF TRUE**
|
||||
*/
|
||||
async deferResponse(interaction, secret) {
|
||||
this.client.api.interactions(interaction.id, interaction.token).callback.post({
|
||||
data: {
|
||||
type: 5,
|
||||
flags: flags(secret),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an interaction response
|
||||
* @param {Interaction} interaction - Interaction object
|
||||
* @param {*} content - Message content
|
||||
* @param {boolean} secret - Ephemeral message? **NOTE: EMBEDS AND ATTACHMENTS DO NOT RENDER IF TRUE**
|
||||
*/
|
||||
async sendResponse(interaction, content, secret) {
|
||||
if (typeof content === 'object')
|
||||
this.client.api.interactions(interaction.id, interaction.token).callback.post({
|
||||
data: {
|
||||
type: 4,
|
||||
flags: flags(secret),
|
||||
data: await createMessage(this.client, interaction.channel_id, content)
|
||||
}
|
||||
});
|
||||
else
|
||||
this.client.api.interactions(interaction.id, interaction.token).callback.post({
|
||||
data: {
|
||||
type: 4,
|
||||
flags: flags(secret),
|
||||
content
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the original interaction response
|
||||
* @param {Interaction} interaction - Interaction object
|
||||
* @param {*} content - Message content
|
||||
*/
|
||||
async editResponse(interaction, content) {
|
||||
if (typeof content === 'object')
|
||||
this.client.api.interactions(interaction.id, interaction.token).messages.patch({
|
||||
embeds: content
|
||||
});
|
||||
else
|
||||
this.client.api.interactions(interaction.id, interaction.token).messages.patch({
|
||||
content
|
||||
});
|
||||
}
|
||||
};
|
@@ -11,4 +11,9 @@ module.exports = {
|
||||
CHANNEL: 7,
|
||||
ROLE: 8,
|
||||
},
|
||||
ResponseTypes: {
|
||||
Pong: 1,
|
||||
ChannelMessageWithSource: 4,
|
||||
DeferredChannelMessageWithSource: 5,
|
||||
},
|
||||
};
|
@@ -40,8 +40,9 @@ module.exports = class CommandManager {
|
||||
|
||||
/** Register a command */
|
||||
register(cmd) {
|
||||
const is_internal = (this.commands.has(cmd.name) && cmd.internal)
|
||||
|| (this.commands.has(cmd.name) && this.commands.get(cmd.name).internal);
|
||||
const exists = this.commands.has(cmd.name);
|
||||
const is_internal = (exists && cmd.internal)
|
||||
|| (exists && this.commands.get(cmd.name).internal);
|
||||
|
||||
if (is_internal) {
|
||||
let plugin = this.client.plugins.plugins.find(p => p.commands.includes(cmd.name));
|
||||
@@ -51,7 +52,7 @@ module.exports = class CommandManager {
|
||||
this.client.log.commands(`An unknown plugin has overridden the internal "${cmd.name}" command`);
|
||||
if(cmd.internal) return;
|
||||
}
|
||||
else if (this.commands.has(cmd.name))
|
||||
else if (exists)
|
||||
throw new Error(`A non-internal command with the name "${cmd.name}" already exists`);
|
||||
|
||||
this.commands.set(cmd.name, cmd);
|
||||
@@ -136,4 +137,46 @@ module.exports = class CommandManager {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command
|
||||
* @param {string} cmd_name - Name of the command
|
||||
* @param {interaction} interaction - Command interaction
|
||||
*/
|
||||
async execute(cmd_name, interaction) {
|
||||
if (!this.commands.has(cmd_name))
|
||||
throw new Error(`Unregistered command: "${cmd_name}"`);
|
||||
|
||||
let args = {};
|
||||
if (interaction.data.options)
|
||||
interaction.data.options.forEach(({ name, value }) => args[name] = value);
|
||||
|
||||
let data = { args };
|
||||
data.guild = await this.client.guilds.fetch(interaction.guild_id);
|
||||
data.channel = await this.client.channels.fetch(interaction.channel_id),
|
||||
data.member = await data.guild.members.fetch(interaction.member.user.id);
|
||||
|
||||
const cmd = this.commands.get(cmd_name);
|
||||
|
||||
// if (cmd.staff_only) {
|
||||
// return await cmd.sendResponse(interaction, msg, true);
|
||||
// }
|
||||
|
||||
const no_perm = cmd.permissions instanceof Array
|
||||
&& !data.member.hasPermission(cmd.permissions);
|
||||
if (no_perm) {
|
||||
let perms = cmd.permissions.map(p => `\`${p}\``).join(', ');
|
||||
let msg = `❌ You do not have the permissions required to use this command:\n${perms}`;
|
||||
return await cmd.sendResponse(interaction, msg, true);
|
||||
}
|
||||
|
||||
await cmd.deferResponse(interaction, true);
|
||||
|
||||
this.client.log.commands(`Executing "${cmd_name}" command (invoked by ${data.member.user.tag})`);
|
||||
|
||||
let res = await cmd.execute(data, interaction); // run the command
|
||||
|
||||
if (typeof res === 'object' || typeof res === 'string')
|
||||
cmd.sendResponse(interaction, res, res.secret);
|
||||
}
|
||||
|
||||
};
|
@@ -62,15 +62,10 @@ module.exports = class PluginManager {
|
||||
description
|
||||
};
|
||||
|
||||
this.plugins.set(id, about);
|
||||
|
||||
try {
|
||||
let plugin = new Main(this.client, id);
|
||||
plugin.load();
|
||||
|
||||
this.plugins.set(id, Object.assign(about, {
|
||||
name: plugin.name || id,
|
||||
}));
|
||||
let plugin = new Main(this.client, about);
|
||||
this.plugins.set(id, plugin);
|
||||
plugin.preload();
|
||||
|
||||
} catch (e) {
|
||||
if (npm) {
|
||||
|
@@ -17,7 +17,7 @@ module.exports = class Plugin {
|
||||
* @param {String} options.name A human-friendly name (can be different to the name in package.json)
|
||||
* @param {String[]} options.commands An array of command names the plugin registers
|
||||
*/
|
||||
constructor(client, id, options = {}) {
|
||||
constructor(client, about, options = {}) {
|
||||
/** The Discord Client */
|
||||
this.client = client;
|
||||
|
||||
@@ -28,10 +28,11 @@ module.exports = class Plugin {
|
||||
// make JSDoc happy
|
||||
|
||||
let {
|
||||
id,
|
||||
version,
|
||||
author,
|
||||
description
|
||||
} = this.manager.plugins.get(id);
|
||||
} = about;
|
||||
|
||||
/**
|
||||
* The human-friendly name of the plugin
|
||||
@@ -125,7 +126,15 @@ module.exports = class Plugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* The main function where your code should go. Create functions and event listeners here
|
||||
* The function where any code that needs to be executed before the client is ready should go.
|
||||
* **This is executed _BEFORE_ the ready event**
|
||||
* @abstract
|
||||
*/
|
||||
preload() { }
|
||||
|
||||
/**
|
||||
* The main function where your code should go. Create commands and event listeners here.
|
||||
* **This is executed _after_ the ready event**
|
||||
* @abstract
|
||||
*/
|
||||
load() {}
|
||||
|
3
src/modules/tickets/index.js
Normal file
3
src/modules/tickets/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
|
||||
};
|
Reference in New Issue
Block a user