fix Bringer showing incorrectly from previous weeks on result post

This commit is contained in:
Nuno Duque Nunes 2026-06-22 15:41:28 +01:00
parent 772477e6e8
commit 7d68530826
4 changed files with 76 additions and 94 deletions

View file

@ -18,19 +18,10 @@
import { format } from "@format"; import { format } from "@format";
import { Logger } from "@systems/logger"; import { Logger } from "@systems/logger";
import { DiscordClient } from "@discord/client"; import { DiscordClient } from "@discord/client";
import { ResultUI, ResultRow as UIResultRow } from "@ui/result"; import { ResultUI, ResultRow as UIResultRow, ResultRow } from "@ui/result";
const log = Logger.for("result"); const log = Logger.for("result");
// ─── Types ────────────────────────────────────────────────────────────────────
interface ResultRow {
character: Character;
score?: TGScore;
position?: { currentRank: number; previousRank?: number };
leavesCount: number;
}
// ─── Data ───────────────────────────────────────────────────────────────────── // ─── Data ─────────────────────────────────────────────────────────────────────
function buildRows(historyKey: TGKey): ResultRow[] { function buildRows(historyKey: TGKey): ResultRow[] {
@ -80,6 +71,7 @@
score, score,
position, position,
leavesCount: Leaves.countForChar({ characterName: char.name }), leavesCount: Leaves.countForChar({ characterName: char.name }),
historyKey
}); });
} }
@ -103,7 +95,10 @@
return; return;
} }
const embed = ResultUI.buildEmbed(historyKey, rows as any); const { date } = TGKey.parse(historyKey);
const weekKey = WRank.weekKey(new Date(date));
const week = WRank.weekFromKey(weekKey);
const embed = ResultUI.buildEmbed(historyKey, rows, week);
await PersistentMessage.post({ await PersistentMessage.post({
store: "results", store: "results",

View file

@ -177,19 +177,6 @@ export interface PollState {
calledAt?: string; calledAt?: string;
} }
// export interface PollState {
// messageId: string | null;
// slot: number;
// yes: Map<string, VoteEntry>; // userId → VoteEntry
// no: Map<string, VoteEntry>;
// locked: boolean;
// confirmed: "yes" | "no" | null;
// lockMessage?: string;
// confirmMessage?: string;
// lockedYesKeys?: Set<string>; // snapshot of userKeys in yes at lock time
// }
// ─── Scores ────────────────────────────────────────────────────────────────── // ─── Scores ──────────────────────────────────────────────────────────────────
export interface TGScore { export interface TGScore {

View file

@ -8,9 +8,10 @@
import { Config } from "@systems/config"; import { Config } from "@systems/config";
import { Logger } from "@systems/logger"; import { Logger } from "@systems/logger";
import { Runtime } from "@systems/runtime"; import { Runtime } from "@systems/runtime";
import { TGStats } from "@root/src/types"; import { Character, TGScore, TGStats } from "@root/src/types";
import path from "path"; import path from "path";
import fs from "fs"; import fs from "fs";
import { WRankWeek } from "@root/src/systems/wrank";
const log = Logger.for("result-ui"); const log = Logger.for("result-ui");
@ -19,20 +20,8 @@
// ─── Types ──────────────────────────────────────────────────────────────────── // ─── Types ────────────────────────────────────────────────────────────────────
export interface ResultRow { export interface ResultRow {
character: { character: Character;
name: string; score?: TGScore;
class: any;
level: number;
nation: any;
ownerKey: string;
};
score?: {
pts: number;
k?: number;
d?: number;
stats?: TGStats;
wRankAtSubmission?: { rank: number; delta: number };
};
position?: { currentRank: number; previousRank?: number }; position?: { currentRank: number; previousRank?: number };
leavesCount: number; leavesCount: number;
historyKey: TGKey; historyKey: TGKey;
@ -41,7 +30,7 @@
export interface ResultLayout { export interface ResultLayout {
name: string; name: string;
description: string; description: string;
buildEmbed(historyKey: TGKey, rows: ResultRow[]): EmbedBuilder; buildEmbed(historyKey: TGKey, rows: ResultRow[], week?: WRankWeek|null): EmbedBuilder;
formatRow(row: ResultRow, context: any): string; formatRow(row: ResultRow, context: any): string;
} }
@ -107,8 +96,8 @@
// ─── Dispatcher ─────────────────────────────────────────────────────────────── // ─── Dispatcher ───────────────────────────────────────────────────────────────
export const ResultUI = { export const ResultUI = {
buildEmbed(historyKey: TGKey, rows: ResultRow[]): EmbedBuilder { buildEmbed(historyKey: TGKey, rows: ResultRow[], week?: WRankWeek|null): EmbedBuilder {
return activeLayout().buildEmbed(historyKey, rows); return activeLayout().buildEmbed(historyKey, rows, week);
}, },
formatRow(row: ResultRow, context: any): string { formatRow(row: ResultRow, context: any): string {

View file

@ -15,6 +15,7 @@
import { EmbedHelpers } from "@ui/embed-helpers"; import { EmbedHelpers } from "@ui/embed-helpers";
import { TextAlign } from "@ui/text-align"; import { TextAlign } from "@ui/text-align";
import { ResultLayout, ResultRow } from "../index"; import { ResultLayout, ResultRow } from "../index";
import { WRankWeek } from "@root/src/systems/wrank";
const TEMPLATE = "{wrank} {class} {name}{indicators} {score} {kd}"; const TEMPLATE = "{wrank} {class} {name}{indicators} {score} {kd}";
@ -26,12 +27,13 @@
function formatRow( function formatRow(
row: ResultRow, row: ResultRow,
context: NationContext, context: NationContext,
allNames: string[], allNameBlocks: string[],
allScores: string[], allScores: string[],
allKds: string[], allKds: string[],
allAtks: string[], allAtks: string[],
allDefs: string[] allDefs: string[],
): string { week: WRankWeek | null
): string {
const char = row.character; const char = row.character;
const goal = Config.get({ section: "wrank", key: "goal" }); const goal = Config.get({ section: "wrank", key: "goal" });
const wrEntry = Layout.wrankEntry(char as any, row.position, row.score?.pts, 1); const wrEntry = Layout.wrankEntry(char as any, row.position, row.score?.pts, 1);
@ -44,19 +46,22 @@
const scoreColumn = [...allScores, ...allAtks]; const scoreColumn = [...allScores, ...allAtks];
const kdColumn = [...allKds, ...allDefs]; const kdColumn = [...allKds, ...allDefs];
const bringerTag = Layout.bringer(char as any, week ?? undefined);
const nameBlock = bringerTag ? `${char.name}${TextAlign.gap(1)}${bringerTag}` : char.name;
const paddedBlock = TextAlign.padToMax(nameBlock, allNameBlocks);
const cockroach = Layout.cockroach(char as any, row.historyKey);
const tokens: Record<string, string> = { const tokens: Record<string, string> = {
wrank: Layout.wrank(wrEntry, goal, context), wrank: Layout.wrank(wrEntry, goal, context),
class: Emoji.class(classKey) || classKey || "?", class: Emoji.class(classKey) || classKey || "?",
name: TextAlign.padToMax(char.name, allNames), name: paddedBlock,
indicators: Layout.indicators(char as any, { historyKey: row.historyKey }), indicators: cockroach,
score: `${scoreEmoji} ${TextAlign.padToMax(scoreText, scoreColumn)}`, score: `${scoreEmoji} ${TextAlign.padToMax(scoreText, scoreColumn)}`,
kd: TextAlign.gap(KD_GAP) + TextAlign.padToMax(kdText, kdColumn), kd: TextAlign.gap(KD_GAP) + TextAlign.padToMax(kdText, kdColumn),
}; };
const mainLine = Layout.formatRow(TEMPLATE, tokens); const mainLine = Layout.formatRow(TEMPLATE, tokens);
// Read stats from the NESTED shape (row.score.stats), matching TGScore's
// actual canonical structure.
const stats = row.score?.stats; const stats = row.score?.stats;
const hasStats = !!stats && !!(stats.atk || stats.def || stats.heal); const hasStats = !!stats && !!(stats.atk || stats.def || stats.heal);
if (!hasStats) return mainLine; if (!hasStats) return mainLine;
@ -70,9 +75,9 @@
const statsLine = `${prefixGap} ${TextAlign.gap(SCORE_GAP)}${TextAlign.padToMax(atkText, scoreColumn)} ${TextAlign.gap(DEF_GAP)}${TextAlign.padToMax(defText, kdColumn)} ${TextAlign.gap(HEAL_GAP)}${healText}`; const statsLine = `${prefixGap} ${TextAlign.gap(SCORE_GAP)}${TextAlign.padToMax(atkText, scoreColumn)} ${TextAlign.gap(DEF_GAP)}${TextAlign.padToMax(defText, kdColumn)} ${TextAlign.gap(HEAL_GAP)}${healText}`;
return `${mainLine}\n${statsLine}`; return `${mainLine}\n${statsLine}`;
} }
function buildEmbed(historyKey: TGKey, rows: ResultRow[]): EmbedBuilder { function buildEmbed(historyKey: TGKey, rows: ResultRow[], week: WRankWeek|null): EmbedBuilder {
const { date, slot } = TGKey.parse(historyKey); const { date, slot } = TGKey.parse(historyKey);
const capellaRows = rows.filter((r) => r.character.nation === Nation.Capella); const capellaRows = rows.filter((r) => r.character.nation === Nation.Capella);
@ -86,8 +91,14 @@
const sortedCapella = [...capellaRows].sort(sortByScore); const sortedCapella = [...capellaRows].sort(sortByScore);
const sortedProcyon = [...procyonRows].sort(sortByScore); const sortedProcyon = [...procyonRows].sort(sortByScore);
const capellaNames = sortedCapella.map((r) => r.character.name); const capellaNameBlocks = sortedCapella.map((r) => {
const procyonNames = sortedProcyon.map((r) => r.character.name); const tag = Layout.bringer(r.character as any, week ?? undefined);
return tag ? `${r.character.name}${TextAlign.gap(1)}${tag}` : r.character.name;
});
const procyonNameBlocks = sortedProcyon.map((r) => {
const tag = Layout.bringer(r.character as any, week ?? undefined);
return tag ? `${r.character.name}${TextAlign.gap(1)}${tag}` : r.character.name;
});
const capellaScores = sortedCapella.map((r) => r.score ? format.scoreBold(r.score.pts) : "—"); const capellaScores = sortedCapella.map((r) => r.score ? format.scoreBold(r.score.pts) : "—");
const procyonScores = sortedProcyon.map((r) => r.score ? format.scoreBold(r.score.pts) : "—"); const procyonScores = sortedProcyon.map((r) => r.score ? format.scoreBold(r.score.pts) : "—");
const capellaKds = sortedCapella.map((r) => r.score && (r.score.k || r.score.d) ? format.kd(r.score.k ?? 0, r.score.d ?? 0) : "—"); const capellaKds = sortedCapella.map((r) => r.score && (r.score.k || r.score.d) ? format.kd(r.score.k ?? 0, r.score.d ?? 0) : "—");
@ -108,8 +119,8 @@
const capellaEmoji = Emoji.get("capella"); const capellaEmoji = Emoji.get("capella");
const procyonEmoji = Emoji.get("procyon"); const procyonEmoji = Emoji.get("procyon");
const capellaFormatted = sortedCapella.map((r) => formatRow(r, capContext, capellaNames, capellaScores, capellaKds, capellaAtks, capellaDefs)); const capellaFormatted = sortedCapella.map((r) => formatRow(r, capContext, capellaNameBlocks, capellaScores, capellaKds, capellaAtks, capellaDefs, week));
const procyonFormatted = sortedProcyon.map((r) => formatRow(r, proContext, procyonNames, procyonScores, procyonKds, procyonAtks, procyonDefs)); const procyonFormatted = sortedProcyon.map((r) => formatRow(r, proContext, procyonNameBlocks, procyonScores, procyonKds, procyonAtks, procyonDefs, week));
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setTitle(`⚔️ TG Result — ${format.date(new Date(date), "dd/MM/YYYY")} · ${slot}:00`) .setTitle(`⚔️ TG Result — ${format.date(new Date(date), "dd/MM/YYYY")} · ${slot}:00`)