import type { ChatInputCommandInteraction, UserContextMenuCommandInteraction, MessageContextMenuCommandInteraction, BaseInteraction, ButtonInteraction, AnySelectMenuInteraction, ModalSubmitInteraction, } from "discord.js"; import type { CommandOption, Option, InferOptions } from "./options"; import { KittenComponent } from "./components"; export const INTEGRATION_TYPES = { GUILD_INSTALL: 0, USER_INSTALL: 1, } as const; export const INTERACTION_CONTEXTS = { GUILD: 0, BOT_DM: 1, PRIVATE_CHANNEL: 2, } as const; export const INTEGRATION_TYPE_PRESETS = { ALL: Object.values(INTEGRATION_TYPES), GUILD_ONLY: [INTEGRATION_TYPES.GUILD_INSTALL], USER_ONLY: [INTEGRATION_TYPES.USER_INSTALL], }; export const INTEGRATION_CONTEXT_PRESETS = { EVERYWHERE: Object.values(INTERACTION_CONTEXTS), GUILDS_ONLY: [INTERACTION_CONTEXTS.GUILD], DMS_ONLY: [INTERACTION_CONTEXTS.BOT_DM, INTERACTION_CONTEXTS.PRIVATE_CHANNEL], }; export type CommandResultType = Promise | Res; export type IntegrationType = (typeof INTEGRATION_TYPES)[keyof typeof INTEGRATION_TYPES]; export type InteractionContextType = (typeof INTERACTION_CONTEXTS)[keyof typeof INTERACTION_CONTEXTS]; export type Middleware = ( interaction: I, ctx: InCtx, ) => Promise | OutCtx | void; export type ErrorHandler = ( err: unknown, interaction: I, ctx: Ctx, ) => Promise | unknown; // Parameterized with Res. result is Res | undefined because execution may fail/halt. export type Afterware = ( interaction: I, ctx: Ctx, error: unknown | null, result: Res | undefined, ) => Promise | void; export class Subcommand { constructor( public name: string, public config: { description: string; options?: Record>; onError?: ErrorHandler; run: Function; }, ) {} } export class SubcommandGroup { public subcommands: Record = {}; constructor( public name: string, public description: string, ) {} subcommand>, R extends Res>( name: string, config: { description: string; options?: O; onError?: ( err: unknown, interaction: ChatInputCommandInteraction, ctx: Ctx, ) => Promise | unknown; run: ( interaction: ChatInputCommandInteraction, args: InferOptions, ctx: Ctx, ) => CommandResultType; }, ): this { this.subcommands[name] = new Subcommand(name, config); return this; } } export class SubcommandGroupBuilder { public subcommands: Record = {}; public groups: Record> = {}; constructor( public name: string, public description: string, public middlewares: Middleware[] = [], public errorHandlers: ErrorHandler[] = [], public afterwares: Afterware[] = [], public config?: { integrationTypes?: IntegrationType[]; contexts?: InteractionContextType[]; }, ) {} subcommand>, R extends Res>( name: string, config: { description: string; options?: O; onError?: ( err: unknown, interaction: ChatInputCommandInteraction, ctx: Ctx, ) => Promise | unknown; run: ( interaction: ChatInputCommandInteraction, args: InferOptions, ctx: Ctx, ) => CommandResultType; }, ): this { this.subcommands[name] = new Subcommand(name, config); return this; } group( name: string, config: { description: string }, setup: (group: SubcommandGroup) => void, ): this { const group = new SubcommandGroup(name, config.description); setup(group); this.groups[name] = group; return this; } } export class Command { constructor( public name: string, public config: { description: string; options?: Record>; integrationTypes?: IntegrationType[]; contexts?: InteractionContextType[]; onError?: ErrorHandler; run: ( interaction: ChatInputCommandInteraction, args: any, ctx: Ctx, ) => CommandResultType; }, public middlewares: Middleware[] = [], public errorHandlers: ErrorHandler[] = [], public afterwares: Afterware[] = [], ) {} } export class ContextMenuCommand< Ctx = any, I extends UserContextMenuCommandInteraction | MessageContextMenuCommandInteraction = any, Res = unknown, > { constructor( public name: string, public type: "user" | "message", public run: (interaction: I, ctx: Ctx) => CommandResultType, public middlewares: Middleware[] = [], public errorHandlers: ErrorHandler[] = [], public afterwares: Afterware[] = [], public config?: { integrationTypes?: IntegrationType[]; contexts?: InteractionContextType[]; onError?: ErrorHandler; }, ) {} } export class CommandBuilder { constructor( private middlewares: Middleware[] = [], private errorHandlers: ErrorHandler[] = [], private afterwares: Afterware[] = [], ) {} use>( mw: (interaction: BaseInteraction, ctx: Ctx) => Promise | NewCtx | void, ): CommandBuilder { return new CommandBuilder( [...this.middlewares, mw], this.errorHandlers, this.afterwares, ); } onError(handler: ErrorHandler): CommandBuilder { return new CommandBuilder( this.middlewares, [...this.errorHandlers, handler], this.afterwares, ); } // Refines the Res generic type parameter of the CommandBuilder after(handler: Afterware): CommandBuilder { return new CommandBuilder(this.middlewares, this.errorHandlers, [ ...this.afterwares, handler as any, ]); } command>, R extends Res>( name: string, config: { description: string; options?: O; integrationTypes?: IntegrationType[]; contexts?: InteractionContextType[]; onError?: ErrorHandler; run: ( interaction: ChatInputCommandInteraction, args: InferOptions, ctx: Ctx, ) => CommandResultType; }, ): Command; command( name: string, config: { description: string; integrationTypes?: IntegrationType[]; contexts?: InteractionContextType[]; }, ): SubcommandGroupBuilder; command(name: string, config: any): any { if (config.run) { return new Command( name, config, this.middlewares, this.errorHandlers, this.afterwares, ); } return new SubcommandGroupBuilder( name, config.description, this.middlewares, this.errorHandlers, this.afterwares, { integrationTypes: config.integrationTypes, contexts: config.contexts, }, ); } userContextMenu( name: string, runOrConfig: | ((interaction: UserContextMenuCommandInteraction, ctx: Ctx) => CommandResultType) | { integrationTypes?: IntegrationType[]; contexts?: InteractionContextType[]; onError?: ErrorHandler; run: (interaction: UserContextMenuCommandInteraction, ctx: Ctx) => CommandResultType; }, ): ContextMenuCommand { if (typeof runOrConfig === "function") { return new ContextMenuCommand( name, "user", runOrConfig, this.middlewares, this.errorHandlers, this.afterwares, ); } return new ContextMenuCommand( name, "user", runOrConfig.run, this.middlewares, this.errorHandlers, this.afterwares, { integrationTypes: runOrConfig.integrationTypes, contexts: runOrConfig.contexts, onError: runOrConfig.onError, }, ); } messageContextMenu( name: string, runOrConfig: | ((interaction: MessageContextMenuCommandInteraction, ctx: Ctx) => CommandResultType) | { integrationTypes?: IntegrationType[]; contexts?: InteractionContextType[]; onError?: ErrorHandler; run: ( interaction: MessageContextMenuCommandInteraction, ctx: Ctx, ) => CommandResultType; }, ): ContextMenuCommand { if (typeof runOrConfig === "function") { return new ContextMenuCommand( name, "message", runOrConfig, this.middlewares, this.errorHandlers, this.afterwares, ); } return new ContextMenuCommand( name, "message", runOrConfig.run, this.middlewares, this.errorHandlers, this.afterwares, { integrationTypes: runOrConfig.integrationTypes, contexts: runOrConfig.contexts, onError: runOrConfig.onError, }, ); } button>, R extends Res>( name: string, config: { options?: O; onError?: ErrorHandler; run: ( interaction: ButtonInteraction, args: InferOptions, ctx: Ctx, ) => CommandResultType; }, ): KittenComponent { return new KittenComponent( name, config, this.middlewares, this.errorHandlers, this.afterwares, ); } selectMenu>, R extends Res>( name: string, config: { options?: O; onError?: ErrorHandler; run: ( interaction: AnySelectMenuInteraction, args: InferOptions, ctx: Ctx, ) => CommandResultType; }, ): KittenComponent { return new KittenComponent( name, config, this.middlewares, this.errorHandlers, this.afterwares, ); } modal>, R extends Res>( name: string, config: { options?: O; onError?: ErrorHandler; run: ( interaction: ModalSubmitInteraction, args: InferOptions, ctx: Ctx, ) => CommandResultType; }, ): KittenComponent { return new KittenComponent( name, config, this.middlewares, this.errorHandlers, this.afterwares, ); } }