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