all repos — kitten @ f80d8c5364cf8ed06845366587938faec60d32db

pkgs/core/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
import type { ButtonInteraction } from "discord.js";
import type { Option } from "./options";
import { CustomIdTooLong } from "./errors";

export class KittenComponent {
  type = "component" as const;

  constructor(
    public name: string,
    public config: {
      options?: Record<string, Option<any, any>>;
      run: (interaction: ButtonInteraction, args: any) => Promise<void> | void;
    },
  ) {}

  /**
   * @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: Record<string, any> = {}): 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): Record<string, any> {
    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;
  }
}