最后活跃于 3 weeks ago

gistfile1.txt 原始文件
1import { Character, ClassKey, CharacterClass, Nation, WRankEntry } from "@src/types";
2import { Emoji } from "@systems/emojis";
3
4// ─── Individual formatters ────────────────────────────────────────────────────
5
6export interface CharDisplayOptions {
7 emoji?: boolean; // show class emoji (default: true)
8 level?: boolean; // show level (default: true)
9}
10
11function charButton(c: Character, options?: { shared?: boolean }): string {
12 const sharedMark = options?.shared ? " 🔗" : "";
13 return `${c.level} ${c.name}${sharedMark}`;
14}
15
16/**
17 * Format a character for display in embeds and messages.
18 * Output: <:class:> 79 «Flash»
19 */
20function char(
21 c: { class: ClassKey|CharacterClass; level: number; name: string },
22 options?: CharDisplayOptions
23): string {
24 const showEmoji = options?.emoji ?? true;
25 const showLevel = options?.level ?? true;
26 const classStr = showEmoji ? (Emoji.class(c.class) || c.class) : c.class;
27 const levelStr = showLevel ? `${c.level} ` : "";
28 return `${classStr} ${levelStr}${c.name}`.trim();
29}
30
31/**
32 * Format a nation name with its emoji.
33 * Output: <:capella:> Capella
34 */
35function nation(n: Nation): string {
36 const emoji = Emoji.nation(n);
37 return emoji ? `${emoji} ${n}` : n;
38}
39
40/**
41 * Format a score line.
42 * Output: <:score:> 3000 <:kd:> 32/18
43 */
44function score(pts: number, k?: number, d?: number): string {
45 const scoreEmoji = Emoji.get("score") || "📊";
46 const kdEmoji = Emoji.get("kd") || "⚔️";
47 const kdStr = k !== undefined && d !== undefined ? ` ${kdEmoji} ${k}/${d}` : "";
48 return `${scoreEmoji} ${pts}${kdStr}`;
49}
50
51/**
52 * Parse a Discord custom emoji string to an object for ButtonBuilder.setEmoji()
53 * Input: "<:fb:1511020923510194428>"
54 * Output: { name: "fb", id: "1511020923510194428" }
55 */
56function emoji(emojiStr: string): { name: string; id: string } | string | null {
57 if (!emojiStr) return null;
58 const match = emojiStr.match(/^<:(\w+):(\d+)>$/);
59 if (match) return { name: match[1], id: match[2] };
60 return emojiStr;
61}
62
63// ─── W.Rank formatters ────────────────────────────────────────────────────────
64
65export interface WRankDisplayOptions {
66 goal: number;
67 brackets?: boolean; // wrap delta in parentheses (default: true)
68}
69
70/**
71 * Format the rank indicator for a wrank entry.
72 * Output: <:wrank_1_gold:> or <:wrank_1:> or bold/plain number fallback
73 */
74function wrankRank(entry: WRankEntry, goal: number): string {
75 const isDone = entry.tgCount >= goal;
76 const rankKey = isDone ? `wrank_${entry.currentRank}_gold` : `wrank_${entry.currentRank}`;
77 return Emoji.get(rankKey) || (isDone ? `**${entry.currentRank}**` : `${entry.currentRank}`);
78}
79
80/**
81 * Format the delta indicator for a wrank entry.
82 * Output: <:wrank_up:><:wrank_up_2:> or ↑2, empty string if no change
83 */
84function wrankDelta(entry: WRankEntry, options?: { brackets?: boolean }): string {
85 const brackets = options?.brackets ?? true;
86 const prev = entry.previousRank;
87 const delta = prev !== undefined ? entry.currentRank - prev : 0;
88
89 if (delta === 0 && prev === undefined) return "";
90
91 let inner: string;
92 if (delta < 0) {
93 const abs = Math.abs(delta);
94 const numEmoji = Emoji.get(`wrank_up_${abs}`);
95 inner = (Emoji.get("wrank_up") || "↑") + (numEmoji || abs);
96 } else if (delta > 0) {
97 const numEmoji = Emoji.get(`wrank_down_${delta}`);
98 inner = (Emoji.get("wrank_down") || "↓") + (numEmoji || delta);
99 } else {
100 inner = (Emoji.get("wrank_no_dash") || "·") + (Emoji.get("wrank_neutral_0") || "0");
101 }
102
103 return brackets ? ` (${inner})` : ` ${inner}`;
104}
105
106/**
107 * Format a full wrank display string: rank + delta.
108 * Output: <:wrank_1_gold:> (<:wrank_up:><:wrank_up_2:>)
109 */
110function wrankFull(entry: WRankEntry, options: WRankDisplayOptions): string {
111 return wrankRank(entry, options.goal) + wrankDelta(entry, { brackets: options.brackets });
112}
113
114/**
115 * Placeholder for characters with no W.Rank when others in their nation have one.
116 * Output: — ( — 0 )
117 */
118 function wrankNoRank(): string {
119 const norank = Emoji.get("wrank_no_dash") || "—";
120 const dash = Emoji.get("wrank_no_rank_delta") || "—";
121 const square = Emoji.get("wrank_no_dash") || "■";
122 return `${norank} (${square}${dash})`;
123}
124
125// ─── Bringer formatters ────────────────────────────────────────────────────────
126
127function bringerDisplay(n: Nation): string {
128 const bringerMap: Record<Nation, string> = {
129 Capella: Emoji.get("luminous_bringer") || "🔆 Luminous Bringer",
130 Procyon: Emoji.get("storm_bringer") || "⚡ Storm Bringer",
131 };
132 return bringerMap[n];
133}
134
135// ─── Namespace export ─────────────────────────────────────────────────────────
136
137export const format = {
138 char,
139 charButton,
140 nation,
141 score,
142 emoji,
143 wrank: {
144 rank: wrankRank,
145 delta: wrankDelta,
146 full: wrankFull,
147 noRank: wrankNoRank,
148 },
149 bringer: bringerDisplay,
150};