feat(sources): Genshin's Miliastra Pass windows from Fandom
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0a4be4e339
commit
9f8150fda7
@@ -89,6 +89,7 @@ Two things this method cannot tell you, and both matter:
|
||||
| Silver Palace | — | — | — | Unreleased; beta only |
|
||||
| Genshin Impact (Fandom) | `genshin-impact.fandom.com/api.php?…page=Event` | `fandom`, fifth template | yes (via `api.php`) | **BUILT** 2026-09-12 — adds 11 events Game8 missed; Game8 priority preserves localStorage IDs |
|
||||
| Star Rail — Nameless Honor | `honkai-star-rail.fandom.com/api.php?…page=Nameless_Honor` | new (`fandomtable`) | yes (via `api.php`) | **BUILT** 2026-10-16 — the pass window as one `other` event, day precision |
|
||||
| Genshin — Miliastra Pass | `genshin-impact.fandom.com/api.php?…page=Miliastra_Pass` | existing (`fandomtable`) | yes (via `api.php`) | **BUILT** 2026-10-16 — the live pass run as one `other` event, day precision |
|
||||
| hololive Dreams | `holodori.wiki/wiki/Events` (still) | `holodoriwiki` | yes, but nothing new to fetch | **LAPSED** 2026-10-14 — NamuWiki is behind a Cloudflare challenge, the official site posts no events, Game8 has no page |
|
||||
|
||||
---
|
||||
@@ -869,6 +870,14 @@ passed. A `fetched` with more than zero events means the wiki is back.
|
||||
|
||||
**Reuse:** the parser is `fandomtable` (`src/ingest/parsers/fandomTable.ts`). The next pass pages (ZZZ `New_Eridu_City_Fund`, Genshin `Miliastra_Pass`) are one `PASS_SOURCES` entry each (title as a function of the row's Name cell) plus a `SOURCES` entry; columns are found by heading. A table headed with `Duration` is deliberately not this template (it is a version table).
|
||||
|
||||
## 18. Genshin — Miliastra Pass window (Fandom) — BUILT 2026-10-16
|
||||
|
||||
**Source:** `https://genshin-impact.fandom.com/api.php?action=parse&page=Miliastra_Pass&prop=text&formatversion=2&format=json` (page `https://genshin-impact.fandom.com/wiki/Miliastra_Pass`), source id `genshin-fandom-miliastra-pass`. Second pass-window source; no parser change.
|
||||
|
||||
**What it is:** the first table on the page, `Image | Name | Start | End | Version`, 9 rows (Awakening Chronicle, 2025-10-22, to Silvery Moonlight Chronicle, 2026-09-23 to 2026-11-02; counted independently of the parser). Each row not ended by `ctx.now` becomes one `other` event titled `Miliastra Pass: <name>`, day precision. At capture one is live.
|
||||
|
||||
**Conduct:** the `RobotsCache` with the real User-Agent got `403` (the interstitial challenge) for `robots.txt` on 2026-10-16, so this host rests on the person-recorded reading for `genshin-fandom-events` and the standing `--assume-robots-on-403` decision. The API answered `200` to the real User-Agent for the single fixture fetch.
|
||||
|
||||
## What every one of these costs, beyond the source
|
||||
|
||||
Adding a game is never only a `SOURCES` entry:
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
[
|
||||
{
|
||||
"id": "genshin:miliastra-pass-silvery-moonlight-chronicle:2026-09-23",
|
||||
"game": "genshin",
|
||||
"title": "Miliastra Pass: Silvery Moonlight Chronicle",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-09-23T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-11-02T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://genshin-impact.fandom.com/wiki/Miliastra_Pass/2026-09-23",
|
||||
"sourceId": "genshin-fandom-miliastra-pass",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-10-16T00:00:00.000Z",
|
||||
"updatedAt": "2026-10-16T00:00:00.000Z"
|
||||
}
|
||||
]
|
||||
File diff suppressed because one or more lines are too long
@@ -137,6 +137,27 @@ const SOURCES: SourceSpec[] = [
|
||||
url: "https://honkai-star-rail.fandom.com/api.php?action=parse&page=Nameless_Honor&prop=text&formatversion=2&format=json",
|
||||
parserId: "fandomtable",
|
||||
},
|
||||
{
|
||||
id: "genshin-fandom-miliastra-pass",
|
||||
game: "genshin",
|
||||
// The API, as every Fandom source (AGENTS.md § Fandom). This host's
|
||||
// robots.txt is the one a person recorded for `genshin-fandom-events`
|
||||
// above (standard Fandom file: `Allow: /api.php?action=` for
|
||||
// `User-agent: *`, no `Disallow: /` for `*`). Re-checked with the runner's
|
||||
// own client (`RobotsCache`, the real User-Agent) on 2026-10-16: it
|
||||
// answered 403 (the interstitial challenge), so that person-recorded
|
||||
// reading and the standing `--assume-robots-on-403` decision
|
||||
// (AGENTS.md § Scraping conduct) are what cover this source, as they do
|
||||
// the other Fandom sources. The API path itself answered 200 to the real
|
||||
// User-Agent for the single fixture fetch; no browser-shaped headers.
|
||||
//
|
||||
// A pass history, not an event page: the same `Name | Start | End |
|
||||
// Version` table as Nameless Honor, day precision, one row per run. Read by
|
||||
// the `fandomtable` parser; the title (`Miliastra Pass: <name>`) comes from
|
||||
// `PASS_SOURCES` in `parsers/fandomTable.ts`, keyed by this id.
|
||||
url: "https://genshin-impact.fandom.com/api.php?action=parse&page=Miliastra_Pass&prop=text&formatversion=2&format=json",
|
||||
parserId: "fandomtable",
|
||||
},
|
||||
{
|
||||
id: "r1999-fandom-events",
|
||||
game: "r1999",
|
||||
|
||||
@@ -52,6 +52,12 @@ export const PASS_SOURCES: Readonly<Record<string, PassSource>> = {
|
||||
pass: "Nameless Honor",
|
||||
title: () => "Nameless Honor",
|
||||
},
|
||||
"genshin-fandom-miliastra-pass": {
|
||||
// The Name cell is the run's own chronicle name (`Silvery Moonlight
|
||||
// Chronicle`), a new one each version, so the title carries it.
|
||||
pass: "Miliastra Pass",
|
||||
title: (rowName) => `Miliastra Pass: ${rowName}`,
|
||||
},
|
||||
};
|
||||
|
||||
/** The columns a pass table is recognised by, as normalised heading text. */
|
||||
|
||||
@@ -111,17 +111,25 @@ describe("fandomtable canParse across every fixture", () => {
|
||||
expect(files.length).toBeGreaterThan(20);
|
||||
});
|
||||
|
||||
test("true on the Nameless Honor fixture only", async () => {
|
||||
test("true on the two pass-history fixtures only", async () => {
|
||||
const accepted: string[] = [];
|
||||
for (const f of files) {
|
||||
if (fandomTableParser.canParse(await Bun.file(f).text())) accepted.push(f);
|
||||
}
|
||||
expect(accepted).toEqual([FIXTURE]);
|
||||
expect(accepted).toEqual([
|
||||
"fixtures/genshin/fandom-miliastra-pass-2026-10-16.html",
|
||||
FIXTURE,
|
||||
]);
|
||||
});
|
||||
|
||||
test("no other parser claims the Nameless Honor fixture", async () => {
|
||||
const text = await Bun.file(FIXTURE).text();
|
||||
const claimants = PARSERS.filter((p) => p.canParse(text)).map((p) => p.id);
|
||||
expect(claimants).toEqual(["fandomtable"]);
|
||||
test("no other parser claims a pass-history fixture", async () => {
|
||||
for (const f of [
|
||||
FIXTURE,
|
||||
"fixtures/genshin/fandom-miliastra-pass-2026-10-16.html",
|
||||
]) {
|
||||
const text = await Bun.file(f).text();
|
||||
const claimants = PARSERS.filter((p) => p.canParse(text)).map((p) => p.id);
|
||||
expect(claimants).toEqual(["fandomtable"]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,6 +73,8 @@ const CASES: Array<{ adapter: Adapter; fixture: string; now?: string }> = [
|
||||
// Pinned at the capture day, not NOW: the page is a history, so which rows
|
||||
// survive depends on the clock, and at capture exactly one does (4.6).
|
||||
{ adapter: adapter("hsr-fandom-nameless-honor"), fixture: "fixtures/hsr/fandom-nameless-honor-2026-10-16", now: "2026-10-16T00:00:00.000Z" },
|
||||
// Genshin's Miliastra Pass history (same reader).
|
||||
{ adapter: adapter("genshin-fandom-miliastra-pass"), fixture: "fixtures/genshin/fandom-miliastra-pass-2026-10-16", now: "2026-10-16T00:00:00.000Z" },
|
||||
];
|
||||
|
||||
async function runAdapter(adapter: Adapter, fixture: string, now: string = NOW) {
|
||||
|
||||
Reference in New Issue
Block a user