mirror of
https://github.com/Hessenuk/DiscordTickets.git
synced 2025-09-03 00:41:27 +03:00
Command handler stuff, presences, other stuff
This commit is contained in:
@@ -1,92 +1,106 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
const { Client } = require('discord.js');
|
||||
const { Client, GuildMember, Guild, Channel } = require('discord.js');
|
||||
|
||||
const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
const { path } = require('../../utils/fs');
|
||||
|
||||
/** A plugin */
|
||||
module.exports = class Plugin {
|
||||
/**
|
||||
* A command
|
||||
*/
|
||||
module.exports = class Command {
|
||||
/**
|
||||
* Create a new Plugin
|
||||
* @param {Client} client The Discord Client
|
||||
* @param {String} id The plugin ID
|
||||
* @param {Object} options Plugin options
|
||||
* @param {String} options.name A human-friendly name (can be different to the name in package.json)
|
||||
* A command option choice
|
||||
* @typedef CommandOptionChoice
|
||||
* @property {string} name - Choice name (1-100)
|
||||
* @property {(string|number)} value - choice value
|
||||
*/
|
||||
|
||||
/**
|
||||
* A command option
|
||||
* @typedef CommandOption
|
||||
* @property {number} type - [ApplicationCommandOptionType](https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptiontype)
|
||||
* @property {string} name - Option name (1-32)
|
||||
* @property {string} description - Option description (1-100)
|
||||
* @property {boolean} [required] - Required?
|
||||
* @property {CommandOptionChoice[]} [choices] - Array of choices
|
||||
* @property {CommandOption[]} [options] - Array of options if this option is a subcommand/subcommand group
|
||||
*/
|
||||
constructor(client, id, options = {}) {
|
||||
/** The human-friendly name of the plugin */
|
||||
this.name = options.name || id;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new Command
|
||||
* @param {Client} client - The Discord Client
|
||||
* @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 {CommandOption[]} data.options - The command options, max of 10
|
||||
*/
|
||||
constructor(client, data) {
|
||||
|
||||
/** The Discord Client */
|
||||
this.client = client;
|
||||
|
||||
/** The PluginManager */
|
||||
this.manager = this.client.plugins;
|
||||
/** The CommandManager */
|
||||
this.manager = this.client.commands;
|
||||
|
||||
// Object.assign(this, this.manager.plugins.get(id));
|
||||
// make JSDoc happy
|
||||
if (typeof data !== 'object') {
|
||||
throw new TypeError(`Expected type of data to be an object, got ${typeof data}`);
|
||||
}
|
||||
|
||||
let {
|
||||
version,
|
||||
author,
|
||||
description
|
||||
} = this.manager.plugins.get(id);
|
||||
/**
|
||||
* The name of the command
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = data.name;
|
||||
|
||||
/** The unique ID of the plugin (NPM package name) */
|
||||
this.id = id;
|
||||
/**
|
||||
* The command description
|
||||
* @type {string}
|
||||
*/
|
||||
this.description = data.description;
|
||||
|
||||
/** The version of the plugin (NPM package version) */
|
||||
this.version = version;
|
||||
/**
|
||||
* The command options
|
||||
* @type {CommandOption[]}
|
||||
*/
|
||||
this.options = data.options;
|
||||
|
||||
/** The plugin author's name (NPM package author) */
|
||||
this.author = author;
|
||||
/** True if command is internal, false if it is from a plugin */
|
||||
this.internal = data.internal;
|
||||
|
||||
/** The plugin description (NPM package description) */
|
||||
this.description = description;
|
||||
|
||||
this.directory = {};
|
||||
|
||||
/** A cleaned version of the plugin's ID suitable for use in the directory name */
|
||||
this.directory.name = this.id.replace(/@[-_a-zA-Z0-9]+\//, '');
|
||||
|
||||
/** The absolute path of the plugin directory */
|
||||
this.directory.path = path(`./user/plugins/${this.directory.name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the plugin directory if it doesn't already exist
|
||||
* @returns {Boolean} True if created, false if it already existed
|
||||
*/
|
||||
createDirectory() {
|
||||
if (!fs.existsSync(this.directory.path)) {
|
||||
this.client.log.plugins(`Creating plugin directory for "${this.name}"`);
|
||||
fs.mkdirSync(this.directory.path);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
this.manager.check(data); // validate
|
||||
|
||||
try {
|
||||
this.manager.register(this); // register the command
|
||||
} catch (e) {
|
||||
return this.client.log.error(e);
|
||||
}
|
||||
|
||||
this.client.api.applications(this.client.user.id).commands.post({ data }); // post command to Discord
|
||||
|
||||
let internal = this.internal ? 'internal ' : '';
|
||||
this.client.log.commands(`Loaded ${internal}"${this.name}" command`);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the plugin config file if it doesn't already exist
|
||||
* @param {Object} template The default config template
|
||||
* @returns {Boolean} True if created, false if it already existed
|
||||
* [ApplicationCommandInteractionDataOption](https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondataoption)
|
||||
* @typedef {Object} ApplicationCommandInteractionDataOption
|
||||
* @property {string} name - Name of the parameter
|
||||
* @property {*} value - The value
|
||||
* @property {(undefined|ApplicationCommandInteractionDataOption[])} options - Present if the option is a subcommand/subcommand group
|
||||
*/
|
||||
createConfig(template) {
|
||||
this.createDirectory();
|
||||
let file = join(this.directory.path, 'config.json');
|
||||
if (!fs.existsSync(file)) {
|
||||
this.client.log.plugins(`Creating plugin config file for "${this.name}"`);
|
||||
fs.writeFileSync(file, JSON.stringify(template, null, 2));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The main function where your code should go. Create functions and event listeners here
|
||||
* 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 {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
|
||||
*/
|
||||
load() {}
|
||||
async execute(data) {}
|
||||
};
|
@@ -1,4 +1,14 @@
|
||||
module.exports = {
|
||||
CommandManager: require('./manager'),
|
||||
Command: require('./command')
|
||||
Command: require('./command'),
|
||||
OptionTypes: {
|
||||
SUB_COMMAND: 1,
|
||||
SUB_COMMAND_GROUP: 2,
|
||||
STRING: 3,
|
||||
INTEGER: 4,
|
||||
BOOLEAN: 5,
|
||||
USER: 6,
|
||||
CHANNEL: 7,
|
||||
ROLE: 8,
|
||||
},
|
||||
};
|
@@ -6,7 +6,9 @@ const Command = require('./command');
|
||||
const fs = require('fs');
|
||||
const { path } = require('../../utils/fs');
|
||||
|
||||
/** Manages the loading of commands */
|
||||
/**
|
||||
* Manages the loading of commands
|
||||
*/
|
||||
module.exports = class CommandManager {
|
||||
/**
|
||||
* Create a CommandManager instance
|
||||
@@ -15,6 +17,7 @@ module.exports = class CommandManager {
|
||||
constructor(client) {
|
||||
/** The Discord Client */
|
||||
this.client = client;
|
||||
|
||||
/** A discord.js Collection (Map) of loaded commands */
|
||||
this.commands = new Collection();
|
||||
}
|
||||
@@ -24,10 +27,113 @@ module.exports = class CommandManager {
|
||||
const files = fs.readdirSync(path('./src/commands'))
|
||||
.filter(file => file.endsWith('.js'));
|
||||
|
||||
for (const file of files) {
|
||||
const cmd = require(`../commands/${file}`);
|
||||
this.commands.set(cmd, new cmd(this.client));
|
||||
for (let file of files) {
|
||||
try {
|
||||
file = require(`../../commands/${file}`);
|
||||
new file(this.client);
|
||||
} catch (e) {
|
||||
this.client.log.warn('An error occurred whilst loading an internal command');
|
||||
this.client.log.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 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);
|
||||
|
||||
if (is_internal) {
|
||||
let plugin = this.client.plugins.plugins.find(p => p.commands.includes(cmd.name));
|
||||
if (plugin)
|
||||
this.client.log.commands(`The "${plugin.name}" plugin has overridden the internal "${cmd.name}" command`);
|
||||
else
|
||||
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))
|
||||
throw new Error(`A non-internal command with the name "${cmd.name}" already exists`);
|
||||
|
||||
this.commands.set(cmd.name, cmd);
|
||||
}
|
||||
|
||||
/** Check the command data */
|
||||
check(data) {
|
||||
|
||||
if (typeof data.name !== 'string')
|
||||
throw new TypeError(`Expected type of command name to be a string, got ${typeof data.name}`);
|
||||
|
||||
if (data.name.length < 3 || data.name.length > 32)
|
||||
throw new TypeError('Length of command name must be 3-32');
|
||||
|
||||
if (typeof data.description !== 'string')
|
||||
throw new TypeError(`Expected type of command description to be a string, got ${typeof data.description}`);
|
||||
|
||||
if (data.name.length < 1 || data.name.length > 100)
|
||||
throw new TypeError('Length of description must be 3-32');
|
||||
|
||||
if (typeof data.options !== 'undefined' && !(data.options instanceof Array))
|
||||
throw new TypeError(`Expected type of command options to be undefined or an array, got ${typeof data.options}`);
|
||||
|
||||
if (data.options)
|
||||
this.checkOptions(data.options);
|
||||
|
||||
}
|
||||
|
||||
/** Check the command data's options */
|
||||
checkOptions(options) {
|
||||
let num = 0;
|
||||
options.forEach(o => {
|
||||
if (typeof o.type !== 'number')
|
||||
throw new TypeError(`Expected type of option ${num} type to be a number, got ${typeof o.type}`);
|
||||
|
||||
if (typeof o.name !== 'string')
|
||||
throw new TypeError(`Expected type of option ${num} name to be a string, got ${typeof o.name}`);
|
||||
|
||||
if (o.name.length < 3 || o.name.length > 32)
|
||||
throw new TypeError(`Length of option ${num} name must be 3-32`);
|
||||
|
||||
if (typeof o.description !== 'string')
|
||||
throw new TypeError(`Expected type of option ${num} description to be a string, got ${typeof o.description}`);
|
||||
|
||||
if (o.description.length < 1 || o.description.length > 100)
|
||||
throw new TypeError(`Length of option ${num} description must be 1-100`);
|
||||
|
||||
if (typeof o.required !== 'undefined' && typeof o.required !== 'boolean')
|
||||
throw new TypeError(`Expected type of option ${num} required to be undefined or a boolean, got ${typeof o.required}`);
|
||||
|
||||
if (typeof o.choices !== 'undefined' && !(o.choices instanceof Array))
|
||||
throw new TypeError(`Expected type of option ${num} choices to be undefined or an array, got ${typeof o.choices}`);
|
||||
|
||||
if (o.choices)
|
||||
this.checkOptionChoices(o.choices);
|
||||
|
||||
if (typeof o.options !== 'undefined' && !(o.options instanceof Array))
|
||||
throw new TypeError(`Expected type of option ${num} options to be undefined or an array, got ${typeof o.options}`);
|
||||
|
||||
if (o.options)
|
||||
this.checkOptions(o.options);
|
||||
|
||||
num++;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/** Check command option choices */
|
||||
checkOptionChoices(choices) {
|
||||
let num = 0;
|
||||
choices.forEach(c => {
|
||||
if (typeof c.name !== 'string')
|
||||
throw new TypeError(`Expected type of option choice ${num} name to be a string, got ${typeof c.name}`);
|
||||
|
||||
if (c.name.length < 1 || c.name.length > 100)
|
||||
throw new TypeError(`Length of option choice ${num} name must be 1-100`);
|
||||
|
||||
if (typeof c.value !== 'string' && typeof c.value !== 'number')
|
||||
throw new TypeError(`Expected type of option choice ${num} value to be a string or number, got ${typeof c.value}`);
|
||||
|
||||
num++;
|
||||
});
|
||||
}
|
||||
|
||||
};
|
@@ -7,7 +7,11 @@ module.exports = client => {
|
||||
|
||||
for (const file of files) {
|
||||
const listener = require(`../listeners/${file}`);
|
||||
const exec = (...args) => listener.execute(client, ...args);
|
||||
let on = listener.once ? 'once' : 'on';
|
||||
client[on](listener.event, (...args) => listener.execute(client, ...args));
|
||||
if (listener.raw)
|
||||
client.ws[on](listener.event, exec);
|
||||
else
|
||||
client[on](listener.event, exec);
|
||||
}
|
||||
};
|
@@ -7,7 +7,9 @@ const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
const { path } = require('../../utils/fs');
|
||||
|
||||
/** Manages the loading of plugins */
|
||||
/**
|
||||
* Manages the loading of plugins
|
||||
*/
|
||||
module.exports = class PluginManager {
|
||||
/**
|
||||
* Create a PluginManager instance
|
||||
@@ -16,6 +18,7 @@ module.exports = class PluginManager {
|
||||
constructor(client) {
|
||||
/** The Discord Client */
|
||||
this.client = client;
|
||||
|
||||
/** A discord.js Collection (Map) of loaded plugins */
|
||||
this.plugins = new Collection();
|
||||
|
||||
|
@@ -5,7 +5,9 @@ const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
const { path } = require('../../utils/fs');
|
||||
|
||||
/** A plugin */
|
||||
/**
|
||||
* A plugin
|
||||
*/
|
||||
module.exports = class Plugin {
|
||||
/**
|
||||
* Create a new Plugin
|
||||
@@ -13,11 +15,9 @@ module.exports = class Plugin {
|
||||
* @param {String} id The plugin ID
|
||||
* @param {Object} options Plugin options
|
||||
* @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 = {}) {
|
||||
/** The human-friendly name of the plugin */
|
||||
this.name = options.name || id;
|
||||
|
||||
/** The Discord Client */
|
||||
this.client = client;
|
||||
|
||||
@@ -33,25 +33,53 @@ module.exports = class Plugin {
|
||||
description
|
||||
} = this.manager.plugins.get(id);
|
||||
|
||||
/** The unique ID of the plugin (NPM package name) */
|
||||
/**
|
||||
* The human-friendly name of the plugin
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = options.name || id;
|
||||
|
||||
/**
|
||||
* An array of commands from this plugin
|
||||
* @type {string[]}
|
||||
*/
|
||||
this.commands = options.commands;
|
||||
|
||||
/**
|
||||
* The unique ID of the plugin (NPM package name)
|
||||
* @type {string}
|
||||
*/
|
||||
this.id = id;
|
||||
|
||||
/** The version of the plugin (NPM package version) */
|
||||
/**
|
||||
* The version of the plugin (NPM package version)
|
||||
* @type {string}
|
||||
*/
|
||||
this.version = version;
|
||||
|
||||
/** The plugin author's name (NPM package author) */
|
||||
/**
|
||||
* The plugin author's name (NPM package author)
|
||||
* @type {(undefined|string)}
|
||||
*/
|
||||
this.author = author;
|
||||
|
||||
/** The plugin description (NPM package description) */
|
||||
/**
|
||||
* The plugin description (NPM package description)
|
||||
* @type {string}
|
||||
*/
|
||||
this.description = description;
|
||||
|
||||
this.directory = {};
|
||||
|
||||
/** A cleaned version of the plugin's ID suitable for use in the directory name */
|
||||
this.directory.name = this.id.replace(/@[-_a-zA-Z0-9]+\//, '');
|
||||
let clean = this.id.replace(/@[-_a-zA-Z0-9]+\//, '');
|
||||
|
||||
/** The absolute path of the plugin directory */
|
||||
this.directory.path = path(`./user/plugins/${this.directory.name}`);
|
||||
/**
|
||||
* Information about the plugin directory
|
||||
* @property {string} name - A cleaned version of the plugin's ID suitable for use in the directory name
|
||||
* @property {string} path - The absolute path of the plugin directory
|
||||
*/
|
||||
this.directory = {
|
||||
name: clean,
|
||||
path: path(`./user/plugins/${clean}`)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,8 +113,20 @@ module.exports = class Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the plugin config file to the defaults
|
||||
* @param {Object} template The default config template
|
||||
*/
|
||||
resetConfig(template) {
|
||||
this.createDirectory();
|
||||
let file = join(this.directory.path, 'config.json');
|
||||
this.client.log.plugins(`Resetting plugin config file for "${this.name}"`);
|
||||
fs.writeFileSync(file, JSON.stringify(template, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* The main function where your code should go. Create functions and event listeners here
|
||||
* @abstract
|
||||
*/
|
||||
load() {}
|
||||
};
|
Reference in New Issue
Block a user