mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-15 02:53:31 +01:00
refactor: builders (#10448)
BREAKING CHANGE: formatters export removed (prev. deprecated) BREAKING CHANGE: `SelectMenuBuilder` and `SelectMenuOptionBuilder` have been removed (prev. deprecated) BREAKING CHANGE: `EmbedBuilder` no longer takes camalCase options BREAKING CHANGE: `ActionRowBuilder` now has specialized `[add/set]X` methods as opposed to the current `[add/set]Components` BREAKING CHANGE: Removed `equals` methods BREAKING CHANGE: Sapphire -> zod for validation BREAKING CHANGE: Removed the ability to pass `null`/`undefined` to clear fields, use `clearX()` instead BREAKING CHANGE: Renamed all "slash command" symbols to instead use "chat input command" BREAKING CHANGE: Removed `ContextMenuCommandBuilder` in favor of `MessageCommandBuilder` and `UserCommandBuilder` BREAKING CHANGE: Removed support for passing the "string key"s of enums BREAKING CHANGE: Removed `Button` class in favor for specialized classes depending on the style BREAKING CHANGE: Removed nested `addX` styled-methods in favor of plural `addXs` Co-authored-by: Vlad Frangu <me@vladfrangu.dev> Co-authored-by: Almeida <github@almeidx.dev>
This commit is contained in:
83
packages/builders/src/interactions/commands/Command.ts
Normal file
83
packages/builders/src/interactions/commands/Command.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import type { JSONEncodable } from '@discordjs/util';
|
||||
import type {
|
||||
ApplicationIntegrationType,
|
||||
InteractionContextType,
|
||||
Permissions,
|
||||
RESTPostAPIApplicationCommandsJSONBody,
|
||||
} from 'discord-api-types/v10';
|
||||
import type { RestOrArray } from '../../util/normalizeArray.js';
|
||||
import { normalizeArray } from '../../util/normalizeArray.js';
|
||||
|
||||
export interface CommandData
|
||||
extends Partial<
|
||||
Pick<
|
||||
RESTPostAPIApplicationCommandsJSONBody,
|
||||
'contexts' | 'default_member_permissions' | 'integration_types' | 'nsfw'
|
||||
>
|
||||
> {}
|
||||
|
||||
export abstract class CommandBuilder<Command extends RESTPostAPIApplicationCommandsJSONBody>
|
||||
implements JSONEncodable<Command>
|
||||
{
|
||||
protected declare readonly data: CommandData;
|
||||
|
||||
/**
|
||||
* Sets the contexts of this command.
|
||||
*
|
||||
* @param contexts - The contexts
|
||||
*/
|
||||
public setContexts(...contexts: RestOrArray<InteractionContextType>) {
|
||||
this.data.contexts = normalizeArray(contexts);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the integration types of this command.
|
||||
*
|
||||
* @param integrationTypes - The integration types
|
||||
*/
|
||||
public setIntegrationTypes(...integrationTypes: RestOrArray<ApplicationIntegrationType>) {
|
||||
this.data.integration_types = normalizeArray(integrationTypes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default permissions a member should have in order to run the command.
|
||||
*
|
||||
* @remarks
|
||||
* You can set this to `'0'` to disable the command by default.
|
||||
* @param permissions - The permissions bit field to set
|
||||
* @see {@link https://discord.com/developers/docs/interactions/application-commands#permissions}
|
||||
*/
|
||||
public setDefaultMemberPermissions(permissions: Permissions | bigint | number) {
|
||||
this.data.default_member_permissions = typeof permissions === 'string' ? permissions : permissions.toString();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the default permissions a member should have in order to run the command.
|
||||
*/
|
||||
public clearDefaultMemberPermissions() {
|
||||
this.data.default_member_permissions = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this command is NSFW.
|
||||
*
|
||||
* @param nsfw - Whether this command is NSFW
|
||||
*/
|
||||
public setNSFW(nsfw = true) {
|
||||
this.data.nsfw = nsfw;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes this builder to API-compatible JSON data.
|
||||
*
|
||||
* Note that by disabling validation, there is no guarantee that the resulting object will be valid.
|
||||
*
|
||||
* @param validationOverride - Force validation to run/not run regardless of your global preference
|
||||
*/
|
||||
public abstract toJSON(validationOverride?: boolean): Command;
|
||||
}
|
||||
64
packages/builders/src/interactions/commands/SharedName.ts
Normal file
64
packages/builders/src/interactions/commands/SharedName.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { LocaleString, RESTPostAPIApplicationCommandsJSONBody } from 'discord-api-types/v10';
|
||||
|
||||
export interface SharedNameData
|
||||
extends Partial<Pick<RESTPostAPIApplicationCommandsJSONBody, 'name_localizations' | 'name'>> {}
|
||||
|
||||
/**
|
||||
* This mixin holds name and description symbols for chat input commands.
|
||||
*/
|
||||
export class SharedName {
|
||||
protected readonly data: SharedNameData = {};
|
||||
|
||||
/**
|
||||
* Sets the name of this command.
|
||||
*
|
||||
* @param name - The name to use
|
||||
*/
|
||||
public setName(name: string): this {
|
||||
this.data.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a name localization for this command.
|
||||
*
|
||||
* @param locale - The locale to set
|
||||
* @param localizedName - The localized name for the given `locale`
|
||||
*/
|
||||
public setNameLocalization(locale: LocaleString, localizedName: string) {
|
||||
this.data.name_localizations ??= {};
|
||||
this.data.name_localizations[locale] = localizedName;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears a name localization for this command.
|
||||
*
|
||||
* @param locale - The locale to clear
|
||||
*/
|
||||
public clearNameLocalization(locale: LocaleString) {
|
||||
this.data.name_localizations ??= {};
|
||||
this.data.name_localizations[locale] = undefined;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name localizations for this command.
|
||||
*
|
||||
* @param localizedNames - The object of localized names to set
|
||||
*/
|
||||
public setNameLocalizations(localizedNames: Partial<Record<LocaleString, string>>) {
|
||||
this.data.name_localizations = structuredClone(localizedNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all name localizations for this command.
|
||||
*/
|
||||
public clearNameLocalizations() {
|
||||
this.data.name_localizations = undefined;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import type { APIApplicationCommand, LocaleString } from 'discord-api-types/v10';
|
||||
import type { SharedNameData } from './SharedName.js';
|
||||
import { SharedName } from './SharedName.js';
|
||||
|
||||
export interface SharedNameAndDescriptionData
|
||||
extends SharedNameData,
|
||||
Partial<Pick<APIApplicationCommand, 'description_localizations' | 'description'>> {}
|
||||
|
||||
/**
|
||||
* This mixin holds name and description symbols for chat input commands.
|
||||
*/
|
||||
export class SharedNameAndDescription extends SharedName {
|
||||
protected override readonly data: SharedNameAndDescriptionData = {};
|
||||
|
||||
/**
|
||||
* Sets the description of this command.
|
||||
*
|
||||
* @param description - The description to use
|
||||
*/
|
||||
public setDescription(description: string) {
|
||||
this.data.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a description localization for this command.
|
||||
*
|
||||
* @param locale - The locale to set
|
||||
* @param localizedDescription - The localized description for the given `locale`
|
||||
*/
|
||||
public setDescriptionLocalization(locale: LocaleString, localizedDescription: string) {
|
||||
this.data.description_localizations ??= {};
|
||||
this.data.description_localizations[locale] = localizedDescription;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears a description localization for this command.
|
||||
*
|
||||
* @param locale - The locale to clear
|
||||
*/
|
||||
public clearDescriptionLocalization(locale: LocaleString) {
|
||||
this.data.description_localizations ??= {};
|
||||
this.data.description_localizations[locale] = undefined;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description localizations for this command.
|
||||
*
|
||||
* @param localizedDescriptions - The object of localized descriptions to set
|
||||
*/
|
||||
public setDescriptionLocalizations(localizedDescriptions: Partial<Record<LocaleString, string>>) {
|
||||
this.data.description_localizations = structuredClone(localizedDescriptions);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all description localizations for this command.
|
||||
*/
|
||||
public clearDescriptionLocalizations() {
|
||||
this.data.description_localizations = undefined;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
import {
|
||||
ApplicationIntegrationType,
|
||||
InteractionContextType,
|
||||
ApplicationCommandOptionType,
|
||||
} from 'discord-api-types/v10';
|
||||
import type { ZodTypeAny } from 'zod';
|
||||
import { z } from 'zod';
|
||||
import { localeMapPredicate, memberPermissionsPredicate } from '../../../Assertions.js';
|
||||
import { ApplicationCommandOptionAllowedChannelTypes } from './mixins/ApplicationCommandOptionChannelTypesMixin.js';
|
||||
|
||||
const namePredicate = z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(32)
|
||||
.regex(/^[\p{Ll}\p{Lm}\p{Lo}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+$/u);
|
||||
|
||||
const descriptionPredicate = z.string().min(1).max(100);
|
||||
|
||||
const sharedNameAndDescriptionPredicate = z.object({
|
||||
name: namePredicate,
|
||||
name_localizations: localeMapPredicate.optional(),
|
||||
description: descriptionPredicate,
|
||||
description_localizations: localeMapPredicate.optional(),
|
||||
});
|
||||
|
||||
const numericMixinNumberOptionPredicate = z.object({
|
||||
max_value: z.number().safe().optional(),
|
||||
min_value: z.number().safe().optional(),
|
||||
});
|
||||
|
||||
const numericMixinIntegerOptionPredicate = z.object({
|
||||
max_value: z.number().safe().int().optional(),
|
||||
min_value: z.number().safe().int().optional(),
|
||||
});
|
||||
|
||||
const channelMixinOptionPredicate = z.object({
|
||||
channel_types: z
|
||||
.union(
|
||||
ApplicationCommandOptionAllowedChannelTypes.map((type) => z.literal(type)) as unknown as [
|
||||
ZodTypeAny,
|
||||
ZodTypeAny,
|
||||
...ZodTypeAny[],
|
||||
],
|
||||
)
|
||||
.array()
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const autocompleteMixinOptionPredicate = z.object({
|
||||
autocomplete: z.literal(true),
|
||||
choices: z.union([z.never(), z.never().array(), z.undefined()]),
|
||||
});
|
||||
|
||||
const choiceValueStringPredicate = z.string().min(1).max(100);
|
||||
const choiceValueNumberPredicate = z.number().safe();
|
||||
const choiceBasePredicate = z.object({
|
||||
name: choiceValueStringPredicate,
|
||||
name_localizations: localeMapPredicate.optional(),
|
||||
});
|
||||
const choiceStringPredicate = choiceBasePredicate.extend({
|
||||
value: choiceValueStringPredicate,
|
||||
});
|
||||
const choiceNumberPredicate = choiceBasePredicate.extend({
|
||||
value: choiceValueNumberPredicate,
|
||||
});
|
||||
|
||||
const choiceBaseMixinPredicate = z.object({
|
||||
autocomplete: z.literal(false).optional(),
|
||||
});
|
||||
const choiceStringMixinPredicate = choiceBaseMixinPredicate.extend({
|
||||
choices: choiceStringPredicate.array().max(25).optional(),
|
||||
});
|
||||
const choiceNumberMixinPredicate = choiceBaseMixinPredicate.extend({
|
||||
choices: choiceNumberPredicate.array().max(25).optional(),
|
||||
});
|
||||
|
||||
const basicOptionTypes = [
|
||||
ApplicationCommandOptionType.Attachment,
|
||||
ApplicationCommandOptionType.Boolean,
|
||||
ApplicationCommandOptionType.Channel,
|
||||
ApplicationCommandOptionType.Integer,
|
||||
ApplicationCommandOptionType.Mentionable,
|
||||
ApplicationCommandOptionType.Number,
|
||||
ApplicationCommandOptionType.Role,
|
||||
ApplicationCommandOptionType.String,
|
||||
ApplicationCommandOptionType.User,
|
||||
] as const;
|
||||
|
||||
const basicOptionTypesPredicate = z.union(
|
||||
basicOptionTypes.map((type) => z.literal(type)) as unknown as [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]],
|
||||
);
|
||||
|
||||
export const basicOptionPredicate = sharedNameAndDescriptionPredicate.extend({
|
||||
required: z.boolean().optional(),
|
||||
type: basicOptionTypesPredicate,
|
||||
});
|
||||
|
||||
const autocompleteOrStringChoicesMixinOptionPredicate = z.discriminatedUnion('autocomplete', [
|
||||
autocompleteMixinOptionPredicate,
|
||||
choiceStringMixinPredicate,
|
||||
]);
|
||||
|
||||
const autocompleteOrNumberChoicesMixinOptionPredicate = z.discriminatedUnion('autocomplete', [
|
||||
autocompleteMixinOptionPredicate,
|
||||
choiceNumberMixinPredicate,
|
||||
]);
|
||||
|
||||
export const channelOptionPredicate = basicOptionPredicate.merge(channelMixinOptionPredicate);
|
||||
|
||||
export const integerOptionPredicate = basicOptionPredicate
|
||||
.merge(numericMixinIntegerOptionPredicate)
|
||||
.and(autocompleteOrNumberChoicesMixinOptionPredicate);
|
||||
|
||||
export const numberOptionPredicate = basicOptionPredicate
|
||||
.merge(numericMixinNumberOptionPredicate)
|
||||
.and(autocompleteOrNumberChoicesMixinOptionPredicate);
|
||||
|
||||
export const stringOptionPredicate = basicOptionPredicate
|
||||
.extend({
|
||||
max_length: z.number().min(0).max(6_000).optional(),
|
||||
min_length: z.number().min(1).max(6_000).optional(),
|
||||
})
|
||||
.and(autocompleteOrStringChoicesMixinOptionPredicate);
|
||||
|
||||
const baseChatInputCommandPredicate = sharedNameAndDescriptionPredicate.extend({
|
||||
contexts: z.array(z.nativeEnum(InteractionContextType)).optional(),
|
||||
default_member_permissions: memberPermissionsPredicate.optional(),
|
||||
integration_types: z.array(z.nativeEnum(ApplicationIntegrationType)).optional(),
|
||||
nsfw: z.boolean().optional(),
|
||||
});
|
||||
|
||||
// Because you can only add options via builders, there's no need to validate whole objects here otherwise
|
||||
const chatInputCommandOptionsPredicate = z.union([
|
||||
z.object({ type: basicOptionTypesPredicate }).array(),
|
||||
z.object({ type: z.literal(ApplicationCommandOptionType.Subcommand) }).array(),
|
||||
z.object({ type: z.literal(ApplicationCommandOptionType.SubcommandGroup) }).array(),
|
||||
]);
|
||||
|
||||
export const chatInputCommandPredicate = baseChatInputCommandPredicate.extend({
|
||||
options: chatInputCommandOptionsPredicate.optional(),
|
||||
});
|
||||
|
||||
export const chatInputCommandSubcommandGroupPredicate = sharedNameAndDescriptionPredicate.extend({
|
||||
type: z.literal(ApplicationCommandOptionType.SubcommandGroup),
|
||||
options: z
|
||||
.array(z.object({ type: z.literal(ApplicationCommandOptionType.Subcommand) }))
|
||||
.min(1)
|
||||
.max(25),
|
||||
});
|
||||
|
||||
export const chatInputCommandSubcommandPredicate = sharedNameAndDescriptionPredicate.extend({
|
||||
type: z.literal(ApplicationCommandOptionType.Subcommand),
|
||||
options: z.array(z.object({ type: basicOptionTypesPredicate })).max(25),
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import { ApplicationCommandType, type RESTPostAPIChatInputApplicationCommandsJSONBody } from 'discord-api-types/v10';
|
||||
import { Mixin } from 'ts-mixer';
|
||||
import { isValidationEnabled } from '../../../util/validation.js';
|
||||
import { CommandBuilder } from '../Command.js';
|
||||
import { SharedNameAndDescription } from '../SharedNameAndDescription.js';
|
||||
import { chatInputCommandPredicate } from './Assertions.js';
|
||||
import { SharedChatInputCommandOptions } from './mixins/SharedChatInputCommandOptions.js';
|
||||
import { SharedChatInputCommandSubcommands } from './mixins/SharedSubcommands.js';
|
||||
|
||||
/**
|
||||
* A builder that creates API-compatible JSON data for chat input commands.
|
||||
*/
|
||||
export class ChatInputCommandBuilder extends Mixin(
|
||||
CommandBuilder<RESTPostAPIChatInputApplicationCommandsJSONBody>,
|
||||
SharedChatInputCommandOptions,
|
||||
SharedNameAndDescription,
|
||||
SharedChatInputCommandSubcommands,
|
||||
) {
|
||||
/**
|
||||
* {@inheritDoc CommandBuilder.toJSON}
|
||||
*/
|
||||
public toJSON(validationOverride?: boolean): RESTPostAPIChatInputApplicationCommandsJSONBody {
|
||||
const { options, ...rest } = this.data;
|
||||
|
||||
const data: RESTPostAPIChatInputApplicationCommandsJSONBody = {
|
||||
...structuredClone(rest as Omit<RESTPostAPIChatInputApplicationCommandsJSONBody, 'options'>),
|
||||
type: ApplicationCommandType.ChatInput,
|
||||
options: options?.map((option) => option.toJSON(validationOverride)),
|
||||
};
|
||||
|
||||
if (validationOverride ?? isValidationEnabled()) {
|
||||
chatInputCommandPredicate.parse(data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import type { JSONEncodable } from '@discordjs/util';
|
||||
import type {
|
||||
APIApplicationCommandSubcommandOption,
|
||||
APIApplicationCommandSubcommandGroupOption,
|
||||
} from 'discord-api-types/v10';
|
||||
import { ApplicationCommandOptionType } from 'discord-api-types/v10';
|
||||
import { Mixin } from 'ts-mixer';
|
||||
import { normalizeArray, type RestOrArray } from '../../../util/normalizeArray.js';
|
||||
import { resolveBuilder } from '../../../util/resolveBuilder.js';
|
||||
import { isValidationEnabled } from '../../../util/validation.js';
|
||||
import type { SharedNameAndDescriptionData } from '../SharedNameAndDescription.js';
|
||||
import { SharedNameAndDescription } from '../SharedNameAndDescription.js';
|
||||
import { chatInputCommandSubcommandGroupPredicate, chatInputCommandSubcommandPredicate } from './Assertions.js';
|
||||
import { SharedChatInputCommandOptions } from './mixins/SharedChatInputCommandOptions.js';
|
||||
|
||||
export interface ChatInputCommandSubcommandGroupData {
|
||||
options?: ChatInputCommandSubcommandBuilder[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a folder for subcommands.
|
||||
*
|
||||
* @see {@link https://discord.com/developers/docs/interactions/application-commands#subcommands-and-subcommand-groups}
|
||||
*/
|
||||
export class ChatInputCommandSubcommandGroupBuilder
|
||||
extends SharedNameAndDescription
|
||||
implements JSONEncodable<APIApplicationCommandSubcommandGroupOption>
|
||||
{
|
||||
protected declare readonly data: ChatInputCommandSubcommandGroupData & SharedNameAndDescriptionData;
|
||||
|
||||
public get options(): readonly ChatInputCommandSubcommandBuilder[] {
|
||||
return (this.data.options ??= []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new subcommand to this group.
|
||||
*
|
||||
* @param input - A function that returns a subcommand builder or an already built builder
|
||||
*/
|
||||
public addSubcommands(
|
||||
...input: RestOrArray<
|
||||
| ChatInputCommandSubcommandBuilder
|
||||
| ((subcommandGroup: ChatInputCommandSubcommandBuilder) => ChatInputCommandSubcommandBuilder)
|
||||
>
|
||||
) {
|
||||
const normalized = normalizeArray(input);
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
const result = normalized.map((builder) => resolveBuilder(builder, ChatInputCommandSubcommandBuilder));
|
||||
|
||||
this.data.options ??= [];
|
||||
this.data.options.push(...result);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes this builder to API-compatible JSON data.
|
||||
*
|
||||
* Note that by disabling validation, there is no guarantee that the resulting object will be valid.
|
||||
*
|
||||
* @param validationOverride - Force validation to run/not run regardless of your global preference
|
||||
*/
|
||||
public toJSON(validationOverride?: boolean): APIApplicationCommandSubcommandGroupOption {
|
||||
const { options, ...rest } = this.data;
|
||||
|
||||
const data = {
|
||||
...(structuredClone(rest) as Omit<APIApplicationCommandSubcommandGroupOption, 'type'>),
|
||||
type: ApplicationCommandOptionType.SubcommandGroup as const,
|
||||
options: options?.map((option) => option.toJSON(validationOverride)) ?? [],
|
||||
};
|
||||
|
||||
if (validationOverride ?? isValidationEnabled()) {
|
||||
chatInputCommandSubcommandGroupPredicate.parse(data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder that creates API-compatible JSON data for chat input command subcommands.
|
||||
*
|
||||
* @see {@link https://discord.com/developers/docs/interactions/application-commands#subcommands-and-subcommand-groups}
|
||||
*/
|
||||
export class ChatInputCommandSubcommandBuilder
|
||||
extends Mixin(SharedNameAndDescription, SharedChatInputCommandOptions)
|
||||
implements JSONEncodable<APIApplicationCommandSubcommandOption>
|
||||
{
|
||||
/**
|
||||
* Serializes this builder to API-compatible JSON data.
|
||||
*
|
||||
* Note that by disabling validation, there is no guarantee that the resulting object will be valid.
|
||||
*
|
||||
* @param validationOverride - Force validation to run/not run regardless of your global preference
|
||||
*/
|
||||
public toJSON(validationOverride?: boolean): APIApplicationCommandSubcommandOption {
|
||||
const { options, ...rest } = this.data;
|
||||
|
||||
const data = {
|
||||
...(structuredClone(rest) as Omit<APIApplicationCommandSubcommandOption, 'type'>),
|
||||
type: ApplicationCommandOptionType.Subcommand as const,
|
||||
options: options?.map((option) => option.toJSON(validationOverride)) ?? [],
|
||||
};
|
||||
|
||||
if (validationOverride ?? isValidationEnabled()) {
|
||||
chatInputCommandSubcommandPredicate.parse(data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { APIApplicationCommandIntegerOption } from 'discord-api-types/v10';
|
||||
|
||||
export interface ApplicationCommandNumericOptionMinMaxValueData
|
||||
extends Pick<APIApplicationCommandIntegerOption, 'max_value' | 'min_value'> {}
|
||||
|
||||
/**
|
||||
* This mixin holds minimum and maximum symbols used for options.
|
||||
*/
|
||||
export abstract class ApplicationCommandNumericOptionMinMaxValueMixin {
|
||||
protected declare readonly data: ApplicationCommandNumericOptionMinMaxValueData;
|
||||
|
||||
/**
|
||||
* Sets the maximum number value of this option.
|
||||
*
|
||||
* @param max - The maximum value this option can be
|
||||
*/
|
||||
public setMaxValue(max: number): this {
|
||||
this.data.max_value = max;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the maximum number value of this option.
|
||||
*/
|
||||
public clearMaxValue(): this {
|
||||
this.data.max_value = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number value of this option.
|
||||
*
|
||||
* @param min - The minimum value this option can be
|
||||
*/
|
||||
public setMinValue(min: number): this {
|
||||
this.data.min_value = min;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the minimum number value of this option.
|
||||
*/
|
||||
public clearMinValue(): this {
|
||||
this.data.min_value = undefined;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { ChannelType, type APIApplicationCommandChannelOption } from 'discord-api-types/v10';
|
||||
import { normalizeArray, type RestOrArray } from '../../../../util/normalizeArray';
|
||||
|
||||
export const ApplicationCommandOptionAllowedChannelTypes = [
|
||||
ChannelType.GuildText,
|
||||
ChannelType.GuildVoice,
|
||||
ChannelType.GuildCategory,
|
||||
ChannelType.GuildAnnouncement,
|
||||
ChannelType.AnnouncementThread,
|
||||
ChannelType.PublicThread,
|
||||
ChannelType.PrivateThread,
|
||||
ChannelType.GuildStageVoice,
|
||||
ChannelType.GuildForum,
|
||||
ChannelType.GuildMedia,
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Allowed channel types used for a channel option.
|
||||
*/
|
||||
export type ApplicationCommandOptionAllowedChannelTypes = (typeof ApplicationCommandOptionAllowedChannelTypes)[number];
|
||||
|
||||
export interface ApplicationCommandOptionChannelTypesData
|
||||
extends Pick<APIApplicationCommandChannelOption, 'channel_types'> {}
|
||||
|
||||
/**
|
||||
* This mixin holds channel type symbols used for options.
|
||||
*/
|
||||
export class ApplicationCommandOptionChannelTypesMixin {
|
||||
protected declare readonly data: ApplicationCommandOptionChannelTypesData;
|
||||
|
||||
/**
|
||||
* Adds channel types to this option.
|
||||
*
|
||||
* @param channelTypes - The channel types
|
||||
*/
|
||||
public addChannelTypes(...channelTypes: RestOrArray<ApplicationCommandOptionAllowedChannelTypes>) {
|
||||
this.data.channel_types ??= [];
|
||||
this.data.channel_types.push(...normalizeArray(channelTypes));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the channel types for this option.
|
||||
*
|
||||
* @param channelTypes - The channel types
|
||||
*/
|
||||
public setChannelTypes(...channelTypes: RestOrArray<ApplicationCommandOptionAllowedChannelTypes>) {
|
||||
this.data.channel_types = normalizeArray(channelTypes);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import type {
|
||||
APIApplicationCommandIntegerOption,
|
||||
APIApplicationCommandNumberOption,
|
||||
APIApplicationCommandStringOption,
|
||||
} from 'discord-api-types/v10';
|
||||
|
||||
export type AutocompletableOptions =
|
||||
| APIApplicationCommandIntegerOption
|
||||
| APIApplicationCommandNumberOption
|
||||
| APIApplicationCommandStringOption;
|
||||
|
||||
export interface ApplicationCommandOptionWithAutocompleteData extends Pick<AutocompletableOptions, 'autocomplete'> {}
|
||||
|
||||
/**
|
||||
* This mixin holds choices and autocomplete symbols used for options.
|
||||
*/
|
||||
export class ApplicationCommandOptionWithAutocompleteMixin {
|
||||
protected declare readonly data: ApplicationCommandOptionWithAutocompleteData;
|
||||
|
||||
/**
|
||||
* Whether this option uses autocomplete.
|
||||
*
|
||||
* @param autocomplete - Whether this option should use autocomplete
|
||||
*/
|
||||
public setAutocomplete(autocomplete = true): this {
|
||||
this.data.autocomplete = autocomplete;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { APIApplicationCommandOptionChoice } from 'discord-api-types/v10';
|
||||
import { normalizeArray, type RestOrArray } from '../../../../util/normalizeArray.js';
|
||||
|
||||
// Unlike other places, we're not `Pick`ing from discord-api-types. The union includes `[]` and it breaks everything.
|
||||
export interface ApplicationCommandOptionWithChoicesData {
|
||||
choices?: APIApplicationCommandOptionChoice<number | string>[];
|
||||
}
|
||||
|
||||
/**
|
||||
* This mixin holds choices and autocomplete symbols used for options.
|
||||
*/
|
||||
export class ApplicationCommandOptionWithChoicesMixin<ChoiceType extends number | string> {
|
||||
protected declare readonly data: ApplicationCommandOptionWithChoicesData;
|
||||
|
||||
/**
|
||||
* Adds multiple choices to this option.
|
||||
*
|
||||
* @param choices - The choices to add
|
||||
*/
|
||||
public addChoices(...choices: RestOrArray<APIApplicationCommandOptionChoice<ChoiceType>>): this {
|
||||
const normalizedChoices = normalizeArray(choices);
|
||||
|
||||
this.data.choices ??= [];
|
||||
this.data.choices.push(...normalizedChoices);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets multiple choices for this option.
|
||||
*
|
||||
* @param choices - The choices to set
|
||||
*/
|
||||
public setChoices(...choices: RestOrArray<APIApplicationCommandOptionChoice<ChoiceType>>): this {
|
||||
this.data.choices = normalizeArray(choices);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
import { normalizeArray, type RestOrArray } from '../../../../util/normalizeArray.js';
|
||||
import { resolveBuilder } from '../../../../util/resolveBuilder.js';
|
||||
import type { ApplicationCommandOptionBase } from '../options/ApplicationCommandOptionBase.js';
|
||||
import { ChatInputCommandAttachmentOption } from '../options/attachment.js';
|
||||
import { ChatInputCommandBooleanOption } from '../options/boolean.js';
|
||||
import { ChatInputCommandChannelOption } from '../options/channel.js';
|
||||
import { ChatInputCommandIntegerOption } from '../options/integer.js';
|
||||
import { ChatInputCommandMentionableOption } from '../options/mentionable.js';
|
||||
import { ChatInputCommandNumberOption } from '../options/number.js';
|
||||
import { ChatInputCommandRoleOption } from '../options/role.js';
|
||||
import { ChatInputCommandStringOption } from '../options/string.js';
|
||||
import { ChatInputCommandUserOption } from '../options/user.js';
|
||||
|
||||
export interface SharedChatInputCommandOptionsData {
|
||||
options?: ApplicationCommandOptionBase[];
|
||||
}
|
||||
|
||||
/**
|
||||
* This mixin holds symbols that can be shared in chat input command options.
|
||||
*
|
||||
* @typeParam TypeAfterAddingOptions - The type this class should return after adding an option.
|
||||
*/
|
||||
export class SharedChatInputCommandOptions {
|
||||
protected declare readonly data: SharedChatInputCommandOptionsData;
|
||||
|
||||
public get options(): readonly ApplicationCommandOptionBase[] {
|
||||
return (this.data.options ??= []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds boolean options.
|
||||
*
|
||||
* @param options - Options to add
|
||||
*/
|
||||
public addBooleanOptions(
|
||||
...options: RestOrArray<
|
||||
ChatInputCommandBooleanOption | ((builder: ChatInputCommandBooleanOption) => ChatInputCommandBooleanOption)
|
||||
>
|
||||
) {
|
||||
return this.sharedAddOptions(ChatInputCommandBooleanOption, ...options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds user options.
|
||||
*
|
||||
* @param options - Options to add
|
||||
*/
|
||||
public addUserOptions(
|
||||
...options: RestOrArray<
|
||||
ChatInputCommandUserOption | ((builder: ChatInputCommandUserOption) => ChatInputCommandUserOption)
|
||||
>
|
||||
) {
|
||||
return this.sharedAddOptions(ChatInputCommandUserOption, ...options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds channel options.
|
||||
*
|
||||
* @param options - Options to add
|
||||
*/
|
||||
public addChannelOptions(
|
||||
...options: RestOrArray<
|
||||
ChatInputCommandChannelOption | ((builder: ChatInputCommandChannelOption) => ChatInputCommandChannelOption)
|
||||
>
|
||||
) {
|
||||
return this.sharedAddOptions(ChatInputCommandChannelOption, ...options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds role options.
|
||||
*
|
||||
* @param options - Options to add
|
||||
*/
|
||||
public addRoleOptions(
|
||||
...options: RestOrArray<
|
||||
ChatInputCommandRoleOption | ((builder: ChatInputCommandRoleOption) => ChatInputCommandRoleOption)
|
||||
>
|
||||
) {
|
||||
return this.sharedAddOptions(ChatInputCommandRoleOption, ...options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds attachment options.
|
||||
*
|
||||
* @param options - Options to add
|
||||
*/
|
||||
public addAttachmentOptions(
|
||||
...options: RestOrArray<
|
||||
| ChatInputCommandAttachmentOption
|
||||
| ((builder: ChatInputCommandAttachmentOption) => ChatInputCommandAttachmentOption)
|
||||
>
|
||||
) {
|
||||
return this.sharedAddOptions(ChatInputCommandAttachmentOption, ...options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds mentionable options.
|
||||
*
|
||||
* @param options - Options to add
|
||||
*/
|
||||
public addMentionableOptions(
|
||||
...options: RestOrArray<
|
||||
| ChatInputCommandMentionableOption
|
||||
| ((builder: ChatInputCommandMentionableOption) => ChatInputCommandMentionableOption)
|
||||
>
|
||||
) {
|
||||
return this.sharedAddOptions(ChatInputCommandMentionableOption, ...options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds string options.
|
||||
*
|
||||
* @param options - Options to add
|
||||
*/
|
||||
public addStringOptions(
|
||||
...options: RestOrArray<
|
||||
ChatInputCommandStringOption | ((builder: ChatInputCommandStringOption) => ChatInputCommandStringOption)
|
||||
>
|
||||
) {
|
||||
return this.sharedAddOptions(ChatInputCommandStringOption, ...options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds integer options.
|
||||
*
|
||||
* @param options - Options to add
|
||||
*/
|
||||
public addIntegerOptions(
|
||||
...options: RestOrArray<
|
||||
ChatInputCommandIntegerOption | ((builder: ChatInputCommandIntegerOption) => ChatInputCommandIntegerOption)
|
||||
>
|
||||
) {
|
||||
return this.sharedAddOptions(ChatInputCommandIntegerOption, ...options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds number options.
|
||||
*
|
||||
* @param options - Options to add
|
||||
*/
|
||||
public addNumberOptions(
|
||||
...options: RestOrArray<
|
||||
ChatInputCommandNumberOption | ((builder: ChatInputCommandNumberOption) => ChatInputCommandNumberOption)
|
||||
>
|
||||
) {
|
||||
return this.sharedAddOptions(ChatInputCommandNumberOption, ...options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes, replaces, or inserts options for this command.
|
||||
*
|
||||
* @remarks
|
||||
* This method behaves similarly
|
||||
* to {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice | Array.prototype.splice()}.
|
||||
*
|
||||
* It's useful for modifying and adjusting order of the already-existing options for this command.
|
||||
* @example
|
||||
* Remove the first option:
|
||||
* ```ts
|
||||
* actionRow.spliceOptions(0, 1);
|
||||
* ```
|
||||
* @example
|
||||
* Remove the first n options:
|
||||
* ```ts
|
||||
* const n = 4;
|
||||
* actionRow.spliceOptions(0, n);
|
||||
* ```
|
||||
* @example
|
||||
* Remove the last option:
|
||||
* ```ts
|
||||
* actionRow.spliceOptions(-1, 1);
|
||||
* ```
|
||||
* @param index - The index to start at
|
||||
* @param deleteCount - The number of options to remove
|
||||
* @param options - The replacing option objects
|
||||
*/
|
||||
public spliceOptions(index: number, deleteCount: number, ...options: ApplicationCommandOptionBase[]): this {
|
||||
this.data.options ??= [];
|
||||
this.data.options.splice(index, deleteCount, ...options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Where the actual adding magic happens. ✨
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
private sharedAddOptions<OptionBuilder extends ApplicationCommandOptionBase>(
|
||||
Instance: new () => OptionBuilder,
|
||||
...options: RestOrArray<OptionBuilder | ((builder: OptionBuilder) => OptionBuilder)>
|
||||
): this {
|
||||
const normalized = normalizeArray(options);
|
||||
const resolved = normalized.map((option) => resolveBuilder(option, Instance));
|
||||
|
||||
this.data.options ??= [];
|
||||
this.data.options.push(...resolved);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { RestOrArray } from '../../../../util/normalizeArray.js';
|
||||
import { normalizeArray } from '../../../../util/normalizeArray.js';
|
||||
import { resolveBuilder } from '../../../../util/resolveBuilder.js';
|
||||
import {
|
||||
ChatInputCommandSubcommandGroupBuilder,
|
||||
ChatInputCommandSubcommandBuilder,
|
||||
} from '../ChatInputCommandSubcommands.js';
|
||||
|
||||
export interface SharedChatInputCommandSubcommandsData {
|
||||
options?: (ChatInputCommandSubcommandBuilder | ChatInputCommandSubcommandGroupBuilder)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* This mixin holds symbols that can be shared in chat input subcommands.
|
||||
*
|
||||
* @typeParam TypeAfterAddingSubcommands - The type this class should return after adding a subcommand or subcommand group.
|
||||
*/
|
||||
export class SharedChatInputCommandSubcommands {
|
||||
protected declare readonly data: SharedChatInputCommandSubcommandsData;
|
||||
|
||||
/**
|
||||
* Adds subcommand groups to this command.
|
||||
*
|
||||
* @param input - Subcommand groups to add
|
||||
*/
|
||||
public addSubcommandGroups(
|
||||
...input: RestOrArray<
|
||||
| ChatInputCommandSubcommandGroupBuilder
|
||||
| ((subcommandGroup: ChatInputCommandSubcommandGroupBuilder) => ChatInputCommandSubcommandGroupBuilder)
|
||||
>
|
||||
): this {
|
||||
const normalized = normalizeArray(input);
|
||||
const resolved = normalized.map((value) => resolveBuilder(value, ChatInputCommandSubcommandGroupBuilder));
|
||||
|
||||
this.data.options ??= [];
|
||||
this.data.options.push(...resolved);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds subcommands to this command.
|
||||
*
|
||||
* @param input - Subcommands to add
|
||||
*/
|
||||
public addSubcommands(
|
||||
...input: RestOrArray<
|
||||
| ChatInputCommandSubcommandBuilder
|
||||
| ((subcommandGroup: ChatInputCommandSubcommandBuilder) => ChatInputCommandSubcommandBuilder)
|
||||
>
|
||||
): this {
|
||||
const normalized = normalizeArray(input);
|
||||
const resolved = normalized.map((value) => resolveBuilder(value, ChatInputCommandSubcommandBuilder));
|
||||
|
||||
this.data.options ??= [];
|
||||
this.data.options.push(...resolved);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import type { JSONEncodable } from '@discordjs/util';
|
||||
import type {
|
||||
APIApplicationCommandBasicOption,
|
||||
APIApplicationCommandOption,
|
||||
ApplicationCommandOptionType,
|
||||
} from 'discord-api-types/v10';
|
||||
import type { z } from 'zod';
|
||||
import { isValidationEnabled } from '../../../../util/validation.js';
|
||||
import type { SharedNameAndDescriptionData } from '../../SharedNameAndDescription.js';
|
||||
import { SharedNameAndDescription } from '../../SharedNameAndDescription.js';
|
||||
import { basicOptionPredicate } from '../Assertions.js';
|
||||
|
||||
export interface ApplicationCommandOptionBaseData extends Partial<Pick<APIApplicationCommandOption, 'required'>> {
|
||||
type: ApplicationCommandOptionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base application command option builder that contains common symbols for application command builders.
|
||||
*/
|
||||
export abstract class ApplicationCommandOptionBase
|
||||
extends SharedNameAndDescription
|
||||
implements JSONEncodable<APIApplicationCommandBasicOption>
|
||||
{
|
||||
protected static readonly predicate: z.ZodTypeAny = basicOptionPredicate;
|
||||
|
||||
protected declare readonly data: ApplicationCommandOptionBaseData & SharedNameAndDescriptionData;
|
||||
|
||||
public constructor(type: ApplicationCommandOptionType) {
|
||||
super();
|
||||
this.data.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this option is required.
|
||||
*
|
||||
* @param required - Whether this option should be required
|
||||
*/
|
||||
public setRequired(required = true) {
|
||||
this.data.required = required;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes this builder to API-compatible JSON data.
|
||||
*
|
||||
* Note that by disabling validation, there is no guarantee that the resulting object will be valid.
|
||||
*
|
||||
* @param validationOverride - Force validation to run/not run regardless of your global preference
|
||||
*/
|
||||
public toJSON(validationOverride?: boolean): APIApplicationCommandBasicOption {
|
||||
const clone = structuredClone(this.data);
|
||||
|
||||
if (validationOverride ?? isValidationEnabled()) {
|
||||
(this.constructor as typeof ApplicationCommandOptionBase).predicate.parse(clone);
|
||||
}
|
||||
|
||||
return clone as APIApplicationCommandBasicOption;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ApplicationCommandOptionType } from 'discord-api-types/v10';
|
||||
import { ApplicationCommandOptionBase } from './ApplicationCommandOptionBase.js';
|
||||
|
||||
/**
|
||||
* A chat input command attachment option.
|
||||
*/
|
||||
export class ChatInputCommandAttachmentOption extends ApplicationCommandOptionBase {
|
||||
public constructor() {
|
||||
super(ApplicationCommandOptionType.Attachment);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ApplicationCommandOptionType } from 'discord-api-types/v10';
|
||||
import { ApplicationCommandOptionBase } from './ApplicationCommandOptionBase.js';
|
||||
|
||||
/**
|
||||
* A chat input command boolean option.
|
||||
*/
|
||||
export class ChatInputCommandBooleanOption extends ApplicationCommandOptionBase {
|
||||
public constructor() {
|
||||
super(ApplicationCommandOptionType.Boolean);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ApplicationCommandOptionType } from 'discord-api-types/v10';
|
||||
import { Mixin } from 'ts-mixer';
|
||||
import { channelOptionPredicate } from '../Assertions.js';
|
||||
import { ApplicationCommandOptionChannelTypesMixin } from '../mixins/ApplicationCommandOptionChannelTypesMixin.js';
|
||||
import { ApplicationCommandOptionBase } from './ApplicationCommandOptionBase.js';
|
||||
|
||||
/**
|
||||
* A chat input command channel option.
|
||||
*/
|
||||
export class ChatInputCommandChannelOption extends Mixin(
|
||||
ApplicationCommandOptionBase,
|
||||
ApplicationCommandOptionChannelTypesMixin,
|
||||
) {
|
||||
protected static override readonly predicate = channelOptionPredicate;
|
||||
|
||||
public constructor() {
|
||||
super(ApplicationCommandOptionType.Channel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ApplicationCommandOptionType } from 'discord-api-types/v10';
|
||||
import { Mixin } from 'ts-mixer';
|
||||
import { integerOptionPredicate } from '../Assertions.js';
|
||||
import { ApplicationCommandNumericOptionMinMaxValueMixin } from '../mixins/ApplicationCommandNumericOptionMinMaxValueMixin.js';
|
||||
import { ApplicationCommandOptionWithAutocompleteMixin } from '../mixins/ApplicationCommandOptionWithAutocompleteMixin.js';
|
||||
import { ApplicationCommandOptionWithChoicesMixin } from '../mixins/ApplicationCommandOptionWithChoicesMixin.js';
|
||||
import { ApplicationCommandOptionBase } from './ApplicationCommandOptionBase.js';
|
||||
|
||||
/**
|
||||
* A chat input command integer option.
|
||||
*/
|
||||
export class ChatInputCommandIntegerOption extends Mixin(
|
||||
ApplicationCommandOptionBase,
|
||||
ApplicationCommandNumericOptionMinMaxValueMixin,
|
||||
ApplicationCommandOptionWithAutocompleteMixin,
|
||||
ApplicationCommandOptionWithChoicesMixin<number>,
|
||||
) {
|
||||
protected static override readonly predicate = integerOptionPredicate;
|
||||
|
||||
public constructor() {
|
||||
super(ApplicationCommandOptionType.Integer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ApplicationCommandOptionType } from 'discord-api-types/v10';
|
||||
import { ApplicationCommandOptionBase } from './ApplicationCommandOptionBase.js';
|
||||
|
||||
/**
|
||||
* A chat input command mentionable option.
|
||||
*/
|
||||
export class ChatInputCommandMentionableOption extends ApplicationCommandOptionBase {
|
||||
public constructor() {
|
||||
super(ApplicationCommandOptionType.Mentionable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ApplicationCommandOptionType } from 'discord-api-types/v10';
|
||||
import { Mixin } from 'ts-mixer';
|
||||
import { numberOptionPredicate } from '../Assertions.js';
|
||||
import { ApplicationCommandNumericOptionMinMaxValueMixin } from '../mixins/ApplicationCommandNumericOptionMinMaxValueMixin.js';
|
||||
import { ApplicationCommandOptionWithAutocompleteMixin } from '../mixins/ApplicationCommandOptionWithAutocompleteMixin.js';
|
||||
import { ApplicationCommandOptionWithChoicesMixin } from '../mixins/ApplicationCommandOptionWithChoicesMixin.js';
|
||||
import { ApplicationCommandOptionBase } from './ApplicationCommandOptionBase.js';
|
||||
|
||||
/**
|
||||
* A chat input command number option.
|
||||
*/
|
||||
export class ChatInputCommandNumberOption extends Mixin(
|
||||
ApplicationCommandOptionBase,
|
||||
ApplicationCommandNumericOptionMinMaxValueMixin,
|
||||
ApplicationCommandOptionWithAutocompleteMixin,
|
||||
ApplicationCommandOptionWithChoicesMixin<number>,
|
||||
) {
|
||||
protected static override readonly predicate = numberOptionPredicate;
|
||||
|
||||
public constructor() {
|
||||
super(ApplicationCommandOptionType.Number);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ApplicationCommandOptionType } from 'discord-api-types/v10';
|
||||
import { ApplicationCommandOptionBase } from './ApplicationCommandOptionBase.js';
|
||||
|
||||
/**
|
||||
* A chat input command role option.
|
||||
*/
|
||||
export class ChatInputCommandRoleOption extends ApplicationCommandOptionBase {
|
||||
public constructor() {
|
||||
super(ApplicationCommandOptionType.Role);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { ApplicationCommandOptionType, type APIApplicationCommandStringOption } from 'discord-api-types/v10';
|
||||
import { Mixin } from 'ts-mixer';
|
||||
import { stringOptionPredicate } from '../Assertions.js';
|
||||
import type { ApplicationCommandOptionWithAutocompleteData } from '../mixins/ApplicationCommandOptionWithAutocompleteMixin.js';
|
||||
import { ApplicationCommandOptionWithAutocompleteMixin } from '../mixins/ApplicationCommandOptionWithAutocompleteMixin.js';
|
||||
import type { ApplicationCommandOptionWithChoicesData } from '../mixins/ApplicationCommandOptionWithChoicesMixin.js';
|
||||
import { ApplicationCommandOptionWithChoicesMixin } from '../mixins/ApplicationCommandOptionWithChoicesMixin.js';
|
||||
import { ApplicationCommandOptionBase } from './ApplicationCommandOptionBase.js';
|
||||
import type { ApplicationCommandOptionBaseData } from './ApplicationCommandOptionBase.js';
|
||||
|
||||
/**
|
||||
* A chat input command string option.
|
||||
*/
|
||||
export class ChatInputCommandStringOption extends Mixin(
|
||||
ApplicationCommandOptionBase,
|
||||
ApplicationCommandOptionWithAutocompleteMixin,
|
||||
ApplicationCommandOptionWithChoicesMixin<string>,
|
||||
) {
|
||||
protected static override readonly predicate = stringOptionPredicate;
|
||||
|
||||
protected declare readonly data: ApplicationCommandOptionBaseData &
|
||||
ApplicationCommandOptionWithAutocompleteData &
|
||||
ApplicationCommandOptionWithChoicesData &
|
||||
Partial<Pick<APIApplicationCommandStringOption, 'max_length' | 'min_length'>>;
|
||||
|
||||
public constructor() {
|
||||
super(ApplicationCommandOptionType.String);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum length of this string option.
|
||||
*
|
||||
* @param max - The maximum length this option can be
|
||||
*/
|
||||
public setMaxLength(max: number): this {
|
||||
this.data.max_length = max;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the maximum length of this string option.
|
||||
*/
|
||||
public clearMaxLength(): this {
|
||||
this.data.max_length = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum length of this string option.
|
||||
*
|
||||
* @param min - The minimum length this option can be
|
||||
*/
|
||||
public setMinLength(min: number): this {
|
||||
this.data.min_length = min;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the minimum length of this string option.
|
||||
*/
|
||||
public clearMinLength(): this {
|
||||
this.data.min_length = undefined;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ApplicationCommandOptionType } from 'discord-api-types/v10';
|
||||
import { ApplicationCommandOptionBase } from './ApplicationCommandOptionBase.js';
|
||||
|
||||
/**
|
||||
* A chat input command user option.
|
||||
*/
|
||||
export class ChatInputCommandUserOption extends ApplicationCommandOptionBase {
|
||||
public constructor() {
|
||||
super(ApplicationCommandOptionType.User);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ApplicationCommandType, ApplicationIntegrationType, InteractionContextType } from 'discord-api-types/v10';
|
||||
import { z } from 'zod';
|
||||
import { localeMapPredicate, memberPermissionsPredicate } from '../../../Assertions.js';
|
||||
|
||||
const namePredicate = z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(32)
|
||||
// eslint-disable-next-line prefer-named-capture-group
|
||||
.regex(/^( *[\p{P}\p{L}\p{N}\p{sc=Devanagari}\p{sc=Thai}]+ *)+$/u);
|
||||
|
||||
const contextsPredicate = z.array(z.nativeEnum(InteractionContextType));
|
||||
const integrationTypesPredicate = z.array(z.nativeEnum(ApplicationIntegrationType));
|
||||
|
||||
const baseContextMenuCommandPredicate = z.object({
|
||||
contexts: contextsPredicate.optional(),
|
||||
default_member_permissions: memberPermissionsPredicate.optional(),
|
||||
name: namePredicate,
|
||||
name_localizations: localeMapPredicate.optional(),
|
||||
integration_types: integrationTypesPredicate.optional(),
|
||||
nsfw: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const userCommandPredicate = baseContextMenuCommandPredicate.extend({
|
||||
type: z.literal(ApplicationCommandType.User),
|
||||
});
|
||||
|
||||
export const messageCommandPredicate = baseContextMenuCommandPredicate.extend({
|
||||
type: z.literal(ApplicationCommandType.Message),
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { ApplicationCommandType, RESTPostAPIContextMenuApplicationCommandsJSONBody } from 'discord-api-types/v10';
|
||||
import { Mixin } from 'ts-mixer';
|
||||
import { CommandBuilder } from '../Command.js';
|
||||
import { SharedName } from '../SharedName.js';
|
||||
|
||||
/**
|
||||
* The type a context menu command can be.
|
||||
*/
|
||||
export type ContextMenuCommandType = ApplicationCommandType.Message | ApplicationCommandType.User;
|
||||
|
||||
/**
|
||||
* A builder that creates API-compatible JSON data for context menu commands.
|
||||
*/
|
||||
export abstract class ContextMenuCommandBuilder extends Mixin(
|
||||
CommandBuilder<RESTPostAPIContextMenuApplicationCommandsJSONBody>,
|
||||
SharedName,
|
||||
) {
|
||||
protected override readonly data: Partial<RESTPostAPIContextMenuApplicationCommandsJSONBody>;
|
||||
|
||||
public constructor(data: Partial<RESTPostAPIContextMenuApplicationCommandsJSONBody> = {}) {
|
||||
super();
|
||||
this.data = structuredClone(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc CommandBuilder.toJSON}
|
||||
*/
|
||||
public abstract override toJSON(validationOverride?: boolean): RESTPostAPIContextMenuApplicationCommandsJSONBody;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ApplicationCommandType, type RESTPostAPIContextMenuApplicationCommandsJSONBody } from 'discord-api-types/v10';
|
||||
import { isValidationEnabled } from '../../../util/validation.js';
|
||||
import { messageCommandPredicate } from './Assertions.js';
|
||||
import { ContextMenuCommandBuilder } from './ContextMenuCommand.js';
|
||||
|
||||
export class MessageContextCommandBuilder extends ContextMenuCommandBuilder {
|
||||
/**
|
||||
* {@inheritDoc CommandBuilder.toJSON}
|
||||
*/
|
||||
public override toJSON(validationOverride?: boolean): RESTPostAPIContextMenuApplicationCommandsJSONBody {
|
||||
const data = { ...structuredClone(this.data), type: ApplicationCommandType.Message };
|
||||
|
||||
if (validationOverride ?? isValidationEnabled()) {
|
||||
messageCommandPredicate.parse(data);
|
||||
}
|
||||
|
||||
return data as RESTPostAPIContextMenuApplicationCommandsJSONBody;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ApplicationCommandType, type RESTPostAPIContextMenuApplicationCommandsJSONBody } from 'discord-api-types/v10';
|
||||
import { isValidationEnabled } from '../../../util/validation.js';
|
||||
import { userCommandPredicate } from './Assertions.js';
|
||||
import { ContextMenuCommandBuilder } from './ContextMenuCommand.js';
|
||||
|
||||
export class UserContextCommandBuilder extends ContextMenuCommandBuilder {
|
||||
/**
|
||||
* {@inheritDoc CommandBuilder.toJSON}
|
||||
*/
|
||||
public override toJSON(validationOverride?: boolean): RESTPostAPIContextMenuApplicationCommandsJSONBody {
|
||||
const data = { ...structuredClone(this.data), type: ApplicationCommandType.User };
|
||||
|
||||
if (validationOverride ?? isValidationEnabled()) {
|
||||
userCommandPredicate.parse(data);
|
||||
}
|
||||
|
||||
return data as RESTPostAPIContextMenuApplicationCommandsJSONBody;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user