SHARE
TWEET

ah code stuff

a guest Mar 20th, 2019 60 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. const path = require('path');
  2. const discord = require('discord.js');
  3. const Command = require('./commands/base');
  4. const CommandGroup = require('./commands/group');
  5. const CommandMessage = require('./commands/message');
  6. const ArgumentType = require('./types/base');
  7. /** Handles registration and searching of commands and groups */
  8. class CommandRegistry {
  9.     /** @param {CommandoClient} [client] - Client to use  */
  10.     constructor(client) {
  11.         /**
  12.          * The client this registry is for
  13.          * @name CommandRegistry#client
  14.          * @type {CommandoClient}
  15.          * @readonly
  16.          */
  17.         Object.defineProperty(this, 'client', { value: client });
  18.  
  19.         /**
  20.          * Registered commands
  21.          * @type {Collection<string, Command>}
  22.          */
  23.         this.commands = new discord.Collection();
  24.  
  25.         /**
  26.          * Registered command groups
  27.          * @type {Collection<string, CommandGroup>}
  28.          */
  29.         this.groups = new discord.Collection();
  30.  
  31.         /**
  32.          * Registered argument types
  33.          * @type {Collection<string, ArgumentType>}
  34.          */
  35.         this.types = new discord.Collection();
  36.  
  37.         /**
  38.          * Registered objects for the eval command
  39.          * @type {Object}
  40.          */
  41.         this.evalObjects = {};
  42.  
  43.         /**
  44.          * Fully resolved path to the bot's commands directory
  45.          * @type {?string}
  46.          */
  47.         this.commandsPath = null;
  48.     }
  49.  
  50.     /**
  51.      * Registers a single group
  52.      * @param {CommandGroup|Function|string[]|string} group - A CommandGroup instance, a constructor,
  53.      * an array of [ID, Name], or the group ID
  54.      * @param {string} [name] - Name for the group (if the first argument is the group ID)
  55.      * @return {CommandRegistry}
  56.      * @see {@link CommandRegistry#registerGroups}
  57.      */
  58.     registerGroup(group, name) {
  59.         if(typeof group === 'string') return this.registerGroups([[group, name]]);
  60.         return this.registerGroups([group]);
  61.     }
  62.  
  63.     /**
  64.      * Registers multiple groups
  65.      * @param {CommandGroup[]|Function[]|Array<string[]>} groups - An array of CommandGroup instances, constructors,
  66.      * or arrays of [ID, Name]
  67.      * @return {CommandRegistry}
  68.      */
  69.     registerGroups(groups) {
  70.         if(!Array.isArray(groups)) throw new TypeError('Groups must be an Array.');
  71.         for(let group of groups) {
  72.             if(typeof group === 'function') {
  73.                 group = new group(this.client); // eslint-disable-line new-cap
  74.             } else if(Array.isArray(group)) {
  75.                 group = new CommandGroup(this.client, ...group);
  76.             } else if(!(group instanceof CommandGroup)) {
  77.                 group = new CommandGroup(this.client, group.id, group.name, null, group.commands);
  78.             }
  79.  
  80.             const existing = this.groups.get(group.id);
  81.             if(existing) {
  82.                 existing.name = group.name;
  83.                 this.client.emit('debug', `Group ${group.id} is already registered; renamed it to "${group.name}".`);
  84.             } else {
  85.                 this.groups.set(group.id, group);
  86.                 /**
  87.                  * Emitted when a group is registered
  88.                  * @event CommandoClient#groupRegister
  89.                  * @param {CommandGroup} group - Group that was registered
  90.                  * @param {CommandRegistry} registry - Registry that the group was registered to
  91.                  */
  92.                 this.client.emit('groupRegister', group, this);
  93.                 this.client.emit('debug', `Registered group ${group.id}.`);
  94.             }
  95.         }
  96.         return this;
  97.     }
  98.  
  99.     /**
  100.      * Registers a single command
  101.      * @param {Command|Function} command - Either a Command instance, or a constructor for one
  102.      * @return {CommandRegistry}
  103.      * @see {@link CommandRegistry#registerCommands}
  104.      */
  105.     registerCommand(command) {
  106.         return this.registerCommands([command]);
  107.     }
  108.  
  109.     /**
  110.      * Registers multiple commands
  111.      * @param {Command[]|Function[]} commands - An array of Command instances or constructors
  112.      * @return {CommandRegistry}
  113.      */
  114.     registerCommands(commands) {
  115.         if(!Array.isArray(commands)) throw new TypeError('Commands must be an Array.');
  116.         for(let command of commands) {
  117.             if(typeof command === 'function') command = new command(this.client); // eslint-disable-line new-cap
  118.  
  119.             // Verify that it's an actual command
  120.             if(!(command instanceof Command)) {
  121.                 this.client.emit('warn', `Attempting to register an invalid command object: ${command}; skipping.`);
  122.                 continue;
  123.             }
  124.  
  125.             // Make sure there aren't any conflicts
  126.             if(this.commands.some(cmd => cmd.name === command.name || cmd.aliases.includes(command.name))) {
  127.                 throw new Error(`A command with the name/alias "${command.name}" is already registered.`);
  128.             }
  129.             for(const alias of command.aliases) {
  130.                 if(this.commands.some(cmd => cmd.name === alias || cmd.aliases.includes(alias))) {
  131.                     throw new Error(`A command with the name/alias "${alias}" is already registered.`);
  132.                 }
  133.             }
  134.             const group = this.groups.find(grp => grp.id === command.groupID);
  135.             if(!group) throw new Error(`Group "${command.groupID}" is not registered.`);
  136.             if(group.commands.some(cmd => cmd.memberName === command.memberName)) {
  137.                 throw new Error(`A command with the member name "${command.memberName}" is already registered in ${group.id}`);
  138.             }
  139.  
  140.             // Add the command
  141.             command.group = group;
  142.             group.commands.set(command.name, command);
  143.             this.commands.set(command.name, command);
  144.             /**
  145.              * Emitted when a command is registered
  146.              * @event CommandoClient#commandRegister
  147.              * @param {Command} command - Command that was registered
  148.              * @param {CommandRegistry} registry - Registry that the command was registered to
  149.              */
  150.             this.client.emit('commandRegister', command, this);
  151.             this.client.emit('debug', `Registered command ${group.id}:${command.memberName}.`);
  152.         }
  153.  
  154.         return this;
  155.     }
  156.  
  157.     /**
  158.      * Registers all commands in a directory. The files must export a Command class constructor or instance.
  159.      * @param {string|RequireAllOptions} options - The path to the directory, or a require-all options object
  160.      * @return {CommandRegistry}
  161.      */
  162.     registerCommandsIn(options) {
  163.         const obj = require('require-all')(options);
  164.         const commands = [];
  165.         for(const group of Object.values(obj)) {
  166.             for(let command of Object.values(group)) {
  167.                 if(typeof command.default === 'function') command = command.default;
  168.                 commands.push(command);
  169.             }
  170.         }
  171.         if(typeof options === 'string' && !this.commandsPath) this.commandsPath = options;
  172.         return this.registerCommands(commands);
  173.     }
  174.  
  175.     /**
  176.      * Registers a single argument type
  177.      * @param {ArgumentType|Function} type - Either an ArgumentType instance, or a constructor for one
  178.      * @return {CommandRegistry}
  179.      * @see {@link CommandRegistry#registerTypes}
  180.      */
  181.     registerType(type) {
  182.         return this.registerTypes([type]);
  183.     }
  184.  
  185.     /**
  186.      * Registers multiple argument types
  187.      * @param {ArgumentType[]|Function[]} types - An array of ArgumentType instances or constructors
  188.      * @return {CommandRegistry}
  189.      */
  190.     registerTypes(types) {
  191.         if(!Array.isArray(types)) throw new TypeError('Types must be an Array.');
  192.         for(let type of types) {
  193.             if(typeof type === 'function') type = new type(this.client); // eslint-disable-line new-cap
  194.  
  195.             // Verify that it's an actual type
  196.             if(!(type instanceof ArgumentType)) {
  197.                 this.client.emit('warn', `Attempting to register an invalid argument type object: ${type}; skipping.`);
  198.                 continue;
  199.             }
  200.  
  201.             // Make sure there aren't any conflicts
  202.             if(this.types.has(type.id)) throw new Error(`An argument type with the ID "${type.id}" is already registered.`);
  203.  
  204.             // Add the type
  205.             this.types.set(type.id, type);
  206.             /**
  207.              * Emitted when an argument type is registered
  208.              * @event CommandoClient#typeRegister
  209.              * @param {ArgumentType} type - Argument type that was registered
  210.              * @param {CommandRegistry} registry - Registry that the type was registered to
  211.              */
  212.             this.client.emit('typeRegister', type, this);
  213.             this.client.emit('debug', `Registered argument type ${type.id}.`);
  214.         }
  215.  
  216.         return this;
  217.     }
  218.  
  219.     /**
  220.      * Registers all argument types in a directory. The files must export an ArgumentType class constructor or instance.
  221.      * @param {string|RequireAllOptions} options - The path to the directory, or a require-all options object
  222.      * @return {CommandRegistry}
  223.      */
  224.     registerTypesIn(options) {
  225.         const obj = require('require-all')(options);
  226.         const types = [];
  227.         for(const type of Object.values(obj)) types.push(type);
  228.         return this.registerTypes(types);
  229.     }
  230.  
  231.     /**
  232.      * Registers the default argument types, groups, and commands. This is equivalent to:
  233.      * ```js
  234.      * registry.registerDefaultTypes()
  235.      *  .registerDefaultGroups()
  236.      *  .registerDefaultCommands();
  237.      * ```
  238.      * @return {CommandRegistry}
  239.      */
  240.     registerDefaults() {
  241.         this.registerDefaultTypes();
  242.         this.registerDefaultGroups();
  243.         this.registerDefaultCommands();
  244.         return this;
  245.     }
  246.  
  247.     /**
  248.      * Registers the default groups ("util" and "commands")
  249.      * @return {CommandRegistry}
  250.      */
  251.     registerDefaultGroups() {
  252.         return this.registerGroups([
  253.             ['commands', 'Commands', true],
  254.             ['util', 'Utility']
  255.         ]);
  256.     }
  257.  
  258.     /**
  259.      * Registers the default commands to the registry
  260.      * @param {Object} [options] - Object specifying what commands to register
  261.      * @param {boolean} [options.help=true] - Whether or not to register the built-in help command
  262.      * @param {boolean} [options.prefix=true] - Whether or not to register the built-in prefix command
  263.      * @param {boolean} [options.eval_=true] - Whether or not to register the built-in eval command
  264.      * @param {boolean} [options.ping=true] - Whether or not to register the built-in ping command
  265.      * @param {boolean} [options.commandState=true] - Whether or not to register the built-in command state commands
  266.      * (enable, disable, reload, list groups)
  267.      * @return {CommandRegistry}
  268.      */
  269.     registerDefaultCommands({ help = true, prefix = true, ping = true, eval_ = true, commandState = true } = {}) {
  270.         if(help) this.registerCommand(require('./commands/util/help'));
  271.         if(prefix) this.registerCommand(require('./commands/util/prefix'));
  272.         if(ping) this.registerCommand(require('./commands/util/ping'));
  273.         if(eval_) this.registerCommand(require('./commands/util/eval'));
  274.         if(commandState) {
  275.             this.registerCommands([
  276.                 require('./commands/commands/groups'),
  277.                 require('./commands/commands/enable'),
  278.                 require('./commands/commands/disable'),
  279.                 require('./commands/commands/reload'),
  280.                 require('./commands/commands/load'),
  281.                 require('./commands/commands/unload')
  282.             ]);
  283.         }
  284.         return this;
  285.     }
  286.  
  287.     /**
  288.      * Registers the default argument types to the registry. These are:
  289.      * - string
  290.      * - integer
  291.      * - float
  292.      * - boolean
  293.      * - user
  294.      * - member
  295.      * - role
  296.      * - channel
  297.      * - message
  298.      * - command
  299.      * - group
  300.      * @return {CommandRegistry}
  301.      */
  302.     registerDefaultTypes() {
  303.         this.registerTypes([
  304.             require('./types/string'),
  305.             require('./types/integer'),
  306.             require('./types/float'),
  307.             require('./types/boolean'),
  308.             require('./types/user'),
  309.             require('./types/member'),
  310.             require('./types/role'),
  311.             require('./types/channel'),
  312.             require('./types/message'),
  313.             require('./types/command'),
  314.             require('./types/group')
  315.         ]);
  316.         return this;
  317.     }
  318.  
  319.     /**
  320.      * Reregisters a command (does not support changing name, group, or memberName)
  321.      * @param {Command|Function} command - New command
  322.      * @param {Command} oldCommand - Old command
  323.      */
  324.     reregisterCommand(command, oldCommand) {
  325.         if(typeof command === 'function') command = new command(this.client); // eslint-disable-line new-cap
  326.         if(command.name !== oldCommand.name) throw new Error('Command name cannot change.');
  327.         if(command.groupID !== oldCommand.groupID) throw new Error('Command group cannot change.');
  328.         if(command.memberName !== oldCommand.memberName) throw new Error('Command memberName cannot change.');
  329.         command.group = this.resolveGroup(command.groupID);
  330.         command.group.commands.set(command.name, command);
  331.         this.commands.set(command.name, command);
  332.         /**
  333.          * Emitted when a command is reregistered
  334.          * @event CommandoClient#commandReregister
  335.          * @param {Command} newCommand - New command
  336.          * @param {Command} oldCommand - Old command
  337.          */
  338.         this.client.emit('commandReregister', command, oldCommand);
  339.         this.client.emit('debug', `Reregistered command ${command.groupID}:${command.memberName}.`);
  340.     }
  341.  
  342.     /**
  343.      * Unregisters a command
  344.      * @param {Command} command - Command to unregister
  345.      */
  346.     unregisterCommand(command) {
  347.         this.commands.delete(command.name);
  348.         command.group.commands.delete(command.name);
  349.         /**
  350.          * Emitted when a command is unregistered
  351.          * @event CommandoClient#commandUnregister
  352.          * @param {Command} command - Command that was unregistered
  353.          */
  354.         this.client.emit('commandUnregister', command);
  355.         this.client.emit('debug', `Unregistered command ${command.groupID}:${command.memberName}.`);
  356.     }
  357.  
  358.     /**
  359.      * Registers a single object to be usable by the eval command
  360.      * @param {string} key - The key for the object
  361.      * @param {Object} obj - The object
  362.      * @return {CommandRegistry}
  363.      * @see {@link CommandRegistry#registerEvalObjects}
  364.      */
  365.     registerEvalObject(key, obj) {
  366.         const registerObj = {};
  367.         registerObj[key] = obj;
  368.         return this.registerEvalObjects(registerObj);
  369.     }
  370.  
  371.     /**
  372.      * Registers multiple objects to be usable by the eval command
  373.      * @param {Object} obj - An object of keys: values
  374.      * @return {CommandRegistry}
  375.      */
  376.     registerEvalObjects(obj) {
  377.         Object.assign(this.evalObjects, obj);
  378.         return this;
  379.     }
  380.  
  381.     /**
  382.      * Finds all groups that match the search string
  383.      * @param {string} [searchString] - The string to search for
  384.      * @param {boolean} [exact=false] - Whether the search should be exact
  385.      * @return {CommandGroup[]} All groups that are found
  386.      */
  387.     findGroups(searchString = null, exact = false) {
  388.         if(!searchString) return this.groups;
  389.  
  390.         // Find all matches
  391.         const lcSearch = searchString.toLowerCase();
  392.         const matchedGroups = this.groups.filterArray(
  393.             exact ? groupFilterExact(lcSearch) : groupFilterInexact(lcSearch)
  394.         );
  395.         if(exact) return matchedGroups;
  396.  
  397.         // See if there's an exact match
  398.         for(const group of matchedGroups) {
  399.             if(group.name.toLowerCase() === lcSearch || group.id === lcSearch) return [group];
  400.         }
  401.         return matchedGroups;
  402.     }
  403.  
  404.     /**
  405.      * A CommandGroupResolvable can be:
  406.      * * A CommandGroup
  407.      * * A group ID
  408.      * @typedef {CommandGroup|string} CommandGroupResolvable
  409.      */
  410.  
  411.     /**
  412.      * Resolves a CommandGroupResolvable to a CommandGroup object
  413.      * @param {CommandGroupResolvable} group - The group to resolve
  414.      * @return {CommandGroup} The resolved CommandGroup
  415.      */
  416.     resolveGroup(group) {
  417.         if(group instanceof CommandGroup) return group;
  418.         if(typeof group === 'string') {
  419.             const groups = this.findGroups(group, true);
  420.             if(groups.length === 1) return groups[0];
  421.         }
  422.         throw new Error('Unable to resolve group.');
  423.     }
  424.  
  425.     /**
  426.      * Finds all commands that match the search string
  427.      * @param {string} [searchString] - The string to search for
  428.      * @param {boolean} [exact=false] - Whether the search should be exact
  429.      * @param {Message} [message] - The message to check usability against
  430.      * @return {Command[]} All commands that are found
  431.      */
  432.     findCommands(searchString = null, exact = false, message = null) {
  433.         if(!searchString) return message ? this.commands.filterArray(cmd => cmd.isUsable(message)) : this.commands;
  434.  
  435.         // Find all matches
  436.         const lcSearch = searchString.toLowerCase();
  437.         const matchedCommands = this.commands.filterArray(
  438.             exact ? commandFilterExact(lcSearch) : commandFilterInexact(lcSearch)
  439.         );
  440.         if(exact) return matchedCommands;
  441.  
  442.         // See if there's an exact match
  443.         for(const command of matchedCommands) {
  444.             if(command.name === lcSearch || (command.aliases && command.aliases.some(ali => ali === lcSearch))) {
  445.                 return [command];
  446.             }
  447.         }
  448.  
  449.         return matchedCommands;
  450.     }
  451.  
  452.     /**
  453.      * A CommandResolvable can be:
  454.      * * A Command
  455.      * * A command name
  456.      * * A CommandMessage
  457.      * @typedef {Command|string} CommandResolvable
  458.      */
  459.  
  460.     /**
  461.      * Resolves a CommandResolvable to a Command object
  462.      * @param {CommandResolvable} command - The command to resolve
  463.      * @return {Command} The resolved Command
  464.      */
  465.     resolveCommand(command) {
  466.         if(command instanceof Command) return command;
  467.         if(command instanceof CommandMessage) return command.command;
  468.         if(typeof command === 'string') {
  469.             const commands = this.findCommands(command, true);
  470.             if(commands.length === 1) return commands[0];
  471.         }
  472.         throw new Error('Unable to resolve command.');
  473.     }
  474.  
  475.     /**
  476.      * Resolves a command file path from a command's group ID and memberName
  477.      * @param {string} group - ID of the command's group
  478.      * @param {string} memberName - Member name of the command
  479.      * @return {string} Fully-resolved path to the corresponding command file
  480.      */
  481.     resolveCommandPath(group, memberName) {
  482.         return path.join(this.commandsPath, group, `${memberName}.js`);
  483.     }
  484. }
  485.  
  486. function groupFilterExact(search) {
  487.     return grp => grp.id === search || grp.name.toLowerCase() === search;
  488. }
  489.  
  490. function groupFilterInexact(search) {
  491.     return grp => grp.id.includes(search) || grp.name.toLowerCase().includes(search);
  492. }
  493.  
  494. function commandFilterExact(search) {
  495.     return cmd => cmd.name === search ||
  496.         (cmd.aliases && cmd.aliases.some(ali => ali === search)) ||
  497.         `${cmd.groupID}:${cmd.memberName}` === search;
  498. }
  499.  
  500. function commandFilterInexact(search) {
  501.     return cmd => cmd.name.includes(search) ||
  502.         `${cmd.groupID}:${cmd.memberName}` === search ||
  503.         (cmd.aliases && cmd.aliases.some(ali => ali.includes(search)));
  504. }
  505.  
  506. module.exports = CommandRegistry;
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top