const CommandRequirements = require('./requirements');
/** Class representing a command. */
class Command {
/**
* Create a command.
* @class
* @param {string|Array} name - The name of the command. If passed as an
* array, the first item of the array is used as the name and the rest of
* the items are set as aliases.
* @param {object} options - Options of command
* @param {string} [options.category='Default'] - Category from this command
* @param {string} [options.help=''] - Description of command
* @param {string} [options.args=''] - The command arguments
* @param {boolean} [options.hide=false] - Hide command from default help command
* @param {boolean} [options.enable=true] - Enable/Disable the command
* @param {string} [options.childOf=undefined] - Parent command name
* @param {Array} [options.requirements=[]] - Requirements are mapped in client.addCommand
* @param {object} [options.hooks = {}] - Command hooks.
* @param {Array<function>} [options.hooks.pre=[]] - Hook pre run command.
* @param {Array<function>} [options.hooks.executed=[]] - Hook after run command.
* @param {Array<function>} [options.hooks.error=[]] - Hook fired when there an error with execution command.
* @param {object} [options.custom={}] - Define custom methods or properties to copy to command.
* @param {Command~run} run - The function to be called when the command is executed.
* @param {string | function | EmbedMessageObject } response - Response to command. Ignore run function.
* @param {string | function | EmbedMessageObject } responseDM - DM response to command. Ignore run function.
*/
constructor(name, options = {}, run) {
if (Array.isArray(name)) {
/** @prop {string} - Name of Command */
[this.name] = name.splice(0, 1);
/** @prop {string[]} - Command aliases */
this.aliases = name;
} else if (typeof name === 'string') {
this.name = name;
this.aliases = [];
} else if (typeof name === 'object') {
options = name;
if (Array.isArray(options.name)) {
[this.name] = options.name.splice(0, 1);
this.aliases = options.name;
} else if (typeof options.name === 'string') {
this.name = options.name;
this.aliases = [];
}
}
if (!this.name) throw new Error('Name is required');
/** @prop {Command~run} - Run function of command */
this.run = run || options.run || async function () {};
/** @prop {string} - Description of command */
this.help = options.help || '';
this.description = options.description || '';
this.options = options.options || undefined;
this.customOptions = options.customOptions || undefined;
/** @prop {string | EmbedMessageObject | function } - Response of command. If it exists, ignore run function. If function (msg, args, client, commad) */
this.response = options.response || '';
/** @prop {string | EmbedMessageObject | function } - Response of command with a direct message. If it exists, ignore run function. If function (msg, args, client, commad) */
this.responseDM = options.responseDM || '';
/** @prop {Command | undefined} - Parent Command */
this.parent = undefined;
/** @prop {Command[]} - Subcommands of Command. */
this.childs = [];
/** @prop {array} - Command Requirements */
this.requirements = Array.isArray(options.requirements)
? options.requirements
: []; // These requirements are mapped in client.addCommand
/** @prop {object} - Command Hooks */
this.hooks = [];
[
'trigger',
'prereq', // Fired before check requirements
'prerun', // Fired before run command
'execute', // Fired after command is run
'error' // Fired when there is an error running pre/executed hooks and response/run methods
].forEach((hookStep) => (this.hooks[hookStep] = []));
if (options.hooks && typeof options.hooks === 'object') {
Object.entries(options.hooks).forEach(([hookStep, hook]) => {
if (typeof hook !== 'function')
throw new TypeError(
`Hook ${hookStep} is not a function on ${this.name}`
);
this.addHook(hookStep, hook);
});
}
/** @prop {string|undefined} - Name of uppercomand */
this.childOf = options.childOf;
/** @prop {string} - Command category. It should exist if not, will be 'Default' */
this.category = options.category || 'Default';
/** @prop {string} - Arguments for a command */
this.args = options.args || '';
/** @prop {boolean} - Hide to default help command */
this.hide = options.hide !== undefined ? options.hide : false; // Hide command from help command
/** @prop {boolean} - Enable/Disable the command */
this.enable = options.enable !== undefined ? options.enable : true; // Enable or disable command
/** @prop {object | undefined} - Custom props */
this.scope = options.scope || { type: 'global' };
/** @prop {Client} - Client instance */
this.client = undefined;
/** @prop {object | undefined} - Custom props */
this.custom = undefined;
}
/**
* @callback Command~run
* A function to be called when a command is executed. Accepts information
* about the message that triggered the command as arguments.
* @param {Eris.Message} msg - The Eris message object that triggered the command.
* For more information, see the Eris documentation:
* {@link https://abal.moe/Eris/docs/Message}
* @param {args} args - An array of arguments passed to the command,
* obtained by removing the command name and prefix from the message, then
* splitting on spaces. To get the raw text that was passed to the
* command, use `args.join(' ')`.
* @param {Client} client client instance that recieved the message triggering the
* command.
* @param {Command} command - The name or alias used to call the command in
* the message. Will be one of the values of `this.names`.
*/
/**
* All names that can be used to invoke the command - its primary name in
* addition to its aliases.
* @return {Array<string>}
*/
get names() {
return [this.name, ...this.aliases];
}
/** Add a requirement
* @param {object} requirement - Requirement to add. Inject a method to remove it.
*/
addRequirement(requirement) {
requirement.remove = () => this.removeRequirement(requirement);
this.requirements.push(requirement);
if (requirement.init) {
requirement.init(this.client, this, requirement);
}
}
/** Remove a requirement
* @param {object} requirement - Requirement to remove.
*/
removeRequirement(requirement) {
this.requirements = this.requirements.filter((req) => req !== requirement);
}
/**
* Command hook
* @callback CommandHook
* @param {Eris.Message} msg - Eris message
* @param {args} args - Arguments
* @param {Client} client - Eris message
* @param {Command} command - Command
*/
/** Add a hook
* @param {string} hookname - Hook name.
* @param {CommandHook} hook - Hook to add.Inject a method to remove it.
*/
addHook(hookname, hook) {
if (!this.hooks[hookname]) {
throw new Error(
`Add command hook error: ${hookname} not defined on ${this.name} command`
);
}
hook.remove = () => this.removeHook(hookname, hook);
this.hooks[hookname].push(hook);
}
/** Remove a hook
* @param {string} hookname - Hook name.
* @param {CommandHook} hook - Hook to remove.
*/
removeHook(hookname, hook) {
if (!this.hooks[hookname]) {
throw new Error(
`Add command hook error: ${hookname} not defined on ${this.name} command`
);
}
this.hooks[hookname] = this.hooks[hookname].filter((h) => h !== hook);
}
runHook(hookname, ...args) {
this.hooks[hookname].forEach((hook) => hook(...args));
}
/** Throw a command error */
error(message) {
throw new Error(message);
}
}
/**
* @typedef CommandRequirementObject
* @prop {string} type - Type requirement
* @prop {function} condition - If returns false, do first of response/resposneDM/run
* @prop {string|function|undefined} response - Reply with this response
* @prop {string|function|undefined} responseDM - Reply with this response by direct message
* @prop {function|undefined} run - Run this custom function
* @prop {function|undefined} init - Run when requirement is added to command
*/
/**
* @callback CommandRequirementFunction
* @param {object} config - Config requirement
* @param {Command} config.command - Command
* @param {Client} config.command - Client
* @returns {CommandRequirementObject|Array<CommandRequirementObject>}
*/
module.exports = Command;