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