diff --git a/packages/discord.js/src/index.js b/packages/discord.js/src/index.js index 901c31e017ce3..ecf22a1b366c5 100644 --- a/packages/discord.js/src/index.js +++ b/packages/discord.js/src/index.js @@ -70,6 +70,7 @@ exports.WebSocketManager = require('./client/websocket/WebSocketManager'); exports.WebSocketShard = require('./client/websocket/WebSocketShard'); // Structures +exports.ActionRow = require('./structures/ActionRow'); exports.Activity = require('./structures/Presence').Activity; exports.AnonymousGuild = require('./structures/AnonymousGuild'); exports.Application = require('./structures/interfaces/Application'); @@ -80,6 +81,7 @@ exports.BaseGuild = require('./structures/BaseGuild'); exports.BaseGuildEmoji = require('./structures/BaseGuildEmoji'); exports.BaseGuildTextChannel = require('./structures/BaseGuildTextChannel'); exports.BaseGuildVoiceChannel = require('./structures/BaseGuildVoiceChannel'); +exports.ButtonComponent = require('./structures/ButtonComponent'); exports.ButtonInteraction = require('./structures/ButtonInteraction'); exports.CategoryChannel = require('./structures/CategoryChannel'); exports.Channel = require('./structures/Channel').Channel; @@ -131,6 +133,7 @@ exports.ReactionCollector = require('./structures/ReactionCollector'); exports.ReactionEmoji = require('./structures/ReactionEmoji'); exports.RichPresenceAssets = require('./structures/Presence').RichPresenceAssets; exports.Role = require('./structures/Role').Role; +exports.SelectMenuComponent = require('./structures/SelectMenuComponent'); exports.SelectMenuInteraction = require('./structures/SelectMenuInteraction'); exports.StageChannel = require('./structures/StageChannel'); exports.StageInstance = require('./structures/StageInstance').StageInstance; @@ -187,10 +190,7 @@ exports.StickerType = require('discord-api-types/v9').StickerType; exports.StickerFormatType = require('discord-api-types/v9').StickerFormatType; exports.UserFlags = require('discord-api-types/v9').UserFlags; exports.WebhookType = require('discord-api-types/v9').WebhookType; -exports.ActionRow = require('@discordjs/builders').ActionRow; -exports.ButtonComponent = require('@discordjs/builders').ButtonComponent; exports.UnsafeButtonComponent = require('@discordjs/builders').UnsafeButtonComponent; -exports.SelectMenuComponent = require('@discordjs/builders').SelectMenuComponent; exports.UnsafeSelectMenuComponent = require('@discordjs/builders').UnsafeSelectMenuComponent; exports.SelectMenuOption = require('@discordjs/builders').SelectMenuOption; exports.UnsafeSelectMenuOption = require('@discordjs/builders').UnsafeSelectMenuOption; diff --git a/packages/discord.js/src/structures/ActionRow.js b/packages/discord.js/src/structures/ActionRow.js new file mode 100644 index 0000000000000..6c11f57e1757a --- /dev/null +++ b/packages/discord.js/src/structures/ActionRow.js @@ -0,0 +1,14 @@ +'use strict'; + +const Builders = require('@discordjs/builders'); +const Components = require('../util/Components'); + +class ActionRow extends Builders.ActionRow { + constructor(data) { + // TODO: Simply when getters PR is merged. + const initData = Components.transformJSON(data); + super({ ...initData, components: initData.components ?? [] }); + } +} + +module.exports = ActionRow; diff --git a/packages/discord.js/src/structures/ButtonComponent.js b/packages/discord.js/src/structures/ButtonComponent.js new file mode 100644 index 0000000000000..3da81be2c6760 --- /dev/null +++ b/packages/discord.js/src/structures/ButtonComponent.js @@ -0,0 +1,12 @@ +'use strict'; + +const Builders = require('@discordjs/builders'); +const Components = require('../util/Components'); + +class ButtonComponent extends Builders.ButtonComponent { + constructor(data) { + super(Components.transformJSON(data)); + } +} + +module.exports = ButtonComponent; diff --git a/packages/discord.js/src/structures/MessagePayload.js b/packages/discord.js/src/structures/MessagePayload.js index 219b5a6ee4873..a012cefc52dcd 100644 --- a/packages/discord.js/src/structures/MessagePayload.js +++ b/packages/discord.js/src/structures/MessagePayload.js @@ -132,6 +132,7 @@ class MessagePayload { } const components = this.options.components?.map(c => createComponent(c).toJSON()); + console.log(components); let username; let avatarURL; diff --git a/packages/discord.js/src/structures/SelectMenuComponent.js b/packages/discord.js/src/structures/SelectMenuComponent.js new file mode 100644 index 0000000000000..1df8f9d6d0c5c --- /dev/null +++ b/packages/discord.js/src/structures/SelectMenuComponent.js @@ -0,0 +1,12 @@ +'use strict'; + +const Builders = require('@discordjs/builders'); +const Components = require('../util/Components'); + +class SelectMenuComponent extends Builders.SelectMenuComponent { + constructor(data) { + super(Components.transformJSON(data)); + } +} + +module.exports = SelectMenuComponent; diff --git a/packages/discord.js/src/util/Components.js b/packages/discord.js/src/util/Components.js new file mode 100644 index 0000000000000..72810287b4791 --- /dev/null +++ b/packages/discord.js/src/util/Components.js @@ -0,0 +1,27 @@ +'use strict'; + +class Components extends null { + /** + * Transforms json data into api-compatible json data. + * @param {MessageComponentData|APIMessageComponent} data The data to transform. + * @returns {APIMessageComponentData} + */ + static transformJSON(data) { + return { + type: data?.type, + custom_id: data?.customId ?? data?.custom_id, + disabled: data?.disabled, + style: data?.style, + label: data?.label, + emoji: data?.emoji, + url: data?.url, + options: data?.options, + placeholder: data?.placeholder, + min_values: data?.minValues ?? data?.min_values, + max_values: data?.maxValues ?? data?.max_values, + components: data?.components?.map(c => Components.transformJSON(c)), + }; + } +} + +module.exports = Components; diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 9fb378527dde5..de562f2d88577 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -1,9 +1,9 @@ import { - ActionRow, + ActionRow as BuilderActionRow, ActionRowComponent, blockQuote, bold, - ButtonComponent, + ButtonComponent as BuilderButtonComponent, channelMention, codeBlock, Component, @@ -16,7 +16,7 @@ import { memberNicknameMention, quote, roleMention, - SelectMenuComponent, + SelectMenuComponent as BuilderSelectMenuComponent, spoiler, strikethrough, time, @@ -88,6 +88,7 @@ import { GuildSystemChannelFlags, GatewayIntentBits, ActivityFlags, + APIMessageComponentEmoji, } from 'discord-api-types/v9'; import { ChildProcess } from 'node:child_process'; import { EventEmitter } from 'node:events'; @@ -186,6 +187,20 @@ export class Activity { export type ActivityFlagsString = keyof typeof ActivityFlags; +export interface BaseComponentData { + type?: ComponentType; +} + +export type ActionRowComponentData = ButtonComponentData | SelectMenuComponentData; + +export interface ActionRowData extends BaseComponentData { + components: ActionRowComponentData[]; +} + +export class ActionRow extends BuilderActionRow { + constructor(data?: ActionRowData | APIActionRowComponent); +} + export class ActivityFlagsBitField extends BitField { public static Flags: typeof ActivityFlags; public static resolve(bit?: BitFieldResolvable): number; @@ -449,6 +464,14 @@ export class ButtonInteraction extends Mes public inRawGuild(): this is ButtonInteraction<'raw'>; } +export class ButtonComponent extends BuilderButtonComponent { + public constructor(data?: ButtonComponentData | APIButtonComponent); +} + +export class SelectMenuComponent extends BuilderSelectMenuComponent { + public constructor(data?: SelectMenuComponentData | APISelectMenuComponent); +} + export interface MappedChannelCategoryTypes { [ChannelType.GuildNews]: NewsChannel; [ChannelType.GuildVoice]: VoiceChannel; @@ -3346,10 +3369,6 @@ export interface ThreadMemberFetchOptions extends BaseFetchOptions { member?: UserResolvable; } -export interface BaseMessageComponentOptions { - type?: ComponentType; -} - export type BitFieldResolvable = | RecursiveReadonlyArray>> | T @@ -4479,37 +4498,33 @@ export interface MakeErrorOptions { export type MemberMention = UserMention | `<@!${Snowflake}>`; export type ActionRowComponentOptions = - | (Required & MessageButtonOptions) - | (Required & MessageSelectMenuOptions); + | (Required & ButtonComponentData) + | (Required & SelectMenuComponentData); export type MessageActionRowComponentResolvable = ActionRowComponent | ActionRowComponentOptions; -export interface ActionRowOptions extends BaseMessageComponentOptions { - components: ActionRowComponent[]; -} - export interface MessageActivity { partyId: string; type: number; } -export interface BaseButtonOptions extends BaseMessageComponentOptions { +export interface BaseButtonComponentData extends BaseComponentData { disabled?: boolean; emoji?: EmojiIdentifierResolvable; label?: string; } -export interface LinkButtonOptions extends BaseButtonOptions { - style: 'Link' | ButtonStyle.Link; +export interface LinkButtonComponentData extends BaseButtonComponentData { + style: ButtonStyle.Link; url: string; } -export interface InteractionButtonOptions extends BaseButtonOptions { +export interface InteractionButtonComponentData extends BaseButtonComponentData { style: Exclude; customId: string; } -export type MessageButtonOptions = InteractionButtonOptions | LinkButtonOptions; +export type ButtonComponentData = InteractionButtonComponentData | LinkButtonComponentData; export interface MessageCollectorOptions extends CollectorOptions<[Message]> { max?: number; @@ -4528,12 +4543,6 @@ export type MessageChannelComponentCollectorOptions; -export type MessageComponentOptions = - | BaseMessageComponentOptions - | ActionRowOptions - | MessageButtonOptions - | MessageSelectMenuOptions; - export interface MessageEditOptions { attachments?: MessageAttachment[]; content?: string | null; @@ -4541,7 +4550,7 @@ export interface MessageEditOptions { files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[]; flags?: BitFieldResolvable; allowedMentions?: MessageMentionOptions; - components?: (ActionRow | (Required & ActionRowOptions))[]; + components?: (ActionRow | (Required & ActionRowData))[]; } export interface MessageEvent { @@ -4578,7 +4587,7 @@ export interface MessageOptions { nonce?: string | number; content?: string | null; embeds?: (Embed | APIEmbed)[]; - components?: (ActionRow | (Required & ActionRowOptions))[]; + components?: (ActionRow | (Required & ActionRowData))[]; allowedMentions?: MessageMentionOptions; files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[]; reply?: ReplyOptions; @@ -4603,12 +4612,12 @@ export interface MessageReference { export type MessageResolvable = Message | Snowflake; -export interface MessageSelectMenuOptions extends BaseMessageComponentOptions { +export interface SelectMenuComponentData extends BaseComponentData { customId?: string; disabled?: boolean; maxValues?: number; minValues?: number; - options?: MessageSelectOptionData[]; + options?: SelectMenuComponentOptionData[]; placeholder?: string; } @@ -4620,10 +4629,10 @@ export interface MessageSelectOption { value: string; } -export interface MessageSelectOptionData { +export interface SelectMenuComponentOptionData { default?: boolean; description?: string; - emoji?: EmojiIdentifierResolvable; + emoji?: APIMessageComponentEmoji; label: string; value: string; } @@ -5172,10 +5181,7 @@ export { WebhookType, } from 'discord-api-types/v9'; export { - ActionRow, - ButtonComponent, UnsafeButtonComponent, - SelectMenuComponent, UnsafeSelectMenuComponent, SelectMenuOption, UnsafeSelectMenuOption, diff --git a/packages/discord.js/typings/index.test-d.ts b/packages/discord.js/typings/index.test-d.ts index 7acb15089d854..814adac9de328 100644 --- a/packages/discord.js/typings/index.test-d.ts +++ b/packages/discord.js/typings/index.test-d.ts @@ -16,6 +16,7 @@ import { InteractionType, GatewayIntentBits, PermissionFlagsBits, + ButtonStyle, } from 'discord-api-types/v9'; import { AuditLogEvent } from 'discord-api-types/v9'; import { @@ -1322,3 +1323,19 @@ expectType(GuildTextBasedChannel); + +const button = new ButtonComponent({ + label: 'test', + style: ButtonStyle.Primary, + customId: 'test', +}); + +const selectMenu = new SelectMenuComponent({ + maxValues: 10, + minValues: 2, + customId: 'test', +}); + +new ActionRow({ + components: [selectMenu, button], +});