@lunibee/builders 0.2.3 → 0.3.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/src/commands.ts CHANGED
@@ -29,14 +29,14 @@ function validateContexts(contexts: number[]): void {
29
29
  /** Builds Discord application command payloads. */
30
30
  export class CreateSlashCommand {
31
31
  readonly #data: Record<string, unknown> = { type: 1 };
32
- /** Sets the command name. */ public setName(name: string): this {
32
+ /** Sets the command name. */
33
+ public setName(name: string): this {
33
34
  validateName(name, "Command name");
34
35
  this.#data.name = name;
35
36
  return this;
36
37
  }
37
- /** Sets the command description. */ public setDescription(
38
- description: string,
39
- ): this {
38
+ /** Sets the command description. */
39
+ public setDescription(description: string): this {
40
40
  validateText(description, "Command description", 100);
41
41
  this.#data.description = description;
42
42
  return this;
@@ -47,11 +47,6 @@ export class CreateSlashCommand {
47
47
  this.#data.contexts = [...contexts];
48
48
  return this;
49
49
  }
50
- /** @deprecated Discord deprecated `dm_permission`. Use {@link CreateSlashCommand.setContexts}: `setContexts(0)` for guilds only, `setContexts(0, 1)` to allow the bot's DMs. Removed in 0.3.0. */
51
- public setDMPermission(enabled: boolean): this {
52
- this.#data.dm_permission = enabled;
53
- return this;
54
- }
55
50
  /** Sets whether the command is NSFW. */
56
51
  public setNSFW(nsfw = true): this {
57
52
  this.#data.nsfw = nsfw;
@@ -61,110 +56,96 @@ export class CreateSlashCommand {
61
56
  public setNsfw(nsfw = true): this {
62
57
  return this.setNSFW(nsfw);
63
58
  }
64
- /** Sets command default member permissions. */ public setDefaultMemberPermissions(
59
+ /** Sets command default member permissions. */
60
+ public setDefaultMemberPermissions(
65
61
  permissions: bigint | number | string | null,
66
62
  ): this {
67
63
  this.#data.default_member_permissions =
68
64
  permissions === null ? null : BigInt(permissions).toString();
69
65
  return this;
70
66
  }
71
- /** Sets command integration types. */ public setIntegrationTypes(
72
- ...types: number[]
73
- ): this {
67
+ /** Sets command integration types. */
68
+ public setIntegrationTypes(...types: number[]): this {
74
69
  this.#data.integration_types = [...types];
75
70
  return this;
76
71
  }
77
- /** Adds a string option. */ public addStringOption(
72
+ /** Adds a string option. */
73
+ public addStringOption(
78
74
  configure: (option: CreateStringOption) => CreateStringOption,
79
75
  ): this {
80
76
  return this.addOption(configure(new CreateStringOption()));
81
77
  }
82
- /** Adds an integer option. */ public addIntegerOption(
78
+ /** Adds an integer option. */
79
+ public addIntegerOption(
83
80
  configure: (option: CreateIntegerOption) => CreateIntegerOption,
84
81
  ): this {
85
82
  return this.addOption(configure(new CreateIntegerOption()));
86
83
  }
87
- /** Adds a number option. */ public addNumberOption(
84
+ /** Adds a number option. */
85
+ public addNumberOption(
88
86
  configure: (option: CreateNumberOption) => CreateNumberOption,
89
87
  ): this {
90
88
  return this.addOption(configure(new CreateNumberOption()));
91
89
  }
92
- /** Adds a boolean option. */ public addBooleanOption(
90
+ /** Adds a boolean option. */
91
+ public addBooleanOption(
93
92
  configure: (option: CreateBooleanOption) => CreateBooleanOption,
94
93
  ): this {
95
94
  return this.addOption(configure(new CreateBooleanOption()));
96
95
  }
97
- /** Adds a user option. */ public addUserOption(
96
+ /** Adds a user option. */
97
+ public addUserOption(
98
98
  configure: (option: CreateUserOption) => CreateUserOption,
99
99
  ): this {
100
100
  return this.addOption(configure(new CreateUserOption()));
101
101
  }
102
- /** Adds a channel option. */ public addChannelOption(
102
+ /** Adds a channel option. */
103
+ public addChannelOption(
103
104
  configure: (option: CreateChannelOption) => CreateChannelOption,
104
105
  ): this {
105
106
  return this.addOption(configure(new CreateChannelOption()));
106
107
  }
107
- /** Adds a role option. */ public addRoleOption(
108
+ /** Adds a role option. */
109
+ public addRoleOption(
108
110
  configure: (option: CreateRoleOption) => CreateRoleOption,
109
111
  ): this {
110
112
  return this.addOption(configure(new CreateRoleOption()));
111
113
  }
112
- /** Adds a mentionable option. */ public addMentionableOption(
114
+ /** Adds a mentionable option. */
115
+ public addMentionableOption(
113
116
  configure: (option: CreateMentionableOption) => CreateMentionableOption,
114
117
  ): this {
115
118
  return this.addOption(configure(new CreateMentionableOption()));
116
119
  }
117
- /** Adds an attachment option. */ public addAttachmentOption(
120
+ /** Adds an attachment option. */
121
+ public addAttachmentOption(
118
122
  configure: (option: CreateAttachmentOption) => CreateAttachmentOption,
119
123
  ): this {
120
124
  return this.addOption(configure(new CreateAttachmentOption()));
121
125
  }
122
- /** Adds a subcommand. */ public addSubcommand(
126
+ /** Adds a subcommand. */
127
+ public addSubcommand(
123
128
  configure: (option: CreateSubcommand) => CreateSubcommand,
124
129
  ): this {
125
130
  return this.addOption(configure(new CreateSubcommand()));
126
131
  }
127
- /** Adds a subcommand group. */ public addSubcommandGroup(
132
+ /** Adds a subcommand group. */
133
+ public addSubcommandGroup(
128
134
  configure: (option: CreateSubcommandGroup) => CreateSubcommandGroup,
129
135
  ): this {
130
136
  return this.addOption(configure(new CreateSubcommandGroup()));
131
137
  }
132
- /** Serializes the command payload. */ public toJSON(): Record<
133
- string,
134
- unknown
135
- > {
138
+ /** Serializes the command payload. */
139
+ public toJSON(): Record<string, unknown> {
136
140
  return structuredClone(this.#data);
137
141
  }
138
- /** Adds a validated top-level command option. */ protected addOption(
139
- option: CreateCommandOption,
140
- ): this {
141
- const options = (this.#data.options as unknown[] | undefined) ?? [];
142
- if (options.length >= 25)
143
- throw new RangeError(
144
- "An application command cannot contain more than 25 options.",
145
- );
146
- const payload = option.toJSON();
147
- if (
148
- options.some(
149
- (existing) =>
150
- (existing as Record<string, unknown>).name === payload.name,
151
- )
152
- )
153
- throw new RangeError(
154
- `Duplicate option name: ${String(payload.name)}.`,
155
- );
156
- if (
157
- payload.required === true &&
158
- options.some(
159
- (existing) =>
160
- (existing as Record<string, unknown>).required !== true,
161
- )
162
- )
163
- throw new RangeError(
164
- "Required application command options must be placed before optional options.",
165
- );
166
- options.push(payload);
167
- this.#data.options = options;
142
+ /** Adds a validated top-level command option. */
143
+ protected addOption(option: CreateCommandOption): this {
144
+ this.#data.options = appendOption(
145
+ this.#data.options,
146
+ option.toJSON(),
147
+ "application command",
148
+ );
168
149
  return this;
169
150
  }
170
151
  }
@@ -172,76 +153,61 @@ export class CreateSlashCommand {
172
153
  /** Base builder for command options. */
173
154
  export class CreateCommandOption {
174
155
  protected readonly data: Record<string, unknown>;
175
- /** Creates an option builder. */ public constructor(type: number) {
156
+ /** Creates an option builder. */
157
+ public constructor(type: number) {
176
158
  this.data = { type };
177
159
  }
178
- /** Sets the option name. */ public setName(name: string): this {
160
+ /** Sets the option name. */
161
+ public setName(name: string): this {
179
162
  validateName(name, "Option name");
180
163
  this.data.name = name;
181
164
  return this;
182
165
  }
183
- /** Sets the option description. */ public setDescription(
184
- description: string,
185
- ): this {
166
+ /** Sets the option description. */
167
+ public setDescription(description: string): this {
186
168
  validateText(description, "Option description", 100);
187
169
  this.data.description = description;
188
170
  return this;
189
171
  }
190
- /** Makes the option required or optional. */ public setRequired(
191
- required = true,
192
- ): this {
172
+ /** Makes the option required or optional. */
173
+ public setRequired(required = true): this {
193
174
  this.data.required = required;
194
175
  return this;
195
176
  }
196
- /** Sets autocomplete. */ public setAutocomplete(enabled = true): this {
177
+ /** Sets autocomplete. */
178
+ public setAutocomplete(enabled = true): this {
197
179
  this.data.autocomplete = enabled;
198
180
  return this;
199
181
  }
200
- /** Serializes the option payload. */ public toJSON(): Record<
201
- string,
202
- unknown
203
- > {
182
+ /** Serializes the option payload. */
183
+ public toJSON(): Record<string, unknown> {
204
184
  return structuredClone(this.data);
205
185
  }
206
186
  }
207
187
 
208
188
  /** Builds a string command option. */
209
189
  export class CreateStringOption extends CreateCommandOption {
210
- /** Creates a string option. */ public constructor() {
190
+ /** Creates a string option. */
191
+ public constructor() {
211
192
  super(ApplicationCommandOptionEnum.String);
212
193
  }
213
- /** Adds string choices. */ public addChoices(
194
+ /** Adds string choices. */
195
+ public addChoices(
214
196
  ...choices: Array<{ name: string; value: string }>
215
197
  ): this {
216
- const current = (this.data.choices as unknown[] | undefined) ?? [];
217
- if (!choices.length)
218
- throw new TypeError("At least one choice is required.");
219
- if (this.data.autocomplete === true)
220
- throw new RangeError("Autocomplete options cannot define choices.");
221
- if (current.length + choices.length > 25)
222
- throw new RangeError(
223
- "An option cannot contain more than 25 choices.",
224
- );
225
- for (const choice of choices) {
226
- validateText(choice.name, "Choice name", 100);
198
+ for (const choice of choices)
227
199
  validateText(choice.value, "Choice value", 100);
228
- }
229
- this.data.choices = [
230
- ...current,
231
- ...choices.map((choice) => ({ ...choice })),
232
- ];
200
+ appendChoices(this.data, choices);
233
201
  return this;
234
202
  }
235
- /** Sets the minimum string length. */ public setMinLength(
236
- value: number,
237
- ): this {
203
+ /** Sets the minimum string length. */
204
+ public setMinLength(value: number): this {
238
205
  validateIntegerRange(value, 0, 6000, "min_length");
239
206
  this.data.min_length = value;
240
207
  return this;
241
208
  }
242
- /** Sets the maximum string length. */ public setMaxLength(
243
- value: number,
244
- ): this {
209
+ /** Sets the maximum string length. */
210
+ public setMaxLength(value: number): this {
245
211
  validateIntegerRange(value, 1, 6000, "max_length");
246
212
  if (
247
213
  (this.data.min_length as number | undefined) !== undefined &&
@@ -252,38 +218,42 @@ export class CreateStringOption extends CreateCommandOption {
252
218
  return this;
253
219
  }
254
220
  }
255
- /** Builds an integer command option. */
256
- export class CreateIntegerOption extends CreateCommandOption {
257
- public constructor() {
258
- super(ApplicationCommandOptionEnum.Integer);
259
- }
221
+ /** Shared bounds, choices and autocomplete rules of integer and number options. */
222
+ export class CreateNumericOption extends CreateCommandOption {
223
+ readonly #integer: boolean;
224
+ /** Creates a numeric option; `integer` rejects fractional bounds. */
225
+ public constructor(type: number, integer: boolean) {
226
+ super(type);
227
+ this.#integer = integer;
228
+ }
229
+ /** Sets the minimum value. */
260
230
  public setMinValue(value: number): this {
261
231
  validateNumberRange(
262
232
  value,
263
- -9_007_199_254_740_991,
264
- 9_007_199_254_740_991,
233
+ -MAX_SAFE,
234
+ MAX_SAFE,
265
235
  "min_value",
266
- true,
236
+ this.#integer,
267
237
  );
268
238
  this.data.min_value = value;
269
239
  return this;
270
240
  }
241
+ /** Sets the maximum value; it cannot be below the minimum. */
271
242
  public setMaxValue(value: number): this {
272
243
  validateNumberRange(
273
244
  value,
274
- -9_007_199_254_740_991,
275
- 9_007_199_254_740_991,
245
+ -MAX_SAFE,
246
+ MAX_SAFE,
276
247
  "max_value",
277
- true,
248
+ this.#integer,
278
249
  );
279
- if (
280
- (this.data.min_value as number | undefined) !== undefined &&
281
- (this.data.min_value as number) > value
282
- )
250
+ const min = this.data.min_value as number | undefined;
251
+ if (min !== undefined && min > value)
283
252
  throw new RangeError("min_value cannot exceed max_value.");
284
253
  this.data.max_value = value;
285
254
  return this;
286
255
  }
256
+ /** Enables autocomplete; options with choices cannot use it. */
287
257
  public override setAutocomplete(enabled = true): this {
288
258
  if (
289
259
  enabled &&
@@ -294,108 +264,67 @@ export class CreateIntegerOption extends CreateCommandOption {
294
264
  this.data.autocomplete = enabled;
295
265
  return this;
296
266
  }
267
+ /** Adds fixed choices. */
297
268
  public addChoices(...choices: { name: string; value: number }[]): this {
298
- const current = (this.data.choices as unknown[] | undefined) ?? [];
299
- if (!choices.length)
300
- throw new TypeError("At least one choice is required.");
301
- if (this.data.autocomplete === true)
302
- throw new RangeError("Autocomplete options cannot define choices.");
303
- if (current.length + choices.length > 25)
304
- throw new RangeError(
305
- "An option cannot contain more than 25 choices.",
306
- );
307
- this.data.choices = [...current, ...choices];
269
+ appendChoices(this.data, choices);
308
270
  return this;
309
271
  }
310
272
  }
311
- /** Builds a number command option. */
312
- export class CreateNumberOption extends CreateCommandOption {
273
+ /** Builds an integer command option. */
274
+ export class CreateIntegerOption extends CreateNumericOption {
313
275
  public constructor() {
314
- super(ApplicationCommandOptionEnum.Number);
315
- }
316
- public setMinValue(value: number): this {
317
- validateNumberRange(
318
- value,
319
- -9_007_199_254_740_991,
320
- 9_007_199_254_740_991,
321
- "min_value",
322
- false,
323
- );
324
- this.data.min_value = value;
325
- return this;
276
+ super(ApplicationCommandOptionEnum.Integer, true);
326
277
  }
327
- public setMaxValue(value: number): this {
328
- validateNumberRange(
329
- value,
330
- -9_007_199_254_740_991,
331
- 9_007_199_254_740_991,
332
- "max_value",
333
- false,
334
- );
335
- if (
336
- (this.data.min_value as number | undefined) !== undefined &&
337
- (this.data.min_value as number) > value
338
- )
339
- throw new RangeError("min_value cannot exceed max_value.");
340
- this.data.max_value = value;
341
- return this;
342
- }
343
- public override setAutocomplete(enabled = true): this {
344
- if (
345
- enabled &&
346
- Array.isArray(this.data.choices) &&
347
- this.data.choices.length
348
- )
349
- throw new RangeError("Autocomplete options cannot define choices.");
350
- this.data.autocomplete = enabled;
351
- return this;
352
- }
353
- public addChoices(...choices: { name: string; value: number }[]): this {
354
- const current = (this.data.choices as unknown[] | undefined) ?? [];
355
- if (!choices.length)
356
- throw new TypeError("At least one choice is required.");
357
- if (this.data.autocomplete === true)
358
- throw new RangeError("Autocomplete options cannot define choices.");
359
- if (current.length + choices.length > 25)
360
- throw new RangeError(
361
- "An option cannot contain more than 25 choices.",
362
- );
363
- this.data.choices = [...current, ...choices];
364
- return this;
278
+ }
279
+ /** Builds a number command option. */
280
+ export class CreateNumberOption extends CreateNumericOption {
281
+ public constructor() {
282
+ super(ApplicationCommandOptionEnum.Number, false);
365
283
  }
366
284
  }
367
- /** Builds a boolean command option. */ export class CreateBooleanOption extends CreateCommandOption {
368
- /** Creates a boolean option. */ public constructor() {
285
+ /** Builds a boolean command option. */
286
+ export class CreateBooleanOption extends CreateCommandOption {
287
+ /** Creates a boolean option. */
288
+ public constructor() {
369
289
  super(ApplicationCommandOptionEnum.Boolean);
370
290
  }
371
291
  }
372
- /** Builds a user command option. */ export class CreateUserOption extends CreateCommandOption {
373
- /** Creates a user option. */ public constructor() {
292
+ /** Builds a user command option. */
293
+ export class CreateUserOption extends CreateCommandOption {
294
+ /** Creates a user option. */
295
+ public constructor() {
374
296
  super(ApplicationCommandOptionEnum.User);
375
297
  }
376
298
  }
377
- /** Builds a role command option. */ export class CreateRoleOption extends CreateCommandOption {
378
- /** Creates a role option. */ public constructor() {
299
+ /** Builds a role command option. */
300
+ export class CreateRoleOption extends CreateCommandOption {
301
+ /** Creates a role option. */
302
+ public constructor() {
379
303
  super(ApplicationCommandOptionEnum.Role);
380
304
  }
381
305
  }
382
- /** Builds a mentionable command option. */ export class CreateMentionableOption extends CreateCommandOption {
383
- /** Creates a mentionable option. */ public constructor() {
306
+ /** Builds a mentionable command option. */
307
+ export class CreateMentionableOption extends CreateCommandOption {
308
+ /** Creates a mentionable option. */
309
+ public constructor() {
384
310
  super(ApplicationCommandOptionEnum.Mentionable);
385
311
  }
386
312
  }
387
- /** Builds an attachment command option. */ export class CreateAttachmentOption extends CreateCommandOption {
388
- /** Creates an attachment option. */ public constructor() {
313
+ /** Builds an attachment command option. */
314
+ export class CreateAttachmentOption extends CreateCommandOption {
315
+ /** Creates an attachment option. */
316
+ public constructor() {
389
317
  super(ApplicationCommandOptionEnum.Attachment);
390
318
  }
391
319
  }
392
- /** Builds a channel command option. */ export class CreateChannelOption extends CreateCommandOption {
393
- /** Creates a channel option. */ public constructor() {
320
+ /** Builds a channel command option. */
321
+ export class CreateChannelOption extends CreateCommandOption {
322
+ /** Creates a channel option. */
323
+ public constructor() {
394
324
  super(ApplicationCommandOptionEnum.Channel);
395
325
  }
396
- /** Restricts accepted channel types. */ public addChannelTypes(
397
- ...types: number[]
398
- ): this {
326
+ /** Restricts accepted channel types. */
327
+ public addChannelTypes(...types: number[]): this {
399
328
  if (types.some((type) => !Number.isInteger(type) || type < 0))
400
329
  throw new RangeError(
401
330
  "Channel types must be non-negative integers.",
@@ -404,101 +333,94 @@ export class CreateNumberOption extends CreateCommandOption {
404
333
  return this;
405
334
  }
406
335
  }
407
- /** Builds a nested subcommand. */ export class CreateSubcommand extends CreateCommandOption {
408
- /** Creates a subcommand. */ public constructor() {
336
+ /** Builds a nested subcommand. */
337
+ export class CreateSubcommand extends CreateCommandOption {
338
+ /** Creates a subcommand. */
339
+ public constructor() {
409
340
  super(ApplicationCommandOptionEnum.Subcommand);
410
341
  }
411
- /** Adds a string option to this subcommand. */ public addStringOption(
342
+ /** Adds a string option to this subcommand. */
343
+ public addStringOption(
412
344
  configure: (option: CreateStringOption) => CreateStringOption,
413
345
  ): this {
414
346
  return this.addChildOption(configure(new CreateStringOption()));
415
347
  }
416
- /** Adds an integer option to this subcommand. */ public addIntegerOption(
348
+ /** Adds an integer option to this subcommand. */
349
+ public addIntegerOption(
417
350
  configure: (option: CreateIntegerOption) => CreateIntegerOption,
418
351
  ): this {
419
352
  return this.addChildOption(configure(new CreateIntegerOption()));
420
353
  }
421
- /** Adds a number option to this subcommand. */ public addNumberOption(
354
+ /** Adds a number option to this subcommand. */
355
+ public addNumberOption(
422
356
  configure: (option: CreateNumberOption) => CreateNumberOption,
423
357
  ): this {
424
358
  return this.addChildOption(configure(new CreateNumberOption()));
425
359
  }
426
- /** Adds a boolean option to this subcommand. */ public addBooleanOption(
360
+ /** Adds a boolean option to this subcommand. */
361
+ public addBooleanOption(
427
362
  configure: (option: CreateBooleanOption) => CreateBooleanOption,
428
363
  ): this {
429
364
  return this.addChildOption(configure(new CreateBooleanOption()));
430
365
  }
431
- /** Adds a user option to this subcommand. */ public addUserOption(
366
+ /** Adds a user option to this subcommand. */
367
+ public addUserOption(
432
368
  configure: (option: CreateUserOption) => CreateUserOption,
433
369
  ): this {
434
370
  return this.addChildOption(configure(new CreateUserOption()));
435
371
  }
436
- /** Adds a channel option to this subcommand. */ public addChannelOption(
372
+ /** Adds a channel option to this subcommand. */
373
+ public addChannelOption(
437
374
  configure: (option: CreateChannelOption) => CreateChannelOption,
438
375
  ): this {
439
376
  return this.addChildOption(configure(new CreateChannelOption()));
440
377
  }
441
- /** Adds a role option to this subcommand. */ public addRoleOption(
378
+ /** Adds a role option to this subcommand. */
379
+ public addRoleOption(
442
380
  configure: (option: CreateRoleOption) => CreateRoleOption,
443
381
  ): this {
444
382
  return this.addChildOption(configure(new CreateRoleOption()));
445
383
  }
446
- /** Adds a mentionable option to this subcommand. */ public addMentionableOption(
384
+ /** Adds a mentionable option to this subcommand. */
385
+ public addMentionableOption(
447
386
  configure: (option: CreateMentionableOption) => CreateMentionableOption,
448
387
  ): this {
449
388
  return this.addChildOption(configure(new CreateMentionableOption()));
450
389
  }
451
- /** Adds an attachment option to this subcommand. */ public addAttachmentOption(
390
+ /** Adds an attachment option to this subcommand. */
391
+ public addAttachmentOption(
452
392
  configure: (option: CreateAttachmentOption) => CreateAttachmentOption,
453
393
  ): this {
454
394
  return this.addChildOption(configure(new CreateAttachmentOption()));
455
395
  }
456
396
  protected addChildOption(option: CreateCommandOption): this {
457
- const options = (this.data.options as unknown[] | undefined) ?? [];
458
- if (options.length >= 25)
459
- throw new RangeError(
460
- "A subcommand cannot contain more than 25 options.",
461
- );
462
- const payload = option.toJSON();
397
+ const options =
398
+ (this.data.options as OptionPayload[] | undefined) ?? [];
463
399
  if (
464
400
  options.some(
465
401
  (existing) =>
466
- (existing as Record<string, unknown>).name === payload.name,
467
- )
468
- )
469
- throw new RangeError(
470
- `Duplicate option name: ${String(payload.name)}.`,
471
- );
472
- if (
473
- payload.required === true &&
474
- options.some(
475
- (existing) =>
476
- (existing as Record<string, unknown>).required !== true,
477
- )
478
- )
479
- throw new RangeError(
480
- "Required subcommand options must be placed before optional options.",
481
- );
482
- if (
483
- options.some(
484
- (existing) =>
485
- (existing as Record<string, unknown>).type ===
486
- ApplicationCommandOptionEnum.Subcommand,
402
+ existing.type === ApplicationCommandOptionEnum.Subcommand,
487
403
  )
488
404
  )
489
405
  throw new RangeError(
490
406
  "Subcommands cannot contain nested subcommands.",
491
407
  );
492
- options.push(payload);
493
- this.data.options = options;
408
+ this.data.options = appendOption(
409
+ options,
410
+ option.toJSON(),
411
+ "subcommand",
412
+ );
494
413
  return this;
495
414
  }
496
415
  }
497
- /** Builds a nested subcommand group. */ export class CreateSubcommandGroup extends CreateCommandOption {
498
- /** Creates a subcommand group. */ public constructor() {
416
+ /** Builds a nested subcommand group. */
417
+ export class CreateSubcommandGroup extends CreateCommandOption {
418
+ /** Creates a subcommand group. */
419
+ public constructor() {
499
420
  super(ApplicationCommandOptionEnum.SubcommandGroup);
500
421
  }
501
- /** Adds a subcommand to this group. */ public addSubcommand(
422
+ /** Adds a subcommand to this group. */
423
+ public addSubcommand(
502
424
  configure: (option: CreateSubcommand) => CreateSubcommand,
503
425
  ): this {
504
426
  const options = (this.data.options as unknown[] | undefined) ?? [];
@@ -525,6 +447,53 @@ export class CreateNumberOption extends CreateCommandOption {
525
447
  return this;
526
448
  }
527
449
  }
450
+ const MAX_SAFE = Number.MAX_SAFE_INTEGER;
451
+
452
+ /** A serialized option as stored on its parent. */
453
+ type OptionPayload = Record<string, unknown>;
454
+
455
+ /**
456
+ * Returns `options` with `payload` appended, enforcing Discord's limit of 25,
457
+ * unique names, and required options before optional ones.
458
+ */
459
+ function appendOption(
460
+ options: unknown,
461
+ payload: OptionPayload,
462
+ parent: "application command" | "subcommand",
463
+ ): OptionPayload[] {
464
+ const list = (options as OptionPayload[] | undefined) ?? [];
465
+ if (list.length >= 25)
466
+ throw new RangeError(
467
+ `${parent === "subcommand" ? "A subcommand" : "An application command"} cannot contain more than 25 options.`,
468
+ );
469
+ if (list.some((existing) => existing.name === payload.name))
470
+ throw new RangeError(`Duplicate option name: ${String(payload.name)}.`);
471
+ if (
472
+ payload.required === true &&
473
+ list.some((existing) => existing.required !== true)
474
+ )
475
+ throw new RangeError(
476
+ `Required ${parent} options must be placed before optional options.`,
477
+ );
478
+ return [...list, payload];
479
+ }
480
+
481
+ /** Appends choices to an option, enforcing the shared choice rules. */
482
+ function appendChoices(
483
+ data: OptionPayload,
484
+ choices: { name: string; value: string | number }[],
485
+ ): void {
486
+ const current = (data.choices as unknown[] | undefined) ?? [];
487
+ if (!choices.length)
488
+ throw new TypeError("At least one choice is required.");
489
+ if (data.autocomplete === true)
490
+ throw new RangeError("Autocomplete options cannot define choices.");
491
+ if (current.length + choices.length > 25)
492
+ throw new RangeError("An option cannot contain more than 25 choices.");
493
+ for (const choice of choices) validateText(choice.name, "Choice name", 100);
494
+ data.choices = [...current, ...choices.map((choice) => ({ ...choice }))];
495
+ }
496
+
528
497
  function validateName(value: string, field: string): void {
529
498
  if (!/^[\p{L}\p{N}_-]{1,32}$/u.test(value) || value !== value.toLowerCase())
530
499
  throw new RangeError(
@@ -606,11 +575,6 @@ export class CreateContextMenuCommand {
606
575
  permissions === null ? null : BigInt(permissions).toString();
607
576
  return this;
608
577
  }
609
- /** @deprecated Discord deprecated `dm_permission`. Use {@link CreateContextMenuCommand.setContexts}: `setContexts(0)` for guilds only, `setContexts(0, 1)` to allow the bot's DMs. Removed in 0.3.0. */
610
- public setDMPermission(enabled: boolean): this {
611
- this.data.dm_permission = enabled;
612
- return this;
613
- }
614
578
  /** Sets command integration types (0 guild install, 1 user install). */
615
579
  public setIntegrationTypes(...types: number[]): this {
616
580
  this.data.integration_types = [...types];
@@ -655,92 +619,3 @@ export class CreateMessageCommand extends CreateContextMenuCommand {
655
619
  super(3);
656
620
  }
657
621
  }
658
-
659
- // ─── Deprecated names (0.2.2), removed in 0.3.0 ────────────────────────────────
660
- // Builders are now `CreateX` (`ButtonBuilder` → `CreateButton`); `…Style` became
661
- // `…Type` and `…Type` became `…Enum`.
662
-
663
- /** @deprecated Use {@link CreateAttachmentOption}. Removed in 0.3.0. */
664
- export const AttachmentOptionBuilder = CreateAttachmentOption;
665
- /** @deprecated Use {@link CreateAttachmentOption}. Removed in 0.3.0. */
666
- export type AttachmentOptionBuilder = CreateAttachmentOption;
667
-
668
- /** @deprecated Use {@link CreateBooleanOption}. Removed in 0.3.0. */
669
- export const BooleanOptionBuilder = CreateBooleanOption;
670
- /** @deprecated Use {@link CreateBooleanOption}. Removed in 0.3.0. */
671
- export type BooleanOptionBuilder = CreateBooleanOption;
672
-
673
- /** @deprecated Use {@link CreateChannelOption}. Removed in 0.3.0. */
674
- export const ChannelOptionBuilder = CreateChannelOption;
675
- /** @deprecated Use {@link CreateChannelOption}. Removed in 0.3.0. */
676
- export type ChannelOptionBuilder = CreateChannelOption;
677
-
678
- /** @deprecated Use {@link CreateCommandOption}. Removed in 0.3.0. */
679
- export const CommandOptionBuilder = CreateCommandOption;
680
- /** @deprecated Use {@link CreateCommandOption}. Removed in 0.3.0. */
681
- export type CommandOptionBuilder = CreateCommandOption;
682
-
683
- /** @deprecated Use {@link CreateContextMenuCommand}. Removed in 0.3.0. */
684
- export const ContextMenuCommandBuilder = CreateContextMenuCommand;
685
- /** @deprecated Use {@link CreateContextMenuCommand}. Removed in 0.3.0. */
686
- export type ContextMenuCommandBuilder = CreateContextMenuCommand;
687
-
688
- /** @deprecated Use {@link CreateIntegerOption}. Removed in 0.3.0. */
689
- export const IntegerOptionBuilder = CreateIntegerOption;
690
- /** @deprecated Use {@link CreateIntegerOption}. Removed in 0.3.0. */
691
- export type IntegerOptionBuilder = CreateIntegerOption;
692
-
693
- /** @deprecated Use {@link CreateMentionableOption}. Removed in 0.3.0. */
694
- export const MentionableOptionBuilder = CreateMentionableOption;
695
- /** @deprecated Use {@link CreateMentionableOption}. Removed in 0.3.0. */
696
- export type MentionableOptionBuilder = CreateMentionableOption;
697
-
698
- /** @deprecated Use {@link CreateMessageCommand}. Removed in 0.3.0. */
699
- export const MessageCommandBuilder = CreateMessageCommand;
700
- /** @deprecated Use {@link CreateMessageCommand}. Removed in 0.3.0. */
701
- export type MessageCommandBuilder = CreateMessageCommand;
702
-
703
- /** @deprecated Use {@link CreateNumberOption}. Removed in 0.3.0. */
704
- export const NumberOptionBuilder = CreateNumberOption;
705
- /** @deprecated Use {@link CreateNumberOption}. Removed in 0.3.0. */
706
- export type NumberOptionBuilder = CreateNumberOption;
707
-
708
- /** @deprecated Use {@link CreateRoleOption}. Removed in 0.3.0. */
709
- export const RoleOptionBuilder = CreateRoleOption;
710
- /** @deprecated Use {@link CreateRoleOption}. Removed in 0.3.0. */
711
- export type RoleOptionBuilder = CreateRoleOption;
712
-
713
- /** @deprecated Use {@link CreateSlashCommand}. Removed in 0.3.0. */
714
- export const SlashCommandBuilder = CreateSlashCommand;
715
- /** @deprecated Use {@link CreateSlashCommand}. Removed in 0.3.0. */
716
- export type SlashCommandBuilder = CreateSlashCommand;
717
-
718
- /** @deprecated Use {@link CreateStringOption}. Removed in 0.3.0. */
719
- export const StringOptionBuilder = CreateStringOption;
720
- /** @deprecated Use {@link CreateStringOption}. Removed in 0.3.0. */
721
- export type StringOptionBuilder = CreateStringOption;
722
-
723
- /** @deprecated Use {@link CreateSubcommand}. Removed in 0.3.0. */
724
- export const SubcommandBuilder = CreateSubcommand;
725
- /** @deprecated Use {@link CreateSubcommand}. Removed in 0.3.0. */
726
- export type SubcommandBuilder = CreateSubcommand;
727
-
728
- /** @deprecated Use {@link CreateSubcommandGroup}. Removed in 0.3.0. */
729
- export const SubcommandGroupBuilder = CreateSubcommandGroup;
730
- /** @deprecated Use {@link CreateSubcommandGroup}. Removed in 0.3.0. */
731
- export type SubcommandGroupBuilder = CreateSubcommandGroup;
732
-
733
- /** @deprecated Use {@link CreateUserCommand}. Removed in 0.3.0. */
734
- export const UserCommandBuilder = CreateUserCommand;
735
- /** @deprecated Use {@link CreateUserCommand}. Removed in 0.3.0. */
736
- export type UserCommandBuilder = CreateUserCommand;
737
-
738
- /** @deprecated Use {@link CreateUserOption}. Removed in 0.3.0. */
739
- export const UserOptionBuilder = CreateUserOption;
740
- /** @deprecated Use {@link CreateUserOption}. Removed in 0.3.0. */
741
- export type UserOptionBuilder = CreateUserOption;
742
-
743
- /** @deprecated Use {@link ApplicationCommandOptionEnum}. Removed in 0.3.0. */
744
- export const ApplicationCommandOptionType = ApplicationCommandOptionEnum;
745
- /** @deprecated Use {@link ApplicationCommandOptionEnum}. Removed in 0.3.0. */
746
- export type ApplicationCommandOptionType = ApplicationCommandOptionEnum;