feat: optimize code & implement onRunError and onStopError in collectors & use Promise.all

This commit is contained in:
MARCROCK22 2024-06-02 14:33:44 +00:00
parent 3850ef38a6
commit d2157e7324
5 changed files with 824 additions and 804 deletions

View File

@ -1,70 +1,70 @@
import type { APIGuildMember } from 'discord-api-types/v10'; import type { APIGuildMember } from 'discord-api-types/v10';
import type { ReturnCache } from '../..'; import type { ReturnCache } from '../..';
import { fakePromise } from '../../common'; import { fakePromise } from '../../common';
import { GuildMember } from '../../structures'; import { GuildMember } from '../../structures';
import { GuildBasedResource } from './default/guild-based'; import { GuildBasedResource } from './default/guild-based';
export class Members extends GuildBasedResource { export class Members extends GuildBasedResource {
namespace = 'member'; namespace = 'member';
//@ts-expect-error //@ts-expect-error
filter(data: APIGuildMember, id: string, guild_id: string) { filter(data: APIGuildMember, id: string, guild_id: string) {
return true; return true;
} }
override parse(data: any, key: string, guild_id: string) { override parse(data: any, key: string, guild_id: string) {
const { user, ...rest } = super.parse(data, data.user?.id ?? key, guild_id); const { user, ...rest } = super.parse(data, data.user?.id ?? key, guild_id);
return rest; return rest;
} }
override get(id: string, guild: string): ReturnCache<GuildMember | undefined> { override get(id: string, guild: string): ReturnCache<GuildMember | undefined> {
return fakePromise(super.get(id, guild)).then(rawMember => return fakePromise(super.get(id, guild)).then(rawMember =>
fakePromise(this.client.cache.users?.get(id)).then(user => fakePromise(this.client.cache.users?.get(id)).then(user =>
rawMember && user ? new GuildMember(this.client, rawMember, user, guild) : undefined, rawMember && user ? new GuildMember(this.client, rawMember, user, guild) : undefined,
), ),
); );
} }
override bulk(ids: string[], guild: string): ReturnCache<GuildMember[]> { override bulk(ids: string[], guild: string): ReturnCache<GuildMember[]> {
return fakePromise(super.bulk(ids, guild)).then(members => return fakePromise(super.bulk(ids, guild)).then(members =>
fakePromise(this.client.cache.users?.bulk(ids) ?? []).then( fakePromise(this.client.cache.users?.bulk(ids)).then(
users => users =>
members members
.map(rawMember => { .map(rawMember => {
const user = users.find(x => x.id === rawMember.id); const user = users?.find(x => x.id === rawMember.id);
return user ? new GuildMember(this.client, rawMember, user, guild) : undefined; return user ? new GuildMember(this.client, rawMember, user, guild) : undefined;
}) })
.filter(Boolean) as GuildMember[], .filter(Boolean) as GuildMember[],
), ),
); );
} }
override values(guild: string): ReturnCache<GuildMember[]> { override values(guild: string): ReturnCache<GuildMember[]> {
return fakePromise(super.values(guild)).then(members => return fakePromise(super.values(guild)).then(members =>
fakePromise(this.client.cache.users?.values() ?? []).then( fakePromise(this.client.cache.users?.values()).then(
users => users =>
members members
.map(rawMember => { .map(rawMember => {
const user = users.find(x => x.id === rawMember.id); const user = users?.find(x => x.id === rawMember.id);
return user ? new GuildMember(this.client, rawMember, user, rawMember.guild_id) : undefined; return user ? new GuildMember(this.client, rawMember, user, rawMember.guild_id) : undefined;
}) })
.filter(Boolean) as GuildMember[], .filter(Boolean) as GuildMember[],
), ),
); );
} }
override async set(memberId: string, guildId: string, data: any): Promise<void>; override async set(memberId: string, guildId: string, data: any): Promise<void>;
override async set(memberId_dataArray: [string, any][], guildId: string): Promise<void>; override async set(memberId_dataArray: [string, any][], guildId: string): Promise<void>;
override async set(__keys: string | [string, any][], guild: string, data?: any) { override async set(__keys: string | [string, any][], guild: string, data?: any) {
const keys: [string, any][] = Array.isArray(__keys) ? __keys : [[__keys, data]]; const keys: [string, any][] = Array.isArray(__keys) ? __keys : [[__keys, data]];
const bulkData: (['members', any, string, string] | ['users', any, string])[] = []; const bulkData: (['members', any, string, string] | ['users', any, string])[] = [];
for (const [id, value] of keys) { for (const [id, value] of keys) {
if (value.user) { if (value.user) {
bulkData.push(['members', value, id, guild]); bulkData.push(['members', value, id, guild]);
bulkData.push(['users', value.user, id]); bulkData.push(['users', value.user, id]);
} }
} }
await this.cache.bulkSet(bulkData); await this.cache.bulkSet(bulkData);
} }
} }

View File

