@lunibee/builders 0.1.6 → 0.1.8
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 +58 -11
- package/package.json +10 -10
- package/src/attachment.ts +54 -51
- package/src/commands.ts +582 -472
- package/src/components.ts +670 -600
- package/src/embed.ts +171 -159
- package/src/index.ts +45 -37
package/src/commands.ts
CHANGED
|
@@ -1,519 +1,629 @@
|
|
|
1
1
|
/** Discord application command option types. */
|
|
2
2
|
export const ApplicationCommandOptionType = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
Subcommand: 1,
|
|
4
|
+
SubcommandGroup: 2,
|
|
5
|
+
String: 3,
|
|
6
|
+
Integer: 4,
|
|
7
|
+
Boolean: 5,
|
|
8
|
+
User: 6,
|
|
9
|
+
Channel: 7,
|
|
10
|
+
Role: 8,
|
|
11
|
+
Mentionable: 9,
|
|
12
|
+
Number: 10,
|
|
13
|
+
Attachment: 11,
|
|
14
14
|
} as const;
|
|
15
15
|
|
|
16
16
|
/** Builds Discord application command payloads. */
|
|
17
17
|
export class SlashCommandBuilder {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
18
|
+
readonly #data: Record<string, unknown> = { type: 1 };
|
|
19
|
+
/** Sets the command name. */ public setName(name: string): this {
|
|
20
|
+
validateName(name, "Command name");
|
|
21
|
+
this.#data.name = name;
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
/** Sets the command description. */ public setDescription(
|
|
25
|
+
description: string,
|
|
26
|
+
): this {
|
|
27
|
+
validateText(description, "Command description", 100);
|
|
28
|
+
this.#data.description = description;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
/** Sets whether the command is available in direct messages. */ public setDMPermission(
|
|
32
|
+
enabled: boolean,
|
|
33
|
+
): this {
|
|
34
|
+
this.#data.dm_permission = enabled;
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
/** Sets whether the command is NSFW. */
|
|
38
|
+
public setNSFW(nsfw = true): this {
|
|
39
|
+
this.#data.nsfw = nsfw;
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
/** Sets whether the command is NSFW. */
|
|
43
|
+
public setNsfw(nsfw = true): this {
|
|
44
|
+
return this.setNSFW(nsfw);
|
|
45
|
+
}
|
|
46
|
+
/** Sets command default member permissions. */ public setDefaultMemberPermissions(
|
|
47
|
+
permissions: bigint | number | string | null,
|
|
48
|
+
): this {
|
|
49
|
+
this.#data.default_member_permissions =
|
|
50
|
+
permissions === null ? null : BigInt(permissions).toString();
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
/** Sets command integration types. */ public setIntegrationTypes(
|
|
54
|
+
...types: number[]
|
|
55
|
+
): this {
|
|
56
|
+
this.#data.integration_types = [...types];
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
/** Adds a string option. */ public addStringOption(
|
|
60
|
+
configure: (option: StringOptionBuilder) => StringOptionBuilder,
|
|
61
|
+
): this {
|
|
62
|
+
return this.addOption(configure(new StringOptionBuilder()));
|
|
63
|
+
}
|
|
64
|
+
/** Adds an integer option. */ public addIntegerOption(
|
|
65
|
+
configure: (option: IntegerOptionBuilder) => IntegerOptionBuilder,
|
|
66
|
+
): this {
|
|
67
|
+
return this.addOption(configure(new IntegerOptionBuilder()));
|
|
68
|
+
}
|
|
69
|
+
/** Adds a number option. */ public addNumberOption(
|
|
70
|
+
configure: (option: NumberOptionBuilder) => NumberOptionBuilder,
|
|
71
|
+
): this {
|
|
72
|
+
return this.addOption(configure(new NumberOptionBuilder()));
|
|
73
|
+
}
|
|
74
|
+
/** Adds a boolean option. */ public addBooleanOption(
|
|
75
|
+
configure: (option: BooleanOptionBuilder) => BooleanOptionBuilder,
|
|
76
|
+
): this {
|
|
77
|
+
return this.addOption(configure(new BooleanOptionBuilder()));
|
|
78
|
+
}
|
|
79
|
+
/** Adds a user option. */ public addUserOption(
|
|
80
|
+
configure: (option: UserOptionBuilder) => UserOptionBuilder,
|
|
81
|
+
): this {
|
|
82
|
+
return this.addOption(configure(new UserOptionBuilder()));
|
|
83
|
+
}
|
|
84
|
+
/** Adds a channel option. */ public addChannelOption(
|
|
85
|
+
configure: (option: ChannelOptionBuilder) => ChannelOptionBuilder,
|
|
86
|
+
): this {
|
|
87
|
+
return this.addOption(configure(new ChannelOptionBuilder()));
|
|
88
|
+
}
|
|
89
|
+
/** Adds a role option. */ public addRoleOption(
|
|
90
|
+
configure: (option: RoleOptionBuilder) => RoleOptionBuilder,
|
|
91
|
+
): this {
|
|
92
|
+
return this.addOption(configure(new RoleOptionBuilder()));
|
|
93
|
+
}
|
|
94
|
+
/** Adds a mentionable option. */ public addMentionableOption(
|
|
95
|
+
configure: (
|
|
96
|
+
option: MentionableOptionBuilder,
|
|
97
|
+
) => MentionableOptionBuilder,
|
|
98
|
+
): this {
|
|
99
|
+
return this.addOption(configure(new MentionableOptionBuilder()));
|
|
100
|
+
}
|
|
101
|
+
/** Adds an attachment option. */ public addAttachmentOption(
|
|
102
|
+
configure: (option: AttachmentOptionBuilder) => AttachmentOptionBuilder,
|
|
103
|
+
): this {
|
|
104
|
+
return this.addOption(configure(new AttachmentOptionBuilder()));
|
|
105
|
+
}
|
|
106
|
+
/** Adds a subcommand. */ public addSubcommand(
|
|
107
|
+
configure: (option: SubcommandBuilder) => SubcommandBuilder,
|
|
108
|
+
): this {
|
|
109
|
+
return this.addOption(configure(new SubcommandBuilder()));
|
|
110
|
+
}
|
|
111
|
+
/** Adds a subcommand group. */ public addSubcommandGroup(
|
|
112
|
+
configure: (option: SubcommandGroupBuilder) => SubcommandGroupBuilder,
|
|
113
|
+
): this {
|
|
114
|
+
return this.addOption(configure(new SubcommandGroupBuilder()));
|
|
115
|
+
}
|
|
116
|
+
/** Serializes the command payload. */ public toJSON(): Record<
|
|
117
|
+
string,
|
|
118
|
+
unknown
|
|
119
|
+
> {
|
|
120
|
+
return structuredClone(this.#data);
|
|
121
|
+
}
|
|
122
|
+
/** Adds a validated top-level command option. */ protected addOption(
|
|
123
|
+
option: CommandOptionBuilder,
|
|
124
|
+
): this {
|
|
125
|
+
const options = (this.#data.options as unknown[] | undefined) ?? [];
|
|
126
|
+
if (options.length >= 25)
|
|
127
|
+
throw new RangeError(
|
|
128
|
+
"An application command cannot contain more than 25 options.",
|
|
129
|
+
);
|
|
130
|
+
const payload = option.toJSON();
|
|
131
|
+
if (
|
|
132
|
+
options.some(
|
|
133
|
+
(existing) =>
|
|
134
|
+
(existing as Record<string, unknown>).name === payload.name,
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
throw new RangeError(
|
|
138
|
+
`Duplicate option name: ${String(payload.name)}.`,
|
|
139
|
+
);
|
|
140
|
+
if (
|
|
141
|
+
payload.required === true &&
|
|
142
|
+
options.some(
|
|
143
|
+
(existing) =>
|
|
144
|
+
(existing as Record<string, unknown>).required !== true,
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
throw new RangeError(
|
|
148
|
+
"Required application command options must be placed before optional options.",
|
|
149
|
+
);
|
|
150
|
+
options.push(payload);
|
|
151
|
+
this.#data.options = options;
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
149
154
|
}
|
|
150
155
|
|
|
151
156
|
/** Base builder for command options. */
|
|
152
157
|
export class CommandOptionBuilder {
|
|
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
|
-
|
|
158
|
+
protected readonly data: Record<string, unknown>;
|
|
159
|
+
/** Creates an option builder. */ public constructor(type: number) {
|
|
160
|
+
this.data = { type };
|
|
161
|
+
}
|
|
162
|
+
/** Sets the option name. */ public setName(name: string): this {
|
|
163
|
+
validateName(name, "Option name");
|
|
164
|
+
this.data.name = name;
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
/** Sets the option description. */ public setDescription(
|
|
168
|
+
description: string,
|
|
169
|
+
): this {
|
|
170
|
+
validateText(description, "Option description", 100);
|
|
171
|
+
this.data.description = description;
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
/** Makes the option required or optional. */ public setRequired(
|
|
175
|
+
required = true,
|
|
176
|
+
): this {
|
|
177
|
+
this.data.required = required;
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
/** Sets autocomplete. */ public setAutocomplete(enabled = true): this {
|
|
181
|
+
this.data.autocomplete = enabled;
|
|
182
|
+
return this;
|
|
183
|
+
}
|
|
184
|
+
/** Serializes the option payload. */ public toJSON(): Record<
|
|
185
|
+
string,
|
|
186
|
+
unknown
|
|
187
|
+
> {
|
|
188
|
+
return structuredClone(this.data);
|
|
189
|
+
}
|
|
185
190
|
}
|
|
186
191
|
|
|
187
192
|
/** Builds a string command option. */
|
|
188
193
|
export class StringOptionBuilder extends CommandOptionBuilder {
|
|
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
|
-
this
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
194
|
+
/** Creates a string option. */ public constructor() {
|
|
195
|
+
super(ApplicationCommandOptionType.String);
|
|
196
|
+
}
|
|
197
|
+
/** Adds string choices. */ public addChoices(
|
|
198
|
+
...choices: Array<{ name: string; value: string }>
|
|
199
|
+
): this {
|
|
200
|
+
const current = (this.data.choices as unknown[] | undefined) ?? [];
|
|
201
|
+
if (!choices.length)
|
|
202
|
+
throw new TypeError("At least one choice is required.");
|
|
203
|
+
if (this.data.autocomplete === true)
|
|
204
|
+
throw new RangeError("Autocomplete options cannot define choices.");
|
|
205
|
+
if (current.length + choices.length > 25)
|
|
206
|
+
throw new RangeError(
|
|
207
|
+
"An option cannot contain more than 25 choices.",
|
|
208
|
+
);
|
|
209
|
+
for (const choice of choices) {
|
|
210
|
+
validateText(choice.name, "Choice name", 100);
|
|
211
|
+
validateText(choice.value, "Choice value", 100);
|
|
212
|
+
}
|
|
213
|
+
this.data.choices = [
|
|
214
|
+
...current,
|
|
215
|
+
...choices.map((choice) => ({ ...choice })),
|
|
216
|
+
];
|
|
217
|
+
return this;
|
|
218
|
+
}
|
|
219
|
+
/** Sets the minimum string length. */ public setMinLength(
|
|
220
|
+
value: number,
|
|
221
|
+
): this {
|
|
222
|
+
validateIntegerRange(value, 0, 6000, "min_length");
|
|
223
|
+
this.data.min_length = value;
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
/** Sets the maximum string length. */ public setMaxLength(
|
|
227
|
+
value: number,
|
|
228
|
+
): this {
|
|
229
|
+
validateIntegerRange(value, 1, 6000, "max_length");
|
|
230
|
+
if (
|
|
231
|
+
(this.data.min_length as number | undefined) !== undefined &&
|
|
232
|
+
(this.data.min_length as number) > value
|
|
233
|
+
)
|
|
234
|
+
throw new RangeError("min_length cannot exceed max_length.");
|
|
235
|
+
this.data.max_length = value;
|
|
236
|
+
return this;
|
|
237
|
+
}
|
|
231
238
|
}
|
|
232
239
|
/** Builds an integer command option. */
|
|
233
240
|
export class IntegerOptionBuilder extends CommandOptionBuilder {
|
|
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
|
-
|
|
241
|
+
public constructor() {
|
|
242
|
+
super(ApplicationCommandOptionType.Integer);
|
|
243
|
+
}
|
|
244
|
+
public setMinValue(value: number): this {
|
|
245
|
+
validateNumberRange(
|
|
246
|
+
value,
|
|
247
|
+
-9_007_199_254_740_991,
|
|
248
|
+
9_007_199_254_740_991,
|
|
249
|
+
"min_value",
|
|
250
|
+
true,
|
|
251
|
+
);
|
|
252
|
+
this.data.min_value = value;
|
|
253
|
+
return this;
|
|
254
|
+
}
|
|
255
|
+
public setMaxValue(value: number): this {
|
|
256
|
+
validateNumberRange(
|
|
257
|
+
value,
|
|
258
|
+
-9_007_199_254_740_991,
|
|
259
|
+
9_007_199_254_740_991,
|
|
260
|
+
"max_value",
|
|
261
|
+
true,
|
|
262
|
+
);
|
|
263
|
+
if (
|
|
264
|
+
(this.data.min_value as number | undefined) !== undefined &&
|
|
265
|
+
(this.data.min_value as number) > value
|
|
266
|
+
)
|
|
267
|
+
throw new RangeError("min_value cannot exceed max_value.");
|
|
268
|
+
this.data.max_value = value;
|
|
269
|
+
return this;
|
|
270
|
+
}
|
|
271
|
+
public setAutocomplete(enabled = true): this {
|
|
272
|
+
if (
|
|
273
|
+
enabled &&
|
|
274
|
+
Array.isArray(this.data.choices) &&
|
|
275
|
+
this.data.choices.length
|
|
276
|
+
)
|
|
277
|
+
throw new RangeError("Autocomplete options cannot define choices.");
|
|
278
|
+
this.data.autocomplete = enabled;
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
public addChoices(...choices: { name: string; value: number }[]): this {
|
|
282
|
+
const current = (this.data.choices as unknown[] | undefined) ?? [];
|
|
283
|
+
if (!choices.length)
|
|
284
|
+
throw new TypeError("At least one choice is required.");
|
|
285
|
+
if (this.data.autocomplete === true)
|
|
286
|
+
throw new RangeError("Autocomplete options cannot define choices.");
|
|
287
|
+
if (current.length + choices.length > 25)
|
|
288
|
+
throw new RangeError(
|
|
289
|
+
"An option cannot contain more than 25 choices.",
|
|
290
|
+
);
|
|
291
|
+
this.data.choices = [...current, ...choices];
|
|
292
|
+
return this;
|
|
293
|
+
}
|
|
281
294
|
}
|
|
282
295
|
/** Builds a number command option. */
|
|
283
296
|
export class NumberOptionBuilder extends CommandOptionBuilder {
|
|
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
|
-
|
|
297
|
+
public constructor() {
|
|
298
|
+
super(ApplicationCommandOptionType.Number);
|
|
299
|
+
}
|
|
300
|
+
public setMinValue(value: number): this {
|
|
301
|
+
validateNumberRange(
|
|
302
|
+
value,
|
|
303
|
+
-9_007_199_254_740_991,
|
|
304
|
+
9_007_199_254_740_991,
|
|
305
|
+
"min_value",
|
|
306
|
+
false,
|
|
307
|
+
);
|
|
308
|
+
this.data.min_value = value;
|
|
309
|
+
return this;
|
|
310
|
+
}
|
|
311
|
+
public setMaxValue(value: number): this {
|
|
312
|
+
validateNumberRange(
|
|
313
|
+
value,
|
|
314
|
+
-9_007_199_254_740_991,
|
|
315
|
+
9_007_199_254_740_991,
|
|
316
|
+
"max_value",
|
|
317
|
+
false,
|
|
318
|
+
);
|
|
319
|
+
if (
|
|
320
|
+
(this.data.min_value as number | undefined) !== undefined &&
|
|
321
|
+
(this.data.min_value as number) > value
|
|
322
|
+
)
|
|
323
|
+
throw new RangeError("min_value cannot exceed max_value.");
|
|
324
|
+
this.data.max_value = value;
|
|
325
|
+
return this;
|
|
326
|
+
}
|
|
327
|
+
public setAutocomplete(enabled = true): this {
|
|
328
|
+
if (
|
|
329
|
+
enabled &&
|
|
330
|
+
Array.isArray(this.data.choices) &&
|
|
331
|
+
this.data.choices.length
|
|
332
|
+
)
|
|
333
|
+
throw new RangeError("Autocomplete options cannot define choices.");
|
|
334
|
+
this.data.autocomplete = enabled;
|
|
335
|
+
return this;
|
|
336
|
+
}
|
|
337
|
+
public addChoices(...choices: { name: string; value: number }[]): this {
|
|
338
|
+
const current = (this.data.choices as unknown[] | undefined) ?? [];
|
|
339
|
+
if (!choices.length)
|
|
340
|
+
throw new TypeError("At least one choice is required.");
|
|
341
|
+
if (this.data.autocomplete === true)
|
|
342
|
+
throw new RangeError("Autocomplete options cannot define choices.");
|
|
343
|
+
if (current.length + choices.length > 25)
|
|
344
|
+
throw new RangeError(
|
|
345
|
+
"An option cannot contain more than 25 choices.",
|
|
346
|
+
);
|
|
347
|
+
this.data.choices = [...current, ...choices];
|
|
348
|
+
return this;
|
|
349
|
+
}
|
|
331
350
|
}
|
|
332
351
|
/** Builds a boolean command option. */ export class BooleanOptionBuilder extends CommandOptionBuilder {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
352
|
+
/** Creates a boolean option. */ public constructor() {
|
|
353
|
+
super(ApplicationCommandOptionType.Boolean);
|
|
354
|
+
}
|
|
336
355
|
}
|
|
337
356
|
/** Builds a user command option. */ export class UserOptionBuilder extends CommandOptionBuilder {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
357
|
+
/** Creates a user option. */ public constructor() {
|
|
358
|
+
super(ApplicationCommandOptionType.User);
|
|
359
|
+
}
|
|
341
360
|
}
|
|
342
361
|
/** Builds a role command option. */ export class RoleOptionBuilder extends CommandOptionBuilder {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
362
|
+
/** Creates a role option. */ public constructor() {
|
|
363
|
+
super(ApplicationCommandOptionType.Role);
|
|
364
|
+
}
|
|
346
365
|
}
|
|
347
366
|
/** Builds a mentionable command option. */ export class MentionableOptionBuilder extends CommandOptionBuilder {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
367
|
+
/** Creates a mentionable option. */ public constructor() {
|
|
368
|
+
super(ApplicationCommandOptionType.Mentionable);
|
|
369
|
+
}
|
|
351
370
|
}
|
|
352
371
|
/** Builds an attachment command option. */ export class AttachmentOptionBuilder extends CommandOptionBuilder {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
372
|
+
/** Creates an attachment option. */ public constructor() {
|
|
373
|
+
super(ApplicationCommandOptionType.Attachment);
|
|
374
|
+
}
|
|
356
375
|
}
|
|
357
376
|
/** Builds a channel command option. */ export class ChannelOptionBuilder extends CommandOptionBuilder {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
377
|
+
/** Creates a channel option. */ public constructor() {
|
|
378
|
+
super(ApplicationCommandOptionType.Channel);
|
|
379
|
+
}
|
|
380
|
+
/** Restricts accepted channel types. */ public addChannelTypes(
|
|
381
|
+
...types: number[]
|
|
382
|
+
): this {
|
|
383
|
+
if (types.some((type) => !Number.isInteger(type) || type < 0))
|
|
384
|
+
throw new RangeError(
|
|
385
|
+
"Channel types must be non-negative integers.",
|
|
386
|
+
);
|
|
387
|
+
this.data.channel_types = [...new Set(types)];
|
|
388
|
+
return this;
|
|
389
|
+
}
|
|
369
390
|
}
|
|
370
391
|
/** Builds a nested subcommand. */ export class SubcommandBuilder extends CommandOptionBuilder {
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
392
|
+
/** Creates a subcommand. */ public constructor() {
|
|
393
|
+
super(ApplicationCommandOptionType.Subcommand);
|
|
394
|
+
}
|
|
395
|
+
/** Adds a string option to this subcommand. */ public addStringOption(
|
|
396
|
+
configure: (option: StringOptionBuilder) => StringOptionBuilder,
|
|
397
|
+
): this {
|
|
398
|
+
return this.addChildOption(configure(new StringOptionBuilder()));
|
|
399
|
+
}
|
|
400
|
+
/** Adds an integer option to this subcommand. */ public addIntegerOption(
|
|
401
|
+
configure: (option: IntegerOptionBuilder) => IntegerOptionBuilder,
|
|
402
|
+
): this {
|
|
403
|
+
return this.addChildOption(configure(new IntegerOptionBuilder()));
|
|
404
|
+
}
|
|
405
|
+
/** Adds a number option to this subcommand. */ public addNumberOption(
|
|
406
|
+
configure: (option: NumberOptionBuilder) => NumberOptionBuilder,
|
|
407
|
+
): this {
|
|
408
|
+
return this.addChildOption(configure(new NumberOptionBuilder()));
|
|
409
|
+
}
|
|
410
|
+
/** Adds a boolean option to this subcommand. */ public addBooleanOption(
|
|
411
|
+
configure: (option: BooleanOptionBuilder) => BooleanOptionBuilder,
|
|
412
|
+
): this {
|
|
413
|
+
return this.addChildOption(configure(new BooleanOptionBuilder()));
|
|
414
|
+
}
|
|
415
|
+
/** Adds a user option to this subcommand. */ public addUserOption(
|
|
416
|
+
configure: (option: UserOptionBuilder) => UserOptionBuilder,
|
|
417
|
+
): this {
|
|
418
|
+
return this.addChildOption(configure(new UserOptionBuilder()));
|
|
419
|
+
}
|
|
420
|
+
/** Adds a channel option to this subcommand. */ public addChannelOption(
|
|
421
|
+
configure: (option: ChannelOptionBuilder) => ChannelOptionBuilder,
|
|
422
|
+
): this {
|
|
423
|
+
return this.addChildOption(configure(new ChannelOptionBuilder()));
|
|
424
|
+
}
|
|
425
|
+
/** Adds a role option to this subcommand. */ public addRoleOption(
|
|
426
|
+
configure: (option: RoleOptionBuilder) => RoleOptionBuilder,
|
|
427
|
+
): this {
|
|
428
|
+
return this.addChildOption(configure(new RoleOptionBuilder()));
|
|
429
|
+
}
|
|
430
|
+
/** Adds a mentionable option to this subcommand. */ public addMentionableOption(
|
|
431
|
+
configure: (
|
|
432
|
+
option: MentionableOptionBuilder,
|
|
433
|
+
) => MentionableOptionBuilder,
|
|
434
|
+
): this {
|
|
435
|
+
return this.addChildOption(configure(new MentionableOptionBuilder()));
|
|
436
|
+
}
|
|
437
|
+
/** Adds an attachment option to this subcommand. */ public addAttachmentOption(
|
|
438
|
+
configure: (option: AttachmentOptionBuilder) => AttachmentOptionBuilder,
|
|
439
|
+
): this {
|
|
440
|
+
return this.addChildOption(configure(new AttachmentOptionBuilder()));
|
|
441
|
+
}
|
|
442
|
+
protected addChildOption(option: CommandOptionBuilder): this {
|
|
443
|
+
const options = (this.data.options as unknown[] | undefined) ?? [];
|
|
444
|
+
if (options.length >= 25)
|
|
445
|
+
throw new RangeError(
|
|
446
|
+
"A subcommand cannot contain more than 25 options.",
|
|
447
|
+
);
|
|
448
|
+
const payload = option.toJSON();
|
|
449
|
+
if (
|
|
450
|
+
options.some(
|
|
451
|
+
(existing) =>
|
|
452
|
+
(existing as Record<string, unknown>).name === payload.name,
|
|
453
|
+
)
|
|
454
|
+
)
|
|
455
|
+
throw new RangeError(
|
|
456
|
+
`Duplicate option name: ${String(payload.name)}.`,
|
|
457
|
+
);
|
|
458
|
+
if (
|
|
459
|
+
payload.required === true &&
|
|
460
|
+
options.some(
|
|
461
|
+
(existing) =>
|
|
462
|
+
(existing as Record<string, unknown>).required !== true,
|
|
463
|
+
)
|
|
464
|
+
)
|
|
465
|
+
throw new RangeError(
|
|
466
|
+
"Required subcommand options must be placed before optional options.",
|
|
467
|
+
);
|
|
468
|
+
if (
|
|
469
|
+
options.some(
|
|
470
|
+
(existing) =>
|
|
471
|
+
(existing as Record<string, unknown>).type ===
|
|
472
|
+
ApplicationCommandOptionType.Subcommand,
|
|
473
|
+
)
|
|
474
|
+
)
|
|
475
|
+
throw new RangeError(
|
|
476
|
+
"Subcommands cannot contain nested subcommands.",
|
|
477
|
+
);
|
|
478
|
+
options.push(payload);
|
|
479
|
+
this.data.options = options;
|
|
480
|
+
return this;
|
|
481
|
+
}
|
|
452
482
|
}
|
|
453
483
|
/** Builds a nested subcommand group. */ export class SubcommandGroupBuilder extends CommandOptionBuilder {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
484
|
+
/** Creates a subcommand group. */ public constructor() {
|
|
485
|
+
super(ApplicationCommandOptionType.SubcommandGroup);
|
|
486
|
+
}
|
|
487
|
+
/** Adds a subcommand to this group. */ public addSubcommand(
|
|
488
|
+
configure: (option: SubcommandBuilder) => SubcommandBuilder,
|
|
489
|
+
): this {
|
|
490
|
+
const options = (this.data.options as unknown[] | undefined) ?? [];
|
|
491
|
+
if (options.length >= 25)
|
|
492
|
+
throw new RangeError(
|
|
493
|
+
"A subcommand group cannot contain more than 25 subcommands.",
|
|
494
|
+
);
|
|
495
|
+
const payload = configure(new SubcommandBuilder()).toJSON();
|
|
496
|
+
if (
|
|
497
|
+
options.some(
|
|
498
|
+
(existing) =>
|
|
499
|
+
(existing as Record<string, unknown>).name === payload.name,
|
|
500
|
+
)
|
|
501
|
+
)
|
|
502
|
+
throw new RangeError(
|
|
503
|
+
`Duplicate subcommand name: ${String(payload.name)}.`,
|
|
504
|
+
);
|
|
505
|
+
if (payload.type !== ApplicationCommandOptionType.Subcommand)
|
|
506
|
+
throw new TypeError(
|
|
507
|
+
"Subcommand group children must be subcommands.",
|
|
508
|
+
);
|
|
509
|
+
options.push(payload);
|
|
510
|
+
this.data.options = options;
|
|
511
|
+
return this;
|
|
512
|
+
}
|
|
481
513
|
}
|
|
482
514
|
function validateName(value: string, field: string): void {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
515
|
+
if (!/^[\p{L}\p{N}_-]{1,32}$/u.test(value) || value !== value.toLowerCase())
|
|
516
|
+
throw new RangeError(
|
|
517
|
+
`${field} must contain 1-32 lowercase letters, numbers, underscores, or hyphens.`,
|
|
518
|
+
);
|
|
487
519
|
}
|
|
488
520
|
function validateText(value: string, field: string, max: number): void {
|
|
489
|
-
|
|
490
|
-
|
|
521
|
+
if (typeof value !== "string" || !value.trim() || value.length > max)
|
|
522
|
+
throw new RangeError(`${field} must contain 1-${max} characters.`);
|
|
491
523
|
}
|
|
492
524
|
function validateIntegerRange(
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
525
|
+
value: number,
|
|
526
|
+
minimum: number,
|
|
527
|
+
maximum: number,
|
|
528
|
+
field: string,
|
|
497
529
|
): void {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
530
|
+
if (!Number.isInteger(value) || value < minimum || value > maximum)
|
|
531
|
+
throw new RangeError(
|
|
532
|
+
`${field} must be an integer between ${minimum} and ${maximum}.`,
|
|
533
|
+
);
|
|
502
534
|
}
|
|
503
535
|
function validateNumberRange(
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
536
|
+
value: number,
|
|
537
|
+
minimum: number,
|
|
538
|
+
maximum: number,
|
|
539
|
+
field: string,
|
|
540
|
+
integerOnly = true,
|
|
509
541
|
): void {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
542
|
+
if (
|
|
543
|
+
!Number.isFinite(value) ||
|
|
544
|
+
(integerOnly && !Number.isInteger(value)) ||
|
|
545
|
+
value < minimum ||
|
|
546
|
+
value > maximum
|
|
547
|
+
)
|
|
548
|
+
throw new RangeError(
|
|
549
|
+
`${field} must be ${integerOnly ? "an integer" : "a number"} between ${minimum} and ${maximum}.`,
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// ─── Context Menu Builders ────────────────────────────────────────────────────
|
|
554
|
+
|
|
555
|
+
/** Builder for application commands that appear in right-click context menus.
|
|
556
|
+
* Discord.js-familiar: `new ContextMenuCommandBuilder().setName("x").setType(2)`. */
|
|
557
|
+
export class ContextMenuCommandBuilder {
|
|
558
|
+
protected readonly data: Record<string, unknown>;
|
|
559
|
+
public constructor(type: 2 | 3 = 2) {
|
|
560
|
+
this.data = { type };
|
|
561
|
+
}
|
|
562
|
+
/** Sets the command type: 2 = USER, 3 = MESSAGE. @throws {RangeError} For any other type. */
|
|
563
|
+
public setType(type: 2 | 3): this {
|
|
564
|
+
if (type !== 2 && type !== 3)
|
|
565
|
+
throw new RangeError("Context menu command type must be 2 or 3.");
|
|
566
|
+
this.data.type = type;
|
|
567
|
+
return this;
|
|
568
|
+
}
|
|
569
|
+
/** Sets the command name (shown in the right-click menu).
|
|
570
|
+
* Unlike CHAT_INPUT commands, USER (type 2) and MESSAGE (type 3) context-menu
|
|
571
|
+
* command names may contain uppercase letters and spaces, so no lowercase/charset
|
|
572
|
+
* validation is applied here — only Discord's 1-32 length limit and a non-empty
|
|
573
|
+
* (non-whitespace) requirement are enforced.
|
|
574
|
+
* @param name Display name; 1-32 characters, mixed case and spaces allowed.
|
|
575
|
+
* @throws {RangeError} If the name is empty/whitespace-only or exceeds 32 characters.
|
|
576
|
+
*/
|
|
577
|
+
public setName(name: string): this {
|
|
578
|
+
if (
|
|
579
|
+
typeof name !== "string" ||
|
|
580
|
+
name.trim().length < 1 ||
|
|
581
|
+
name.length > 32
|
|
582
|
+
)
|
|
583
|
+
throw new RangeError("Command name must contain 1-32 characters.");
|
|
584
|
+
this.data.name = name;
|
|
585
|
+
return this;
|
|
586
|
+
}
|
|
587
|
+
/** Sets default member permissions required to see this command. */
|
|
588
|
+
public setDefaultMemberPermissions(
|
|
589
|
+
permissions: bigint | number | string | null,
|
|
590
|
+
): this {
|
|
591
|
+
this.data.default_member_permissions =
|
|
592
|
+
permissions === null ? null : BigInt(permissions).toString();
|
|
593
|
+
return this;
|
|
594
|
+
}
|
|
595
|
+
/** Sets whether the command is available in direct messages. */
|
|
596
|
+
public setDMPermission(enabled: boolean): this {
|
|
597
|
+
this.data.dm_permission = enabled;
|
|
598
|
+
return this;
|
|
599
|
+
}
|
|
600
|
+
/** Sets command integration types. */
|
|
601
|
+
public setIntegrationTypes(...types: number[]): this {
|
|
602
|
+
this.data.integration_types = [...types];
|
|
603
|
+
return this;
|
|
604
|
+
}
|
|
605
|
+
/** Serializes the command payload for the Discord API. */
|
|
606
|
+
public toJSON(): Record<string, unknown> {
|
|
607
|
+
return structuredClone(this.data);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/** Builds a User context menu command (appears when right-clicking a user, type 2).
|
|
612
|
+
* @example
|
|
613
|
+
* new UserCommandBuilder().setName("View Profile").toJSON()
|
|
614
|
+
*/
|
|
615
|
+
export class UserCommandBuilder extends ContextMenuCommandBuilder {
|
|
616
|
+
public constructor() {
|
|
617
|
+
super(2);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/** Builds a Message context menu command (appears when right-clicking a message, type 3).
|
|
622
|
+
* @example
|
|
623
|
+
* new MessageCommandBuilder().setName("Translate Message").toJSON()
|
|
624
|
+
*/
|
|
625
|
+
export class MessageCommandBuilder extends ContextMenuCommandBuilder {
|
|
626
|
+
public constructor() {
|
|
627
|
+
super(3);
|
|
628
|
+
}
|
|
519
629
|
}
|