pkgs/router/src/options.ts (view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
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<T> {
name: string;
nameLocalizations?: LocalizationMap;
value: T;
}
export interface OptionConfig<T> {
required?: boolean;
autocomplete?: AutocompleteFn<T>;
choices?: OptionChoice<T>[];
nameLocalizations?: LocalizationMap;
descriptionLocalizations?: LocalizationMap;
}
export interface Option<T, Req extends boolean> extends OptionConfig<T> {
type: OptionType;
description?: string;
required: Req;
__type?: T;
}
export interface CommandOption<T, Req extends boolean> extends Option<T, Req> {
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<O> = {
[K in keyof O]: O[K] extends Option<infer Type, infer Required>
? 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<T> = (
interaction: AutocompleteInteraction,
value: T,
) => Promise<ApplicationCommandOptionChoiceData[]> | ApplicationCommandOptionChoiceData[];
export interface OptionBuilder<T> {
(description: string, config: OptionConfig<T> & { required: true }): CommandOption<T, true>;
(description: string, config?: OptionConfig<T> & { required?: false }): CommandOption<T, false>;
(config: OptionConfig<T> & { required: true }): Option<T, true>;
(config?: OptionConfig<T> & { required?: false }): Option<T, false>;
required(description: string, config?: Omit<OptionConfig<T>, "required">): CommandOption<T, true>;
optional(
description: string,
config?: Omit<OptionConfig<T>, "required">,
): CommandOption<T, false>;
}
const createOption = <T>(type: OptionType): OptionBuilder<T> => {
const build = (
descOrConfig?: string | OptionConfig<T>,
configObj?: OptionConfig<T>,
overrideReq?: boolean,
): any => {
const hasDesc = typeof descOrConfig === "string";
const description = hasDesc ? descOrConfig : undefined;
const actualConfig = hasDesc ? configObj : (descOrConfig as OptionConfig<T>);
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<T>;
};
/**
* 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>("string"),
integer: createOption<number>("integer"),
number: createOption<number>("number"),
boolean: createOption<boolean>("boolean"),
user: createOption<User>("user"),
channel: createOption<GuildBasedChannel>("channel"),
role: createOption<Role>("role"),
mentionable: createOption<User | Role>("mentionable"),
attachment: createOption<Attachment>("attachment"),
};
|