最後活躍 1 month ago

post.ts 原始檔案
1import { ChatInputCommandInteraction, TextChannel, EmbedBuilder } from "discord.js";
2import { cfg } from "@systems/config";
3import { getCurrentWeek, getWeekKey, getBringer } from "@systems/wrank";
4import { getEmoji } from "@systems/emojis";
5import { replyAndDelete } from "@utils";
6
7export async function handleRankPost(interaction: ChatInputCommandInteraction): Promise<void> {
8 const week = getCurrentWeek();
9 const goal = cfg("wRankGoal");
10 const weekKey = getWeekKey();
11
12 const formatNation = (nation: "capella" | "procyon"): string => {
13 const entries = [...week.entries[nation]].sort((a, b) => a.currentRank - b.currentRank);
14 if (entries.length === 0) return "—";
15
16 const bringer = getBringer(nation === "capella" ? "Capella" : "Procyon");
17
18 return entries.map((e) => {
19 const isDone = e.tgCount >= goal;
20
21 // ── Rank indicator ───────────────────────────────────────────────────
22 // Use wrank_1_gold emoji if rank 1 and done, wrank_N emoji if available,
23 // otherwise bold number as fallback (no colors in Discord plain text)
24 const rankKey = isDone ? `wrank_${e.currentRank}_gold` : `wrank_${e.currentRank}`;
25 const rankEmoji = getEmoji(rankKey);
26 const rankStr = rankEmoji ?? (isDone ? `**${e.currentRank}**` : `${e.currentRank}`);
27
28
29 // ── Delta ────────────────────────────────────────────────────────────
30 // Use wrank_up_N / wrank_down_N emoji if available, text arrow as fallback
31 const delta = e.previousRank !== undefined ? e.currentRank - e.previousRank : 0;
32 let deltaStr = "";
33 if (delta < 0) {
34 const abs = Math.abs(delta);
35 deltaStr = " " + (getEmoji(`wrank_up_${abs}`) ?? `${abs}`);
36 } else if (delta > 0) {
37 deltaStr = " " + (getEmoji(`wrank_down_${delta}`) ?? `${delta}`);
38 }
39
40 console.log(`rankKey: ${rankKey} rankEmoji: ${rankEmoji} rankStr: ${rankStr} isDone: ${isDone} delta: ${delta}`);
41
42 // ── Bringer label ────────────────────────────────────────────────────
43 const bringerStr = bringer === e.userKey && isDone
44 ? ` · ${nation === "capella" ? "Luminous Bringer" : "Storm Bringer"}`
45 : "";
46
47 return `${rankStr}${deltaStr} «${e.characterName}» — ${e.weeklyPoints} pts (${e.tgCount}/${goal}${bringerStr})`;
48 }).join("\n");
49 };
50
51 const embed = new EmbedBuilder()
52 .setTitle(`⚔️ W.Rank Leaderboard — ${weekKey}`)
53 .setColor(0xe8a317)
54 .addFields(
55 { name: "🔵 Capella", value: formatNation("capella"), inline: true },
56 { name: "🔴 Procyon", value: formatNation("procyon"), inline: true },
57 )
58 .setTimestamp();
59
60 const channelId = cfg("resultsChannelId") || cfg("pollChannelId");
61 const channel = await interaction.client.channels.fetch(channelId) as TextChannel;
62 await channel.send({ embeds: [embed] });
63 return void replyAndDelete(interaction, "✅ Leaderboard posted.");
64}