all repos — kitten @ 29ef210df4837ded4e2a766d96bc1317f64ee84f

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
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[];

export interface OptionBuilder<T> {
	(
		description: string,
		config: { required: true; autocomplete?: AutocompleteFn<T> },
	): CommandOption<T, true>;
	(
		description: string,
		config?: { required?: false; autocomplete?: AutocompleteFn<T> },
	): CommandOption<T, false>;
	(config: { required: true; autocomplete?: AutocompleteFn<T> }): Option<T, true>;
	(config?: { required?: false; autocomplete?: AutocompleteFn<T> }): Option<T, false>;

	required(
		description: string,
		config?: { autocomplete?: AutocompleteFn<T> },
	): CommandOption<T, true>;
	optional(
		description: string,
		config?: { autocomplete?: AutocompleteFn<T> },
	): CommandOption<T, false>;
}

const createOption = <T>(type: OptionType): OptionBuilder<T> => {
	const fn = (
		descriptionOrConfig?: string | { required?: boolean; autocomplete?: AutocompleteFn<T> },
		config?: { required?: boolean; autocomplete?: AutocompleteFn<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,
		};
	};

	fn.required = (description: string, config?: { autocomplete?: AutocompleteFn<T> }) => {
		return fn(description, { required: true, autocomplete: config?.autocomplete });
	};

	fn.optional = (description: string, config?: { autocomplete?: AutocompleteFn<T> }) => {
		return fn(description, { required: false, autocomplete: config?.autocomplete });
	};

	return fn as OptionBuilder<T>;
};

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"),
};