import { ButtonBuilder, ButtonStyle, ActionRowBuilder, } from "discord.js"; import { getCharacters, getCharacterByName } from "@systems/characters"; import { getClassEmoji } from "@systems/emojis"; import { format } from "@format"; import { Character } from "@types"; import fs from "fs"; import path from "path"; const CHARS_PATH = path.join(__dirname, "../../data/characters.json"); export interface CharSelectOptions { customIdPrefix: string; // e.g. "switch_after_reclaim:flash" excludeCharName?: string; // exclude this char from the list appendToCustomId?: string; // appended after charName e.g. ":yes" pageSize?: number; // default 4 page?: number; // default 0 } /** * Builds paginated character selection button rows for a given user. * Includes own characters + shared characters. * Returns up to 2 rows: one for char buttons, one for pagination if needed. */ export function buildCharSelectButtons( userKey: string, options: CharSelectOptions ): ActionRowBuilder[] { const { customIdPrefix, excludeCharName, appendToCustomId = "", pageSize = 4, page = 0, } = options; // Gather own + shared chars const ownChars = getCharacters(userKey); const sharedChars: Character[] = []; try { const chars = JSON.parse(fs.readFileSync(CHARS_PATH, "utf8")); for (const [ownerKey, data] of Object.entries(chars) as [string, any][]) { if (ownerKey === userKey) continue; for (const c of data.characters ?? []) { if (c.sharedWith?.includes(userKey)) sharedChars.push(c); } } } catch {} const allChars = [...ownChars, ...sharedChars] .filter((c) => c.name !== excludeCharName); const pageChars = allChars.slice(page * pageSize, (page + 1) * pageSize); const hasNext = allChars.length > (page + 1) * pageSize; const hasPrev = page > 0; const rows: ActionRowBuilder[] = []; // Char buttons if (pageChars.length > 0) { const btns = pageChars.map((c) => { const emojiStr = getClassEmoji(c.class); const emoji = format.emoji(emojiStr); const btn = new ButtonBuilder() .setCustomId(`${customIdPrefix}:${c.name}${appendToCustomId}`) .setStyle(ButtonStyle.Secondary) .setLabel(`${c.level} ${c.name}`); if (emoji) btn.setEmoji(emoji as any); return btn; }); rows.push(new ActionRowBuilder().addComponents(...btns)); } // Pagination row const navBtns: ButtonBuilder[] = []; if (hasPrev) { navBtns.push( new ButtonBuilder() .setCustomId(`${customIdPrefix}_page:${page - 1}`) .setLabel("← Prev") .setStyle(ButtonStyle.Primary) ); } if (hasNext) { navBtns.push( new ButtonBuilder() .setCustomId(`${customIdPrefix}_page:${page + 1}`) .setLabel("Next →") .setStyle(ButtonStyle.Primary) ); } if (navBtns.length > 0) { rows.push(new ActionRowBuilder().addComponents(...navBtns)); } return rows; }