diff --git a/packages/builders/__tests__/interactions/ChatInputCommands/ChatInputCommands.test.ts b/packages/builders/__tests__/interactions/ChatInputCommands/ChatInputCommands.test.ts index 48a212165..74b475b50 100644 --- a/packages/builders/__tests__/interactions/ChatInputCommands/ChatInputCommands.test.ts +++ b/packages/builders/__tests__/interactions/ChatInputCommands/ChatInputCommands.test.ts @@ -379,21 +379,9 @@ describe('ChatInput Commands', () => { describe('Subcommand builder and subcommand group builder', () => { test('GIVEN both types THEN does not throw error', () => { expect(() => - getBuilder() - .setName('test') - .setDescription('Test command') - .addSubcommands((subcommand) => - subcommand.setName('subcommand').setDescription('Description of subcommand'), - ) - .addSubcommandGroups((subcommandGroup) => - subcommandGroup - .setName('group') - .setDescription('Description of group') - - .addSubcommands((subcommand) => - subcommand.setName('subcommand').setDescription('Description of group subcommand'), - ), - ) + getNamedBuilder() + .addSubcommands(getSubcommand()) + .addSubcommandGroups(getSubcommandGroup().addSubcommands(getSubcommand())) .toJSON(), ).not.toThrowError(); }); diff --git a/packages/builders/src/interactions/commands/chatInput/Assertions.ts b/packages/builders/src/interactions/commands/chatInput/Assertions.ts index 2a313db8e..ebee3c295 100644 --- a/packages/builders/src/interactions/commands/chatInput/Assertions.ts +++ b/packages/builders/src/interactions/commands/chatInput/Assertions.ts @@ -2,6 +2,7 @@ import { ApplicationIntegrationType, InteractionContextType, ApplicationCommandOptionType, + ApplicationCommandType, } from 'discord-api-types/v10'; import { z } from 'zod'; import { localeMapPredicate, memberPermissionsPredicate } from '../../../Assertions.js'; @@ -47,38 +48,55 @@ const choiceBasePredicate = z.object({ name: choiceValueStringPredicate, name_localizations: localeMapPredicate.optional(), }); -const choiceStringPredicate = choiceBasePredicate.extend({ +const choiceStringPredicate = z.object({ + ...choiceBasePredicate.shape, value: choiceValueStringPredicate, }); -const choiceNumberPredicate = choiceBasePredicate.extend({ +const choiceNumberPredicate = z.object({ + ...choiceBasePredicate.shape, value: choiceValueNumberPredicate, }); const choiceBaseMixinPredicate = z.object({ autocomplete: z.literal(false).optional(), }); -const choiceStringMixinPredicate = choiceBaseMixinPredicate.extend({ +const choiceStringMixinPredicate = z.object({ + ...choiceBaseMixinPredicate.shape, choices: choiceStringPredicate.array().max(25).optional(), }); -const choiceNumberMixinPredicate = choiceBaseMixinPredicate.extend({ +const choiceNumberMixinPredicate = z.object({ + ...choiceBaseMixinPredicate.shape, choices: choiceNumberPredicate.array().max(25).optional(), }); -const basicOptionTypesPredicate = z.literal([ - ApplicationCommandOptionType.Attachment, - ApplicationCommandOptionType.Boolean, - ApplicationCommandOptionType.Channel, - ApplicationCommandOptionType.Integer, - ApplicationCommandOptionType.Mentionable, - ApplicationCommandOptionType.Number, - ApplicationCommandOptionType.Role, - ApplicationCommandOptionType.String, - ApplicationCommandOptionType.User, -]); - -export const basicOptionPredicate = sharedNameAndDescriptionPredicate.extend({ +export const baseBasicOptionPredicate = z.object({ + ...sharedNameAndDescriptionPredicate.shape, required: z.boolean().optional(), - type: basicOptionTypesPredicate, +}); + +export const attachmentOptionPredicate = z.object({ + ...baseBasicOptionPredicate.shape, + type: z.literal(ApplicationCommandOptionType.Attachment), +}); + +export const booleanOptionPredicate = z.object({ + ...baseBasicOptionPredicate.shape, + type: z.literal(ApplicationCommandOptionType.Boolean), +}); + +export const mentionableOptionPredicate = z.object({ + ...baseBasicOptionPredicate.shape, + type: z.literal(ApplicationCommandOptionType.Mentionable), +}); + +export const roleOptionPredicate = z.object({ + ...baseBasicOptionPredicate.shape, + type: z.literal(ApplicationCommandOptionType.Role), +}); + +export const userOptionPredicate = z.object({ + ...baseBasicOptionPredicate.shape, + type: z.literal(ApplicationCommandOptionType.User), }); const autocompleteOrStringChoicesMixinOptionPredicate = z.discriminatedUnion('autocomplete', [ @@ -92,64 +110,71 @@ const autocompleteOrNumberChoicesMixinOptionPredicate = z.discriminatedUnion('au ]); export const channelOptionPredicate = z.object({ - ...basicOptionPredicate.shape, + ...baseBasicOptionPredicate.shape, ...channelMixinOptionPredicate.shape, + type: z.literal(ApplicationCommandOptionType.Channel), }); export const integerOptionPredicate = z .object({ - ...basicOptionPredicate.shape, + ...baseBasicOptionPredicate.shape, ...numericMixinIntegerOptionPredicate.shape, + type: z.literal(ApplicationCommandOptionType.Integer), }) .and(autocompleteOrNumberChoicesMixinOptionPredicate); export const numberOptionPredicate = z .object({ - ...basicOptionPredicate.shape, + ...baseBasicOptionPredicate.shape, ...numericMixinNumberOptionPredicate.shape, + type: z.literal(ApplicationCommandOptionType.Number), }) .and(autocompleteOrNumberChoicesMixinOptionPredicate); -export const stringOptionPredicate = basicOptionPredicate - .extend({ +export const stringOptionPredicate = z + .object({ + ...baseBasicOptionPredicate.shape, max_length: z.number().min(1).max(6_000).optional(), min_length: z.number().min(0).max(6_000).optional(), + type: z.literal(ApplicationCommandOptionType.String), }) .and(autocompleteOrStringChoicesMixinOptionPredicate); -const baseChatInputCommandPredicate = sharedNameAndDescriptionPredicate.extend({ +const basicOptionPredicates = [ + attachmentOptionPredicate, + booleanOptionPredicate, + channelOptionPredicate, + integerOptionPredicate, + mentionableOptionPredicate, + numberOptionPredicate, + roleOptionPredicate, + stringOptionPredicate, + userOptionPredicate, +]; + +export const chatInputCommandSubcommandPredicate = z.object({ + ...sharedNameAndDescriptionPredicate.shape, + type: z.literal(ApplicationCommandOptionType.Subcommand), + options: z.array(z.union(basicOptionPredicates)).max(25).optional(), +}); + +export const chatInputCommandSubcommandGroupPredicate = z.object({ + ...sharedNameAndDescriptionPredicate.shape, + type: z.literal(ApplicationCommandOptionType.SubcommandGroup), + options: z.array(chatInputCommandSubcommandPredicate).min(1).max(25), +}); + +export const chatInputCommandPredicate = z.object({ + ...sharedNameAndDescriptionPredicate.shape, contexts: z.array(z.enum(InteractionContextType)).optional(), default_member_permissions: memberPermissionsPredicate.optional(), integration_types: z.array(z.enum(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.union([ - z.literal(ApplicationCommandOptionType.Subcommand), - 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), + .union([ + z.array(z.union(basicOptionPredicates)).max(25), + z.array(z.union([chatInputCommandSubcommandPredicate, chatInputCommandSubcommandGroupPredicate])).max(25), + ]) + .optional(), + type: z.literal(ApplicationCommandType.ChatInput).optional(), }); diff --git a/packages/builders/src/interactions/commands/chatInput/ChatInputCommand.ts b/packages/builders/src/interactions/commands/chatInput/ChatInputCommand.ts index 4d90c2cce..e5f59125c 100644 --- a/packages/builders/src/interactions/commands/chatInput/ChatInputCommand.ts +++ b/packages/builders/src/interactions/commands/chatInput/ChatInputCommand.ts @@ -30,7 +30,7 @@ export class ChatInputCommandBuilder extends Mixin( const data: RESTPostAPIChatInputApplicationCommandsJSONBody = { ...structuredClone(rest as Omit), type: ApplicationCommandType.ChatInput, - options: options?.map((option) => option.toJSON(validationOverride)), + options: options?.map((option) => option.toJSON(false)), }; validate(chatInputCommandPredicate, data, validationOverride); diff --git a/packages/builders/src/interactions/commands/chatInput/options/ApplicationCommandOptionBase.ts b/packages/builders/src/interactions/commands/chatInput/options/ApplicationCommandOptionBase.ts index 9416cb1cf..76ad1b41b 100644 --- a/packages/builders/src/interactions/commands/chatInput/options/ApplicationCommandOptionBase.ts +++ b/packages/builders/src/interactions/commands/chatInput/options/ApplicationCommandOptionBase.ts @@ -8,7 +8,6 @@ import type { z } from 'zod'; import { validate } 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> { type: ApplicationCommandOptionType; @@ -24,7 +23,7 @@ export abstract class ApplicationCommandOptionBase /** * @internal */ - protected static readonly predicate: z.ZodType = basicOptionPredicate; + protected static readonly predicate: z.ZodType; /** * @internal diff --git a/packages/builders/src/interactions/commands/chatInput/options/attachment.ts b/packages/builders/src/interactions/commands/chatInput/options/attachment.ts index 2cf2be045..097c67f29 100644 --- a/packages/builders/src/interactions/commands/chatInput/options/attachment.ts +++ b/packages/builders/src/interactions/commands/chatInput/options/attachment.ts @@ -1,10 +1,16 @@ import { ApplicationCommandOptionType } from 'discord-api-types/v10'; +import { attachmentOptionPredicate } from '../Assertions.js'; import { ApplicationCommandOptionBase } from './ApplicationCommandOptionBase.js'; /** * A chat input command attachment option. */ export class ChatInputCommandAttachmentOption extends ApplicationCommandOptionBase { + /** + * @internal + */ + protected static override readonly predicate = attachmentOptionPredicate; + /** * Creates a new attachment option. */ diff --git a/packages/builders/src/interactions/commands/chatInput/options/boolean.ts b/packages/builders/src/interactions/commands/chatInput/options/boolean.ts index 27d4ae540..a08273bd4 100644 --- a/packages/builders/src/interactions/commands/chatInput/options/boolean.ts +++ b/packages/builders/src/interactions/commands/chatInput/options/boolean.ts @@ -1,10 +1,16 @@ import { ApplicationCommandOptionType } from 'discord-api-types/v10'; +import { booleanOptionPredicate } from '../Assertions.js'; import { ApplicationCommandOptionBase } from './ApplicationCommandOptionBase.js'; /** * A chat input command boolean option. */ export class ChatInputCommandBooleanOption extends ApplicationCommandOptionBase { + /** + * @internal + */ + protected static override readonly predicate = booleanOptionPredicate; + /** * Creates a new boolean option. */ diff --git a/packages/builders/src/interactions/commands/chatInput/options/mentionable.ts b/packages/builders/src/interactions/commands/chatInput/options/mentionable.ts index e039db61f..866b3ecac 100644 --- a/packages/builders/src/interactions/commands/chatInput/options/mentionable.ts +++ b/packages/builders/src/interactions/commands/chatInput/options/mentionable.ts @@ -1,10 +1,16 @@ import { ApplicationCommandOptionType } from 'discord-api-types/v10'; +import { mentionableOptionPredicate } from '../Assertions.js'; import { ApplicationCommandOptionBase } from './ApplicationCommandOptionBase.js'; /** * A chat input command mentionable option. */ export class ChatInputCommandMentionableOption extends ApplicationCommandOptionBase { + /** + * @internal + */ + protected static override readonly predicate = mentionableOptionPredicate; + /** * Creates a new mentionable option. */ diff --git a/packages/builders/src/interactions/commands/chatInput/options/role.ts b/packages/builders/src/interactions/commands/chatInput/options/role.ts index 146ce22d0..4ba215cc5 100644 --- a/packages/builders/src/interactions/commands/chatInput/options/role.ts +++ b/packages/builders/src/interactions/commands/chatInput/options/role.ts @@ -1,10 +1,16 @@ import { ApplicationCommandOptionType } from 'discord-api-types/v10'; +import { roleOptionPredicate } from '../Assertions.js'; import { ApplicationCommandOptionBase } from './ApplicationCommandOptionBase.js'; /** * A chat input command role option. */ export class ChatInputCommandRoleOption extends ApplicationCommandOptionBase { + /** + * @internal + */ + protected static override readonly predicate = roleOptionPredicate; + /** * Creates a new role option. */ diff --git a/packages/builders/src/interactions/commands/chatInput/options/user.ts b/packages/builders/src/interactions/commands/chatInput/options/user.ts index 7f5a83f5e..8f7c4ef7c 100644 --- a/packages/builders/src/interactions/commands/chatInput/options/user.ts +++ b/packages/builders/src/interactions/commands/chatInput/options/user.ts @@ -1,10 +1,16 @@ import { ApplicationCommandOptionType } from 'discord-api-types/v10'; +import { userOptionPredicate } from '../Assertions.js'; import { ApplicationCommandOptionBase } from './ApplicationCommandOptionBase.js'; /** * A chat input command user option. */ export class ChatInputCommandUserOption extends ApplicationCommandOptionBase { + /** + * @internal + */ + protected static override readonly predicate = userOptionPredicate; + /** * Creates a new user option. */