@lunibee/builders 0.1.6 → 0.1.7

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/src/components.ts CHANGED
@@ -1,600 +1,643 @@
1
+ // Re-export the canonical ButtonStyle from @lunibee/types so the builders package
2
+ // never ships a drifting duplicate (the old local copy was missing `Premium: 6`).
3
+ import { ButtonStyle } from "@lunibee/types";
4
+ export { ButtonStyle };
5
+
1
6
  /** Component type constants exposed by Lunibee. */
2
7
  export const ComponentType = {
3
- ActionRow: 1,
4
- Button: 2,
5
- StringSelect: 3,
6
- TextInput: 4,
7
- UserSelect: 5,
8
- RoleSelect: 6,
9
- MentionableSelect: 7,
10
- ChannelSelect: 8,
11
- Section: 9,
12
- TextDisplay: 10,
13
- Thumbnail: 11,
14
- MediaGallery: 12,
15
- File: 13,
16
- Separator: 14,
17
- ContentInventoryEntry: 16,
18
- Container: 17,
19
- } as const;
20
- export const ButtonStyle = {
21
- Primary: 1,
22
- Secondary: 2,
23
- Success: 3,
24
- Danger: 4,
25
- Link: 5,
8
+ ActionRow: 1,
9
+ Button: 2,
10
+ StringSelect: 3,
11
+ TextInput: 4,
12
+ UserSelect: 5,
13
+ RoleSelect: 6,
14
+ MentionableSelect: 7,
15
+ ChannelSelect: 8,
16
+ Section: 9,
17
+ TextDisplay: 10,
18
+ Thumbnail: 11,
19
+ MediaGallery: 12,
20
+ File: 13,
21
+ Separator: 14,
22
+ ContentInventoryEntry: 16,
23
+ Container: 17,
26
24
  } as const;
27
25
  export const TextInputStyle = { Short: 1, Paragraph: 2 } as const;
28
26
  export interface APIComponentEmoji {
29
- id?: string | null;
30
- name?: string | null;
31
- animated?: boolean;
27
+ id?: string | null;
28
+ name?: string | null;
29
+ animated?: boolean;
32
30
  }
33
31
  export interface APIButtonComponent {
34
- type: typeof ComponentType.Button;
35
- style: 1 | 2 | 3 | 4 | 5;
36
- custom_id?: string;
37
- label?: string;
38
- emoji?: APIComponentEmoji;
39
- url?: string;
40
- disabled?: boolean;
32
+ type: typeof ComponentType.Button;
33
+ style: 1 | 2 | 3 | 4 | 5 | 6;
34
+ custom_id?: string;
35
+ label?: string;
36
+ emoji?: APIComponentEmoji;
37
+ url?: string;
38
+ disabled?: boolean;
41
39
  }
42
40
  export interface APISelectOption {
43
- label: string;
44
- value: string;
45
- description?: string;
46
- emoji?: APIComponentEmoji;
47
- default?: boolean;
41
+ label: string;
42
+ value: string;
43
+ description?: string;
44
+ emoji?: APIComponentEmoji;
45
+ default?: boolean;
48
46
  }
49
47
  export interface APIStringSelectComponent {
50
- type: typeof ComponentType.StringSelect;
51
- custom_id?: string;
52
- placeholder?: string;
53
- min_values?: number;
54
- max_values?: number;
55
- required?: boolean;
56
- disabled?: boolean;
57
- options?: APISelectOption[];
48
+ type: typeof ComponentType.StringSelect;
49
+ custom_id?: string;
50
+ placeholder?: string;
51
+ min_values?: number;
52
+ max_values?: number;
53
+ required?: boolean;
54
+ disabled?: boolean;
55
+ options?: APISelectOption[];
58
56
  }
59
57
  export interface APIEntitySelectComponent {
60
- type:
61
- | typeof ComponentType.UserSelect
62
- | typeof ComponentType.RoleSelect
63
- | typeof ComponentType.MentionableSelect
64
- | typeof ComponentType.ChannelSelect;
65
- custom_id?: string;
66
- placeholder?: string;
67
- min_values?: number;
68
- max_values?: number;
69
- required?: boolean;
70
- disabled?: boolean;
58
+ type:
59
+ | typeof ComponentType.UserSelect
60
+ | typeof ComponentType.RoleSelect
61
+ | typeof ComponentType.MentionableSelect
62
+ | typeof ComponentType.ChannelSelect;
63
+ custom_id?: string;
64
+ placeholder?: string;
65
+ min_values?: number;
66
+ max_values?: number;
67
+ required?: boolean;
68
+ disabled?: boolean;
69
+ default_values?: APISelectDefaultValue[];
71
70
  }
72
71
  export interface APITextInputComponent {
73
- type: typeof ComponentType.TextInput;
74
- style: typeof TextInputStyle.Short | typeof TextInputStyle.Paragraph;
75
- custom_id?: string;
76
- label?: string;
77
- placeholder?: string;
78
- min_length?: number;
79
- max_length?: number;
80
- required?: boolean;
81
- value?: string;
72
+ type: typeof ComponentType.TextInput;
73
+ style: typeof TextInputStyle.Short | typeof TextInputStyle.Paragraph;
74
+ custom_id?: string;
75
+ label?: string;
76
+ placeholder?: string;
77
+ min_length?: number;
78
+ max_length?: number;
79
+ required?: boolean;
80
+ value?: string;
82
81
  }
