import { ApplicationCommandOptionTypes, CreateApplicationCommand, DiscordInteractionDataOption} from "@discordeno/types"; import { SLASH_COMMANDS } from "./slash_commands"; import { InteractionData } from ".."; // 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); } } }