Game8 codes pages for Star Rail, ZZZ, Wuthering Waves, Endfield, NTE, P5X, Chaos Zero Nightmare and Umamusume, read by one table parser told each page's active-codes heading. Each page was fetched once and pinned as a fixture. Infinity Nikki's page is stale (still titled July 2025) and is left out. The Codes tab links the publisher's own redeem page where one exists (Genshin, Star Rail, ZZZ) and gives the in-game route for every game, and names the server on a redeemed checkbox only when there are two. Settings uses the width of a large screen: content up to 1320px and the menu column running the full height. Co-Authored-By: Claude Opus 5.5 <[email protected]>
67 lines
2.7 KiB
TypeScript
67 lines
2.7 KiB
TypeScript
import { mkdir, rename } from "node:fs/promises";
|
|
import { CODE_SOURCES } from "../src/ingest/codes/registry.ts";
|
|
import { buildCodesFeed } from "../src/ingest/codes/build.ts";
|
|
import { CodesFeedSchema, type CodesFeed } from "../src/shared/codes.ts";
|
|
import { SnapshotStore, freshnessAt } from "../src/ingest/snapshots.ts";
|
|
import type { SourceHealth } from "../src/shared/feed.ts";
|
|
export async function buildCodes() {
|
|
const root = process.env.SNAPSHOT_DIR ?? "snapshots",
|
|
out = process.env.PUBLIC_DIR ?? "public",
|
|
store = new SnapshotStore(root);
|
|
const firstPath = `${root}/codes-first-seen.json`;
|
|
let first: Record<string, string> = {};
|
|
try {
|
|
first = await Bun.file(firstPath).json();
|
|
} catch {
|
|
/* First offline build uses the fixture's capture date. */
|
|
}
|
|
const previous: CodesFeed = { schemaVersion: 1, codes: [], sources: [] };
|
|
const codes: CodesFeed["codes"] = [];
|
|
const sources: SourceHealth[] = [];
|
|
for (const source of CODE_SOURCES) {
|
|
const snapshot = await store.read(source.id),
|
|
// Until the refresh stores a snapshot, each source builds from its own
|
|
// pinned page, dated by that capture.
|
|
at = snapshot
|
|
? freshnessAt(snapshot)
|
|
: ((await Bun.file(`${source.fixture}.meta.json`).json())
|
|
.contentChangedAt as string),
|
|
html =
|
|
snapshot?.html ?? (await Bun.file(`${source.fixture}.html`).text());
|
|
const parsed = source.parse(html, {
|
|
now: new Date().toISOString(),
|
|
sourceId: source.id,
|
|
sourceUrl: source.url,
|
|
game: source.game,
|
|
});
|
|
const built = buildCodesFeed(previous, parsed, at);
|
|
for (const code of built.codes) {
|
|
code.firstObservedAt = first[code.id] ?? code.firstObservedAt;
|
|
first[code.id] = code.firstObservedAt;
|
|
codes.push(code);
|
|
}
|
|
sources.push({
|
|
sourceId: source.id,
|
|
game: source.game,
|
|
url: source.url,
|
|
lastSuccessAt: at,
|
|
lastConfirmedAt: snapshot?.meta.lastConfirmedAt ?? null,
|
|
contentChangedAt: snapshot?.meta.contentChangedAt ?? at,
|
|
eventCount: parsed.length,
|
|
parsedCount: parsed.length,
|
|
statesNoEvents:
|
|
parsed.length === 0 && "statesNoEvents" in source
|
|
? source.statesNoEvents(html)
|
|
: false,
|
|
});
|
|
}
|
|
const feed = CodesFeedSchema.parse({ schemaVersion: 1, codes, sources });
|
|
await mkdir(`${out}/data`, { recursive: true });
|
|
await Bun.write(`${out}/data/codes.v1.json`, JSON.stringify(feed));
|
|
await mkdir(root, { recursive: true });
|
|
await Bun.write(firstPath + ".tmp", JSON.stringify(first, null, 2) + "\n");
|
|
await rename(firstPath + ".tmp", firstPath);
|
|
console.log(`build-codes: ${codes.length} current codes`);
|
|
}
|
|
if (import.meta.main) await buildCodes();
|