import { ApplicationCommandOptionTypes, type CreateApplicationCommand, type DiscordInteractionDataOption } from '@discordeno/types' import type { InteractionData } from '..' import type { SLASH_COMMANDS } from './slash_commands' // Map option types to their TypeScript types type OptionTypeMap = { [ApplicationCommandOptionTypes.String]: string [ApplicationCommandOptionTypes.Integer]: number [ApplicationCommandOptionTypes.Boolean]: boolean [ApplicationCommandOptionTypes.User]: string // user ID [ApplicationCommandOptionTypes.Channel]: string // channel ID [ApplicationCommandOptionTypes.Role]: string // role ID [ApplicationCommandOptionTypes.Number]: number [ApplicationCommandOptionTypes.Mentionable]: string // ID [ApplicationCommandOptionTypes.Attachment]: string // attachment ID } // Helper type to get option by name type GetOption = Options extends readonly any[] ? (Options[number] extends infer O ? (O extends { name: Name } ? O : never) : never) : never // Extract the argument types from command options export type ExtractArgs = { [K in Options[number]['name']]: GetOption extends { type: infer T; required?: infer R } ? T extends keyof OptionTypeMap ? R extends true ? OptionTypeMap[T] : OptionTypeMap[T] | undefined : never : never } // Handler function type that accepts typed arguments type HandlerFunction = (args: Args) => Promise | void // Get subcommand by name type GetSubcommand = Options extends readonly any[] ? Options[number] extends infer O ? O extends { name: Name; type: ApplicationCommandOptionTypes.SubCommand } ? O : never : never : never // Check if all options are subcommands type HasOnlySubcommands = Options[number] extends { type: ApplicationCommandOptionTypes.SubCommand } ? true : false // Extract subcommand names from options type SubcommandNames = Options[number] extends { name: infer N; type: ApplicationCommandOptionTypes.SubCommand } ? N extends string ? N : never : never // Type to extract subcommand handlers export type SubcommandHandlers = { [K in SubcommandNames]: GetSubcommand extends { options?: infer SubOpts } ? SubOpts extends readonly any[] ? HandlerFunction> : HandlerFunction<{}> : HandlerFunction<{}> } // Get command by name from array type GetCommand = Commands[number] extends infer C ? (C extends { name: Name } ? C : never) : never // Main type to extract command handlers from slash commands export type ExtractCommands = { [Name in T[number]['name']]: GetCommand extends { options?: infer Options } ? Options extends readonly any[] ? HasOnlySubcommands extends true ? SubcommandHandlers : HandlerFunction> : HandlerFunction<{}> : HandlerFunction<{}> } // The actual command handler type based on SLASH_COMMANDS export type CommandHandlers = ExtractCommands // Helper function to parse option values from interaction data function parseOptions(options?: DiscordInteractionDataOption[]): Record { if (!options) return {} const args: Record = {} for (const option of options) { if (option.type === ApplicationCommandOptionTypes.SubCommand || option.type === ApplicationCommandOptionTypes.SubCommandGroup) { continue } args[option.name] = option.value } return args } // Helper function to create command handlers with type safety export function createCommandHandler({ handler, notFoundHandler, }: { handler: ExtractCommands notFoundHandler: HandlerFunction<{}> }) { return async (data: InteractionData): Promise => { if (!data || !data.name) { await notFoundHandler({}) return } const commandName = data.name as keyof typeof handler const commandHandler = handler[commandName] if (!commandHandler) { await notFoundHandler({}) return } // Check if it's a direct command or has subcommands if (typeof commandHandler === 'function') { // Parse arguments from top-level options const args = parseOptions(data.options) await commandHandler(args) } else { // Handle subcommands const subcommand = data.options?.find( (opt) => opt.type === ApplicationCommandOptionTypes.SubCommand || opt.type === ApplicationCommandOptionTypes.SubCommandGroup ) if (!subcommand) { await notFoundHandler({}) return } const subHandler = commandHandler[subcommand.name as keyof typeof commandHandler] if (!subHandler || typeof subHandler !== 'function') { await notFoundHandler({}) return } // Parse arguments from subcommand options const args = parseOptions(subcommand.options) await (subHandler as HandlerFunction)(args) } } }