@lunibee/builders 0.1.5 → 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/package.json +10 -10
- package/src/attachment.ts +68 -67
- package/src/commands.ts +621 -519
- package/src/components.ts +643 -378
- package/src/embed.ts +195 -181
- package/src/index.ts +39 -29
package/src/components.ts
CHANGED
|
@@ -1,378 +1,643 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
this
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
this
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
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
|
+
|
|
6
|
+
/** Component type constants exposed by Lunibee. */
|
|
7
|
+
export const ComponentType = {
|
|
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,
|
|
24
|
+
} as const;
|
|
25
|
+
export const TextInputStyle = { Short: 1, Paragraph: 2 } as const;
|
|
26
|
+
export interface APIComponentEmoji {
|
|
27
|
+
id?: string | null;
|
|
28
|
+
name?: string | null;
|
|
29
|
+
animated?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface APIButtonComponent {
|
|
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;
|
|
39
|
+
}
|
|
40
|
+
export interface APISelectOption {
|
|
41
|
+
label: string;
|
|
42
|
+
value: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
emoji?: APIComponentEmoji;
|
|
45
|
+
default?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface APIStringSelectComponent {
|
|
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[];
|
|
56
|
+
}
|
|
57
|
+
export interface APIEntitySelectComponent {
|
|
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[];
|
|
70
|
+
}
|
|
71
|
+
export interface APITextInputComponent {
|
|
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;
|
|
81
|
+
}
|
|
82
|
+
export interface APIModalComponent {
|
|
83
|
+
type: 9;
|
|
84
|
+
custom_id?: string;
|
|
85
|
+
title?: string;
|
|
86
|
+
components: APIActionRowComponent[];
|
|
87
|
+
}
|
|
88
|
+
export type APIActionRowChild =
|
|
89
|
+
| APIButtonComponent
|
|
90
|
+
| APIStringSelectComponent
|
|
91
|
+
| APIEntitySelectComponent
|
|
92
|
+
| APITextInputComponent;
|
|
93
|
+
export interface APIActionRowComponent {
|
|
94
|
+
type: typeof ComponentType.ActionRow;
|
|
95
|
+
components: APIActionRowChild[];
|
|
96
|
+
}
|
|
97
|
+
export interface APISectionComponent {
|
|
98
|
+
type: typeof ComponentType.Section;
|
|
99
|
+
components?: APIComponent[]; // text displays
|
|
100
|
+
accessory?: APIComponent; // buttons, thumbnails, etc.
|
|
101
|
+
}
|
|
102
|
+
export interface APITextDisplayComponent {
|
|
103
|
+
type: typeof ComponentType.TextDisplay;
|
|
104
|
+
content: string;
|
|
105
|
+
}
|
|
106
|
+
export interface APIMediaGalleryComponent {
|
|
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";
|
|
114
|
+
}
|
|
115
|
+
export interface APIFileComponent {
|
|
116
|
+
type: typeof ComponentType.File;
|
|
117
|
+
file: { url: string };
|
|
118
|
+
spoiler?: boolean;
|
|
119
|
+
}
|
|
120
|
+
export interface APISeparatorComponent {
|
|
121
|
+
type: typeof ComponentType.Separator;
|
|
122
|
+
spacing?: 1 | 2;
|
|
123
|
+
divider?: boolean;
|
|
124
|
+
}
|
|
125
|
+
export interface APIThumbnailComponent {
|
|
126
|
+
type: typeof ComponentType.Thumbnail;
|
|
127
|
+
url: string;
|
|
128
|
+
proxy_url?: string;
|
|
129
|
+
width?: number;
|
|
130
|
+
height?: number;
|
|
131
|
+
description?: string;
|
|
132
|
+
spoiler?: boolean;
|
|
133
|
+
}
|
|
134
|
+
export interface APIContentInventoryEntryComponent {
|
|
135
|
+
type: typeof ComponentType.ContentInventoryEntry;
|
|
136
|
+
id: string;
|
|
137
|
+
}
|
|
138
|
+
export interface APIContainerComponent {
|
|
139
|
+
type: typeof ComponentType.Container;
|
|
140
|
+
components: APIComponent[];
|
|
141
|
+
accent_color?: number;
|
|
142
|
+
spoiler?: boolean;
|
|
143
|
+
}
|
|
144
|
+
export type APIComponent =
|
|
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;
|
|
158
|
+
|
|
159
|
+
export class ActionRowBuilder<
|
|
160
|
+
T extends { toJSON(): APIActionRowChild } = { toJSON(): APIActionRowChild },
|
|
161
|
+
> {
|
|
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
|
+
}
|
|
183
|
+
}
|
|
184
|
+
export class StringSelectBuilder {
|
|
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
|
+
}
|
|
243
|
+
}
|
|
244
|
+
export class ButtonBuilder {
|
|
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
|
+
}
|
|
293
|
+
}
|
|
294
|
+
export class EntitySelectBuilder {
|
|
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
|
+
}
|
|
347
|
+
}
|
|
348
|
+
export class ModalBuilder {
|
|
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
|
+
}
|
|
382
|
+
}
|
|
383
|
+
export class TextInputBuilder {
|
|
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
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export class ContainerBuilder {
|
|
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
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export class SectionBuilder {
|
|
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
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export class TextDisplayBuilder {
|
|
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
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export class MediaGalleryBuilder {
|
|
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
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
export class FileComponentBuilder {
|
|
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
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export class SeparatorBuilder {
|
|
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
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export class ThumbnailBuilder {
|
|
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
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export class ContentInventoryEntryBuilder {
|
|
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
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function validateText(value: string, max: number, field: string): void {
|
|
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.`);
|
|
626
|
+
}
|
|
627
|
+
function validateCount(
|
|
628
|
+
value: number,
|
|
629
|
+
field: string,
|
|
630
|
+
min: number,
|
|
631
|
+
max = 25,
|
|
632
|
+
): void {
|
|
633
|
+
if (!Number.isInteger(value) || value < min || value > max)
|
|
634
|
+
throw new RangeError(
|
|
635
|
+
`${field} must be an integer between ${min} and ${max}.`,
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
function validateLength(value: number, field: string, max = 4000): void {
|
|
639
|
+
if (!Number.isInteger(value) || value < 0 || value > max)
|
|
640
|
+
throw new RangeError(
|
|
641
|
+
`${field} must be an integer between 0 and ${max}.`,
|
|
642
|
+
);
|
|
643
|
+
}
|