charSelect.ts
· 3.0 KiB · TypeScript
原始文件
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<ButtonBuilder>[] {
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<ButtonBuilder>[] = [];
// 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<ButtonBuilder>().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<ButtonBuilder>().addComponents(...navBtns));
}
return rows;
}
| 1 | import { |
| 2 | ButtonBuilder, |
| 3 | ButtonStyle, |
| 4 | ActionRowBuilder, |
| 5 | } from "discord.js"; |
| 6 | import { getCharacters, getCharacterByName } from "@systems/characters"; |
| 7 | import { getClassEmoji } from "@systems/emojis"; |
| 8 | import { format } from "@format"; |
| 9 | import { Character } from "@types"; |
| 10 | import fs from "fs"; |
| 11 | import path from "path"; |
| 12 | |
| 13 | const CHARS_PATH = path.join(__dirname, "../../data/characters.json"); |
| 14 | |
| 15 | export interface CharSelectOptions { |
| 16 | customIdPrefix: string; // e.g. "switch_after_reclaim:flash" |
| 17 | excludeCharName?: string; // exclude this char from the list |
| 18 | appendToCustomId?: string; // appended after charName e.g. ":yes" |
| 19 | pageSize?: number; // default 4 |
| 20 | page?: number; // default 0 |
| 21 | } |
| 22 | |
| 23 | /** |
| 24 | * Builds paginated character selection button rows for a given user. |
| 25 | * Includes own characters + shared characters. |
| 26 | * Returns up to 2 rows: one for char buttons, one for pagination if needed. |
| 27 | */ |
| 28 | export function buildCharSelectButtons( |
| 29 | userKey: string, |
| 30 | options: CharSelectOptions |
| 31 | ): ActionRowBuilder<ButtonBuilder>[] { |
| 32 | const { |
| 33 | customIdPrefix, |
| 34 | excludeCharName, |
| 35 | appendToCustomId = "", |
| 36 | pageSize = 4, |
| 37 | page = 0, |
| 38 | } = options; |
| 39 | |
| 40 | // Gather own + shared chars |
| 41 | const ownChars = getCharacters(userKey); |
| 42 | |
| 43 | const sharedChars: Character[] = []; |
| 44 | try { |
| 45 | const chars = JSON.parse(fs.readFileSync(CHARS_PATH, "utf8")); |
| 46 | for (const [ownerKey, data] of Object.entries(chars) as [string, any][]) { |
| 47 | if (ownerKey === userKey) continue; |
| 48 | for (const c of data.characters ?? []) { |
| 49 | if (c.sharedWith?.includes(userKey)) sharedChars.push(c); |
| 50 | } |
| 51 | } |
| 52 | } catch {} |
| 53 | |
| 54 | const allChars = [...ownChars, ...sharedChars] |
| 55 | .filter((c) => c.name !== excludeCharName); |
| 56 | |
| 57 | const pageChars = allChars.slice(page * pageSize, (page + 1) * pageSize); |
| 58 | const hasNext = allChars.length > (page + 1) * pageSize; |
| 59 | const hasPrev = page > 0; |
| 60 | const rows: ActionRowBuilder<ButtonBuilder>[] = []; |
| 61 | |
| 62 | // Char buttons |
| 63 | if (pageChars.length > 0) { |
| 64 | const btns = pageChars.map((c) => { |
| 65 | const emojiStr = getClassEmoji(c.class); |
| 66 | const emoji = format.emoji(emojiStr); |
| 67 | const btn = new ButtonBuilder() |
| 68 | .setCustomId(`${customIdPrefix}:${c.name}${appendToCustomId}`) |
| 69 | .setStyle(ButtonStyle.Secondary) |
| 70 | .setLabel(`${c.level} ${c.name}`); |
| 71 | if (emoji) btn.setEmoji(emoji as any); |
| 72 | return btn; |
| 73 | }); |
| 74 | rows.push(new ActionRowBuilder<ButtonBuilder>().addComponents(...btns)); |
| 75 | } |
| 76 | |
| 77 | // Pagination row |
| 78 | const navBtns: ButtonBuilder[] = []; |
| 79 | if (hasPrev) { |
| 80 | navBtns.push( |
| 81 | new ButtonBuilder() |
| 82 | .setCustomId(`${customIdPrefix}_page:${page - 1}`) |
| 83 | .setLabel("← Prev") |
| 84 | .setStyle(ButtonStyle.Primary) |
| 85 | ); |
| 86 | } |
| 87 | if (hasNext) { |
| 88 | navBtns.push( |
| 89 | new ButtonBuilder() |
| 90 | .setCustomId(`${customIdPrefix}_page:${page + 1}`) |
| 91 | .setLabel("Next →") |
| 92 | .setStyle(ButtonStyle.Primary) |
| 93 | ); |
| 94 | } |
| 95 | if (navBtns.length > 0) { |
| 96 | rows.push(new ActionRowBuilder<ButtonBuilder>().addComponents(...navBtns)); |
| 97 | } |
| 98 | |
| 99 | return rows; |
| 100 | } |