feat: optionsParser option

This commit is contained in:
MARCROCK22 2024-06-02 20:21:18 +00:00
parent 5f61e8e66b
commit 2737b8f426
4 changed files with 604 additions and 525 deletions

View File

@ -1,6 +1,29 @@
import { GatewayIntentBits, type GatewayDispatchPayload, type GatewayPresenceUpdateData } from 'discord-api-types/v10'; import {
import type { Command, CommandContext, Message, SubCommand } from '..'; type APIApplicationCommandInteractionDataOption,
import { lazyLoadPackage, type DeepPartial, type If, type WatcherPayload, type WatcherSendToShard } from '../common'; GatewayIntentBits,
type GatewayMessageCreateDispatchData,
type GatewayDispatchPayload,
type GatewayPresenceUpdateData,
} from 'discord-api-types/v10';
import type {
Command,
CommandContext,
ContextOptionsResolved,
Message,
MessageCommandOptionErrors,
SubCommand,
UsingClient,
} from '..';
import {
type Awaitable,
type MakeRequired,
MergeOptions,
lazyLoadPackage,
type DeepPartial,
type If,
type WatcherPayload,
type WatcherSendToShard,
} from '../common';
import { EventHandler } from '../events'; import { EventHandler } from '../events';
import { ClientUser } from '../structures'; import { ClientUser } from '../structures';
import { ShardManager, properties, type ShardManagerOptions } from '../websocket'; import { ShardManager, properties, type ShardManagerOptions } from '../websocket';
@ -9,7 +32,7 @@ import { PresenceUpdateHandler } from '../websocket/discord/events/presenceUpdat
import type { BaseClientOptions, InternalRuntimeConfig, ServicesOptions, StartOptions } from './base'; import type { BaseClientOptions, InternalRuntimeConfig, ServicesOptions, StartOptions } from './base';
import { BaseClient } from './base'; import { BaseClient } from './base';
import { onInteractionCreate } from './oninteractioncreate'; import { onInteractionCreate } from './oninteractioncreate';
import { onMessageCreate } from './onmessagecreate'; import { defaultArgsParser, defaultParseOptions, onMessageCreate } from './onmessagecreate';
import { Collectors } from './collectors'; import { Collectors } from './collectors';
let parentPort: import('node:worker_threads').MessagePort; let parentPort: import('node:worker_threads').MessagePort;
@ -18,7 +41,9 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
private __handleGuilds?: Set<string> = new Set(); private __handleGuilds?: Set<string> = new Set();
gateway!: ShardManager; gateway!: ShardManager;
me!: If<Ready, ClientUser>; me!: If<Ready, ClientUser>;
declare options: ClientOptions; declare options: Omit<ClientOptions, 'commands'> & {
commands: MakeRequired<NonNullable<ClientOptions['commands']>, 'argsParser' | 'optionsParser'>;
};
memberUpdateHandler = new MemberUpdateHandler(); memberUpdateHandler = new MemberUpdateHandler();
presenceUpdateHandler = new PresenceUpdateHandler(); presenceUpdateHandler = new PresenceUpdateHandler();
collectors = new Collectors(); collectors = new Collectors();
@ -26,6 +51,12 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
constructor(options?: ClientOptions) { constructor(options?: ClientOptions) {
super(options); super(options);
this.options = MergeOptions(this.options, {
commands: {
argsParser: defaultArgsParser,
optionsParser: defaultParseOptions,
},
} satisfies ClientOptions);
} }
setServices({ setServices({
@ -212,6 +243,20 @@ export interface ClientOptions extends BaseClientOptions {
deferReplyResponse?: (ctx: CommandContext) => Parameters<Message['write']>[0]; deferReplyResponse?: (ctx: CommandContext) => Parameters<Message['write']>[0];
reply?: (ctx: CommandContext) => boolean; reply?: (ctx: CommandContext) => boolean;
argsParser?: (content: string, command: SubCommand | Command, message: Message) => Record<string, string>; argsParser?: (content: string, command: SubCommand | Command, message: Message) => Record<string, string>;
optionsParser?: (
self: UsingClient,
command: Command | SubCommand,
message: GatewayMessageCreateDispatchData,
args: Partial<Record<string, string>>,
resolved: MakeRequired<ContextOptionsResolved>,
) => Awaitable<{
errors: {
name: string;
error: string;
fullError: MessageCommandOptionErrors;
}[];
options: APIApplicationCommandInteractionDataOption[];
}>;
}; };
handlePayload?: ShardManagerOptions['handlePayload']; handlePayload?: ShardManagerOptions['handlePayload'];
} }

View File

@ -1,457 +1,469 @@
import { import {
ApplicationCommandOptionType, ApplicationCommandOptionType,
ChannelType, ChannelType,
InteractionContextType, InteractionContextType,
type APIApplicationCommandInteractionDataOption, type APIApplicationCommandInteractionDataOption,
type GatewayMessageCreateDispatchData, type GatewayMessageCreateDispatchData,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { import {
Command, Command,
CommandContext, CommandContext,
IgnoreCommand, IgnoreCommand,
OptionResolver, type MessageCommandOptionErrors,
SubCommand, OptionResolver,
User, SubCommand,
type Client, User,
type CommandOption, type UsingClient,
type ContextOptionsResolved, type Client,
type SeyfertChannelOption, type CommandOption,
type SeyfertIntegerOption, type ContextOptionsResolved,
type SeyfertNumberOption, type SeyfertChannelOption,
type SeyfertStringOption, type SeyfertIntegerOption,
type WorkerClient, type SeyfertNumberOption,
} from '..'; type SeyfertStringOption,
import type { MakeRequired } from '../common'; type WorkerClient,
import { Message } from '../structures'; } from '..';
import type { MakeRequired } from '../common';
function getCommandFromContent( import { Message } from '../structures';
commandRaw: string[],
self: Client | WorkerClient, function getCommandFromContent(
): { commandRaw: string[],
command?: Command | SubCommand; self: Client | WorkerClient,
parent?: Command; ): {
fullCommandName: string; command?: Command | SubCommand;
} { parent?: Command;
const rawParentName = commandRaw[0]; fullCommandName: string;
const rawGroupName = commandRaw.length === 3 ? commandRaw[1] : undefined; } {
const rawSubcommandName = rawGroupName ? commandRaw[2] : commandRaw[1]; const rawParentName = commandRaw[0];
const parent = self.commands!.values.find( const rawGroupName = commandRaw.length === 3 ? commandRaw[1] : undefined;
x => const rawSubcommandName = rawGroupName ? commandRaw[2] : commandRaw[1];
(!('ignore' in x) || x.ignore !== IgnoreCommand.Message) && const parent = self.commands!.values.find(
(x.name === rawParentName || ('aliases' in x ? x.aliases?.includes(rawParentName) : false)), x =>
); (!('ignore' in x) || x.ignore !== IgnoreCommand.Message) &&
const fullCommandName = `${rawParentName}${ (x.name === rawParentName || ('aliases' in x ? x.aliases?.includes(rawParentName) : false)),
rawGroupName ? ` ${rawGroupName} ${rawSubcommandName}` : `${rawSubcommandName ? ` ${rawSubcommandName}` : ''}` );
}`; const fullCommandName = `${rawParentName}${
rawGroupName ? ` ${rawGroupName} ${rawSubcommandName}` : `${rawSubcommandName ? ` ${rawSubcommandName}` : ''}`
if (!(parent instanceof Command)) return { fullCommandName }; }`;
if (rawGroupName && !parent.groups?.[rawGroupName] && !parent.groupsAliases?.[rawGroupName]) if (!(parent instanceof Command)) return { fullCommandName };
return getCommandFromContent([rawParentName, rawGroupName], self);
if ( if (rawGroupName && !parent.groups?.[rawGroupName] && !parent.groupsAliases?.[rawGroupName])
rawSubcommandName && return getCommandFromContent([rawParentName, rawGroupName], self);
!parent.options?.some( if (
x => x instanceof SubCommand && (x.name === rawSubcommandName || x.aliases?.includes(rawSubcommandName)), rawSubcommandName &&
) !parent.options?.some(
) x => x instanceof SubCommand && (x.name === rawSubcommandName || x.aliases?.includes(rawSubcommandName)),
return getCommandFromContent([rawParentName], self); )
)
const groupName = rawGroupName ? parent.groupsAliases?.[rawGroupName] || rawGroupName : undefined; return getCommandFromContent([rawParentName], self);
const command = const groupName = rawGroupName ? parent.groupsAliases?.[rawGroupName] || rawGroupName : undefined;
groupName || rawSubcommandName
? (parent.options?.find(opt => { const command =
if (opt instanceof SubCommand) { groupName || rawSubcommandName
if (groupName) { ? (parent.options?.find(opt => {
if (opt.group !== groupName) return false; if (opt instanceof SubCommand) {
} if (groupName) {
if (opt.group && !groupName) return false; if (opt.group !== groupName) return false;
return rawSubcommandName === opt.name || opt.aliases?.includes(rawSubcommandName); }
} if (opt.group && !groupName) return false;
return false; return rawSubcommandName === opt.name || opt.aliases?.includes(rawSubcommandName);
}) as SubCommand) }
: parent; return false;
}) as SubCommand)
return { : parent;
command,
fullCommandName, return {
parent, command,
}; fullCommandName,
} parent,
};
export async function onMessageCreate( }
self: Client | WorkerClient,
rawMessage: GatewayMessageCreateDispatchData, export async function onMessageCreate(
shardId: number, self: Client | WorkerClient,
) { rawMessage: GatewayMessageCreateDispatchData,
if (!self.options?.commands?.prefix) return; shardId: number,
const message = new Message(self, rawMessage); ) {
const prefixes = (await self.options.commands.prefix(message)).sort((a, b) => b.length - a.length); if (!self.options?.commands?.prefix) return;
const prefix = prefixes.find(x => message.content.startsWith(x)); const message = new Message(self, rawMessage);
const prefixes = (await self.options.commands.prefix(message)).sort((a, b) => b.length - a.length);
if (!(prefix && message.content.startsWith(prefix))) return; const prefix = prefixes.find(x => message.content.startsWith(x));
const content = message.content.slice(prefix.length).trimStart(); if (!(prefix && message.content.startsWith(prefix))) return;
const { fullCommandName, command, parent } = getCommandFromContent(
content const content = message.content.slice(prefix.length).trimStart();
.split(' ') const { fullCommandName, command, parent } = getCommandFromContent(
.filter(x => x) content
.slice(0, 3), .split(' ')
self, .filter(x => x)
); .slice(0, 3),
self,
if (!command) return; );
if (!command.run) return self.logger.warn(`${fullCommandName} command does not have 'run' callback`);
if (!command) return;
if (!(command.contexts?.includes(InteractionContextType.BotDM) || message.guildId)) return; if (!command.run) return self.logger.warn(`${fullCommandName} command does not have 'run' callback`);
if (command.guildId && !command.guildId?.includes(message.guildId!)) return;
if (!(command.contexts?.includes(InteractionContextType.BotDM) || message.guildId)) return;
const resolved: MakeRequired<ContextOptionsResolved> = { if (command.guildId && !command.guildId?.includes(message.guildId!)) return;
channels: {},
roles: {}, const resolved: MakeRequired<ContextOptionsResolved> = {
users: {}, channels: {},
members: {}, roles: {},
attachments: {}, users: {},
}; members: {},
attachments: {},
let newContent = content; };
for (const i of fullCommandName.split(' ')) {
newContent = newContent.slice(newContent.indexOf(i) + i.length); let newContent = content;
} for (const i of fullCommandName.split(' ')) {
newContent = newContent.slice(newContent.indexOf(i) + i.length);
const args = (self.options?.commands?.argsParser ?? defaultArgsParser)(newContent.slice(1), command, message); }
const { options, errors } = await parseOptions(self, command, rawMessage, args, resolved);
const optionsResolver = new OptionResolver(self, options, parent as Command, message.guildId, resolved); const args = self.options.commands.argsParser(newContent.slice(1), command, message);
const context = new CommandContext(self, message, optionsResolver, shardId, command); const { options, errors } = await self.options.commands.optionsParser(self, command, rawMessage, args, resolved);
//@ts-expect-error const optionsResolver = new OptionResolver(self, options, parent as Command, message.guildId, resolved);
const extendContext = self.options?.context?.(message) ?? {}; const context = new CommandContext(self, message, optionsResolver, shardId, command);
Object.assign(context, extendContext); //@ts-expect-error
try { const extendContext = self.options?.context?.(message) ?? {};
if (command.defaultMemberPermissions && message.guildId) { Object.assign(context, extendContext);
const memberPermissions = await self.members.permissions(message.guildId, message.author.id); try {
const permissions = memberPermissions.missings(...memberPermissions.values([command.defaultMemberPermissions])); if (errors.length) {
if ( return command.onOptionsError?.(
!memberPermissions.has('Administrator') && context,
permissions.length && Object.fromEntries(
(await message.guild())!.ownerId !== message.author.id errors.map(x => {
) { return [
return command.onPermissionsFail?.(context, memberPermissions.keys(permissions)); x.name,
} {
} failed: true,
if (command.botPermissions && message.guildId) { value: x.error,
const meMember = await self.cache.members?.get(self.botId, message.guildId); },
if (!meMember) return; //enable member cache and "Guilds" intent, lol ];
const appPermissions = await meMember.fetchPermissions(); }),
const permissions = appPermissions.missings(...appPermissions.values([command.botPermissions])); ),
if (!appPermissions.has('Administrator') && permissions.length) { );
return command.onBotPermissionsFail?.(context, appPermissions.keys(permissions)); }
} if (command.defaultMemberPermissions && message.guildId) {
} const memberPermissions = await self.members.permissions(message.guildId, message.author.id);
if (errors.length) { const permissions = memberPermissions.missings(...memberPermissions.values([command.defaultMemberPermissions]));
return command.onOptionsError?.( if (
context, !memberPermissions.has('Administrator') &&
Object.fromEntries( permissions.length &&
errors.map(x => { (await message.guild())!.ownerId !== message.author.id
return [ ) {
x.name, return command.onPermissionsFail?.(context, memberPermissions.keys(permissions));
{ }
failed: true, }
value: x.error, if (command.botPermissions && message.guildId) {
}, const meMember = await self.cache.members?.get(self.botId, message.guildId);
]; if (!meMember) return; //enable member cache and "Guilds" intent, lol
}), const appPermissions = await meMember.fetchPermissions();
), const permissions = appPermissions.missings(...appPermissions.values([command.botPermissions]));
); if (!appPermissions.has('Administrator') && permissions.length) {
} return command.onBotPermissionsFail?.(context, appPermissions.keys(permissions));
const [erroredOptions, result] = await command.__runOptions(context, optionsResolver); }
if (erroredOptions) { }
return command.onOptionsError?.(context, result); const [erroredOptions, result] = await command.__runOptions(context, optionsResolver);
} if (erroredOptions) {
const resultRunGlobalMiddlewares = await command.__runGlobalMiddlewares(context); return command.onOptionsError?.(context, result);
if (resultRunGlobalMiddlewares.pass) { }
return; const resultRunGlobalMiddlewares = await command.__runGlobalMiddlewares(context);
} if (resultRunGlobalMiddlewares.pass) {
if ('error' in resultRunGlobalMiddlewares) { return;
return command.onMiddlewaresError?.(context, resultRunGlobalMiddlewares.error ?? 'Unknown error'); }
} if ('error' in resultRunGlobalMiddlewares) {
const resultRunMiddlewares = await command.__runMiddlewares(context); return command.onMiddlewaresError?.(context, resultRunGlobalMiddlewares.error ?? 'Unknown error');
if (resultRunMiddlewares.pass) { }
return; const resultRunMiddlewares = await command.__runMiddlewares(context);
} if (resultRunMiddlewares.pass) {
if ('error' in resultRunMiddlewares) { return;
return command.onMiddlewaresError?.(context, resultRunMiddlewares.error ?? 'Unknown error'); }
} if ('error' in resultRunMiddlewares) {
return command.onMiddlewaresError?.(context, resultRunMiddlewares.error ?? 'Unknown error');
try { }
await command.run?.(context);
await command.onAfterRun?.(context, undefined); try {
} catch (error) { await command.run?.(context);
await command.onRunError?.(context, error); await command.onAfterRun?.(context, undefined);
await command.onAfterRun?.(context, error); } catch (error) {
} await command.onRunError?.(context, error);
} catch (error) { await command.onAfterRun?.(context, error);
try { }
await command.onInternalError?.(self, context.command, error); } catch (error) {
} catch { try {
// supress error await command.onInternalError?.(self, context.command, error);
} } catch {
} // supress error
} }
}
async function parseOptions( }
self: Client | WorkerClient,
command: Command | SubCommand, export async function defaultParseOptions(
message: GatewayMessageCreateDispatchData, self: UsingClient,
args: Partial<Record<string, string>>, command: Command | SubCommand,
resolved: MakeRequired<ContextOptionsResolved>, message: GatewayMessageCreateDispatchData,
) { args: Partial<Record<string, string>>,
const options: APIApplicationCommandInteractionDataOption[] = []; resolved: MakeRequired<ContextOptionsResolved>,
const errors: { name: string; error: string }[] = []; ) {
for (const i of (command.options ?? []) as (CommandOption & { type: ApplicationCommandOptionType })[]) { const options: APIApplicationCommandInteractionDataOption[] = [];
try { const errors: { name: string; error: string; fullError: MessageCommandOptionErrors }[] = [];
let value: string | boolean | number | undefined; for (const i of (command.options ?? []) as (CommandOption & { type: ApplicationCommandOptionType })[]) {
let indexAttachment = -1; try {
switch (i.type) { let value: string | boolean | number | undefined;
case ApplicationCommandOptionType.Attachment: let indexAttachment = -1;
if (message.attachments[++indexAttachment]) { switch (i.type) {
value = message.attachments[indexAttachment].id; case ApplicationCommandOptionType.Attachment:
resolved.attachments[value] = message.attachments[indexAttachment]; if (message.attachments[++indexAttachment]) {
} value = message.attachments[indexAttachment].id;
break; resolved.attachments[value] = message.attachments[indexAttachment];
case ApplicationCommandOptionType.Boolean: }
if (args[i.name]) { break;
value = ['yes', 'y', 'true', 'treu'].includes(args[i.name]!.toLowerCase()); case ApplicationCommandOptionType.Boolean:
} if (args[i.name]) {
break; value = ['yes', 'y', 'true', 'treu'].includes(args[i.name]!.toLowerCase());
case ApplicationCommandOptionType.Channel: }
{ break;
const rawId = case ApplicationCommandOptionType.Channel:
message.content.match(/(?<=<#)[0-9]{17,19}(?=>)/g)?.find(x => args[i.name]?.includes(x)) || {
args[i.name]?.match(/[0-9]{17,19}/g)?.[0]; const rawId =
if (rawId) { message.content.match(/(?<=<#)[0-9]{17,19}(?=>)/g)?.find(x => args[i.name]?.includes(x)) ||
const channel = args[i.name]?.match(/[0-9]{17,19}/g)?.[0];
(await self.cache.channels?.get(rawId)) ?? (i.required ? await self.channels.fetch(rawId) : undefined); if (rawId) {
if (channel) { const channel =
if ('channel_types' in i) { (await self.cache.channels?.get(rawId)) ?? (i.required ? await self.channels.fetch(rawId) : undefined);
if (!(i as SeyfertChannelOption).channel_types!.includes(channel.type)) { if (channel) {
errors.push({ if ('channel_types' in i) {
name: i.name, if (!(i as SeyfertChannelOption).channel_types!.includes(channel.type)) {
error: `The entered channel type is not one of ${(i as SeyfertChannelOption) errors.push({
.channel_types!.map(t => ChannelType[t]) name: i.name,
.join(', ')}`, error: `The entered channel type is not one of ${(i as SeyfertChannelOption)
}); .channel_types!.map(t => ChannelType[t])
break; .join(', ')}`,
} fullError: ['CHANNEL_TYPES', (i as SeyfertChannelOption).channel_types!],
} });
value = rawId; break;
resolved.channels[rawId] = channel; }
} }
} value = rawId;
} resolved.channels[rawId] = channel;
break; }
case ApplicationCommandOptionType.Mentionable: }
{ }
const matches = message.content.match(/<@[0-9]{17,19}(?=>)|<@&[0-9]{17,19}(?=>)/g) ?? []; break;
for (const match of matches) { case ApplicationCommandOptionType.Mentionable:
if (match.includes('&')) { {
const rawId = match.slice(3); const matches = message.content.match(/<@[0-9]{17,19}(?=>)|<@&[0-9]{17,19}(?=>)/g) ?? [];
if (rawId) { for (const match of matches) {
const role = if (match.includes('&')) {
(await self.cache.roles?.get(rawId)) ?? const rawId = match.slice(3);
(i.required ? (await self.roles.list(message.guild_id!)).find(x => x.id === rawId) : undefined); if (rawId) {
if (role) { const role =
value = rawId; (await self.cache.roles?.get(rawId)) ??
resolved.roles[rawId] = role; (i.required ? (await self.roles.list(message.guild_id!)).find(x => x.id === rawId) : undefined);
break; if (role) {
} value = rawId;
} resolved.roles[rawId] = role;
} else { break;
const rawId = match.slice(2); }
const raw = message.mentions.find(x => rawId === x.id); }
if (raw) { } else {
const { member, ...user } = raw; const rawId = match.slice(2);
value = raw.id; const raw = message.mentions.find(x => rawId === x.id);
resolved.users[raw.id] = user; if (raw) {
if (member) resolved.members[raw.id] = member; const { member, ...user } = raw;
break; value = raw.id;
} resolved.users[raw.id] = user;
} if (member) resolved.members[raw.id] = member;
} break;
} }
break; }
case ApplicationCommandOptionType.Role: }
{ }
const rawId = break;
message.mention_roles.find(x => args[i.name]?.includes(x)) || args[i.name]?.match(/[0-9]{17,19}/g)?.[0]; case ApplicationCommandOptionType.Role:
if (rawId) { {
const role = const rawId =
(await self.cache.roles?.get(rawId)) ?? message.mention_roles.find(x => args[i.name]?.includes(x)) || args[i.name]?.match(/[0-9]{17,19}/g)?.[0];
(i.required ? (await self.roles.list(message.guild_id!)).find(x => x.id === rawId) : undefined); if (rawId) {
const role =
if (role) { (await self.cache.roles?.get(rawId)) ??
value = rawId; (i.required ? (await self.roles.list(message.guild_id!)).find(x => x.id === rawId) : undefined);
resolved.roles[rawId] = role;
} if (role) {
} value = rawId;
} resolved.roles[rawId] = role;
break; }
case ApplicationCommandOptionType.User: }
{ }
const rawId = break;
message.mentions.find(x => args[i.name]?.includes(x.id))?.id || args[i.name]?.match(/[0-9]{17,19}/g)?.[0]; case ApplicationCommandOptionType.User:
if (rawId) { {
const raw = const rawId =
message.mentions.find(x => args[i.name]?.includes(x.id)) ?? message.mentions.find(x => args[i.name]?.includes(x.id))?.id || args[i.name]?.match(/[0-9]{17,19}/g)?.[0];
(await self.cache.users?.get(rawId)) ?? if (rawId) {
(i.required ? await self.users.fetch(rawId) : undefined); const raw =
if (raw) { message.mentions.find(x => args[i.name]?.includes(x.id)) ??
value = raw.id; (await self.cache.users?.get(rawId)) ??
if (raw instanceof User) { (i.required ? await self.users.fetch(rawId) : undefined);
resolved.users[raw.id] = raw; if (raw) {
if (message.guild_id) { value = raw.id;
const member = if (raw instanceof User) {
message.mentions.find(x => args[i.name]?.includes(x.id))?.member ?? resolved.users[raw.id] = raw;
(await self.cache.members?.get(rawId, message.guild_id)) ?? if (message.guild_id) {
(i.required ? await self.members.fetch(rawId, message.guild_id) : undefined); const member =
if (member) resolved.members[raw.id] = member; message.mentions.find(x => args[i.name]?.includes(x.id))?.member ??
} (await self.cache.members?.get(rawId, message.guild_id)) ??
} else { (i.required ? await self.members.fetch(rawId, message.guild_id) : undefined);
const { member, ...user } = raw; if (member) resolved.members[raw.id] = member;
resolved.users[user.id] = user; }
if (member) resolved.members[user.id] = member; } else {
} const { member, ...user } = raw;
} resolved.users[user.id] = user;
} if (member) resolved.members[user.id] = member;
} }
break; }
case ApplicationCommandOptionType.String: }
{ }
value = args[i.name]; break;
const option = i as SeyfertStringOption; case ApplicationCommandOptionType.String:
if (!value) break; {
if (option.min_length) { value = args[i.name];
if (value.length < option.min_length) { const option = i as SeyfertStringOption;
value = undefined; if (!value) break;
errors.push({ if (option.min_length) {
name: i.name, if (value.length < option.min_length) {
error: `The entered string has less than ${option.min_length} characters. The minimum required is ${option.min_length} characters.`, value = undefined;
}); errors.push({
break; name: i.name,
} error: `The entered string has less than ${option.min_length} characters. The minimum required is ${option.min_length} characters.`,
} fullError: ['STRING_MIN_LENGTH', option.min_length],
if (option.max_length) { });
if (value.length > option.max_length) { break;
value = undefined; }
errors.push({ }
name: i.name, if (option.max_length) {
error: `The entered string has more than ${option.max_length} characters. The maximum required is ${option.max_length} characters.`, if (value.length > option.max_length) {
}); value = undefined;
break; errors.push({
} name: i.name,
} error: `The entered string has more than ${option.max_length} characters. The maximum required is ${option.max_length} characters.`,
if (option.choices?.length) { fullError: ['STRING_MAX_LENGTH', option.max_length],
const choice = option.choices.find(x => x.name === value); });
if (!choice) { break;
value = undefined; }
errors.push({ }
name: i.name, if (option.choices?.length) {
error: `The entered choice is invalid. Please choose one of the following options: ${option.choices const choice = option.choices.find(x => x.name === value);
.map(x => x.name) if (!choice) {
.join(', ')}.`, value = undefined;
}); errors.push({
break; name: i.name,
} error: `The entered choice is invalid. Please choose one of the following options: ${option.choices
value = choice.value; .map(x => x.name)
} .join(', ')}.`,
} fullError: ['STRING_INVALID_CHOICE', option.choices],
break; });
case ApplicationCommandOptionType.Number: break;
case ApplicationCommandOptionType.Integer: }
{ value = choice.value;
const option = i as SeyfertNumberOption | SeyfertIntegerOption; }
if (!option.choices?.length) { }
value = Number(args[i.name]); break;
if (args[i.name] === undefined) { case ApplicationCommandOptionType.Number:
value = undefined; case ApplicationCommandOptionType.Integer:
break; {
} const option = i as SeyfertNumberOption | SeyfertIntegerOption;
if (Number.isNaN(value)) { if (!option.choices?.length) {
value = undefined; value = Number(args[i.name]);
errors.push({ if (args[i.name] === undefined) {
name: i.name, value = undefined;
error: 'The entered choice is an invalid number.', break;
}); }
break; if (Number.isNaN(value)) {
} value = undefined;
if (option.min_value) { errors.push({
if (value < option.min_value) { name: i.name,
value = undefined; error: 'The entered choice is an invalid number.',
errors.push({ fullError: ['NUMBER_NAN', args[i.name]],
name: i.name, });
error: `The entered number is less than ${option.min_value}. The minimum allowed is ${option.min_value}`, break;
}); }
break; if (option.min_value) {
} if (value < option.min_value) {
} value = undefined;
if (option.max_value) { errors.push({
if (value > option.max_value) { name: i.name,
value = undefined; error: `The entered number is less than ${option.min_value}. The minimum allowed is ${option.min_value}`,
errors.push({ fullError: ['NUMBER_MIN_VALUE', option.min_value],
name: i.name, });
error: `The entered number is greater than ${option.max_value}. The maximum allowed is ${option.max_value}`, break;
}); }
break; }
} if (option.max_value) {
} if (value > option.max_value) {
break; value = undefined;
} errors.push({
const choice = option.choices.find(x => x.name === args[i.name]); name: i.name,
if (!choice) { error: `The entered number is greater than ${option.max_value}. The maximum allowed is ${option.max_value}`,
value = undefined; fullError: ['NUMBER_MAX_VALUE', option.max_value],
errors.push({ });
name: i.name, break;
error: `The entered choice is invalid. Please choose one of the following options: ${option.choices }
.map(x => x.name) }
.join(', ')}.`, break;
}); }
break; const choice = option.choices.find(x => x.name === args[i.name]);
} if (!choice) {
value = choice.value; value = undefined;
} errors.push({
break; name: i.name,
default: error: `The entered choice is invalid. Please choose one of the following options: ${option.choices
break; .map(x => x.name)
} .join(', ')}.`,
if (value !== undefined) { fullError: ['NUMBER_INVALID_CHOICE', option.choices],
options.push({ });
name: i.name, break;
type: i.type, }
value, value = choice.value;
} as APIApplicationCommandInteractionDataOption); }
} else if (i.required) break;
if (!errors.some(x => x.name === i.name)) default:
errors.push({ break;
error: 'Option is required but returned undefined', }
name: i.name, if (value !== undefined) {
}); options.push({
} catch (e) { name: i.name,
errors.push({ type: i.type,
error: e && typeof e === 'object' && 'message' in e ? (e.message as string) : `${e}`, value,
name: i.name, } as APIApplicationCommandInteractionDataOption);
}); } else if (i.required)
} if (!errors.some(x => x.name === i.name))
} errors.push({
error: 'Option is required but returned undefined',
return { errors, options }; name: i.name,
} fullError: ['OPTION_REQUIRED'],
});
function defaultArgsParser(content: string) { } catch (e) {
const args: Record<string, string> = {}; errors.push({
for (const i of content.match(/-(.*?)(?=\s-|$)/gs) ?? []) { error: e && typeof e === 'object' && 'message' in e ? (e.message as string) : `${e}`,
args[i.slice(1).split(' ')[0]] = i.split(' ').slice(1).join(' '); name: i.name,
} fullError: ['UNKNOWN', e],
return args; });
} }
}
//-(.*?)(?=\s-|$)/gs
//-(?<text>[^-]*)/gm return { errors, options };
}
export function defaultArgsParser(content: string) {
const args: Record<string, string> = {};
for (const i of content.match(/-(.*?)(?=\s-|$)/gs) ?? []) {
args[i.slice(1).split(' ')[0]] = i.split(' ').slice(1).join(' ');
}
return args;
}
//-(.*?)(?=\s-|$)/gs
//-(?<text>[^-]*)/gm

View File

@ -31,6 +31,7 @@ import type {
StopFunction, StopFunction,
UsingClient, UsingClient,
} from './shared'; } from './shared';
import { inspect } from 'node:util';
export interface ReturnOptionsTypes { export interface ReturnOptionsTypes {
1: never; // subcommand 1: never; // subcommand
@ -153,9 +154,15 @@ export class BaseCommand {
const value = const value =
resolver.getHoisted(i.name)?.value !== undefined resolver.getHoisted(i.name)?.value !== undefined
? await new Promise( ? await new Promise(
(res, rej) => // biome-ignore lint/suspicious/noAsyncPromiseExecutor: yes
option.value?.({ context: ctx, value: resolver.getValue(i.name) } as never, res, rej) || async (res, rej) => {
res(resolver.getValue(i.name)), try {
(await option.value?.({ context: ctx, value: resolver.getValue(i.name) } as never, res, rej)) ||
res(resolver.getValue(i.name));
} catch (e) {
rej(e);
}
},
) )
: undefined; : undefined;
if (value === undefined) { if (value === undefined) {
@ -178,7 +185,7 @@ export class BaseCommand {
errored = true; errored = true;
data[i.name] = { data[i.name] = {
failed: true, failed: true,
value: e instanceof Error ? e.message : `${e}`, value: e instanceof Error ? e.message : typeof e === 'string' ? e : inspect(e),
}; };
} }
} }

View File

@ -1,59 +1,74 @@
import type { BaseClient } from '../../client/base'; import type { ChannelType } from 'discord-api-types/v10';
import type { IsStrictlyUndefined } from '../../common'; import type { BaseClient } from '../../client/base';
import type { RegisteredMiddlewares } from '../decorators'; import type { IsStrictlyUndefined } from '../../common';
import type { RegisteredMiddlewares } from '../decorators';
export type OKFunction<T> = (value: T) => void;
export type StopFunction = (error: string) => void; export type OKFunction<T> = (value: T) => void;
export type NextFunction<T = unknown> = IsStrictlyUndefined<T> extends true ? () => void : (data: T) => void; export type StopFunction = (error: string) => void;
export type PassFunction = () => void; export type NextFunction<T = unknown> = IsStrictlyUndefined<T> extends true ? () => void : (data: T) => void;
export type PassFunction = () => void;
export type InferWithPrefix = InternalOptions extends { withPrefix: infer P } ? P : false;
export type InferWithPrefix = InternalOptions extends { withPrefix: infer P } ? P : false;
export interface GlobalMetadata {}
export interface DefaultLocale {} export interface GlobalMetadata {}
export interface ExtendContext {} export interface DefaultLocale {}
export interface UsingClient extends BaseClient {} export interface ExtendContext {}
export type ParseClient<T extends BaseClient> = T; export interface UsingClient extends BaseClient {}
export interface InternalOptions {} export type ParseClient<T extends BaseClient> = T;
export interface InternalOptions {}
export type MiddlewareContext<T = any, C = any> = (context: {
context: C; export type MiddlewareContext<T = any, C = any> = (context: {
next: NextFunction<T>; context: C;
stop: StopFunction; next: NextFunction<T>;
pass: PassFunction; stop: StopFunction;
}) => any; pass: PassFunction;
export type MetadataMiddleware<T extends MiddlewareContext> = IsStrictlyUndefined< }) => any;
Parameters<Parameters<T>[0]['next']>[0] export type MetadataMiddleware<T extends MiddlewareContext> = IsStrictlyUndefined<
> extends true Parameters<Parameters<T>[0]['next']>[0]
? never > extends true
: Parameters<Parameters<T>[0]['next']>[0]; ? never
export type CommandMetadata<T extends readonly (keyof RegisteredMiddlewares)[]> = T extends readonly [ : Parameters<Parameters<T>[0]['next']>[0];
infer first, export type CommandMetadata<T extends readonly (keyof RegisteredMiddlewares)[]> = T extends readonly [
...infer rest, infer first,
] ...infer rest,
? first extends keyof RegisteredMiddlewares ]
? (MetadataMiddleware<RegisteredMiddlewares[first]> extends never ? first extends keyof RegisteredMiddlewares
? {} ? (MetadataMiddleware<RegisteredMiddlewares[first]> extends never
: { ? {}
[key in first]: MetadataMiddleware<RegisteredMiddlewares[first]>; : {
}) & [key in first]: MetadataMiddleware<RegisteredMiddlewares[first]>;
(rest extends readonly (keyof RegisteredMiddlewares)[] ? CommandMetadata<rest> : {}) }) &
: {} (rest extends readonly (keyof RegisteredMiddlewares)[] ? CommandMetadata<rest> : {})
: {}; : {}
: {};
export type OnOptionsReturnObject = Record<
string, export type MessageCommandOptionErrors =
| { | ['CHANNEL_TYPES', type: ChannelType[]]
failed: false; | ['STRING_MIN_LENGTH', min: number]
value: unknown; | ['STRING_MAX_LENGTH', max: number]
} | ['STRING_INVALID_CHOICE', choices: readonly { name: string; value: string }[]]
| { | ['NUMBER_NAN', value: string | undefined]
failed: true; | ['NUMBER_MIN_VALUE', min: number]
value: string; | ['NUMBER_MAX_VALUE', max: number]
} | ['NUMBER_INVALID_CHOICE', choices: readonly { name: string; value: number }[]]
>; | ['OPTION_REQUIRED']
| ['UNKNOWN', error: unknown];
export enum IgnoreCommand {
Slash = 0, export type OnOptionsReturnObject = Record<
Message = 1, string,
} | {
failed: false;
value: unknown;
}
| {
failed: true;
value: string;
parseError?: //only for text command
MessageCommandOptionErrors;
}
>;
export enum IgnoreCommand {
Slash = 0,
Message = 1,
}