feat(sources): ZZZ's City Fund 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
9f8150fda7
commit
a6a8efa661
@@ -90,6 +90,7 @@ Two things this method cannot tell you, and both matter:
|
||||
| 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 |
|
||||
| ZZZ — New Eridu City Fund | `zenless-zone-zero.fandom.com/api.php?…page=New_Eridu_City_Fund` | existing (`fandomtable`) | yes (via `api.php`) | **BUILT** 2026-10-16 — the live fund 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 |
|
||||
|
||||
---
|
||||
@@ -878,6 +879,14 @@ passed. A `fetched` with more than zero events means the wiki is back.
|
||||
|
||||
**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.
|
||||
|
||||
## 19. ZZZ — New Eridu City Fund window (Fandom) — BUILT 2026-10-16
|
||||
|
||||
**Source:** `https://zenless-zone-zero.fandom.com/api.php?action=parse&page=New_Eridu_City_Fund&prop=text&formatversion=2&format=json` (page `https://zenless-zone-zero.fandom.com/wiki/New_Eridu_City_Fund`), source id `zzz-fandom-city-fund`. Third pass-window source; no parser change beyond one `PASS_SOURCES` entry (constant title).
|
||||
|
||||
**What it is:** the first table on the page, `Image | Name | Start | End | Version`, 20 rows (1.0, 2024-07-04, to 3.2, 2026-09-09 to 2026-10-19; counted independently of the parser). Each row not ended by `ctx.now` becomes one `other` event titled `New Eridu City Fund`, day precision. At capture (now 2026-10-16) one row is not ended (3.2) and one event is emitted. The second table (`Language | Official Name`) is not a pass table and is ignored.
|
||||
|
||||
**Conduct:** the `RobotsCache` gets `403` (the interstitial challenge) for this host's `robots.txt`. The repository owner read it in a browser on 2026-10-16: the `User-agent: *` group allows `/api.php?`, `/api.php?action=` and `/api.php?*&action=` and disallows only `/wiki/Special:`, `/wiki/User_talk:`, `/wiki/Template:`, `/wiki/Template_talk:`, `/wiki/Help:`, `/wiki/User:`, `/wiki/UserProfile:` (and language variants); `Disallow: /` applies only to named crawlers (SemrushBot, serpstatbot, GPTBot, ImagesiftBot, CCBot, OAI-SearchBot, ClaudeBot, GrokBot, xAI-SearchBot, xAI-Grok, GrokApp, xAI-Bot); `ias_crawler` has `Crawl-delay: 1`. With the standing `--assume-robots-on-403` decision that covers this source. 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": "zzz:new-eridu-city-fund:2026-09-09",
|
||||
"game": "zzz",
|
||||
"title": "New Eridu City Fund",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-09-09T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-10-19T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://zenless-zone-zero.fandom.com/wiki/New_Eridu_City_Fund/2026-09-09",
|
||||
"sourceId": "zzz-fandom-city-fund",
|
||||
"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
@@ -158,6 +158,32 @@ const SOURCES: SourceSpec[] = [
|
||||
url: "https://genshin-impact.fandom.com/api.php?action=parse&page=Miliastra_Pass&prop=text&formatversion=2&format=json",
|
||||
parserId: "fandomtable",
|
||||
},
|
||||
{
|
||||
id: "zzz-fandom-city-fund",
|
||||
game: "zzz",
|
||||
// The API, as every Fandom source (AGENTS.md § Fandom). This host's
|
||||
// robots.txt answers our client (`RobotsCache`, the real User-Agent) with
|
||||
// 403, the interstitial challenge. The repository owner read
|
||||
// https://zenless-zone-zero.fandom.com/robots.txt in a browser on
|
||||
// 2026-10-16: the `User-agent: *` group has `Allow: /api.php?`,
|
||||
// `Allow: /api.php?action=` and `Allow: /api.php?*&action=`, then
|
||||
// `Disallow:` only for /wiki/Special:, /wiki/User_talk:, /wiki/Template:,
|
||||
// /wiki/Template_talk:, /wiki/Help:, /wiki/User: and /wiki/UserProfile:
|
||||
// (and their language variants); no `Disallow: /` for `*`. `Disallow: /`
|
||||
// applies only to named crawlers (SemrushBot, serpstatbot, GPTBot,
|
||||
// ImagesiftBot, CCBot, OAI-SearchBot, ClaudeBot, GrokBot, xAI-SearchBot,
|
||||
// xAI-Grok, GrokApp, xAI-Bot), none of which we are; `ias_crawler` has
|
||||
// `Crawl-delay: 1`. That reading and the standing `--assume-robots-on-403`
|
||||
// decision (scripts/refresh-sources.ts, AGENTS.md § Scraping conduct) are
|
||||
// what cover this source. The API path answered 200 to the real
|
||||
// User-Agent for the single fixture fetch; no browser-shaped headers.
|
||||
//
|
||||
// A pass history: the `fandomtable` reader finds the `Name | Start | End |
|
||||
// Version` table by heading; the constant title comes from `PASS_SOURCES`
|
||||
// in `parsers/fandomTable.ts`, keyed by this id.
|
||||
url: "https://zenless-zone-zero.fandom.com/api.php?action=parse&page=New_Eridu_City_Fund&prop=text&formatversion=2&format=json",
|
||||
parserId: "fandomtable",
|
||||
},
|
||||
{
|
||||
id: "r1999-fandom-events",
|
||||
game: "r1999",
|
||||
|
||||
@@ -58,6 +58,11 @@ export const PASS_SOURCES: Readonly<Record<string, PassSource>> = {
|
||||
pass: "Miliastra Pass",
|
||||
title: (rowName) => `Miliastra Pass: ${rowName}`,
|
||||
},
|
||||
"zzz-fandom-city-fund": {
|
||||
// One fund, one name; the Name cell is the run's own label, so a constant.
|
||||
pass: "New Eridu City Fund",
|
||||
title: () => "New Eridu City Fund",
|
||||
},
|
||||
};
|
||||
|
||||
/** The columns a pass table is recognised by, as normalised heading text. */
|
||||
|
||||
@@ -111,7 +111,7 @@ describe("fandomtable canParse across every fixture", () => {
|
||||
expect(files.length).toBeGreaterThan(20);
|
||||
});
|
||||
|
||||
test("true on the two pass-history fixtures only", async () => {
|
||||
test("true on the three pass-history fixtures only", async () => {
|
||||
const accepted: string[] = [];
|
||||
for (const f of files) {
|
||||
if (fandomTableParser.canParse(await Bun.file(f).text())) accepted.push(f);
|
||||
@@ -119,6 +119,7 @@ describe("fandomtable canParse across every fixture", () => {
|
||||
expect(accepted).toEqual([
|
||||
"fixtures/genshin/fandom-miliastra-pass-2026-10-16.html",
|
||||
FIXTURE,
|
||||
"fixtures/zzz/fandom-city-fund-2026-10-16.html",
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -126,6 +127,7 @@ describe("fandomtable canParse across every fixture", () => {
|
||||
for (const f of [
|
||||
FIXTURE,
|
||||
"fixtures/genshin/fandom-miliastra-pass-2026-10-16.html",
|
||||
"fixtures/zzz/fandom-city-fund-2026-10-16.html",
|
||||
]) {
|
||||
const text = await Bun.file(f).text();
|
||||
const claimants = PARSERS.filter((p) => p.canParse(text)).map((p) => p.id);
|
||||
|
||||
@@ -75,6 +75,8 @@ const CASES: Array<{ adapter: Adapter; fixture: string; now?: string }> = [
|
||||
{ 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" },
|
||||
// ZZZ's New Eridu City Fund history (same reader).
|
||||
{ adapter: adapter("zzz-fandom-city-fund"), fixture: "fixtures/zzz/fandom-city-fund-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