@ -1,99 +1,115 @@
import { randomUUID } from 'node:crypto'; import { randomUUID } from 'node:crypto';
import type { Awaitable, CamelCase, SnakeCase } from '../common'; import type { Awaitable, CamelCase, SnakeCase } from '../common';
import type { ClientNameEvents, GatewayEvents } from '../events'; import type { ClientNameEvents, GatewayEvents } from '../events';
import type { ClientEvents } from '../events/hooks'; import type { ClientEvents } from '../events/hooks';
import { error } from 'node:console';
type SnakeCaseClientNameEvents = Uppercase<SnakeCase<ClientNameEvents>>;
type SnakeCaseClientNameEvents = Uppercase<SnakeCase<ClientNameEvents>>;
type RunData<T extends SnakeCaseClientNameEvents> = {
options: { type RunData<T extends SnakeCaseClientNameEvents> = {
event: T; options: {
idle?: number; event: T;
timeout?: number; idle?: number;
onStop?: (reason: string) => unknown; timeout?: number;
filter: (arg: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>) => Awaitable<boolean>; onStop?: (reason: string) => unknown;
run: (arg: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>, stop: (reason?: string) => void) => unknown; onStopError?: (reason: string, error: unknown) => unknown;
}; filter: (arg: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>) => Awaitable<boolean>;
idle?: NodeJS.Timeout; run: (arg: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>, stop: (reason?: string) => void) => unknown;
timeout?: NodeJS.Timeout; onRunError?: (
nonce: string; arg: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>,
}; error: unknown,
stop: (reason?: string) => void,
export class Collectors { ) => unknown;
readonly values = new Map<SnakeCaseClientNameEvents, RunData<any>[]>(); };
idle?: NodeJS.Timeout;
private generateRandomUUID(name: SnakeCaseClientNameEvents) { timeout?: NodeJS.Timeout;
const collectors = this.values.get(name); nonce: string;
if (!collectors) return '*'; };
let nonce = randomUUID(); export class Collectors {
readonly values = new Map<SnakeCaseClientNameEvents, RunData<any>[]>();
while (collectors.find(x => x.nonce === nonce)) {
nonce = randomUUID(); private generateRandomUUID(name: SnakeCaseClientNameEvents) {
} const collectors = this.values.get(name);
if (!collectors) return '*';
return nonce;
} let nonce = randomUUID();
create<T extends SnakeCaseClientNameEvents>(options: RunData<T>['options']) { while (collectors.find(x => x.nonce === nonce)) {
if (!this.values.has(options.event)) { nonce = randomUUID();
this.values.set(options.event, []); }
}
return nonce;
const nonce = this.generateRandomUUID(options.event); }
this.values.get(options.event)!.push({ create<T extends SnakeCaseClientNameEvents>(options: RunData<T>['options']) {
options: { if (!this.values.has(options.event)) {
...options, this.values.set(options.event, []);
name: options.event, }
} as RunData<any>['options'],
idle: const nonce = this.generateRandomUUID(options.event);
options.idle && options.idle > 0
? setTimeout(() => { this.values.get(options.event)!.push({
return this.delete(options.event, nonce, 'idle'); options: {
}, options.idle) ...options,
: undefined, name: options.event,
timeout: } as RunData<any>['options'],
options.timeout && options.timeout > 0 idle:
? setTimeout(() => { options.idle && options.idle > 0
return this.delete(options.event, nonce, 'timeout'); ? setTimeout(() => {
}, options.timeout) return this.delete(options.event, nonce, 'idle');
: undefined, }, options.idle)
nonce, : undefined,
}); timeout:
return options; options.timeout && options.timeout > 0
} ? setTimeout(() => {
return this.delete(options.event, nonce, 'timeout');
private delete(name: SnakeCaseClientNameEvents, nonce: string, reason = 'unknown') { }, options.timeout)
const collectors = this.values.get(name); : undefined,
nonce,
if (!collectors?.length) { });
if (collectors) this.values.delete(name); return options;
return; }
}
private async delete(name: SnakeCaseClientNameEvents, nonce: string, reason = 'unknown') {
const index = collectors.findIndex(x => x.nonce === nonce); const collectors = this.values.get(name);
if (index === -1) return;
const collector = collectors[index]; if (!collectors?.length) {
clearTimeout(collector.idle); if (collectors) this.values.delete(name);
clearTimeout(collector.timeout); return;
collectors.splice(index, 1); }
return collector.options.onStop?.(reason);
} const index = collectors.findIndex(x => x.nonce === nonce);
if (index === -1) return;
/**@internal */ const collector = collectors[index];
async run<T extends GatewayEvents>(name: T, data: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>) { clearTimeout(collector.idle);
const collectors = this.values.get(name); clearTimeout(collector.timeout);
if (!collectors) return; collectors.splice(index, 1);
try {
for (const i of collectors) { await collector.options.onStop?.(reason);
if (await i.options.filter(data)) { } catch (e) {
i.idle?.refresh(); await collector.options.onStopError?.(reason, error);
await i.options.run(data, (reason = 'unknown') => { }
return this.delete(i.options.event, i.nonce, reason); }
});
break; /**@internal */
} async run<T extends GatewayEvents>(name: T, data: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>) {
} const collectors = this.values.get(name);
} if (!collectors) return;
}
for (const i of collectors) {
if (await i.options.filter(data)) {
i.idle?.refresh();
const stop = (reason = 'unknown') => {
return this.delete(i.options.event, i.nonce, reason);
};
try {
await i.options.run(data, stop);
} catch (e) {
await i.options.onRunError?.(data, e, stop);
}
break;
}
}
}
}

View File

