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 | void; 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; 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>>( 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 config?: { integrationTypes?: IntegrationType[]; contexts?: InteractionContextType[]; }, ) {} subcommand>>( 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[] = [], ) {} } export class ContextMenuCommand< Ctx = any, I extends UserContextMenuCommandInteraction | MessageContextMenuCommandInteraction = any, > { constructor( public name: string, public type: "user" | "message", public run: (interaction: I, ctx: Ctx) => Promise | unknown, public middlewares: Middleware[] = [], public errorHandlers: ErrorHandler[] = [], public config?: { integrationTypes?: IntegrationType[]; contexts?: InteractionContextType[]; onError?: ErrorHandler; }, ) {} } export class CommandBuilder { constructor( private middlewares: Middleware[] = [], private errorHandlers: ErrorHandler[] = [], ) {} use>( mw: (interaction: BaseInteraction, ctx: Ctx) => Promise | NewCtx | void, ): CommandBuilder { return new CommandBuilder([...this.middlewares, mw], this.errorHandlers); } onError(handler: ErrorHandler): CommandBuilder { return new CommandBuilder(this.middlewares, [...this.errorHandlers, handler]); } command>>( 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); } return new SubcommandGroupBuilder( name, config.description, this.middlewares, this.errorHandlers, { integrationTypes: config.integrationTypes, contexts: config.contexts, }, ); } userContextMenu( name: string, runOrConfig: | ((interaction: UserContextMenuCommandInteraction, ctx: Ctx) => Promise | unknown) | { 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, ); } return new ContextMenuCommand( name, "user", runOrConfig.run, this.middlewares, this.errorHandlers, { integrationTypes: runOrConfig.integrationTypes, contexts: runOrConfig.contexts, onError: runOrConfig.onError, }, ); } messageContextMenu( name: string, runOrConfig: | (( interaction: MessageContextMenuCommandInteraction, ctx: Ctx, ) => Promise | unknown) | { 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, ); } return new ContextMenuCommand( name, "message", runOrConfig.run, this.middlewares, this.errorHandlers, { integrationTypes: runOrConfig.integrationTypes, contexts: runOrConfig.contexts, onError: runOrConfig.onError, }, ); } button>>( 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, ); } selectMenu>>( 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, ); } modal>>( 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, ); } }