src/commands/command.js
'use babel';
'use strict';
import Setting from '../storage/models/setting';
/** A command that can be run in a bot */
export default class Command {
/**
* @param {Bot} bot - The bot the command is for
* @param {CommandInfo} info - The command information
*/
constructor(bot, info) { // eslint-disable-line complexity
if(!bot) throw new Error('A bot must be specified.');
if(!info) throw new Error('Command info must be specified.');
if(!info.name) throw new Error('Command must have a name specified.');
if(info.name !== info.name.toLowerCase()) throw new Error('Command name must be lowercase.');
if(info.aliases && !Array.isArray(info.aliases)) throw new TypeError('Command aliases must be an array.');
if(info.aliases && info.aliases.some(ali => ali !== ali.toLowerCase())) throw new Error('Command aliases must be lowercase.');
if(!info.module) throw new Error('Command must have a module specified.');
if(info.module !== info.module.toLowerCase()) throw new Error('Command module must be lowercase.');
if(!info.memberName) throw new Error('Command must have a memberName specified.');
if(info.memberName !== info.memberName.toLowerCase()) throw new Error('Command memberName must be lowercase.');
if(!info.description) throw new Error('Command must have a description specified.');
if(info.examples && !Array.isArray(info.examples)) throw new TypeError('Command examples must be an array.');
if(info.argsType && !['single', 'multiple'].includes(info.argsType)) throw new RangeError('Command argsType must be one of "single" or "multiple".');
if(info.argsType === 'multiple' && info.argsCount && info.argsCount < 2) throw new RangeError('Command argsCount must be at least 2.');
if(info.patterns && !Array.isArray(info.patterns)) throw new TypeError('Command patterns must be an array.');
/** @type {Bot} */
this.bot = bot;
/**
* @type {string}
* @see {@link CommandInfo}
*/
this.name = info.name;
/**
* @type {string[]}
* @see {@link CommandInfo}
*/
this.aliases = info.aliases || [];
/**
* @type {string}
* @see {@link CommandInfo}
*/
this.module = info.module;
/**
* @type {string}
* @see {@link CommandInfo}
*/
this.memberName = info.memberName;
/**
* @type {string}
* @see {@link CommandInfo}
*/
this.description = info.description;
/**
* @type {string}
* @see {@link CommandInfo}
*/
this.usage = info.usage || info.name;
/**
* @type {string}
* @see {@link CommandInfo}
*/
this.details = info.details || null;
/**
* @type {string[]}
* @see {@link CommandInfo}
*/
this.examples = info.examples || null;
/**
* @type {boolean}
* @see {@link CommandInfo}
*/
this.guildOnly = !!info.guildOnly;
/**
* @type {boolean}
* @see {@link CommandInfo}
*/
this.defaultHandling = 'defaultHandling' in info ? info.defaultHandling : true;
/**
* @type {string}
* @see {@link CommandInfo}
*/
this.argsType = info.argsType || 'single';
/**
* @type {number}
* @see {@link CommandInfo}
*/
this.argsCount = info.argsCount || 0;
/**
* @type {boolean}
* @see {@link CommandInfo}
*/
this.argsSingleQuotes = 'argsSingleQuotes' in info ? info.argsSingleQuotes : true;
/**
* @type {RegExp[]}
* @see {@link CommandInfo}
*/
this.patterns = info.patterns || null;
}
/**
* Checks a user's permission on a guild
* @param {Guild} guild - The guild to test the user's permission in
* @param {User} user - The user to test the permission of
* @return {boolean} Whether or not the user has permission to use the command in a guild
*/
hasPermission(guild, user) { // eslint-disable-line no-unused-vars
return true;
}
/* eslint-disable valid-jsdoc */
/**
* Runs the command
* @param {Message} message - The message the command is being run for
* @param {string[]} args - The arguments for the command, or the matches from a pattern
* @param {boolean} fromPattern - Whether or not the command is being run from a pattern match or not
* @return {Promise<CommandResult|string[]|string>} The result of running the command
*/
async run(message, args, fromPattern) { // eslint-disable-line no-unused-vars
throw new Error(`${this.constructor.name} doesn't have a run() method, or called the super.run() method.`);
}
/* eslint-enable valid-jsdoc */
/**
* Enables or disables the command on a guild
* @param {Guild|string} guild - The guild or guild ID
* @param {boolean} enabled - Whether the command should be enabled or disabled
* @see {@link Command.setEnabled}
*/
setEnabled(guild, enabled) {
this.constructor.setEnabled(this.bot.storage.settings, guild, this, enabled);
}
/**
* Enables or disables a command on a guild
* @param {SettingStorage} settings - The setting storage to use
* @param {Guild|string} guild - The guild or guild ID
* @param {Command|string} command - The command or command name
* @param {boolean} enabled - Whether the command should be enabled or disabled
* @see {@link Command#setEnabled}
*/
static setEnabled(settings, guild, command, enabled) {
settings.save(new Setting(guild, `cmd-${command.name || command}`, enabled));
}
/**
* Checks if the command is enabled on a guild
* @param {Guild} guild - The guild
* @return {boolean} Whether or not the command is enabled
* @see {@link Command.isEnabled}
*/
isEnabled(guild) {
return this.constructor.isEnabled(this.bot.storage.settings, guild, this);
}
/**
* Checks if a command is enabled on a guild
* @param {SettingStorage} settings - The setting storage to use
* @param {Guild} guild - The guild
* @param {Command|string} command - The command or command name
* @return {boolean} Whether or not the command is enabled
* @see {@link Command#isEnabled}
*/
static isEnabled(settings, guild, command) {
return (!command.module || settings.getValue(guild, `mod-${command.module}`, true)) && settings.getValue(guild, `cmd-${command.name || command}`, true);
}
/**
* Checks if the command is usable for a message
* @param {?Message} message - The message
* @return {boolean} Whether or not the command is usable
*/
isUsable(message = null) {
if(this.guildOnly && message && !message.guild) return false;
return !message || (this.isEnabled(message.guild) && this.hasPermission(message.guild, message.author));
}
}
/**
* @typedef {Object} CommandInfo
* @property {string} name - The name of the command (must be lowercase)
* @property {string[]} [aliases] - Alternative names for the command (all must be lowercase)
* @property {string} module - The ID of the module the command belongs to (must be lowercase)
* @property {string} memberName - The member name of the command in the module (must be lowercase)
* @property {string} description - A short description of the command
* @property {string} [usage=name] - The command usage format string
* @property {string} [details] - A detailed description of the command and its functionality
* @property {string[]} [examples] - Usage examples of the command
* @property {boolean} [guildOnly=false] - Whether or not the command should only function in a guild channel
* @property {boolean} [defaultHandling=true] - Whether or not the default command handling should be used. If false, then only patterns will trigger the command.
* @property {string} [argsType=single] - One of 'single' or 'multiple'. When 'single', the entire argument string will be passed to run as one argument.
* When 'multiple', it will be passed as multiple arguments.
* @property {number} [argsCount=0] - The number of arguments to parse from the command string. Only applicable when argsType is 'multiple'. If nonzero, it should be at least 2.
* When this is 0, the command argument string will be split into as many arguments as it can be. When nonzero, it will be split into a maximum of this number of arguments.
* @property {boolean} [argsSingleQuotes=true] - Whether or not single quotes should be allowed to box-in arguments in the command string.
* @property {RegExp[]} [patterns] - Patterns to use for triggering the command
*/