Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Message Command Flags #63

Merged
merged 16 commits into from
Aug 4, 2024
29 changes: 29 additions & 0 deletions example/modules/Commands/Flags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// @ts-check
import { MessageCommandBuilder } from "reciple";
import { createMessageCommandUsage } from '@reciple/message-command-utils';

export class Message {
commands = [
new MessageCommandBuilder()
.setName('flag')
.setDescription('Sends a message')
.addFlag(flag => flag
.setName('flag')
.setDescription('A flag')
.setValueType('string')
.setRequired(true)
.setMandatory(true)
)
.setExecute(async ({ message, flags }) => {
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;
}
}

export default new Message()
1 change: 0 additions & 1 deletion example/modules/Commands/Say.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @ts-check

import { SlashCommandBuilder } from 'reciple';

/**
Expand Down
15 changes: 12 additions & 3 deletions example/modules/Halts/MessageCommandArguments.js
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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;
Expand Down
1 change: 0 additions & 1 deletion example/modules/Preconditions/MyPrecondition.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @ts-check

/**
* @satisfies {import("reciple").CommandPreconditionData}
*/
Expand Down
2 changes: 1 addition & 1 deletion example/nodemon.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"node_modules/**"
],
"watch": [
"src",
"modules",
"reciple.mjs",
".env"
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ContextMenuCommandExecuteData|null> {
Expand Down
132 changes: 126 additions & 6 deletions packages/core/src/classes/builders/MessageCommandBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ 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';
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;
client: RecipleClient<true>;
message: Message<boolean>;
parserData: CommandData;
options: MessageCommandOptionManager;
flags: MessageCommandFlagManager;
builder: MessageCommandBuilder;
}

Expand Down Expand Up @@ -47,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
Expand All @@ -61,6 +71,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 {
Expand All @@ -77,9 +91,11 @@ 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[] = [];
public flags: MessageCommandFlagBuilder[] = [];

constructor(data?: Omit<Partial<MessageCommandBuilderData>, 'command_type'>) {
super(data);
Expand All @@ -88,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);
}

/**
Expand Down Expand Up @@ -145,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.
Expand All @@ -170,7 +198,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.');
Expand All @@ -196,6 +224,36 @@ 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);

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;
}

/**
* Sets the flags of the command.
* @param flags Flags data or builders.
*/
public setFlags(...flags: RestOrArray<MessageCommandFlagResolvable|((builder: MessageCommandFlagBuilder) => 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,
Expand All @@ -204,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<CommandType.MessageCommand, MessageCommandExecuteFunction>()
};
}
Expand All @@ -214,20 +273,53 @@ 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<MessageCommandExecuteData|null> {
if (!message.content) return null;

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 builder = command ? this.resolve(command) : client.commands.get(parserData.name, CommandType.MessageCommand);
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,
Object.fromEntries(
Object.entries({
type: o.value_type ?? 'string',
multiple: o.multiple,
short: o.short,
default: o.multiple ? o.default_values : o.default_values?.[0],
})
.filter(([key, value]) => value !== undefined)
) as any
])
),
});

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 executeData: MessageCommandExecuteData = {
type: builder.command_type,
client,
Expand All @@ -239,6 +331,12 @@ export class MessageCommandBuilder extends BaseCommandBuilder implements Message
message,
parserData,
client
}),
flags: await MessageCommandFlagManager.parseFlags({
command: builder,
message,
parserData,
client
})
};

Expand Down Expand Up @@ -295,6 +393,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;
}
}
Expand Down
Loading
Loading