@ -1,362 +1,361 @@
import { import {
ApplicationCommandOptionType, ApplicationCommandOptionType,
ApplicationCommandType, ApplicationCommandType,
type ApplicationIntegrationType, type ApplicationIntegrationType,
type InteractionContextType, type InteractionContextType,
type APIApplicationCommandBasicOption, type APIApplicationCommandBasicOption,
type APIApplicationCommandOption, type APIApplicationCommandOption,
type APIApplicationCommandSubcommandGroupOption, type APIApplicationCommandSubcommandGroupOption,
type LocaleString, type LocaleString,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import type { import type {
ComponentContext, ComponentContext,
MenuCommandContext, MenuCommandContext,
ModalContext, ModalContext,
PermissionStrings, PermissionStrings,
SeyfertNumberOption, SeyfertNumberOption,
SeyfertStringOption, SeyfertStringOption,
} from '../..'; } from '../..';
import type { Attachment } from '../../builders'; import type { Attachment } from '../../builders';
import { magicImport, type FlatObjectKeys } from '../../common'; import { magicImport, type FlatObjectKeys } from '../../common';
import type { AllChannels, AutocompleteInteraction, GuildRole, InteractionGuildMember, User } from '../../structures'; import type { AllChannels, AutocompleteInteraction, GuildRole, InteractionGuildMember, User } from '../../structures';
import type { Groups, RegisteredMiddlewares } from '../decorators'; import type { Groups, RegisteredMiddlewares } from '../decorators';
import type { OptionResolver } from '../optionresolver'; import type { OptionResolver } from '../optionresolver';
import type { CommandContext } from './chatcontext'; import type { CommandContext } from './chatcontext';
import type { import type {
DefaultLocale, DefaultLocale,
IgnoreCommand, IgnoreCommand,
OKFunction, OKFunction,
OnOptionsReturnObject, OnOptionsReturnObject,
PassFunction, PassFunction,
StopFunction, StopFunction,
UsingClient, UsingClient,
} from './shared'; } from './shared';
export interface ReturnOptionsTypes { export interface ReturnOptionsTypes {
1: never; // subcommand 1: never; // subcommand
2: never; // subcommandgroup 2: never; // subcommandgroup
3: string; 3: string;
4: number; // integer 4: number; // integer
5: boolean; 5: boolean;
6: InteractionGuildMember | User; 6: InteractionGuildMember | User;
7: AllChannels; 7: AllChannels;
8: GuildRole; 8: GuildRole;
9: GuildRole | AllChannels | User; 9: GuildRole | AllChannels | User;
10: number; // number 10: number; // number
11: Attachment; 11: Attachment;
} }
type Wrap<N extends ApplicationCommandOptionType> = N extends type Wrap<N extends ApplicationCommandOptionType> = N extends
| ApplicationCommandOptionType.Subcommand | ApplicationCommandOptionType.Subcommand
| ApplicationCommandOptionType.SubcommandGroup | ApplicationCommandOptionType.SubcommandGroup
? never ? never
: { : {
required?: boolean; required?: boolean;
value?( value?(
data: { context: CommandContext; value: ReturnOptionsTypes[N] }, data: { context: CommandContext; value: ReturnOptionsTypes[N] },
ok: OKFunction<any>, ok: OKFunction<any>,
fail: StopFunction, fail: StopFunction,
): void; ): void;
} & { } & {
description: string; description: string;
description_localizations?: APIApplicationCommandBasicOption['description_localizations']; description_localizations?: APIApplicationCommandBasicOption['description_localizations'];
name_localizations?: APIApplicationCommandBasicOption['name_localizations']; name_localizations?: APIApplicationCommandBasicOption['name_localizations'];
locales?: { locales?: {
name?: FlatObjectKeys<DefaultLocale>; name?: FlatObjectKeys<DefaultLocale>;
description?: FlatObjectKeys<DefaultLocale>; description?: FlatObjectKeys<DefaultLocale>;
}; };
}; };
export type __TypeWrapper<T extends ApplicationCommandOptionType> = Wrap<T>; export type __TypeWrapper<T extends ApplicationCommandOptionType> = Wrap<T>;
export type __TypesWrapper = { export type __TypesWrapper = {
[P in keyof typeof ApplicationCommandOptionType]: `${(typeof ApplicationCommandOptionType)[P]}` extends `${infer D extends [P in keyof typeof ApplicationCommandOptionType]: `${(typeof ApplicationCommandOptionType)[P]}` extends `${infer D extends
number}` number}`
? Wrap<D> ? Wrap<D>
: never; : never;
}; };
export type AutocompleteCallback = (interaction: AutocompleteInteraction) => any; export type AutocompleteCallback = (interaction: AutocompleteInteraction) => any;
export type OnAutocompleteErrorCallback = (interaction: AutocompleteInteraction, error: unknown) => any; export type OnAutocompleteErrorCallback = (interaction: AutocompleteInteraction, error: unknown) => any;
export type CommandBaseOption = __TypesWrapper[keyof __TypesWrapper]; export type CommandBaseOption = __TypesWrapper[keyof __TypesWrapper];
export type CommandBaseAutocompleteOption = __TypesWrapper[keyof __TypesWrapper] & { export type CommandBaseAutocompleteOption = __TypesWrapper[keyof __TypesWrapper] & {
autocomplete: AutocompleteCallback; autocomplete: AutocompleteCallback;
onAutocompleteError?: OnAutocompleteErrorCallback; onAutocompleteError?: OnAutocompleteErrorCallback;
}; };
export type CommandAutocompleteOption = CommandBaseAutocompleteOption & { name: string }; export type CommandAutocompleteOption = CommandBaseAutocompleteOption & { name: string };
export type __CommandOption = CommandBaseOption; //| CommandBaseAutocompleteOption; export type __CommandOption = CommandBaseOption; //| CommandBaseAutocompleteOption;
export type CommandOption = __CommandOption & { name: string }; export type CommandOption = __CommandOption & { name: string };
export type OptionsRecord = Record<string, __CommandOption & { type: ApplicationCommandOptionType }>; export type OptionsRecord = Record<string, __CommandOption & { type: ApplicationCommandOptionType }>;
type KeysWithoutRequired<T extends OptionsRecord> = { type KeysWithoutRequired<T extends OptionsRecord> = {
[K in keyof T]-?: T[K]['required'] extends true ? never : K; [K in keyof T]-?: T[K]['required'] extends true ? never : K;
}[keyof T]; }[keyof T];
type ContextOptionsAux<T extends OptionsRecord> = { type ContextOptionsAux<T extends OptionsRecord> = {
[K in Exclude<keyof T, KeysWithoutRequired<T>>]: T[K]['value'] extends (...args: any) => any [K in Exclude<keyof T, KeysWithoutRequired<T>>]: T[K]['value'] extends (...args: any) => any
? Parameters<Parameters<T[K]['value']>[1]>[0] ? Parameters<Parameters<T[K]['value']>[1]>[0]
: T[K] extends SeyfertStringOption | SeyfertNumberOption : T[K] extends SeyfertStringOption | SeyfertNumberOption
? T[K]['choices'] extends NonNullable<SeyfertStringOption['choices'] | SeyfertNumberOption['choices']> ? T[K]['choices'] extends NonNullable<SeyfertStringOption['choices'] | SeyfertNumberOption['choices']>
? T[K]['choices'][number]['value'] ? T[K]['choices'][number]['value']
: ReturnOptionsTypes[T[K]['type']] : ReturnOptionsTypes[T[K]['type']]
: ReturnOptionsTypes[T[K]['type']]; : ReturnOptionsTypes[T[K]['type']];
} & { } & {
[K in KeysWithoutRequired<T>]?: T[K]['value'] extends (...args: any) => any [K in KeysWithoutRequired<T>]?: T[K]['value'] extends (...args: any) => any
? Parameters<Parameters<T[K]['value']>[1]>[0] ? Parameters<Parameters<T[K]['value']>[1]>[0]
: T[K] extends SeyfertStringOption | SeyfertNumberOption : T[K] extends SeyfertStringOption | SeyfertNumberOption
? T[K]['choices'] extends NonNullable<SeyfertStringOption['choices'] | SeyfertNumberOption['choices']> ? T[K]['choices'] extends NonNullable<SeyfertStringOption['choices'] | SeyfertNumberOption['choices']>
? T[K]['choices'][number]['value'] ? T[K]['choices'][number]['value']
: ReturnOptionsTypes[T[K]['type']] : ReturnOptionsTypes[T[K]['type']]
: ReturnOptionsTypes[T[K]['type']]; : ReturnOptionsTypes[T[K]['type']];
}; };
export type ContextOptions<T extends OptionsRecord> = ContextOptionsAux<T>; export type ContextOptions<T extends OptionsRecord> = ContextOptionsAux<T>;
export class BaseCommand { export class BaseCommand {
middlewares: (keyof RegisteredMiddlewares)[] = []; middlewares: (keyof RegisteredMiddlewares)[] = [];
__filePath?: string; __filePath?: string;
__t?: { name: string | undefined; description: string | undefined }; __t?: { name: string | undefined; description: string | undefined };
__autoload?: true; __autoload?: true;
guildId?: string[]; guildId?: string[];
name!: string; name!: string;
type!: number; // ApplicationCommandType.ChatInput | ApplicationCommandOptionType.Subcommand type!: number; // ApplicationCommandType.ChatInput | ApplicationCommandOptionType.Subcommand
nsfw?: boolean; nsfw?: boolean;
description!: string; description!: string;
defaultMemberPermissions?: bigint; defaultMemberPermissions?: bigint;
integrationTypes?: ApplicationIntegrationType[]; integrationTypes?: ApplicationIntegrationType[];
contexts?: InteractionContextType[]; contexts?: InteractionContextType[];
botPermissions?: bigint; botPermissions?: bigint;
name_localizations?: Partial<Record<LocaleString, string>>; name_localizations?: Partial<Record<LocaleString, string>>;
description_localizations?: Partial<Record<LocaleString, string>>; description_localizations?: Partial<Record<LocaleString, string>>;
options?: CommandOption[] | SubCommand[]; options?: CommandOption[] | SubCommand[];
ignore?: IgnoreCommand; ignore?: IgnoreCommand;
aliases?: string[]; aliases?: string[];
/** @internal */ /** @internal */
async __runOptions( async __runOptions(
ctx: CommandContext<{}, never>, ctx: CommandContext<{}, never>,
resolver: OptionResolver, resolver: OptionResolver,
): Promise<[boolean, OnOptionsReturnObject]> { ): Promise<[boolean, OnOptionsReturnObject]> {
if (!this?.options?.length) { if (!this?.options?.length) {
return [false, {}]; return [false, {}];
} }
const data: OnOptionsReturnObject = {}; const data: OnOptionsReturnObject = {};
let errored = false; let errored = false;
for (const i of this.options ?? []) { for (const i of this.options ?? []) {
try { try {
const option = this.options!.find(x => x.name === i.name) as __CommandOption; const option = this.options!.find(x => x.name === i.name) as __CommandOption;
const value = const value =
resolver.getHoisted(i.name)?.value !== undefined resolver.getHoisted(i.name)?.value !== undefined
? await new Promise( ? await new Promise(
(res, rej) => (res, rej) =>
option.value?.({ context: ctx, value: resolver.getValue(i.name) } as never, res, rej) || option.value?.({ context: ctx, value: resolver.getValue(i.name) } as never, res, rej) ||
res(resolver.getValue(i.name)), res(resolver.getValue(i.name)),
) )
: undefined; : undefined;
if (value === undefined) { if (value === undefined) {
if (option.required) { if (option.required) {
errored = true; errored = true;
data[i.name] = { data[i.name] = {
failed: true, failed: true,
value: `${i.name} is required but returned no value`, value: `${i.name} is required but returned no value`,
}; };
continue; continue;
} }
} }
// @ts-expect-error // @ts-expect-error
ctx.options[i.name] = value; ctx.options[i.name] = value;
data[i.name] = { data[i.name] = {
failed: false, failed: false,
value, value,
}; };
} catch (e) { } catch (e) {
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 : `${e}`,
}; };
} }
} }
return [errored, data]; return [errored, data];
} }
/** @internal */ /** @internal */
static __runMiddlewares( static __runMiddlewares(
context: CommandContext<{}, never> | ComponentContext | MenuCommandContext<any> | ModalContext, context: CommandContext<{}, never> | ComponentContext | MenuCommandContext<any> | ModalContext,
middlewares: (keyof RegisteredMiddlewares)[], middlewares: (keyof RegisteredMiddlewares)[],
global: boolean, global: boolean,
): Promise<{ error?: string; pass?: boolean }> { ): Promise<{ error?: string; pass?: boolean }> {
if (!middlewares.length) { if (!middlewares.length) {
return Promise.resolve({}); return Promise.resolve({});
} }
let index = 0; let index = 0;
return new Promise(res => { return new Promise(res => {
let running = true; let running = true;
const pass: PassFunction = () => { const pass: PassFunction = () => {
if (!running) { if (!running) {
return; return;
} }
running = false; running = false;
return res({ pass: true }); return res({ pass: true });
}; };
function next(obj: any) { function next(obj: any) {
if (!running) { if (!running) {
return; return;
} }
// biome-ignore lint/style/noArguments: yes // biome-ignore lint/style/noArguments: yes
// biome-ignore lint/correctness/noUndeclaredVariables: xd // biome-ignore lint/correctness/noUndeclaredVariables: xd
if (arguments.length) { if (arguments.length) {
// @ts-expect-error // @ts-expect-error
context[global ? 'globalMetadata' : 'metadata'][middlewares[index]] = obj; context[global ? 'globalMetadata' : 'metadata'][middlewares[index]] = obj;
} }
if (++index >= middlewares.length) { if (++index >= middlewares.length) {
running = false; running = false;
return res({}); return res({});
} }
context.client.middlewares![middlewares[index]]({ context, next, stop, pass }); context.client.middlewares![middlewares[index]]({ context, next, stop, pass });
} }
const stop: StopFunction = err => { const stop: StopFunction = err => {
if (!running) { if (!running) {
return; return;
} }
running = false; running = false;
return res({ error: err }); return res({ error: err });
}; };
context.client.middlewares![middlewares[0]]({ context, next, stop, pass }); context.client.middlewares![middlewares[0]]({ context, next, stop, pass });
}); });
} }
/** @internal */ /** @internal */
__runMiddlewares(context: CommandContext<{}, never>) { __runMiddlewares(context: CommandContext<{}, never>) {
return BaseCommand.__runMiddlewares(context, this.middlewares as (keyof RegisteredMiddlewares)[], false); return BaseCommand.__runMiddlewares(context, this.middlewares as (keyof RegisteredMiddlewares)[], false);
} }
/** @internal */ /** @internal */
__runGlobalMiddlewares(context: CommandContext<{}, never>) { __runGlobalMiddlewares(context: CommandContext<{}, never>) {
return BaseCommand.__runMiddlewares( return BaseCommand.__runMiddlewares(
context, context,
(context.client.options?.globalMiddlewares ?? []) as (keyof RegisteredMiddlewares)[], (context.client.options?.globalMiddlewares ?? []) as (keyof RegisteredMiddlewares)[],
true, true,
); );
} }
toJSON() { toJSON() {
const data = { const data = {
name: this.name, name: this.name,
type: this.type, type: this.type,
nsfw: !!this.nsfw, nsfw: !!this.nsfw,
description: this.description, description: this.description,
name_localizations: this.name_localizations, name_localizations: this.name_localizations,
description_localizations: this.description_localizations, description_localizations: this.description_localizations,
guild_id: this.guildId, guild_id: this.guildId,
default_member_permissions: this.defaultMemberPermissions ? this.defaultMemberPermissions.toString() : undefined, default_member_permissions: this.defaultMemberPermissions ? this.defaultMemberPermissions.toString() : undefined,
contexts: this.contexts, contexts: this.contexts,
integration_types: this.integrationTypes, integration_types: this.integrationTypes,
} as { } as {
name: BaseCommand['name']; name: BaseCommand['name'];
type: BaseCommand['type']; type: BaseCommand['type'];
nsfw: BaseCommand['nsfw']; nsfw: BaseCommand['nsfw'];
description: BaseCommand['description']; description: BaseCommand['description'];
name_localizations: BaseCommand['name_localizations']; name_localizations: BaseCommand['name_localizations'];
description_localizations: BaseCommand['description_localizations']; description_localizations: BaseCommand['description_localizations'];
guild_id: BaseCommand['guildId']; guild_id: BaseCommand['guildId'];
default_member_permissions: string; default_member_permissions: string;
contexts: BaseCommand['contexts']; contexts: BaseCommand['contexts'];
integration_types: BaseCommand['integrationTypes']; integration_types: BaseCommand['integrationTypes'];
}; };
return data; return data;
} }
async reload() { async reload() {
delete require.cache[this.__filePath!]; delete require.cache[this.__filePath!];
for (const i of this.options ?? []) { for (const i of this.options ?? []) {
if (i instanceof SubCommand && i.__filePath) { if (i instanceof SubCommand && i.__filePath) {
await i.reload(); await i.reload();
} }
} }
const __tempCommand = await magicImport(this.__filePath!).then(x => x.default ?? x); const __tempCommand = await magicImport(this.__filePath!).then(x => x.default ?? x);
Object.setPrototypeOf(this, __tempCommand.prototype); Object.setPrototypeOf(this, __tempCommand.prototype);
} }
run?(context: CommandContext): any; run?(context: CommandContext): any;
onAfterRun?(context: CommandContext, error: unknown | undefined): any; onAfterRun?(context: CommandContext, error: unknown | undefined): any;
onRunError?(context: CommandContext, error: unknown): any; onRunError?(context: CommandContext, error: unknown): any;
onOptionsError?(context: CommandContext, metadata: OnOptionsReturnObject): any; onOptionsError?(context: CommandContext, metadata: OnOptionsReturnObject): any;
onMiddlewaresError?(context: CommandContext, error: string): any; onMiddlewaresError?(context: CommandContext, error: string): any;
onBotPermissionsFail?(context: CommandContext, permissions: PermissionStrings): any; onBotPermissionsFail?(context: CommandContext, permissions: PermissionStrings): any;
onPermissionsFail?(context: CommandContext, permissions: PermissionStrings): any; onPermissionsFail?(context: CommandContext, permissions: PermissionStrings): any;
onInternalError?(client: UsingClient, command: Command | SubCommand, error?: unknown): any; onInternalError?(client: UsingClient, command: Command | SubCommand, error?: unknown): any;
} }
export class Command extends BaseCommand { export class Command extends BaseCommand {
type = ApplicationCommandType.ChatInput; type = ApplicationCommandType.ChatInput;
groups?: Parameters<typeof Groups>[0]; groups?: Parameters<typeof Groups>[0];
groupsAliases?: Record<string, string>; groupsAliases?: Record<string, string>;
__tGroups?: Record< __tGroups?: Record<
string /* name for group*/, string /* name for group*/,
{ {
name: string | undefined; name: string | undefined;
description: string | undefined; description: string | undefined;
defaultDescription: string; defaultDescription: string;
} }
>; >;
toJSON() { toJSON() {
const options: APIApplicationCommandOption[] = []; const options: APIApplicationCommandOption[] = [];
for (const i of this.options ?? []) { for (const i of this.options ?? []) {
if (!(i instanceof SubCommand)) { if (!(i instanceof SubCommand)) {
options.push({ ...i, autocomplete: 'autocomplete' in i } as APIApplicationCommandBasicOption); options.push({ ...i, autocomplete: 'autocomplete' in i } as APIApplicationCommandBasicOption);
continue; continue;
} }
if (i.group) { if (i.group) {
if (!options.find(x => x.name === i.group)) { if (!options.find(x => x.name === i.group)) {
options.push({ options.push({
type: ApplicationCommandOptionType.SubcommandGroup, type: ApplicationCommandOptionType.SubcommandGroup,
name: i.group, name: i.group,
description: this.groups![i.group].defaultDescription, description: this.groups![i.group].defaultDescription,
description_localizations: Object.fromEntries(this.groups?.[i.group].description ?? []), description_localizations: Object.fromEntries(this.groups?.[i.group].description ?? []),
name_localizations: Object.fromEntries(this.groups?.[i.group].name ?? []), name_localizations: Object.fromEntries(this.groups?.[i.group].name ?? []),
options: [], options: [],
}); });
} }
const group = options.find(x => x.name === i.group) as APIApplicationCommandSubcommandGroupOption; const group = options.find(x => x.name === i.group) as APIApplicationCommandSubcommandGroupOption;
group.options?.push(i.toJSON()); group.options?.push(i.toJSON());
continue; continue;
} }
options.push(i.toJSON()); options.push(i.toJSON());
} }
return { return {
...super.toJSON(), ...super.toJSON(),
options, options,
}; };
} }
} }
export abstract class SubCommand extends BaseCommand { export abstract class SubCommand extends BaseCommand {
type = ApplicationCommandOptionType.Subcommand; type = ApplicationCommandOptionType.Subcommand;
group?: string; group?: string;
declare options?: CommandOption[]; declare options?: CommandOption[];
toJSON() { toJSON() {
return { return {
...super.toJSON(), ...super.toJSON(),
options: (this.options ?? []).map( options:
x => ({ ...x, autocomplete: 'autocomplete' in x }) as APIApplicationCommandBasicOption, this.options?.map(x => ({ ...x, autocomplete: 'autocomplete' in x }) as APIApplicationCommandBasicOption) ?? [],
), };
}; }
}
abstract run(context: CommandContext<any>): any;
abstract run(context: CommandContext<any>): any; }
}

