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