diff --git a/packages/bot/src/desiredProperties.ts b/packages/bot/src/desiredProperties.ts index ec993fcc8..11af415f9 100644 --- a/packages/bot/src/desiredProperties.ts +++ b/packages/bot/src/desiredProperties.ts @@ -327,6 +327,7 @@ export function createDesiredPropertiesObject; + + if (props.type && payload.type) radioGroup.type = payload.type; + if (props.id && payload.id) radioGroup.id = payload.id; + if (props.customId && payload.custom_id) radioGroup.customId = payload.custom_id; + + // Check if this is the component (has options) or the interaction response (modal submit, has value) + if ('options' in payload) { + if (props.options && payload.options) radioGroup.options = payload.options; + if (props.required && payload.required !== undefined) radioGroup.required = payload.required; + } else { + if (props.value) radioGroup.value = payload.value ?? undefined; + } + + return radioGroup; +} + +function transformCheckboxGroupComponent(bot: Bot, payload: DiscordCheckboxGroupComponent | DiscordCheckboxGroupInteractionResponse) { + const props = bot.transformers.desiredProperties.component; + const checkboxGroup = {} as SetupDesiredProps; + + if (props.type && payload.type) checkboxGroup.type = payload.type; + if (props.id && payload.id) checkboxGroup.id = payload.id; + if (props.customId && payload.custom_id) checkboxGroup.customId = payload.custom_id; + + // Check if this is the component (has options) or the interaction response (modal submit, has values) + if ('options' in payload) { + if (props.options && payload.options) checkboxGroup.options = payload.options; + if (props.minValues && payload.min_values !== undefined) checkboxGroup.minValues = payload.min_values; + if (props.maxValues && payload.max_values !== undefined) checkboxGroup.maxValues = payload.max_values; + if (props.required && payload.required !== undefined) checkboxGroup.required = payload.required; + } else { + if (props.values && payload.values) checkboxGroup.values = payload.values; + } + + return checkboxGroup; +} + +function transformCheckboxComponent(bot: Bot, payload: DiscordCheckboxComponent | DiscordCheckboxInteractionResponse) { + const props = bot.transformers.desiredProperties.component; + const checkbox = {} as SetupDesiredProps; + + if (props.type && payload.type) checkbox.type = payload.type; + if (props.id && payload.id) checkbox.id = payload.id; + if (props.customId && payload.custom_id) checkbox.customId = payload.custom_id; + + if (props.value && 'value' in payload) checkbox.value = payload.value; + if (props.default && 'default' in payload) checkbox.default = payload.default; + + return checkbox; +} diff --git a/packages/bot/src/transformers/reverse/component.ts b/packages/bot/src/transformers/reverse/component.ts index 4aded23a2..f195cc1d3 100644 --- a/packages/bot/src/transformers/reverse/component.ts +++ b/packages/bot/src/transformers/reverse/component.ts @@ -2,6 +2,8 @@ import { type ButtonStyles, type DiscordActionRow, type DiscordButtonComponent, + type DiscordCheckboxComponent, + type DiscordCheckboxGroupComponent, type DiscordChannelSelectComponent, type DiscordChannelSelectInteractionResponseFromModal, type DiscordContainerComponent, @@ -14,6 +16,7 @@ import { type DiscordMentionableSelectInteractionResponseFromModal, type DiscordMessageComponent, type DiscordMessageComponentFromModalInteractionResponse, + type DiscordRadioGroupComponent, type DiscordRoleSelectComponent, type DiscordRoleSelectInteractionResponseFromModal, type DiscordSectionComponent, @@ -27,6 +30,7 @@ import { type DiscordUserSelectComponent, type DiscordUserSelectInteractionResponseFromModal, MessageComponentTypes, + type SelectOption, type TextStyles, } from '@discordeno/types'; import type { Bot } from '../../bot.js'; @@ -68,6 +72,12 @@ export function transformComponentToDiscordComponent( return transformLabelComponent(bot, payload); case MessageComponentTypes.FileUpload: return transformFileUploadComponent(bot, payload); + case MessageComponentTypes.RadioGroup: + return transformRadioGroupComponent(bot, payload); + case MessageComponentTypes.CheckboxGroup: + return transformCheckboxGroupComponent(bot, payload); + case MessageComponentTypes.Checkbox: + return transformCheckboxComponent(bot, payload); case MessageComponentTypes.Separator: case MessageComponentTypes.TextDisplay: // As of now they are compatible @@ -143,7 +153,7 @@ function transformInputTextComponent(_bot: Bot, payload: Component): DiscordText style: payload.style as TextStyles, custom_id: payload.customId!, label: payload.label!, - value: payload.value, + value: payload.value as string | undefined, max_length: payload.maxLength, min_length: payload.minLength, placeholder: payload.placeholder, @@ -171,7 +181,7 @@ function transformStringSelectMenuComponent( disabled: payload.disabled, max_values: payload.maxValues, min_values: payload.minValues, - options: payload.options?.map((option) => ({ + options: (payload.options as SelectOption[] | undefined)?.map((option) => ({ label: option.label, value: option.value, description: option.description, @@ -354,3 +364,34 @@ function transformFileUploadComponent(bot: Bot, payload: Component): DiscordFile required: payload.required, }; } + +function transformRadioGroupComponent(bot: Bot, payload: Component): DiscordRadioGroupComponent { + return { + type: MessageComponentTypes.RadioGroup, + id: payload.id, + custom_id: payload.customId!, + options: payload.options ?? [], + required: payload.required, + }; +} + +function transformCheckboxGroupComponent(bot: Bot, payload: Component): DiscordCheckboxGroupComponent { + return { + type: MessageComponentTypes.CheckboxGroup, + id: payload.id, + custom_id: payload.customId!, + options: payload.options ?? [], + min_values: payload.minValues, + max_values: payload.maxValues, + required: payload.required, + }; +} + +function transformCheckboxComponent(bot: Bot, payload: Component): DiscordCheckboxComponent { + return { + type: MessageComponentTypes.Checkbox, + id: payload.id, + custom_id: payload.customId!, + default: payload.default, + }; +} diff --git a/packages/bot/src/transformers/types.ts b/packages/bot/src/transformers/types.ts index 19667ff1d..d0f101806 100644 --- a/packages/bot/src/transformers/types.ts +++ b/packages/bot/src/transformers/types.ts @@ -13,6 +13,7 @@ import type { ButtonStyles, Camelize, ChannelTypes, + CheckboxGroupOption, DefaultMessageNotificationLevels, DiscordActivityInstanceResource, DiscordActivityLocationKind, @@ -54,6 +55,7 @@ import type { PremiumTiers, PremiumTypes, PresenceStatus, + RadioGroupOption, RoleFlags, ScheduledEventEntityType, ScheduledEventPrivacyLevel, @@ -576,8 +578,8 @@ export interface Component { style?: ButtonStyles | TextStyles; /** text that appears on the button (max 80 characters) */ label?: string; - /** the dev-define value of the option, max 100 characters for select or 4000 for input. */ - value?: string; + /** the dev-define value of the option, max 100 characters for select or 4000 for input; or boolean for checkbox response. */ + value?: string | boolean; /** Emoji object that includes fields of name, id, and animated supporting unicode and custom emojis. */ emoji?: Pick, 'id' | 'name' | 'animated'>; /** optional url for link-style buttons that can navigate a user to the web. Only type 5 Link buttons can have a url */ @@ -585,7 +587,8 @@ export interface Component { /** List of channel types to include in a channel select menu options list */ channelTypes?: ChannelTypes[]; /** The choices! Maximum of 25 items. */ - options?: SelectOption[]; + /** The string select options or the radio or checkbox group options */ + options?: SelectOption[] | RadioGroupOption[] | CheckboxGroupOption[]; /** A custom placeholder text if nothing is selected. Maximum 150 characters. */ placeholder?: string; /** The minimum number of items that must be selected. Default 1. Between 1-25. */ @@ -632,6 +635,8 @@ export interface Component { component?: Component; /** The text of the selected options */ values?: string[]; + /** Whether the checkbox is selected by default (Checkbox component). */ + default?: boolean; /** Resolved entities from selected options */ resolved?: InteractionDataResolved; } diff --git a/packages/types/src/discord/components.ts b/packages/types/src/discord/components.ts index a8f97dd64..a8f885594 100644 --- a/packages/types/src/discord/components.ts +++ b/packages/types/src/discord/components.ts @@ -41,6 +41,12 @@ export enum MessageComponentTypes { Label, /** Component for uploading files */ FileUpload, + /** An interactive component for selecting exactly one option from a defined list */ + RadioGroup = 21, + /** An interactive component for selecting one or many options via checkboxes */ + CheckboxGroup = 22, + /** A single interactive component for yes/no style questions */ + Checkbox = 23, } export type DiscordMessageComponents = DiscordMessageComponent[]; @@ -61,12 +67,18 @@ export type DiscordMessageComponent = | DiscordContainerComponent | DiscordFileComponent | DiscordLabelComponent - | DiscordFileUploadComponent; + | DiscordFileUploadComponent + | DiscordRadioGroupComponent + | DiscordCheckboxGroupComponent + | DiscordCheckboxComponent; export type DiscordMessageComponentFromModalInteractionResponse = | DiscordTextInputInteractionResponse | DiscordTextDisplayInteractionResponse | DiscordLabelInteractionResponse + | DiscordRadioGroupInteractionResponse + | DiscordCheckboxGroupInteractionResponse + | DiscordCheckboxInteractionResponse | DiscordRoleSelectInteractionResponseFromModal | DiscordUserSelectInteractionResponseFromModal | DiscordStringSelectInteractionResponseFromModal @@ -699,6 +711,10 @@ export interface DiscordLabelComponent extends DiscordBaseComponent { /** The component within the label */ component: | DiscordTextInputComponent + | DiscordFileUploadComponent + | DiscordRadioGroupComponent + | DiscordCheckboxGroupComponent + | DiscordCheckboxComponent | DiscordStringSelectComponent | DiscordUserSelectComponent | DiscordRoleSelectComponent @@ -720,7 +736,10 @@ export interface DiscordLabelInteractionResponse { | DiscordRoleSelectInteractionResponseFromModal | DiscordMentionableSelectInteractionResponseFromModal | DiscordChannelSelectInteractionResponseFromModal - | DiscordFileUploadInteractionResponse; + | DiscordFileUploadInteractionResponse + | DiscordRadioGroupInteractionResponse + | DiscordCheckboxGroupInteractionResponse + | DiscordCheckboxInteractionResponse; } /** https://docs.discord.com/developers/components/reference#file-upload-file-upload-structure */ @@ -766,6 +785,98 @@ export interface DiscordFileUploadInteractionResponse { values: string[]; } +/** https://docs.discord.com/developers/components/reference#radio-group-structure */ +export interface DiscordRadioGroupComponent extends DiscordBaseComponent { + type: MessageComponentTypes.RadioGroup; + /** A dev-defined unique string for the component; 1-100 characters. */ + custom_id: string; + /** List of options to show; min 2, max 10. */ + options: DiscordRadioGroupOption[]; + /** Whether a selection is required to submit the modal. Defaults to `true`. */ + required?: boolean; +} + +/** https://docs.discord.com/developers/components/reference#radio-group-option-structure */ +export interface DiscordRadioGroupOption { + /** The dev-defined value of the option. Maximum 100 characters. */ + value: string; + /** The user-facing label of the option. Maximum 100 characters. */ + label: string; + /** An optional description for the option. Maximum 100 characters. */ + description?: string; + /** Will render this option as already-selected by default. */ + default?: boolean; +} + +/** https://docs.discord.com/developers/components/reference#radio-group-interaction-response-structure */ +export interface DiscordRadioGroupInteractionResponse { + type: MessageComponentTypes.RadioGroup; + /** 32 bit integer used as an optional identifier for component */ + id: number; + /** The custom id for the radio group */ + custom_id: string; + /** The value of the selected option, or null if no option is selected */ + value?: string | null; +} + +/** https://docs.discord.com/developers/components/reference#checkbox-group-structure */ +export interface DiscordCheckboxGroupComponent extends DiscordBaseComponent { + type: MessageComponentTypes.CheckboxGroup; + /** A dev-defined unique string for the component; 1-100 characters. */ + custom_id: string; + /** List of options to show; min 1, max 10. */ + options: DiscordCheckboxGroupOption[]; + /** Minimum number of items that must be chosen; min 0, max 10. Defaults to 1; if set to 0 required must be false. */ + min_values?: number; + /** Maximum number of items that can be chosen; min 1, max 10. Defaults to the number of options. */ + max_values?: number; + /** Whether selecting within the group is required. Defaults to `true`. */ + required?: boolean; +} + +/** https://docs.discord.com/developers/components/reference#checkbox-group-option-structure */ +export interface DiscordCheckboxGroupOption { + /** The dev-defined value of the option. Maximum 100 characters. */ + value: string; + /** The user-facing label of the option. Maximum 100 characters. */ + label: string; + /** An optional description for the option. Maximum 100 characters. */ + description?: string; + /** Will render this option as already-selected by default. */ + default?: boolean; +} + +/** https://docs.discord.com/developers/components/reference#checkbox-group-interaction-response-structure */ +export interface DiscordCheckboxGroupInteractionResponse { + type: MessageComponentTypes.CheckboxGroup; + /** 32 bit integer used as an optional identifier for component */ + id: number; + /** The custom id for the checkbox group */ + custom_id: string; + /** The values of the selected options, or an empty array if no options are selected */ + values: string[]; +} + +/** https://docs.discord.com/developers/components/reference#checkbox-structure */ +export interface DiscordCheckboxComponent extends DiscordBaseComponent { + type: MessageComponentTypes.Checkbox; + /** A dev-defined unique string for the component; 1-100 characters. */ + custom_id: string; + /** Whether the checkbox is selected by default. */ + default?: boolean; +} + +/** https://docs.discord.com/developers/components/reference#checkbox-interaction-response-structure */ +export interface DiscordCheckboxInteractionResponse { + type: MessageComponentTypes.Checkbox; + /** 32 bit integer used as an optional identifier for component */ + id: number; + /** The custom id for the checkbox */ + custom_id: string; + /** The state of the checkbox (true if checked, false if unchecked) */ + value: boolean; +} + /** https://docs.discord.com/developers/components/reference#unfurled-media-item-structure */ export interface DiscordUnfurledMediaItem { /** Supports arbitrary urls and attachment:// references */ diff --git a/packages/types/src/discordeno/components.ts b/packages/types/src/discordeno/components.ts index 42b39a1d1..9d688fd37 100644 --- a/packages/types/src/discordeno/components.ts +++ b/packages/types/src/discordeno/components.ts @@ -29,7 +29,10 @@ export type MessageComponent = | ContainerComponent | FileComponent | LabelComponent - | FileUploadComponent; + | FileUploadComponent + | RadioGroupComponent + | CheckboxGroupComponent + | CheckboxComponent; /** https://docs.discord.com/developers/components/reference#anatomy-of-a-component */ export interface BaseComponent { @@ -440,7 +443,10 @@ export interface LabelComponent extends BaseComponent { | RoleSelectComponent | MentionableSelectComponent | ChannelSelectComponent - | FileUploadComponent; + | FileUploadComponent + | RadioGroupComponent + | CheckboxGroupComponent + | CheckboxComponent; } /** https://docs.discord.com/developers/components/reference#file-upload-file-upload-structure */ @@ -469,3 +475,62 @@ export interface FileUploadComponent extends BaseComponent { */ required?: boolean; } + +/** https://docs.discord.com/developers/components/reference#radio-group-structure */ +export interface RadioGroupComponent extends BaseComponent { + type: MessageComponentTypes.RadioGroup; + /** A custom identifier for this component. Maximum 100 characters. */ + customId: string; + /** List of options to show; min 2, max 10. */ + options: RadioGroupOption[]; + /** Whether a selection is required to submit the modal. Defaults to `true`. */ + required?: boolean; +} + +/** https://docs.discord.com/developers/components/reference#radio-group-option-structure */ +export interface RadioGroupOption { + /** The dev-defined value of the option. Maximum 100 characters. */ + value: string; + /** The user-facing label of the option. Maximum 100 characters. */ + label: string; + /** An optional description for the option. Maximum 100 characters. */ + description?: string; + /** Will render this option as already-selected by default. */ + default?: boolean; +} + +/** https://docs.discord.com/developers/components/reference#checkbox-group-structure */ +export interface CheckboxGroupComponent extends BaseComponent { + type: MessageComponentTypes.CheckboxGroup; + /** A custom identifier for this component. Maximum 100 characters. */ + customId: string; + /** List of options to show; min 1, max 10. */ + options: CheckboxGroupOption[]; + /** Minimum number of items that must be chosen; min 0, max 10. Defaults to 1; if set to 0 required must be false. */ + minValues?: number; + /** Maximum number of items that can be chosen; min 1, max 10. Defaults to the number of options. */ + maxValues?: number; + /** Whether selecting within the group is required. Defaults to `true`. */ + required?: boolean; +} + +/** https://docs.discord.com/developers/components/reference#checkbox-group-option-structure */ +export interface CheckboxGroupOption { + /** The dev-defined value of the option. Maximum 100 characters. */ + value: string; + /** The user-facing label of the option. Maximum 100 characters. */ + label: string; + /** An optional description for the option. Maximum 100 characters. */ + description?: string; + /** Will render this option as already-selected by default. */ + default?: boolean; +} + +/** https://docs.discord.com/developers/components/reference#checkbox-structure */ +export interface CheckboxComponent extends BaseComponent { + type: MessageComponentTypes.Checkbox; + /** A custom identifier for this component. Maximum 100 characters. */ + customId: string; + /** Whether the checkbox is selected by default. */ + default?: boolean; +}