feat: add Girls' Frontline 2, from IOP Wiki
The best date material this project has after wiki.gg: every row states an exact instant on both boundaries and names the zone, so nothing is converted and nothing is assumed. `parseIsoClockRangeUtc` requires that `(UTC)` rather than defaulting to it — a row that ever loses it should drop out instead of landing hours off a boundary the reader is watching in-game. The hazard here is the Server column. CN, EN and JP rows share one table and the Chinese schedule runs about a year ahead, which is the akwiki CN-column problem verbatim: publish EN, skip the rest. Betas are fenced off separately, being dated exactly like everything else and playable by nobody, and the page is an archive back to 2023, so currency is decided against ctx.now as in bawiki. That leaves one live event today — thin, and true. No resetOffsets: the EN boundaries land on three different clocks, which is a patch window rather than a reset hour. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c1cd1148d9
commit
4dff14087f
@@ -37,8 +37,8 @@ A web app that aggregates live and upcoming events across popular gacha games, p
|
||||
calendar, sorts them by end date or by what the reader is partway through, tracks day-by-day
|
||||
progress on events that repeat daily, and lets a user mark events completed.
|
||||
|
||||
**Status: working app, refreshing itself on a schedule.** Schema, six parsers, fourteen sources across
|
||||
thirteen games, the full interface, offline support, a static server, a Docker image and CI all exist and
|
||||
**Status: working app, refreshing itself on a schedule.** Schema, seven parsers, fifteen sources across
|
||||
fourteen games, the full interface, offline support, a static server, a Docker image and CI all exist and
|
||||
are tested. The refresh runner (`bun run refresh`) fetches, caches raw snapshots and rebuilds the
|
||||
feed; `.github/workflows/refresh.yml` runs it twice a day and commits only when a page actually
|
||||
changed. The SQLite layer and the review queue are still specified in `docs/` but not built, so the
|
||||
@@ -106,8 +106,9 @@ re-verify a sample against the live page afterward.
|
||||
```
|
||||
src/shared/ schema.ts (the contract), time.ts, daily.ts, effort.ts, games.ts, feed.ts
|
||||
custom.ts — reader-authored games and events, and their key spaces
|
||||
src/ingest/ html.ts, dates.ts (twelve formats), merge.ts, sanitize.ts, robots.ts, snapshots.ts
|
||||
parsers/ game8.ts, wikigg.ts, akwiki.ts, fandom.ts, bawiki.ts, holodori.ts — keyed by SITE, not game
|
||||
src/ingest/ html.ts, dates.ts (thirteen formats), merge.ts, sanitize.ts, robots.ts, snapshots.ts
|
||||
parsers/ game8.ts, wikigg.ts, akwiki.ts, fandom.ts, bawiki.ts, holodori.ts,
|
||||
iopwiki.ts — keyed by SITE, not game
|
||||
adapters/ index.ts — SOURCES registry binding url+game+parser, and the sanitize seam
|
||||
src/client/ React app, service worker, manifest
|
||||
state/ progress, daily log, ignores, prefs, sort — all localStorage
|
||||
@@ -118,7 +119,7 @@ src/client/ React app, service worker, manifest
|
||||
theme.ts — dark or light, and what a game hue reads as on each
|
||||
scripts/ build-feed.ts, build-static.ts, parse-fixture.ts (offline), refresh-sources.ts (fetches)
|
||||
serve.ts static server + /api/health
|
||||
test/ 620 tests
|
||||
test/ 641 tests
|
||||
fixtures/<game>/ raw HTML + .expected.json per source — pinned, kept forever
|
||||
snapshots/ current page per source, rewritten by refresh — see its README
|
||||
```
|
||||
@@ -164,7 +165,7 @@ These come from how gacha games actually schedule things, and they cause most bu
|
||||
year, month, or end. `readColumnTable` drops a row it cannot date. An omitted event is a
|
||||
recoverable disappointment; a confidently wrong date is the failure this product exists to prevent.
|
||||
- **Parsers are keyed by site, not game.** One `game8` parser serves eight sources and `fandom` two;
|
||||
`wikigg`, `akwiki`, `bawiki` and `holodoriwiki` serve one each — the first two share a host family
|
||||
`wikigg`, `akwiki`, `bawiki`, `holodoriwiki` and `iopwiki` serve one each — the first two share a host family
|
||||
and have entirely different templates, and the last two are both Miraheze wikis whose page
|
||||
templates have nothing in common. Adding a source for a known site is one `SOURCES` entry; a new
|
||||
site is a parser module.
|
||||
@@ -242,7 +243,7 @@ Sources are community wikis. Treat them as a guest would:
|
||||
- Honor `robots.txt`; set a descriptive `User-Agent` with a contact URL.
|
||||
- One request per source per refresh cycle, minimum 6 hours apart.
|
||||
- **Space requests to one host**, honouring its `Crawl-delay` and defaulting to 2s. Eight of the
|
||||
fourteen sources are game8.co pages, so the per-source floor alone still permits one cycle to arrive
|
||||
fifteen sources are game8.co pages, so the per-source floor alone still permits one cycle to arrive
|
||||
as eight back-to-back requests to a single site — which is the shape an edge network throttles, and
|
||||
what a burst looks like from the far end regardless of our intent.
|
||||
- Send `If-None-Match` / `If-Modified-Since`; treat `304` as "skip, unchanged".
|
||||
@@ -269,7 +270,8 @@ refresh from an address game8 will serve, or to find those games another source.
|
||||
|
||||
A source whose ToS forbids automated access does not get an adapter. Flag it and ask.
|
||||
|
||||
**Sources assessed and declined** (2026-08-17), so these are not re-litigated each pass:
|
||||
**Sources assessed and declined** (2026-08-17, extended 2026-08-19), so these are not
|
||||
re-litigated each pass:
|
||||
|
||||
| Source | Verdict |
|
||||
|---|---|
|
||||
@@ -281,6 +283,7 @@ A source whose ToS forbids automated access does not get an adapter. Flag it and
|
||||
| `fategrandorder.fandom.com` | **Built** (2026-08-18), via `api.php` like Reverse: 1999 — but off `Event_List_(US)`, **not** `Event_List`, which is the Japanese server. See § Fandom below |
|
||||
| `holodori.wiki` | **Built** (2026-08-18), from the rendered `/wiki/Events` page. Miraheze again, so the same call as Blue Archive; CC BY-SA 4.0, no `Content-Signal`, no `Crawl-delay` for `*` |
|
||||
| `prydwen.gg`, `gametora.com` | **Cleared, unbuilt.** `User-agent: *` allows the paths we would want. prydwen sets `Crawl-delay: 10`, far below our one-per-6h |
|
||||
| `iopwiki.com` | **Built** (2026-08-19), Girls' Frontline 2 — see § IOP Wiki below. `robots.txt` is two lines, `User-agent: *` and `Crawl-Delay: 20`, no `Disallow` anywhere |
|
||||
|
||||
`.github/ISSUE_TEMPLATE/feature_request.yml` points readers at that table by heading, so a source
|
||||
request can be checked against it before anyone writes it up — the loudest feedback on the first
|
||||
@@ -391,6 +394,26 @@ calendar of deadlines is for. An `Unknown` **end** is kept, though — that is `
|
||||
unlike `bawiki.ts` this parser does not drop a started-but-undated row, because the heading has
|
||||
already said the event is running.
|
||||
|
||||
**IOP Wiki: the Server column is the whole safety story.** `iopwiki.com/wiki/GFL2_Events` is the
|
||||
best date material here after wiki.gg — every row states an exact instant on both boundaries *and*
|
||||
names the zone (`2026-08-06 13:00 - 2026-08-26 22:59 (UTC)`), so `parseIsoClockRangeUtc` converts
|
||||
nothing and both sides are `exact`. Three things about it:
|
||||
|
||||
- **CN, EN and JP rows share one table**, and the Chinese schedule runs about a year ahead. This is
|
||||
the `akwiki` CN-column hazard verbatim and gets the same answer: publish `EN`, skip the rest. It
|
||||
would be wrong by *months* on a row that otherwise looks perfect.
|
||||
- **`Betas` is a section, not an event type.** Closed beta rows are dated exactly like everything
|
||||
else and would parse cleanly onto a calendar of things nobody can play. Fenced on the `<h2>`.
|
||||
- **The page is an archive**, 145 rows back to 2023, so inclusion is decided against `ctx.now` as in
|
||||
`bawiki.ts`. The lane is therefore thin by design — one live event on a quiet week is the truth,
|
||||
not a gap.
|
||||
|
||||
The zone requirement is deliberate: `parseIsoClockRangeUtc` refuses a row that loses its `(UTC)`
|
||||
rather than assuming it, exactly as `parseSlashClockZone` does. GFL2 takes no `resetOffsets`: its EN
|
||||
boundaries land on three different clocks (22:59, 08:59 and 02:59 UTC), which is a patch window
|
||||
rather than a reset hour — Arknights and Reverse: 1999 each earned an override from a single
|
||||
boundary their whole page agreed on.
|
||||
|
||||
`scripts/refresh-sources.ts` enforces all of the above in code — the 6h floor, one request, no
|
||||
retries, conditional headers, per-host spacing, robots (failing closed when `robots.txt` cannot be
|
||||
read). Anything that would make it fetch more often is a change to this section first.
|
||||
|
||||
Reference in New Issue
Block a user