Skip to content

Commit edace17

Browse files
QjuhimnaiyarJiralitevladfranguGeniusTimo
authored
feat: components v2 in v14 (#10781)
Co-authored-by: Naiyar <[email protected]> Co-authored-by: Jiralite <[email protected]> Co-authored-by: Vlad Frangu <[email protected]> Co-authored-by: Timo <[email protected]>
1 parent d3154cf commit edace17

24 files changed

+883
-123
lines changed

packages/builders/__tests__/interactions/SlashCommands/SlashCommands.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ describe('Slash Commands', () => {
565565
});
566566

567567
describe('integration types', () => {
568-
test('GIVEN a builder with valid integraton types THEN does not throw an error', () => {
568+
test('GIVEN a builder with valid integration types THEN does not throw an error', () => {
569569
expect(() =>
570570
getBuilder().setIntegrationTypes([
571571
ApplicationIntegrationType.GuildInstall,

packages/discord.js/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,14 @@
6565
"homepage": "https://discord.js.org",
6666
"funding": "https://github.com/discordjs/discord.js?sponsor",
6767
"dependencies": {
68-
"@discordjs/builders": "^1.10.1",
68+
"@discordjs/builders": "^1.11.0",
6969
"@discordjs/collection": "1.5.3",
7070
"@discordjs/formatters": "^0.6.0",
7171
"@discordjs/rest": "workspace:^",
7272
"@discordjs/util": "workspace:^",
7373
"@discordjs/ws": "^1.2.1",
7474
"@sapphire/snowflake": "3.5.3",
75-
"discord-api-types": "^0.37.119",
75+
"discord-api-types": "^0.38.1",
7676
"fast-deep-equal": "3.1.3",
7777
"lodash.snakecase": "4.1.1",
7878
"magic-bytes.js": "^1.10.0",

packages/discord.js/src/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,14 @@ exports.CommandInteraction = require('./structures/CommandInteraction');
124124
exports.Collector = require('./structures/interfaces/Collector');
125125
exports.CommandInteractionOptionResolver = require('./structures/CommandInteractionOptionResolver');
126126
exports.Component = require('./structures/Component');
127+
exports.ContainerComponent = require('./structures/ContainerComponent');
127128
exports.ContextMenuCommandInteraction = require('./structures/ContextMenuCommandInteraction');
128129
exports.DMChannel = require('./structures/DMChannel');
129130
exports.Embed = require('./structures/Embed');
130131
exports.EmbedBuilder = require('./structures/EmbedBuilder');
131132
exports.Emoji = require('./structures/Emoji').Emoji;
132133
exports.Entitlement = require('./structures/Entitlement').Entitlement;
134+
exports.FileComponent = require('./structures/FileComponent');
133135
exports.ForumChannel = require('./structures/ForumChannel');
134136
exports.Guild = require('./structures/Guild').Guild;
135137
exports.GuildAuditLogs = require('./structures/GuildAuditLogs');
@@ -162,6 +164,8 @@ exports.Attachment = require('./structures/Attachment');
162164
exports.AttachmentBuilder = require('./structures/AttachmentBuilder');
163165
exports.ModalBuilder = require('./structures/ModalBuilder');
164166
exports.MediaChannel = require('./structures/MediaChannel');
167+
exports.MediaGalleryComponent = require('./structures/MediaGalleryComponent');
168+
exports.MediaGalleryItem = require('./structures/MediaGalleryItem');
165169
exports.MessageCollector = require('./structures/MessageCollector');
166170
exports.MessageComponentInteraction = require('./structures/MessageComponentInteraction');
167171
exports.MessageContextMenuCommandInteraction = require('./structures/MessageContextMenuCommandInteraction');
@@ -181,6 +185,7 @@ exports.ReactionCollector = require('./structures/ReactionCollector');
181185
exports.ReactionEmoji = require('./structures/ReactionEmoji');
182186
exports.RichPresenceAssets = require('./structures/Presence').RichPresenceAssets;
183187
exports.Role = require('./structures/Role').Role;
188+
exports.SectionComponent = require('./structures/SectionComponent');
184189
exports.SelectMenuBuilder = require('./structures/SelectMenuBuilder');
185190
exports.ChannelSelectMenuBuilder = require('./structures/ChannelSelectMenuBuilder');
186191
exports.MentionableSelectMenuBuilder = require('./structures/MentionableSelectMenuBuilder');
@@ -202,6 +207,7 @@ exports.RoleSelectMenuInteraction = require('./structures/RoleSelectMenuInteract
202207
exports.StringSelectMenuInteraction = require('./structures/StringSelectMenuInteraction');
203208
exports.UserSelectMenuInteraction = require('./structures/UserSelectMenuInteraction');
204209
exports.SelectMenuOptionBuilder = require('./structures/SelectMenuOptionBuilder');
210+
exports.SeparatorComponent = require('./structures/SeparatorComponent');
205211
exports.SKU = require('./structures/SKU').SKU;
206212
exports.SoundboardSound = require('./structures/SoundboardSound.js').SoundboardSound;
207213
exports.StringSelectMenuOptionBuilder = require('./structures/StringSelectMenuOptionBuilder');
@@ -213,12 +219,15 @@ exports.StickerPack = require('./structures/StickerPack');
213219
exports.Team = require('./structures/Team');
214220
exports.TeamMember = require('./structures/TeamMember');
215221
exports.TextChannel = require('./structures/TextChannel');
222+
exports.TextDisplayComponent = require('./structures/TextDisplayComponent');
216223
exports.TextInputBuilder = require('./structures/TextInputBuilder');
217224
exports.TextInputComponent = require('./structures/TextInputComponent');
218225
exports.ThreadChannel = require('./structures/ThreadChannel');
219226
exports.ThreadMember = require('./structures/ThreadMember');
220227
exports.ThreadOnlyChannel = require('./structures/ThreadOnlyChannel');
228+
exports.ThumbnailComponent = require('./structures/ThumbnailComponent');
221229
exports.Typing = require('./structures/Typing');
230+
exports.UnfurledMediaItem = require('./structures/UnfurledMediaItem');
222231
exports.User = require('./structures/User');
223232
exports.UserContextMenuCommandInteraction = require('./structures/UserContextMenuCommandInteraction');
224233
exports.VoiceChannelEffect = require('./structures/VoiceChannelEffect');

packages/discord.js/src/structures/Component.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ class Component {
1414
this.data = data;
1515
}
1616

17+
/**
18+
* The id of this component
19+
* @type {number}
20+
* @readonly
21+
*/
22+
get id() {
23+
return this.data.id;
24+
}
25+
1726
/**
1827
* The type of the component
1928
* @type {ComponentType}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
'use strict';
2+
3+
const Component = require('./Component');
4+
const { createComponent } = require('../util/Components');
5+
6+
/**
7+
* Represents a container component
8+
* @extends {Component}
9+
*/
10+
class ContainerComponent extends Component {
11+
constructor({ components, ...data }) {
12+
super(data);
13+
14+
/**
15+
* The components in this container
16+
* @type {Component[]}
17+
* @readonly
18+
*/
19+
this.components = components.map(component => createComponent(component));
20+
}
21+
22+
/**
23+
* The accent color of this container
24+
* @type {?number}
25+
* @readonly
26+
*/
27+
get accentColor() {
28+
return this.data.accent_color ?? null;
29+
}
30+
31+
/**
32+
* The hex accent color of this container
33+
* @type {?string}
34+
* @readonly
35+
*/
36+
get hexAccentColor() {
37+
return typeof this.data.accent_color === 'number'
38+
? `#${this.data.accent_color.toString(16).padStart(6, '0')}`
39+
: (this.data.accent_color ?? null);
40+
}
41+
42+
/**
43+
* Whether this container is spoilered
44+
* @type {boolean}
45+
* @readonly
46+
*/
47+
get spoiler() {
48+
return this.data.spoiler ?? false;
49+
}
50+
51+
/**
52+
* Returns the API-compatible JSON for this component
53+
* @returns {APIContainerComponent}
54+
*/
55+
toJSON() {
56+
return { ...this.data, components: this.components.map(component => component.toJSON()) };
57+
}
58+
}
59+
60+
module.exports = ContainerComponent;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use strict';
2+
3+
const Component = require('./Component');
4+
const UnfurledMediaItem = require('./UnfurledMediaItem');
5+
6+
/**
7+
* Represents a file component
8+
* @extends {Component}
9+
*/
10+
class FileComponent extends Component {
11+
constructor({ file, ...data }) {
12+
super(data);
13+
14+
/**
15+
* The media associated with this file
16+
* @type {UnfurledMediaItem}
17+
* @readonly
18+
*/
19+
this.file = new UnfurledMediaItem(file);
20+
}
21+
22+
/**
23+
* Whether this thumbnail is spoilered
24+
* @type {boolean}
25+
* @readonly
26+
*/
27+
get spoiler() {
28+
return this.data.spoiler ?? false;
29+
}
30+
31+
/**
32+
* Returns the API-compatible JSON for this component
33+
* @returns {APIFileComponent}
34+
*/
35+
toJSON() {
36+
return { ...this.data, file: this.file.toJSON() };
37+
}
38+
}
39+
40+
module.exports = FileComponent;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use strict';
2+
3+
const Component = require('./Component');
4+
const MediaGalleryItem = require('./MediaGalleryItem');
5+
6+
/**
7+
* Represents a media gallery component
8+
* @extends {Component}
9+
*/
10+
class MediaGalleryComponent extends Component {
11+
constructor({ items, ...data }) {
12+
super(data);
13+
14+
/**
15+
* The items in this media gallery
16+
* @type {MediaGalleryItem[]}
17+
* @readonly
18+
*/
19+
this.items = items.map(item => new MediaGalleryItem(item));
20+
}
21+
22+
/**
23+
* Returns the API-compatible JSON for this component
24+
* @returns {APIMediaGalleryComponent}
25+
*/
26+
toJSON() {
27+
return { ...this.data, items: this.items.map(item => item.toJSON()) };
28+
}
29+
}
30+
31+
module.exports = MediaGalleryComponent;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
'use strict';
2+
3+
const UnfurledMediaItem = require('./UnfurledMediaItem');
4+
5+
/**
6+
* Represents an item in a media gallery
7+
*/
8+
class MediaGalleryItem {
9+
constructor({ media, ...data }) {
10+
/**
11+
* The API data associated with this component
12+
* @type {APIMediaGalleryItem}
13+
*/
14+
this.data = data;
15+
16+
/**
17+
* The media associated with this media gallery item
18+
* @type {UnfurledMediaItem}
19+
* @readonly
20+
*/
21+
this.media = new UnfurledMediaItem(media);
22+
}
23+
24+
/**
25+
* The description of this media gallery item
26+
* @type {?string}
27+
* @readonly
28+
*/
29+
get description() {
30+
return this.data.description ?? null;
31+
}
32+
33+
/**
34+
* Whether this media gallery item is spoilered
35+
* @type {boolean}
36+
* @readonly
37+
*/
38+
get spoiler() {
39+
return this.data.spoiler ?? false;
40+
}
41+
42+
/**
43+
* Returns the API-compatible JSON for this component
44+
* @returns {APIMediaGalleryItem}
45+
*/
46+
toJSON() {
47+
return { ...this.data, media: this.media.toJSON() };
48+
}
49+
}
50+
51+
module.exports = MediaGalleryItem;

packages/discord.js/src/structures/Message.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const ReactionCollector = require('./ReactionCollector');
2323
const { Sticker } = require('./Sticker');
2424
const { DiscordjsError, ErrorCodes } = require('../errors');
2525
const ReactionManager = require('../managers/ReactionManager');
26-
const { createComponent } = require('../util/Components');
26+
const { createComponent, findComponentByCustomId } = require('../util/Components');
2727
const { NonSystemMessageTypes, MaxBulkDeletableMessageAge, UndeletableMessageTypes } = require('../util/Constants');
2828
const MessageFlagsBitField = require('../util/MessageFlagsBitField');
2929
const PermissionsBitField = require('../util/PermissionsBitField');
@@ -151,10 +151,10 @@ class Message extends Base {
151151

152152
if ('components' in data) {
153153
/**
154-
* An array of action rows in the message.
154+
* An array of components in the message.
155155
* <info>This property requires the {@link GatewayIntentBits.MessageContent} privileged intent
156156
* in a guild for messages that do not mention the client.</info>
157-
* @type {ActionRow[]}
157+
* @type {Component[]}
158158
*/
159159
this.components = data.components.map(component => createComponent(component));
160160
} else {
@@ -1055,7 +1055,7 @@ class Message extends Base {
10551055
* @returns {?MessageActionRowComponent}
10561056
*/
10571057
resolveComponent(customId) {
1058-
return this.components.flatMap(row => row.components).find(component => component.customId === customId) ?? null;
1058+
return findComponentByCustomId(this.components, customId);
10591059
}
10601060

10611061
/**

packages/discord.js/src/structures/MessageComponentInteraction.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const { lazy } = require('@discordjs/util');
44
const BaseInteraction = require('./BaseInteraction');
55
const InteractionWebhook = require('./InteractionWebhook');
66
const InteractionResponses = require('./interfaces/InteractionResponses');
7+
const { findComponentByCustomId } = require('../util/Components');
78

89
const getMessage = lazy(() => require('./Message').Message);
910

@@ -79,13 +80,11 @@ class MessageComponentInteraction extends BaseInteraction {
7980

8081
/**
8182
* The component which was interacted with
82-
* @type {MessageActionRowComponent|APIMessageActionRowComponent}
83+
* @type {MessageActionRowComponent|APIComponentInMessageActionRow}
8384
* @readonly
8485
*/
8586
get component() {
86-
return this.message.components
87-
.flatMap(row => row.components)
88-
.find(component => (component.customId ?? component.custom_id) === this.customId);
87+
return findComponentByCustomId(this.message.components, this.customId);
8988
}
9089

9190
// These are here only for documentation purposes - they are implemented by InteractionResponses

packages/discord.js/src/structures/MessagePayload.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ const { Buffer } = require('node:buffer');
44
const { lazy, isJSONEncodable } = require('@discordjs/util');
55
const { DiscordSnowflake } = require('@sapphire/snowflake');
66
const { MessageFlags, MessageReferenceType } = require('discord-api-types/v10');
7-
const ActionRowBuilder = require('./ActionRowBuilder');
87
const { DiscordjsError, DiscordjsRangeError, ErrorCodes } = require('../errors');
98
const { resolveFile } = require('../util/DataResolver');
109
const MessageFlagsBitField = require('../util/MessageFlagsBitField');
@@ -149,7 +148,7 @@ class MessagePayload {
149148
}
150149

151150
const components = this.options.components?.map(component =>
152-
(isJSONEncodable(component) ? component : new ActionRowBuilder(component)).toJSON(),
151+
isJSONEncodable(component) ? component.toJSON() : this.target.client.options.jsonTransformer(component),
153152
);
154153

155154
let username;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
3+
const Component = require('./Component');
4+
const { createComponent } = require('../util/Components');
5+
6+
/**
7+
* Represents a section component
8+
* @extends {Component}
9+
*/
10+
class SectionComponent extends Component {
11+
constructor({ accessory, components, ...data }) {
12+
super(data);
13+
14+
/**
15+
* The components in this section
16+
* @type {Component[]}
17+
* @readonly
18+
*/
19+
this.components = components.map(component => createComponent(component));
20+
21+
/**
22+
* The accessory component of this section
23+
* @type {Component}
24+
* @readonly
25+
*/
26+
this.accessory = createComponent(accessory);
27+
}
28+
29+
/**
30+
* Returns the API-compatible JSON for this component
31+
* @returns {APISectionComponent}
32+
*/
33+
toJSON() {
34+
return {
35+
...this.data,
36+
accessory: this.accessory.toJSON(),
37+
components: this.components.map(component => component.toJSON()),
38+
};
39+
}
40+
}
41+
42+
module.exports = SectionComponent;

0 commit comments

Comments
 (0)