/* Module definition */ /* Variable Definition */ const Discord = require('discord.js'), args = require('args-parser')(process.argv), config = require('../config.json'), gcmdTempl = require('../commands/globalcommands'), scmdTempl = require('../commands/servercommands'); let logger = require('winston'), globCommands = {}; /** * @type {Servant} */ exports.Servant = class { constructor(prefix) { this.commands = {}; this.prefix = prefix; // show all commands (except the owner commands if the user is not an owner) this.createCommand(gcmdTempl.utils.help, (msg, kwargs) => { if (kwargs.command) { let cmd = kwargs.command; let allCommands = {...globCommands, ...this.commands}; if (cmd.charAt(0) !== prefix) cmd = this.prefix + cmd; if (allCommands[cmd]) return new Discord.RichEmbed() .setTitle(`Help for ${cmd}`) .addField('Usage', `\`${cmd} [${allCommands[cmd].args.join('] [')}]\``.replace('[]', '')) .addField('Description', allCommands[cmd].description) .addField('Permission Role', allCommands[cmd].role || 'all'); else return 'Command not found :('; } else { let helpEmbed = new Discord.RichEmbed() .setTitle('Commands') .setDescription('Create a sequence of commands with `;` (semicolon).') .setTimestamp(); let globHelp = ''; Object.entries(globCommands).sort().forEach(([key, value]) => { if (value.role !== 'owner' || checkPermission(msg, 'owner')) globHelp += `\`${key}\` \t`; }); helpEmbed.addField('Global Commands', globHelp); let categories = []; let catCommands = {}; Object.entries(this.commands).sort().forEach(([key, value]) => { if (value.role !== 'owner' || checkPermission(msg, 'owner')) if (!categories.includes(value.category)) { categories.push(value.category); catCommands[value.category] = `\`${key}\` \t`; } else { catCommands[value.category] += `\`${key}\` \t`; } }); for (let cat of categories) helpEmbed.addField(cat, catCommands[cat]); helpEmbed.setFooter(prefix + 'help [command] for more info to each command'); return helpEmbed; } }); // show all roles that are used by commands this.createCommand(scmdTempl.utils.roles, () => { let roles = []; Object.values(globCommands).concat(Object.values(this.commands)).sort().forEach((value) => { roles.push(value.role || 'all'); }); return `**Roles**\n${[...new Set(roles)].join('\n')}`; }); } /** * Creates a command entry in the private commands dict * @param template * @param call */ createCommand(template, call) { if (!template.name) { logger.debug(`Name of command template is null or undef. Failed creating command.`); return; } this.commands[this.prefix + template.name] = { 'args': template.args || [], 'description': template.description, 'callback': call, 'role': template.permission, 'category': template.category || 'Other' }; logger.debug(`Created server command: ${this.prefix + template.name}, args: ${template.args}`); } /** * Removes a command * @param command * @deprecated Why would you want to remove a command? */ removeCommand(command) { delete this.commands[command]; } /** * Processes the command * @param msg * @param globResult * @param content * @param returnFunction Boolean if the return value should be a function. * @param fallback * @returns {*} */ processCommand(msg, globResult, content, returnFunction, fallback) { let command = (content.match(/^.\w+/) || [])[0]; if (!command || !this.commands[command]) if (fallback && !globResult) { command = fallback; content = `${fallback} ${content}`; } else { return globResult; } let cmd = this.commands[command]; if (!checkPermission(msg, cmd.role)) return 'No Permission'; logger.debug(`Permission <${cmd.role || 'all'}> granted for command ${command} for user <${msg.author.tag}>`); let argvars = content.match(/(?<= )\S+/g) || []; let kwargs = {}; let nLength = Math.min(cmd.args.length, argvars.length); for (let i = 0; i < nLength; i++) kwargs[cmd.args[i]] = argvars[i]; let argv = argvars.slice(nLength); logger.debug(`Executing callback for command: ${command}, kwargs: ${kwargs}, argv: ${argv}`); try { let locResult = returnFunction? () => cmd.callback(msg, kwargs, argv) : cmd.callback(msg, kwargs, argv); return locResult || globResult; } catch (err) { logger.error(err.message); return `The command \`${command}\` has thrown an error.`; } } /** * Parses the message and executes the command callback for the found command entry in the commands dict * @param msg * @returns {*} */ parseCommand(msg) { let globResult = parseGlobalCommand(msg); logger.debug(`Global command result is ${globResult}`); let content = msg.content; let commands = content.split(/(? x.replace(/^ +/, '')); if (commands.length === 1) { return this.processCommand(msg, globResult, content); } else if (commands.length < (config.maxCmdSequenceLength || 5)) { let answers = []; let previousCommand = (commands[0].match(/^.\w+/) || [])[0];; for (let i = 0; i < commands.length; i++) { answers.push(this.processCommand(msg, globResult[i], commands[i], true, previousCommand)); // return function to avoid "race conditions" let commandMatch = (commands[i].match(/^.\w+/) || [])[0]; previousCommand = this.commands[commandMatch]? commandMatch : previousCommand; } return answers; } else { return 'This command sequence is too long!'; } } }; /** * Getting the logger * @param {Object} newLogger */ exports.setLogger = function (newLogger) { logger = newLogger; }; /** * Creates a global command that can be executed in every channel. * @param prefix * @param template * @param call */ exports.createGlobalCommand = function (prefix, template, call) { if (!template.name) { logger.debug(`Name of command template is null or undef. Failed to create command.`); return; } globCommands[prefix + template.name] = { 'args': template.args || [], 'description': template.description, 'callback': call, 'role': template.permission, 'name': template.name, 'category': template.category || 'Other' }; logger.debug(`Created global command: ${prefix + template.name}, args: ${template.args}`); }; /** * Parses a message for a global command * @param msg * @returns {boolean|*} */ exports.parseMessage = function (msg) { return parseGlobalCommand(msg); }; /** * Initializes the module by creating a help command */ exports.init = function (prefix) { logger.verbose("Created help command"); this.createGlobalCommand((prefix || config.prefix), gcmdTempl.utils.help, (msg, kwargs) => { if (kwargs.command) { let cmd = kwargs.command; if (cmd.charAt(0) !== prefix) cmd = prefix + cmd; if (globCommands[cmd]) return new Discord.RichEmbed() .setTitle(`Help for ${cmd}`) .addField('Usage', `\`${cmd} [${globCommands[cmd].args.join('] [')}]\``.replace('[]', '')) .addField('Description', globCommands[cmd].description) .addField('Permission Role', globCommands[cmd].role || 'all'); } else { let helpEmbed = new Discord.RichEmbed() .setTitle('Global Commands') .setDescription('Create a sequence of commands with `;` (semicolon).') .setTimestamp(); let description = ''; Object.entries(globCommands).sort().forEach(([key, value]) => { if (value.role === 'owner' && checkPermission(msg, 'owner')) description += `\`${key}\` \t`; else if (value.role !== 'owner') description += `\`${key}\` \t`; }); helpEmbed.setFooter(prefix + 'help [command] for more info to each command'); helpEmbed.setDescription(description); return helpEmbed; } }); }; function processCommand(cmd, msg, content, returnFunction) { let argvars = content.match(/(?<= )\S+/g) || []; let kwargs = {}; let nLength = Math.min(cmd.args.length, argvars.length); for (let i = 0; i < nLength; i++) kwargs[cmd.args[i]] = argvars[i]; let argv = argvars.slice(nLength); logger.debug(`Executing callback for command: ${cmd.name}, kwargs: ${JSON.stringify(kwargs)}, argv: ${argv}`); return returnFunction? () => cmd.callback(msg, kwargs, argv) : cmd.callback(msg, kwargs, argv); } /** * Parses the message by calling the assigned function for the command with arguments * @param msg */ function parseGlobalCommand(msg) { let content = msg.content; let commands = content.split(/(? x.replace(/^ +/, '')); if (commands.length === 1) { let command = (content.match(/^.\w+/) || [])[0]; if (!command || !globCommands[command]) return false; let cmd = globCommands[command]; if (!checkPermission(msg, cmd.role)) return false; logger.debug(`Permission <${cmd.role}> granted for command ${command} for user <${msg.author.tag}>`); return processCommand(cmd, msg, content); } else if (commands.length < (config.maxCmdSequenceLength || 5)) { let answers = []; let previousCommand = ''; for (let commandPart of commands) { let command = (commandPart.match(/^.\w+/) || [])[0] || previousCommand; previousCommand = globCommands[command]? command : previousCommand; if (!commandPart || !globCommands[command]) { commandPart = `${previousCommand} ${commandPart}`; command = previousCommand; } if (command && globCommands[command]) { let cmd = globCommands[command]; if (checkPermission(msg, cmd.role)) { logger.debug(`Permission <${cmd.role}> granted for command ${command} for user <${msg.author.tag}>`); answers.push(processCommand(cmd, msg, commandPart, true)); // return an function to avoid "race conditions" } else { answers.push(false); } } else { answers.push(false); } } return answers; } else { return 'This command sequence is too long!'; } } /** * @param msg * @param rolePerm {String} * @returns {boolean} */ function checkPermission(msg, rolePerm) { if (!rolePerm || ['all', 'any', 'everyone'].includes(rolePerm)) return true; if (msg.author.tag === args.owner || config.owners.includes(msg.author.tag)) return true; else if (msg.member && rolePerm && rolePerm !== 'owner' && msg.member.roles .some(role => (role.name.toLowerCase() === rolePerm.toLowerCase() || role.name.toLowerCase() === 'botcommander'))) return true; return false; }