@ovencord/builders 1.11.6 → 1.11.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@ovencord/builders",
4
- "version": "1.11.6",
4
+ "version": "1.11.8",
5
5
  "description": "A set of builders that you can use when creating your bot",
6
6
  "scripts": {
7
7
  "test": "bun test",
@@ -34,25 +34,25 @@ import { StringSelectMenuBuilder } from './selectMenu/StringSelectMenu.js';
34
34
  import { UserSelectMenuBuilder } from './selectMenu/UserSelectMenu.js';
35
35
  import { TextInputBuilder } from './textInput/TextInput.js';
36
36
 
37
- export interface ActionRowBuilderData extends Partial<
37
+ export interface ActionRowBuilderData<ComponentType extends AnyActionRowComponentBuilder = AnyActionRowComponentBuilder> extends Partial<
38
38
  Omit<APIActionRowComponent<APIComponentInActionRow>, 'components'>
39
39
  > {
40
- components: AnyActionRowComponentBuilder[];
40
+ components: ComponentType[];
41
41
  }
42
42
 
43
43
  /**
44
44
  * A builder that creates API-compatible JSON data for action rows.
45
45
  */
46
- export class ActionRowBuilder extends ComponentBuilder<APIActionRowComponent<APIComponentInActionRow>> {
46
+ export class ActionRowBuilder<ComponentType extends AnyActionRowComponentBuilder = AnyActionRowComponentBuilder> extends ComponentBuilder<APIActionRowComponent<APIComponentInActionRow>> {
47
47
  /**
48
48
  * @internal
49
49
  */
50
- protected readonly data: ActionRowBuilderData;
50
+ protected readonly data: ActionRowBuilderData<ComponentType>;
51
51
 
52
52
  /**
53
53
  * The components within this action row.
54
54
  */
55
- public get components(): readonly AnyActionRowComponentBuilder[] {
55
+ public get components(): readonly ComponentType[] {
56
56
  return this.data.components;
57
57
  }
58
58
 
@@ -97,7 +97,7 @@ export class ActionRowBuilder extends ComponentBuilder<APIActionRowComponent<API
97
97
 
98
98
  this.data = {
99
99
  ...structuredClone(rest),
100
- components: components.map((component) => createComponentBuilder(component)),
100
+ components: components.map((component) => createComponentBuilder(component)) as ComponentType[],
101
101
  type: ComponentType.ActionRow,
102
102
  };
103
103
  }
@@ -115,7 +115,7 @@ export class ActionRowBuilder extends ComponentBuilder<APIActionRowComponent<API
115
115
  const normalized = normalizeArray(input);
116
116
  const resolved = normalized.map((component) => resolveBuilder(component, PrimaryButtonBuilder));
117
117
 
118
- this.data.components.push(...resolved);
118
+ this.data.components.push(...(resolved as ComponentType[]));
119
119
  return this;
120
120
  }
121
121
 
@@ -134,7 +134,7 @@ export class ActionRowBuilder extends ComponentBuilder<APIActionRowComponent<API
134
134
  const normalized = normalizeArray(input);
135
135
  const resolved = normalized.map((component) => resolveBuilder(component, SecondaryButtonBuilder));
136
136
 
137
- this.data.components.push(...resolved);
137
+ this.data.components.push(...(resolved as ComponentType[]));
138
138
  return this;
139
139
  }
140
140
 
@@ -151,7 +151,7 @@ export class ActionRowBuilder extends ComponentBuilder<APIActionRowComponent<API
151
151
  const normalized = normalizeArray(input);
152
152
  const resolved = normalized.map((component) => resolveBuilder(component, SuccessButtonBuilder));
153
153
 
154
- this.data.components.push(...resolved);
154
+ this.data.components.push(...(resolved as ComponentType[]));
155
155
  return this;
156
156
  }
157
157
 
@@ -166,7 +166,7 @@ export class ActionRowBuilder extends ComponentBuilder<APIActionRowComponent<API
166
166
  const normalized = normalizeArray(input);
167
167
  const resolved = normalized.map((component) => resolveBuilder(component, DangerButtonBuilder));
168
168
 
169
- this.data.components.push(...resolved);
169
+ this.data.components.push(...(resolved as ComponentType[]));
170
170
  return this;
171
171
  }
172
172
 
@@ -175,7 +175,7 @@ export class ActionRowBuilder extends ComponentBuilder<APIActionRowComponent<API
175
175
  */
176
176
  public addComponents(...input: RestOrArray<AnyActionRowComponentBuilder>): this {
177
177
  const normalized = normalizeArray(input);
178
- this.data.components.push(...normalized);
178
+ this.data.components.push(...(normalized as ComponentType[]));
179
179
 
180
180
  return this;
181
181
  }
@@ -187,7 +187,7 @@ export class ActionRowBuilder extends ComponentBuilder<APIActionRowComponent<API
187
187
  */
