scores.ts
· 2.8 KiB · TypeScript
原始文件
import { TGScore, Nation, ClassKey } from "../types";
import { cfg } from "./config";
import { upsertScore, todayString } from "./history";
import { recordScore } from "./wrank";
// Normalize a slot string to a 24h integer hour
// Accepts: "20", "8", "8pm", "20:00", "midnight", "midday", "noon"
export function normalizeSlot(input: string): number | null {
const s = input.trim().toLowerCase();
if (s === "midnight") return 0;
if (s === "midday" || s === "noon") return 12;
const pmMatch = s.match(/^(\d{1,2})pm$/);
if (pmMatch) {
const h = parseInt(pmMatch[1]);
return h === 12 ? 12 : h + 12;
}
const amMatch = s.match(/^(\d{1,2})am$/);
if (amMatch) {
const h = parseInt(amMatch[1]);
return h === 12 ? 0 : h;
}
const colonMatch = s.match(/^(\d{1,2}):\d{2}$/);
if (colonMatch) return parseInt(colonMatch[1]);
const numMatch = s.match(/^(\d{1,2})$/);
if (numMatch) return parseInt(numMatch[1]);
return null;
}
// Detect which slot a submission belongs to based on current time
export function detectSlot(): number | null {
const slots = cfg("slots").filter((s) => s.active);
const windowMs = cfg("scoreWindowHours") * 60 * 60 * 1000;
const durationMs = cfg("tgDurationMinutes") * 60 * 1000;
const now = Date.now();
for (const slot of slots) {
const today = new Date();
const tgTime = new Date(today);
tgTime.setHours(slot.tgHour, 0, 0, 0);
const closeTime = tgTime.getTime() + durationMs;
const windowEnd = closeTime + windowMs;
if (now >= closeTime && now <= windowEnd) {
return slot.tgHour;
}
}
return null;
}
export interface ScoreSubmission {
userKey: string; // owner's key (score goes here)
playedBy?: string; // borrower's key if different from owner
characterName: string;
cls: ClassKey;
nation: Nation;
pts: number;
k?: number;
d?: number;
slot: number;
date?: string;
atk?: number;
def?: number;
heal?: number;
submittedByOfficer: boolean;
}
export function submitScore(sub: ScoreSubmission): void {
const date = sub.date ?? todayString();
const historyKey = `${date}-${String(sub.slot).padStart(2, "0")}`;
const score: TGScore = {
userKey: sub.userKey,
playedBy: sub.playedBy,
characterName: sub.characterName,
class: sub.cls,
nation: sub.nation,
pts: sub.pts,
k: sub.k,
d: sub.d,
atk: sub.atk,
def: sub.def,
heal: sub.heal,
submittedAt: new Date().toISOString(),
slot: sub.slot,
date,
submittedByOfficer: sub.submittedByOfficer,
};
upsertScore(score);
recordScore(sub.userKey, sub.characterName, sub.cls, sub.nation, sub.pts, historyKey);
}
| 1 | import { TGScore, Nation, ClassKey } from "../types"; |
| 2 | import { cfg } from "./config"; |
| 3 | import { upsertScore, todayString } from "./history"; |
| 4 | import { recordScore } from "./wrank"; |
| 5 | |
| 6 | // Normalize a slot string to a 24h integer hour |
| 7 | // Accepts: "20", "8", "8pm", "20:00", "midnight", "midday", "noon" |
| 8 | export function normalizeSlot(input: string): number | null { |
| 9 | const s = input.trim().toLowerCase(); |
| 10 | if (s === "midnight") return 0; |
| 11 | if (s === "midday" || s === "noon") return 12; |
| 12 | |
| 13 | const pmMatch = s.match(/^(\d{1,2})pm$/); |
| 14 | if (pmMatch) { |
| 15 | const h = parseInt(pmMatch[1]); |
| 16 | return h === 12 ? 12 : h + 12; |
| 17 | } |
| 18 | |
| 19 | const amMatch = s.match(/^(\d{1,2})am$/); |
| 20 | if (amMatch) { |
| 21 | const h = parseInt(amMatch[1]); |
| 22 | return h === 12 ? 0 : h; |
| 23 | } |
| 24 | |
| 25 | const colonMatch = s.match(/^(\d{1,2}):\d{2}$/); |
| 26 | if (colonMatch) return parseInt(colonMatch[1]); |
| 27 | |
| 28 | const numMatch = s.match(/^(\d{1,2})$/); |
| 29 | if (numMatch) return parseInt(numMatch[1]); |
| 30 | |
| 31 | return null; |
| 32 | } |
| 33 | |
| 34 | // Detect which slot a submission belongs to based on current time |
| 35 | export function detectSlot(): number | null { |
| 36 | const slots = cfg("slots").filter((s) => s.active); |
| 37 | const windowMs = cfg("scoreWindowHours") * 60 * 60 * 1000; |
| 38 | const durationMs = cfg("tgDurationMinutes") * 60 * 1000; |
| 39 | const now = Date.now(); |
| 40 | |
| 41 | for (const slot of slots) { |
| 42 | const today = new Date(); |
| 43 | const tgTime = new Date(today); |
| 44 | tgTime.setHours(slot.tgHour, 0, 0, 0); |
| 45 | const closeTime = tgTime.getTime() + durationMs; |
| 46 | const windowEnd = closeTime + windowMs; |
| 47 | |
| 48 | if (now >= closeTime && now <= windowEnd) { |
| 49 | return slot.tgHour; |
| 50 | } |
| 51 | } |
| 52 | return null; |
| 53 | } |
| 54 | |
| 55 | export interface ScoreSubmission { |
| 56 | userKey: string; // owner's key (score goes here) |
| 57 | playedBy?: string; // borrower's key if different from owner |
| 58 | characterName: string; |
| 59 | cls: ClassKey; |
| 60 | nation: Nation; |
| 61 | pts: number; |
| 62 | k?: number; |
| 63 | d?: number; |
| 64 | slot: number; |
| 65 | date?: string; |
| 66 | atk?: number; |
| 67 | def?: number; |
| 68 | heal?: number; |
| 69 | submittedByOfficer: boolean; |
| 70 | } |
| 71 | |
| 72 | export function submitScore(sub: ScoreSubmission): void { |
| 73 | const date = sub.date ?? todayString(); |
| 74 | const historyKey = `${date}-${String(sub.slot).padStart(2, "0")}`; |
| 75 | |
| 76 | const score: TGScore = { |
| 77 | userKey: sub.userKey, |
| 78 | playedBy: sub.playedBy, |
| 79 | characterName: sub.characterName, |
| 80 | class: sub.cls, |
| 81 | nation: sub.nation, |
| 82 | pts: sub.pts, |
| 83 | k: sub.k, |
| 84 | d: sub.d, |
| 85 | atk: sub.atk, |
| 86 | def: sub.def, |
| 87 | heal: sub.heal, |
| 88 | submittedAt: new Date().toISOString(), |
| 89 | slot: sub.slot, |
| 90 | date, |
| 91 | submittedByOfficer: sub.submittedByOfficer, |
| 92 | }; |
| 93 | |
| 94 | upsertScore(score); |
| 95 | recordScore(sub.userKey, sub.characterName, sub.cls, sub.nation, sub.pts, historyKey); |
| 96 | } |
| 97 |