UNPKG

dexare

Version:

Modular and extendable Discord bot framework

262 lines (261 loc) 11.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const collection_1 = __importDefault(require("@discordjs/collection")); const path_1 = require("path"); const module_1 = __importDefault(require("../../module")); const util_1 = require("../../util"); const context_1 = __importDefault(require("./context")); const interpreter_1 = __importDefault(require("./interpreter")); const eval_1 = __importDefault(require("./default/eval")); const help_1 = __importDefault(require("./default/help")); const ping_1 = __importDefault(require("./default/ping")); const exec_1 = __importDefault(require("./default/exec")); const kill_1 = __importDefault(require("./default/kill")); const load_1 = __importDefault(require("./default/load")); const unload_1 = __importDefault(require("./default/unload")); const reload_1 = __importDefault(require("./default/reload")); /** The commands module in Dexare. */ class CommandsModule extends module_1.default { constructor(client) { super(client, { name: 'commands', description: "Dexare's command handler." }); /** The commands loaded into the module. */ this.commands = new collection_1.default(); this.filePath = __filename; } /** @hidden */ load() { this.registerEvent('messageCreate', this.onMessage.bind(this)); } /** @hidden */ unload() { this.unregisterAllEvents(); } /** * Registers a command. * @param command The command to register */ register(commandObj) { if (typeof commandObj === 'function') commandObj = new commandObj(this.client); else if (typeof commandObj.default === 'function') commandObj = new commandObj.default(this.client); if (typeof commandObj.run !== 'function') throw new Error(`Invalid command object to register: ${commandObj}`); const command = commandObj; // Make sure there aren't any conflicts if (this.commands.some((cmd) => cmd.name === command.name || cmd.aliases.includes(command.name))) { throw new Error(`A command with the name/alias "${command.name}" is already registered.`); } for (const alias of command.aliases) { if (this.commands.some((cmd) => cmd.name === alias || cmd.aliases.includes(alias))) { throw new Error(`A command with the name/alias "${alias}" is already registered.`); } } command.preload(); this.commands.set(command.name, command); this.logger.log(`Registered command ${command.name}.`); return command; } /** * Registers commands from a folder. * @param path The path to register from. */ registerFromFolder(path) { return (0, util_1.iterateFolder)(path, async (file) => this.register(require((0, path_1.join)(process.cwd(), file)))); } /** * Re-registers a command. * @param command The new command * @param oldCommand The old command */ reregister(commandObj, oldCommand) { if (typeof commandObj === 'function') commandObj = new commandObj(this.client); else if (typeof commandObj.default === 'function') commandObj = new commandObj.default(this.client); if (typeof commandObj.run !== 'function') throw new Error(`Invalid command object to register: ${commandObj}`); const command = commandObj; if (command.name !== oldCommand.name) throw new Error('Command name cannot change.'); command.preload(); this.commands.set(command.name, command); this.logger.log(`Reregistered command ${command.name}.`); } /** * Unregisters a command. * @param command The command to unregister */ unregister(command) { this.commands.delete(command.name); this.logger.log(`Unregistered command ${command.name}.`); } /** * Find commands with a query. * @param searchString The string to search with * @param ctx The context to check with */ find(searchString, ctx) { if (!searchString) { return ctx ? Array.from(this.commands.filter((cmd) => cmd.isUsable(ctx)).values()) : Array.from(this.commands.values()); } const matchedCommands = Array.from(this.commands .filter((cmd) => cmd.name === searchString || (cmd.aliases && cmd.aliases.some((ali) => ali === searchString))) .values()); return matchedCommands; } /** * Registers default commands. (eval, help, ping) * @param commands The commands to register, if not defined, all commands are used. */ registerDefaults(commands) { if (!commands) commands = ['eval', 'help', 'ping', 'exec', 'kill', 'load', 'unload', 'reload']; if (commands.includes('eval')) this.register(eval_1.default); if (commands.includes('help')) this.register(help_1.default); if (commands.includes('ping')) this.register(ping_1.default); if (commands.includes('exec')) this.register(exec_1.default); if (commands.includes('kill')) this.register(kill_1.default); if (commands.includes('load')) this.register(load_1.default); if (commands.includes('unload')) this.register(unload_1.default); if (commands.includes('reload')) this.register(reload_1.default); } /** @hidden */ _escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } /** @hidden */ _buildPrefixes(event) { const prefixes = []; let useMentionPrefix = false; let caseSensitive = false; if (event.has('prefix')) { const eventPrefixes = event.get('prefix'); if (Array.isArray(eventPrefixes)) prefixes.push(...eventPrefixes); else prefixes.push(eventPrefixes); } if (this.client.config.prefix && !event.has('skipConfigPrefix')) { const configPrefixes = this.client.config.prefix; if (Array.isArray(configPrefixes)) prefixes.push(...configPrefixes); else prefixes.push(configPrefixes); } if ((this.client.config.mentionPrefix && !event.has('skipConfigPrefix')) || event.has('mentionPrefix')) useMentionPrefix = true; if (this.client.config.caseSensitivePrefix || event.has('caseSensitivePrefix')) caseSensitive = true; if (!prefixes.length && !useMentionPrefix) return; const escapedPrefixes = prefixes.map(this._escapeRegExp); if (useMentionPrefix) escapedPrefixes.push(`<@!?${this.client.bot.user.id}>`); return new RegExp(`^(?<prefix>${escapedPrefixes.join('|')})(?<space> )?`, caseSensitive ? '' : 'i'); } /** @hidden */ _logCommand(level, command, ...args) { return this.client.emit('logger', level, this.options.name, args, { command }); } /** @hidden */ async onMessage(event, message) { if (message.author.bot || message.author.system) return; const prefixRegex = this._buildPrefixes(event); if (!prefixRegex) return; const match = prefixRegex.exec(message.content); if (!message.content || !match) return; const prefixUsed = match.groups.prefix; const strippedContent = message.content.slice(match[0].length); const argInterpretor = new interpreter_1.default(strippedContent); const args = argInterpretor.parseAsStrings(); const commandName = args.splice(0, 1)[0]; const ctx = new context_1.default(this, event, args, prefixUsed, message); const command = this.find(commandName, ctx)[0]; event.set('commands/invoked', !commandName || !command); if (!commandName || !command) return; event.set('commands/prefixMatch', match); event.set('commands/spacedPrefix', !!match.groups?.space); event.set('commands/strippedContent', strippedContent); event.set('commands/commandName', commandName); event.set('commands/command', command); event.set('commands/ctx', ctx); // Obtain the member if we don't have it if ('permissionsOf' in message.channel && !ctx.guild?.members.has(ctx.author.id) && !message.webhookID) { const member = (await ctx.guild?.fetchMembers({ userIDs: [ctx.author.id] }))[0]; ctx.guild.members.set(ctx.author.id, member); ctx.member = member; } // Obtain the member for the ClientUser if it doesn't already exist if ('permissionsOf' in message.channel && !ctx.guild.members.has(ctx.client.bot.user.id)) { ctx.guild.members.set(ctx.client.bot.user.id, (await ctx.guild?.fetchMembers({ userIDs: [ctx.client.bot.user.id] }))[0]); } // Ensure the user has permission to use the command const hasPermission = command.hasPermission(ctx); if (!hasPermission || typeof hasPermission === 'string') { const data = { response: typeof hasPermission === 'string' ? hasPermission : undefined }; await command.onBlock(ctx, 'permission', data); return; } // Ensure the client user has the required permissions if ('permissionsOf' in message.channel && command.clientPermissions) { const perms = message.channel.permissionsOf(this.client.bot.user.id).json; const missing = command.clientPermissions.filter((perm) => !perms[perm]); if (missing.length > 0) { const data = { missing }; await command.onBlock(ctx, 'clientPermissions', data); return; } } // Throttle the command if (command.throttling) { const throttle = await command.throttle(ctx.message); if (throttle && !throttle.okay) { const remaining = throttle.reset ? (throttle.reset - Date.now()) / 1000 : null; const data = { throttle, remaining }; command.onBlock(ctx, 'throttling', data); return; } } // Run the command try { this._logCommand('debug', command, `Running command '${command.name}' (${ctx.author.username}#${ctx.author.discriminator}, ${ctx.author.id})`); const promise = command.run(ctx); const retVal = await promise; await command.finalize(retVal, ctx); } catch (err) { try { await command.onError(err, ctx); } catch (secondErr) { this._logCommand('error', command, command.name, secondErr); } } } } exports.default = CommandsModule;