188
188
  public setComponents(...input: RestOrArray<AnyActionRowComponentBuilder>): this {
189
189
  const normalized = normalizeArray(input);
190
- this.data.components = [...normalized];
190
+ this.data.components = [...normalized] as ComponentType[];
191
191
 
192
192
  return this;
193
193
  }
@@ -205,7 +205,7 @@ export class ActionRowBuilder extends ComponentBuilder<APIActionRowComponent<API
205
205
  const normalized = normalizeArray(input);
206
206
  const resolved = normalized.map((component) => resolveBuilder(component, PremiumButtonBuilder));
207
207
 
208
- this.data.components.push(...resolved);
208
+ this.data.components.push(...(resolved as ComponentType[]));
209
209
  return this;
210
210
  }
211
211
 
@@ -222,7 +222,7 @@ export class ActionRowBuilder extends ComponentBuilder<APIActionRowComponent<API
222
222
  const normalized = normalizeArray(input);
223
223
  const resolved = normalized.map((component) => resolveBuilder(component, LinkButtonBuilder));
224
224
 
225
- this.data.components.push(...resolved);
225
+ this.data.components.push(...(resolved as ComponentType[]));
226
226
  return this;
227
227
  }
228
228
 
@@ -237,7 +237,7 @@ export class ActionRowBuilder extends ComponentBuilder<APIActionRowComponent<API
237
237
  | ChannelSelectMenuBuilder
238
238
  | ((builder: ChannelSelectMenuBuilder) => ChannelSelectMenuBuilder),
239
239
  ): this {
240
- this.data.components.push(resolveBuilder(input, ChannelSelectMenuBuilder));
240
+ this.data.components.push(resolveBuilder(input, ChannelSelectMenuBuilder) as ComponentType);
241
241
  return this;
242
242
  }
243
243
 
@@ -252,7 +252,7 @@ export class ActionRowBuilder extends ComponentBuilder<APIActionRowComponent<API
252
252
  | MentionableSelectMenuBuilder
253
253
  | ((builder: MentionableSelectMenuBuilder) => MentionableSelectMenuBuilder),
254
254
  ): this {
255
- this.data.components.push(resolveBuilder(input, MentionableSelectMenuBuilder));
255
+ this.data.components.push(resolveBuilder(input, MentionableSelectMenuBuilder) as ComponentType);
256
256
  return this;
257
257
  }
258
258
 
@@ -264,7 +264,7 @@ export class ActionRowBuilder extends ComponentBuilder<APIActionRowComponent<API
264
264
  public addRoleSelectMenuComponent(
265
265
  input: APIRoleSelectComponent | RoleSelectMenuBuilder | ((builder: RoleSelectMenuBuilder) => RoleSelectMenuBuilder),
266
266
  ): this {
267
- this.data.components.push(resolveBuilder(input, RoleSelectMenuBuilder));
267
+ this.data.components.push(resolveBuilder(input, RoleSelectMenuBuilder) as ComponentType);
268
268
  return this;
269
269
  }
270
270
 
@@ -279,7 +279,7 @@ export class ActionRowBuilder extends ComponentBuilder<APIActionRowComponent<API
279
279
  | StringSelectMenuBuilder
280
280
  | ((builder: StringSelectMenuBuilder) => StringSelectMenuBuilder),
281
281
  ): this {
282
- this.data.components.push(resolveBuilder(input, StringSelectMenuBuilder));
282
+ this.data.components.push(resolveBuilder(input, StringSelectMenuBuilder) as ComponentType);
283
283
  return this;
284
284
  }
285
285
 
@@ -291,7 +291,7 @@ export class ActionRowBuilder extends ComponentBuilder<APIActionRowComponent<API
291
291
  public addUserSelectMenuComponent(
292
292
  input: APIUserSelectComponent | UserSelectMenuBuilder | ((builder: UserSelectMenuBuilder) => UserSelectMenuBuilder),
293
293
  ): this {
294
- this.data.components.push(resolveBuilder(input, UserSelectMenuBuilder));
294
+ this.data.components.push(resolveBuilder(input, UserSelectMenuBuilder) as ComponentType);
295
295
  return this;
296
296
  }
297
297
 
@@ -303,7 +303,7 @@ export class ActionRowBuilder extends ComponentBuilder<APIActionRowComponent<API
303
303
  public addTextInputComponent(
304
304
  input: APITextInputComponent | TextInputBuilder | ((builder: TextInputBuilder) => TextInputBuilder),
305
305
  ): this {
306
- this.data.components.push(resolveBuilder(input, TextInputBuilder));
306
+ this.data.components.push(resolveBuilder(input, TextInputBuilder) as ComponentType);
307
307
  return this;
308
308
  }
309
309
 
