pkgs/router/src/components.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 |
import type {
ButtonInteraction,
AnySelectMenuInteraction,
ModalSubmitInteraction,
} from "discord.js";
import type { Option, InferOptions } from "./options";
import type { Middleware, ErrorHandler, Afterware } from "./commands";
import { CustomIdTooLong } from "./errors";
export type KittenComponentInteraction =
| ButtonInteraction
| AnySelectMenuInteraction
| ModalSubmitInteraction;
export class KittenComponent<
I extends KittenComponentInteraction = KittenComponentInteraction,
O extends Record<string, Option<any, any>> = Record<string, Option<any, any>>,
Ctx = any,
Res = unknown, // Added Res generic
> {
type = "component" as const;
constructor(
public name: string,
public config: {
options?: Record<string, Option<any, any>>;
onError?: ErrorHandler<I, Ctx>;
run: (interaction: I, args: InferOptions<O>, ctx: Ctx) => Promise<Res> | Res; // Typed run return
},
public middlewares: Middleware<any, any, any>[] = [],
public errorHandlers: ErrorHandler<any, any>[] = [],
public afterwares: Afterware<any, any, any>[] = [],
) {}
/**
* @throws {CustomIdTooLong} if the generated custom ID exceeds Discord's 100 character limit.
* @returns a custom ID string that encodes the component name and its options' values.
*/
id(
...[args]: {} extends InferOptions<O> ? [args?: InferOptions<O>] : [args: InferOptions<O>]
): string {
const keys = Object.keys(this.config.options || {});
const values = keys.map((key) => encodeURIComponent(String(args?.[key] ?? "")));
const customId = [this.name, ...values].join(":");
if (customId.length > 100) throw new CustomIdTooLong(this.name, customId.length);
return customId;
}
parseArgs(customId: string): InferOptions<O> {
const [, ...parts] = customId.split(":");
const keys = Object.keys(this.config.options || {});
const parsed: Record<string, any> = {};
keys.forEach((key, index) => {
const rawVal = parts[index];
if (rawVal === undefined) return;
const val = decodeURIComponent(rawVal);
const opt = this.config.options?.[key];
if (opt?.type === "boolean") parsed[key] = val === "true";
else if (opt?.type === "integer" || opt?.type === "number") parsed[key] = Number(val);
else parsed[key] = val === "" ? undefined : val;
});
return parsed as InferOptions<O>;
}
}
|