@lunibee/builders 0.1.5 → 0.1.6

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.
Files changed (3) hide show
  1. package/package.json +10 -10
  2. package/src/components.ts +600 -378
  3. package/src/index.ts +37 -29
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
- {
2
- "name": "@lunibee/builders",
3
- "version": "0.1.5",
4
- "type": "module",
5
- "module": "./src/index.ts",
6
- "types": "./src/index.ts",
7
- "exports": {
8
- ".": "./src/index.ts"
9
- }
10
- }
1
+ {
2
+ "name": "@lunibee/builders",
3
+ "version": "0.1.6",
4
+ "type": "module",
5
+ "module": "./src/index.ts",
6
+ "types": "./src/index.ts",
7
+ "exports": {
8
+ ".": "./src/index.ts"
9
+ }
10
+ }
package/src/components.ts CHANGED
@@ -1,378 +1,600 @@
1
- /** Component type constants exposed by Lunibee. */
2
- 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
- } as const;
12
- export const ButtonStyle = {
13
- Primary: 1,
14
- Secondary: 2,
15
- Success: 3,
16
- Danger: 4,
17
- Link: 5,
18
- } as const;
19
- export const TextInputStyle = { Short: 1, Paragraph: 2 } as const;
20
- export interface APIComponentEmoji {
21
- id?: string | null;
22
- name?: string | null;
23
- animated?: boolean;
24
- }
25
- export interface APIButtonComponent {
26
- type: typeof ComponentType.Button;
27
- style: 1 | 2 | 3 | 4 | 5;
28
- custom_id?: string;
29
- label?: string;
30
- emoji?: APIComponentEmoji;
31
- url?: string;
32
- disabled?: boolean;
33
- }
34
- export interface APISelectOption {
35
- label: string;
36
- value: string;
37
- description?: string;
38
- emoji?: APIComponentEmoji;
39
- default?: boolean;
40
- }
41
- export interface APIStringSelectComponent {
42
- type: typeof ComponentType.StringSelect;
43
- custom_id?: string;
44
- placeholder?: string;
45
- min_values?: number;
46
- max_values?: number;
47
- required?: boolean;
48
- disabled?: boolean;
49
- options?: APISelectOption[];
50
- }
51
- export interface APIEntitySelectComponent {
52
- type:
53
- | typeof ComponentType.UserSelect
54
- | typeof ComponentType.RoleSelect
55
- | typeof ComponentType.MentionableSelect
56
- | typeof ComponentType.ChannelSelect;
57
- custom_id?: string;
58
- placeholder?: string;
59
- min_values?: number;
60
- max_values?: number;
61
- required?: boolean;
62
- disabled?: boolean;
63
- }
64
- export interface APITextInputComponent {
65
- type: typeof ComponentType.TextInput;
66
- style: typeof TextInputStyle.Short | typeof TextInputStyle.Paragraph;
67
- custom_id?: string;
68
- label?: string;
69
- placeholder?: string;
70
- min_length?: number;
71
- max_length?: number;
72
- required?: boolean;
73
- value?: string;
74
- }
75
- export interface APIModalComponent {
76
- type: 9;
77
- custom_id?: string;
78
- title?: string;
79
- components: APIActionRowComponent[];
80
- }
81
- export type APIActionRowChild =
82
- | APIButtonComponent
83
- | APIStringSelectComponent
84
- | APIEntitySelectComponent
85
- | APITextInputComponent;
86
- export interface APIActionRowComponent {
87
- type: typeof ComponentType.ActionRow;
88
- components: APIActionRowChild[];
89
- }
90
-
91
- export class ActionRowBuilder<
92
- T extends { toJSON(): APIActionRowChild } = { toJSON(): APIActionRowChild },
93
- > {
94
- readonly #components: T[] = [];
95
- public addComponents(...components: T[]): this {
96
- if (!components.length)
97
- throw new TypeError("At least one component is required.");
98
- if (this.#components.length + components.length > 5)
99
- throw new RangeError(
100
- "An action row cannot contain more than 5 components.",
101
- );
102
- this.#components.push(...components);
103
- return this;
104
- }
105
- public clearComponents(): this {
106
- this.#components.length = 0;
107
- return this;
108
- }
109
- public toJSON(): APIActionRowComponent {
110
- return {
111
- type: ComponentType.ActionRow,
112
- components: this.#components.map((component) => component.toJSON()),
113
- };
114
- }
115
- }
116
- export class StringSelectBuilder {
117
- readonly #data: APIStringSelectComponent = {
118
- type: ComponentType.StringSelect,
119
- };
120
- public setCustomId(value: string): this {
121
- validateText(value, 100, "Component custom ID");
122
- this.#data.custom_id = value;
123
- return this;
124
- }
125
- public setPlaceholder(value: string): this {
126
- validateText(value, 150, "Component placeholder");
127
- this.#data.placeholder = value;
128
- return this;
129
- }
130
- public setMinValues(value: number): this {
131
- validateCount(value, "min_values", 0);
132
- this.#data.min_values = value;
133
- return this;
134
- }
135
- public setMaxValues(value: number): this {
136
- validateCount(value, "max_values", 1);
137
- this.#data.max_values = value;
138
- return this;
139
- }
140
- public setRequired(value = true): this {
141
- this.#data.required = value;
142
- return this;
143
- }
144
- public setDisabled(value = true): this {
145
- this.#data.disabled = value;
146
- return this;
147
- }
148
- public addOptions(...options: APISelectOption[]): this {
149
- if (!options.length)
150
- throw new TypeError("At least one select option is required.");
151
- const current = this.#data.options ?? [];
152
- if (current.length + options.length > 25)
153
- throw new RangeError(
154
- "A string select cannot contain more than 25 options.",
155
- );
156
- for (const option of options) {
157
- validateText(option.label, 100, "Select option label");
158
- validateText(option.value, 100, "Select option value");
159
- if (option.description)
160
- validateText(option.description, 100, "Select option description");
161
- }
162
- this.#data.options = [
163
- ...current,
164
- ...options.map((option) => ({ ...option })),
165
- ];
166
- return this;
167
- }
168
- public toJSON(): APIStringSelectComponent {
169
- return structuredClone(this.#data);
170
- }
171
- }
172
- export class ButtonBuilder {
173
- readonly #data: APIButtonComponent = {
174
- type: ComponentType.Button,
175
- style: ButtonStyle.Secondary,
176
- };
177
- public setStyle(style: APIButtonComponent["style"]): this {
178
- this.#data.style = style;
179
- if (style === ButtonStyle.Link) delete this.#data.custom_id;
180
- return this;
181
- }
182
- public setCustomId(value: string): this {
183
- validateText(value, 100, "Button custom ID");
184
- if (this.#data.style === ButtonStyle.Link)
185
- throw new TypeError("Link buttons cannot use custom IDs.");
186
- this.#data.custom_id = value;
187
- return this;
188
- }
189
- public setLabel(value: string): this {
190
- validateText(value, 80, "Button label");
191
- this.#data.label = value;
192
- return this;
193
- }
194
- public setURL(value: string): this {
195
- validateText(value, 512, "Button URL");
196
- let url: URL;
197
- try {
198
- url = new URL(value);
199
- } catch (error) {
200
- throw new TypeError("Button URL must be a valid URL.", { cause: error });
201
- }
202
- this.#data.style = ButtonStyle.Link;
203
- delete this.#data.custom_id;
204
- this.#data.url = url.toString();
205
- return this;
206
- }
207
- public setEmoji(emoji: APIComponentEmoji | string): this {
208
- this.#data.emoji =
209
- typeof emoji === "string" ? { name: emoji } : { ...emoji };
210
- return this;
211
- }
212
- public setDisabled(value = true): this {
213
- this.#data.disabled = value;
214
- return this;
215
- }
216
- public toJSON(): APIButtonComponent {
217
- return structuredClone(this.#data);
218
- }
219
- }
220
- export class EntitySelectBuilder {
221
- readonly #data: APIEntitySelectComponent;
222
- public constructor(
223
- type:
224
- | typeof ComponentType.UserSelect
225
- | typeof ComponentType.RoleSelect
226
- | typeof ComponentType.MentionableSelect
227
- | typeof ComponentType.ChannelSelect,
228
- ) {
229
- if (
230
- type !== ComponentType.UserSelect &&
231
- type !== ComponentType.RoleSelect &&
232
- type !== ComponentType.MentionableSelect &&
233
- type !== ComponentType.ChannelSelect
234
- )
235
- throw new TypeError("Invalid select component type.");
236
- this.#data = { type };
237
- }
238
- public setCustomId(value: string): this {
239
- validateText(value, 100, "Component custom ID");
240
- this.#data.custom_id = value;
241
- return this;
242
- }
243
- public setPlaceholder(value: string): this {
244
- validateText(value, 150, "Component placeholder");
245
- this.#data.placeholder = value;
246
- return this;
247
- }
248
- public setMinValues(value: number): this {
249
- validateCount(value, "min_values", 0);
250
- this.#data.min_values = value;
251
- return this;
252
- }
253
- public setMaxValues(value: number): this {
254
- validateCount(value, "max_values", 1);
255
- this.#data.max_values = value;
256
- return this;
257
- }
258
- public setRequired(value = true): this {
259
- this.#data.required = value;
260
- return this;
261
- }
262
- public setDisabled(value = true): this {
263
- this.#data.disabled = value;
264
- return this;
265
- }
266
- public setDefaultValues(
267
- ...values: { id: string; type: "user" | "role" | "channel" }[]
268
- ): this {
269
- (this.#data as any).default_values = values;
270
- return this;
271
- }
272
- public toJSON(): APIEntitySelectComponent {
273
- return structuredClone(this.#data);
274
- }
275
- }
276
- export class ModalBuilder {
277
- readonly #components: Array<{ toJSON(): APIActionRowComponent }> = [];
278
- #custom_id?: string;
279
- #title?: string;
280
- public setCustomId(value: string): this {
281
- validateText(value, 100, "Modal custom ID");
282
- this.#custom_id = value;
283
- return this;
284
- }
285
- public setTitle(value: string): this {
286
- validateText(value, 45, "Modal title");
287
- this.#title = value;
288
- return this;
289
- }
290
- public addComponents(
291
- ...components: Array<{ toJSON(): APIActionRowComponent }>
292
- ): this {
293
- if (!components.length)
294
- throw new TypeError("At least one modal component is required.");
295
- if (this.#components.length + components.length > 5)
296
- throw new RangeError("A modal cannot contain more than 5 action rows.");
297
- this.#components.push(...components);
298
- return this;
299
- }
300
- public toJSON(): APIModalComponent {
301
- return {
302
- type: 9,
303
- ...(this.#custom_id ? { custom_id: this.#custom_id } : {}),
304
- ...(this.#title ? { title: this.#title } : {}),
305
- components: this.#components.map((component) => component.toJSON()),
306
- };
307
- }
308
- }
309
- export class TextInputBuilder {
310
- readonly #data: APITextInputComponent = {
311
- type: ComponentType.TextInput,
312
- style: TextInputStyle.Short,
313
- };
314
- public setStyle(style: APITextInputComponent["style"]): this {
315
- this.#data.style = style;
316
- return this;
317
- }
318
- public setCustomId(value: string): this {
319
- validateText(value, 100, "Text input custom ID");
320
- this.#data.custom_id = value;
321
- return this;
322
- }
323
- public setLabel(value: string): this {
324
- validateText(value, 45, "Text input label");
325
- this.#data.label = value;
326
- return this;
327
- }
328
- public setPlaceholder(value: string): this {
329
- validateText(value, 100, "Text input placeholder");
330
- this.#data.placeholder = value;
331
- return this;
332
- }
333
- public setMinLength(value: number): this {
334
- validateLength(value, "min_length", 4000);
335
- this.#data.min_length = value;
336
- return this;
337
- }
338
- public setMaxLength(value: number): this {
339
- validateLength(value, "max_length", 4000);
340
- this.#data.max_length = value;
341
- return this;
342
- }
343
- public setRequired(value = true): this {
344
- this.#data.required = value;
345
- return this;
346
- }
347
- public setValue(value: string): this {
348
- validateText(value, 4000, "Text input value");
349
- this.#data.value = value;
350
- return this;
351
- }
352
- public toJSON(): APITextInputComponent {
353
- return structuredClone(this.#data);
354
- }
355
- }
356
- function validateText(value: string, max: number, field: string): void {
357
- if (typeof value !== "string")
358
- throw new TypeError(`${field} must be a string.`);
359
- if (!value.trim())
360
- throw new RangeError(`${field} must be a non-empty string.`);
361
- if (value.length > max)
362
- throw new RangeError(`${field} cannot exceed ${max} characters.`);
363
- }
364
- function validateCount(
365
- value: number,
366
- field: string,
367
- min: number,
368
- max = 25,
369
- ): void {
370
- if (!Number.isInteger(value) || value < min || value > max)
371
- throw new RangeError(
372
- `${field} must be an integer between ${min} and ${max}.`,
373
- );
374
- }
375
- function validateLength(value: number, field: string, max = 4000): void {
376
- if (!Number.isInteger(value) || value < 0 || value > max)
377
- throw new RangeError(`${field} must be an integer between 0 and ${max}.`);
378
- }
1
+ /** Component type constants exposed by Lunibee. */
2
+ 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,
26
+ } as const;
27
+ export const TextInputStyle = { Short: 1, Paragraph: 2 } as const;
28
+ export interface APIComponentEmoji {
29
+ id?: string | null;
30
+ name?: string | null;
31
+ animated?: boolean;
32
+ }
33
+ 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;
41
+ }
42
+ export interface APISelectOption {
43
+ label: string;
44
+ value: string;
45
+ description?: string;
46
+ emoji?: APIComponentEmoji;
47
+ default?: boolean;
48
+ }
49
+ 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[];
58
+ }
59
+ 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;
71
+ }
72
+ 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;
82
+ }
83
+ export interface APIModalComponent {
84
+ type: 9;
85
+ custom_id?: string;
86
+ title?: string;
87
+ components: APIActionRowComponent[];
88
+ }
89
+ export type APIActionRowChild =
90
+ | APIButtonComponent
91
+ | APIStringSelectComponent
92
+ | APIEntitySelectComponent
93
+ | APITextInputComponent;
94
+ export interface APIActionRowComponent {
95
+ type: typeof ComponentType.ActionRow;
96
+ components: APIActionRowChild[];
97
+ }
98
+ export interface APISectionComponent {
99
+ type: typeof ComponentType.Section;
100
+ components?: APIComponent[]; // text displays
101
+ accessory?: APIComponent; // buttons, thumbnails, etc.
102
+ }
103
+ export interface APITextDisplayComponent {
104
+ type: typeof ComponentType.TextDisplay;
105
+ content: string;
106
+ }
107
+ export interface APIMediaGalleryComponent {
108
+ type: typeof ComponentType.MediaGallery;
109
+ items: { media: { url: string; description?: string } }[];
110
+ }
111
+ export interface APIFileComponent {
112
+ type: typeof ComponentType.File;
113
+ file: { url: string };
114
+ spoiler?: boolean;
115
+ }
116
+ export interface APISeparatorComponent {
117
+ type: typeof ComponentType.Separator;
118
+ spacing?: 1 | 2;
119
+ divider?: boolean;
120
+ }
121
+ export interface APIThumbnailComponent {
122
+ type: typeof ComponentType.Thumbnail;
123
+ url: string;
124
+ proxy_url?: string;
125
+ width?: number;
126
+ height?: number;
127
+ }
128
+ export interface APIContentInventoryEntryComponent {
129
+ type: typeof ComponentType.ContentInventoryEntry;
130
+ id: string;
131
+ }
132
+ export interface APIContainerComponent {
133
+ type: typeof ComponentType.Container;
134
+ components: APIComponent[];
135
+ accent_color?: number;
136
+ }
137
+ 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;
151
+
152
+ export class ActionRowBuilder<
153
+ T extends { toJSON(): APIActionRowChild } = { toJSON(): APIActionRowChild },
154
+ > {
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
+ }
176
+ }
177
+ 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
+ }
232
+ }
233
+ 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
+ }
280
+ }
281
+ 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
+ }
336
+ }
337
+ 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
+ }
369
+ }
370
+ 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
+ }
416
+ }
417
+
418
+ 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
+ }
443
+ }
444
+
445
+ 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
+ }
467
+ }
468
+
469
+ 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
+ }
482
+ }
483
+
484
+ 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
+ }
502
+ }
503
+
504
+ 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
+ }
525
+ }
526
+
527
+ 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
+ }
546
+ }
547
+
548
+ 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
+ }
561
+ }
562
+
563
+ 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
+ }
576
+ }
577
+
578
+ 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.`);
585
+ }
586
+ function validateCount(
587
+ value: number,
588
+ field: string,
589
+ min: number,
590
+ max = 25,
591
+ ): 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
+ );
596
+ }
597
+ 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}.`);
600
+ }
package/src/index.ts CHANGED
@@ -1,29 +1,37 @@
1
- export { EmbedBuilder } from "./embed.js";
2
- export type { EmbedField } from "./embed.js";
3
- export {
4
- ButtonBuilder,
5
- ButtonStyle,
6
- ComponentType,
7
- ActionRowBuilder,
8
- StringSelectBuilder,
9
- EntitySelectBuilder,
10
- ModalBuilder,
11
- TextInputBuilder,
12
- TextInputStyle,
13
- } from "./components.js";
14
- export {
15
- SlashCommandBuilder,
16
- StringOptionBuilder,
17
- IntegerOptionBuilder,
18
- NumberOptionBuilder,
19
- BooleanOptionBuilder,
20
- UserOptionBuilder,
21
- ChannelOptionBuilder,
22
- RoleOptionBuilder,
23
- MentionableOptionBuilder,
24
- AttachmentOptionBuilder,
25
- SubcommandBuilder,
26
- SubcommandGroupBuilder,
27
- ApplicationCommandOptionType,
28
- } from "./commands.js";
29
- export { AttachmentBuilder, type AttachmentData } from "./attachment.js";
1
+ export { EmbedBuilder } from "./embed.js";
2
+ export type { EmbedField } from "./embed.js";
3
+ export {
4
+ ButtonBuilder,
5
+ ButtonStyle,
6
+ ComponentType,
7
+ ActionRowBuilder,
8
+ StringSelectBuilder,
9
+ EntitySelectBuilder,
10
+ ModalBuilder,
11
+ TextInputBuilder,
12
+ TextInputStyle,
13
+ ContainerBuilder,
14
+ SectionBuilder,
15
+ TextDisplayBuilder,
16
+ MediaGalleryBuilder,
17
+ FileComponentBuilder,
18
+ SeparatorBuilder,
19
+ ThumbnailBuilder,
20
+ ContentInventoryEntryBuilder,
21
+ } from "./components.js";
22
+ export {
23
+ SlashCommandBuilder,
24
+ StringOptionBuilder,
25
+ IntegerOptionBuilder,
26
+ NumberOptionBuilder,
27
+ BooleanOptionBuilder,
28
+ UserOptionBuilder,
29
+ ChannelOptionBuilder,
30
+ RoleOptionBuilder,
31
+ MentionableOptionBuilder,
32
+ AttachmentOptionBuilder,
33
+ SubcommandBuilder,
34
+ SubcommandGroupBuilder,
35
+ ApplicationCommandOptionType,
36
+ } from "./commands.js";
37
+ export { AttachmentBuilder, type AttachmentData } from "./attachment.js";