feat: add Star Rail, Wuthering Waves and Zenless Zone Zero
Three more Game8 templates. ZZZ spans each event name across two rows with a rowspan, so a flat cell reader sees [title, "Start", date] then ["End", date]; losing that pairing would silently halve the calendar. Section matching also had to widen — these pages head their sections "Featured Events", "List of Events and Schedule" and "Ongoing Events". Verified independently of the parser: HSR's year-less summary table lists six ongoing events and all six are captured via their detail rows, so nothing is silently dropped. 28 events added across the three games. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
90be312942
commit
817ad41dcd
@@ -0,0 +1,128 @@
|
||||
[
|
||||
{
|
||||
"id": "hsr:stellar-companion:2026-04-22",
|
||||
"game": "hsr",
|
||||
"title": "Stellar Companion",
|
||||
"type": "other",
|
||||
"summary": "Log in from 4.2 to the end of 4.4 to get a Golden Companion Spirit, which can be exchanged for a free 5-Star!",
|
||||
"startsAt": "2026-04-22T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": null,
|
||||
"endPrecision": "unknown",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Honkai-Star-Rail/archives/408749",
|
||||
"sourceId": "hsr-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.75,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "hsr:antigraft-brickbuster:2026-07-15",
|
||||
"game": "hsr",
|
||||
"title": "Antigraft Brickbuster",
|
||||
"type": "other",
|
||||
"summary": "Help Aventurine investigate IPC corruption by hacking into their database. Stack as many bricks of data to dismantle the firewall and complete each challenge!",
|
||||
"startsAt": "2026-07-15T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-08-26T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Honkai-Star-Rail/archives/408749",
|
||||
"sourceId": "hsr-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "hsr:fate-gift:2026-07-15",
|
||||
"game": "hsr",
|
||||
"title": "Fate Gift",
|
||||
"type": "other",
|
||||
"summary": "Spend 200 Star Rail Special Passes on any Version 4.4 banner to get either Gilgamesh or Archer's Light Cone!",
|
||||
"startsAt": "2026-07-15T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-08-26T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Honkai-Star-Rail/archives/408749",
|
||||
"sourceId": "hsr-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "hsr:fate-star-rail-night:2026-07-24",
|
||||
"game": "hsr",
|
||||
"title": "Fate/Star Rail Night",
|
||||
"type": "other",
|
||||
"summary": "Team up with Rin Tohsaka in this combat event and unlock various Noble Phantasms to unleash strong abilities against foes!",
|
||||
"startsAt": "2026-07-24T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-08-26T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Honkai-Star-Rail/archives/408749",
|
||||
"sourceId": "hsr-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "hsr:free-gilgamesh-or-archer:2026-07-24",
|
||||
"game": "hsr",
|
||||
"title": "Free Gilgamesh or Archer",
|
||||
"type": "other",
|
||||
"summary": "Log in during the Fate Collab update until the end of Version 4.6 to claim either a free Gilgamesh or Archer !",
|
||||
"startsAt": "2026-07-24T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": null,
|
||||
"endPrecision": "unknown",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Honkai-Star-Rail/archives/408749",
|
||||
"sourceId": "hsr-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.75,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "hsr:4-4-garden-of-plenty:2026-08-14",
|
||||
"game": "hsr",
|
||||
"title": "4.4 Garden of Plenty",
|
||||
"type": "other",
|
||||
"summary": "Get double the rewards when challenging Golden and Crimson Calyxes!",
|
||||
"startsAt": "2026-08-14T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-08-24T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Honkai-Star-Rail/archives/408749",
|
||||
"sourceId": "hsr-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
}
|
||||
]
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,212 @@
|
||||
[
|
||||
{
|
||||
"id": "wuwa:a-glimpse-of-xuanfang:2026-07-10",
|
||||
"game": "wuwa",
|
||||
"title": "A Glimpse of Xuanfang",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-07-10T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-08-19T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Wuthering-Waves/archives/453473",
|
||||
"sourceId": "wuwa-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "wuwa:gifts-of-aftertune:2026-07-10",
|
||||
"game": "wuwa",
|
||||
"title": "Gifts of Aftertune",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-07-10T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-08-19T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Wuthering-Waves/archives/453473",
|
||||
"sourceId": "wuwa-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "wuwa:gifts-of-starpath:2026-07-10",
|
||||
"game": "wuwa",
|
||||
"title": "Gifts of Starpath",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-07-10T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-08-19T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Wuthering-Waves/archives/453473",
|
||||
"sourceId": "wuwa-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "wuwa:shape-of-yesterday:2026-07-10",
|
||||
"game": "wuwa",
|
||||
"title": "Shape of Yesterday",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-07-10T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": null,
|
||||
"endPrecision": "unknown",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Wuthering-Waves/archives/453473",
|
||||
"sourceId": "wuwa-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.75,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "wuwa:lament-recon-tacet-crisis:2026-07-11",
|
||||
"game": "wuwa",
|
||||
"title": "Lament Recon: Tacet Crisis",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-07-11T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-08-19T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Wuthering-Waves/archives/453473",
|
||||
"sourceId": "wuwa-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "wuwa:ascendant-aces:2026-07-30",
|
||||
"game": "wuwa",
|
||||
"title": "Ascendant Aces",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-07-30T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-08-19T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Wuthering-Waves/archives/453473",
|
||||
"sourceId": "wuwa-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "wuwa:in-search-of-lost-jade:2026-07-30",
|
||||
"game": "wuwa",
|
||||
"title": "In Search of Lost Jade",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-07-30T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-08-13T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Wuthering-Waves/archives/453473",
|
||||
"sourceId": "wuwa-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "wuwa:virtual-crisis-quadrant-trials:2026-07-30",
|
||||
"game": "wuwa",
|
||||
"title": "Virtual Crisis: Quadrant Trials",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-07-30T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-08-19T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Wuthering-Waves/archives/453473",
|
||||
"sourceId": "wuwa-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "wuwa:lollo-campaign-new-journey:2026-08-06",
|
||||
"game": "wuwa",
|
||||
"title": "Lollo Campaign: New Journey",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-08-06T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-08-19T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Wuthering-Waves/archives/453473",
|
||||
"sourceId": "wuwa-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "wuwa:chord-cleansing:2026-08-12",
|
||||
"game": "wuwa",
|
||||
"title": "Chord Cleansing",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-08-12T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-08-19T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Wuthering-Waves/archives/453473",
|
||||
"sourceId": "wuwa-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
}
|
||||
]
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,254 @@
|
||||
[
|
||||
{
|
||||
"id": "zzz:the-final-callback:2026-07-09",
|
||||
"game": "zzz",
|
||||
"title": "The Final Callback",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-07-09T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-08-19T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Zenless-Zone-Zero/archives/457176",
|
||||
"sourceId": "zzz-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "zzz:filmgoer-thank-you:2026-07-29",
|
||||
"game": "zzz",
|
||||
"title": "Filmgoer Thank You",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-07-29T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-09-09T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Zenless-Zone-Zero/archives/457176",
|
||||
"sourceId": "zzz-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "zzz:gift-from-the-clouds:2026-07-29",
|
||||
"game": "zzz",
|
||||
"title": "Gift From the Clouds",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-07-29T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-09-08T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Zenless-Zone-Zero/archives/457176",
|
||||
"sourceId": "zzz-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "zzz:phaethons-grand-reveal-of-the-year:2026-07-29",
|
||||
"game": "zzz",
|
||||
"title": "Phaethon's Grand Reveal of the Year",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-07-29T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-09-09T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Zenless-Zone-Zero/archives/457176",
|
||||
"sourceId": "zzz-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "zzz:summer-waves-rolls-in:2026-07-29",
|
||||
"game": "zzz",
|
||||
"title": "Summer Waves Rolls In",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-07-29T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-09-07T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Zenless-Zone-Zero/archives/457176",
|
||||
"sourceId": "zzz-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "zzz:snap-focus-showdown:2026-08-07",
|
||||
"game": "zzz",
|
||||
"title": "Snap Focus Showdown",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-08-07T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-08-24T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Zenless-Zone-Zero/archives/457176",
|
||||
"sourceId": "zzz-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "zzz:extensive-patrol-triple-bounty:2026-08-12",
|
||||
"game": "zzz",
|
||||
"title": "Extensive Patrol - Triple Bounty",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-08-12T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-08-17T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Zenless-Zone-Zero/archives/457176",
|
||||
"sourceId": "zzz-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "zzz:crispy-meal-deployment-plan:2026-08-19",
|
||||
"game": "zzz",
|
||||
"title": "Crispy Meal Deployment Plan",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-08-19T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-09-07T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Zenless-Zone-Zero/archives/457176",
|
||||
"sourceId": "zzz-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "zzz:the-great-en-nah-giveaway:2026-08-19",
|
||||
"game": "zzz",
|
||||
"title": "The Great En-Nah Giveaway",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-08-19T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-09-08T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Zenless-Zone-Zero/archives/457176",
|
||||
"sourceId": "zzz-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "zzz:dangerous-fugitives-leisurely-vacation:2026-08-24",
|
||||
"game": "zzz",
|
||||
"title": "Dangerous Fugitive's Leisurely Vacation",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-08-24T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-09-07T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Zenless-Zone-Zero/archives/457176",
|
||||
"sourceId": "zzz-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "zzz:ding-dong-delivery-training-in-progress:2026-08-28",
|
||||
"game": "zzz",
|
||||
"title": "Ding-Dong Delivery Training In Progress",
|
||||
"type": "other",
|
||||
"summary": null,
|
||||
"startsAt": "2026-08-28T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-09-14T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Zenless-Zone-Zero/archives/457176",
|
||||
"sourceId": "zzz-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "zzz:combat-training-triple-bounty:2026-09-02",
|
||||
"game": "zzz",
|
||||
"title": "Combat Training - Triple Bounty",
|
||||
"type": "challenge",
|
||||
"summary": null,
|
||||
"startsAt": "2026-09-02T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-09-07T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://game8.co/games/Zenless-Zone-Zero/archives/457176",
|
||||
"sourceId": "zzz-game8-events",
|
||||
"status": "published",
|
||||
"confidence": 0.85,
|
||||
"extractionMethod": "parser",
|
||||
"version": 1,
|
||||
"firstSeenAt": "2026-08-14T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-14T00:00:00.000Z"
|
||||
}
|
||||
]
|
||||
File diff suppressed because one or more lines are too long
@@ -26,6 +26,24 @@ const SOURCES: SourceSpec[] = [
|
||||
url: "https://game8.co/games/Genshin-Impact/archives/301601",
|
||||
parserId: "game8",
|
||||
},
|
||||
{
|
||||
id: "hsr-game8-events",
|
||||
game: "hsr",
|
||||
url: "https://game8.co/games/Honkai-Star-Rail/archives/408749",
|
||||
parserId: "game8",
|
||||
},
|
||||
{
|
||||
id: "wuwa-game8-events",
|
||||
game: "wuwa",
|
||||
url: "https://game8.co/games/Wuthering-Waves/archives/453473",
|
||||
parserId: "game8",
|
||||
},
|
||||
{
|
||||
id: "zzz-game8-events",
|
||||
game: "zzz",
|
||||
url: "https://game8.co/games/Zenless-Zone-Zero/archives/457176",
|
||||
parserId: "game8",
|
||||
},
|
||||
{
|
||||
id: "nte-game8-events",
|
||||
game: "nte",
|
||||
|
||||
@@ -4,8 +4,10 @@ import {
|
||||
type GachaEvent,
|
||||
} from "../../shared/schema.ts";
|
||||
import {
|
||||
parseFullRange,
|
||||
parseMonthDayRange,
|
||||
parseMonthDayYear,
|
||||
parseOpenRange,
|
||||
parseSlashDateTimeRange,
|
||||
type ParsedInstant,
|
||||
} from "../dates.ts";
|
||||
@@ -34,8 +36,11 @@ import type { SourceParser } from "./types.ts";
|
||||
const INCLUDED_SECTIONS = [
|
||||
/current events/i,
|
||||
/upcoming events/i,
|
||||
/^recurring events$/i,
|
||||
/recurring events/i,
|
||||
/events? schedule/i,
|
||||
/featured events/i,
|
||||
/list of events/i,
|
||||
/ongoing events/i,
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -57,7 +62,10 @@ const RANGE_LABEL = /^(availability period|event period|duration|period|dates)$/
|
||||
|
||||
/** Column-table header matchers. */
|
||||
const COL_TITLE = /^(.*\b)?events?$/i;
|
||||
const COL_RANGE = /^(duration|dates?|period|availability period|schedule)$/i;
|
||||
const COL_RANGE =
|
||||
/^(event |all )?(duration|dates?|event date|period|availability period|schedule)$/i;
|
||||
const COL_START = /^start$/i;
|
||||
const COL_END = /^end$/i;
|
||||
const COL_SUMMARY = /^(event )?(details?|description|overview)$/i;
|
||||
|
||||
interface Candidate {
|
||||
@@ -107,6 +115,12 @@ export function parseGame8EventsPage(
|
||||
|
||||
if (!sectionIncluded || node.kind !== "table") continue;
|
||||
|
||||
const fromStartEnd = readStartEndTable(node.headers, node.rows);
|
||||
if (fromStartEnd.length > 0) {
|
||||
candidates.push(...fromStartEnd);
|
||||
continue;
|
||||
}
|
||||
|
||||
const fromColumns = readColumnTable(node.rows);
|
||||
if (fromColumns.length > 0) {
|
||||
candidates.push(...fromColumns);
|
||||
@@ -203,10 +217,60 @@ function readColumnTable(rows: string[][]): Candidate[] {
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try every known range shape, most specific first. `parseOpenRange` is last
|
||||
* because it is the most permissive — it accepts any leading full date and
|
||||
* reports no end.
|
||||
*/
|
||||
/**
|
||||
* Shape 4: a `Event | Duration | Start | End` table where the event name spans
|
||||
* two rows and each row carries one labelled boundary:
|
||||
*
|
||||
* | Summer Waves Rolls In | Start | July 29, 2026 |
|
||||
* | | End | September 7, 2026 |
|
||||
*
|
||||
* The rowspan is invisible to a flat cell reader, so the shape is recovered
|
||||
* from cell counts: a row that introduces a title, then a continuation row.
|
||||
*/
|
||||
function readStartEndTable(headers: string[], rows: string[][]): Candidate[] {
|
||||
if (!headers.some((h) => COL_START.test(h)) || !headers.some((h) => COL_END.test(h))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const out: Candidate[] = [];
|
||||
for (const row of rows) {
|
||||
const cells = row.map((c) => c.trim());
|
||||
|
||||
// [title, "Start", date] — begins an event.
|
||||
if (cells.length >= 3 && COL_START.test(cells[1] ?? "")) {
|
||||
const title = cells[0] ?? "";
|
||||
const start = parseMonthDayYear(cells[2] ?? "");
|
||||
if (title.length > 0 && start !== null) {
|
||||
out.push({ title, summary: null, start, end: null });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// ["End", date] — completes the event opened above.
|
||||
if (cells.length >= 2 && COL_END.test(cells[0] ?? "")) {
|
||||
const current = out.at(-1);
|
||||
if (current !== undefined && current.end === null) {
|
||||
current.end = parseMonthDayYear(cells[1] ?? "");
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function parseRange(
|
||||
value: string,
|
||||
): { start: ParsedInstant; end: ParsedInstant } | null {
|
||||
return parseSlashDateTimeRange(value) ?? parseMonthDayRange(value);
|
||||
): { start: ParsedInstant; end: ParsedInstant | null } | null {
|
||||
return (
|
||||
parseSlashDateTimeRange(value) ??
|
||||
parseFullRange(value) ??
|
||||
parseMonthDayRange(value) ??
|
||||
parseOpenRange(value)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { genshinGame8, nteGame8 } from "../../src/ingest/adapters/index.ts";
|
||||
import { adapterById } from "../../src/ingest/adapters/index.ts";
|
||||
import type { Adapter } from "../../src/ingest/adapters/types.ts";
|
||||
import { inferType } from "../../src/ingest/parsers/game8.ts";
|
||||
import { GachaEvent, type EventType } from "../../src/shared/schema.ts";
|
||||
@@ -10,15 +10,21 @@ import { GachaEvent, type EventType } from "../../src/shared/schema.ts";
|
||||
*/
|
||||
const NOW = "2026-08-14T00:00:00.000Z";
|
||||
|
||||
function adapter(id: string): Adapter {
|
||||
const found = adapterById(id);
|
||||
if (found === undefined) throw new Error(`no adapter '${id}'`);
|
||||
return found;
|
||||
}
|
||||
|
||||
const genshinGame8 = adapter("genshin-game8-events");
|
||||
const nteGame8 = adapter("nte-game8-events");
|
||||
|
||||
const CASES: Array<{ adapter: Adapter; fixture: string }> = [
|
||||
{
|
||||
adapter: genshinGame8,
|
||||
fixture: "fixtures/genshin/game8-events-2026-08-14",
|
||||
},
|
||||
{
|
||||
adapter: nteGame8,
|
||||
fixture: "fixtures/nte/game8-events-2026-08-14",
|
||||
},
|
||||
{ adapter: genshinGame8, fixture: "fixtures/genshin/game8-events-2026-08-14" },
|
||||
{ adapter: nteGame8, fixture: "fixtures/nte/game8-events-2026-08-14" },
|
||||
{ adapter: adapter("hsr-game8-events"), fixture: "fixtures/hsr/game8-events-2026-08-14" },
|
||||
{ adapter: adapter("wuwa-game8-events"), fixture: "fixtures/wuwa/game8-events-2026-08-14" },
|
||||
{ adapter: adapter("zzz-game8-events"), fixture: "fixtures/zzz/game8-events-2026-08-14" },
|
||||
];
|
||||
|
||||
async function runAdapter(adapter: Adapter, fixture: string) {
|
||||
@@ -161,3 +167,41 @@ describe("inferType", () => {
|
||||
expect(inferType(title)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("new source shapes", () => {
|
||||
test("zzz recovers events from rowspan Start/End rows", async () => {
|
||||
// The event name spans two rows, so a flat cell reader sees
|
||||
// [title, "Start", date] then ["End", date]. Losing the pairing would
|
||||
// silently halve the calendar.
|
||||
const events = await runAdapter(
|
||||
adapter("zzz-game8-events"),
|
||||
"fixtures/zzz/game8-events-2026-08-14",
|
||||
);
|
||||
const summer = events.find((e) => e.title === "Summer Waves Rolls In");
|
||||
expect(summer?.startsAt).toBe("2026-07-29T00:00:00.000Z");
|
||||
expect(summer?.endsAt).toBe("2026-09-07T00:00:00.000Z");
|
||||
expect(events.every((e) => e.endsAt !== null)).toBe(true);
|
||||
});
|
||||
|
||||
test("hsr keeps events whose end is not announced", async () => {
|
||||
// "Jul. 24, 2026 - End of 4.6" has a real start and no knowable end.
|
||||
// Publishing it with a guessed end would be the worst possible outcome.
|
||||
const events = await runAdapter(
|
||||
adapter("hsr-game8-events"),
|
||||
"fixtures/hsr/game8-events-2026-08-14",
|
||||
);
|
||||
const open = events.filter((e) => e.endsAt === null);
|
||||
expect(open.length).toBeGreaterThan(0);
|
||||
for (const e of open) expect(e.endPrecision).toBe("unknown");
|
||||
});
|
||||
|
||||
test("wuwa parses ranges carrying a year on both sides", async () => {
|
||||
const events = await runAdapter(
|
||||
adapter("wuwa-game8-events"),
|
||||
"fixtures/wuwa/game8-events-2026-08-14",
|
||||
);
|
||||
const jade = events.find((e) => e.title === "In Search of Lost Jade");
|
||||
expect(jade?.startsAt).toBe("2026-07-30T00:00:00.000Z");
|
||||
expect(jade?.endsAt).toBe("2026-08-13T00:00:00.000Z");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user