Última actividad 1 month ago

format.ts Sin formato
1import { ClassKey, Nation, WRankEntry } from "@src/types";
2import { getClassEmoji, getNationEmoji, getEmoji } 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
11/**
12 * Format a character for display in embeds and messages.
13 * Output: <:class:> 79 «Flash»
14 */
15function char(
16 c: { class: ClassKey; level: number; name: string },
17 options?: CharDisplayOptions
18): string {
19 const showEmoji = options?.emoji ?? true;
20 const showLevel = options?.level ?? true;
21 const classStr = showEmoji ? (getClassEmoji(c.class) || c.class) : c.class;
22 const levelStr = showLevel ? `${c.level} ` : "";
23 return `${classStr} ${levelStr}${c.name}`.trim();
24}
25
26/**
27 * Format a nation name with its emoji.
28 * Output: <:capella:> Capella
29 */
30function nation(n: Nation): string {
31 const emoji = getNationEmoji(n);
32 return emoji ? `${emoji} ${n}` : n;
33}
34
35/**
36 * Format a score line.
37 * Output: <:score:> 3000 <:kd:> 32/18
38 */
39function score(pts: number, k?: number, d?: number): string {
40 const scoreEmoji = getEmoji("score") || "📊";
41 const kdEmoji = getEmoji("kd") || "⚔️";
42 const kdStr = k !== undefined && d !== undefined ? ` ${kdEmoji} ${k}/${d}` : "";
43 return `${scoreEmoji} ${pts}${kdStr}`;
44}
45
46/**
47 * Parse a Discord custom emoji string to an object for ButtonBuilder.setEmoji()
48 * Input: "<:fb:1511020923510194428>"
49 * Output: { name: "fb", id: "1511020923510194428" }
50 */
51function emoji(emojiStr: string): { name: string; id: string } | string | null {
52 if (!emojiStr) return null;
53 const match = emojiStr.match(/^<:(\w+):(\d+)>$/);
54 if (match) return { name: match[1], id: match[2] };
55 return emojiStr;
56}
57
58// ─── W.Rank formatters ────────────────────────────────────────────────────────
59
60export interface WRankDisplayOptions {
61 goal: number;
62 brackets?: boolean; // wrap delta in parentheses (default: true)
63}
64
65/**
66 * Format the rank indicator for a wrank entry.
67 * Output: <:wrank_1_gold:> or <:wrank_1:> or bold/plain number fallback
68 */
69function wrankRank(entry: WRankEntry, goal: number): string {
70 const isDone = entry.tgCount >= goal;
71 const rankKey = isDone ? `wrank_${entry.currentRank}_gold` : `wrank_${entry.currentRank}`;
72 return getEmoji(rankKey) || (isDone ? `**${entry.currentRank}**` : `${entry.currentRank}`);
73}
74
75/**
76 * Format the delta indicator for a wrank entry.
77 * Output: <:wrank_up:><:wrank_up_2:> or ↑2, empty string if no change
78 */
79function wrankDelta(entry: WRankEntry, options?: { brackets?: boolean }): string {
80 const brackets = options?.brackets ?? true;
81 const prev = entry.previousRank;
82 const delta = prev !== undefined ? entry.currentRank - prev : 0;
83
84 if (delta === 0 && prev === undefined) return "";
85
86 let inner: string;
87 if (delta < 0) {
88 const abs = Math.abs(delta);
89 const numEmoji = getEmoji(`wrank_up_${abs}`);
90 inner = (getEmoji("wrank_up") || "↑") + (numEmoji || abs);
91 } else if (delta > 0) {
92 const numEmoji = getEmoji(`wrank_down_${delta}`);
93 inner = (getEmoji("wrank_down") || "↓") + (numEmoji || delta);
94 } else {
95 inner = (getEmoji("wrank_neutral") || "·") + "0";
96 }
97
98 return brackets ? ` (${inner})` : ` ${inner}`;
99}
100
101/**
102 * Format a full wrank display string: rank + delta.
103 * Output: <:wrank_1_gold:> (<:wrank_up:><:wrank_up_2:>)
104 */
105function wrankFull(entry: WRankEntry, options: WRankDisplayOptions): string {
106 return wrankRank(entry, options.goal) + wrankDelta(entry, { brackets: options.brackets });
107}
108
109// ─── Namespace export ─────────────────────────────────────────────────────────
110
111export const format = {
112 char,
113 nation,
114 score,
115 emoji,
116 wrank: {
117 rank: wrankRank,
118 delta: wrankDelta,
119 full: wrankFull,
120 },
121};