83
82
  export interface APIModalComponent {
84
- type: 9;
85
- custom_id?: string;
86
- title?: string;
87
- components: APIActionRowComponent[];
83
+ type: 9;
84
+ custom_id?: string;
85
+ title?: string;
86
+ components: APIActionRowComponent[];
88
87
  }
89
88
  export type APIActionRowChild =
90
- | APIButtonComponent
91
- | APIStringSelectComponent
92
- | APIEntitySelectComponent
93
- | APITextInputComponent;
89
+ | APIButtonComponent
90
+ | APIStringSelectComponent
91
+ | APIEntitySelectComponent
92
+ | APITextInputComponent;
94
93
  export interface APIActionRowComponent {
95
- type: typeof ComponentType.ActionRow;
96
- components: APIActionRowChild[];
94
+ type: typeof ComponentType.ActionRow;
95
+ components: APIActionRowChild[];
97
96
  }
98
97
  export interface APISectionComponent {
99
- type: typeof ComponentType.Section;
100
- components?: APIComponent[]; // text displays
101
- accessory?: APIComponent; // buttons, thumbnails, etc.
98
+ type: typeof ComponentType.Section;
99
+ components?: APIComponent[]; // text displays
100
+ accessory?: APIComponent; // buttons, thumbnails, etc.
102
101
  }
103
102
  export interface APITextDisplayComponent {
104
- type: typeof ComponentType.TextDisplay;
105
- content: string;
103
+ type: typeof ComponentType.TextDisplay;
104
+ content: string;
106
105
  }
107
106
  export interface APIMediaGalleryComponent {
108
- type: typeof ComponentType.MediaGallery;
109
- items: { media: { url: string; description?: string } }[];
107
+ type: typeof ComponentType.MediaGallery;
108
+ items: { media: { url: string; description?: string } }[];
109
+ }
110
+ /** Default values accepted by an auto-populated entity select. */
111
+ export interface APISelectDefaultValue {
112
+ id: string;
113
+ type: "user" | "role" | "channel";
110
114
  }
111
115
  export interface APIFileComponent {
112
- type: typeof ComponentType.File;
113
- file: { url: string };
114
- spoiler?: boolean;
116
+ type: typeof ComponentType.File;
117
+ file: { url: string };
118
+ spoiler?: boolean;
115
119
  }
116
120
  export interface APISeparatorComponent {
117
- type: typeof ComponentType.Separator;
118
- spacing?: 1 | 2;
119
- divider?: boolean;
121
+ type: typeof ComponentType.Separator;
122
+ spacing?: 1 | 2;
123
+ divider?: boolean;
120
124
  }
121
125
  export interface APIThumbnailComponent {
122
- type: typeof ComponentType.Thumbnail;
123
- url: string;
124
- proxy_url?: string;
125
- width?: number;
126
- height?: number;
126
+ type: typeof ComponentType.Thumbnail;
127
+ url: string;
128
+ proxy_url?: string;
129
+ width?: number;
130
+ height?: number;
131
+ description?: string;
132
+ spoiler?: boolean;
127
133
  }
128
134
  export interface APIContentInventoryEntryComponent {
129
- type: typeof ComponentType.ContentInventoryEntry;
130
- id: string;
135
+ type: typeof ComponentType.ContentInventoryEntry;
136
+ id: string;
131
137
  }
132
138
  export interface APIContainerComponent {
133
- type: typeof ComponentType.Container;
134
- components: APIComponent[];
135
- accent_color?: number;
139
+ type: typeof ComponentType.Container;
140
+ components: APIComponent[];
141
+ accent_color?: number;
142
+ spoiler?: boolean;
136
143
  }
137
144
  export type APIComponent =
138
- | APIActionRowComponent
139
- | APIButtonComponent
140
- | APIStringSelectComponent
141
- | APIEntitySelectComponent
142
- | APITextInputComponent
143
- | APISectionComponent
144
- | APITextDisplayComponent
145
- | APIThumbnailComponent
146
- | APIMediaGalleryComponent
147
- | APIFileComponent
148
- | APISeparatorComponent
149
- | APIContentInventoryEntryComponent
150
- | APIContainerComponent;
145
+ | APIActionRowComponent
146
+ | APIButtonComponent
147
+ | APIStringSelectComponent
148
+ | APIEntitySelectComponent
149
+ | APITextInputComponent
150
+ | APISectionComponent
151
+ | APITextDisplayComponent
152
+ | APIThumbnailComponent
153
+ | APIMediaGalleryComponent
154
+ | APIFileComponent
155
+ | APISeparatorComponent
156
+ | APIContentInventoryEntryComponent
157
+ | APIContainerComponent;
151
158
 
152
159
  export class ActionRowBuilder<
