feat: add wiki.gg as a second Endfield source

First non-Game8 parser, and the first source that states region-scoped ends:
wiki.gg emits ISO timestamps with one timer per server region, so Endfield
events now carry exact times and separate Asia / Americas-Europe ends. That
gap is up to 13 hours, which is what regionEnds was built for.

Two sources for one game also exercised merge for real. It caught both
overlaps — including "Bedazzling Dawnstar" against Game8's "Bedazzling
Dawnstar Sign-In" — and flagged that the two disagree on the end date by 70
hours rather than averaging them into a date neither states.

Matching an appended qualifier needed a prefix test, not a subset test: a
subset would also fuse "Gold Clash" with "Gold Rush Clash Royale".

Also fixes build-feed picking fixtures by game rather than by source, which
handed the wiki.gg page to the Game8 parser once Endfield had two.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Lucas Winther
2026-08-15 01:17:17 +02:00
co-authored by Claude Opus 5
parent f4b133274a
commit dd596b5a43
10 changed files with 1256 additions and 16 deletions
+11 -4
View File
@@ -14,13 +14,20 @@ import type { GachaEvent, GameId } from "../src/shared/schema.ts";
const OUT = "public/data/events.v1.json";
/** Newest fixture per adapter. */
/**
* Newest fixture for one *source*, not one game.
*
* A game can have several sources, and fixtures are named `<site>-events-<date>`
* against adapter ids of `<game>-<site>-events`. Globbing by game alone hands
* one site's page to another site's parser.
*/
async function latestFixture(adapterId: string, game: GameId) {
const glob = new Bun.Glob(`fixtures/${game}/*.html`);
const files = [...glob.scanSync(".")].sort();
const site = adapterId.replace(`${game}-`, "").replace(/-events$/, "");
const pattern = `fixtures/${game}/${site}-*.html`;
const files = [...new Bun.Glob(pattern).scanSync(".")].sort();
const file = files.at(-1);
if (file === undefined) {
throw new Error(`no fixture found for ${adapterId} (fixtures/${game}/*.html)`);
throw new Error(`no fixture found for ${adapterId} (${pattern})`);
}
return { file, html: await Bun.file(file).text() };
}