@@ -1,4 +1,5 @@
1
1
  import type { APIButtonComponent, APIButtonComponentWithSKUId, APIMessageComponentEmoji } from 'discord-api-types/v10';
2
+ import { parseEmoji } from '../../../util/componentUtil.js';
2
3
 
3
4
  export interface EmojiOrLabelButtonData extends Pick<
4
5
  Exclude<APIButtonComponent, APIButtonComponentWithSKUId>,
@@ -21,7 +22,7 @@ export class EmojiOrLabelButtonMixin {
21
22
  */
22
23
  public setEmoji(emoji: APIMessageComponentEmoji | string) {
23
24
  if (typeof emoji === 'string') {
24
- this.data.emoji = { name: emoji };
25
+ this.data.emoji = parseEmoji(emoji);
25
26
  } else {
26
27
  this.data.emoji = emoji;
27
28
  }
@@ -1,7 +1,7 @@
1
1
  import { ComponentType, SeparatorSpacingSize } from 'discord-api-types/v10';
2
2
  import { z } from 'zod';
3
3
  import { idPredicate } from '../../Assertions.js';
4
- import { actionRowPredicate } from '../Assertions.js';
4
+ import { actionRowPredicate, buttonPredicate } from '../Assertions.js';
5
5
 
6
6
  const unfurledMediaItemPredicate = z.object({
7
7
  url: z.string().url().refine((url) => url.startsWith('http:') || url.startsWith('https:') || url.startsWith('attachment:'), { message: 'URL must use http, https, or attachment protocol' }),
@@ -57,8 +57,8 @@ export const sectionPredicate = z.object({
57
57
  id: idPredicate,
58
58
  components: z.array(textDisplayPredicate).min(1).max(3),
59
59
  accessory: z.union([
60
- z.object({ type: z.literal(ComponentType.Button) }),
61
- z.object({ type: z.literal(ComponentType.Thumbnail) }),
60
+ buttonPredicate,
61
+ thumbnailPredicate,
62
62
  ]),
63
63
  });
64
64
 
@@ -8,6 +8,7 @@ import {
8
8
  type APIComponentInContainer,
9
9
  type APIMediaGalleryComponent,
10
10
  type APISectionComponent,
11
+ type APIComponentInActionRow,
11
12
  ComponentType,
12
13
  } from 'discord-api-types/v10';
13
14
  import { normalizeArray, type RestOrArray } from '../../util/normalizeArray';
@@ -19,7 +20,7 @@ import { createComponentBuilder } from '../Components';
19
20
  import { containerPredicate } from './Assertions';
20
21
  import { FileBuilder } from './File.js';
21
22
  import { MediaGalleryBuilder } from './MediaGallery';
22
- import { SectionBuilder } from './Section';
23
+ import { SectionBuilder } from './Section.js';
23
24
  import { SeparatorBuilder } from './Separator.js';
24
25
  import { TextDisplayBuilder } from './TextDisplay';
25
26
 
@@ -109,7 +110,7 @@ export class ContainerBuilder extends ComponentBuilder<APIContainerComponent> {
109
110
  >
110
111
  ): this {
111
112
  const normalized = normalizeArray(input);
112
- const resolved = normalized.map((component) => resolveBuilder(component, ActionRowBuilder));
113
+ const resolved = normalized.map((component) => resolveBuilder<ActionRowBuilder, Partial<APIActionRowComponent<APIComponentInActionRow>>>(component, ActionRowBuilder));
113
114
 
114
115
  this.data.components.push(...resolved);
115
116
  return this;
@@ -94,6 +94,7 @@ export class SectionBuilder extends ComponentBuilder<APISectionComponent> {
94
94
  public constructor(data: Partial<APISectionComponent> = {}) {
95
95
  super();
96
96
 
97
+
97
98
  const { components = [], accessory, ...rest } = data;
98
99
 
99
100
  this.data = {
@@ -255,20 +256,33 @@ export class SectionBuilder extends ComponentBuilder<APISectionComponent> {
255
256
  return this;
256
257
  }
257
258
 
259
+ /**
260
+ * {@inheritDoc ComponentBuilder.toJSON}
261
+ */
258
262
  /**
259
263
  * {@inheritDoc ComponentBuilder.toJSON}
260
264
  */
261
265
  public override toJSON(validationOverride?: boolean): APISectionComponent {
262
266
  const { components, accessory, ...rest } = this.data;
263
267
 
268
+ // Resolve accessory if it exists
269
+ const accessoryData = accessory
270
+ ? (accessory as any).toJSON
271
+ ? (accessory as any).toJSON(validationOverride)
272
+ : accessory
273
+ : undefined;
274
+
264
275
  const data = {
265
276
  ...structuredClone(rest),
266
- components: components.map((component) => component.toJSON(false)),
267
- accessory: accessory?.toJSON(validationOverride),
268
- };
277
+ type: ComponentType.Section,
278
+ components: components.map((component) => component.toJSON(validationOverride)),
279
+ accessory: accessoryData,
280
+ } as APISectionComponent;
281
+
282
+
269
283
 
270
284
  validate(sectionPredicate, data, validationOverride);
271
285
 
272
- return data as APISectionComponent;
286
+ return data;
273
287
  }
274
288
  }
@@ -56,8 +56,15 @@ export class TextDisplayBuilder extends ComponentBuilder<APITextDisplayComponent
56
56
  */
57
57
  public override toJSON(validationOverride?: boolean): APITextDisplayComponent {
58
58
  const clone = structuredClone(this.data);
59
- validate(textDisplayPredicate, clone, validationOverride);
59
+
60
+ // Enforce only type and content are present
61
+ const data: APITextDisplayComponent = {
62
+ type: ComponentType.TextDisplay,
63
+ content: clone.content!,
64
+ };
65
+
66
+ validate(textDisplayPredicate, data, validationOverride);
60
67
 
61
- return clone as APITextDisplayComponent;
68
+ return data;
62
69
  }
63
70
  }
@@ -93,8 +93,18 @@ export class ThumbnailBuilder extends ComponentBuilder<APIThumbnailComponent> {
93
93
  */
94
94
  public override toJSON(validationOverride?: boolean): APIThumbnailComponent {
95
95
  const clone = structuredClone(this.data);
96
- validate(thumbnailPredicate, clone, validationOverride);
97
96
 
98
- return clone as APIThumbnailComponent;
97
+ // Enforce Type 11 structure: { type: 11, media: { url: ... } }
98
+ // We ignore description and spoiler for now as per V2 accessory specs
99
+ const data: APIThumbnailComponent = {
100
+ type: ComponentType.Thumbnail,
101
+ media: {
102
+ url: clone.media!.url,
103
+ },
104
+ };
105
+
106
+ validate(thumbnailPredicate, data, validationOverride);
107
+
108
+ return data;
99
109
  }
100
110
  }
@@ -17,6 +17,7 @@ import type {
17
17
  APISeparatorComponent,
18
18
  APITextDisplayComponent,
19
19
  APIMessageTopLevelComponent,
20
+ APIComponentInActionRow,
20
21
  } from 'discord-api-types/v10';
21
22
  import { ActionRowBuilder } from '../components/ActionRow.js';
22
23
  import { ComponentBuilder } from '../components/Component.js';
@@ -310,7 +311,7 @@ export class MessageBuilder
310
311
  ): this {
311
312
  this.data.components ??= [];
312
313
 
313
- const resolved = normalizeArray(components).map((component) => resolveBuilder(component, ActionRowBuilder));
314
+ const resolved = normalizeArray(components).map((component) => resolveBuilder<ActionRowBuilder, Partial<APIActionRowComponent<APIComponentInActionRow>>>(component, ActionRowBuilder));
314
315
  this.data.components.push(...resolved);
315
316
 
316
317
  return this;
@@ -0,0 +1,26 @@
1
+ import type { APIMessageComponentEmoji } from 'discord-api-types/v10';
2
+
3
+ /**
4
+ * Parses emoji info out of a string. The string must be one of:
5
+ * - A UTF-8 emoji (no id)
6
+ * - A URL-encoded UTF-8 emoji (no id)
7
+ * - A Discord custom emoji (`<:name:id>` or `<a:name:id>`)
8
+ *
9
+ * @param text - Emoji string to parse
10
+ * @returns The parsed emoji data
11
+ */
12
+ export function parseEmoji(text: string): APIMessageComponentEmoji {
13
+ const decodedText = text.includes('%') ? decodeURIComponent(text) : text;
14
+ if (!decodedText.includes(':')) return { animated: false, name: decodedText, id: undefined };
15
+ const match = /<?(?:(?<animated>a):)?(?<name>\w{2,32}):(?<id>\d{17,19})?>?/.exec(decodedText);
16
+
17
+ if (!match || !match.groups) {
18
+ return { animated: false, name: decodedText, id: undefined };
19
+ }
20
+
21
+ return {
22
+ animated: Boolean(match.groups.animated),
23
+ name: match.groups.name,
24
+ id: match.groups.id,
25
+ };
26
+ }
@@ -36,5 +36,9 @@ export function resolveBuilder<Builder extends JSONEncodable<any>, BuilderData e
36
36
  return builder(new Constructor());
37
37
  }
38
38
 
39
- return new Constructor(builder);
39
+ if (typeof builder === 'object' && builder !== null && 'toJSON' in builder) {
40
+ return new Constructor((builder as unknown as JSONEncodable<BuilderData>).toJSON());
41
+ }
42
+
43
+ return new Constructor(builder as BuilderData);
40
44
  }