import { type AutocompleteInteraction, type User, type Attachment, type Role, type GuildBasedChannel, type ApplicationCommandOptionChoiceData, type LocalizationMap, } from "discord.js"; export const OPTION_TYPES = { subcommand: 1, subcommandGroup: 2, string: 3, integer: 4, boolean: 5, user: 6, channel: 7, role: 8, mentionable: 9, number: 10, attachment: 11, } as const; export type OptionType = keyof typeof OPTION_TYPES; export interface OptionChoice { name: string; nameLocalizations?: LocalizationMap; value: T; } export interface OptionConfig { required?: boolean; autocomplete?: AutocompleteFn; choices?: OptionChoice[]; nameLocalizations?: LocalizationMap; descriptionLocalizations?: LocalizationMap; } export interface Option extends OptionConfig { type: OptionType; description?: string; required: Req; __type?: T; } export interface CommandOption extends Option { description: string; } /** * recursively infers strict typescript types from your option schema. * options marked as `required: true` will be inferred as `T`, while optional * parameters will be inferred as `T | undefined`. */ export type InferOptions = { [K in keyof O]: O[K] extends Option ? Required extends true ? Type : Type | undefined : never; }; /** * a helper function to provide dynamic autocomplete choices to the user. * kitten handles routing the autocomplete interaction and running this helper automatically. */ export type AutocompleteFn = ( interaction: AutocompleteInteraction, value: T, ) => Promise | ApplicationCommandOptionChoiceData[]; export interface OptionBuilder { (description: string, config: OptionConfig & { required: true }): CommandOption; (description: string, config?: OptionConfig & { required?: false }): CommandOption; (config: OptionConfig & { required: true }): Option; (config?: OptionConfig & { required?: false }): Option; required(description: string, config?: Omit, "required">): CommandOption; optional( description: string, config?: Omit, "required">, ): CommandOption; } const createOption = (type: OptionType): OptionBuilder => { const build = ( descOrConfig?: string | OptionConfig, configObj?: OptionConfig, overrideReq?: boolean, ): any => { const hasDesc = typeof descOrConfig === "string"; const description = hasDesc ? descOrConfig : undefined; const actualConfig = hasDesc ? configObj : (descOrConfig as OptionConfig); return { type, description, required: overrideReq ?? actualConfig?.required ?? false, autocomplete: actualConfig?.autocomplete, choices: actualConfig?.choices, nameLocalizations: actualConfig?.nameLocalizations, descriptionLocalizations: actualConfig?.descriptionLocalizations, }; }; const fn: any = (doc?: any, c?: any) => build(doc, c); fn.required = (d: string, c?: any) => build(d, c, true); fn.optional = (d: string, c?: any) => build(d, c, false); return fn as OptionBuilder; }; /** * definitions for command and component options. these generate type-safe parameters * directly inside of your interaction's `run` callback. * * @example * ```ts * options: { * user: option.user.required("the user to get information about"), * ephemeral: option.boolean.optional("whether the response should be ephemeral"), * } * ``` */ export const option = { string: createOption("string"), integer: createOption("integer"), number: createOption("number"), boolean: createOption("boolean"), user: createOption("user"), channel: createOption("channel"), role: createOption("role"), mentionable: createOption("mentionable"), attachment: createOption("attachment"), };