@lunibee/builders 0.1.8 → 0.2.0
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/README.md +4 -0
- package/package.json +1 -1
- package/src/components.ts +99 -10
package/README.md
CHANGED
|
@@ -55,4 +55,8 @@ console.log(embed.toJSON(), buttons.toJSON(), menu.toJSON(), ping.toJSON());
|
|
|
55
55
|
|
|
56
56
|
Every builder validates Discord's limits as you set values and serialises with `toJSON()`.
|
|
57
57
|
|
|
58
|
+
## What's new in 0.2.0
|
|
59
|
+
|
|
60
|
+
`toJSON()` validates whole components (required fields, value bounds, action-row rules, button field exclusivity); `ButtonBuilder.setSKUId()` for premium buttons.
|
|
61
|
+
|
|
58
62
|
Docs: https://lunibee.js.org/core-concepts/builders/
|
package/package.json
CHANGED
package/src/components.ts
CHANGED
|
@@ -35,6 +35,8 @@ export interface APIButtonComponent {
|
|
|
35
35
|
label?: string;
|
|
36
36
|
emoji?: APIComponentEmoji;
|
|
37
37
|
url?: string;
|
|
38
|
+
/** SKU for premium (style 6) buttons. */
|
|
39
|
+
sku_id?: string;
|
|
38
40
|
disabled?: boolean;
|
|
39
41
|
}
|
|
40
42
|
export interface APISelectOption {
|
|
@@ -175,10 +177,20 @@ export class ActionRowBuilder<
|
|
|
175
177
|
return this;
|
|
176
178
|
}
|
|
177
179
|
public toJSON(): APIActionRowComponent {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
180
|
+
const components = this.#components.map((component) =>
|
|
181
|
+
component.toJSON(),
|
|
182
|
+
);
|
|
183
|
+
if (components.length === 0)
|
|
184
|
+
throw new RangeError("An action row needs at least one component.");
|
|
185
|
+
const types = components.map((component) => component.type);
|
|
186
|
+
if (
|
|
187
|
+
types.some((type) => type !== ComponentType.Button) &&
|
|
188
|
+
components.length > 1
|
|
189
|
+
)
|
|
190
|
+
throw new RangeError(
|
|
191
|
+
"A select menu or text input must be alone in its action row.",
|
|
192
|
+
);
|
|
193
|
+
return { type: ComponentType.ActionRow, components };
|
|
182
194
|
}
|
|
183
195
|
}
|
|
184
196
|
export class StringSelectBuilder {
|
|
@@ -238,6 +250,14 @@ export class StringSelectBuilder {
|
|
|
238
250
|
return this;
|
|
239
251
|
}
|
|
240
252
|
public toJSON(): APIStringSelectComponent {
|
|
253
|
+
assertSelect(this.#data);
|
|
254
|
+
const count = this.#data.options?.length ?? 0;
|
|
255
|
+
if (count === 0)
|
|
256
|
+
throw new RangeError("A string select needs at least one option.");
|
|
257
|
+
if ((this.#data.max_values ?? 1) > count)
|
|
258
|
+
throw new RangeError(
|
|
259
|
+
"max_values cannot exceed the number of options.",
|
|
260
|
+
);
|
|
241
261
|
return structuredClone(this.#data);
|
|
242
262
|
}
|
|
243
263
|
}
|
|
@@ -246,9 +266,22 @@ export class ButtonBuilder {
|
|
|
246
266
|
type: ComponentType.Button,
|
|
247
267
|
style: ButtonStyle.Secondary,
|
|
248
268
|
};
|
|
269
|
+
/** Sets the style and drops fields that style cannot carry. */
|
|
249
270
|
public setStyle(style: APIButtonComponent["style"]): this {
|
|
250
|
-
|
|
251
|
-
|
|
271
|
+
const data = this.#data;
|
|
272
|
+
data.style = style;
|
|
273
|
+
if (style === ButtonStyle.Link) {
|
|
274
|
+
delete data.custom_id;
|
|
275
|
+
delete data.sku_id;
|
|
276
|
+
} else if (style === ButtonStyle.Premium) {
|
|
277
|
+
delete data.custom_id;
|
|
278
|
+
delete data.url;
|
|
279
|
+
delete data.label;
|
|
280
|
+
delete data.emoji;
|
|
281
|
+
} else {
|
|
282
|
+
delete data.url;
|
|
283
|
+
delete data.sku_id;
|
|
284
|
+
}
|
|
252
285
|
return this;
|
|
253
286
|
}
|
|
254
287
|
public setCustomId(value: string): this {
|
|
@@ -273,11 +306,17 @@ export class ButtonBuilder {
|
|
|
273
306
|
cause: error,
|
|
274
307
|
});
|
|
275
308
|
}
|
|
276
|
-
this
|
|
277
|
-
delete this.#data.custom_id;
|
|
309
|
+
this.setStyle(ButtonStyle.Link);
|
|
278
310
|
this.#data.url = url.toString();
|
|
279
311
|
return this;
|
|
280
312
|
}
|
|
313
|
+
/** Makes this a premium (purchase) button for a SKU. Premium buttons carry no label, emoji, custom ID or URL. */
|
|
314
|
+
public setSKUId(skuId: string): this {
|
|
315
|
+
validateText(skuId, 20, "Button SKU ID");
|
|
316
|
+
this.setStyle(ButtonStyle.Premium);
|
|
317
|
+
this.#data.sku_id = skuId;
|
|
318
|
+
return this;
|
|
319
|
+
}
|
|
281
320
|
public setEmoji(emoji: APIComponentEmoji | string): this {
|
|
282
321
|
this.#data.emoji =
|
|
283
322
|
typeof emoji === "string" ? { name: emoji } : { ...emoji };
|
|
@@ -288,7 +327,31 @@ export class ButtonBuilder {
|
|
|
288
327
|
return this;
|
|
289
328
|
}
|
|
290
329
|
public toJSON(): APIButtonComponent {
|
|
291
|
-
|
|
330
|
+
const data = this.#data;
|
|
331
|
+
if (data.style === ButtonStyle.Link) {
|
|
332
|
+
if (!data.url) throw new TypeError("Link buttons require a URL.");
|
|
333
|
+
if (data.custom_id || data.sku_id)
|
|
334
|
+
throw new TypeError(
|
|
335
|
+
"Link buttons cannot have a custom ID or SKU ID.",
|
|
336
|
+
);
|
|
337
|
+
} else if (data.style === ButtonStyle.Premium) {
|
|
338
|
+
if (!data.sku_id)
|
|
339
|
+
throw new TypeError("Premium buttons require a SKU ID.");
|
|
340
|
+
if (data.custom_id || data.url || data.label || data.emoji)
|
|
341
|
+
throw new TypeError(
|
|
342
|
+
"Premium buttons cannot have a custom ID, URL, label or emoji.",
|
|
343
|
+
);
|
|
344
|
+
} else {
|
|
345
|
+
if (!data.custom_id)
|
|
346
|
+
throw new TypeError("Non-link buttons require a custom ID.");
|
|
347
|
+
if (data.url || data.sku_id)
|
|
348
|
+
throw new TypeError(
|
|
349
|
+
"Only link buttons have a URL and only premium buttons have a SKU ID.",
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
if (data.style !== ButtonStyle.Premium && !data.label && !data.emoji)
|
|
353
|
+
throw new TypeError("Buttons require a label or an emoji.");
|
|
354
|
+
return structuredClone(data);
|
|
292
355
|
}
|
|
293
356
|
}
|
|
294
357
|
export class EntitySelectBuilder {
|
|
@@ -342,6 +405,7 @@ export class EntitySelectBuilder {
|
|
|
342
405
|
return this;
|
|
343
406
|
}
|
|
344
407
|
public toJSON(): APIEntitySelectComponent {
|
|
408
|
+
assertSelect(this.#data);
|
|
345
409
|
return structuredClone(this.#data);
|
|
346
410
|
}
|
|
347
411
|
}
|
|
@@ -372,6 +436,10 @@ export class ModalBuilder {
|
|
|
372
436
|
return this;
|
|
373
437
|
}
|
|
374
438
|
public toJSON(): APIModalComponent {
|
|
439
|
+
if (!this.#custom_id || !this.#title)
|
|
440
|
+
throw new TypeError("Modals require a custom ID and a title.");
|
|
441
|
+
if (this.#components.length === 0)
|
|
442
|
+
throw new RangeError("Modals require at least one component.");
|
|
375
443
|
return {
|
|
376
444
|
type: 9,
|
|
377
445
|
...(this.#custom_id ? { custom_id: this.#custom_id } : {}),
|
|
@@ -424,7 +492,16 @@ export class TextInputBuilder {
|
|
|
424
492
|
return this;
|
|
425
493
|
}
|
|
426
494
|
public toJSON(): APITextInputComponent {
|
|
427
|
-
|
|
495
|
+
const data = this.#data;
|
|
496
|
+
if (!data.custom_id)
|
|
497
|
+
throw new TypeError("Text inputs require a custom ID.");
|
|
498
|
+
if (
|
|
499
|
+
data.min_length !== undefined &&
|
|
500
|
+
data.max_length !== undefined &&
|
|
501
|
+
data.min_length > data.max_length
|
|
502
|
+
)
|
|
503
|
+
throw new RangeError("min_length cannot exceed max_length.");
|
|
504
|
+
return structuredClone(data);
|
|
428
505
|
}
|
|
429
506
|
}
|
|
430
507
|
|
|
@@ -668,3 +745,15 @@ export class ChannelSelectMenuBuilder extends EntitySelectBuilder {
|
|
|
668
745
|
super(ComponentType.ChannelSelect);
|
|
669
746
|
}
|
|
670
747
|
}
|
|
748
|
+
|
|
749
|
+
/** Checks the fields every select menu needs. */
|
|
750
|
+
function assertSelect(data: {
|
|
751
|
+
custom_id?: string;
|
|
752
|
+
min_values?: number;
|
|
753
|
+
max_values?: number;
|
|
754
|
+
}): void {
|
|
755
|
+
if (!data.custom_id)
|
|
756
|
+
throw new TypeError("Select menus require a custom ID.");
|
|
757
|
+
if ((data.min_values ?? 1) > (data.max_values ?? 1))
|
|
758
|
+
throw new RangeError("min_values cannot exceed max_values.");
|
|
759
|
+
}
|