UNPKG

@sapphire/framework

Version:

Discord bot framework built for advanced and amazing bots.

258 lines (256 loc) 11.8 kB
import { RegisterBehavior } from "../types/Enums.mjs"; import { getNeededRegistryParameters } from "../utils/application-commands/getNeededParameters.mjs"; import { emitPerRegistryError } from "../utils/application-commands/registriesErrors.mjs"; import { acquire, getDefaultBehaviorWhenNotIdentical, handleBulkOverwrite } from "../utils/application-commands/ApplicationCommandRegistries.mjs"; import { Args } from "../parsers/Args.mjs"; import { parseConstructorPreConditionsRequiredClientPermissions } from "../precondition-resolvers/clientPermissions.mjs"; import { PreconditionContainerArray } from "../utils/preconditions/PreconditionContainerArray.mjs"; import { parseConstructorPreConditionsCooldown } from "../precondition-resolvers/cooldown.mjs"; import { parseConstructorPreConditionsNsfw } from "../precondition-resolvers/nsfw.mjs"; import { parseConstructorPreConditionsRunIn } from "../precondition-resolvers/runIn.mjs"; import { parseConstructorPreConditionsRequiredUserPermissions } from "../precondition-resolvers/userPermissions.mjs"; import "../precondition-resolvers/index.mjs"; import { FlagUnorderedStrategy } from "../utils/strategies/FlagUnorderedStrategy.mjs"; import { AliasPiece } from "@sapphire/pieces"; import { isFunction, isNullish, isObject } from "@sapphire/utilities"; import { ChannelType, ChatInputCommandInteraction, ContextMenuCommandInteraction } from "discord.js"; import { ArgumentStream, Lexer, Parser } from "@sapphire/lexure"; //#region src/lib/structures/Command.ts const ChannelTypes = Object.values(ChannelType).filter((type) => typeof type === "number"); const GuildChannelTypes = ChannelTypes.filter((type) => type !== ChannelType.DM && type !== ChannelType.GroupDM); var Command = class 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() }); 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 ?? [ ["\"", "\""], ["“", "”"], ["「", "」"], ["«", "»"] ] }); 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 args = new ArgumentStream(new Parser(this.strategy).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) if (store.aliases.get(nameOrId) === this) store.aliases.delete(nameOrId); for (const nameOrId of registry.contextMenuCommands) if (store.aliases.get(nameOrId) === 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); } }; //#endregion export { Command }; //# sourceMappingURL=Command.mjs.map