mirror of
https://github.com/tiramisulabs/seyfert.git
synced 2025-07-04 22:16:08 +00:00
feat: optionsParser option
This commit is contained in:
parent
5f61e8e66b
commit
2737b8f426
@ -1,6 +1,29 @@
|
||||
import { GatewayIntentBits, type GatewayDispatchPayload, type GatewayPresenceUpdateData } from 'discord-api-types/v10';
|
||||
import type { Command, CommandContext, Message, SubCommand } from '..';
|
||||
import { lazyLoadPackage, type DeepPartial, type If, type WatcherPayload, type WatcherSendToShard } from '../common';
|
||||
import {
|
||||
type APIApplicationCommandInteractionDataOption,
|
||||
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 { ClientUser } from '../structures';
|
||||
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 { BaseClient } from './base';
|
||||
import { onInteractionCreate } from './oninteractioncreate';
|
||||
import { onMessageCreate } from './onmessagecreate';
|
||||
import { defaultArgsParser, defaultParseOptions, onMessageCreate } from './onmessagecreate';
|
||||
import { Collectors } from './collectors';
|
||||
|
||||
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();
|
||||
gateway!: ShardManager;
|
||||
me!: If<Ready, ClientUser>;
|
||||
declare options: ClientOptions;
|
||||
declare options: Omit<ClientOptions, 'commands'> & {
|
||||
commands: MakeRequired<NonNullable<ClientOptions['commands']>, 'argsParser' | 'optionsParser'>;
|
||||
};
|
||||
memberUpdateHandler = new MemberUpdateHandler();
|
||||
presenceUpdateHandler = new PresenceUpdateHandler();
|
||||
collectors = new Collectors();
|
||||
@ -26,6 +51,12 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
|
||||
|
||||
constructor(options?: ClientOptions) {
|
||||
super(options);
|
||||
this.options = MergeOptions(this.options, {
|
||||
commands: {
|
||||
argsParser: defaultArgsParser,
|
||||
optionsParser: defaultParseOptions,
|
||||
},
|
||||
} satisfies ClientOptions);
|
||||
}
|
||||
|
||||
setServices({
|
||||
@ -212,6 +243,20 @@ export interface ClientOptions extends BaseClientOptions {
|
||||
deferReplyResponse?: (ctx: CommandContext) => Parameters<Message['write']>[0];
|
||||
reply?: (ctx: CommandContext) => boolean;
|
||||
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'];
|
||||
}
|
||||
|
@ -9,9 +9,11 @@ import {
|
||||
Command,
|
||||
CommandContext,
|
||||
IgnoreCommand,
|
||||
type MessageCommandOptionErrors,
|
||||
OptionResolver,
|
||||
SubCommand,
|
||||
User,
|
||||
type UsingClient,
|
||||
type Client,
|
||||
type CommandOption,
|
||||
type ContextOptionsResolved,
|
||||
@ -119,14 +121,30 @@ export async function onMessageCreate(
|
||||
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 args = self.options.commands.argsParser(newContent.slice(1), command, message);
|
||||
const { options, errors } = await self.options.commands.optionsParser(self, command, rawMessage, args, resolved);
|
||||
const optionsResolver = new OptionResolver(self, options, parent as Command, message.guildId, resolved);
|
||||
const context = new CommandContext(self, message, optionsResolver, shardId, command);
|
||||
//@ts-expect-error
|
||||
const extendContext = self.options?.context?.(message) ?? {};
|
||||
Object.assign(context, extendContext);
|
||||
try {
|
||||
if (errors.length) {
|
||||
return command.onOptionsError?.(
|
||||
context,
|
||||
Object.fromEntries(
|
||||
errors.map(x => {
|
||||
return [
|
||||
x.name,
|
||||
{
|
||||
failed: true,
|
||||
value: x.error,
|
||||
},
|
||||
];
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (command.defaultMemberPermissions && message.guildId) {
|
||||
const memberPermissions = await self.members.permissions(message.guildId, message.author.id);
|
||||
const permissions = memberPermissions.missings(...memberPermissions.values([command.defaultMemberPermissions]));
|
||||
@ -147,22 +165,6 @@ export async function onMessageCreate(
|
||||
return command.onBotPermissionsFail?.(context, appPermissions.keys(permissions));
|
||||
}
|
||||
}
|
||||
if (errors.length) {
|
||||
return command.onOptionsError?.(
|
||||
context,
|
||||
Object.fromEntries(
|
||||
errors.map(x => {
|
||||
return [
|
||||
x.name,
|
||||
{
|
||||
failed: true,
|
||||
value: x.error,
|
||||
},
|
||||
];
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
const [erroredOptions, result] = await command.__runOptions(context, optionsResolver);
|
||||
if (erroredOptions) {
|
||||
return command.onOptionsError?.(context, result);
|
||||
@ -198,15 +200,15 @@ export async function onMessageCreate(
|
||||
}
|
||||
}
|
||||
|
||||
async function parseOptions(
|
||||
self: Client | WorkerClient,
|
||||
export async function defaultParseOptions(
|
||||
self: UsingClient,
|
||||
command: Command | SubCommand,
|
||||
message: GatewayMessageCreateDispatchData,
|
||||
args: Partial<Record<string, string>>,
|
||||
resolved: MakeRequired<ContextOptionsResolved>,
|
||||
) {
|
||||
const options: APIApplicationCommandInteractionDataOption[] = [];
|
||||
const errors: { name: string; error: string }[] = [];
|
||||
const errors: { name: string; error: string; fullError: MessageCommandOptionErrors }[] = [];
|
||||
for (const i of (command.options ?? []) as (CommandOption & { type: ApplicationCommandOptionType })[]) {
|
||||
try {
|
||||
let value: string | boolean | number | undefined;
|
||||
@ -239,6 +241,7 @@ async function parseOptions(
|
||||
error: `The entered channel type is not one of ${(i as SeyfertChannelOption)
|
||||
.channel_types!.map(t => ChannelType[t])
|
||||
.join(', ')}`,
|
||||
fullError: ['CHANNEL_TYPES', (i as SeyfertChannelOption).channel_types!],
|
||||
});
|
||||
break;
|
||||
}
|
||||
@ -335,6 +338,7 @@ async function parseOptions(
|
||||
errors.push({
|
||||
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],
|
||||
});
|
||||
break;
|
||||
}
|
||||
@ -345,6 +349,7 @@ async function parseOptions(
|
||||
errors.push({
|
||||
name: i.name,
|
||||
error: `The entered string has more than ${option.max_length} characters. The maximum required is ${option.max_length} characters.`,
|
||||
fullError: ['STRING_MAX_LENGTH', option.max_length],
|
||||
});
|
||||
break;
|
||||
}
|
||||
@ -358,6 +363,7 @@ async function parseOptions(
|
||||
error: `The entered choice is invalid. Please choose one of the following options: ${option.choices
|
||||
.map(x => x.name)
|
||||
.join(', ')}.`,
|
||||
fullError: ['STRING_INVALID_CHOICE', option.choices],
|
||||
});
|
||||
break;
|
||||
}
|
||||
@ -380,6 +386,7 @@ async function parseOptions(
|
||||
errors.push({
|
||||
name: i.name,
|
||||
error: 'The entered choice is an invalid number.',
|
||||
fullError: ['NUMBER_NAN', args[i.name]],
|
||||
});
|
||||
break;
|
||||
}
|
||||
@ -389,6 +396,7 @@ async function parseOptions(
|
||||
errors.push({
|
||||
name: i.name,
|
||||
error: `The entered number is less than ${option.min_value}. The minimum allowed is ${option.min_value}`,
|
||||
fullError: ['NUMBER_MIN_VALUE', option.min_value],
|
||||
});
|
||||
break;
|
||||
}
|
||||
@ -399,6 +407,7 @@ async function parseOptions(
|
||||
errors.push({
|
||||
name: i.name,
|
||||
error: `The entered number is greater than ${option.max_value}. The maximum allowed is ${option.max_value}`,
|
||||
fullError: ['NUMBER_MAX_VALUE', option.max_value],
|
||||
});
|
||||
break;
|
||||
}
|
||||
@ -413,6 +422,7 @@ async function parseOptions(
|
||||
error: `The entered choice is invalid. Please choose one of the following options: ${option.choices
|
||||
.map(x => x.name)
|
||||
.join(', ')}.`,
|
||||
fullError: ['NUMBER_INVALID_CHOICE', option.choices],
|
||||
});
|
||||
break;
|
||||
}
|
||||
@ -433,11 +443,13 @@ async function parseOptions(
|
||||
errors.push({
|
||||
error: 'Option is required but returned undefined',
|
||||
name: i.name,
|
||||
fullError: ['OPTION_REQUIRED'],
|
||||
});
|
||||
} catch (e) {
|
||||
errors.push({
|
||||
error: e && typeof e === 'object' && 'message' in e ? (e.message as string) : `${e}`,
|
||||
name: i.name,
|
||||
fullError: ['UNKNOWN', e],
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -445,7 +457,7 @@ async function parseOptions(
|
||||
return { errors, options };
|
||||
}
|
||||
|
||||
function defaultArgsParser(content: string) {
|
||||
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(' ');
|
||||
|
@ -31,6 +31,7 @@ import type {
|
||||
StopFunction,
|
||||
UsingClient,
|
||||
} from './shared';
|
||||
import { inspect } from 'node:util';
|
||||
|
||||
export interface ReturnOptionsTypes {
|
||||
1: never; // subcommand
|
||||
@ -153,9 +154,15 @@ export class BaseCommand {
|
||||
const value =
|
||||
resolver.getHoisted(i.name)?.value !== undefined
|
||||
? await new Promise(
|
||||
(res, rej) =>
|
||||
option.value?.({ context: ctx, value: resolver.getValue(i.name) } as never, res, rej) ||
|
||||
res(resolver.getValue(i.name)),
|
||||
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: yes
|
||||
async (res, rej) => {
|
||||
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;
|
||||
if (value === undefined) {
|
||||
@ -178,7 +185,7 @@ export class BaseCommand {
|
||||
errored = true;
|
||||
data[i.name] = {
|
||||
failed: true,
|
||||
value: e instanceof Error ? e.message : `${e}`,
|
||||
value: e instanceof Error ? e.message : typeof e === 'string' ? e : inspect(e),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { ChannelType } from 'discord-api-types/v10';
|
||||
import type { BaseClient } from '../../client/base';
|
||||
import type { IsStrictlyUndefined } from '../../common';
|
||||
import type { RegisteredMiddlewares } from '../decorators';
|
||||
@ -41,6 +42,18 @@ export type CommandMetadata<T extends readonly (keyof RegisteredMiddlewares)[]>
|
||||
: {}
|
||||
: {};
|
||||
|
||||
export type MessageCommandOptionErrors =
|
||||
| ['CHANNEL_TYPES', type: ChannelType[]]
|
||||
| ['STRING_MIN_LENGTH', min: number]
|
||||
| ['STRING_MAX_LENGTH', max: number]
|
||||
| ['STRING_INVALID_CHOICE', choices: readonly { name: string; value: string }[]]
|
||||
| ['NUMBER_NAN', value: string | undefined]
|
||||
| ['NUMBER_MIN_VALUE', min: number]
|
||||
| ['NUMBER_MAX_VALUE', max: number]
|
||||
| ['NUMBER_INVALID_CHOICE', choices: readonly { name: string; value: number }[]]
|
||||
| ['OPTION_REQUIRED']
|
||||
| ['UNKNOWN', error: unknown];
|
||||
|
||||
export type OnOptionsReturnObject = Record<
|
||||
string,
|
||||
| {
|
||||
@ -50,6 +63,8 @@ export type OnOptionsReturnObject = Record<
|
||||
| {
|
||||
failed: true;
|
||||
value: string;
|
||||
parseError?: //only for text command
|
||||
MessageCommandOptionErrors;
|
||||
}
|
||||
>;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user