feat(ingest): add the Reverse: 1999 event source via Fandom's API
New `fandom` parser plus the r1999-fandom-events source, giving the game six live and upcoming events at exact precision. It fetches api.php?action=parse, not /wiki/Events. The rendered page answers a non-browser client with a Cloudflare challenge, and getting past that would be defeating an access control — the reason uma.moe was declined. The wiki's robots.txt instead allows /api.php?action= for *, and that endpoint serves our real User-Agent a 200, so this reads the sanctioned surface with our own headers and no impersonation. The body is therefore JSON, which is what canParse checks first: a challenge page or an error payload must fail loudly, not parse to zero events. Two page facts shape the parser. Titles come from each row's <b>, because a missing banner image renders as a red link reading "File:<Event> Banner.png" that a cell-text reader would publish as the event name. And the page is an archive of 154 rows since v1.1 with no ongoing section to gate on, so inclusion is decided against ctx.now — the six-event count is asserted against an independent extraction off the fixture, per docs/INGESTION.md § Testing. Because robots.txt is unreadable from a challenged address, the gate fails closed in CI and the source is skipped there — a warning, not a broken build. Refreshing it means running `bun run refresh` from an address Fandom serves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1a65a1f06c
commit
3377fcb211
8 files changed
+469
-9
No files matched your search
@@ -0,0 +1,128 @@
|
||||
[
|
||||
{
|
||||
"id": "r1999:8-bit-arcade-remix:2026-08-13",
|
||||
"game": "r1999",
|
||||
"title": "8-Bit Arcade Remix",
|
||||
"type": "other",
|
||||
"summary": "Time-Limited Events",
|
||||
"startsAt": "2026-08-13T10:00:00.000Z",
|
||||
"startPrecision": "exact",
|
||||
"endsAt": "2026-09-24T09:59:00.000Z",
|
||||
"endPrecision": "exact",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://reverse1999.fandom.com/api.php?action=parse&page=Events&prop=text&formatversion=2&format=json",
|
||||
"sourceId": "r1999-fandom-events",
|
||||
"status": "published",
|
||||
"confidence": 0.95,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "r1999:on-anothers-sorrow:2026-08-13",
|
||||
"game": "r1999",
|
||||
"title": "On Another's Sorrow",
|
||||
"type": "other",
|
||||
"summary": "Version Events",
|
||||
"startsAt": "2026-08-13T10:00:00.000Z",
|
||||
"startPrecision": "exact",
|
||||
"endsAt": "2026-09-21T09:59:00.000Z",
|
||||
"endPrecision": "exact",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://reverse1999.fandom.com/wiki/On_Another%27s_Sorrow",
|
||||
"sourceId": "r1999-fandom-events",
|
||||
"status": "published",
|
||||
"confidence": 0.95,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "r1999:the-you-thats-meant-to-be:2026-08-13",
|
||||
"game": "r1999",
|
||||
"title": "The You That's Meant to Be",
|
||||
"type": "story",
|
||||
"summary": "Character Story Events",
|
||||
"startsAt": "2026-08-13T10:00:00.000Z",
|
||||
"startPrecision": "exact",
|
||||
"endsAt": "2026-09-24T09:59:00.000Z",
|
||||
"endPrecision": "exact",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://reverse1999.fandom.com/api.php?action=parse&page=Events&prop=text&formatversion=2&format=json",
|
||||
"sourceId": "r1999-fandom-events",
|
||||
"status": "published",
|
||||
"confidence": 0.95,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "r1999:truth-a-posteriori:2026-08-22",
|
||||
"game": "r1999",
|
||||
"title": "Truth a Posteriori",
|
||||
"type": "other",
|
||||
"summary": "Side Events",
|
||||
"startsAt": "2026-08-22T10:00:00.000Z",
|
||||
"startPrecision": "exact",
|
||||
"endsAt": "2026-09-24T09:59:00.000Z",
|
||||
"endPrecision": "exact",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://reverse1999.fandom.com/api.php?action=parse&page=Events&prop=text&formatversion=2&format=json",
|
||||
"sourceId": "r1999-fandom-events",
|
||||
"status": "published",
|
||||
"confidence": 0.95,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "r1999:a-stranger-to-memory-lane:2026-09-03",
|
||||
"game": "r1999",
|
||||
"title": "A Stranger to Memory Lane",
|
||||
"type": "story",
|
||||
"summary": "Character Story Events",
|
||||
"startsAt": "2026-09-03T10:00:00.000Z",
|
||||
"startPrecision": "exact",
|
||||
"endsAt": "2026-09-24T09:59:00.000Z",
|
||||
"endPrecision": "exact",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://reverse1999.fandom.com/api.php?action=parse&page=Events&prop=text&formatversion=2&format=json",
|
||||
"sourceId": "r1999-fandom-events",
|
||||
"status": "published",
|
||||
"confidence": 0.95,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "r1999:what-does-a-brick-want:2026-09-17",
|
||||
"game": "r1999",
|
||||
"title": "What Does a Brick Want",
|
||||
"type": "other",
|
||||
"summary": "Warm Up Events",
|
||||
"startsAt": "2026-09-17T10:00:00.000Z",
|
||||
"startPrecision": "exact",
|
||||
"endsAt": "2026-09-24T09:59:00.000Z",
|
||||
"endPrecision": "exact",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://reverse1999.fandom.com/api.php?action=parse&page=Events&prop=text&formatversion=2&format=json",
|
||||
"sourceId": "r1999-fandom-events",
|
||||
"status": "published",
|
||||
"confidence": 0.95,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
{"parse":{"title":"Events","pageid":3479,"text":"<div class=\"mw-content-ltr mw-parser-output\" lang=\"en\" dir=\"ltr\"><p>A list of all events available in the Global Version of <a href=\"/wiki/Reverse:_1999\" title=\"Reverse: 1999\">Reverse: 1999</a>.\n</p>\n<div id=\"toc\" class=\"toc\" role=\"navigation\" aria-labelledby=\"mw-toc-heading\"><input type=\"checkbox\" role=\"button\" id=\"toctogglecheckbox\" class=\"toctogglecheckbox\" style=\"display:none\" /><div class=\"toctitle\" lang=\"en\" dir=\"ltr\"><h2 id=\"mw-toc-heading\">Contents</h2><span class=\"toctogglespan\"><label class=\"toctogglelabel\" for=\"toctogglecheckbox\"></label></span></div>\n<ul>\n<li class=\"toclevel-1 tocsection-1\"><a href=\"#Version_Events\"><span class=\"tocnumber\">1</span> <span class=\"toctext\">Version Events</span></a></li>\n<li class=\"toclevel-1 tocsection-2\"><a href=\"#Character_Story_Events\"><span class=\"tocnumber\">2</span> <span class=\"toctext\">Character Story Events</span></a></li>\n<li class=\"toclevel-1 tocsection-3\"><a href=\"#Side_Events\"><span class=\"tocnumber\">3</span> <span class=\"toctext\">Side Events</span></a></li>\n<li class=\"toclevel-1 tocsection-4\"><a href=\"#Time-Limited_Events\"><span class=\"tocnumber\">4</span> <span class=\"toctext\">Time-Limited Events</span></a></li>\n<li class=\"toclevel-1 tocsection-5\"><a href=\"#Warm_Up_Events\"><span class=\"tocnumber\">5</span> <span class=\"toctext\">Warm Up Events</span></a></li>\n</ul>\n</div>\n\n<h2><span class=\"mw-headline\" id=\"Version_Events\">Version Events</span><span class=\"mw-editsection\"><span class=\"mw-editsection-bracket\">[</span><a title=\"Sign in to edit\" class=\"mw-editsection-visualeditor\" data-tracking-label=\"log-in-edit-section\" data-action=\"edit-section\" href=\"https://auth.fandom.com/signin?redirect=https%3A%2F%2Freverse1999.fandom.com%2Fwiki%2FEvents%3Fveaction%3Dedit%26section%3D1&uselang=en&metadata=article-registration-edit-article-section\" data-testid=\"log-in-edit-section\" aria-label=\"Sign in to edit\"><svg class=\"wds-icon wds-icon-tiny\" aria-hidden=\"true\" focusable=\"false\"><use xlink:href=\"#wds-icons-pencil-tiny\"></use></svg></a><span class=\"mw-editsection-bracket\">]</span></span></h2>\n<p>Version Events are the main events of each version, as they come with new stories and gameplay featuring the newly introduced characters.\n</p>\n<table class=\"wikitable sortable\" style=\"width: 100%; font-size: 100%; text-align: center;\">\n<tbody><tr>\n<th width=\"35%\">Event\n</th>\n<th width=\"40%\">Time Period\n</th>\n<th width=\"5%\">Version\n</th></tr>\n<tr>\n<td><span typeof=\"mw:File\"><a href=\"/wiki/The_Theft_of_the_Rimet_Cup\" title=\"The Theft of the Rimet Cup\"><img alt=\"The Theft of the Rimet Cup Banner\" src=\"https://static.wikia.nocookie.net/reverse1999/images/e/e4/The_Theft_of_the_Rimet_Cup_Banner.jpg/revision/latest/scale-to-width-down/400?cb=20231109203715\" decoding=\"async\" loading=\"lazy\" width=\"400\" height=\"70\" class=\"mw-file-element\" data-image-name=\"The Theft of the Rimet Cup Banner.jpg\" data-image-key=\"The_Theft_of_the_Rimet_Cup_Banner.jpg\" data-relevant=\"1\" /></a></span> <br /> <b><a href=\"/wiki/The_Theft_of_the_Rimet_Cup\" title=\"The Theft of the Rimet Cup\">The Theft of the Rimet Cup</a></b>\n</td>\n<td>November 9th, 05:00 - December 4th, 2023, 04:59 (UTC-5)\n</td>\n<td><a href=\"/wiki/Version_1.1\" title=\"Version 1.1\">1.1</a>\n</td></tr>\n<tr>\n<td><span typeof=\"mw:File\"><a href=\"/wiki/A_Nightmare_at_Green_Lake\" title=\"A Nightmare at Green Lake\"><img alt=\"A Nightmare at Green Lake Banner\" src=\"https://static.wikia.nocookie.net/reverse1999/images/b/b9/A_Nightmare_at_Green_Lake_Banner.png/revision/latest/scale-to-width-down/400?cb=20231222232931\" decoding=\"async\" loading=\"lazy\" width=\"400\" height=\"70\" class=\"mw-file-element\" data-image-name=\"A Nightmare at Green Lake Banner.png\" data-image-key=\"A_Nightmare_at_Green_Lake_Banner.png\" data-relevant=\"1\" /></a></span> <br /> <b><a href=\"/wiki/A_Nightmare_at_Green_Lake\" title=\"A Nightmare at Green Lake\">A Nightmare at Green Lake</a></b>\n</td>\n<td>December 7th, 2023, 05:00 - January 15th, 2024, 04:59 (UTC-5)\n</td>\n<td><a href=\"/wiki/Version_1.2\" title=\"Version 1.2\">1.2</a>\n</td></tr>\n<tr>\n<td><span typeof=\"mw:File\"><a href=\"/wiki/Journey_to_Mor_Pankh\" title=\"Journey to Mor Pankh\"><img alt=\"Journey to Mor Pankh Banner\" src=\"https://static.wikia.nocookie.net/reverse1999/images/0/0d/Journey_to_Mor_Pankh_Banner.png/revision/latest/scale-to-width-down/400?cb=20240127220504\" decoding=\"async\" loading=\"lazy\" width=\"400\" height=\"70\" class=\"mw-file-element\" data-image-name=\"Journey to Mor Pankh Banner.png\" data-image-key=\"Journey_to_Mor_Pankh_Banner.png\" data-relevant=\"1\" /></a></span><br /> <b><a href=\"/wiki/Journey_to_Mor_Pankh\" title=\"Journey to Mor Pankh\">Journey to Mor Pankh</a></b>\n</td>\n<td>January 18th, 05:00 - February 26th, 2024,Line truncated
|
||||
Reference in new issue
Block a user