feat(types,bot): Add Label component and new modal stuff (#4304)

* feat(types,bot): Add Label component and new modal stuff

Since now there are some fields that are only in responses the types got a bit more complicated

* Add char limits to label label & desc

* update comments

* fix format

* Move Require to shared.ts

* code review

---------

Co-authored-by: Awesome Stickz <awesome@stickz.dev>
This commit is contained in:
Fleny
2025-09-18 00:50:59 +05:30
committed by GitHub
co-authored by Awesome Stickz
parent 35133a1575
commit 87d16dd9ba
9 changed files with 329 additions and 46 deletions
+2
View File
@@ -338,6 +338,8 @@ export function createDesiredPropertiesObject<T extends RecursivePartial<Transfo
accentColor: defaultValue,
name: defaultValue,
size: defaultValue,
component: defaultValue,
values: defaultValue,
...desiredProperties.component,
},
forumTag: {
+10 -2
View File
@@ -47,6 +47,7 @@ import type {
DiscordMessage,
DiscordMessageCall,
DiscordMessageComponent,
DiscordMessageComponentFromModalInteractionResponse,
DiscordMessageInteractionMetadata,
DiscordMessagePin,
DiscordMessageSnapshot,
@@ -266,7 +267,14 @@ export type TransformerFunctions<TProps extends TransformersDesiredProperties, T
avatarDecorationData: TransformerFunction<TProps, TBehavior, DiscordAvatarDecorationData, AvatarDecorationData>
channel: TransformerFunction<TProps, TBehavior, DiscordChannel, Channel, { guildId?: BigString }>
collectibles: TransformerFunction<TProps, TBehavior, DiscordCollectibles, Collectibles>
component: TransformerFunction<TProps, TBehavior, DiscordMessageComponent, Component, {}, 'unchanged'>
component: TransformerFunction<
TProps,
TBehavior,
DiscordMessageComponent | DiscordMessageComponentFromModalInteractionResponse,
Component,
{},
'unchanged'
>
defaultReactionEmoji: TransformerFunction<TProps, TBehavior, DiscordDefaultReactionEmoji, DefaultReactionEmoji>
embed: TransformerFunction<TProps, TBehavior, DiscordEmbed, Embed, {}, 'unchanged'>
emoji: TransformerFunction<TProps, TBehavior, DiscordEmoji, Emoji>
@@ -352,7 +360,7 @@ export type Transformers<TProps extends TransformersDesiredProperties, TBehavior
applicationCommandOption: (bot: Bot<TProps, TBehavior>, payload: ApplicationCommandOption) => DiscordApplicationCommandOption
applicationCommandOptionChoice: (bot: Bot<TProps, TBehavior>, payload: ApplicationCommandOptionChoice) => DiscordApplicationCommandOptionChoice
attachment: (bot: Bot<TProps, TBehavior>, payload: SetupDesiredProps<Attachment, TProps, TBehavior>) => DiscordAttachment
component: (bot: Bot<TProps, TBehavior>, payload: Component) => DiscordMessageComponent
component: (bot: Bot<TProps, TBehavior>, payload: Component) => DiscordMessageComponent | DiscordMessageComponentFromModalInteractionResponse
embed: (bot: Bot<TProps, TBehavior>, payload: Embed) => DiscordEmbed
mediaGalleryItem: (bot: Bot<TProps, TBehavior>, payload: MediaGalleryItem) => DiscordMediaGalleryItem
member: (bot: Bot<TProps, TBehavior>, payload: SetupDesiredProps<Member, TProps, TBehavior>) => DiscordMember
+64 -34
View File
@@ -3,14 +3,18 @@ import {
type DiscordButtonComponent,
type DiscordContainerComponent,
type DiscordFileComponent,
type DiscordLabelComponent,
type DiscordMediaGalleryComponent,
type DiscordMediaGalleryItem,
type DiscordMessageComponent,
type DiscordMessageComponentFromModalInteractionResponse,
type DiscordSectionComponent,
type DiscordSelectMenuComponent,
type DiscordSeparatorComponent,
type DiscordStringSelectInteractionResponseFromModal,
type DiscordTextDisplayComponent,
type DiscordTextInputComponent,
type DiscordTextInputInteractionResponse,
type DiscordThumbnailComponent,
type DiscordUnfurledMediaItem,
MessageComponentTypes,
@@ -18,7 +22,7 @@ import {
import type { Bot } from '../bot.js'
import type { Component, MediaGalleryItem, UnfurledMediaItem } from './types.js'
export function transformComponent(bot: Bot, payload: DiscordMessageComponent): Component {
export function transformComponent(bot: Bot, payload: DiscordMessageComponent | DiscordMessageComponentFromModalInteractionResponse): Component {
let component: Component
// This switch is exhaustive, so we dont need the default case and TS does not error out for the un-initialized component variable
@@ -60,6 +64,9 @@ export function transformComponent(bot: Bot, payload: DiscordMessageComponent):
case MessageComponentTypes.TextDisplay:
component = transformTextDisplayComponent(bot, payload)
break
case MessageComponentTypes.Label:
component = transformLabelComponent(bot, payload)
break
}
return bot.transformers.customizers.component(bot, payload, component)
@@ -131,55 +138,65 @@ function transformButtonComponent(bot: Bot, payload: DiscordButtonComponent): Co
return button
}
function transformInputTextComponent(bot: Bot, payload: DiscordTextInputComponent): Component {
function transformInputTextComponent(bot: Bot, payload: DiscordTextInputComponent | DiscordTextInputInteractionResponse): Component {
const props = bot.transformers.desiredProperties.component
const input = {} as Component
if (props.type && payload.type) input.type = payload.type
if (props.id && payload.id) input.id = payload.id
if (props.style && payload.style) input.style = payload.style
if (props.required && payload.required) input.required = payload.required
if (props.customId && payload.custom_id) input.customId = payload.custom_id
if (props.label && payload.label) input.label = payload.label
if (props.placeholder && payload.placeholder) input.placeholder = payload.placeholder
if (props.minLength && payload.min_length) input.minLength = payload.min_length
if (props.maxLength && payload.max_length) input.maxLength = payload.max_length
if (props.value && payload.value) input.value = payload.value
// Check if it is the component or the response
if ('style' in payload) {
if (props.style && payload.style) input.style = payload.style
if (props.required && payload.required) input.required = payload.required
if (props.customId && payload.custom_id) input.customId = payload.custom_id
if (props.label && payload.label) input.label = payload.label
if (props.placeholder && payload.placeholder) input.placeholder = payload.placeholder
if (props.minLength && payload.min_length) input.minLength = payload.min_length
if (props.maxLength && payload.max_length) input.maxLength = payload.max_length
}
return input
}
function transformSelectMenuComponent(bot: Bot, payload: DiscordSelectMenuComponent): Component {
function transformSelectMenuComponent(bot: Bot, payload: DiscordSelectMenuComponent | DiscordStringSelectInteractionResponseFromModal): Component {
const props = bot.transformers.desiredProperties.component
const select = {} as Component
if (props.type && payload.type) select.type = payload.type
if (props.id && payload.id) select.id = payload.id
if (props.customId && payload.custom_id) select.customId = payload.custom_id
if (props.placeholder && payload.placeholder) select.placeholder = payload.placeholder
if (props.minValues && payload.min_values) select.minValues = payload.min_values
if (props.maxValues && payload.max_values) select.maxValues = payload.max_values
if (props.defaultValues && payload.default_values)
select.defaultValues = payload.default_values.map((defaultValue) => ({
id: bot.transformers.snowflake(defaultValue.id),
type: defaultValue.type,
}))
if (props.channelTypes && payload.channel_types) select.channelTypes = payload.channel_types
if (props.options && payload.options)
select.options = payload.options.map((option) => ({
label: option.label,
value: option.value,
description: option.description,
emoji: option.emoji
? {
id: option.emoji.id ? bot.transformers.snowflake(option.emoji.id) : undefined,
name: option.emoji.name ?? undefined,
animated: option.emoji.animated,
}
: undefined,
default: option.default,
}))
if (props.disabled && payload.disabled) select.disabled = payload.disabled
// Check if this is the string select response
if ('values' in payload) {
if (props.values && payload.values) select.values = payload.values
} else {
if (props.placeholder && payload.placeholder) select.placeholder = payload.placeholder
if (props.minValues && payload.min_values) select.minValues = payload.min_values
if (props.maxValues && payload.max_values) select.maxValues = payload.max_values
if (props.defaultValues && payload.default_values)
select.defaultValues = payload.default_values.map((defaultValue) => ({
id: bot.transformers.snowflake(defaultValue.id),
type: defaultValue.type,
}))
if (props.channelTypes && payload.channel_types) select.channelTypes = payload.channel_types
if (props.options && payload.options)
select.options = payload.options.map((option) => ({
label: option.label,
value: option.value,
description: option.description,
emoji: option.emoji
? {
id: option.emoji.id ? bot.transformers.snowflake(option.emoji.id) : undefined,
name: option.emoji.name ?? undefined,
animated: option.emoji.animated,
}
: undefined,
default: option.default,
}))
if (props.disabled && payload.disabled) select.disabled = payload.disabled
}
return select
}
@@ -256,3 +273,16 @@ function transformSeparatorComponent(bot: Bot, payload: DiscordSeparatorComponen
return separator
}
function transformLabelComponent(bot: Bot, payload: DiscordLabelComponent): Component {
const props = bot.transformers.desiredProperties.component
const label = {} as Component
if (props.type && payload.type) label.type = payload.type
if (props.id && payload.id) label.id = payload.id
if (props.label && payload.label) label.label = payload.label
if (props.description && payload.description) label.description = payload.description
if (props.component && payload.component) label.component = bot.transformers.component(bot, payload.component)
return label
}
@@ -4,13 +4,17 @@ import {
type DiscordButtonComponent,
type DiscordContainerComponent,
type DiscordFileComponent,
type DiscordLabelComponent,
type DiscordMediaGalleryComponent,
type DiscordMediaGalleryItem,
type DiscordMessageComponent,
type DiscordMessageComponentFromModalInteractionResponse,
type DiscordSectionComponent,
type DiscordSelectMenuComponent,
type DiscordStringSelectInteractionResponseFromModal,
type DiscordTextDisplayComponent,
type DiscordTextInputComponent,
type DiscordTextInputInteractionResponse,
type DiscordThumbnailComponent,
type DiscordUnfurledMediaItem,
MessageComponentTypes,
@@ -19,7 +23,10 @@ import {
import type { Bot } from '../../bot.js'
import type { Component, MediaGalleryItem, UnfurledMediaItem } from '../types.js'
export function transformComponentToDiscordComponent(bot: Bot, payload: Component): DiscordMessageComponent {
export function transformComponentToDiscordComponent(
bot: Bot,
payload: Component,
): DiscordMessageComponent | DiscordMessageComponentFromModalInteractionResponse {
// This switch should include all cases
switch (payload.type) {
case MessageComponentTypes.ActionRow:
@@ -44,6 +51,8 @@ export function transformComponentToDiscordComponent(bot: Bot, payload: Componen
return transformMediaGalleryComponent(bot, payload)
case MessageComponentTypes.Thumbnail:
return transformThumbnailComponent(bot, payload)
case MessageComponentTypes.Label:
return transformLabelComponent(bot, payload)
case MessageComponentTypes.Separator:
case MessageComponentTypes.TextDisplay:
// As of now they are compatible
@@ -111,7 +120,7 @@ function transformButtonComponent(bot: Bot, payload: Component): DiscordButtonCo
}
}
function transformInputTextComponent(_bot: Bot, payload: Component): DiscordTextInputComponent {
function transformInputTextComponent(_bot: Bot, payload: Component): DiscordTextInputComponent | DiscordTextInputInteractionResponse {
// Since Component is a merge of all components, some casts are necessary
return {
type: MessageComponentTypes.TextInput,
@@ -127,7 +136,16 @@ function transformInputTextComponent(_bot: Bot, payload: Component): DiscordText
}
}
function transformSelectMenuComponent(bot: Bot, payload: Component): DiscordSelectMenuComponent {
function transformSelectMenuComponent(bot: Bot, payload: Component): DiscordSelectMenuComponent | DiscordStringSelectInteractionResponseFromModal {
if (payload.values) {
return {
type: MessageComponentTypes.StringSelect,
values: payload.values,
custom_id: payload.customId!,
id: payload.id!,
}
}
return {
type: payload.type as DiscordSelectMenuComponent['type'],
id: payload.id,
@@ -154,6 +172,7 @@ function transformSelectMenuComponent(bot: Bot, payload: Component): DiscordSele
default: option.default,
})),
placeholder: payload.placeholder,
required: payload.required,
}
}
@@ -194,3 +213,13 @@ function transformThumbnailComponent(bot: Bot, payload: Component): DiscordThumb
spoiler: payload.spoiler,
}
}
function transformLabelComponent(bot: Bot, payload: Component): DiscordLabelComponent {
return {
type: MessageComponentTypes.Label,
id: payload.id,
label: payload.label!,
description: payload.description,
component: bot.transformers.reverse.component(bot, payload.component!) as DiscordLabelComponent['component'],
}
}
+4
View File
@@ -572,6 +572,10 @@ export interface Component {
name?: string
/** The size of the file in bytes. This field is ignored and provided by the API as part of the response */
size?: number
/** The component within the label */
component?: Component
/** The text of the selected options */
values?: string[]
}
export interface UnfurledMediaItem {