UNPKG

@sapphire/framework

Version:

Discord bot framework built for advanced and amazing bots.

307 lines (305 loc) • 12.7 kB
import { __name } from '../../chunk-PAWJFY3S.mjs'; import { Lexer, Parser, ArgumentStream } from '@sapphire/lexure'; import { AliasPiece } from '@sapphire/pieces'; import { isFunction, isNullish, isObject } from '@sapphire/utilities'; import { ChannelType } from 'discord.js'; import { Args } from '../parsers/Args.mjs'; import { parseConstructorPreConditionsNsfw, parseConstructorPreConditionsRunIn, parseConstructorPreConditionsRequiredClientPermissions, parseConstructorPreConditionsRequiredUserPermissions, parseConstructorPreConditionsCooldown } from '../precondition-resolvers/index.mjs'; import { RegisterBehavior } from '../types/Enums.mjs'; import { acquire, getDefaultBehaviorWhenNotIdentical, handleBulkOverwrite } from '../utils/application-commands/ApplicationCommandRegistries.mjs'; import { getNeededRegistryParameters } from '../utils/application-commands/getNeededParameters.mjs'; import { emitPerRegistryError } from '../utils/application-commands/registriesErrors.mjs'; import { PreconditionContainerArray } from '../utils/preconditions/PreconditionContainerArray.mjs'; import { FlagUnorderedStrategy } from '../utils/strategies/FlagUnorderedStrategy.mjs'; var ChannelTypes = Object.values(ChannelType).filter((type) => typeof type === "number"); var GuildChannelTypes = ChannelTypes.filter((type) => type !== ChannelType.DM && type !== ChannelType.GroupDM); var _Command = class _Command extends AliasPiece { /** * @since 1.0.0 * @param context The context. * @param options Optional Command settings. */ constructor(context, options = {}) { const name = options.name ?? context.name; super(context, { ...options, name: name.toLowerCase() }); /** * The application command registry associated with this command. * @since 3.0.0 */ this.applicationCommandRegistry = acquire(this.name); this.rawName = name; this.description = options.description ?? ""; this.detailedDescription = options.detailedDescription ?? ""; this.strategy = new FlagUnorderedStrategy(options); this.fullCategory = options.fullCategory ?? this.location.directories; this.typing = options.typing ?? true; this.lexer = new Lexer({ quotes: options.quotes ?? [ ['"', '"'], // Double quotes ["\u201C", "\u201D"], // Fancy quotes (on iOS) ["\u300C", "\u300D"], // Corner brackets (CJK) ["\xAB", "\xBB"] // French quotes (guillemets) ] }); if (options.generateDashLessAliases) { const dashLessAliases = []; if (this.name.includes("-")) dashLessAliases.push(this.name.replace(/-/g, "")); for (const alias of this.aliases) if (alias.includes("-")) dashLessAliases.push(alias.replace(/-/g, "")); this.aliases = [...this.aliases, ...dashLessAliases]; } if (options.generateUnderscoreLessAliases) { const underscoreLessAliases = []; if (this.name.includes("_")) underscoreLessAliases.push(this.name.replace(/_/g, "")); for (const alias of this.aliases) if (alias.includes("_")) underscoreLessAliases.push(alias.replace(/_/g, "")); this.aliases = [...this.aliases, ...underscoreLessAliases]; } this.preconditions = new PreconditionContainerArray(options.preconditions); this.parseConstructorPreConditions(options); } /** * The message pre-parse method. This method can be overridden by plugins to define their own argument parser. * @param message The message that triggered the command. * @param parameters The raw parameters as a single string. * @param context The command-context used in this execution. */ messagePreParse(message, parameters, context) { const parser = new Parser(this.strategy); const args = new ArgumentStream(parser.run(this.lexer.run(parameters))); return new Args(message, this, args, context); } /** * The main category for the command, if any. * * This getter retrieves the first value of {@link Command.fullCategory}, if it has at least one item, otherwise it * returns `null`. * * @note You can set {@link Command.Options.fullCategory} to override the built-in category resolution. */ get category() { return this.fullCategory.at(0) ?? null; } /** * The sub-category for the command, if any. * * This getter retrieves the second value of {@link Command.fullCategory}, if it has at least two items, otherwise * it returns `null`. * * @note You can set {@link Command.Options.fullCategory} to override the built-in category resolution. */ get subCategory() { return this.fullCategory.at(1) ?? null; } /** * The parent category for the command. * * This getter retrieves the last value of {@link Command.fullCategory}, if it has at least one item, otherwise it * returns `null`. * * @note You can set {@link Command.Options.fullCategory} to override the built-in category resolution. */ get parentCategory() { return this.fullCategory.at(-1) ?? null; } /** * Defines the JSON.stringify behavior of the command. */ toJSON() { return { ...super.toJSON(), description: this.description, detailedDescription: this.detailedDescription, category: this.category }; } /** * Type-guard that ensures the command supports message commands by checking if the handler for it is present */ supportsMessageCommands() { return isFunction(Reflect.get(this, "messageRun")); } /** * Type-guard that ensures the command supports chat input commands by checking if the handler for it is present */ supportsChatInputCommands() { return isFunction(Reflect.get(this, "chatInputRun")); } /** * Type-guard that ensures the command supports context menu commands by checking if the handler for it is present */ supportsContextMenuCommands() { return isFunction(Reflect.get(this, "contextMenuRun")); } /** * Type-guard that ensures the command supports handling autocomplete interactions by checking if the handler for it is present */ supportsAutocompleteInteractions() { return isFunction(Reflect.get(this, "autocompleteRun")); } async reload() { const { store } = this; const registry = this.applicationCommandRegistry; for (const nameOrId of registry.chatInputCommands) { const aliasedPiece = store.aliases.get(nameOrId); if (aliasedPiece === this) { store.aliases.delete(nameOrId); } } for (const nameOrId of registry.contextMenuCommands) { const aliasedPiece = store.aliases.get(nameOrId); if (aliasedPiece === this) { store.aliases.delete(nameOrId); } } registry.chatInputCommands.clear(); registry.contextMenuCommands.clear(); registry.guildIdsToFetch.clear(); registry["apiCalls"].length = 0; await super.reload(); const updatedPiece = store.get(this.name); if (!updatedPiece) return; const updatedRegistry = updatedPiece.applicationCommandRegistry; if (updatedPiece.registerApplicationCommands) { try { await updatedPiece.registerApplicationCommands(updatedRegistry); } catch (err) { emitPerRegistryError(err, updatedPiece); return; } } if (!updatedRegistry["apiCalls"].length) { return; } if (getDefaultBehaviorWhenNotIdentical() === RegisterBehavior.BulkOverwrite) { await handleBulkOverwrite(store, this.container.client.application.commands); return; } const { applicationCommands, globalCommands, guildCommands } = await getNeededRegistryParameters(updatedRegistry.guildIdsToFetch); await updatedRegistry["runAPICalls"](applicationCommands, globalCommands, guildCommands); for (const nameOrId of updatedRegistry.chatInputCommands) { store.aliases.set(nameOrId, updatedPiece); } for (const nameOrId of updatedRegistry.contextMenuCommands) { store.aliases.set(nameOrId, updatedPiece); } } /** * Parses the command's options and processes them, calling {@link Command#parseConstructorPreConditionsRunIn}, * {@link Command#parseConstructorPreConditionsNsfw}, * {@link Command#parseConstructorPreConditionsRequiredClientPermissions}, and * {@link Command#parseConstructorPreConditionsCooldown}. * @since 2.0.0 * @param options The command options given from the constructor. */ parseConstructorPreConditions(options) { this.parseConstructorPreConditionsRunIn(options); this.parseConstructorPreConditionsNsfw(options); this.parseConstructorPreConditionsRequiredClientPermissions(options); this.parseConstructorPreConditionsRequiredUserPermissions(options); this.parseConstructorPreConditionsCooldown(options); } /** * Appends the `NSFW` precondition if {@link Command.Options.nsfw} is set to true. * @param options The command options given from the constructor. */ parseConstructorPreConditionsNsfw(options) { parseConstructorPreConditionsNsfw(options.nsfw, this.preconditions); } /** * Appends the `RunIn` precondition based on the values passed, defaulting to `null`, which doesn't add a * precondition. * @param options The command options given from the constructor. */ parseConstructorPreConditionsRunIn(options) { parseConstructorPreConditionsRunIn(options.runIn, this.resolveConstructorPreConditionsRunType.bind(this), this.preconditions); } /** * Appends the `ClientPermissions` precondition when {@link Command.Options.requiredClientPermissions} resolves to a * non-zero bitfield. * @param options The command options given from the constructor. */ parseConstructorPreConditionsRequiredClientPermissions(options) { parseConstructorPreConditionsRequiredClientPermissions(options.requiredClientPermissions, this.preconditions); } /** * Appends the `UserPermissions` precondition when {@link Command.Options.requiredUserPermissions} resolves to a * non-zero bitfield. * @param options The command options given from the constructor. */ parseConstructorPreConditionsRequiredUserPermissions(options) { parseConstructorPreConditionsRequiredUserPermissions(options.requiredUserPermissions, this.preconditions); } /** * Appends the `Cooldown` precondition when {@link Command.Options.cooldownLimit} and * {@link Command.Options.cooldownDelay} are both non-zero. * @param options The command options given from the constructor. */ parseConstructorPreConditionsCooldown(options) { parseConstructorPreConditionsCooldown( this, options.cooldownLimit, options.cooldownDelay, options.cooldownScope, options.cooldownFilteredUsers, this.preconditions ); } /** * Resolves the {@link Command.Options.runIn} option into a {@link Command.RunInTypes} array. * @param types The types to resolve. * @returns The resolved types, or `null` if no types were resolved. */ resolveConstructorPreConditionsRunType(types) { if (isNullish(types)) return null; if (typeof types === "number") return [types]; if (typeof types === "string") { switch (types) { case "DM": return [ChannelType.DM]; case "GUILD_TEXT": return [ChannelType.GuildText]; case "GUILD_VOICE": return [ChannelType.GuildVoice]; case "GUILD_NEWS": return [ChannelType.GuildAnnouncement]; case "GUILD_NEWS_THREAD": return [ChannelType.AnnouncementThread]; case "GUILD_PUBLIC_THREAD": return [ChannelType.PublicThread]; case "GUILD_PRIVATE_THREAD": return [ChannelType.PrivateThread]; case "GUILD_ANY": return GuildChannelTypes; default: return null; } } if (types.length === 0) { throw new Error(`${this.constructor.name}[${this.name}]: "runIn" was specified as an empty array.`); } if (types.length === 1) { return this.resolveConstructorPreConditionsRunType(types[0]); } const resolved = /* @__PURE__ */ new Set(); for (const typeResolvable of types) { for (const type of this.resolveConstructorPreConditionsRunType(typeResolvable) ?? []) resolved.add(type); } if (resolved.size === ChannelTypes.length) return null; return [...resolved].sort((a, b) => a - b); } static runInTypeIsSpecificsObject(types) { if (!isObject(types)) { return false; } const specificTypes = types; return Boolean(specificTypes.chatInputRun || specificTypes.messageRun || specificTypes.contextMenuRun); } }; __name(_Command, "Command"); var Command = _Command; export { Command }; //# sourceMappingURL=Command.mjs.map //# sourceMappingURL=Command.mjs.map