From c5e271ce25d89c5c356f1e004e38b35f3b25ac6c Mon Sep 17 00:00:00 2001 From: Cat++ <69035887+NotGhex@users.noreply.github.com> Date: Sat, 3 Aug 2024 22:11:27 +0800 Subject: [PATCH 01/16] add builder --- .../classes/builders/MessageCommandBuilder.ts | 23 ++- .../builders/MessageCommandFlagBuilder.ts | 135 ++++++++++++++++++ .../validators/MessageCommandFlagValidator.ts | 114 +++++++++++++++ packages/core/src/types/structures.ts | 1 + 4 files changed, 271 insertions(+), 2 deletions(-) create mode 100644 packages/core/src/classes/builders/MessageCommandFlagBuilder.ts create mode 100644 packages/core/src/classes/validators/MessageCommandFlagValidator.ts diff --git a/packages/core/src/classes/builders/MessageCommandBuilder.ts b/packages/core/src/classes/builders/MessageCommandBuilder.ts index d9458034..8e043370 100644 --- a/packages/core/src/classes/builders/MessageCommandBuilder.ts +++ b/packages/core/src/classes/builders/MessageCommandBuilder.ts @@ -11,6 +11,7 @@ import type { RecipleClient } from '../structures/RecipleClient.js'; import { RecipleError } from '../structures/RecipleError.js'; import type { CooldownData } from '../structures/Cooldown.js'; import { getCommand } from 'fallout-utility/commands'; +import { parseArgs } from 'util'; export interface MessageCommandExecuteData { type: CommandType.MessageCommand; @@ -222,8 +223,26 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message const prefix = typeof client.config.commands?.messageCommand?.prefix === 'function' ? await Promise.resolve(client.config.commands.messageCommand.prefix({ client, message, guild: message.guild, command })) : client.config.commands?.messageCommand?.prefix; const separator = typeof client.config.commands?.messageCommand?.commandArgumentSeparator === 'function' ? await Promise.resolve(client.config.commands.messageCommand.commandArgumentSeparator({ client, message, guild: message.guild, command })) : client.config.commands?.messageCommand?.commandArgumentSeparator; - const parserData = getCommand(message.content, prefix, separator); - if (!parserData || !parserData.name) return null; + const commandData = getCommand(message.content, prefix, separator); + if (!commandData || !commandData.name) return null; + + const { positionals: args, values: flags } = parseArgs({ + args: commandData.args, + allowPositionals: true, + strict: false + }); + + const parserData = { + ...commandData as CommandData & { name: string; }, + args, + flags: Object + .entries(flags) + .filter(([key, value]) => value !== undefined) + .map(([key, value]) => ({ + name: key, + value: Array.isArray(value) ? value : [value] as (string|boolean)[] + })) + }; const builder = command ? this.resolve(command) : client.commands.get(parserData.name, CommandType.MessageCommand); if (!builder) return null; diff --git a/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts b/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts new file mode 100644 index 00000000..87f6f2aa --- /dev/null +++ b/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts @@ -0,0 +1,135 @@ +import { isJSONEncodable, normalizeArray, type Awaitable, type JSONEncodable, type Message, type RestOrArray } from 'discord.js'; +import type { CommandData } from '../../types/structures.js'; +import type { MessageCommandBuilder } from './MessageCommandBuilder.js'; +import type { RecipleClient } from '../structures/RecipleClient.js'; + +export interface MessageCommandFlagBuilderResolveValueOptions { + /** + * The values of the given flag + */ + values: V[]; + /** + * The parser data when parsing this command. + */ + parserData: CommandData; + /** + * The flag builder used to build this option. + */ + option: MessageCommandFlagBuilder; + /** + * The command builder used to build this command. + */ + command: MessageCommandBuilder; + /** + * The message that triggered this command. + */ + message: Message; + /** + * The client instance + */ + client: RecipleClient; +} + +export interface MessageCommandFlagBuilderData { + name: string; + short?: string; + description: string; + default_values?: V[]; + required?: boolean; + multiple?: boolean; + /** + * The function that validates the option value. + * @param options The option value and message. + */ + validate?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; + /** + * Resolves the option value. + * @param options The option value and message. + */ + resolve_value?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; +} + +export class MessageCommandFlagBuilder implements MessageCommandFlagBuilderData { + public name: string = ''; + public short?: string; + public description: string = ''; + public default_values?: V[]; + public required: boolean = false; + public multiple?: boolean = false; + public validate?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; + public resolve_value?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; + + constructor(data?: Partial>) { + if (data?.name) this.setName(data.name); + if (data?.short) this.setShort(data.short); + if (data?.description) this.setDescription(data.description); + if (data?.default_values) this.setDefaultValues(data.default_values); + if (data?.required) this.setRequired(data.required); + if (data?.multiple) this.setMultiple(data.multiple); + if (data?.validate) this.setValidate(data.validate); + if (data?.resolve_value) this.setResolveValue(data.resolve_value); + } + + public setName(name: string): this { + this.name = name; + return this; + } + + public setShort(short: string): this { + this.short = short; + return this; + } + + public setDescription(description: string): this { + this.description = description; + return this; + } + + public setDefaultValues(...defaultValues: RestOrArray): this { + this.default_values = normalizeArray(defaultValues); + return this; + } + + public setRequired(required: boolean): this { + this.required = required; + return this; + } + + public setMultiple(multiple: boolean): this { + this.multiple = multiple; + return this; + } + + public setValidate(validate: MessageCommandFlagBuilderData['validate']): this { + this.validate = validate; + return this; + } + + public setResolveValue(resolve_value: MessageCommandFlagBuilderData['resolve_value']): this { + this.resolve_value = resolve_value; + return this; + } + + public toJSON(): MessageCommandFlagBuilderData { + return { + name: this.name, + short: this.short, + description: this.description, + default_values: this.default_values, + required: this.required, + multiple: this.multiple, + validate: this.validate, + resolve_value: this.resolve_value + }; + } + + public static from(data: MessageCommandFlagResolvable): MessageCommandFlagBuilder { + return new MessageCommandFlagBuilder(isJSONEncodable(data) ? data.toJSON() : data); + } + + public static resolve(data: MessageCommandFlagResolvable): MessageCommandFlagBuilder { + return data instanceof MessageCommandFlagBuilder ? data : MessageCommandFlagBuilder.from(data); + } +} + +export type MessageCommandFlagResolvable = JSONEncodable>|MessageCommandFlagBuilderData; diff --git a/packages/core/src/classes/validators/MessageCommandFlagValidator.ts b/packages/core/src/classes/validators/MessageCommandFlagValidator.ts new file mode 100644 index 00000000..2286f788 --- /dev/null +++ b/packages/core/src/classes/validators/MessageCommandFlagValidator.ts @@ -0,0 +1,114 @@ +import type { MessageCommandFlagBuilderData, MessageCommandFlagResolvable } from '../builders/MessageCommandFlagBuilder.js'; +import { BaseCommandValidators } from './BaseCommandValidators.js'; +import { isJSONEncodable } from 'discord.js'; + +export class MessageCommandFlagValidators extends BaseCommandValidators { + public static name = MessageCommandFlagValidators.s + .string({ message: 'Expected string as message command flag name' }) + .lengthGreaterThanOrEqual(1, { message: 'Message command flag name needs to have at least single character' }) + .lengthLessThanOrEqual(32, { message: 'Message command flag name cannot exceed 32 characters' }) + .regex(/^[\p{Ll}\p{Lm}\p{Lo}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+$/u, { message: 'Message command flag name can only be alphanumeric without spaces' }); + + public static short = MessageCommandFlagValidators.s + .string({ message: 'Expected string as message command flag short' }) + .lengthEqual(1, { message: 'Message command flag short needs to have at least single character' }) + .optional(); + + public static description = MessageCommandFlagValidators.s + .string({ message: 'Expected string as message command flag description' }) + .lengthGreaterThanOrEqual(1, { message: 'Message command flag description needs to have at least single character' }) + .lengthLessThanOrEqual(100, { message: 'Message command flag description cannot exceed 100 characters' }); + + public static default_values = MessageCommandFlagValidators.s + .union([ + MessageCommandFlagValidators.s.string({ message: 'Expected string as message command flag default value' }), + MessageCommandFlagValidators.s.boolean({ message: 'Expected boolean as message command flag default value' }), + ]) + .array({ message: 'Expected array as message command flag default values' }) + .optional(); + + public static required = MessageCommandFlagValidators.s + .boolean({ message: 'Expected boolean for .required' }) + .optional(); + + public static multiple = MessageCommandFlagValidators.s + .boolean({ message: 'Expected boolean for .multiple' }) + .optional(); + + public static validate = MessageCommandFlagValidators.s + .instance(Function, { message: 'Expected a function for .validate' }) + .optional(); + + public static resolve_value = MessageCommandFlagValidators.s + .instance(Function, { message: 'Expected a function for .resolve_value' }) + .optional(); + + public static MessageCommandFlagBuilderData = MessageCommandFlagValidators.s.object({ + name: MessageCommandFlagValidators.name, + short: MessageCommandFlagValidators.short, + description: MessageCommandFlagValidators.description, + default_values: MessageCommandFlagValidators.default_values, + required: MessageCommandFlagValidators.required, + multiple: MessageCommandFlagValidators.multiple, + validate: MessageCommandFlagValidators.validate, + resolve_value: MessageCommandFlagValidators.resolve_value + }); + + public static MessageCommandFlagResolvable = MessageCommandFlagValidators.s.union([MessageCommandFlagValidators.MessageCommandFlagBuilderData, MessageCommandFlagValidators.jsonEncodable]); + + public static isValidName(name: unknown): asserts name is MessageCommandFlagBuilderData['name'] { + MessageCommandFlagValidators.name.setValidationEnabled(MessageCommandFlagValidators.isValidationEnabled).parse(name); + } + + public static isValidShort(short: unknown): asserts short is MessageCommandFlagBuilderData['short'] { + MessageCommandFlagValidators.short.setValidationEnabled(MessageCommandFlagValidators.isValidationEnabled).parse(short); + } + + public static isValidDescription(description: unknown): asserts description is MessageCommandFlagBuilderData['description'] { + MessageCommandFlagValidators.description.setValidationEnabled(MessageCommandFlagValidators.isValidationEnabled).parse(description); + } + + public static isValidDefaultValues(defaultValues: unknown): asserts defaultValues is MessageCommandFlagBuilderData['default_values'] { + MessageCommandFlagValidators.default_values.setValidationEnabled(MessageCommandFlagValidators.isValidationEnabled).parse(defaultValues); + } + + public static isValidRequired(required: unknown): asserts required is MessageCommandFlagBuilderData['required'] { + MessageCommandFlagValidators.required.setValidationEnabled(MessageCommandFlagValidators.isValidationEnabled).parse(required); + } + + public static isValidMultiple(multiple: unknown): asserts multiple is MessageCommandFlagBuilderData['multiple'] { + MessageCommandFlagValidators.multiple.setValidationEnabled(MessageCommandFlagValidators.isValidationEnabled).parse(multiple); + } + + public static isValidValidate(validate: unknown): asserts validate is MessageCommandFlagBuilderData['validate'] { + MessageCommandFlagValidators.validate.setValidationEnabled(MessageCommandFlagValidators.isValidationEnabled).parse(validate); + } + + public static isValidResolveValue(resolveValue: unknown): asserts resolveValue is MessageCommandFlagBuilderData['resolve_value'] { + MessageCommandFlagValidators.resolve_value.setValidationEnabled(MessageCommandFlagValidators.isValidationEnabled).parse(resolveValue); + } + + public static isValidMessageCommandFlagBuilderData(data: unknown): asserts data is MessageCommandFlagBuilderData { + const opt = data as MessageCommandFlagBuilderData; + + MessageCommandFlagValidators.isValidName(opt.name); + MessageCommandFlagValidators.isValidShort(opt.short); + MessageCommandFlagValidators.isValidDescription(opt.description); + MessageCommandFlagValidators.isValidDefaultValues(opt.default_values); + MessageCommandFlagValidators.isValidRequired(opt.required); + MessageCommandFlagValidators.isValidMultiple(opt.multiple); + MessageCommandFlagValidators.isValidValidate(opt.validate); + MessageCommandFlagValidators.isValidResolveValue(opt.resolve_value); + } + + public static isValidMessageCommandFlagResolvable(data: unknown): asserts data is MessageCommandFlagResolvable { + const opt = data as MessageCommandFlagResolvable; + + if (isJSONEncodable(opt)) { + const i = opt.toJSON(); + MessageCommandFlagValidators.isValidMessageCommandFlagBuilderData(i); + } else { + MessageCommandFlagValidators.isValidMessageCommandFlagBuilderData(opt); + } + } +} diff --git a/packages/core/src/types/structures.ts b/packages/core/src/types/structures.ts index 3fcdf245..1da7cea7 100644 --- a/packages/core/src/types/structures.ts +++ b/packages/core/src/types/structures.ts @@ -109,6 +109,7 @@ export interface CommandPreconditionResultHaltTriggerData export interface CommandData { name?: string; prefix?: string; + flags: { name: string; value: (string|boolean)[]; }[]; args: string[]; raw: string; rawArgs: string; From 81929377435bc166287ffef45f439800e52895ad Mon Sep 17 00:00:00 2001 From: Cat++ <69035887+NotGhex@users.noreply.github.com> Date: Sun, 4 Aug 2024 12:01:01 +0800 Subject: [PATCH 02/16] validator --- .../builders/ContextMenuCommandBuilder.ts | 2 +- .../classes/builders/MessageCommandBuilder.ts | 25 +++- .../builders/MessageCommandFlagBuilder.ts | 21 ++- .../builders/MessageCommandOptionBuilder.ts | 2 +- .../classes/builders/SlashCommandBuilder.ts | 2 +- .../managers/MessageCommandFlagManager.ts | 77 +++++++++++ .../managers/MessageCommandOptionManager.ts | 20 ++- .../src/classes/structures/CommandHalt.ts | 2 +- .../classes/structures/CommandPrecondition.ts | 2 +- .../structures/MessageCommandFlagValue.ts | 129 ++++++++++++++++++ .../validators/MessageCommandFlagValidator.ts | 9 +- 11 files changed, 271 insertions(+), 20 deletions(-) create mode 100644 packages/core/src/classes/managers/MessageCommandFlagManager.ts create mode 100644 packages/core/src/classes/structures/MessageCommandFlagValue.ts diff --git a/packages/core/src/classes/builders/ContextMenuCommandBuilder.ts b/packages/core/src/classes/builders/ContextMenuCommandBuilder.ts index 33f6f148..61fb6a13 100644 --- a/packages/core/src/classes/builders/ContextMenuCommandBuilder.ts +++ b/packages/core/src/classes/builders/ContextMenuCommandBuilder.ts @@ -93,7 +93,7 @@ export class ContextMenuCommandBuilder extends Mixin(DiscordJsContextMenuCommand } public static resolve(data: ContextMenuCommandResolvable): ContextMenuCommandBuilder { - return data instanceof ContextMenuCommandBuilder ? data : this.from(data); + return data instanceof ContextMenuCommandBuilder ? data : ContextMenuCommandBuilder.from(data); } public static async execute({ client, interaction, command }: ContextMenuCommandExecuteOptions): Promise { diff --git a/packages/core/src/classes/builders/MessageCommandBuilder.ts b/packages/core/src/classes/builders/MessageCommandBuilder.ts index 8e043370..3ed4ad67 100644 --- a/packages/core/src/classes/builders/MessageCommandBuilder.ts +++ b/packages/core/src/classes/builders/MessageCommandBuilder.ts @@ -12,6 +12,8 @@ import { RecipleError } from '../structures/RecipleError.js'; import type { CooldownData } from '../structures/Cooldown.js'; import { getCommand } from 'fallout-utility/commands'; import { parseArgs } from 'util'; +import { MessageCommandFlagBuilder, type MessageCommandFlagResolvable } from './MessageCommandFlagBuilder.js'; +import { MessageCommandFlagValidators } from '../validators/MessageCommandFlagValidator.js'; export interface MessageCommandExecuteData { type: CommandType.MessageCommand; @@ -62,6 +64,10 @@ export interface MessageCommandBuilderData extends BaseCommandBuilderData { * The options of the command. */ options?: MessageCommandOptionResolvable[]; + /** + * The flags of the command. + */ + flags?: MessageCommandFlagResolvable[]; } export interface MessageCommandBuilder extends BaseCommandBuilder { @@ -81,6 +87,7 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message public dm_permission: boolean = false; public allow_bot: boolean = false; public options: MessageCommandOptionBuilder[] = []; + public flags: MessageCommandFlagBuilder[] = []; constructor(data?: Omit, 'command_type'>) { super(data); @@ -171,7 +178,7 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message * @param option Option data or builder. */ public addOption(option: MessageCommandOptionResolvable|((builder: MessageCommandOptionBuilder) => MessageCommandOptionBuilder)): this { - const opt = typeof option === 'function' ? option(new MessageCommandOptionBuilder()) : MessageCommandOptionBuilder.from(option); + const opt = typeof option === 'function' ? option(new MessageCommandOptionBuilder()) : MessageCommandOptionBuilder.resolve(option); MessageCommandOptionValidators.isValidMessageCommandOptionResolvable(opt); if (this.options.find(o => o.name === opt.name)) throw new RecipleError('An option with name "' + opt.name + '" already exists.'); @@ -197,6 +204,16 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message return this; } + public addFlag(option: MessageCommandFlagResolvable|((builder: MessageCommandFlagBuilder) => MessageCommandFlagBuilder)): this { + const opt = typeof option === 'function' ? option(new MessageCommandFlagBuilder()) : MessageCommandFlagBuilder.resolve(option); + MessageCommandFlagValidators.isValidMessageCommandFlagResolvable(opt); + + if (this.flags.find(o => o.name === opt.name)) throw new RecipleError('A flag with name "' + opt.name + '" already exists.'); + + this.flags.push(MessageCommandFlagBuilder.resolve(opt)); + return this; + } + public toJSON(): MessageCommandBuilderData { return { name: this.name, @@ -215,7 +232,7 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message } public static resolve(data: MessageCommandResolvable): MessageCommandBuilder { - return data instanceof MessageCommandBuilder ? data : this.from(data); + return data instanceof MessageCommandBuilder ? data : MessageCommandBuilder.from(data); } public static async execute({ client, message, command }: MessageCommandExecuteOptions): Promise { @@ -229,7 +246,7 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message const { positionals: args, values: flags } = parseArgs({ args: commandData.args, allowPositionals: true, - strict: false + strict: false, }); const parserData = { @@ -240,7 +257,7 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message .filter(([key, value]) => value !== undefined) .map(([key, value]) => ({ name: key, - value: Array.isArray(value) ? value : [value] as (string|boolean)[] + value: Array.isArray(value) ? value : [value] as (string|boolean)[], })) }; diff --git a/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts b/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts index 87f6f2aa..82b434cb 100644 --- a/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts +++ b/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts @@ -2,6 +2,7 @@ import { isJSONEncodable, normalizeArray, type Awaitable, type JSONEncodable, ty import type { CommandData } from '../../types/structures.js'; import type { MessageCommandBuilder } from './MessageCommandBuilder.js'; import type { RecipleClient } from '../structures/RecipleClient.js'; +import { MessageCommandFlagValidators } from '../validators/MessageCommandFlagValidator.js'; export interface MessageCommandFlagBuilderResolveValueOptions { /** @@ -46,7 +47,7 @@ export interface MessageCommandFlagBuilderData) => Awaitable; + resolve_value?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; } export class MessageCommandFlagBuilder implements MessageCommandFlagBuilderData { @@ -57,7 +58,7 @@ export class MessageCommandFlagBuilder) => Awaitable; - public resolve_value?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; + public resolve_value?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; constructor(data?: Partial>) { if (data?.name) this.setName(data.name); @@ -71,41 +72,50 @@ export class MessageCommandFlagBuilder): this { - this.default_values = normalizeArray(defaultValues); + defaultValues = normalizeArray(defaultValues); + MessageCommandFlagValidators.isValidDefaultValues(defaultValues); + this.default_values = defaultValues; return this; } public setRequired(required: boolean): this { + MessageCommandFlagValidators.isValidRequired(required); this.required = required; return this; } public setMultiple(multiple: boolean): this { + MessageCommandFlagValidators.isValidMultiple(multiple); this.multiple = multiple; return this; } public setValidate(validate: MessageCommandFlagBuilderData['validate']): this { + MessageCommandFlagValidators.isValidValidate(validate); this.validate = validate; return this; } public setResolveValue(resolve_value: MessageCommandFlagBuilderData['resolve_value']): this { + MessageCommandFlagValidators.isValidResolveValue(resolve_value); this.resolve_value = resolve_value; return this; } @@ -123,6 +133,11 @@ export class MessageCommandFlagBuilder(data: MessageCommandFlagResolvable): MessageCommandFlagBuilder { return new MessageCommandFlagBuilder(isJSONEncodable(data) ? data.toJSON() : data); } diff --git a/packages/core/src/classes/builders/MessageCommandOptionBuilder.ts b/packages/core/src/classes/builders/MessageCommandOptionBuilder.ts index 26d884a2..5f305b49 100644 --- a/packages/core/src/classes/builders/MessageCommandOptionBuilder.ts +++ b/packages/core/src/classes/builders/MessageCommandOptionBuilder.ts @@ -137,7 +137,7 @@ export class MessageCommandOptionBuilder implements Message } public static resolve(data: MessageCommandOptionResolvable): MessageCommandOptionBuilder { - return data instanceof MessageCommandOptionBuilder ? data : this.from(data); + return data instanceof MessageCommandOptionBuilder ? data : MessageCommandOptionBuilder.from(data); } } diff --git a/packages/core/src/classes/builders/SlashCommandBuilder.ts b/packages/core/src/classes/builders/SlashCommandBuilder.ts index 61167bef..60eda726 100644 --- a/packages/core/src/classes/builders/SlashCommandBuilder.ts +++ b/packages/core/src/classes/builders/SlashCommandBuilder.ts @@ -213,7 +213,7 @@ export class SlashCommandBuilder extends Mixin(DiscordJsSlashCommandBuilder, Bas } public static resolve(data: SlashCommandResolvable): AnySlashCommandBuilder { - return data instanceof SlashCommandBuilder ? data : this.from(data); + return data instanceof SlashCommandBuilder ? data : SlashCommandBuilder.from(data); } public static async execute({ client, interaction, command }: SlashCommandExecuteOptions): Promise { diff --git a/packages/core/src/classes/managers/MessageCommandFlagManager.ts b/packages/core/src/classes/managers/MessageCommandFlagManager.ts new file mode 100644 index 00000000..6013e29b --- /dev/null +++ b/packages/core/src/classes/managers/MessageCommandFlagManager.ts @@ -0,0 +1,77 @@ +import type { Message } from 'discord.js'; +import type { MessageCommandBuilder } from '../builders/MessageCommandBuilder.js'; +import type { MessageCommandFlagValue } from '../structures/MessageCommandFlagValue.js'; +import type { RecipleClient } from '../structures/RecipleClient.js'; +import { DataManager } from './DataManager.js'; +import type { MessageCommandOptionManagerOptions } from './MessageCommandOptionManager.js'; +import type { CommandData } from '../../types/structures.js'; +import { RecipleError } from '../structures/RecipleError.js'; + +export class MessageCommandFlagManager extends DataManager implements MessageCommandOptionManagerOptions { + readonly command: MessageCommandBuilder; + readonly client: RecipleClient; + readonly message: Message; + readonly parserData: CommandData; + + constructor(data: MessageCommandOptionManagerOptions) { + super(); + + this.command = data.command; + this.client = data.client; + this.message = data.message; + this.parserData = data.parserData; + } + + get hasMissingFlags() { + return this.cache.some(o => o.missing); + } + + get hasInvalidFlags() { + return this.cache.some(o => o.invalid); + } + + get hasValidationErrors() { + return this.cache.some(o => o.error); + } + + get missingFlags() { + return this.cache.filter(o => o.missing); + } + + get invalidFlags() { + return this.cache.filter(o => o.invalid); + } + + /** + * Retrieves the value of a message command flag. + * + * @param {string} name - The name of the flag. + * @param {boolean} required - Whether the flag is required or not. + * @return {MessageCommandOptionValue|null} The value of the message command flag. + */ + public getFlag(name: string, required: true): MessageCommandFlagValue; + public getFlag(name: string, required?: boolean): MessageCommandFlagValue|null; + public getFlag(name: string, required: boolean = false): MessageCommandFlagValue|null { + const flag = this.cache.get(name) as MessageCommandFlagValue|undefined; + if (required && !flag) throw new RecipleError(`Unable to find required message flag '${name}'`); + + return flag ?? null; + } + + /** + * Obtains the value of a message command flag. + * @param name The name of the flag. + * @param options The flags to use when parsing the flag value. + * @param options.required Whether the flag is required or not. + * @param options.resolveValue Whether to resolve the value or not. + */ + public getFlagValues(name: string, options?: { required?: boolean; resolveValue?: false; }): V[]; + public getFlagValues(name: string, options?: { required?: boolean; resolveValue?: true; }): Promise; + public getFlagValues(name: string, options?: { required?: boolean; resolveValue?: boolean; }): Promise|V[]; + public getFlagValues(name: string, options: { required?: boolean; resolveValue?: boolean; } = { required: false, resolveValue: false }): Promise|V[] { + const value = this.getFlag(name, options.required); + return options.resolveValue + ? Promise.resolve(value?.resolveValues()).then(v => v ?? []) + : value?.values ?? []; + } +} diff --git a/packages/core/src/classes/managers/MessageCommandOptionManager.ts b/packages/core/src/classes/managers/MessageCommandOptionManager.ts index 2d9832aa..1f5b81f7 100644 --- a/packages/core/src/classes/managers/MessageCommandOptionManager.ts +++ b/packages/core/src/classes/managers/MessageCommandOptionManager.ts @@ -95,8 +95,8 @@ export class MessageCommandOptionManager extends DataManager|null} The value of the message command option. */ - public getOption(name: string, required?: false): MessageCommandOptionValue; - public getOption(name: string, required?: true): MessageCommandOptionValue; + public getOption(name: string, required: true): MessageCommandOptionValue; + public getOption(name: string, required?: boolean): MessageCommandOptionValue|null; public getOption(name: string, required: boolean = false): MessageCommandOptionValue|null { const option = this.cache.get(name); if (required && !option) throw new RecipleError(`Unable to find required message option '${name}'`); @@ -113,11 +113,21 @@ export class MessageCommandOptionManager extends DataManager(name: string, options?: { required?: false; resolveValue?: false; }): string|null; public getOptionValue(name: string, options?: { required?: true; resolveValue?: false; }): string; - public getOptionValue(name: string, options?: { required?: true; resolveValue?: true; }): Promise; + public getOptionValue(name: string, options?: { required?: boolean; resolveValue?: false; }): string|null; public getOptionValue(name: string, options?: { required?: false; resolveValue?: true; }): Promise; + public getOptionValue(name: string, options?: { required?: true; resolveValue?: true; }): Promise; + public getOptionValue(name: string, options?: { required?: boolean; resolveValue?: true; }): Promise; + public getOptionValue(name: string, options?: { required?: boolean; resolveValue?: boolean; }): string|null|Promise; public getOptionValue(name: string, options: { required?: boolean; resolveValue?: boolean; } = { required: false, resolveValue: false }): string|null|Promise { - const value = this.getOption(name); - return options.resolveValue ? Promise.resolve(value.resolveValue(options.required) ?? value.value) : value.value; + const value = this.getOption(name, options.required); + + if (options.resolveValue) { + if (!value) return Promise.resolve(null); + + return value.resolveValue(options.required) ?? null; + } + + return value?.value ?? null; } public toJSON() { diff --git a/packages/core/src/classes/structures/CommandHalt.ts b/packages/core/src/classes/structures/CommandHalt.ts index 7d3824e3..56ade0ab 100644 --- a/packages/core/src/classes/structures/CommandHalt.ts +++ b/packages/core/src/classes/structures/CommandHalt.ts @@ -126,7 +126,7 @@ export class CommandHalt implements CommandHaltData { } public static resolve(data: CommandHaltResolvable): CommandHalt { - return data instanceof CommandHalt ? data : this.from(data); + return data instanceof CommandHalt ? data : CommandHalt.from(data); } } diff --git a/packages/core/src/classes/structures/CommandPrecondition.ts b/packages/core/src/classes/structures/CommandPrecondition.ts index c50ae104..5cddc6dd 100644 --- a/packages/core/src/classes/structures/CommandPrecondition.ts +++ b/packages/core/src/classes/structures/CommandPrecondition.ts @@ -111,7 +111,7 @@ export class CommandPrecondition implements CommandPreconditionData { } public static resolve(data: CommandPreconditionResolvable): CommandPrecondition { - return data instanceof CommandPrecondition ? data : this.from(data); + return data instanceof CommandPrecondition ? data : CommandPrecondition.from(data); } } diff --git a/packages/core/src/classes/structures/MessageCommandFlagValue.ts b/packages/core/src/classes/structures/MessageCommandFlagValue.ts new file mode 100644 index 00000000..4da9d092 --- /dev/null +++ b/packages/core/src/classes/structures/MessageCommandFlagValue.ts @@ -0,0 +1,129 @@ +import type { Message } from 'discord.js'; +import type { CommandData } from '../../types/structures.js'; +import type { MessageCommandBuilder } from '../builders/MessageCommandBuilder.js'; +import type { MessageCommandFlagBuilder, MessageCommandFlagBuilderResolveValueOptions } from '../builders/MessageCommandFlagBuilder.js'; +import type { RecipleClient } from './RecipleClient.js'; + +export interface MessageCommandFlagValueData { + /** + * The name of the option. + */ + name: string; + /** + * The builder that is used to create the option. + */ + option: MessageCommandFlagBuilder; + /** + * The raw value of the option. + */ + values: V[]; + /** + * Whether the option is missing. + */ + missing: boolean; + /** + * Whether the option is invalid. + */ + invalid: boolean; + /** + * The error that occurred while parsing the option. + */ + error?: string|Error; +} + +export interface MessageCommandFlagParseOptionValueOptions extends Omit, 'values'> { + values?: V[]|null; +} + +export interface MessageCommandFlagValueOptions extends MessageCommandFlagValueData, Pick, 'parserData'|'command'> { + client: RecipleClient; + message: Message; +} + +export class MessageCommandFlagValue implements MessageCommandFlagValueData { + readonly name: string; + readonly option: MessageCommandFlagBuilder; + readonly values: V[]; + readonly missing: boolean; + readonly invalid: boolean; + readonly message: Message; + readonly error?: Error; + + readonly parserData: CommandData; + readonly command: MessageCommandBuilder; + readonly client: RecipleClient; + + constructor(options: MessageCommandFlagValueOptions) { + this.name = options.name; + this.option = options.option; + this.values = options.values; + this.missing = options.missing; + this.invalid = options.invalid; + this.message = options.message; + this.error = typeof options.error === 'string' ? new Error(options.error) : options.error; + this.parserData = options.parserData; + this.command = options.command; + this.client = options.client; + } + + /** + * Resolves the raw value of the option. + * @param required Whether the option is required. + */ + public async resolveValues(): Promise { + if (this.values.length) return []; + + return this.option.resolve_value + ? Promise.resolve(this.option.resolve_value({ + values: this.values, + option: this.option, + parserData: this.parserData, + command: this.command, + message: this.message, + client: this.client, + })) + : []; + } + + public toJSON(): MessageCommandFlagValueData { + return { + name: this.name, + option: this.option, + values: this.values, + missing: this.missing, + invalid: this.invalid, + error: this.error, + } + } + + public static async parseFlagValue(options: MessageCommandFlagParseOptionValueOptions): Promise> { + const missing = !!options.option.required && !options.values?.length; + const validateData = !missing + ? options.option.validate && options.values?.length + ? await Promise.resolve(options.option.validate({ + values: options.values, + option: options.option, + parserData: options.parserData, + command: options.command, + message: options.message, + client: options.client, + })) + : true + : false; + + return new MessageCommandFlagValue({ + name: options.option.name, + option: options.option, + values: options.values ?? [], + missing, + invalid: !validateData, + message: options.message, + error: typeof validateData !== 'boolean' + ? typeof validateData === 'string' ? new Error(validateData) : validateData + : undefined, + parserData: options.parserData, + command: options.command, + client: options.client, + }) + } +} diff --git a/packages/core/src/classes/validators/MessageCommandFlagValidator.ts b/packages/core/src/classes/validators/MessageCommandFlagValidator.ts index 2286f788..4944f931 100644 --- a/packages/core/src/classes/validators/MessageCommandFlagValidator.ts +++ b/packages/core/src/classes/validators/MessageCommandFlagValidator.ts @@ -21,10 +21,13 @@ export class MessageCommandFlagValidators extends BaseCommandValidators { public static default_values = MessageCommandFlagValidators.s .union([ - MessageCommandFlagValidators.s.string({ message: 'Expected string as message command flag default value' }), - MessageCommandFlagValidators.s.boolean({ message: 'Expected boolean as message command flag default value' }), + MessageCommandFlagValidators.s + .string({ message: 'Expected string as message command flag default value' }) + .array({ message: 'Expected array as message command flag default values' }), + MessageCommandFlagValidators.s + .boolean({ message: 'Expected boolean as message command flag default value' }) + .array({ message: 'Expected array as message command flag default values' }), ]) - .array({ message: 'Expected array as message command flag default values' }) .optional(); public static required = MessageCommandFlagValidators.s From d9eee963cdb655aba6be1226f53dd227facd951d Mon Sep 17 00:00:00 2001 From: Cat++ <69035887+NotGhex@users.noreply.github.com> Date: Sun, 4 Aug 2024 12:26:26 +0800 Subject: [PATCH 03/16] check --- .../classes/builders/MessageCommandBuilder.ts | 18 ++++++++++-- .../builders/MessageCommandFlagBuilder.ts | 11 ++++++- .../structures/MessageCommandFlagValue.ts | 29 ++++++++++--------- .../validators/MessageCommandFlagValidator.ts | 9 ++++++ 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/packages/core/src/classes/builders/MessageCommandBuilder.ts b/packages/core/src/classes/builders/MessageCommandBuilder.ts index 3ed4ad67..da5885b6 100644 --- a/packages/core/src/classes/builders/MessageCommandBuilder.ts +++ b/packages/core/src/classes/builders/MessageCommandBuilder.ts @@ -243,10 +243,25 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message const commandData = getCommand(message.content, prefix, separator); if (!commandData || !commandData.name) return null; + const builder = command ? this.resolve(command) : client.commands.get(commandData.name, CommandType.MessageCommand); + if (!builder) return null; + const { positionals: args, values: flags } = parseArgs({ args: commandData.args, allowPositionals: true, strict: false, + options: Object.fromEntries( + builder.flags + .map((o) => [ + o.name, + { + type: MessageCommandFlagBuilder.getFlagValuesType(o.default_values ?? []), + multiple: o.multiple, + short: o.short, + default: o.multiple ? o.default_values as string[] : o.default_values?.[0], + } + ]) + ), }); const parserData = { @@ -261,9 +276,6 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message })) }; - const builder = command ? this.resolve(command) : client.commands.get(parserData.name, CommandType.MessageCommand); - if (!builder) return null; - const executeData: MessageCommandExecuteData = { type: builder.command_type, client, diff --git a/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts b/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts index 82b434cb..6e34019a 100644 --- a/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts +++ b/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts @@ -16,7 +16,7 @@ export interface MessageCommandFlagBuilderResolveValueOptions; + flag: MessageCommandFlagBuilder; /** * The command builder used to build this command. */ @@ -37,6 +37,7 @@ export interface MessageCommandFlagBuilderData) => Awaitable; public resolve_value?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; @@ -66,6 +68,7 @@ export class MessageCommandFlagBuilder; + flag: MessageCommandFlagBuilder; /** * The raw value of the option. */ @@ -42,7 +42,7 @@ export interface MessageCommandFlagValueOptions implements MessageCommandFlagValueData { readonly name: string; - readonly option: MessageCommandFlagBuilder; + readonly flag: MessageCommandFlagBuilder; readonly values: V[]; readonly missing: boolean; readonly invalid: boolean; @@ -55,7 +55,7 @@ export class MessageCommandFlagValue) { this.name = options.name; - this.option = options.option; + this.flag = options.flag; this.values = options.values; this.missing = options.missing; this.invalid = options.invalid; @@ -73,10 +73,10 @@ export class MessageCommandFlagValue { if (this.values.length) return []; - return this.option.resolve_value - ? Promise.resolve(this.option.resolve_value({ + return this.flag.resolve_value + ? Promise.resolve(this.flag.resolve_value({ values: this.values, - option: this.option, + flag: this.flag, parserData: this.parserData, command: this.command, message: this.message, @@ -88,7 +88,7 @@ export class MessageCommandFlagValue { return { name: this.name, - option: this.option, + flag: this.flag, values: this.values, missing: this.missing, invalid: this.invalid, @@ -97,12 +97,15 @@ export class MessageCommandFlagValue(options: MessageCommandFlagParseOptionValueOptions): Promise> { - const missing = !!options.option.required && !options.values?.length; + const missing = options.values + ? !!options.flag.required && !options.values?.length + : options.flag.mandatory ?? false; + const validateData = !missing - ? options.option.validate && options.values?.length - ? await Promise.resolve(options.option.validate({ + ? options.flag.validate && options.values?.length + ? await Promise.resolve(options.flag.validate({ values: options.values, - option: options.option, + flag: options.flag, parserData: options.parserData, command: options.command, message: options.message, @@ -112,8 +115,8 @@ export class MessageCommandFlagValue Date: Sun, 4 Aug 2024 12:36:31 +0800 Subject: [PATCH 04/16] some data --- .../classes/builders/MessageCommandBuilder.ts | 8 +++++ .../managers/MessageCommandFlagManager.ts | 32 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/core/src/classes/builders/MessageCommandBuilder.ts b/packages/core/src/classes/builders/MessageCommandBuilder.ts index da5885b6..4f6721b7 100644 --- a/packages/core/src/classes/builders/MessageCommandBuilder.ts +++ b/packages/core/src/classes/builders/MessageCommandBuilder.ts @@ -14,6 +14,7 @@ import { getCommand } from 'fallout-utility/commands'; import { parseArgs } from 'util'; import { MessageCommandFlagBuilder, type MessageCommandFlagResolvable } from './MessageCommandFlagBuilder.js'; import { MessageCommandFlagValidators } from '../validators/MessageCommandFlagValidator.js'; +import { MessageCommandFlagManager } from '../managers/MessageCommandFlagManager.js'; export interface MessageCommandExecuteData { type: CommandType.MessageCommand; @@ -21,6 +22,7 @@ export interface MessageCommandExecuteData { message: Message; parserData: CommandData; options: MessageCommandOptionManager; + flags: MessageCommandFlagManager; builder: MessageCommandBuilder; } @@ -287,6 +289,12 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message message, parserData, client + }), + flags: await MessageCommandFlagManager.parseFlags({ + command: builder, + message, + parserData, + client }) }; diff --git a/packages/core/src/classes/managers/MessageCommandFlagManager.ts b/packages/core/src/classes/managers/MessageCommandFlagManager.ts index 6013e29b..81365e5b 100644 --- a/packages/core/src/classes/managers/MessageCommandFlagManager.ts +++ b/packages/core/src/classes/managers/MessageCommandFlagManager.ts @@ -1,18 +1,25 @@ import type { Message } from 'discord.js'; import type { MessageCommandBuilder } from '../builders/MessageCommandBuilder.js'; -import type { MessageCommandFlagValue } from '../structures/MessageCommandFlagValue.js'; +import { MessageCommandFlagValue, type MessageCommandFlagParseOptionValueOptions } from '../structures/MessageCommandFlagValue.js'; import type { RecipleClient } from '../structures/RecipleClient.js'; import { DataManager } from './DataManager.js'; import type { MessageCommandOptionManagerOptions } from './MessageCommandOptionManager.js'; import type { CommandData } from '../../types/structures.js'; import { RecipleError } from '../structures/RecipleError.js'; +export interface MessageCommandFlagManagerParseOptionsData extends Omit { + command: MessageCommandBuilder; + parserData: CommandData; +} + export class MessageCommandFlagManager extends DataManager implements MessageCommandOptionManagerOptions { readonly command: MessageCommandBuilder; readonly client: RecipleClient; readonly message: Message; readonly parserData: CommandData; + get rawFlags() { return this.parserData.flags; } + constructor(data: MessageCommandOptionManagerOptions) { super(); @@ -74,4 +81,27 @@ export class MessageCommandFlagManager extends DataManager v ?? []) : value?.values ?? []; } + + private async _parseFlags(): Promise { + if (!this.command.flags?.length || !this.client.isReady()) return; + + for (const data of this.command.flags) { + const flag = this.rawFlags.find(f => f.name === data.name); + this._cache.set(data.name, await MessageCommandFlagValue.parseFlagValue({ + client: this.client, + message: this.message, + command: this.command, + parserData: this.parserData, + values: flag?.value, + flag: data + })); + } + } + + public static async parseFlags(options: MessageCommandFlagManagerParseOptionsData): Promise { + const manager = new MessageCommandFlagManager(options); + + await manager._parseFlags(); + return manager; + } } From ab9854840f4b5cc23e8102c3bb305021c1c1207c Mon Sep 17 00:00:00 2001 From: Cat++ <69035887+NotGhex@users.noreply.github.com> Date: Sun, 4 Aug 2024 13:28:58 +0800 Subject: [PATCH 05/16] fix undefined --- .../src/classes/builders/MessageCommandBuilder.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/core/src/classes/builders/MessageCommandBuilder.ts b/packages/core/src/classes/builders/MessageCommandBuilder.ts index 4f6721b7..6e8c9793 100644 --- a/packages/core/src/classes/builders/MessageCommandBuilder.ts +++ b/packages/core/src/classes/builders/MessageCommandBuilder.ts @@ -256,12 +256,15 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message builder.flags .map((o) => [ o.name, - { - type: MessageCommandFlagBuilder.getFlagValuesType(o.default_values ?? []), - multiple: o.multiple, - short: o.short, - default: o.multiple ? o.default_values as string[] : o.default_values?.[0], - } + Object.fromEntries( + Object.entries({ + type: o.accept ?? 'string', + multiple: o.multiple, + short: o.short, + default: o.multiple ? o.default_values : o.default_values?.[0], + }) + .filter(([key, value]) => value !== undefined) + ) as any ]) ), }); From a646ac6e3490c0ea8827546fdd158bab29aa4fa3 Mon Sep 17 00:00:00 2001 From: Cat++ <69035887+NotGhex@users.noreply.github.com> Date: Sun, 4 Aug 2024 13:29:53 +0800 Subject: [PATCH 06/16] types --- .../builders/MessageCommandFlagBuilder.ts | 53 +++++++++--------- .../managers/MessageCommandFlagManager.ts | 18 +++--- .../structures/MessageCommandFlagValue.ts | 56 +++++++++++-------- .../structures/MessageCommandOptionValue.ts | 3 +- .../src/classes/structures/RecipleError.ts | 18 +++++- .../validators/MessageCommandFlagValidator.ts | 16 ++++++ 6 files changed, 104 insertions(+), 60 deletions(-) diff --git a/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts b/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts index 6e34019a..870e900e 100644 --- a/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts +++ b/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts @@ -4,11 +4,11 @@ import type { MessageCommandBuilder } from './MessageCommandBuilder.js'; import type { RecipleClient } from '../structures/RecipleClient.js'; import { MessageCommandFlagValidators } from '../validators/MessageCommandFlagValidator.js'; -export interface MessageCommandFlagBuilderResolveValueOptions { +export interface MessageCommandFlagBuilderResolveValueOptions { /** * The values of the given flag */ - values: V[]; + values: string[]|boolean[]; /** * The parser data when parsing this command. */ @@ -16,7 +16,7 @@ export interface MessageCommandFlagBuilderResolveValueOptions; + flag: MessageCommandFlagBuilder; /** * The command builder used to build this command. */ @@ -31,38 +31,40 @@ export interface MessageCommandFlagBuilderResolveValueOptions; } -export interface MessageCommandFlagBuilderData { +export interface MessageCommandFlagBuilderData { name: string; short?: string; description: string; - default_values?: V[]; + default_values?: string[]|boolean[]; required?: boolean; mandatory?: boolean; multiple?: boolean; + accept?: 'string'|'boolean'; /** * The function that validates the option value. * @param options The option value and message. */ - validate?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; + validate?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; /** * Resolves the option value. * @param options The option value and message. */ - resolve_value?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; + resolve_value?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; } -export class MessageCommandFlagBuilder implements MessageCommandFlagBuilderData { +export class MessageCommandFlagBuilder implements MessageCommandFlagBuilderData { public name: string = ''; public short?: string; public description: string = ''; - public default_values?: V[]; + public default_values?: string[]|boolean[]; public required: boolean = false; public mandatory?: boolean = false; public multiple?: boolean = false; - public validate?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; - public resolve_value?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; + public accept?: 'string'|'boolean'; + public validate?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; + public resolve_value?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; - constructor(data?: Partial>) { + constructor(data?: Partial>) { if (data?.name) this.setName(data.name); if (data?.short) this.setShort(data.short); if (data?.description) this.setDescription(data.description); @@ -92,8 +94,8 @@ export class MessageCommandFlagBuilder): this { - defaultValues = normalizeArray(defaultValues); + public setDefaultValues(...defaultValues: RestOrArray): this { + defaultValues = normalizeArray(defaultValues) as string[]|boolean[]; MessageCommandFlagValidators.isValidDefaultValues(defaultValues); this.default_values = defaultValues; return this; @@ -117,19 +119,25 @@ export class MessageCommandFlagBuilder['validate']): this { + public setAccept(accept: 'string'|'boolean'): this { + MessageCommandFlagValidators.isValidAccept(accept); + this.accept = accept as any; + return this as any; + } + + public setValidate(validate: MessageCommandFlagBuilderData['validate']): this { MessageCommandFlagValidators.isValidValidate(validate); this.validate = validate; return this; } - public setResolveValue(resolve_value: MessageCommandFlagBuilderData['resolve_value']): this { + public setResolveValue(resolve_value: MessageCommandFlagBuilderData['resolve_value']): this { MessageCommandFlagValidators.isValidResolveValue(resolve_value); this.resolve_value = resolve_value; return this; } - public toJSON(): MessageCommandFlagBuilderData { + public toJSON(): MessageCommandFlagBuilderData { return { name: this.name, short: this.short, @@ -142,18 +150,13 @@ export class MessageCommandFlagBuilder(data: MessageCommandFlagResolvable): MessageCommandFlagBuilder { + public static from(data: MessageCommandFlagResolvable): MessageCommandFlagBuilder { return new MessageCommandFlagBuilder(isJSONEncodable(data) ? data.toJSON() : data); } - public static resolve(data: MessageCommandFlagResolvable): MessageCommandFlagBuilder { + public static resolve(data: MessageCommandFlagResolvable): MessageCommandFlagBuilder { return data instanceof MessageCommandFlagBuilder ? data : MessageCommandFlagBuilder.from(data); } } -export type MessageCommandFlagResolvable = JSONEncodable>|MessageCommandFlagBuilderData; +export type MessageCommandFlagResolvable = JSONEncodable>|MessageCommandFlagBuilderData; diff --git a/packages/core/src/classes/managers/MessageCommandFlagManager.ts b/packages/core/src/classes/managers/MessageCommandFlagManager.ts index 81365e5b..b84545b7 100644 --- a/packages/core/src/classes/managers/MessageCommandFlagManager.ts +++ b/packages/core/src/classes/managers/MessageCommandFlagManager.ts @@ -56,10 +56,10 @@ export class MessageCommandFlagManager extends DataManager|null} The value of the message command flag. */ - public getFlag(name: string, required: true): MessageCommandFlagValue; - public getFlag(name: string, required?: boolean): MessageCommandFlagValue|null; - public getFlag(name: string, required: boolean = false): MessageCommandFlagValue|null { - const flag = this.cache.get(name) as MessageCommandFlagValue|undefined; + public getFlag(name: string, required: true): MessageCommandFlagValue; + public getFlag(name: string, required?: boolean): MessageCommandFlagValue|null; + public getFlag(name: string, required: boolean = false): MessageCommandFlagValue|null { + const flag = this.cache.get(name) as MessageCommandFlagValue|undefined; if (required && !flag) throw new RecipleError(`Unable to find required message flag '${name}'`); return flag ?? null; @@ -72,11 +72,11 @@ export class MessageCommandFlagManager extends DataManager(name: string, options?: { required?: boolean; resolveValue?: false; }): V[]; - public getFlagValues(name: string, options?: { required?: boolean; resolveValue?: true; }): Promise; - public getFlagValues(name: string, options?: { required?: boolean; resolveValue?: boolean; }): Promise|V[]; - public getFlagValues(name: string, options: { required?: boolean; resolveValue?: boolean; } = { required: false, resolveValue: false }): Promise|V[] { - const value = this.getFlag(name, options.required); + public getFlagValues(name: string, options?: { required?: boolean; resolveValue?: false; }): string[]|boolean[]; + public getFlagValues(name: string, options?: { required?: boolean; resolveValue?: true; }): Promise; + public getFlagValues(name: string, options?: { required?: boolean; resolveValue?: boolean; }): Promise|string[]|boolean[]; + public getFlagValues(name: string, options: { required?: boolean; resolveValue?: boolean; } = { required: false, resolveValue: false }): Promise|string[]|boolean[] { + const value = this.getFlag(name, options.required); return options.resolveValue ? Promise.resolve(value?.resolveValues()).then(v => v ?? []) : value?.values ?? []; diff --git a/packages/core/src/classes/structures/MessageCommandFlagValue.ts b/packages/core/src/classes/structures/MessageCommandFlagValue.ts index 275aff73..4ee534f1 100644 --- a/packages/core/src/classes/structures/MessageCommandFlagValue.ts +++ b/packages/core/src/classes/structures/MessageCommandFlagValue.ts @@ -3,8 +3,9 @@ import type { CommandData } from '../../types/structures.js'; import type { MessageCommandBuilder } from '../builders/MessageCommandBuilder.js'; import type { MessageCommandFlagBuilder, MessageCommandFlagBuilderResolveValueOptions } from '../builders/MessageCommandFlagBuilder.js'; import type { RecipleClient } from './RecipleClient.js'; +import { RecipleError } from './RecipleError.js'; -export interface MessageCommandFlagValueData { +export interface MessageCommandFlagValueData { /** * The name of the option. */ @@ -12,11 +13,11 @@ export interface MessageCommandFlagValueData; + flag: MessageCommandFlagBuilder; /** * The raw value of the option. */ - values: V[]; + values: string[]|boolean[]; /** * Whether the option is missing. */ @@ -31,19 +32,19 @@ export interface MessageCommandFlagValueData extends Omit, 'values'> { - values?: V[]|null; +export interface MessageCommandFlagParseOptionValueOptions extends Omit, 'values'> { + values?: T[]|null; } -export interface MessageCommandFlagValueOptions extends MessageCommandFlagValueData, Pick, 'parserData'|'command'> { +export interface MessageCommandFlagValueOptions extends MessageCommandFlagValueData, Pick, 'parserData'|'command'> { client: RecipleClient; message: Message; } -export class MessageCommandFlagValue implements MessageCommandFlagValueData { +export class MessageCommandFlagValue implements MessageCommandFlagValueData { readonly name: string; - readonly flag: MessageCommandFlagBuilder; - readonly values: V[]; + readonly flag: MessageCommandFlagBuilder; + readonly values: string[]|boolean[]; readonly missing: boolean; readonly invalid: boolean; readonly message: Message; @@ -53,7 +54,7 @@ export class MessageCommandFlagValue; - constructor(options: MessageCommandFlagValueOptions) { + constructor(options: MessageCommandFlagValueOptions) { this.name = options.name; this.flag = options.flag; this.values = options.values; @@ -85,7 +86,7 @@ export class MessageCommandFlagValue { + public toJSON(): MessageCommandFlagValueData { return { name: this.name, flag: this.flag, @@ -96,28 +97,35 @@ export class MessageCommandFlagValue(options: MessageCommandFlagParseOptionValueOptions): Promise> { + public static async parseFlagValue(options: MessageCommandFlagParseOptionValueOptions): Promise> { const missing = options.values ? !!options.flag.required && !options.values?.length : options.flag.mandatory ?? false; + const invalidValues = options.values?.filter(value => typeof value !== options.flag.accept) ?? []; const validateData = !missing - ? options.flag.validate && options.values?.length - ? await Promise.resolve(options.flag.validate({ - values: options.values, - flag: options.flag, - parserData: options.parserData, - command: options.command, - message: options.message, - client: options.client, - })) - : true - : false; + ? !invalidValues.length + ? options.flag.validate && options.values?.length + ? await Promise.resolve(options.flag.validate({ + values: options.values as string[]|boolean[], + flag: options.flag, + parserData: options.parserData, + command: options.command, + message: options.message, + client: options.client, + })) + : true + : new RecipleError(`Invalid value${invalidValues.length > 1 ? 's' : ''} for flag '${options.flag.name}'`) + : new RecipleError( + options.values + ? RecipleError.createCommandRequiredFlagNotFoundErrorOptions(options.flag.name, options.values.join(', ')) + : RecipleError.createCommandMandatoryFlagNotFoundErrorOptions(options.flag.name, 'undefined') + ); return new MessageCommandFlagValue({ name: options.flag.name, flag: options.flag, - values: options.values ?? [], + values: (options.values ?? []) as string[]|boolean[], missing, invalid: !validateData, message: options.message, diff --git a/packages/core/src/classes/structures/MessageCommandOptionValue.ts b/packages/core/src/classes/structures/MessageCommandOptionValue.ts index 4561e55a..38b461ea 100644 --- a/packages/core/src/classes/structures/MessageCommandOptionValue.ts +++ b/packages/core/src/classes/structures/MessageCommandOptionValue.ts @@ -3,6 +3,7 @@ import { MessageCommandBuilder } from '../builders/MessageCommandBuilder.js'; import type { CommandData } from '../../types/structures.js'; import { RecipleClient } from './RecipleClient.js'; import { Message } from 'discord.js'; +import { RecipleError } from './RecipleError.js'; export interface MessageCommandOptionValueData { /** @@ -107,7 +108,7 @@ export class MessageCommandOptionValue implements MessageCo client: options.client, })) : true - : false; + : new RecipleError(RecipleError.createCommandRequiredOptionNotFoundErrorOptions(options.option.name, options.value)); return new MessageCommandOptionValue({ name: options.option.name, diff --git a/packages/core/src/classes/structures/RecipleError.ts b/packages/core/src/classes/structures/RecipleError.ts index e3096f93..de3f9e2b 100644 --- a/packages/core/src/classes/structures/RecipleError.ts +++ b/packages/core/src/classes/structures/RecipleError.ts @@ -60,7 +60,23 @@ export class RecipleError extends Error { public static createCommandRequiredOptionNotFoundErrorOptions(optionName: string, value: unknown): RecipleErrorOptions { return { - message: `No value given from required option ${kleur.cyan("'" + optionName + "'")}`, + message: `No value given for required option ${kleur.cyan("'" + optionName + "'")}`, + cause: { value }, + name: 'RequiredOptionNotFound' + }; + } + + public static createCommandRequiredFlagNotFoundErrorOptions(flagName: string, value: unknown): RecipleErrorOptions { + return { + message: `No value given for required flag ${kleur.cyan("'" + flagName + "'")}`, + cause: { value }, + name: 'RequiredOptionNotFound' + }; + } + + public static createCommandMandatoryFlagNotFoundErrorOptions(flagName: string, value: unknown): RecipleErrorOptions { + return { + message: `No value given for mandatory flag ${kleur.cyan("'" + flagName + "'")}`, cause: { value }, name: 'RequiredOptionNotFound' }; diff --git a/packages/core/src/classes/validators/MessageCommandFlagValidator.ts b/packages/core/src/classes/validators/MessageCommandFlagValidator.ts index 36f42fda..c1bb35ff 100644 --- a/packages/core/src/classes/validators/MessageCommandFlagValidator.ts +++ b/packages/core/src/classes/validators/MessageCommandFlagValidator.ts @@ -42,6 +42,17 @@ export class MessageCommandFlagValidators extends BaseCommandValidators { .boolean({ message: 'Expected boolean for .multiple' }) .optional(); + public static accept = MessageCommandFlagValidators.s + .union([ + MessageCommandFlagValidators.s + .literal('string', { equalsOptions: { message: 'Expected "string" for .accept' } }) + .optional(), + MessageCommandFlagValidators.s + .literal('boolean', { equalsOptions: { message: 'Expected "boolean" for .accept' } }) + .optional(), + ]) + .optional(); + public static validate = MessageCommandFlagValidators.s .instance(Function, { message: 'Expected a function for .validate' }) .optional(); @@ -91,6 +102,10 @@ export class MessageCommandFlagValidators extends BaseCommandValidators { MessageCommandFlagValidators.multiple.setValidationEnabled(MessageCommandFlagValidators.isValidationEnabled).parse(multiple); } + public static isValidAccept(accept: unknown): asserts accept is MessageCommandFlagBuilderData['accept'] { + MessageCommandFlagValidators.accept.setValidationEnabled(MessageCommandFlagValidators.isValidationEnabled).parse(accept); + } + public static isValidValidate(validate: unknown): asserts validate is MessageCommandFlagBuilderData['validate'] { MessageCommandFlagValidators.validate.setValidationEnabled(MessageCommandFlagValidators.isValidationEnabled).parse(validate); } @@ -109,6 +124,7 @@ export class MessageCommandFlagValidators extends BaseCommandValidators { MessageCommandFlagValidators.isValidRequired(opt.required); MessageCommandFlagValidators.isValidMandatory(opt.mandatory); MessageCommandFlagValidators.isValidMultiple(opt.multiple); + MessageCommandFlagValidators.isValidAccept(opt.accept); MessageCommandFlagValidators.isValidValidate(opt.validate); MessageCommandFlagValidators.isValidResolveValue(opt.resolve_value); } From 3932a1e874335ddd3d2d6df362550aa1c669c7e4 Mon Sep 17 00:00:00 2001 From: Cat++ <69035887+NotGhex@users.noreply.github.com> Date: Sun, 4 Aug 2024 13:31:11 +0800 Subject: [PATCH 07/16] fix nodemon --- example/nodemon.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/nodemon.json b/example/nodemon.json index 465767fb..7c52f1dd 100644 --- a/example/nodemon.json +++ b/example/nodemon.json @@ -8,7 +8,7 @@ "node_modules/**" ], "watch": [ - "src", + "modules", "reciple.mjs", ".env" ], From 3b7f1fd9f40229e141e25a2d1dcf726bf26be142 Mon Sep 17 00:00:00 2001 From: Cat++ <69035887+NotGhex@users.noreply.github.com> Date: Sun, 4 Aug 2024 13:35:15 +0800 Subject: [PATCH 08/16] filter values --- .../structures/MessageCommandFlagValue.ts | 34 +++++++++---------- .../src/classes/structures/RecipleError.ts | 4 +-- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/packages/core/src/classes/structures/MessageCommandFlagValue.ts b/packages/core/src/classes/structures/MessageCommandFlagValue.ts index 4ee534f1..17e48e49 100644 --- a/packages/core/src/classes/structures/MessageCommandFlagValue.ts +++ b/packages/core/src/classes/structures/MessageCommandFlagValue.ts @@ -98,34 +98,32 @@ export class MessageCommandFlagValue implements } public static async parseFlagValue(options: MessageCommandFlagParseOptionValueOptions): Promise> { - const missing = options.values - ? !!options.flag.required && !options.values?.length + const filteredValues = options.values?.filter(value => typeof value == options.flag.accept); + const missing = filteredValues + ? !!options.flag.required && !filteredValues?.length : options.flag.mandatory ?? false; - const invalidValues = options.values?.filter(value => typeof value !== options.flag.accept) ?? []; const validateData = !missing - ? !invalidValues.length - ? options.flag.validate && options.values?.length - ? await Promise.resolve(options.flag.validate({ - values: options.values as string[]|boolean[], - flag: options.flag, - parserData: options.parserData, - command: options.command, - message: options.message, - client: options.client, - })) - : true - : new RecipleError(`Invalid value${invalidValues.length > 1 ? 's' : ''} for flag '${options.flag.name}'`) + ? options.flag.validate && filteredValues?.length + ? await Promise.resolve(options.flag.validate({ + values: filteredValues as string[]|boolean[], + flag: options.flag, + parserData: options.parserData, + command: options.command, + message: options.message, + client: options.client, + })) + : true : new RecipleError( - options.values - ? RecipleError.createCommandRequiredFlagNotFoundErrorOptions(options.flag.name, options.values.join(', ')) + filteredValues + ? RecipleError.createCommandRequiredFlagNotFoundErrorOptions(options.flag.name, filteredValues.join(', ')) : RecipleError.createCommandMandatoryFlagNotFoundErrorOptions(options.flag.name, 'undefined') ); return new MessageCommandFlagValue({ name: options.flag.name, flag: options.flag, - values: (options.values ?? []) as string[]|boolean[], + values: (filteredValues ?? []) as string[]|boolean[], missing, invalid: !validateData, message: options.message, diff --git a/packages/core/src/classes/structures/RecipleError.ts b/packages/core/src/classes/structures/RecipleError.ts index de3f9e2b..00362199 100644 --- a/packages/core/src/classes/structures/RecipleError.ts +++ b/packages/core/src/classes/structures/RecipleError.ts @@ -70,7 +70,7 @@ export class RecipleError extends Error { return { message: `No value given for required flag ${kleur.cyan("'" + flagName + "'")}`, cause: { value }, - name: 'RequiredOptionNotFound' + name: 'RequiredFlagNotFound' }; } @@ -78,7 +78,7 @@ export class RecipleError extends Error { return { message: `No value given for mandatory flag ${kleur.cyan("'" + flagName + "'")}`, cause: { value }, - name: 'RequiredOptionNotFound' + name: 'MandatoryFlagNotFound' }; } From 6bc4d81411c805a179c98daa34d9798c658a558d Mon Sep 17 00:00:00 2001 From: Cat++ <69035887+NotGhex@users.noreply.github.com> Date: Sun, 4 Aug 2024 13:41:48 +0800 Subject: [PATCH 09/16] Add methods --- .../classes/builders/MessageCommandBuilder.ts | 38 +++++++++++++++++++ .../validators/MessageCommandValidators.ts | 17 +++++++++ 2 files changed, 55 insertions(+) diff --git a/packages/core/src/classes/builders/MessageCommandBuilder.ts b/packages/core/src/classes/builders/MessageCommandBuilder.ts index 6e8c9793..dc20797e 100644 --- a/packages/core/src/classes/builders/MessageCommandBuilder.ts +++ b/packages/core/src/classes/builders/MessageCommandBuilder.ts @@ -52,6 +52,11 @@ export interface MessageCommandBuilderData extends BaseCommandBuilderData { * @default true */ validate_options?: boolean; + /** + * Whether to validate flags or not. + * @default true + */ + validate_flags?: boolean; /** * Allows commands to be executed in DMs. * @default false @@ -86,6 +91,7 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message public description: string = ''; public aliases: string[] = []; public validate_options: boolean = true; + public validate_flags: boolean = true; public dm_permission: boolean = false; public allow_bot: boolean = false; public options: MessageCommandOptionBuilder[] = []; @@ -98,9 +104,11 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message if (data?.description) this.setDescription(data.description); if (data?.aliases) this.setAliases(data.aliases); if (data?.validate_options) this.setValidateOptions(data.validate_options); + if (data?.validate_flags) this.setValidateFlags(data.validate_flags); if (data?.dm_permission) this.setDMPermission(data.dm_permission); if (data?.allow_bot) this.setAllowBot(data.allow_bot); if (data?.options) this.setOptions(data.options); + if (data?.flags) this.setFlags(data.flags); } /** @@ -155,6 +163,16 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message return this; } + /** + * Set whether to validate flags or not. + * @param enabled Enable flag validation. + */ + public setValidateFlags(enabled: boolean): this { + MessageCommandValidators.isValidValidateFlags(enabled); + this.validate_flags = enabled; + return this; + } + /** * Sets whether the command is available in DMs or not. * @param DMPermission Enable command in Dms. @@ -206,6 +224,10 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message return this; } + /** + * Adds new flag to the command. + * @param option Flag data or builder. + */ public addFlag(option: MessageCommandFlagResolvable|((builder: MessageCommandFlagBuilder) => MessageCommandFlagBuilder)): this { const opt = typeof option === 'function' ? option(new MessageCommandFlagBuilder()) : MessageCommandFlagBuilder.resolve(option); MessageCommandFlagValidators.isValidMessageCommandFlagResolvable(opt); @@ -216,6 +238,22 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message return this; } + /** + * Sets the flags of the command. + * @param flags Flags data or builders. + */ + public setFlags(...flags: RestOrArray MessageCommandFlagBuilder)>): this { + flags = normalizeArray(flags); + MessageCommandValidators.isValidFlags(flags); + this.flags = []; + + for (const flag of flags) { + this.addFlag(flag); + } + + return this; + } + public toJSON(): MessageCommandBuilderData { return { name: this.name, diff --git a/packages/core/src/classes/validators/MessageCommandValidators.ts b/packages/core/src/classes/validators/MessageCommandValidators.ts index f4fa8645..7516bf64 100644 --- a/packages/core/src/classes/validators/MessageCommandValidators.ts +++ b/packages/core/src/classes/validators/MessageCommandValidators.ts @@ -1,6 +1,7 @@ import { MessageCommandOptionValidators } from './MessageCommandOptionValidators.js'; import type { MessageCommandBuilderData } from '../builders/MessageCommandBuilder.js'; import { BaseCommandValidators } from './BaseCommandValidators.js'; +import { MessageCommandFlagValidators } from './MessageCommandFlagValidator.js'; export class MessageCommandValidators extends BaseCommandValidators { public static name = MessageCommandValidators.s @@ -22,6 +23,10 @@ export class MessageCommandValidators extends BaseCommandValidators { .boolean({ message: 'Expected boolean for .validate_options' }) .optional(); + public static validate_flags = MessageCommandValidators.s + .boolean({ message: 'Expected boolean for .validate_flags' }) + .optional(); + public static dm_permission = MessageCommandValidators.s .boolean({ message: 'Expected boolean for .dm_permission' }) .optional(); @@ -34,6 +39,10 @@ export class MessageCommandValidators extends BaseCommandValidators { .array({ message: 'Expected an array for message command options' }) .optional(); + public static flags = MessageCommandFlagValidators.MessageCommandFlagResolvable + .array({ message: 'Expected an array for message command flags' }) + .optional(); + public static isValidName(name: unknown): asserts name is MessageCommandBuilderData['name'] { MessageCommandValidators.name.setValidationEnabled(MessageCommandValidators.isValidationEnabled).parse(name); } @@ -50,6 +59,10 @@ export class MessageCommandValidators extends BaseCommandValidators { MessageCommandValidators.validate_options.setValidationEnabled(MessageCommandValidators.isValidationEnabled).parse(parseOptions); } + public static isValidValidateFlags(parseFlags: unknown): asserts parseFlags is MessageCommandBuilderData['validate_flags'] { + MessageCommandValidators.validate_flags.setValidationEnabled(MessageCommandValidators.isValidationEnabled).parse(parseFlags); + } + public static isValidDMPermission(DMPermission: unknown): asserts DMPermission is MessageCommandBuilderData['dm_permission'] { MessageCommandValidators.dm_permission.setValidationEnabled(MessageCommandValidators.isValidationEnabled).parse(DMPermission); } @@ -61,4 +74,8 @@ export class MessageCommandValidators extends BaseCommandValidators { public static isValidOptions(options: unknown): asserts options is MessageCommandBuilderData['options'] { MessageCommandValidators.options.setValidationEnabled(MessageCommandValidators.isValidationEnabled).parse(options); } + + public static isValidFlags(flags: unknown): asserts flags is MessageCommandBuilderData['flags'] { + MessageCommandValidators.flags.setValidationEnabled(MessageCommandValidators.isValidationEnabled).parse(flags); + } } From 5d00c4eec0ac5d6c5974af54966fb9063346b741 Mon Sep 17 00:00:00 2001 From: Cat++ <69035887+NotGhex@users.noreply.github.com> Date: Sun, 4 Aug 2024 13:45:29 +0800 Subject: [PATCH 10/16] new halts --- .../classes/builders/MessageCommandBuilder.ts | 22 +++++++++++++++++++ packages/core/src/types/constants.ts | 4 +++- packages/core/src/types/structures.ts | 16 +++++++++++++- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/core/src/classes/builders/MessageCommandBuilder.ts b/packages/core/src/classes/builders/MessageCommandBuilder.ts index dc20797e..703c7be5 100644 --- a/packages/core/src/classes/builders/MessageCommandBuilder.ts +++ b/packages/core/src/classes/builders/MessageCommandBuilder.ts @@ -392,6 +392,28 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message } } + if (builder.validate_flags) { + if (executeData.flags.hasInvalidFlags) { + await client.commands.executeHalts({ + commandType: builder.command_type, + reason: CommandHaltReason.InvalidFlags, + executeData, + invalidFlags: executeData.flags.invalidFlags + }); + return null; + } + + if (executeData.flags.hasMissingFlags) { + await client.commands.executeHalts({ + commandType: builder.command_type, + reason: CommandHaltReason.MissingFlags, + executeData, + missingFlags: executeData.flags.missingFlags + }); + return null; + } + } + return (await client.commands.executeCommandBuilderExecute(executeData)) ? executeData : null; } } diff --git a/packages/core/src/types/constants.ts b/packages/core/src/types/constants.ts index 2febfede..573f857a 100644 --- a/packages/core/src/types/constants.ts +++ b/packages/core/src/types/constants.ts @@ -14,7 +14,9 @@ export enum CommandHaltReason { Cooldown, InvalidArguments, MissingArguments, - PreconditionTrigger + PreconditionTrigger, + InvalidFlags, + MissingFlags } export enum RecipleModuleStatus { diff --git a/packages/core/src/types/structures.ts b/packages/core/src/types/structures.ts index 1da7cea7..45e34a82 100644 --- a/packages/core/src/types/structures.ts +++ b/packages/core/src/types/structures.ts @@ -8,6 +8,7 @@ import type { MessageCommandOptionValue } from '../classes/structures/MessageCom import type { CooldownSweeperOptions } from '../classes/managers/CooldownManager.js'; import type { CommandHaltReason, CommandType } from './constants.js'; import type { Cooldown } from '../classes/structures/Cooldown.js'; +import type { MessageCommandFlagValue } from '../classes/structures/MessageCommandFlagValue.js'; // Config export interface RecipleClientConfig { @@ -66,7 +67,10 @@ export type AnySlashCommandOptionData = AnyNonSubcommandSlashCommandOptionData|A export type CommandHaltTriggerData = | CommandErrorHaltTriggerData | CommandCooldownHaltTriggerData - | (T extends CommandType.MessageCommand ? CommandInvalidArgumentsHaltTriggerData | CommandMissingArgumentsHaltTriggerData : never) + | (T extends CommandType.MessageCommand + ? CommandInvalidArgumentsHaltTriggerData | CommandMissingArgumentsHaltTriggerData | CommandInvalidFlagsHaltTriggerData | CommandMissingFlagsHaltTriggerData + : never + ) | CommandPreconditionResultHaltTriggerData; export interface BaseCommandHaltTriggerData { @@ -101,6 +105,16 @@ export interface CommandMissingArgumentsHaltTriggerData e missingOptions: Collection; } +export interface CommandInvalidFlagsHaltTriggerData extends BaseCommandHaltTriggerData { + reason: CommandHaltReason.InvalidFlags; + invalidFlags: Collection; +} + +export interface CommandMissingFlagsHaltTriggerData extends BaseCommandHaltTriggerData { + reason: CommandHaltReason.MissingFlags; + missingFlags: Collection; +} + export interface CommandPreconditionResultHaltTriggerData extends BaseCommandHaltTriggerData, Omit { reason: CommandHaltReason.PreconditionTrigger; } From a98060d85f581d83e7cdab25b229cd3054789abb Mon Sep 17 00:00:00 2001 From: Cat++ <69035887+NotGhex@users.noreply.github.com> Date: Sun, 4 Aug 2024 13:46:20 +0800 Subject: [PATCH 11/16] example --- example/modules/Commands/Flags.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 example/modules/Commands/Flags.js diff --git a/example/modules/Commands/Flags.js b/example/modules/Commands/Flags.js new file mode 100644 index 00000000..340dbb5f --- /dev/null +++ b/example/modules/Commands/Flags.js @@ -0,0 +1,25 @@ +import { MessageCommandBuilder } from "reciple"; + +export class Message { + commands = [ + new MessageCommandBuilder() + .setName('flag') + .setDescription('Sends a message') + .addFlag(flag => flag + .setName('flag') + .setDescription('A flag') + .setAccept('string') + .setRequired(true) + .setMandatory(true) + ) + .setExecute(async ({ message, flags }) => { + await message.reply(flags.getFlagValues('flag')[0]); + }) + ]; + + onStart() { + return true; + } +} + +export default new Message() From 107c2d23ebc3ea22f3b8119be6b67debd4969074 Mon Sep 17 00:00:00 2001 From: Cat++ <69035887+NotGhex@users.noreply.github.com> Date: Sun, 4 Aug 2024 13:50:29 +0800 Subject: [PATCH 12/16] example halt --- example/modules/Halts/MessageCommandArguments.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/example/modules/Halts/MessageCommandArguments.js b/example/modules/Halts/MessageCommandArguments.js index 76738ad2..b309a25e 100644 --- a/example/modules/Halts/MessageCommandArguments.js +++ b/example/modules/Halts/MessageCommandArguments.js @@ -13,9 +13,12 @@ export class MessageCommandArguments { * @param {import('reciple').MessageCommandHaltTriggerData} data */ async messageCommandHalt(data) { - if (data.reason !== CommandHaltReason.InvalidArguments && data.reason !== CommandHaltReason.MissingArguments) return; - console.log(data.executeData.options.invalidOptions); - console.log(data.executeData.options.missingOptions); + if ( + data.reason !== CommandHaltReason.InvalidArguments && + data.reason !== CommandHaltReason.MissingArguments && + data.reason !== CommandHaltReason.InvalidFlags && + data.reason !== CommandHaltReason.MissingFlags + ) return; switch (data.reason) { case CommandHaltReason.InvalidArguments: @@ -24,6 +27,12 @@ export class MessageCommandArguments { case CommandHaltReason.MissingArguments: await data.executeData.message.reply(`## Missing arguments\n${data.executeData.options.missingOptions.map(o => `- ${inlineCode(o.name)}`).join('\n')}`); break; + case CommandHaltReason.InvalidFlags: + await data.executeData.message.reply(`## Invalid flags\n${data.executeData.flags.invalidFlags.map(o => `- ${inlineCode(o.name)} ${o.error?.message ?? 'Invalid value'}`).join('\n')}`); + break; + case CommandHaltReason.MissingFlags: + await data.executeData.message.reply(`## Missing flags\n${data.executeData.flags.missingFlags.map(o => `- ${inlineCode(o.name)}`).join('\n')}`); + break; } return true; From bed8cedb7d9960cc1be388256fb1fdc2760da0bd Mon Sep 17 00:00:00 2001 From: Cat++ <69035887+NotGhex@users.noreply.github.com> Date: Sun, 4 Aug 2024 14:02:10 +0800 Subject: [PATCH 13/16] usage --- .../src/helpers/createMessageCommandUsage.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/message-command-utils/src/helpers/createMessageCommandUsage.ts b/packages/message-command-utils/src/helpers/createMessageCommandUsage.ts index 04d728a7..d5d875eb 100644 --- a/packages/message-command-utils/src/helpers/createMessageCommandUsage.ts +++ b/packages/message-command-utils/src/helpers/createMessageCommandUsage.ts @@ -3,6 +3,16 @@ import { isJSONEncodable } from 'discord.js'; export interface CreateMessageCommandUsageOptions { prefix?: string; + flags?: { + include?: boolean; + useShort?: boolean; + showValueType?: boolean; + flagBrackets?: { + required?: [string, string]; + mandatory?: [string, string]; + optional?: [string, string]; + } + }; optionBrackets?: { required?: [string, string]; optional?: [string, string]; @@ -23,5 +33,18 @@ export function createMessageCommandUsage(data: MessageCommandResolvable, option usage += ` ${brackets[0]}${option.name}${brackets[1]}`; } + if (options?.flags?.include !== false && command.flags) for (const flagData of command.flags) { + const flag = isJSONEncodable(flagData) ? flagData.toJSON() : flagData; + const brackets = flag.mandatory + ? options?.flags?.flagBrackets?.mandatory ?? ['<', '>'] + : flag.required + ? options?.flags?.flagBrackets?.required ?? ['<', '>'] + : options?.flags?.flagBrackets?.optional ?? ['[', ']']; + + let value = `${options?.flags?.useShort ? '-' + flag.short : '--' + flag.name}`; + + if (options?.flags?.showValueType) value += `=${brackets[0]}${flag.accept === 'string' ? 'string' : 'boolean'}${flag.multiple ? '...' : ''}${brackets[1]}`; + } + return usage; } From a40b9fdbfe8581c6a4072a171397fcffa5b02061 Mon Sep 17 00:00:00 2001 From: Cat++ <69035887+NotGhex@users.noreply.github.com> Date: Sun, 4 Aug 2024 14:46:38 +0800 Subject: [PATCH 14/16] flags in data --- packages/core/src/classes/builders/MessageCommandBuilder.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/classes/builders/MessageCommandBuilder.ts b/packages/core/src/classes/builders/MessageCommandBuilder.ts index 703c7be5..73abd577 100644 --- a/packages/core/src/classes/builders/MessageCommandBuilder.ts +++ b/packages/core/src/classes/builders/MessageCommandBuilder.ts @@ -262,7 +262,8 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message validate_options: this.validate_options, dm_permission: this.dm_permission, allow_bot: this.allow_bot, - options: this.options, + options: this.options.map(b => b.toJSON()), + flags: this.flags.map(b => b.toJSON()), ...super._toJSON() }; } From 151a4b339f0975c499b2ce09f4ad6a78011e615b Mon Sep 17 00:00:00 2001 From: Cat++ <69035887+NotGhex@users.noreply.github.com> Date: Sun, 4 Aug 2024 15:06:26 +0800 Subject: [PATCH 15/16] fix some types --- example/modules/Commands/Flags.js | 8 ++++++-- example/modules/Commands/Say.js | 1 - example/modules/Preconditions/MyPrecondition.js | 1 - .../src/classes/builders/MessageCommandBuilder.ts | 2 +- .../classes/builders/MessageCommandFlagBuilder.ts | 11 ++++++----- .../classes/managers/MessageCommandFlagManager.ts | 6 +++--- .../classes/structures/MessageCommandFlagValue.ts | 14 +++++++------- .../validators/MessageCommandFlagValidator.ts | 12 ++++++------ .../src/helpers/createMessageCommandUsage.ts | 2 +- 9 files changed, 30 insertions(+), 27 deletions(-) diff --git a/example/modules/Commands/Flags.js b/example/modules/Commands/Flags.js index 340dbb5f..1d78446e 100644 --- a/example/modules/Commands/Flags.js +++ b/example/modules/Commands/Flags.js @@ -1,4 +1,6 @@ +// @ts-check import { MessageCommandBuilder } from "reciple"; +import { createMessageCommandUsage } from '@reciple/message-command-utils'; export class Message { commands = [ @@ -8,16 +10,18 @@ export class Message { .addFlag(flag => flag .setName('flag') .setDescription('A flag') - .setAccept('string') + .setValueType('string') .setRequired(true) .setMandatory(true) ) .setExecute(async ({ message, flags }) => { - await message.reply(flags.getFlagValues('flag')[0]); + await message.reply(flags.getFlagValues('flag', { required: true, type: 'string' })[0]); }) ]; onStart() { + logger.log(this.commands[0]) + logger.log(createMessageCommandUsage(this.commands[0])) return true; } } diff --git a/example/modules/Commands/Say.js b/example/modules/Commands/Say.js index da3675f0..04ea5bfd 100644 --- a/example/modules/Commands/Say.js +++ b/example/modules/Commands/Say.js @@ -1,5 +1,4 @@ // @ts-check - import { SlashCommandBuilder } from 'reciple'; /** diff --git a/example/modules/Preconditions/MyPrecondition.js b/example/modules/Preconditions/MyPrecondition.js index 05c8de1e..1e74c5f5 100644 --- a/example/modules/Preconditions/MyPrecondition.js +++ b/example/modules/Preconditions/MyPrecondition.js @@ -1,5 +1,4 @@ // @ts-check - /** * @satisfies {import("reciple").CommandPreconditionData} */ diff --git a/packages/core/src/classes/builders/MessageCommandBuilder.ts b/packages/core/src/classes/builders/MessageCommandBuilder.ts index 73abd577..2e8a2e3c 100644 --- a/packages/core/src/classes/builders/MessageCommandBuilder.ts +++ b/packages/core/src/classes/builders/MessageCommandBuilder.ts @@ -297,7 +297,7 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message o.name, Object.fromEntries( Object.entries({ - type: o.accept ?? 'string', + type: o.value_type ?? 'string', multiple: o.multiple, short: o.short, default: o.multiple ? o.default_values : o.default_values?.[0], diff --git a/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts b/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts index 870e900e..5290afc7 100644 --- a/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts +++ b/packages/core/src/classes/builders/MessageCommandFlagBuilder.ts @@ -39,7 +39,7 @@ export interface MessageCommandFlagBuilderData { required?: boolean; mandatory?: boolean; multiple?: boolean; - accept?: 'string'|'boolean'; + value_type?: 'string'|'boolean'; /** * The function that validates the option value. * @param options The option value and message. @@ -60,7 +60,7 @@ export class MessageCommandFlagBuilder implement public required: boolean = false; public mandatory?: boolean = false; public multiple?: boolean = false; - public accept?: 'string'|'boolean'; + public value_type?: 'string'|'boolean' = 'string'; public validate?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; public resolve_value?: (options: MessageCommandFlagBuilderResolveValueOptions) => Awaitable; @@ -72,6 +72,7 @@ export class MessageCommandFlagBuilder implement if (data?.required) this.setRequired(data.required); if (data?.mandatory) this.setMandatory(data.mandatory); if (data?.multiple) this.setMultiple(data.multiple); + if (data?.value_type) this.setValueType(data.value_type); if (data?.validate) this.setValidate(data.validate); if (data?.resolve_value) this.setResolveValue(data.resolve_value); } @@ -119,9 +120,9 @@ export class MessageCommandFlagBuilder implement return this; } - public setAccept(accept: 'string'|'boolean'): this { - MessageCommandFlagValidators.isValidAccept(accept); - this.accept = accept as any; + public setValueType(valueType: 'string'|'boolean'): this { + MessageCommandFlagValidators.isValidValueType(valueType); + this.value_type = valueType as any; return this as any; } diff --git a/packages/core/src/classes/managers/MessageCommandFlagManager.ts b/packages/core/src/classes/managers/MessageCommandFlagManager.ts index b84545b7..dddc6cac 100644 --- a/packages/core/src/classes/managers/MessageCommandFlagManager.ts +++ b/packages/core/src/classes/managers/MessageCommandFlagManager.ts @@ -72,10 +72,10 @@ export class MessageCommandFlagManager extends DataManager(name: string, options?: { required?: boolean; resolveValue?: false; }): string[]|boolean[]; + public getFlagValues(name: string, options?: { required?: boolean; resolveValue?: false; type?: V }): V extends 'string' ? string[] : V extends 'boolean' ? boolean[] : string[]|boolean[]; public getFlagValues(name: string, options?: { required?: boolean; resolveValue?: true; }): Promise; - public getFlagValues(name: string, options?: { required?: boolean; resolveValue?: boolean; }): Promise|string[]|boolean[]; - public getFlagValues(name: string, options: { required?: boolean; resolveValue?: boolean; } = { required: false, resolveValue: false }): Promise|string[]|boolean[] { + public getFlagValues(name: string, options?: { required?: boolean; resolveValue?: boolean; type?: V }): Promise|(V extends 'string' ? string[] : V extends 'boolean' ? boolean[] : string[]|boolean[]); + public getFlagValues(name: string, options: { required?: boolean; resolveValue?: boolean; type?: 'string'|'boolean' } = { required: false, resolveValue: false }): Promise|string[]|boolean[] { const value = this.getFlag(name, options.required); return options.resolveValue ? Promise.resolve(value?.resolveValues()).then(v => v ?? []) diff --git a/packages/core/src/classes/structures/MessageCommandFlagValue.ts b/packages/core/src/classes/structures/MessageCommandFlagValue.ts index 17e48e49..d0369b55 100644 --- a/packages/core/src/classes/structures/MessageCommandFlagValue.ts +++ b/packages/core/src/classes/structures/MessageCommandFlagValue.ts @@ -5,7 +5,7 @@ import type { MessageCommandFlagBuilder, MessageCommandFlagBuilderResolveValueOp import type { RecipleClient } from './RecipleClient.js'; import { RecipleError } from './RecipleError.js'; -export interface MessageCommandFlagValueData { +export interface MessageCommandFlagValueData { /** * The name of the option. */ @@ -17,7 +17,7 @@ export interface MessageCommandFlagValueData { /** * The raw value of the option. */ - values: string[]|boolean[]; + values: V extends 'boolean' ? boolean[] : V extends 'string' ? string[] : string[]|boolean[]; /** * Whether the option is missing. */ @@ -36,15 +36,15 @@ export interface MessageCommandFlagParseOptionValueOptions extends MessageCommandFlagValueData, Pick, 'parserData'|'command'> { +export interface MessageCommandFlagValueOptions extends MessageCommandFlagValueData, Pick, 'parserData'|'command'> { client: RecipleClient; message: Message; } -export class MessageCommandFlagValue implements MessageCommandFlagValueData { +export class MessageCommandFlagValue implements MessageCommandFlagValueData { readonly name: string; readonly flag: MessageCommandFlagBuilder; - readonly values: string[]|boolean[]; + readonly values: V extends 'boolean' ? boolean[] : V extends 'string' ? string[] : string[]|boolean[]; readonly missing: boolean; readonly invalid: boolean; readonly message: Message; @@ -54,7 +54,7 @@ export class MessageCommandFlagValue implements readonly command: MessageCommandBuilder; readonly client: RecipleClient; - constructor(options: MessageCommandFlagValueOptions) { + constructor(options: MessageCommandFlagValueOptions) { this.name = options.name; this.flag = options.flag; this.values = options.values; @@ -98,7 +98,7 @@ export class MessageCommandFlagValue implements } public static async parseFlagValue(options: MessageCommandFlagParseOptionValueOptions): Promise> { - const filteredValues = options.values?.filter(value => typeof value == options.flag.accept); + const filteredValues = options.values?.filter(value => typeof value == options.flag.value_type); const missing = filteredValues ? !!options.flag.required && !filteredValues?.length : options.flag.mandatory ?? false; diff --git a/packages/core/src/classes/validators/MessageCommandFlagValidator.ts b/packages/core/src/classes/validators/MessageCommandFlagValidator.ts index c1bb35ff..6d92463b 100644 --- a/packages/core/src/classes/validators/MessageCommandFlagValidator.ts +++ b/packages/core/src/classes/validators/MessageCommandFlagValidator.ts @@ -42,13 +42,13 @@ export class MessageCommandFlagValidators extends BaseCommandValidators { .boolean({ message: 'Expected boolean for .multiple' }) .optional(); - public static accept = MessageCommandFlagValidators.s + public static value_type = MessageCommandFlagValidators.s .union([ MessageCommandFlagValidators.s - .literal('string', { equalsOptions: { message: 'Expected "string" for .accept' } }) + .literal('string', { equalsOptions: { message: 'Expected "string" for .value_type' } }) .optional(), MessageCommandFlagValidators.s - .literal('boolean', { equalsOptions: { message: 'Expected "boolean" for .accept' } }) + .literal('boolean', { equalsOptions: { message: 'Expected "boolean" for .value_type' } }) .optional(), ]) .optional(); @@ -102,8 +102,8 @@ export class MessageCommandFlagValidators extends BaseCommandValidators { MessageCommandFlagValidators.multiple.setValidationEnabled(MessageCommandFlagValidators.isValidationEnabled).parse(multiple); } - public static isValidAccept(accept: unknown): asserts accept is MessageCommandFlagBuilderData['accept'] { - MessageCommandFlagValidators.accept.setValidationEnabled(MessageCommandFlagValidators.isValidationEnabled).parse(accept); + public static isValidValueType(valueType: unknown): asserts valueType is MessageCommandFlagBuilderData['value_type'] { + MessageCommandFlagValidators.value_type.setValidationEnabled(MessageCommandFlagValidators.isValidationEnabled).parse(valueType); } public static isValidValidate(validate: unknown): asserts validate is MessageCommandFlagBuilderData['validate'] { @@ -124,7 +124,7 @@ export class MessageCommandFlagValidators extends BaseCommandValidators { MessageCommandFlagValidators.isValidRequired(opt.required); MessageCommandFlagValidators.isValidMandatory(opt.mandatory); MessageCommandFlagValidators.isValidMultiple(opt.multiple); - MessageCommandFlagValidators.isValidAccept(opt.accept); + MessageCommandFlagValidators.isValidValueType(opt.value_type); MessageCommandFlagValidators.isValidValidate(opt.validate); MessageCommandFlagValidators.isValidResolveValue(opt.resolve_value); } diff --git a/packages/message-command-utils/src/helpers/createMessageCommandUsage.ts b/packages/message-command-utils/src/helpers/createMessageCommandUsage.ts index d5d875eb..8fe046ed 100644 --- a/packages/message-command-utils/src/helpers/createMessageCommandUsage.ts +++ b/packages/message-command-utils/src/helpers/createMessageCommandUsage.ts @@ -43,7 +43,7 @@ export function createMessageCommandUsage(data: MessageCommandResolvable, option let value = `${options?.flags?.useShort ? '-' + flag.short : '--' + flag.name}`; - if (options?.flags?.showValueType) value += `=${brackets[0]}${flag.accept === 'string' ? 'string' : 'boolean'}${flag.multiple ? '...' : ''}${brackets[1]}`; + if (options?.flags?.showValueType) value += `=${brackets[0]}${flag.value_type === 'string' ? 'string' : 'boolean'}${flag.multiple ? '...' : ''}${brackets[1]}`; } return usage; From d2281098390fe82b705b15abf64524049f0d0dbf Mon Sep 17 00:00:00 2001 From: Cat++ <69035887+NotGhex@users.noreply.github.com> Date: Sun, 4 Aug 2024 15:08:39 +0800 Subject: [PATCH 16/16] fix usage --- .../src/helpers/createMessageCommandUsage.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/message-command-utils/src/helpers/createMessageCommandUsage.ts b/packages/message-command-utils/src/helpers/createMessageCommandUsage.ts index 8fe046ed..d86fddeb 100644 --- a/packages/message-command-utils/src/helpers/createMessageCommandUsage.ts +++ b/packages/message-command-utils/src/helpers/createMessageCommandUsage.ts @@ -35,15 +35,16 @@ export function createMessageCommandUsage(data: MessageCommandResolvable, option if (options?.flags?.include !== false && command.flags) for (const flagData of command.flags) { const flag = isJSONEncodable(flagData) ? flagData.toJSON() : flagData; - const brackets = flag.mandatory - ? options?.flags?.flagBrackets?.mandatory ?? ['<', '>'] - : flag.required - ? options?.flags?.flagBrackets?.required ?? ['<', '>'] - : options?.flags?.flagBrackets?.optional ?? ['[', ']']; + const brackets = flag.required + ? options?.flags?.flagBrackets?.required ?? ['<', '>'] + : options?.flags?.flagBrackets?.optional ?? ['[', ']']; + const mandatory = options?.flags?.flagBrackets?.mandatory ?? ['', ''] let value = `${options?.flags?.useShort ? '-' + flag.short : '--' + flag.name}`; if (options?.flags?.showValueType) value += `=${brackets[0]}${flag.value_type === 'string' ? 'string' : 'boolean'}${flag.multiple ? '...' : ''}${brackets[1]}`; + + usage += ` ${mandatory[0]}${value}${mandatory[1]}`; } return usage;