post.ts
· 3.2 KiB · TypeScript
Sin formato
import { ChatInputCommandInteraction, TextChannel, EmbedBuilder } from "discord.js";
import { cfg } from "@systems/config";
import { getCurrentWeek, getWeekKey, getBringer } from "@systems/wrank";
import { getEmoji } from "@systems/emojis";
import { replyAndDelete } from "@utils";
export async function handleRankPost(interaction: ChatInputCommandInteraction): Promise<void> {
const week = getCurrentWeek();
const goal = cfg("wRankGoal");
const weekKey = getWeekKey();
const formatNation = (nation: "capella" | "procyon"): string => {
const entries = [...week.entries[nation]].sort((a, b) => a.currentRank - b.currentRank);
if (entries.length === 0) return "—";
const bringer = getBringer(nation === "capella" ? "Capella" : "Procyon");
return entries.map((e) => {
const isDone = e.tgCount >= goal;
// ── Rank indicator ───────────────────────────────────────────────────
// Use wrank_1_gold emoji if rank 1 and done, wrank_N emoji if available,
// otherwise bold number as fallback (no colors in Discord plain text)
const rankKey = isDone ? `wrank_${e.currentRank}_gold` : `wrank_${e.currentRank}`;
const rankEmoji = getEmoji(rankKey);
const rankStr = rankEmoji ?? (isDone ? `**${e.currentRank}**` : `${e.currentRank}`);
// ── Delta ────────────────────────────────────────────────────────────
// Use wrank_up_N / wrank_down_N emoji if available, text arrow as fallback
const delta = e.previousRank !== undefined ? e.currentRank - e.previousRank : 0;
let deltaStr = "";
if (delta < 0) {
const abs = Math.abs(delta);
deltaStr = " " + (getEmoji(`wrank_up_${abs}`) ?? `↑${abs}`);
} else if (delta > 0) {
deltaStr = " " + (getEmoji(`wrank_down_${delta}`) ?? `↓${delta}`);
}
console.log(`rankKey: ${rankKey} rankEmoji: ${rankEmoji} rankStr: ${rankStr} isDone: ${isDone} delta: ${delta}`);
// ── Bringer label ────────────────────────────────────────────────────
const bringerStr = bringer === e.userKey && isDone
? ` · ${nation === "capella" ? "Luminous Bringer" : "Storm Bringer"}`
: "";
return `${rankStr}${deltaStr} «${e.characterName}» — ${e.weeklyPoints} pts (${e.tgCount}/${goal}${bringerStr})`;
}).join("\n");
};
const embed = new EmbedBuilder()
.setTitle(`⚔️ W.Rank Leaderboard — ${weekKey}`)
.setColor(0xe8a317)
.addFields(
{ name: "🔵 Capella", value: formatNation("capella"), inline: true },
{ name: "🔴 Procyon", value: formatNation("procyon"), inline: true },
)
.setTimestamp();
const channelId = cfg("resultsChannelId") || cfg("pollChannelId");
const channel = await interaction.client.channels.fetch(channelId) as TextChannel;
await channel.send({ embeds: [embed] });
return void replyAndDelete(interaction, "✅ Leaderboard posted.");
}
| 1 | import { ChatInputCommandInteraction, TextChannel, EmbedBuilder } from "discord.js"; |
| 2 | import { cfg } from "@systems/config"; |
| 3 | import { getCurrentWeek, getWeekKey, getBringer } from "@systems/wrank"; |
| 4 | import { getEmoji } from "@systems/emojis"; |
| 5 | import { replyAndDelete } from "@utils"; |
| 6 | |
| 7 | export 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 | } |