153
- T extends { toJSON(): APIActionRowChild } = { toJSON(): APIActionRowChild },
160
+ T extends { toJSON(): APIActionRowChild } = { toJSON(): APIActionRowChild },
154
161
  > {
155
- readonly #components: T[] = [];
156
- public addComponents(...components: T[]): this {
157
- if (!components.length)
158
- throw new TypeError("At least one component is required.");
159
- if (this.#components.length + components.length > 5)
160
- throw new RangeError(
161
- "An action row cannot contain more than 5 components.",
162
- );
163
- this.#components.push(...components);
164
- return this;
165
- }
166
- public clearComponents(): this {
167
- this.#components.length = 0;
168
- return this;
169
- }
170
- public toJSON(): APIActionRowComponent {
171
- return {
172
- type: ComponentType.ActionRow,
173
- components: this.#components.map((component) => component.toJSON()),
174
- };
175
- }
162
+ readonly #components: T[] = [];
163
+ public addComponents(...components: T[]): this {
164
+ if (!components.length)
165
+ throw new TypeError("At least one component is required.");
166
+ if (this.#components.length + components.length > 5)
167
+ throw new RangeError(
168
+ "An action row cannot contain more than 5 components.",
169
+ );
170
+ this.#components.push(...components);
171
+ return this;
172
+ }
173
+ public clearComponents(): this {
174
+ this.#components.length = 0;
175
+ return this;
176
+ }
177
+ public toJSON(): APIActionRowComponent {
178
+ return {
179
+ type: ComponentType.ActionRow,
180
+ components: this.#components.map((component) => component.toJSON()),
181
+ };
182
+ }
176
183
  }
177
184
  export class StringSelectBuilder {
178
- readonly #data: APIStringSelectComponent = {
179
- type: ComponentType.StringSelect,
180
- };
181
- public setCustomId(value: string): this {
182
- validateText(value, 100, "Component custom ID");
183
- this.#data.custom_id = value;
184
- return this;
185
- }
186
- public setPlaceholder(value: string): this {
187
- validateText(value, 150, "Component placeholder");
188
- this.#data.placeholder = value;
189
- return this;
190
- }
191
- public setMinValues(value: number): this {
192
- validateCount(value, "min_values", 0);
193
- this.#data.min_values = value;
194
- return this;
195
- }
196
- public setMaxValues(value: number): this {
197
- validateCount(value, "max_values", 1);
198
- this.#data.max_values = value;
199
- return this;
200
- }
201
- public setRequired(value = true): this {
202
- this.#data.required = value;
203
- return this;
204
- }
205
- public setDisabled(value = true): this {
206
- this.#data.disabled = value;
207
- return this;
208
- }
209
- public addOptions(...options: APISelectOption[]): this {
210
- if (!options.length)
211
- throw new TypeError("At least one select option is required.");
212
- const current = this.#data.options ?? [];
213
- if (current.length + options.length > 25)
214
- throw new RangeError(
215
- "A string select cannot contain more than 25 options.",
216
- );
217
- for (const option of options) {
218
- validateText(option.label, 100, "Select option label");
219
- validateText(option.value, 100, "Select option value");
220
- if (option.description)
221
- validateText(option.description, 100, "Select option description");
222
- }
223
- this.#data.options = [
224
- ...current,
225
- ...options.map((option) => ({ ...option })),
226
- ];
227
- return this;
228
- }
229
- public toJSON(): APIStringSelectComponent {
230
- return structuredClone(this.#data);
231
- }
185
+ readonly #data: APIStringSelectComponent = {
186
+ type: ComponentType.StringSelect,
187
+ };
188
+ public setCustomId(value: string): this {
189
+ validateText(value, 100, "Component custom ID");
190
+ this.#data.custom_id = value;
191
+ return this;
192
+ }
193
+ public setPlaceholder(value: string): this {
194
+ validateText(value, 150, "Component placeholder");
195
+ this.#data.placeholder = value;
196
+ return this;
197
+ }
198
+ public setMinValues(value: number): this {
199
+ validateCount(value, "min_values", 0);
200
+ this.#data.min_values = value;
201
+ return this;
202
+ }
203
+ public setMaxValues(value: number): this {
204
+ validateCount(value, "max_values", 1);
205
+ this.#data.max_values = value;
206
+ return this;
207
+ }
208
+ public setRequired(value = true): this {
209
+ this.#data.required = value;
210
+ return this;
211
+ }
212
+ public setDisabled(value = true): this {
213
+ this.#data.disabled = value;
214
+ return this;
215
+ }
216
+ public addOptions(...options: APISelectOption[]): this {
217
+ if (!options.length)
218
+ throw new TypeError("At least one select option is required.");
219
+ const current = this.#data.options ?? [];
220
+ if (current.length + options.length > 25)
221
+ throw new RangeError(
222
+ "A string select cannot contain more than 25 options.",
223
+ );
224
+ for (const option of options) {
225
+ validateText(option.label, 100, "Select option label");
226
+ validateText(option.value, 100, "Select option value");
227
+ if (option.description)
228
+ validateText(
229
+ option.description,
230
+ 100,
231
+ "Select option description",
232
+ );
233
+ }
234
+ this.#data.options = [
235
+ ...current,
236
+ ...options.map((option) => ({ ...option })),
237
+ ];
238
+ return this;
239
+ }
240
+ public toJSON(): APIStringSelectComponent {
241
+ return structuredClone(this.#data);
242
+ }
232
243
  }
