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 }; // Extract the argument types from command options type ExtractArgs = { [K in Options[number] as K extends { name: infer N; type: infer T } ? T extends keyof OptionTypeMap ? N extends string ? N : never : never : never]: Options[number] extends { name: K; 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; // Helper types to extract command structure with proper argument types type ExtractSubcommands = T extends { options: infer O } ? O extends readonly any[] ? { [K in O[number] as K extends { name: infer N; type: ApplicationCommandOptionTypes.SubCommand | ApplicationCommandOptionTypes.SubCommandGroup } ? N extends string ? N : never : never]: O[number] extends { name: K; options?: infer SubO } ? SubO extends readonly any[] ? HandlerFunction> : HandlerFunction<{}> : HandlerFunction<{}> } : never : never; type ExtractCommands = { [K in T[number] as K['name']]: ExtractSubcommands extends never ? T[number] extends { name: K; options?: infer O } ? O extends readonly any[] ? HandlerFunction> : HandlerFunction<{}> : HandlerFunction<{}> : ExtractSubcommands }; // 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(args); } } } // Function to validate that all commands are implemented export function validateCommandImplementation(handlers: CommandHandlers): void { for (const command of SLASH_COMMANDS) { const commandName = command.name; if (!(commandName in handlers)) { throw new Error(`Command "${commandName}" is not implemented`); } // Check subcommands if they exist if (command.options) { const subcommands = command.options.filter( opt => opt.type === ApplicationCommandOptionTypes.SubCommand ); if (subcommands.length > 0) { const handlerObj = handlers[commandName as keyof CommandHandlers]; if (typeof handlerObj === 'function') { throw new Error(`Command "${commandName}" has subcommands but is implemented as a function`); } for (const subcommand of subcommands) { if (!(subcommand.name in handlerObj)) { throw new Error(`Subcommand "${commandName}.${subcommand.name}" is not implemented`); } } } } } } // Helper function to create command handlers with type safety export function createCommandHandlers(handlers: T): T { return handlers; } // Example usage showing the type-safe implementation export const exampleHandlers = createCommandHandlers({ guild: { leaderboard: async (args: {}) => { console.log("Guild leaderboard command"); }, info: async (args: {}) => { console.log("Guild info command"); }, online: async (args: {}) => { console.log("Guild online command"); }, }, admin: { set_wynn_guild: async (args: {}) => { console.log("Admin set_wynn_guild command"); }, }, });