diff --git a/packages/builders/src/components/ActionRow.ts b/packages/builders/src/components/ActionRow.ts index 1ad4c06ea..adb3a4374 100644 --- a/packages/builders/src/components/ActionRow.ts +++ b/packages/builders/src/components/ActionRow.ts @@ -34,7 +34,7 @@ export class ActionRowBuilder< /** * The components within this action row */ - private readonly components: T[]; + public readonly components: T[]; public constructor({ components, diff --git a/packages/builders/src/components/selectMenu/UnsafeSelectMenu.ts b/packages/builders/src/components/selectMenu/UnsafeSelectMenu.ts index fa61cc73e..27b096c27 100644 --- a/packages/builders/src/components/selectMenu/UnsafeSelectMenu.ts +++ b/packages/builders/src/components/selectMenu/UnsafeSelectMenu.ts @@ -11,7 +11,7 @@ export class UnsafeSelectMenuBuilder extends ComponentBuilder< /** * The options within this select menu */ - protected readonly options: UnsafeSelectMenuOptionBuilder[]; + public readonly options: UnsafeSelectMenuOptionBuilder[]; public constructor(data?: Partial) { const { options, ...initData } = data ?? {}; diff --git a/packages/builders/src/interactions/modals/UnsafeModal.ts b/packages/builders/src/interactions/modals/UnsafeModal.ts index f70619dff..5c0695355 100644 --- a/packages/builders/src/interactions/modals/UnsafeModal.ts +++ b/packages/builders/src/interactions/modals/UnsafeModal.ts @@ -6,7 +6,7 @@ import type { import { ActionRowBuilder, createComponentBuilder, JSONEncodable, ModalActionRowComponentBuilder } from '../../index'; export class UnsafeModalBuilder implements JSONEncodable { - protected readonly data: Partial>; + public readonly data: Partial>; public readonly components: ActionRowBuilder[] = []; public constructor({ components, ...data }: Partial = {}) { diff --git a/packages/discord.js/src/structures/MessagePayload.js b/packages/discord.js/src/structures/MessagePayload.js index 1fe65bbf7..6767f01c6 100644 --- a/packages/discord.js/src/structures/MessagePayload.js +++ b/packages/discord.js/src/structures/MessagePayload.js @@ -3,6 +3,7 @@ const { Buffer } = require('node:buffer'); const { isJSONEncodable } = require('@discordjs/builders'); const { MessageFlags } = require('discord-api-types/v10'); +const ActionRowBuilder = require('./ActionRowBuilder'); const { RangeError } = require('../errors'); const DataResolver = require('../util/DataResolver'); const MessageFlagsBitField = require('../util/MessageFlagsBitField'); @@ -131,9 +132,7 @@ class MessagePayload { } } - const components = this.options.components?.map(c => - isJSONEncodable(c) ? c.toJSON() : this.target.client.options.jsonTransformer(c), - ); + const components = this.options.components?.map(c => (isJSONEncodable(c) ? c : new ActionRowBuilder(c)).toJSON()); let username; let avatarURL; diff --git a/packages/discord.js/src/structures/ModalSubmitInteraction.js b/packages/discord.js/src/structures/ModalSubmitInteraction.js index 2f0889896..6192ff1e1 100644 --- a/packages/discord.js/src/structures/ModalSubmitInteraction.js +++ b/packages/discord.js/src/structures/ModalSubmitInteraction.js @@ -1,10 +1,10 @@ 'use strict'; -const { createComponent } = require('@discordjs/builders'); const Interaction = require('./Interaction'); const InteractionWebhook = require('./InteractionWebhook'); const ModalSubmitFieldsResolver = require('./ModalSubmitFieldsResolver'); const InteractionResponses = require('./interfaces/InteractionResponses'); +const Components = require('../util/Components'); /** * @typedef {Object} ModalFieldData @@ -40,7 +40,7 @@ class ModalSubmitInteraction extends Interaction { * The components within the modal * @type {ActionRow[]} */ - this.components = data.data.components?.map(c => createComponent(c)) ?? []; + this.components = data.data.components?.map(c => Components.createComponent(c)) ?? []; /** * The fields within the modal diff --git a/packages/discord.js/src/util/Components.js b/packages/discord.js/src/util/Components.js index 80d486102..5f1d2336a 100644 --- a/packages/discord.js/src/util/Components.js +++ b/packages/discord.js/src/util/Components.js @@ -80,6 +80,8 @@ class Components extends null { return new ButtonComponent(data); case ComponentType.SelectMenu: return new SelectMenuComponent(data); + case ComponentType.TextInput: + return new TextInputComponent(data); default: throw new Error(`Found unknown component type: ${data.type}`); } @@ -92,3 +94,4 @@ const ActionRow = require('../structures/ActionRow'); const ButtonComponent = require('../structures/ButtonComponent'); const Component = require('../structures/Component'); const SelectMenuComponent = require('../structures/SelectMenuComponent'); +const TextInputComponent = require('../structures/TextInputComponent'); diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 02c178c10..811799cab 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -222,7 +222,9 @@ export type ActionRowComponentData = MessageActionRowComponentData | ModalAction export type ActionRowComponent = MessageActionRowComponent | ModalActionRowComponent; -export interface ActionRowData extends BaseComponentData { +export type ActionRowComponentBuilder = MessageActionRowComponentBuilder | ModalActionRowComponentBuilder; + +export interface ActionRowData extends BaseComponentData { components: T[]; } @@ -233,7 +235,7 @@ export class ActionRowBuilder< > extends BuilderActionRow { constructor( data?: - | ActionRowData + | ActionRowData | (Omit, 'type'> & { type?: ComponentType.ActionRow; }), @@ -4719,14 +4721,13 @@ export type MessageChannelComponentCollectorOptions | APIEmbed)[] | null; files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[]; flags?: BitFieldResolvable; allowedMentions?: MessageMentionOptions; components?: ( | JSONEncodable> - | ActionRow - | (Required & ActionRowData) + | (Required & ActionRowData) | APIActionRowComponent )[]; } @@ -4767,8 +4768,7 @@ export interface MessageOptions { embeds?: (JSONEncodable | APIEmbed)[]; components?: ( | JSONEncodable> - | ActionRow - | (Required & ActionRowData) + | (Required & ActionRowData) | APIActionRowComponent )[]; allowedMentions?: MessageMentionOptions; diff --git a/packages/discord.js/typings/index.test-d.ts b/packages/discord.js/typings/index.test-d.ts index 6a667134d..bf5d1d6b4 100644 --- a/packages/discord.js/typings/index.test-d.ts +++ b/packages/discord.js/typings/index.test-d.ts @@ -100,7 +100,6 @@ import { ActionRowBuilder, ButtonComponent, SelectMenuComponent, - MessageActionRowComponentBuilder, InteractionResponseFields, ThreadChannelType, Events, @@ -118,8 +117,10 @@ import { TextInputBuilder, TextInputComponent, Embed, + MessageActionRowComponentBuilder, } from '.'; import { expectAssignable, expectDeprecated, expectNotAssignable, expectNotType, expectType } from 'tsd'; +import { UnsafeButtonBuilder, UnsafeEmbedBuilder, UnsafeSelectMenuBuilder } from '@discordjs/builders'; // Test type transformation: declare const serialize: (value: T) => Serialized; @@ -727,6 +728,39 @@ client.on('messageCreate', async message => { return true; }, }); + + // Check that both builders and builder data can be sent in messages + const row = new ActionRowBuilder(); + const buttonsRow = { + type: ComponentType.ActionRow, + components: [ + new ButtonBuilder(), + new UnsafeButtonBuilder(), + { type: ComponentType.Button, label: 'test', style: ButtonStyle.Primary, customId: 'test' }, + { + type: ComponentType.Button, + label: 'another test', + style: ButtonStyle.Link as const, + url: 'https://discord.js.org', + }, + ], + }; + const selectsRow = { + type: ComponentType.ActionRow, + components: [ + new SelectMenuBuilder(), + new UnsafeSelectMenuBuilder(), + { + type: ComponentType.SelectMenu, + label: 'select menu', + options: [{ label: 'test', value: 'test' }], + customId: 'test', + }, + ], + }; + const buildersEmbed = new UnsafeEmbedBuilder(); + const embedData = { description: 'test', color: 0xff0000 }; + channel.send({ components: [row, buttonsRow, selectsRow], embeds: [embed, buildersEmbed, embedData] }); }); client.on('threadMembersUpdate', (thread, addedMembers, removedMembers) => {