all repos — kitten @ b644bafbebff752e61e8885a072dcf3c8fca1a2a

pkgs/core/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
import {
	type AutocompleteInteraction,
	type User,
	type Attachment,
	type Role,
	type GuildBasedChannel,
	type ApplicationCommandOptionChoiceData,
} 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;
type OptionType = keyof typeof OPTION_TYPES;

export interface Option<T, Req extends boolean> {
	type: OptionType;
	description?: string;
	required: Req;
	autocomplete?: (
		interaction: AutocompleteInteraction,
		value: T,
	) => Promise<ApplicationCommandOptionChoiceData[]> | ApplicationCommandOptionChoiceData[];
	__type?: T;
}

export interface CommandOption<T, Req extends boolean> extends Option<T, Req> {
	description: string;
}

export type InferOptions<O> = {
	[K in keyof O]: O[K] extends Option<infer Type, infer Required>
		? Required extends true
			? Type
			: Type | undefined
		: never;
};

export type AutocompleteFn<T> = (
	interaction: AutocompleteInteraction,
	value: T,
) => Promise<ApplicationCommandOptionChoiceData[]> | ApplicationCommandOptionChoiceData[];

const createOption = <T>(type: OptionType) => {
	return ((
		descriptionOrConfig?: string | { required?: boolean; autocomplete?: AutocompleteFn<T> },
		config?: { required?: boolean; autocomplete?: AutocompleteFn<T> },
	): Option<T, any> => {
		const hasDescription = typeof descriptionOrConfig === "string";
		const description = hasDescription ? descriptionOrConfig : undefined;
		const actualConfig = hasDescription ? config : descriptionOrConfig;

		return {
			type,
			description,
			required: actualConfig?.required ?? false,
			autocomplete: actualConfig?.autocomplete,
		};
	}) as {
		// w/ description for commands
		(
			description: string,
			config: { required: true; autocomplete?: AutocompleteFn<T> },
		): CommandOption<T, true>;
		(
			description: string,
			config?: { required?: false; autocomplete?: AutocompleteFn<T> },
		): CommandOption<T, false>;

		// w/out description for components
		(config: { required: true; autocomplete?: AutocompleteFn<T> }): Option<T, true>;
		(config?: { required?: false; autocomplete?: AutocompleteFn<T> }): Option<T, false>;
	};
};

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 | any>("mentionable"),
	attachment: createOption<Attachment>("attachment"),
};