View File

@ -73,8 +73,10 @@ export class EventHandler extends BaseHandler {
break; break;
} }
await this.runEvent(args[0].t, args[1], args[0].d, args[2]); await Promise.all([
await this.client.collectors.run(args[0].t, args[0].d); this.runEvent(args[0].t, args[1], args[0].d, args[2]),
this.client.collectors.run(args[0].t, args[0].d),
]);
} }
async runEvent(name: GatewayEvents, client: Client | WorkerClient, packet: any, shardId: number) { async runEvent(name: GatewayEvents, client: Client | WorkerClient, packet: any, shardId: number) {

View File

@ -1,271 +1,274 @@
import type { import type {
APIChannelMention, APIChannelMention,
APIEmbed, APIEmbed,
APIGuildMember, APIGuildMember,
APIMessage, APIMessage,
APIUser, APIUser,
GatewayMessageCreateDispatchData, GatewayMessageCreateDispatchData,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import type { ListenerOptions } from '../builders'; import type { ListenerOptions } from '../builders';
import type { UsingClient } from '../commands'; import type { UsingClient } from '../commands';
import { toCamelCase, type ObjectToLower } from '../common'; import { toCamelCase, type ObjectToLower } from '../common';
import type { EmojiResolvable } from '../common/types/resolvables'; import type { EmojiResolvable } from '../common/types/resolvables';
import type { MessageCreateBodyRequest, MessageUpdateBodyRequest } from '../common/types/write'; import type { MessageCreateBodyRequest, MessageUpdateBodyRequest } from '../common/types/write';
import type { ActionRowMessageComponents } from '../components'; import type { ActionRowMessageComponents } from '../components';
import { MessageActionRowComponent } from '../components/ActionRow'; import { MessageActionRowComponent } from '../components/ActionRow';
import { GuildMember } from './GuildMember'; import { GuildMember } from './GuildMember';
import { User } from './User'; import { User } from './User';
import type { MessageWebhookMethodEditParams, MessageWebhookMethodWriteParams } from './Webhook'; import type { MessageWebhookMethodEditParams, MessageWebhookMethodWriteParams } from './Webhook';
import { DiscordBase } from './extra/DiscordBase'; import { DiscordBase } from './extra/DiscordBase';
import { messageLink } from './extra/functions'; import { messageLink } from './extra/functions';
import { Embed, Poll } from '..'; import { Embed, Poll } from '..';
export type MessageData = APIMessage | GatewayMessageCreateDispatchData; export type MessageData = APIMessage | GatewayMessageCreateDispatchData;
export interface BaseMessage export interface BaseMessage
extends DiscordBase, extends DiscordBase,
ObjectToLower<Omit<MessageData, 'timestamp' | 'author' | 'mentions' | 'components' | 'poll' | 'embeds'>> {} ObjectToLower<Omit<MessageData, 'timestamp' | 'author' | 'mentions' | 'components' | 'poll' | 'embeds'>> {}
export class BaseMessage extends DiscordBase { export class BaseMessage extends DiscordBase {
guildId: string | undefined; guildId: string | undefined;
timestamp?: number; timestamp?: number;
author!: User; author!: User;
member?: GuildMember; member?: GuildMember;
components: MessageActionRowComponent<ActionRowMessageComponents>[]; components: MessageActionRowComponent<ActionRowMessageComponents>[];
poll?: Poll; poll?: Poll;
mentions: { mentions: {
roles: string[]; roles: string[];
channels: APIChannelMention[]; channels: APIChannelMention[];
users: (GuildMember | User)[]; users: (GuildMember | User)[];
}; };
embeds: InMessageEmbed[]; embeds: InMessageEmbed[];
constructor(client: UsingClient, data: MessageData) { constructor(client: UsingClient, data: MessageData) {
super(client, data); super(client, data);
this.mentions = { this.mentions = {
roles: data.mention_roles ?? [], roles: data.mention_roles ?? [],
channels: data.mention_channels ?? [], channels: data.mention_channels ?? [],
users: [], users: [],
}; };
this.components = (data.components ?? []).map(x => new MessageActionRowComponent(x)); this.components = data.components?.map(x => new MessageActionRowComponent(x)) ?? [];
this.embeds = data.embeds.map(embed => new InMessageEmbed(embed)); this.embeds = data.embeds.map(embed => new InMessageEmbed(embed));
this.patch(data); this.patch(data);
} }
get user() { get user() {
return this.author; return this.author;
} }
createComponentCollector(options?: ListenerOptions) { createComponentCollector(options?: ListenerOptions) {
return this.client.components!.createComponentCollector(this.id, options); return this.client.components!.createComponentCollector(this.id, options);
} }
get url() { get url() {
return messageLink(this.channelId, this.id, this.guildId); return messageLink(this.channelId, this.id, this.guildId);
} }
guild(force = false) { guild(force = false) {
if (!this.guildId) return; if (!this.guildId) return;
return this.client.guilds.fetch(this.guildId, force); return this.client.guilds.fetch(this.guildId, force);
} }
async channel(force = false) { async channel(force = false) {
return this.client.channels.fetch(this.channelId, force); return this.client.channels.fetch(this.channelId, force);
} }
react(emoji: EmojiResolvable) { react(emoji: EmojiResolvable) {
return this.client.reactions.add(this.id, this.channelId, emoji); return this.client.reactions.add(this.id, this.channelId, emoji);
} }
private patch(data: MessageData) { private patch(data: MessageData) {
if ('guild_id' in data) { if ('guild_id' in data) {
this.guildId = data.guild_id; this.guildId = data.guild_id;
} }
if (data.type !== undefined) { if (data.type !== undefined) {
this.type = data.type; this.type = data.type;
} }
if ('timestamp' in data && data.timestamp) { if ('timestamp' in data && data.timestamp) {
this.timestamp = Date.parse(data.timestamp); this.timestamp = Date.parse(data.timestamp);
} }
if ('application_id' in data) { if ('application_id' in data) {
this.applicationId = data.application_id; this.applicationId = data.application_id;
} }
if ('author' in data && data.author) { if ('author' in data && data.author) {
this.author = new User(this.client, data.author); this.author = new User(this.client, data.author);
} }
if ('member' in data && data.member) { if ('member' in data && data.member) {
this.member = new GuildMember(this.client, data.member, this.author, this.guildId!); this.member = new GuildMember(this.client, data.member, this.author, this.guildId!);
} }
if (data.mentions?.length) { if (data.mentions?.length) {
this.mentions.users = this.guildId this.mentions.users = this.guildId
? data.mentions.map( ? data.mentions.map(
m => m =>
new GuildMember( new GuildMember(
this.client, this.client,
{ {
...(m as APIUser & { member?: Omit<APIGuildMember, 'user'> }).member!, ...(m as APIUser & { member?: Omit<APIGuildMember, 'user'> }).member!,
user: m, user: m,
}, },
m, m,
this.guildId!, this.guildId!,
), ),
) )
: data.mentions.map(u => new User(this.client, u)); : data.mentions.map(u => new User(this.client, u));
} }
if (data.poll) { if (data.poll) {
this.poll = new Poll(this.client, data.poll, this.channelId, this.id); this.poll = new Poll(this.client, data.poll, this.channelId, this.id);
} }
} }
} }
export interface Message export interface Message
extends BaseMessage, extends BaseMessage,
ObjectToLower<Omit<MessageData, 'timestamp' | 'author' | 'mentions' | 'components' | 'poll' | 'embeds'>> {} ObjectToLower<Omit<MessageData, 'timestamp' | 'author' | 'mentions' | 'components' | 'poll' | 'embeds'>> {}
export class Message extends BaseMessage { export class Message extends BaseMessage {
constructor(client: UsingClient, data: MessageData) { constructor(client: UsingClient, data: MessageData) {
super(client, data); super(client, data);
} }
fetch() { fetch() {
return this.client.messages.fetch(this.id, this.channelId); return this.client.messages.fetch(this.id, this.channelId);
} }
reply(body: Omit<MessageCreateBodyRequest, 'message_reference'>, fail = true) { reply(body: Omit<MessageCreateBodyRequest, 'message_reference'>, fail = true) {
return this.write({ return this.write({
...body, ...body,
message_reference: { message_reference: {
message_id: this.id, message_id: this.id,
channel_id: this.channelId, channel_id: this.channelId,
guild_id: this.guildId, guild_id: this.guildId,
fail_if_not_exists: fail, fail_if_not_exists: fail,
}, },
}); });
} }
edit(body: MessageUpdateBodyRequest) { edit(body: MessageUpdateBodyRequest) {
return this.client.messages.edit(this.id, this.channelId, body); return this.client.messages.edit(this.id, this.channelId, body);
} }
write(body: MessageCreateBodyRequest) { write(body: MessageCreateBodyRequest) {
return this.client.messages.write(this.channelId, body); return this.client.messages.write(this.channelId, body);
} }
delete(reason?: string) { delete(reason?: string) {
return this.client.messages.delete(this.id, this.channelId, reason); return this.client.messages.delete(this.id, this.channelId, reason);
} }
crosspost(reason?: string) { crosspost(reason?: string) {
return this.client.messages.crosspost(this.id, this.channelId, reason); return this.client.messages.crosspost(this.id, this.channelId, reason);
} }
} }
export type EditMessageWebhook = Omit<MessageWebhookMethodEditParams, 'messageId'>['body'] & export type EditMessageWebhook = Omit<MessageWebhookMethodEditParams, 'messageId'>['body'] &
Pick<MessageWebhookMethodEditParams, 'query'>; Pick<MessageWebhookMethodEditParams, 'query'>;
export type WriteMessageWebhook = MessageWebhookMethodWriteParams['body'] & export type WriteMessageWebhook = MessageWebhookMethodWriteParams['body'] &
Pick<MessageWebhookMethodWriteParams, 'query'>; Pick<MessageWebhookMethodWriteParams, 'query'>;
export class WebhookMessage extends BaseMessage { export class WebhookMessage extends BaseMessage {
constructor( constructor(
client: UsingClient, client: UsingClient,
data: MessageData, data: MessageData,
readonly webhookId: string, readonly webhookId: string,
readonly webhookToken: string, readonly webhookToken: string,
) { ) {
super(client, data); super(client, data);
} }
fetch() { fetch() {
return this.api.webhooks(this.webhookId)(this.webhookToken).get({ query: this.thread?.id }); return this.api.webhooks(this.webhookId)(this.webhookToken).get({ query: this.thread?.id });
} }
edit(body: EditMessageWebhook) { edit(body: EditMessageWebhook) {
const { query, ...rest } = body; const { query, ...rest } = body;
return this.client.webhooks.editMessage(this.webhookId, this.webhookToken, { return this.client.webhooks.editMessage(this.webhookId, this.webhookToken, {
body: rest, body: rest,
query, query,
messageId: this.id, messageId: this.id,
}); });
} }
write(body: WriteMessageWebhook) { write(body: WriteMessageWebhook) {
const { query, ...rest } = body; const { query, ...rest } = body;
return this.client.webhooks.writeMessage(this.webhookId, this.webhookToken, { return this.client.webhooks.writeMessage(this.webhookId, this.webhookToken, {
body: rest, body: rest,
query, query,
}); });
} }
delete(reason?: string) { delete(reason?: string) {
return this.client.webhooks.deleteMessage(this.webhookId, this.webhookToken, this.id, reason); return this.client.webhooks.deleteMessage(this.webhookId, this.webhookToken, this.id, reason);
} }
} }
export class InMessageEmbed { export class InMessageEmbed {
constructor(public data: APIEmbed) {} constructor(public data: APIEmbed) {}
get title() { get title() {
return this.data.title; return this.data.title;
} }
get type() { /**
return this.data.type; * @deprecated
} */
get type() {
get description() { return this.data.type;
return this.data.description; }
}
get description() {
get url() { return this.data.description;
return this.data.url; }
}
get url() {
get timestamp() { return this.data.url;
return this.data.timestamp; }
}
get timestamp() {
get color() { return this.data.timestamp;
return this.data.color; }
}
get color() {
get footer() { return this.data.color;
return toCamelCase(this.data.footer ?? {}); }
}
get footer() {
get image() { return this.data.footer ? toCamelCase(this.data.footer) : undefined;
return toCamelCase(this.data.image ?? {}); }
}
get image() {
get thumbnail() { return this.data.image ? toCamelCase(this.data.image) : undefined;
return toCamelCase(this.data.thumbnail ?? {}); }
}
get thumbnail() {
get video() { return this.data.thumbnail ? toCamelCase(this.data.thumbnail) : undefined;
return toCamelCase(this.data.video ?? {}); }
}
get video() {
get provider() { return this.data.video ? toCamelCase(this.data.video) : undefined;
return this.data.provider; }
}
get provider() {
get author() { return this.data.provider;
return toCamelCase(this.data.author ?? {}); }
}
get author() {
get fields() { return this.data.author ? toCamelCase(this.data.author) : undefined;
return this.data.fields; }
}
get fields() {
toBuilder() { return this.data.fields;
return new Embed(this.data); }
}
toBuilder() {
toJSON() { return new Embed(this.data);
return { ...this.data }; }
}
} toJSON() {
return { ...this.data };
}
}