233
244
  export class ButtonBuilder {
234
- readonly #data: APIButtonComponent = {
235
- type: ComponentType.Button,
236
- style: ButtonStyle.Secondary,
237
- };
238
- public setStyle(style: APIButtonComponent["style"]): this {
239
- this.#data.style = style;
240
- if (style === ButtonStyle.Link) delete this.#data.custom_id;
241
- return this;
242
- }
243
- public setCustomId(value: string): this {
244
- validateText(value, 100, "Button custom ID");
245
- if (this.#data.style === ButtonStyle.Link)
246
- throw new TypeError("Link buttons cannot use custom IDs.");
247
- this.#data.custom_id = value;
248
- return this;
249
- }
250
- public setLabel(value: string): this {
251
- validateText(value, 80, "Button label");
252
- this.#data.label = value;
253
- return this;
254
- }
255
- public setURL(value: string): this {
256
- validateText(value, 512, "Button URL");
257
- let url: URL;
258
- try {
259
- url = new URL(value);
260
- } catch (error) {
261
- throw new TypeError("Button URL must be a valid URL.", { cause: error });
262
- }
263
- this.#data.style = ButtonStyle.Link;
264
- delete this.#data.custom_id;
265
- this.#data.url = url.toString();
266
- return this;
267
- }
268
- public setEmoji(emoji: APIComponentEmoji | string): this {
269
- this.#data.emoji =
270
- typeof emoji === "string" ? { name: emoji } : { ...emoji };
271
- return this;
272
- }
273
- public setDisabled(value = true): this {
274
- this.#data.disabled = value;
275
- return this;
276
- }
277
- public toJSON(): APIButtonComponent {
278
- return structuredClone(this.#data);
279
- }
245
+ readonly #data: APIButtonComponent = {
246
+ type: ComponentType.Button,
247
+ style: ButtonStyle.Secondary,
248
+ };
249
+ public setStyle(style: APIButtonComponent["style"]): this {
250
+ this.#data.style = style;
251
+ if (style === ButtonStyle.Link) delete this.#data.custom_id;
252
+ return this;
253
+ }
254
+ public setCustomId(value: string): this {
255
+ validateText(value, 100, "Button custom ID");
256
+ if (this.#data.style === ButtonStyle.Link)
257
+ throw new TypeError("Link buttons cannot use custom IDs.");
258
+ this.#data.custom_id = value;
259
+ return this;
260
+ }
261
+ public setLabel(value: string): this {
262
+ validateText(value, 80, "Button label");
263
+ this.#data.label = value;
264
+ return this;
265
+ }
266
+ public setURL(value: string): this {
267
+ validateText(value, 512, "Button URL");
268
+ let url: URL;
269
+ try {
270
+ url = new URL(value);
271
+ } catch (error) {
272
+ throw new TypeError("Button URL must be a valid URL.", {
273
+ cause: error,
274
+ });
275
+ }
276
+ this.#data.style = ButtonStyle.Link;
277
+ delete this.#data.custom_id;
278
+ this.#data.url = url.toString();
279
+ return this;
280
+ }
281
+ public setEmoji(emoji: APIComponentEmoji | string): this {
282
+ this.#data.emoji =
283
+ typeof emoji === "string" ? { name: emoji } : { ...emoji };
284
+ return this;
285
+ }
286
+ public setDisabled(value = true): this {
287
+ this.#data.disabled = value;
288
+ return this;
289
+ }
290
+ public toJSON(): APIButtonComponent {
291
+ return structuredClone(this.#data);
292
+ }
280
293
  }
281
294
  export class EntitySelectBuilder {
282
- readonly #data: APIEntitySelectComponent;
283
- public constructor(
284
- type:
285
- | typeof ComponentType.UserSelect
286
- | typeof ComponentType.RoleSelect
287
- | typeof ComponentType.MentionableSelect
288
- | typeof ComponentType.ChannelSelect,
289
- ) {
290
- if (
291
- type !== ComponentType.UserSelect &&
292
- type !== ComponentType.RoleSelect &&
293
- type !== ComponentType.MentionableSelect &&
294
- type !== ComponentType.ChannelSelect
295
- )
296
- throw new TypeError("Invalid select component type.");
297
- this.#data = { type };
298
- }
299
- public setCustomId(value: string): this {
300
- validateText(value, 100, "Component custom ID");
301
- this.#data.custom_id = value;
302
- return this;
303
- }
304
- public setPlaceholder(value: string): this {
305
- validateText(value, 150, "Component placeholder");
306
- this.#data.placeholder = value;
307
- return this;
308
- }
309
- public setMinValues(value: number): this {
310
- validateCount(value, "min_values", 0);
311
- this.#data.min_values = value;
312
- return this;
313
- }
314
- public setMaxValues(value: number): this {
315
- validateCount(value, "max_values", 1);
316
- this.#data.max_values = value;
317
- return this;
318
- }
319
- public setRequired(value = true): this {
320
- this.#data.required = value;
321
- return this;
322
- }
323
- public setDisabled(value = true): this {
324
- this.#data.disabled = value;
325
- return this;
326
- }
327
- public setDefaultValues(
328
- ...values: { id: string; type: "user" | "role" | "channel" }[]
329
- ): this {
330
- (this.#data as any).default_values = values;
331
- return this;
332
- }
333
- public toJSON(): APIEntitySelectComponent {
334
- return structuredClone(this.#data);
335
- }
295
+ readonly #data: APIEntitySelectComponent;
296
+ public constructor(
297
+ type:
298
+ | typeof ComponentType.UserSelect
299
+ | typeof ComponentType.RoleSelect
300
+ | typeof ComponentType.MentionableSelect
301
+ | typeof ComponentType.ChannelSelect,
302
+ ) {
303
+ if (
304
+ type !== ComponentType.UserSelect &&
305
+ type !== ComponentType.RoleSelect &&
306
+ type !== ComponentType.MentionableSelect &&
307
+ type !== ComponentType.ChannelSelect
308
+ )
309
+ throw new TypeError("Invalid select component type.");
310
+ this.#data = { type };
311
+ }
312
+ public setCustomId(value: string): this {
313
+ validateText(value, 100, "Component custom ID");
314
+ this.#data.custom_id = value;
315
+ return this;
316
+ }
317
+ public setPlaceholder(value: string): this {
318
+ validateText(value, 150, "Component placeholder");
319
+ this.#data.placeholder = value;
320
+ return this;
321
+ }
322
+ public setMinValues(value: number): this {
323
+ validateCount(value, "min_values", 0);
324
+ this.#data.min_values = value;
325
+ return this;
326
+ }
327
+ public setMaxValues(value: number): this {
328
+ validateCount(value, "max_values", 1);
329
+ this.#data.max_values = value;
330
+ return this;
331
+ }
332
+ public setRequired(value = true): this {
333
+ this.#data.required = value;
334
+ return this;
335
+ }
336
+ public setDisabled(value = true): this {
337
+ this.#data.disabled = value;
338
+ return this;
339
+ }
340
+ public setDefaultValues(...values: APISelectDefaultValue[]): this {
341
+ this.#data.default_values = values.map((value) => ({ ...value }));
342
+ return this;
343
+ }
344
+ public toJSON(): APIEntitySelectComponent {
345
+ return structuredClone(this.#data);
346
+ }
336
347
  }
337
348
  export class ModalBuilder {
338
- readonly #components: Array<{ toJSON(): APIActionRowComponent }> = [];
339
- #custom_id?: string;
340
- #title?: string;
341
- public setCustomId(value: string): this {
342
- validateText(value, 100, "Modal custom ID");
343
- this.#custom_id = value;
344
- return this;
345
- }
346
- public setTitle(value: string): this {
347
- validateText(value, 45, "Modal title");
348
- this.#title = value;
349
- return this;
350
- }
351
- public addComponents(
352
- ...components: Array<{ toJSON(): APIActionRowComponent }>
353
- ): this {
354
- if (!components.length)
355
- throw new TypeError("At least one modal component is required.");
356
- if (this.#components.length + components.length > 5)
357
- throw new RangeError("A modal cannot contain more than 5 action rows.");
358
- this.#components.push(...components);
359
- return this;
360
- }
361
- public toJSON(): APIModalComponent {
362
- return {
363
- type: 9,
364
- ...(this.#custom_id ? { custom_id: this.#custom_id } : {}),
365
- ...(this.#title ? { title: this.#title } : {}),
366
- components: this.#components.map((component) => component.toJSON()),
367
- };
368
- }
349
+ readonly #components: Array<{ toJSON(): APIActionRowComponent }> = [];
350
+ #custom_id?: string;
351
+ #title?: string;
352
+ public setCustomId(value: string): this {
353
+ validateText(value, 100, "Modal custom ID");
354
+ this.#custom_id = value;
355
+ return this;
356
+ }
357
+ public setTitle(value: string): this {
358
+ validateText(value, 45, "Modal title");
359
+ this.#title = value;
360
+ return this;
361
+ }
362
+ public addComponents(
363
+ ...components: Array<{ toJSON(): APIActionRowComponent }>
364
+ ): this {
365
+ if (!components.length)
366
+ throw new TypeError("At least one modal component is required.");
367
+ if (this.#components.length + components.length > 5)
368
+ throw new RangeError(
369
+ "A modal cannot contain more than 5 action rows.",
370
+ );
371
+ this.#components.push(...components);
372
+ return this;
373
+ }
374
+ public toJSON(): APIModalComponent {
375
+ return {
376
+ type: 9,
377
+ ...(this.#custom_id ? { custom_id: this.#custom_id } : {}),
378
+ ...(this.#title ? { title: this.#title } : {}),
379
+ components: this.#components.map((component) => component.toJSON()),
380
+ };
381
+ }
369
382
  }
370
383
  export class TextInputBuilder {
371
- readonly #data: APITextInputComponent = {
372
- type: ComponentType.TextInput,
373
- style: TextInputStyle.Short,
374
- };
375
- public setStyle(style: APITextInputComponent["style"]): this {
376
- this.#data.style = style;
377
- return this;
378
- }
379
- public setCustomId(value: string): this {
380
- validateText(value, 100, "Text input custom ID");
381
- this.#data.custom_id = value;
382
- return this;
383
- }
384
- public setLabel(value: string): this {
385
- validateText(value, 45, "Text input label");
386
- this.#data.label = value;
387
- return this;
388
- }
389
- public setPlaceholder(value: string): this {
390
- validateText(value, 100, "Text input placeholder");
391
- this.#data.placeholder = value;
392
- return this;
393
- }
394
- public setMinLength(value: number): this {
395
- validateLength(value, "min_length", 4000);
396
- this.#data.min_length = value;
397
- return this;
398
- }
399
- public setMaxLength(value: number): this {
400
- validateLength(value, "max_length", 4000);
401
- this.#data.max_length = value;
402
- return this;
403
- }
404
- public setRequired(value = true): this {
405
- this.#data.required = value;
406
- return this;
407
- }
408
- public setValue(value: string): this {
409
- validateText(value, 4000, "Text input value");
410
- this.#data.value = value;
411
- return this;
412
- }
413
- public toJSON(): APITextInputComponent {
414
- return structuredClone(this.#data);
415
- }
384
+ readonly #data: APITextInputComponent = {
385
+ type: ComponentType.TextInput,
386
+ style: TextInputStyle.Short,
387
+ };
388
+ public setStyle(style: APITextInputComponent["style"]): this {
389
+ this.#data.style = style;
390
+ return this;
391
+ }
392
+ public setCustomId(value: string): this {
393
+ validateText(value, 100, "Text input custom ID");
394
+ this.#data.custom_id = value;
395
+ return this;
396
+ }
397
+ public setLabel(value: string): this {
398
+ validateText(value, 45, "Text input label");
399
+ this.#data.label = value;
400
+ return this;
401
+ }
402
+ public setPlaceholder(value: string): this {
403
+ validateText(value, 100, "Text input placeholder");
404
+ this.#data.placeholder = value;
405
+ return this;
406
+ }
407
+ public setMinLength(value: number): this {
408
+ validateLength(value, "min_length", 4000);
409
+ this.#data.min_length = value;
410
+ return this;
411
+ }
412
+ public setMaxLength(value: number): this {
413
+ validateLength(value, "max_length", 4000);
414
+ this.#data.max_length = value;
415
+ return this;
416
+ }
417
+ public setRequired(value = true): this {
418
+ this.#data.required = value;
419
+ return this;
420
+ }
421
+ public setValue(value: string): this {
422
+ validateText(value, 4000, "Text input value");
423
+ this.#data.value = value;
424
+ return this;
425
+ }
426
+ public toJSON(): APITextInputComponent {
427
+ return structuredClone(this.#data);
428
+ }
416
429
  }
417
430
 
418
431
  export class ContainerBuilder {
419
- readonly #components: { toJSON(): APIComponent }[] = [];
420
- #accentColor?: number;
421
- public addComponents(...components: { toJSON(): APIComponent }[]): this {
422
- if (!components.length)
423
- throw new TypeError("At least one component is required.");
424
- this.#components.push(...components);
425
- return this;
426
- }
427
- public setAccentColor(color: number): this {
428
- if (!Number.isInteger(color) || color < 0 || color > 0xffffff) {
429
- throw new RangeError(
430
- "Accent color must be an integer between 0 and 16777215 (0xFFFFFF).",
431
- );
432
- }
433
- this.#accentColor = color;
434
- return this;
435
- }
436
- public toJSON(): APIContainerComponent {
437
- return {
438
- type: ComponentType.Container,
439
- components: this.#components.map((c) => c.toJSON()),
440
- accent_color: this.#accentColor,
441
- };
442
- }
432
+ readonly #components: { toJSON(): APIComponent }[] = [];
433
+ #accentColor?: number;
434
+ #spoiler?: boolean;
435
+ public addComponents(...components: { toJSON(): APIComponent }[]): this {
436
+ if (!components.length)
437
+ throw new TypeError("At least one component is required.");
438
+ this.#components.push(...components);
439
+ return this;
440
+ }
441
+ public setAccentColor(color: number): this {
442
+ if (!Number.isInteger(color) || color < 0 || color > 0xffffff) {
443
+ throw new RangeError(
444
+ "Accent color must be an integer between 0 and 16777215 (0xFFFFFF).",
445
+ );
446
+ }
447
+ this.#accentColor = color;
448
+ return this;
449
+ }
450
+ /** Marks the whole container as a spoiler (blurred until clicked). */
451
+ public setSpoiler(spoiler = true): this {
452
+ this.#spoiler = spoiler;
453
+ return this;
454
+ }
455
+ public toJSON(): APIContainerComponent {
456
+ const data: APIContainerComponent = {
457
+ type: ComponentType.Container,
458
+ components: this.#components.map((c) => c.toJSON()),
459
+ };
460
+ if (this.#accentColor !== undefined)
461
+ data.accent_color = this.#accentColor;
462
+ if (this.#spoiler !== undefined) data.spoiler = this.#spoiler;
463
+ return data;
464
+ }
443
465
  }
444
466
 
445
467
  export class SectionBuilder {
446
- readonly #components: { toJSON(): APIComponent }[] = [];
447
- #accessory?: { toJSON(): APIComponent };
448
- public addComponents(...components: { toJSON(): APIComponent }[]): this {
449
- if (!components.length)
450
- throw new TypeError("At least one component is required.");
451
- this.#components.push(...components);
452
- return this;
453
- }
454
- public setAccessory(accessory: { toJSON(): APIComponent }): this {
455
- this.#accessory = accessory;
456
- return this;
457
- }
458
- public toJSON(): APISectionComponent {
459
- return {
460
- type: ComponentType.Section,
461
- components: this.#components.length
462
- ? this.#components.map((c) => c.toJSON())
463
- : undefined,
464
- accessory: this.#accessory?.toJSON(),
465
- };
466
- }
468
+ readonly #components: { toJSON(): APIComponent }[] = [];
469
+ #accessory?: { toJSON(): APIComponent };
470
+ public addComponents(...components: { toJSON(): APIComponent }[]): this {
471
+ if (!components.length)
472
+ throw new TypeError("At least one component is required.");
473
+ this.#components.push(...components);
474
+ return this;
475
+ }
476
+ public setAccessory(accessory: { toJSON(): APIComponent }): this {
477
+ this.#accessory = accessory;
478
+ return this;
479
+ }
480
+ public toJSON(): APISectionComponent {
481
+ return {
482
+ type: ComponentType.Section,
483
+ components: this.#components.length
484
+ ? this.#components.map((c) => c.toJSON())
485
+ : undefined,
486
+ accessory: this.#accessory?.toJSON(),
487
+ };
488
+ }
467
489
  }
468
490
 
469
491
  export class TextDisplayBuilder {
470
- #content = "";
471
- public setContent(content: string): this {
472
- validateText(content, 4000, "Text display content");
473
- this.#content = content;
474
- return this;
475
- }
476
- public toJSON(): APITextDisplayComponent {
477
- return {
478
- type: ComponentType.TextDisplay,
479
- content: this.#content,
480
- };
481
- }
492
+ #content = "";
493
+ public setContent(content: string): this {
494
+ validateText(content, 4000, "Text display content");
495
+ this.#content = content;
496
+ return this;
497
+ }
498
+ public toJSON(): APITextDisplayComponent {
499
+ return {
500
+ type: ComponentType.TextDisplay,
501
+ content: this.#content,
502
+ };
503
+ }
482
504
  }
483
505
 
484
506
  export class MediaGalleryBuilder {
485
- readonly #items: { media: { url: string; description?: string } }[] = [];
486
- public addItems(...items: { url: string; description?: string }[]): this {
487
- if (!items.length) {
488
- throw new TypeError("At least one media gallery item is required.");
489
- }
490
- if (this.#items.length + items.length > 10) {
491
- throw new RangeError("MediaGallery can only contain up to 10 items.");
492
- }
493
- this.#items.push(...items.map((item) => ({ media: item })));
494
- return this;
495
- }
496
- public toJSON(): APIMediaGalleryComponent {
497
- return {
498
- type: ComponentType.MediaGallery,
499
- items: structuredClone(this.#items),
500
- };
501
- }
507
+ readonly #items: { media: { url: string; description?: string } }[] = [];
508
+ public addItems(...items: { url: string; description?: string }[]): this {
509
+ if (!items.length) {
510
+ throw new TypeError("At least one media gallery item is required.");
511
+ }
512
+ if (this.#items.length + items.length > 10) {
513
+ throw new RangeError(
514
+ "MediaGallery can only contain up to 10 items.",
515
+ );
516
+ }
517
+ this.#items.push(...items.map((item) => ({ media: item })));
518
+ return this;
519
+ }
520
+ public toJSON(): APIMediaGalleryComponent {
521
+ return {
522
+ type: ComponentType.MediaGallery,
523
+ items: structuredClone(this.#items),
524
+ };
525
+ }
502
526
  }
503
527
 
504
528
  export class FileComponentBuilder {
505
- #url = "";
506
- #spoiler?: boolean;
507
- public setUrl(url: string): this {
508
- validateText(url, 2048, "File component URL");
509
- this.#url = url;
510
- return this;
511
- }
512
- public setSpoiler(spoiler = true): this {
513
- this.#spoiler = spoiler;
514
- return this;
515
- }
516
- public toJSON(): APIFileComponent {
517
- if (!this.#url) throw new Error("File component must have a URL.");
518
- const data: APIFileComponent = {
519
- type: ComponentType.File,
520
- file: { url: this.#url },
521
- };
522
- if (this.#spoiler !== undefined) data.spoiler = this.#spoiler;
523
- return data;
524
- }
529
+ #url = "";
530
+ #spoiler?: boolean;
531
+ public setUrl(url: string): this {
532
+ validateText(url, 2048, "File component URL");
533
+ this.#url = url;
534
+ return this;
535
+ }
536
+ public setSpoiler(spoiler = true): this {
537
+ this.#spoiler = spoiler;
538
+ return this;
539
+ }
540
+ public toJSON(): APIFileComponent {
541
+ if (!this.#url) throw new Error("File component must have a URL.");
542
+ const data: APIFileComponent = {
543
+ type: ComponentType.File,
544
+ file: { url: this.#url },
545
+ };
546
+ if (this.#spoiler !== undefined) data.spoiler = this.#spoiler;
547
+ return data;
548
+ }
525
549
  }
526
550
 
527
551
  export class SeparatorBuilder {
528
- #spacing?: 1 | 2;
529
- #divider?: boolean;
530
- public setSpacing(spacing: 1 | 2): this {
531
- this.#spacing = spacing;
532
- return this;
533
- }
534
- public setDivider(divider = true): this {
535
- this.#divider = divider;
536
- return this;
537
- }
538
- public toJSON(): APISeparatorComponent {
539
- const data: APISeparatorComponent = {
540
- type: ComponentType.Separator,
541
- };
542
- if (this.#spacing !== undefined) data.spacing = this.#spacing;
543
- if (this.#divider !== undefined) data.divider = this.#divider;
544
- return data;
545
- }
552
+ #spacing?: 1 | 2;
553
+ #divider?: boolean;
554
+ public setSpacing(spacing: 1 | 2): this {
555
+ this.#spacing = spacing;
556
+ return this;
557
+ }
558
+ public setDivider(divider = true): this {
559
+ this.#divider = divider;
560
+ return this;
561
+ }
562
+ public toJSON(): APISeparatorComponent {
563
+ const data: APISeparatorComponent = {
564
+ type: ComponentType.Separator,
565
+ };
566
+ if (this.#spacing !== undefined) data.spacing = this.#spacing;
567
+ if (this.#divider !== undefined) data.divider = this.#divider;
568
+ return data;
569
+ }
546
570
  }
547
571
 
548
572
  export class ThumbnailBuilder {
549
- #url = "";
550
- public setUrl(url: string): this {
551
- validateText(url, 2048, "Thumbnail URL");
552
- this.#url = url;
553
- return this;
554
- }
555
- public toJSON(): APIThumbnailComponent {
556
- return {
557
- type: ComponentType.Thumbnail,
558
- url: this.#url,
559
- };
560
- }
573
+ #url = "";
574
+ #description?: string;
575
+ #spoiler?: boolean;
576
+ public setUrl(url: string): this {
577
+ validateText(url, 2048, "Thumbnail URL");
578
+ this.#url = url;
579
+ return this;
580
+ }
581
+ /** Sets the thumbnail's alt-text description. */
582
+ public setDescription(description: string): this {
583
+ validateText(description, 1024, "Thumbnail description");
584
+ this.#description = description;
585
+ return this;
586
+ }
587
+ /** Marks the thumbnail as a spoiler (blurred until clicked). */
588
+ public setSpoiler(spoiler = true): this {
589
+ this.#spoiler = spoiler;
590
+ return this;
591
+ }
592
+ public toJSON(): APIThumbnailComponent {
593
+ const data: APIThumbnailComponent = {
594
+ type: ComponentType.Thumbnail,
595
+ url: this.#url,
596
+ };
597
+ if (this.#description !== undefined)
598
+ data.description = this.#description;
599
+ if (this.#spoiler !== undefined) data.spoiler = this.#spoiler;
600
+ return data;
601
+ }
561
602
  }
562
603
 
563
604
  export class ContentInventoryEntryBuilder {
564
- #id = "";
565
- public setId(id: string): this {
566
- validateText(id, 100, "Content inventory entry ID");
567
- this.#id = id;
568
- return this;
569
- }
570
- public toJSON(): APIContentInventoryEntryComponent {
571
- return {
572
- type: ComponentType.ContentInventoryEntry,
573
- id: this.#id,
574
- };
575
- }
605
+ #id = "";
606
+ public setId(id: string): this {
607
+ validateText(id, 100, "Content inventory entry ID");
608
+ this.#id = id;
609
+ return this;
610
+ }
611
+ public toJSON(): APIContentInventoryEntryComponent {
612
+ return {
613
+ type: ComponentType.ContentInventoryEntry,
614
+ id: this.#id,
615
+ };
616
+ }
576
617
  }
577
618
 
578
619
  function validateText(value: string, max: number, field: string): void {
579
- if (typeof value !== "string")
580
- throw new TypeError(`${field} must be a string.`);
581
- if (!value.trim())
582
- throw new RangeError(`${field} must be a non-empty string.`);
583
- if (value.length > max)
584
- throw new RangeError(`${field} cannot exceed ${max} characters.`);
620
+ if (typeof value !== "string")
621
+ throw new TypeError(`${field} must be a string.`);
622
+ if (!value.trim())
623
+ throw new RangeError(`${field} must be a non-empty string.`);
624
+ if (value.length > max)
625
+ throw new RangeError(`${field} cannot exceed ${max} characters.`);
585
626
  }
586
627
  function validateCount(
587
- value: number,
588
- field: string,
589
- min: number,
590
- max = 25,
628
+ value: number,
629
+ field: string,
630
+ min: number,
631
+ max = 25,
591
632
  ): void {
592
- if (!Number.isInteger(value) || value < min || value > max)
593
- throw new RangeError(
594
- `${field} must be an integer between ${min} and ${max}.`,
595
- );
633
+ if (!Number.isInteger(value) || value < min || value > max)
634
+ throw new RangeError(
635
+ `${field} must be an integer between ${min} and ${max}.`,
636
+ );
596
637
  }
597
638
  function validateLength(value: number, field: string, max = 4000): void {
598
- if (!Number.isInteger(value) || value < 0 || value > max)
599
- throw new RangeError(`${field} must be an integer between 0 and ${max}.`);
639
+ if (!Number.isInteger(value) || value < 0 || value > max)
640
+ throw new RangeError(
641
+ `${field} must be an integer between 0 and ${max}.`,
642
+ );
600
643
  }