@sapphire/framework
Version:
Discord bot framework built for advanced and amazing bots.
258 lines (256 loc) • 11.8 kB
JavaScript
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