2025-06-14 05:36:37 +00:00
|
|
|
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
|
|
|
|
|
};
|
|
|
|
|
|
2025-06-14 21:17:42 +00:00
|
|
|
// Helper type to get option by name
|
|
|
|
|
type GetOption<Options, Name> = Options extends readonly any[]
|
|
|
|
|
? Options[number] extends infer O
|
|
|
|
|
? O extends { name: Name }
|
|
|
|
|
? O
|
|
|
|
|
: never
|
|
|
|
|
: never
|
|
|
|
|
: never;
|
|
|
|
|
|
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 21:17:42 +00:00
|
|
|
: never;
|
2025-06-14 05:36:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Handler function type that accepts typed arguments
|
|
|
|
|
type HandlerFunction<Args = {}> = (args: Args) => Promise<void> | void;
|
|
|
|
|
|
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
|
|
|
|
|
: never;
|
|
|
|
|
|
|
|
|
|
// Check if all options are subcommands
|
|
|
|
|
type HasOnlySubcommands<Options extends readonly any[]> =
|
|
|
|
|
Options[number] extends { type: ApplicationCommandOptionTypes.SubCommand }
|
|
|
|
|
? true
|
|
|
|
|
: false;
|
|
|
|
|
|
|
|
|
|
// Extract subcommand names from options
|
|
|
|
|
type SubcommandNames<Options extends readonly any[]> =
|
|
|
|
|
Options[number] extends { name: infer N; type: ApplicationCommandOptionTypes.SubCommand }
|
|
|
|
|
? N extends string
|
|
|
|
|
? N
|
|
|
|
|
: never
|
|
|
|
|
: never;
|
|
|
|
|
|
|
|
|
|
// 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<{}>
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Get command by name from array
|
|
|
|
|
type GetCommand<Commands extends readonly any[], Name> = Commands[number] extends infer C
|
|
|
|
|
? C extends { name: Name }
|
|
|
|
|
? C
|
2025-06-14 05:36:37 +00:00
|
|
|
: never
|
|
|
|
|
: never;
|
|
|
|
|
|
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 05:36:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// The actual command handler type based on SLASH_COMMANDS
|
|
|
|
|
export type CommandHandlers = ExtractCommands<typeof SLASH_COMMANDS>;
|
|
|
|
|
|
|
|
|
|
// Helper function to parse option values from interaction data
|
|
|
|
|
function parseOptions(options?: DiscordInteractionDataOption[]): Record<string, any> {
|
|
|
|
|
if (!options) return {};
|
|
|
|
|
|
|
|
|
|
const args: Record<string, any> = {};
|
|
|
|
|
|
|
|
|
|
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<T extends readonly CreateApplicationCommand[]>(
|
|
|
|
|
{handler, notFoundHandler}:{
|
|
|
|
|
handler: ExtractCommands<T>
|
|
|
|
|
notFoundHandler: HandlerFunction<{}>
|
|
|
|
|
}) {
|
|
|
|
|
return async (data: InteractionData): Promise<void> => {
|
|
|
|
|
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);
|
2025-06-14 21:17:42 +00:00
|
|
|
await (subHandler as HandlerFunction<any>)(args);
|
2025-06-14 05:36:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|