@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/package.json +1 -1
- package/src/attachment.ts +68 -67
- package/src/commands.ts +621 -519
- package/src/components.ts +559 -516
- package/src/embed.ts +195 -181
- package/src/index.ts +32 -30
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
id?: string | null;
|
|
28
|
+
name?: string | null;
|
|
29
|
+
animated?: boolean;
|
|
32
30
|
}
|
|
33
31
|
export interface APIButtonComponent {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
label: string;
|
|
42
|
+
value: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
emoji?: APIComponentEmoji;
|
|
45
|
+
default?: boolean;
|
|
48
46
|
}
|
|
49
47
|
export interface APIStringSelectComponent {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
type: 9;
|
|
84
|
+
custom_id?: string;
|
|
85
|
+
title?: string;
|
|
86
|
+
components: APIActionRowComponent[];
|
|
88
87
|
}
|
|
89
88
|
export type APIActionRowChild =
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
| APIButtonComponent
|
|
90
|
+
| APIStringSelectComponent
|
|
91
|
+
| APIEntitySelectComponent
|
|
92
|
+
| APITextInputComponent;
|
|
94
93
|
export interface APIActionRowComponent {
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
type: typeof ComponentType.ActionRow;
|
|
95
|
+
components: APIActionRowChild[];
|
|
97
96
|
}
|
|
98
97
|
export interface APISectionComponent {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
type: typeof ComponentType.Section;
|
|
99
|
+
components?: APIComponent[]; // text displays
|
|
100
|
+
accessory?: APIComponent; // buttons, thumbnails, etc.
|
|
102
101
|
}
|
|
103
102
|
export interface APITextDisplayComponent {
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
type: typeof ComponentType.TextDisplay;
|
|
104
|
+
content: string;
|
|
106
105
|
}
|
|
107
106
|
export interface APIMediaGalleryComponent {
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
type: typeof ComponentType.File;
|
|
117
|
+
file: { url: string };
|
|
118
|
+
spoiler?: boolean;
|
|
115
119
|
}
|
|
116
120
|
export interface APISeparatorComponent {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
type: typeof ComponentType.Separator;
|
|
122
|
+
spacing?: 1 | 2;
|
|
123
|
+
divider?: boolean;
|
|
120
124
|
}
|
|
121
125
|
export interface APIThumbnailComponent {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
130
|
-
|
|
135
|
+
type: typeof ComponentType.ContentInventoryEntry;
|
|
136
|
+
id: string;
|
|
131
137
|
}
|
|
132
138
|
export interface APIContainerComponent {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
139
|
+
type: typeof ComponentType.Container;
|
|
140
|
+
components: APIComponent[];
|
|
141
|
+
accent_color?: number;
|
|
142
|
+
spoiler?: boolean;
|
|
136
143
|
}
|
|
137
144
|
export type APIComponent =
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
160
|
+
T extends { toJSON(): APIActionRowChild } = { toJSON(): APIActionRowChild },
|
|
154
161
|
> {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
628
|
+
value: number,
|
|
629
|
+
field: string,
|
|
630
|
+
min: number,
|
|
631
|
+
max = 25,
|
|
591
632
|
): void {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
-
|
|
599
|
-
|
|
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
|
}
|