1 |
/** |
2 |
* This file is part of SudoBot. |
3 |
* |
4 |
* Copyright (C) 2021-2023 OSN Developers. |
5 |
* |
6 |
* SudoBot is free software; you can redistribute it and/or modify it |
7 |
* under the terms of the GNU Affero General Public License as published by |
8 |
* the Free Software Foundation, either version 3 of the License, or |
9 |
* (at your option) any later version. |
10 |
* |
11 |
* SudoBot is distributed in the hope that it will be useful, but |
12 |
* WITHOUT ANY WARRANTY; without even the implied warranty of |
13 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 |
* GNU Affero General Public License for more details. |
15 |
* |
16 |
* You should have received a copy of the GNU Affero General Public License |
17 |
* along with SudoBot. If not, see <https://www.gnu.org/licenses/>. |
18 |
*/ |
19 |
|
20 |
import { |
21 |
APIMessage, |
22 |
ApplicationCommandType, |
23 |
CacheType, |
24 |
ChatInputCommandInteraction, |
25 |
ContextMenuCommandBuilder, |
26 |
ContextMenuCommandInteraction, |
27 |
GuildBasedChannel, |
28 |
GuildMember, |
29 |
InteractionDeferReplyOptions, |
30 |
InteractionEditReplyOptions, |
31 |
InteractionReplyOptions, |
32 |
Message, |
33 |
MessageCreateOptions, |
34 |
MessageMentions, |
35 |
MessagePayload, |
36 |
PermissionResolvable, |
37 |
PermissionsBitField, |
38 |
Role, |
39 |
SlashCommandBuilder, |
40 |
Snowflake, |
41 |
User |
42 |
} from "discord.js"; |
43 |
import { ChatInputCommandContext, ContextMenuCommandContext, LegacyCommandContext } from "../services/CommandManager"; |
44 |
import { stringToTimeInterval } from "../utils/datetime"; |
45 |
import { log, logError } from "../utils/logger"; |
46 |
import { getEmoji, isSnowflake } from "../utils/utils"; |
47 |
import Client from "./Client"; |
48 |
|
49 |
export type CommandMessage = Message<boolean> | ChatInputCommandInteraction<CacheType> | ContextMenuCommandInteraction; |
50 |
export type BasicCommandContext = LegacyCommandContext | ChatInputCommandContext; |
51 |
export type AnyCommandContext = BasicCommandContext | ContextMenuCommandContext; |
52 |
export type CommandReturn = |
53 |
| ((MessageCreateOptions | APIMessage | InteractionReplyOptions) & { __reply?: boolean }) |
54 |
| undefined |
55 |
| null |
56 |
| void; |
57 |
|
58 |
export enum ArgumentType { |
59 |
String = 1, |
60 |
StringRest, |
61 |
Number, |
62 |
Integer, |
63 |
Float, |
64 |
Boolean, |
65 |
Snowflake, |
66 |
User, |
67 |
GuildMember, |
68 |
Channel, |
69 |
Role, |
70 |
Link, |
71 |
TimeInterval |
72 |
} |
73 |
|
74 |
export interface ValidationRule { |
75 |
types?: readonly ArgumentType[]; |
76 |
optional?: boolean; |
77 |
default?: any; |
78 |
requiredErrorMessage?: string; |
79 |
typeErrorMessage?: string; |
80 |
entityNotNullErrorMessage?: string; |
81 |
entityNotNull?: boolean; |
82 |
minValue?: number; |
83 |
maxValue?: number; |
84 |
minMaxErrorMessage?: string; |
85 |
lengthMaxErrorMessage?: string; |
86 |
lengthMax?: number; |
87 |
name?: string; |
88 |
timeMilliseconds?: boolean; |
89 |
} |
90 |
|
91 |
type ValidationRuleAndOutputMap = { |
92 |
[ArgumentType.Boolean]: boolean; |
93 |
[ArgumentType.Channel]: GuildBasedChannel; |
94 |
[ArgumentType.Float]: number; |
95 |
[ArgumentType.Number]: number; |
96 |
[ArgumentType.Integer]: number; |
97 |
[ArgumentType.TimeInterval]: number; |
98 |
[ArgumentType.Link]: string; |
99 |
[ArgumentType.Role]: Role; |
100 |
[ArgumentType.Snowflake]: Snowflake; |
101 |
[ArgumentType.String]: string; |
102 |
[ArgumentType.StringRest]: string; |
103 |
[ArgumentType.User]: User; |
104 |
[ArgumentType.GuildMember]: GuildMember; |
105 |
}; |
106 |
|
107 |
type ValidationRuleParsedArg<T extends ValidationRule["types"]> = T extends ReadonlyArray<infer U> |
108 |
? U extends keyof ValidationRuleAndOutputMap |
109 |
? ValidationRuleAndOutputMap[U] |
110 |
: never |
111 |
: T extends Array<infer U> |
112 |
? U extends keyof ValidationRuleAndOutputMap |
113 |
? ValidationRuleAndOutputMap[U] |
114 |
: never |
115 |
: never; |
116 |
|
117 |
export type ValidationRuleParsedArgs<T extends readonly ValidationRule[]> = { |
118 |
[K in keyof T]: ValidationRuleParsedArg<T[K]["types"]>; |
119 |
}; |
120 |
|
121 |
// TODO: Split the logic into separate methods |
122 |
|
123 |
export default abstract class Command { |
124 |
public readonly name: string = ""; |
125 |
public group: string = "Default"; |
126 |
public readonly aliases: string[] = []; |
127 |
|
128 |
public readonly supportsInteractions: boolean = true; |
129 |
public readonly supportsLegacy: boolean = true; |
130 |
|
131 |
public readonly permissions: PermissionResolvable[] = []; |
132 |
public readonly validationRules: readonly ValidationRule[] = []; |
133 |
public readonly permissionMode: "or" | "and" = "and"; |
134 |
public readonly systemAdminOnly: boolean = false; |
135 |
|
136 |
public readonly description?: string; |
137 |
public readonly detailedDescription?: string; |
138 |
public readonly argumentSyntaxes?: string[]; |
139 |
public readonly availableOptions?: Record<string, string>; |
140 |
public readonly beta: boolean = false; |
141 |
public readonly since: string = "1.0.0"; |
142 |
public readonly botRequiredPermissions: PermissionResolvable[] = []; |
143 |
public readonly slashCommandBuilder?: |
144 |
| Partial<Pick<SlashCommandBuilder, "addSubcommand" | "addSubcommandGroup">> |
145 |
| Omit<SlashCommandBuilder, "addSubcommand" | "addSubcommandGroup">; |
146 |
|
147 |
public readonly applicationCommandType: ApplicationCommandType = ApplicationCommandType.ChatInput; |
148 |
public readonly otherApplicationCommandBuilders: (ContextMenuCommandBuilder | SlashCommandBuilder)[] = []; |
149 |
|
150 |
public readonly subcommands: string[] = []; |
151 |
|
152 |
constructor(protected client: Client) {} |
153 |
|
154 |
abstract execute(message: CommandMessage, context: AnyCommandContext): Promise<CommandReturn>; |
155 |
|
156 |
async deferIfInteraction(message: CommandMessage, options?: InteractionDeferReplyOptions) { |
157 |
if (message instanceof ChatInputCommandInteraction) return await message.deferReply(options).catch(logError); |
158 |
} |
159 |
|
160 |
async deferredReply( |
161 |
message: CommandMessage, |
162 |
options: MessageCreateOptions | MessagePayload | InteractionEditReplyOptions | string |
163 |
) { |
164 |
if (message instanceof ChatInputCommandInteraction || message instanceof ContextMenuCommandInteraction) { |
165 |
return message.deferred ? await message.editReply(options) : await message.reply(options as any); |
166 |
} |
167 |
|
168 |
return message.reply(options as any); |
169 |
} |
170 |
|
171 |
async error(message: CommandMessage, errorMessage?: string) { |
172 |
return await this.deferredReply( |
173 |
message, |
174 |
errorMessage |
175 |
? `${this.emoji("error")} ${errorMessage}` |
176 |
: `⚠️ An error has occurred while performing this action. Please make sure that the bot has the required permissions to perform this action.` |
177 |
); |
178 |
} |
179 |
|
180 |
async success(message: CommandMessage, successMessage?: string) { |
181 |
return await this.deferredReply( |
182 |
message, |
183 |
successMessage ? `${this.emoji("check")} ${successMessage}` : `Successfully completed the given task.` |
184 |
); |
185 |
} |
186 |
|
187 |
emoji(name: string) { |
188 |
return getEmoji(this.client, name); |
189 |
} |
190 |
|
191 |
async run(message: CommandMessage, context: AnyCommandContext, checkOnly = false) { |
192 |
const isSystemAdmin = this.client.configManager.systemConfig.system_admins.includes(message.member!.user.id); |
193 |
|
194 |
if (this.systemAdminOnly && !isSystemAdmin) { |
195 |
message |
196 |
.reply({ |
197 |
content: `${this.emoji("error")} You don't have permission to run this command.`, |
198 |
ephemeral: true |
199 |
}) |
200 |
.catch(logError); |
201 |
|
202 |
return; |
203 |
} |
204 |
|
205 |
if (!isSystemAdmin) { |
206 |
const commandName = this.client.commands.get(context.isLegacy ? context.argv[0] : context.commandName)?.name; |
207 |
const { disabled_commands } = this.client.configManager.systemConfig; |
208 |
|
209 |
if (disabled_commands.includes(commandName ?? "")) { |
210 |
await this.error(message, "This command is disabled."); |
211 |
return; |
212 |
} |
213 |
|
214 |
const { channels, guild } = this.client.configManager.config[message.guildId!]?.disabled_commands ?? {}; |
215 |
|
216 |
if (guild && guild.includes(commandName ?? "")) { |
217 |
await this.error(message, "This command is disabled in this server."); |
218 |
return; |
219 |
} |
220 |
|
221 |
if (channels && channels[message.channelId!] && channels[message.channelId!].includes(commandName ?? "")) { |
222 |
await this.error(message, "This command is disabled in this channel."); |
223 |
return; |
224 |
} |
225 |
} |
226 |
|
227 |
const { validationRules, permissions } = this; |
228 |
const parsedArgs = []; |
229 |
const parsedNamedArgs: Record<string, any> = {}; |
230 |
|
231 |
if (!isSystemAdmin) { |
232 |
let member: GuildMember = <any>message.member!; |
233 |
|
234 |
if (!(member.permissions as any)?.has) { |
235 |
try { |
236 |
member = await message.guild!.members.fetch(member.user.id); |
237 |
|
238 |
if (!member) { |
239 |
throw new Error("Invalid member"); |
240 |
} |
241 |
} catch (e) { |
242 |
logError(e); |
243 |
message |
244 |
.reply({ |
245 |
content: `Sorry, I couldn't determine whether you have the enough permissions to perform this action or not. Please contact the bot developer.`, |
246 |
ephemeral: true |
247 |
}) |
248 |
.catch(logError); |
249 |
return; |
250 |
} |
251 |
} |
252 |
|
253 |
const mode = this.client.configManager.config[message.guildId!]?.permissions?.mode; |
254 |
|
255 |
if (permissions.length > 0) { |
256 |
const memberBotPermissions = this.client.permissionManager.getMemberPermissions(member); |
257 |
const memberRequiredPermissions = new PermissionsBitField(permissions).toArray(); |
258 |
|
259 |
if (this.permissionMode === "and") { |
260 |
for (const permission of permissions) { |
261 |
if (!member.permissions.has(permission, true)) { |
262 |
const mode = this.client.configManager.config[message.guildId!]?.permissions?.mode; |
263 |
|
264 |
if (mode !== "advanced" && mode !== "levels") { |
265 |
await message.reply({ |
266 |
content: `${this.emoji("error")} You don't have permission to run this command.`, |
267 |
ephemeral: true |
268 |
}); |
269 |
|
270 |
log("Skip"); |
271 |
|
272 |
return; |
273 |
} |
274 |
|
275 |
const memberBotPermissions = this.client.permissionManager.getMemberPermissions(member); |
276 |
const memberRequiredPermissions = new PermissionsBitField(permissions).toArray(); |
277 |
|
278 |
log("PERMS: ", [...memberBotPermissions.values()]); |
279 |
log("PERMS 2: ", memberRequiredPermissions); |
280 |
|
281 |
for (const memberRequiredPermission of memberRequiredPermissions) { |
282 |
if (!memberBotPermissions.has(memberRequiredPermission)) { |
283 |
await message.reply({ |
284 |
content: `${this.emoji("error")} You don't have permission to run this command.`, |
285 |
ephemeral: true |
286 |
}); |
287 |
|
288 |
return; |
289 |
} |
290 |
} |
291 |
} |
292 |
} |
293 |
} else |
294 |
orMode: { |
295 |
for (const permission of permissions) { |
296 |
if (member.permissions.has(permission, true)) { |
297 |
break orMode; |
298 |
} |
299 |
} |
300 |
|
301 |
if (mode === "advanced" || mode === "levels") { |
302 |
for (const memberRequiredPermission of memberRequiredPermissions) { |
303 |
if (memberBotPermissions.has(memberRequiredPermission)) { |
304 |
break orMode; |
305 |
} |
306 |
} |
307 |
} |
308 |
|
309 |
await message.reply({ |
310 |
content: `${this.emoji("error")} You don't have enough permissions to run this command.`, |
311 |
ephemeral: true |
312 |
}); |
313 |
|
314 |
return; |
315 |
} |
316 |
} |
317 |
|
318 |
const permissionOverwrite = this.client.commandManager.permissionOverwrites.get( |
319 |
`${message.guildId!}____${this.name}` |
320 |
); |
321 |
|
322 |
log([...this.client.commandManager.permissionOverwrites.keys()]); |
323 |
|
324 |
errorRootBlock: if (permissionOverwrite) { |
325 |
let userCheckPassed = false; |
326 |
let levelCheckPassed = false; |
327 |
let roleCheckPassed = false; |
328 |
let permissonCheckPassed = false; |
329 |
|
330 |
permissionOverwriteIfBlock: { |
331 |
if (permissionOverwrite.requiredUsers.length > 0) { |
332 |
userCheckPassed = permissionOverwrite.requiredUsers.includes(member.user.id); |
333 |
|
334 |
if (permissionOverwrite.mode === "AND" && !userCheckPassed) { |
335 |
break permissionOverwriteIfBlock; |
336 |
} |
337 |
|
338 |
if (permissionOverwrite.mode === "OR" && userCheckPassed) { |
339 |
log("User check passed [OR]"); |
340 |
break errorRootBlock; |
341 |
} |
342 |
} |
343 |
|
344 |
if (mode === "levels" && permissionOverwrite.requiredLevel !== null) { |
345 |
const level = this.client.permissionManager.getMemberPermissionLevel(member); |
346 |
levelCheckPassed = level >= permissionOverwrite.requiredLevel; |
347 |
|
348 |
log("level", level, "<", permissionOverwrite.requiredLevel); |
349 |
|
350 |
if (!levelCheckPassed && permissionOverwrite.mode === "AND") { |
351 |
break permissionOverwriteIfBlock; |
352 |
} else if (levelCheckPassed && permissionOverwrite.mode === "OR") { |
353 |
log("Level check passed [OR]"); |
354 |
break errorRootBlock; |
355 |
} |
356 |
} |
357 |
|
358 |
if (permissionOverwrite.requiredPermissions.length > 0) { |
359 |
const requiredPermissions = permissionOverwrite.requiredPermissions as PermissionResolvable[]; |
360 |
|
361 |
if (permissionOverwrite.requiredPermissionMode === "OR") { |
362 |
let found = false; |
363 |
|
364 |
for (const permission of requiredPermissions) { |
365 |
if (member.permissions.has(permission, true)) { |
366 |
found = true; |
367 |
break; |
368 |
} |
369 |
} |
370 |
|
371 |
permissonCheckPassed = found; |
372 |
|
373 |
if (!found && permissionOverwrite.mode === "AND") { |
374 |
break permissionOverwriteIfBlock; |
375 |
} else if (found && permissionOverwrite.mode === "OR") { |
376 |
log("Permission check passed [OR_OR]"); |
377 |
break errorRootBlock; |
378 |
} |
379 |
} else { |
380 |
log(requiredPermissions); |
381 |
permissonCheckPassed = member.permissions.has(requiredPermissions, true); |
382 |
|
383 |
if (!permissonCheckPassed && permissionOverwrite.mode === "AND") { |
384 |
log("Fail"); |
385 |
break permissionOverwriteIfBlock; |
386 |
} else if (permissonCheckPassed && permissionOverwrite.mode === "OR") { |
387 |
log("Permission check passed [AND_OR]"); |
388 |
break errorRootBlock; |
389 |
} |
390 |
} |
391 |
} |
392 |
|
393 |
if (permissionOverwrite.requiredRoles.length > 0) { |
394 |
roleCheckPassed = member.roles.cache.hasAll(...permissionOverwrite.requiredRoles); |
395 |
|
396 |
if (!roleCheckPassed && permissionOverwrite.mode === "AND") { |
397 |
break permissionOverwriteIfBlock; |
398 |
} else if (roleCheckPassed && permissionOverwrite.mode === "OR") { |
399 |
break errorRootBlock; |
400 |
} |
401 |
} |
402 |
|
403 |
log("userCheckPassed", userCheckPassed); |
404 |
log("levelCheckPassed", levelCheckPassed); |
405 |
log("permissonCheckPassed", permissonCheckPassed); |
406 |
log("roleCheckPassed", roleCheckPassed); |
407 |
|
408 |
if (permissionOverwrite.mode === "OR") { |
409 |
break permissionOverwriteIfBlock; |
410 |
} |
411 |
|
412 |
if ( |
413 |
permissionOverwrite.requiredChannels.length > 0 && |
414 |
!permissionOverwrite.requiredChannels.includes(message.channelId!) |
415 |
) { |
416 |
await message.reply({ |
417 |
content: `${this.emoji("error")} This command is disabled in this channel.`, |
418 |
ephemeral: true |
419 |
}); |
420 |
|
421 |
return; |
422 |
} |
423 |
|
424 |
break errorRootBlock; |
425 |
} |
426 |
|
427 |
await message.reply({ |
428 |
content: `${this.emoji("error")} You don't have enough permissions to run this command.`, |
429 |
ephemeral: true |
430 |
}); |
431 |
|
432 |
return; |
433 |
} |
434 |
} |
435 |
|
436 |
if (context.isLegacy) { |
437 |
let index = 0; |
438 |
|
439 |
loop: for await (const rule of validationRules) { |
440 |
const arg = context.args[index]; |
441 |
|
442 |
if (arg === undefined) { |
443 |
if (!rule.optional) { |
444 |
await this.error(message, rule.requiredErrorMessage ?? `Argument #${index} is required`); |
445 |
return; |
446 |
} |
447 |
|
448 |
if (rule.default !== undefined) { |
449 |
parsedArgs.push(rule.default); |
450 |
} |
451 |
|
452 |
continue; |
453 |
} |
454 |
|
455 |
if (rule.types) { |
456 |
const prevLengthOuter = parsedArgs.length; |
457 |
|
458 |
for (const type of rule.types) { |
459 |
const prevLength = parsedArgs.length; |
460 |
|
461 |
if ( |
462 |
/^(\-)?[\d\.]+$/.test(arg) && |
463 |
(((rule.minValue || rule.maxValue) && type === ArgumentType.Float) || |
464 |
type === ArgumentType.Integer || |
465 |
type === ArgumentType.Number) |
466 |
) { |
467 |
const float = parseFloat(arg); |
468 |
|
469 |
if ( |
470 |
!isNaN(float) && |
471 |
((rule.minValue !== undefined && rule.minValue > float) || |
472 |
(rule.maxValue !== undefined && rule.maxValue < float)) |
473 |
) { |
474 |
await message.reply( |
475 |
rule.minMaxErrorMessage ?? |
476 |
`Argument #${index} has a min/max numeric value range but the given value is out of range.` |
477 |
); |
478 |
return; |
479 |
} |
480 |
} |
481 |
|
482 |
switch (type) { |
483 |
case ArgumentType.Boolean: |
484 |
if (["true", "false"].includes(arg.toLowerCase())) { |
485 |
parsedArgs[index] = arg.toLowerCase() === "true"; |
486 |
} |
487 |
|
488 |
break; |
489 |
|
490 |
case ArgumentType.Float: |
491 |
const float = parseFloat(arg); |
492 |
|
493 |
if (isNaN(float)) { |
494 |
break; |
495 |
} |
496 |
|
497 |
parsedArgs[index] = float; |
498 |
break; |
499 |
|
500 |
case ArgumentType.Integer: |
501 |
if (!/^(\-)?\d+$/.test(arg)) { |
502 |
break; |
503 |
} |
504 |
|
505 |
const int = parseInt(arg); |
506 |
|
507 |
if (isNaN(int)) { |
508 |
break; |
509 |
} |
510 |
|
511 |
parsedArgs[index] = int; |
512 |
break; |
513 |
|
514 |
case ArgumentType.Number: |
515 |
const number = arg.includes(".") ? parseFloat(arg) : parseInt(arg); |
516 |
|
517 |
if (isNaN(number)) { |
518 |
break; |
519 |
} |
520 |
|
521 |
parsedArgs[index] = number; |
522 |
break; |
523 |
|
524 |
case ArgumentType.TimeInterval: |
525 |
const { result, error } = stringToTimeInterval(arg, { |
526 |
milliseconds: rule.timeMilliseconds ?? false |
527 |
}); |
528 |
|
529 |
if (error) { |
530 |
if (rule.types.length === 1) { |
531 |
await message |
532 |
.reply({ |
533 |
ephemeral: true, |
534 |
content: `${this.emoji("error")} ${error}` |
535 |
}) |
536 |
.catch(logError); |
537 |
|
538 |
return; |
539 |
} |
540 |
|
541 |
break; |
542 |
} |
543 |
|
544 |
if ( |
545 |
!isNaN(result) && |
546 |
((rule.minValue !== undefined && rule.minValue > result) || |
547 |
(rule.maxValue !== undefined && rule.maxValue < result)) |
548 |
) { |
549 |
await message.reply( |
550 |
`${this.emoji("error")} ` + rule.minMaxErrorMessage ?? |
551 |
`Argument #${index} has a min/max numeric time value range but the given value is out of range.` |
552 |
); |
553 |
return; |
554 |
} |
555 |
|
556 |
parsedArgs[index] = result; |
557 |
break; |
558 |
|
559 |
case ArgumentType.Link: |
560 |
try { |
561 |
parsedArgs[index] = new URL(arg); |
562 |
} catch (e) { |
563 |
break; |
564 |
} |
565 |
|
566 |
break; |
567 |
|
568 |
case ArgumentType.String: |
569 |
if (arg.trim() === "") break; |
570 |
|
571 |
parsedArgs[index] = arg; |
572 |
break; |
573 |
|
574 |
case ArgumentType.Snowflake: |
575 |
if (!isSnowflake(arg)) break; |
576 |
|
577 |
parsedArgs[index] = arg; |
578 |
break; |
579 |
|
580 |
case ArgumentType.User: |
581 |
case ArgumentType.GuildMember: |
582 |
case ArgumentType.Channel: |
583 |
case ArgumentType.Role: |
584 |
// TODO: Use message.mentions object to improve performance and reduce API requests |
585 |
|
586 |
let id; |
587 |
|
588 |
if (MessageMentions.UsersPattern.test(arg)) { |
589 |
id = arg.substring(arg.includes("!") ? 3 : 2, arg.length - 1); |
590 |
} else if (MessageMentions.ChannelsPattern.test(arg)) { |
591 |
id = arg.substring(2, arg.length - 1); |
592 |
} else if (MessageMentions.RolesPattern.test(arg)) { |
593 |
id = arg.substring(3, arg.length - 1); |
594 |
} else if (isSnowflake(arg)) { |
595 |
id = arg; |
596 |
} else { |
597 |
break; |
598 |
} |
599 |
|
600 |
try { |
601 |
let entity = null; |
602 |
|
603 |
if (type === ArgumentType.User) entity = await this.client.users.fetch(id); |
604 |
else { |
605 |
entity = |
606 |
type === ArgumentType.Role |
607 |
? await message.guild!.roles.fetch(id) |
608 |
: type === ArgumentType.Channel |
609 |
? await message.guild!.channels.fetch(id) |
610 |
: await message.guild!.members.fetch(id); |
611 |
} |
612 |
|
613 |
if (!entity) { |
614 |
throw new Error("Invalid entity received"); |
615 |
} |
616 |
|
617 |
parsedArgs[index] = entity; |
618 |
} catch (e) { |
619 |
logError(e); |
620 |
|
621 |
if (rule.entityNotNull) { |
622 |
await message.reply( |
623 |
`${this.emoji("error")} ` + rule.entityNotNullErrorMessage ?? |
624 |
`Argument ${index} is invalid` |
625 |
); |
626 |
return; |
627 |
} |
628 |
|
629 |
parsedArgs[index] = null; |
630 |
} |
631 |
|
632 |
break; |
633 |
|
634 |
case ArgumentType.StringRest: |
635 |
if (arg.trim() === "") break; |
636 |
|
637 |
let str = ((message as Message).content ?? "") |
638 |
.slice(context.prefix.length) |
639 |
.trimStart() |
640 |
.slice(context.argv[0].length) |
641 |
.trimStart(); |
642 |
|
643 |
for (let i = 0; i < index; i++) { |
644 |
str = str.slice(context.args[i].length).trimStart(); |
645 |
} |
646 |
|
647 |
str = str.trimEnd(); |
648 |
|
649 |
if (str === "") break; |
650 |
|
651 |
parsedArgs[index] = str; |
652 |
|
653 |
if (rule.name) { |
654 |
parsedNamedArgs[rule.name] = parsedArgs[index]; |
655 |
} |
656 |
|
657 |
break loop; |
658 |
} |
659 |
|
660 |
if ( |
661 |
rule.lengthMax !== undefined && |
662 |
typeof parsedArgs[index] === "string" && |
663 |
parsedArgs[index].length > rule.lengthMax |
664 |
) { |
665 |
await message.reply( |
666 |
`${this.emoji("error")} ` + rule.lengthMaxErrorMessage ?? `Argument #${index} is too long` |
667 |
); |
668 |
return; |
669 |
} |
670 |
|
671 |
if (prevLength !== parsedArgs.length) { |
672 |
break; |
673 |
} |
674 |
} |
675 |
|
676 |
if (prevLengthOuter === parsedArgs.length) { |
677 |
await message.reply( |
678 |
`${this.emoji("error")} ` + rule.typeErrorMessage ?? `Argument #${index} is invalid, type mismatch` |
679 |
); |
680 |
return; |
681 |
} |
682 |
} |
683 |
|
684 |
if (rule.name) { |
685 |
parsedNamedArgs[rule.name] = parsedArgs[index]; |
686 |
} |
687 |
|
688 |
index++; |
689 |
} |
690 |
} |
691 |
|
692 |
if (!checkOnly) { |
693 |
return await this.execute(message, { |
694 |
...context, |
695 |
...(context.isLegacy |
696 |
? { |
697 |
parsedArgs, |
698 |
parsedNamedArgs |
699 |
} |
700 |
: {}) |
701 |
}); |
702 |
} |
703 |
} |
704 |
} |