feat(holodori): track hololive Dreams from its own wiki
New holodoriwiki parser plus the holodori-holodoriwiki-events source, giving the game its two live and upcoming events at exact precision. holodori.wiki is Miraheze, so this is the same call bawiki makes and for the same reason: robots.txt disallows /w/ and /*?action=, which closes the API route and leaves the rendered /wiki/Events page as the surface * is allowed. It serves our own User-Agent a 200, sets no Content-Signal and no Crawl-delay for us, and the wiki is CC BY-SA 4.0. What makes this source unusually good is that it states its timezone on every cell — 08/17/2026 8:00PM (JST) — so it is the only wiki source here publishing exact precision on both boundaries without a per-region timer. parseSlashClockZone requires that zone rather than defaulting to UTC: a row that ever loses it drops out instead of silently landing nine hours off, and an abbreviation absent from the table is refused rather than guessed, which is why the table holds only JST and not the plausible-looking candidates that are not fixed offsets. The 12-hour clock gets its own handling and its own tests, because 12PM and 12AM are the two readings a naive parser gets wrong while still producing a valid-looking instant. Inclusion is fenced by the Current Events <h2> and bounded by the next one. That matters more than it looks: Past Events sits directly below with identical columns, so a reader taking every wikitable would put the back catalogue on the calendar with nothing to mark it. Rows are still checked against ctx.now on top of the heading, because Current is maintained by hand and goes stale before anyone moves a row down. Two rows are deliberately missing. Beginner Mission runs Game Launch → Unknown, and no start means no event ID — a permanent tutorial chore is not what a calendar of deadlines is for. An Unknown end is kept as endsAt null, and unlike bawiki this parser does not drop a started-but-undated row: the heading has already said the event is running, which is the fact bawiki lacks when reading an archive. Every event title is still a red link — the wiki has no article for any of them yet — so each points at ?action=edit&redlink=1, a create-page form on a path this robots.txt disallows. Refusing a href with a query keeps those out and falls back to the events page; the articles get linked when they exist, with no change here. holodori gets resetOffsets of UTC+9 across all three regions, evidenced rather than assumed the way Arknights' is: the game launched worldwide simultaneously on one service, every boundary is stated in JST, and Training Support Missions ends at 3:59AM JST, one minute before a 04:00 local reset. Only the offset is overridden; the hour is the default. Setting it now is free, because no reader has a day key for a game the app has never shipped — the same override a year from now would re-label ticks already logged. Both boundaries and the exclusions are asserted against an independent extraction off the fixture, computed through a timezone library rather than by the parser's own arithmetic. Several counts in AGENTS.md and PRD.md were already stale before this — the Fate/Grand Order source did not update them — and are corrected to what the tree now holds rather than to what it held yesterday. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f3d0df9fda
commit
7ab801f409
@@ -36,8 +36,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, five parsers, twelve sources across
|
||||
eleven 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, six parsers, fourteen sources across
|
||||
thirteen 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
|
||||
@@ -105,8 +105,8 @@ 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 (eleven formats), merge.ts, sanitize.ts, robots.ts, snapshots.ts
|
||||
parsers/ game8.ts, wikigg.ts, akwiki.ts, fandom.ts, bawiki.ts — keyed by SITE, not game
|
||||
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
|
||||
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
|
||||
@@ -114,7 +114,7 @@ src/client/ React app, service worker, manifest
|
||||
lens.ts — who sees which rows (focus, outstanding, next-to-expire); pure
|
||||
scripts/ build-feed.ts, build-static.ts, parse-fixture.ts (offline), refresh-sources.ts (fetches)
|
||||
serve.ts static server + /api/health
|
||||
test/ 536 tests
|
||||
test/ 560 tests
|
||||
fixtures/<game>/ raw HTML + .expected.json per source — pinned, kept forever
|
||||
snapshots/ current page per source, rewritten by refresh — see its README
|
||||
```
|
||||
@@ -146,10 +146,11 @@ These come from how gacha games actually schedule things, and they cause most bu
|
||||
- **Skip, never guess.** Every function in `dates.ts` returns `null` rather than inferring a missing
|
||||
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; `wikigg`,
|
||||
`akwiki`, `fandom` and `bawiki` serve one each — the first two share a host family and have
|
||||
entirely different templates. Adding a source for a known site is one `SOURCES` entry; a new site is
|
||||
a parser module.
|
||||
- **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
|
||||
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.
|
||||
- **A source may publish more than one region's schedule.** Arknights' wiki lists CN and Global on
|
||||
every row, five months apart. Publish the one our readers are on and skip the row that lacks it —
|
||||
a CN date on a Global calendar is a confidently wrong date, not a near miss.
|
||||
@@ -215,7 +216,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
|
||||
twelve sources are game8.co pages, so the per-source floor alone still permits one cycle to arrive
|
||||
fourteen 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".
|
||||
@@ -251,6 +252,7 @@ A source whose ToS forbids automated access does not get an adapter. Flag it and
|
||||
| `reverse1999.fandom.com` | **Built** (2026-08-17), via `api.php`, not the wiki page — see § Fandom below |
|
||||
| `bluearchive.fandom.com` | **Declined.** Fetches and parses fine; the page is the problem. Its `Event/Event_List` is a JP-server archive whose newest entry ended 2026-02-18, so all 88 rows are history and it yields **zero** live or upcoming events. An adapter would put an empty lane on the calendar and, because the runner rejects a body that parses to nothing, report a broken source forever. Same failure as the Infinity Nikki Game8 page, further along |
|
||||
| `bluearchive.wiki` | **Built** (2026-08-17), from the rendered `/wiki/Events` page — see § Blue Archive 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 |
|
||||
|
||||
`.github/ISSUE_TEMPLATE/feature_request.yml` points readers at that table by heading, so a source
|
||||
@@ -313,6 +315,31 @@ confidently wrong date:
|
||||
dailies source if a zone is ever stated. For the same reason `ba` has no `resetOffsets`: Blue
|
||||
Archive Global does run one worldwide server, but nothing in this source says on what clock.
|
||||
|
||||
**hololive Dreams: the same Miraheze call as Blue Archive, and the opposite data.** `holodori.wiki`
|
||||
is Miraheze too, so `/wiki/Events` is the surface `*` is allowed and `/w/` and `?action=` are closed
|
||||
— `parsers/holodori.ts` takes the route `bawiki.ts` takes, for the reason it takes it. What differs
|
||||
is the quality of what is there, and three things are worth knowing:
|
||||
|
||||
- **It states its timezone on every cell.** Every boundary is `08/17/2026 8:00PM (JST)`, which makes
|
||||
this the only wiki source here publishing `exact` precision on both sides without a per-region
|
||||
timer. `parseSlashClockZone` **requires** the zone rather than defaulting to UTC, so a row that
|
||||
ever loses it drops out instead of landing nine hours off. That is also where `holodori`'s
|
||||
`resetOffsets` of UTC+9 comes from — evidenced, not assumed; see docs/DATA-MODEL.md.
|
||||
- **Inclusion is fenced by an `<h2>`, and the two tables are identical.** `Current Events` and
|
||||
`Past Events` have the same columns, so a reader that took every `wikitable` would put the back
|
||||
catalogue on the calendar with nothing to mark it. Rows are checked against `ctx.now` on top of
|
||||
the heading, because "Current" is maintained by hand and goes stale before anyone moves a row.
|
||||
- **Every event title is still a red link.** The wiki has no article for any of them yet, so each
|
||||
links to `?action=edit&redlink=1` — a create-page form, and a `?action=` URL this wiki's
|
||||
robots.txt disallows. The parser refuses a href with a query and falls back to the events page;
|
||||
when the articles exist, they get linked with no change.
|
||||
|
||||
Two rows on the page are not events and are meant to be missing. `Beginner Mission` runs
|
||||
`Game Launch` → `Unknown`: no start means no event ID, and a permanent tutorial chore is not what a
|
||||
calendar of deadlines is for. An `Unknown` **end** is kept, though — that is `endsAt: null`, and
|
||||
unlike `bawiki.ts` this parser does not drop a started-but-undated row, because the heading has
|
||||
already said the event is running.
|
||||
|
||||
`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.
|
||||
|
||||
@@ -77,6 +77,7 @@ src/
|
||||
akwiki.ts arknights.wiki.gg mrfz-wtable ✓ built
|
||||
fandom.ts Fandom via the action=parse API ✓ built
|
||||
bawiki.ts bluearchive.wiki JP/Global tabber ✓ built
|
||||
holodori.ts holodori.wiki Current/Past Events tables ✓ built
|
||||
index.ts parser registry ✓ built
|
||||
adapters/
|
||||
types.ts Adapter interface, ParseContext ✓ built
|
||||
|
||||
+14
-5
@@ -260,12 +260,21 @@ reader cannot see.
|
||||
| Endfield, Europe | 04:00 | UTC-5 (on the Americas server) | 09:00 | 11:00 / 10:00 |
|
||||
| Endfield, Asia / Americas | 04:00 | regional default | 20:00 / 09:00 | — |
|
||||
| Reverse: 1999, all regions | **05:00** | UTC-5 (one global server) | 10:00 | 12:00 / 11:00 |
|
||||
| hololive Dreams, all regions | 04:00 | UTC+9 (one worldwide server) | 19:00 | 21:00 / 20:00 |
|
||||
|
||||
Arknights is the one game whose override covers **all three** regions, and it is not a blanket
|
||||
per-game offset of the kind this section warns about below: the game genuinely runs a single Global
|
||||
server for every region we model. The offset is read off the source rather than assumed — every
|
||||
ending event on arknights.wiki.gg carries an exact end of `10:59:59Z`, which is `03:59:59` at UTC-7,
|
||||
one second before a 04:00 reset.
|
||||
Arknights and hololive Dreams are the games whose override covers **all three** regions, and neither
|
||||
is a blanket per-game offset of the kind this section warns about below: both genuinely run a single
|
||||
worldwide server for every region we model. Arknights' offset is read off the source rather than
|
||||
assumed — every ending event on arknights.wiki.gg carries an exact end of `10:59:59Z`, which is
|
||||
`03:59:59` at UTC-7, one second before a 04:00 reset.
|
||||
|
||||
hololive Dreams is read the same way and is the cleaner case of the two, because the source states
|
||||
its zone outright: every boundary on holodori.wiki carries `(JST)`, the game launched worldwide
|
||||
simultaneously on one service, and `Training Support Missions` ends at `3:59AM JST` — one minute
|
||||
before a 04:00 local reset. Only the offset is overridden; the hour is the default. It also cost
|
||||
nothing to add, which is the point worth carrying to the next game: an override that arrives with
|
||||
the game moves nobody, while the same override added a year later re-labels every tick already
|
||||
logged under the old clock. Get it right at introduction or accept a migration.
|
||||
|
||||
Infinity Nikki, P5X and Blue Archive carry **no `resetOffsets` entry**, so they take the regional
|
||||
default. That is an assumption, not a verified server map — none of the three sources states one.
|
||||
|
||||
+3
-1
@@ -51,8 +51,9 @@ Consequences worth internalising:
|
||||
| `game8` | game8.co article calendars | Genshin, Star Rail, Wuthering Waves, ZZZ, Endfield, NTE, Infinity Nikki, Persona 5: The Phantom X |
|
||||
| `wikigg` | wiki.gg MediaWiki `mp-event` templates | Endfield |
|
||||
| `akwiki` | arknights.wiki.gg's `mrfz-wtable` "Ongoing/upcoming" table | Arknights |
|
||||
| `fandom` | Fandom wikis via the MediaWiki `action=parse` API — `Event \| Time Period \| Version` wikitables | Reverse: 1999 |
|
||||
| `fandom` | Fandom wikis via the MediaWiki `action=parse` API — `Event \| Time Period \| Version` wikitables | Reverse: 1999, Fate/Grand Order |
|
||||
| `bawiki` | bluearchive.wiki's rendered `/wiki/Events` — a JP/Global tabber over `Name (EN) \| Start date \| End date \| Notes` wikitables | Blue Archive |
|
||||
| `holodoriwiki` | holodori.wiki's rendered `/wiki/Events` — `Current Events` and `Past Events` wikitables over `Event \| Type \| Start Date \| End Date` | hololive Dreams |
|
||||
|
||||
`wikigg` is the better shape by a distance: it emits ISO timestamps with one timer per server
|
||||
region, so its events carry exact precision and real `regionEnds`. Prefer a source like that over a
|
||||
@@ -118,6 +119,7 @@ All live in `src/ingest/dates.ts`, each returning null rather than inferring any
|
||||
| `parseYearFirstSlashRange` | `2026/07/30 – 2026/08/20` (year first, so field order is not inferred) | Arknights |
|
||||
| `parseOrdinalDateTimeRange` | `November 9th, 05:00 - December 4th, 2023, 04:59 (UTC-5)` (ordinal days, stated offset) | Reverse: 1999 |
|
||||
| `parseIsoDay` | `2026-08-04` (one boundary per column, so nothing to split) | Blue Archive |
|
||||
| `parseSlashClockZone` | `08/17/2026 8:00PM (JST)` (one boundary per column, 12-hour clock, **named** zone) | hololive Dreams |
|
||||
| `parseOpenRange` | `Jul. 24, 2026 - End of 4.6`, `July 10, 2026 - Permanent` | Star Rail, Wuthering Waves |
|
||||
|
||||
`parseOpenRange` is tried last because it is the most permissive — it accepts any leading full date
|
||||
|
||||
+3
-2
@@ -51,13 +51,14 @@ event, because a missing event sends them to a wiki while a wrong one makes them
|
||||
| Neverness to Everness | `nte` |
|
||||
|
||||
Added since launch, on the strength of the release thread (`docs/FEEDBACK.md` § P1): Infinity Nikki
|
||||
(`nikki`), Persona 5: The Phantom X (`p5x`), Reverse: 1999 (`r1999`), Blue Archive (`ba`).
|
||||
(`nikki`), Persona 5: The Phantom X (`p5x`), Reverse: 1999 (`r1999`), Blue Archive (`ba`),
|
||||
Fate/Grand Order (`fgo`), hololive Dreams (`holodori`).
|
||||
**`GameId` in `src/shared/schema.ts` is the live answer** and `SOURCES` says which of them actually
|
||||
have a source — this table is the launch scope, not a roster to keep in sync.
|
||||
|
||||
Adding a game must require no change to `GachaEvent` — only a `GameId` entry, its `games.ts`
|
||||
metadata, and a source registration. That is the test of whether the data model is right, and it has
|
||||
held: the eleven games here have cost the event schema nothing. Per-game *metadata* does occasionally
|
||||
held: the thirteen games here have cost the event schema nothing. Per-game *metadata* does occasionally
|
||||
grow (Reverse: 1999 needed `resetHourLocal` for a 05:00 reset), which is a different file and moves
|
||||
no stored key. A game may have several sources; see `docs/INGESTION.md` § Three layers.
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
[
|
||||
{
|
||||
"id": "holodori:ultimate-summer-for-me:2026-08-17",
|
||||
"game": "holodori",
|
||||
"title": "Ultimate Summer For Me?",
|
||||
"type": "banner",
|
||||
"summary": "Spotlight",
|
||||
"startsAt": "2026-08-17T11:00:00.000Z",
|
||||
"startPrecision": "exact",
|
||||
"endsAt": "2026-08-27T10:59:00.000Z",
|
||||
"endPrecision": "exact",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://holodori.wiki/wiki/Events",
|
||||
"sourceId": "holodori-holodoriwiki-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": "holodori:training-support-missions:2026-08-07",
|
||||
"game": "holodori",
|
||||
"title": "Training Support Missions",
|
||||
"type": "other",
|
||||
"summary": "Mission",
|
||||
"startsAt": "2026-08-07T02:00:00.000Z",
|
||||
"startPrecision": "exact",
|
||||
"endsAt": "2026-08-29T18:59:00.000Z",
|
||||
"endPrecision": "exact",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://holodori.wiki/wiki/Events",
|
||||
"sourceId": "holodori-holodoriwiki-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,843 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="client-nojs skin-theme-clientpref-os citizen-feature-autohide-navigation-clientpref-1 citizen-feature-image-dimming-clientpref-0 citizen-feature-pure-black-clientpref-0 citizen-feature-custom-font-size-clientpref-standard citizen-feature-custom-width-clientpref-standard citizen-feature-performance-mode-clientpref-1 citizen-header-position-left citizen-header-position-mobile-bottom" lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Events - hololive Dreams Wiki</title>
|
||||
<script>document.documentElement.className="client-js skin-theme-clientpref-os citizen-feature-autohide-navigation-clientpref-1 citizen-feature-image-dimming-clientpref-0 citizen-feature-pure-black-clientpref-0 citizen-feature-custom-font-size-clientpref-standard citizen-feature-custom-width-clientpref-standard citizen-feature-performance-mode-clientpref-1 citizen-header-position-left citizen-header-position-mobile-bottom";RLCONF={"wgBreakFrames":false,"wgSeparatorTransformTable":["",""],"wgDigitTransformTable":["",""],"wgDefaultDateFormat":"dmy","wgMonthNames":["","January","February","March","April","May","June","July","August","September","October","November","December"],"wgRequestId":"f30c3343a37c70d3f2082146","wgCanonicalNamespace":"","wgCanonicalSpecialPageName":false,"wgNamespaceNumber":0,"wgPageName":"Events","wgTitle":"Events","wgCurRevisionId":8471,"wgRevisionId":8471,"wgArticleId":1149,"wgIsArticle":true,"wgIsRedirect":false,"wgAction":"view","wgUserName":null,"wgUserGroups":["*"],"wgCategories":[],"wgPageViewLanguage":"en","wgPageContentLanguage":"en","wgPageContentModel":"wikitext","wgRelevantPageName":"Events","wgRelevantArticleId":1149,"wgIsProbablyEditable":true,"wgRelevantPageIsProbablyEditable":true,"wgRestrictionEdit":[],"wgRestrictionMove":[],"simpleBatchUploadMaxFilesPerBatch":{"*":5,"autoconfirmed":25,"bureaucrat":500,"confirmed":50,"sysop":250,"user":5},"wgNoticeProject":"all","wgConfirmEditCaptchaNeededForGenericEdit":false,"wgCiteReferencePreviewsActive":true,"wgMFDisplayWikibaseDescriptions":{"search":false,"nearby":false,"watchlist":false,"tagline":false},"wgMediaViewerOnClick":true,"wgMediaViewerEnabledByDefault":true,"wgVisualEditor":{"pageLanguageCode":"en","pageLanguageDir":"ltr","pageVariantFallbacks":"en"},"wgPopupsFlags":0,"prpProofreadPageBookNamespaces":[0],"wgCheckUserClientHintsHeadersJsApi":["brands","architecture","bitness","fullVersionList","mobile","model","platform","platformVersion"],"wgIsMobile":false,"wgEditSubmitButtonLabelPublish":false,"wgULSPosition":"personal","wgULSisCompactLinksEnabled":true,"wgVector2022LanguageInHeader":false,"wgULSisLanguageSelectorEmpty":false};
|
||||
RLSTATE={"ext.globalCssJs.user.styles":"ready","site.styles":"ready","user.styles":"ready","ext.globalCssJs.user":"ready","user":"ready","user.options":"loading","ext.slideshow.css":"ready","ext.proofreadpage.base":"ready","ext.proofreadpage.article":"ready","skins.citizen.styles":"ready","skins.citizen.codex.styles":"ready","skins.citizen.icons":"ready","ext.CookieWarning.styles":"ready","oojs-ui-core.styles":"ready","oojs-ui.styles.indicators":"ready","mediawiki.widgets.styles":"ready","oojs-ui-core.icons":"ready","ext.MobileDetect.nomobile":"ready","ext.visualEditor.desktopArticleTarget.noscript":"ready","ext.embedVideo.styles":"ready","ext.relatedArticles.styles":"ready","ext.uls.pt":"ready","skins.citizen.tokens":"ready"};RLPAGEMODULES=["ext.slideshow.main","site","mediawiki.page.ready","skins.citizen.scripts","ext.createPage","ext.centralNotice.geoIP","ext.centralNotice.startUp","ext.checkUser.clientHints","ext.CookieWarning","ext.echo.centralauth","ext.eventLogging","mmv.bootstrap","ext.urlShortener.toolbar","ext.visualEditor.desktopArticleTarget.init","ext.visualEditor.targetLoader","ext.embedVideo.overlay","ext.popups","ext.relatedArticles.readMore.bootstrap","ext.uls.compactlinks","ext.uls.interface","ext.centralauth.centralautologin","ext.purge","skins.citizen.toc"];</script>
|
||||
<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.impl(function(){return["user.options@12s5i",function($,jQuery,require,module){mw.user.tokens.set({"patrolToken":"+\\","watchToken":"+\\","csrfToken":"+\\"});
|
||||
}];});});</script>
|
||||
<link rel="stylesheet" href="/w/load.php?lang=en&modules=ext.CookieWarning.styles%7Cext.MobileDetect.nomobile%7Cext.embedVideo.styles%7Cext.proofreadpage.article%2Cbase%7Cext.relatedArticles.styles%7Cext.slideshow.css%7Cext.uls.pt%7Cext.visualEditor.desktopArticleTarget.noscript%7Cmediawiki.widgets.styles%7Coojs-ui-core.icons%2Cstyles%7Coojs-ui.styles.indicators%7Cskins.citizen.codex.styles%7Cskins.citizen.icons%2Cstyles%2Ctokens&only=styles&skin=citizen">
|
||||
<script async="" src="/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=citizen"></script>
|
||||
<meta name="ResourceLoaderDynamicStyles" content="">
|
||||
<link rel="stylesheet" href="/w/load.php?lang=en&modules=site.styles&only=styles&skin=citizen">
|
||||
<meta name="generator" content="MediaWiki 1.45.4">
|
||||
<meta name="referrer" content="origin">
|
||||
<meta name="referrer" content="origin-when-cross-origin">
|
||||
<meta name="robots" content="max-image-preview:standard">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta property="og:image" content="https://static.wikitide.net/holodoriwiki/6/60/Ultimate_Summer_For_Me_Logo.png">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="750">
|
||||
<meta name="google-site-verification" content="duoLHiTyTWzz70G6br-a2PZVb1D5AKfa21XPSvz8PMk">
|
||||
<meta name="description" content="Events are in-game campaigns that allow players to earn rewards by completing tasks. Each event has a corresponding Gacha and story that can be unlocked with event...">
|
||||
<meta name="twitter:site" content="">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
||||
<meta property="og:title" content="Events - hololive Dreams Wiki">
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="theme-color" content="#1C1321">
|
||||
<link rel="preconnect" href="//upload.wikimedia.org">
|
||||
<link rel="manifest" href="https://holodori.wiki/w/api.php?action=appmanifest">
|
||||
<link rel="alternate" type="application/x-wiki" title="Edit" href="/wiki/Events?action=edit">
|
||||
<link rel="apple-touch-icon" href="https://static.wikitide.net/holodoriwiki/4/43/Apple-touch-icon.png">
|
||||
<link rel="icon" href="https://static.wikitide.net/holodoriwiki/6/64/Favicon.ico">
|
||||
<link rel="search" type="application/opensearchdescription+xml" href="/w/rest.php/v1/search" title="hololive Dreams Wiki (en)">
|
||||
<link rel="EditURI" type="application/rsd+xml" href="https://holodori.wiki/w/api.php?action=rsd">
|
||||
<link rel="canonical" href="https://holodori.wiki/wiki/Events">
|
||||
<link rel="alternate" type="application/atom+xml" title="hololive Dreams Wiki Atom feed" href="/wiki/Special:RecentChanges?feed=atom">
|
||||
<link rel="dns-prefetch" href="https://meta.miraheze.org" />
|
||||
<meta property="og:title" content="Events">
|
||||
<meta property="og:site_name" content="hololive Dreams Wiki">
|
||||
<meta property="og:url" content="https://holodori.wiki/wiki/Events">
|
||||
<meta property="og:description" content="Events are in-game campaigns that allow players to earn rewards by completing tasks. Each event has a corresponding Gacha and story that can be unlocked with event...">
|
||||
<meta property="article:modified_time" content="2026-08-17T02:59:05Z">
|
||||
<meta property="article:published_time" content="2026-08-17T02:59:05Z">
|
||||
<script type="application/ld+json">{"@context":"http:\/\/schema.org","@type":"Article","name":"Events - hololive Dreams Wiki","headline":"Events - hololive Dreams Wiki","mainEntityOfPage":"Events","identifier":"https:\/\/holodori.wiki\/wiki\/Events","url":"https:\/\/holodori.wiki\/wiki\/Events","description":"Events are in-game campaigns that allow players to earn rewards by completing tasks. Each event has a corresponding Gacha and story that can be unlocked with event...","dateModified":"2026-08-17T02:59:05Z","datePublished":"2026-08-17T02:59:05Z","image":{"@type":"ImageObject","url":"https:\/\/static.wikitide.net\/holodoriwiki\/c\/c4\/HolodoriWikiLogo.png"},"author":{"@type":"Organization","name":"hololive Dreams Wiki","url":"https:\/\/holodori.wiki","logo":{"@type":"ImageObject","url":"https:\/\/static.wikitide.net\/holodoriwiki\/c\/c4\/HolodoriWikiLogo.png","caption":"hololive Dreams Wiki"}},"publisher":{"@type":"Organization","name":"hololive Dreams Wiki","url":"https:\/\/holodori.wiki","logo":{"@type":"ImageObject","url":"https:\/\/static.wikitide.net\/holodoriwiki\/c\/c4\/HolodoriWikiLogo.png","caption":"hololive Dreams Wiki"}},"potentialAction":{"@type":"SearchAction","target":"https:\/\/holodori.wiki\/wiki\/Special:Search?search={search_term}","query-input":"required name=search_term"}}</script>
|
||||
<script>window.clientPrefs=()=>{let className=document.documentElement.className;let storage;try{storage=localStorage.getItem('mwclientpreferences');}catch(e){}if(storage){storage.split(',').forEach((pref)=>{const pattern=new RegExp('(^| )'+pref.replace(/-clientpref-\w+$|[^\w-]+/g,'')+'-clientpref-\\w+( |$)');if(pattern.test(className)){className=className.replace(pattern,'$1'+pref+'$2');}else{className+=' '+pref;}});document.documentElement.className=className;}};(()=>{window.clientPrefs();})();</script>
|
||||
<link rel="dns-prefetch" href="auth.miraheze.org">
|
||||
</head>
|
||||
<body class="citizen-toc-enabled citizen-sections-enabled mediawiki ltr sitedir-ltr mw-hide-empty-elt ns-0 ns-subject mw-editable page-Events rootpage-Events skin-citizen action-view skin--responsive">
|
||||
<a class="mw-jump-link citizen-jump-link" href="#bodyContent">Jump to content</a>
|
||||
<header class="mw-header citizen-header">
|
||||
<div class="citizen-header__logo">
|
||||
<a href="/wiki/Main_Page" class="mw-logo citizen-cdx-button--size-large cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--icon-only cdx-button--weight-quiet" title="Visit the main page">
|
||||
<img class="mw-logo-icon" src="https://static.wikitide.net/holodoriwiki/c/c4/HolodoriWikiLogo.png
|
||||
" alt="" aria-hidden="true" height="32" width="32">
|
||||
<span class="citizen-ui-icon mw-ui-icon-home mw-ui-icon-wikimedia-home"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="citizen-search citizen-header__item citizen-dropdown">
|
||||
<details id="citizen-search-details" class="citizen-dropdown-details">
|
||||
<summary
|
||||
id="citizen-search-summary"
|
||||
class="citizen-dropdown-summary citizen-cdx-button--size-large cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--icon-only cdx-button--weight-quiet"
|
||||
title="Toggle search "
|
||||
aria-details="citizen-search__card">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" aria-hidden="true">
|
||||
<circle cx="8" cy="8" r="6" fill="none" stroke="currentcolor" stroke-width="2"/>
|
||||
<line class="citizen-icon-search__handle" x1="12.5" y1="12.5" x2="18" y2="18" stroke="currentcolor" stroke-width="2" stroke-linecap="round"/>
|
||||
<line class="citizen-icon-search__close" x1="3.6" y1="3.6" x2="16.4" y2="16.4" stroke="currentcolor" stroke-width="2" stroke-linecap="round"/>
|
||||
<line class="citizen-icon-search__close" x1="16.4" y1="3.6" x2="3.6" y2="16.4" stroke="currentcolor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<span>Toggle search</span>
|
||||
</summary>
|
||||
</details>
|
||||
<div role="search" id="citizen-search__card" class="citizen-search-box citizen-search__card citizen-menu__card">
|
||||
<div class="citizen-menu__card-content">
|
||||
<form action="/w/index.php" class="citizen-search__form" id="searchform" autocomplete="off">
|
||||
<input type="hidden" name="title" value="Special:Search">
|
||||
<label class="citizen-search__formIcon" for="searchInput">
|
||||
<span class="citizen-search__icon citizen-ui-icon mw-ui-icon-wikimedia-search"></span>
|
||||
<span class="screen-reader-text">Search</span>
|
||||
</label>
|
||||
<input type="search" name="search" placeholder="Search hololive Dreams Wiki" aria-label="Search hololive Dreams Wiki" autocapitalize="sentences" spellcheck="false" title="Search hololive Dreams Wiki [f]" accesskey="f" id="searchInput">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="citizen-drawer citizen-header__item citizen-dropdown">
|
||||
<details class="citizen-dropdown-details">
|
||||
<summary
|
||||
class="citizen-dropdown-summary citizen-cdx-button--size-large cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--icon-only cdx-button--weight-quiet"
|
||||
title="Toggle menu"
|
||||
aria-details="citizen-drawer__card">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" aria-hidden="true">
|
||||
<rect x="1" y="3" width="18" height="2" fill="currentcolor" rx="1"/>
|
||||
<rect x="1" y="9" width="18" height="2" fill="currentcolor" rx="1"/>
|
||||
<rect x="1" y="15" width="18" height="2" fill="currentcolor" rx="1"/>
|
||||
</svg>
|
||||
<span>Toggle menu</span>
|
||||
</summary> </details>
|
||||
<div id="citizen-drawer__card" class="citizen-drawer__card citizen-menu__card">
|
||||
<div class="citizen-menu__card-content">
|
||||
<div class="citizen-drawer__header">
|
||||
<a href="/wiki/Main_Page" class="mw-logo citizen-drawer__logo" title="Visit the main page">
|
||||
<img class="mw-logo-icon" src="https://static.wikitide.net/holodoriwiki/c/c4/HolodoriWikiLogo.png
|
||||
" alt="" aria-hidden="true" height="80" width="80" loading="lazy">
|
||||
</a>
|
||||
<div class="citizen-drawer__siteinfo">
|
||||
<div class="citizen-siteStats">
|
||||
<div class="citizen-siteStats__item" id="citizen-siteStats__item--articles" title="articles">
|
||||
<span class="citizen-ui-icon mw-ui-icon-article mw-ui-icon-wikimedia-article"></span>
|
||||
<span>437</span>
|
||||
</div>
|
||||
<div class="citizen-siteStats__item" id="citizen-siteStats__item--images" title="files">
|
||||
<span class="citizen-ui-icon mw-ui-icon-image mw-ui-icon-wikimedia-image"></span>
|
||||
<span>743</span>
|
||||
</div>
|
||||
<div class="citizen-siteStats__item" id="citizen-siteStats__item--users" title="users">
|
||||
<span class="citizen-ui-icon mw-ui-icon-userAvatar mw-ui-icon-wikimedia-userAvatar"></span>
|
||||
<span>25</span>
|
||||
</div>
|
||||
<div class="citizen-siteStats__item" id="citizen-siteStats__item--edits" title="edits">
|
||||
<span class="citizen-ui-icon mw-ui-icon-edit mw-ui-icon-wikimedia-edit"></span>
|
||||
<span>8.4K</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mw-logo-wordmark">hololive Dreams Wiki</div>
|
||||
</div>
|
||||
</div>
|
||||
<section id="citizen-main-menu" class="citizen-main-menu citizen-drawer__menu">
|
||||
<nav
|
||||
id="p-navigation"
|
||||
class="citizen-menu mw-portlet mw-portlet-navigation"
|
||||
aria-label="Navigation"
|
||||
|
||||
|
||||
>
|
||||
<div class="citizen-menu__heading">
|
||||
Navigation
|
||||
</div>
|
||||
<div class="citizen-menu__content">
|
||||
|
||||
<ul class="citizen-menu__content-list">
|
||||
|
||||
<li id="n-mainpage-description" class="mw-list-item"><a href="/wiki/Main_Page" title="Visit the main page [z]" accesskey="z"><span class="citizen-ui-icon mw-ui-icon-home mw-ui-icon-wikimedia-home"></span> <span>Main page</span></a></li><li id="n-recentchanges" class="mw-list-item"><a href="/wiki/Special:RecentChanges" rel="nofollow" title="A list of recent changes in the wiki [r]" accesskey="r"><span class="citizen-ui-icon mw-ui-icon-recentChanges mw-ui-icon-wikimedia-recentChanges"></span> <span>Recent changes</span></a></li><li id="n-randompage" class="mw-list-item"><a href="/wiki/Special:Random" rel="nofollow" title="Load a random page [x]" accesskey="x"><span class="citizen-ui-icon mw-ui-icon-die mw-ui-icon-wikimedia-die"></span> <span>Random page</span></a></li><li id="t-upload" class="mw-list-item"><a href="/wiki/Special:Upload" title="Upload files [u]" rel="nofollow" accesskey="u"><span class="citizen-ui-icon mw-ui-icon-upload mw-ui-icon-wikimedia-upload"></span> <span>Upload file</span></a></li>
|
||||
</ul>
|
||||
|
||||
</div></nav>
|
||||
|
||||
<nav
|
||||
id="p-hololive_Dreams"
|
||||
class="citizen-menu mw-portlet mw-portlet-hololive_Dreams"
|
||||
aria-label="hololive Dreams"
|
||||
|
||||
|
||||
>
|
||||
<div class="citizen-menu__heading">
|
||||
hololive Dreams
|
||||
</div>
|
||||
<div class="citizen-menu__content">
|
||||
|
||||
<ul class="citizen-menu__content-list">
|
||||
|
||||
<li id="n-Talents-List" class="mw-list-item"><a href="/wiki/List_of_Talents"><span>Talents List</span></a></li><li id="n-Cards-List" class="mw-list-item"><a href="/wiki/List_of_Cards"><span>Cards List</span></a></li><li id="n-Songs-List" class="mw-list-item"><a href="/wiki/List_of_Songs"><span>Songs List</span></a></li><li id="n-Events" class="mw-list-item"><a href="/wiki/Events"><span>Events</span></a></li>
|
||||
</ul>
|
||||
|
||||
</div></nav>
|
||||
<nav
|
||||
id="p-Official_Pages"
|
||||
class="citizen-menu mw-portlet mw-portlet-Official_Pages"
|
||||
aria-label="Official Pages"
|
||||
|
||||
|
||||
>
|
||||
<div class="citizen-menu__heading">
|
||||
Official Pages
|
||||
</div>
|
||||
<div class="citizen-menu__content">
|
||||
|
||||
<ul class="citizen-menu__content-list">
|
||||
|
||||
<li id="n-Website" class="mw-list-item"><a rel="nofollow" href="https://www.hololive-dreams.com/en/"><span>Website</span></a></li><li id="n-Steam" class="mw-list-item"><a rel="nofollow" href="https://store.steampowered.com/app/4282500/hololive_Dreams/"><span>Steam</span></a></li><li id="n-App-Store" class="mw-list-item"><a rel="nofollow" href="https://itunes.apple.com/us/app/id6756641249"><span>App Store</span></a></li><li id="n-Google-Play" class="mw-list-item"><a rel="nofollow" href="https://play.google.com/store/apps/details?id=game.qualiarts.hololive.dreams.com"><span>Google Play</span></a></li><li id="n-YouTube" class="mw-list-item"><a rel="nofollow" href="https://www.youtube.com/channel/UCawGfU44CZxd2ZVf_VGijbA"><span>YouTube</span></a></li><li id="n-JP-Twitter" class="mw-list-item"><a rel="nofollow" href="https://twitter.com/hololive_dreams"><span>JP Twitter</span></a></li><li id="n-EN-Twitter" class="mw-list-item"><a rel="nofollow" href="https://twitter.com/holo_dreams_en"><span>EN Twitter</span></a></li>
|
||||
</ul>
|
||||
|
||||
</div></nav>
|
||||
<nav
|
||||
id="p-Community"
|
||||
class="citizen-menu mw-portlet mw-portlet-Community"
|
||||
aria-label="Community"
|
||||
|
||||
|
||||
>
|
||||
<div class="citizen-menu__heading">
|
||||
Community
|
||||
</div>
|
||||
<div class="citizen-menu__content">
|
||||
|
||||
<ul class="citizen-menu__content-list">
|
||||
|
||||
<li id="n-Official-Discord-Server" class="mw-list-item"><a rel="nofollow" href="https://discord.gg/hololive-dreams"><span>Official Discord Server</span></a></li><li id="n-Fan-+-Wiki-Discord-Server" class="mw-list-item"><a rel="nofollow" href="https://discord.gg/kSUPMskemN"><span>Fan + Wiki Discord Server</span></a></li><li id="n-Wiki-Twitter" class="mw-list-item"><a rel="nofollow" href="https://twitter.com/holodoriWiki"><span>Wiki Twitter</span></a></li>
|
||||
</ul>
|
||||
|
||||
</div></nav>
|
||||
|
||||
</section> </div>
|
||||
</div>
|
||||
</div> <div class="citizen-header__inner">
|
||||
<div class="citizen-header__start"></div>
|
||||
<div class="citizen-header__end">
|
||||
<div class="citizen-preferences-dropdown citizen-header__item citizen-dropdown">
|
||||
<details id="citizen-preferences-details" class="citizen-dropdown-details">
|
||||
<summary
|
||||
class="citizen-dropdown-summary citizen-cdx-button--size-large cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet cdx-button--icon-only"
|
||||
title="Toggle preferences menu"
|
||||
aria-details="citizen-preferences-dropdown__card">
|
||||
<span class="citizen-ui-icon mw-ui-icon-wikimedia-configure"></span>
|
||||
<span>Toggle preferences menu</span>
|
||||
</summary>
|
||||
</details>
|
||||
<div id="citizen-preferences-dropdown__card" class="citizen-menu__card">
|
||||
<div class="citizen-menu__card-content">
|
||||
<div id="citizen-preferences-content" class="citizen-preferences">
|
||||
<div class="citizen-preferences-skeleton" role="status" aria-busy="true" aria-live="polite">
|
||||
<div class="citizen-preferences-skeleton__heading"></div>
|
||||
<div class="citizen-preferences-skeleton__row">
|
||||
<div class="citizen-preferences-skeleton__label"></div>
|
||||
<div class="citizen-preferences-skeleton__description"></div>
|
||||
</div>
|
||||
<div class="citizen-preferences-skeleton__row">
|
||||
<div class="citizen-preferences-skeleton__label"></div>
|
||||
<div class="citizen-preferences-skeleton__description"></div>
|
||||
</div>
|
||||
<div class="citizen-preferences-skeleton__row">
|
||||
<div class="citizen-preferences-skeleton__label"></div>
|
||||
<div class="citizen-preferences-skeleton__description"></div>
|
||||
</div>
|
||||
<div class="citizen-preferences-skeleton__heading"></div>
|
||||
<div class="citizen-preferences-skeleton__row">
|
||||
<div class="citizen-preferences-skeleton__label"></div>
|
||||
<div class="citizen-preferences-skeleton__description"></div>
|
||||
</div>
|
||||
<div class="citizen-preferences-skeleton__row">
|
||||
<div class="citizen-preferences-skeleton__label"></div>
|
||||
<div class="citizen-preferences-skeleton__description"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="citizen-preferences-error" role="alert" hidden>
|
||||
<p class="citizen-preferences-error__message">Couldn't load preferences. Check your connection and try again.</p>
|
||||
<button class="cdx-button citizen-preferences-error__retry" type="button">
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="citizen-userMenu citizen-header__item citizen-dropdown">
|
||||
<details class="citizen-dropdown-details">
|
||||
<summary
|
||||
class="citizen-dropdown-summary citizen-cdx-button--size-large cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet cdx-button--icon-only"
|
||||
title="Toggle personal menu"
|
||||
aria-details="citizen-userMenu__card">
|
||||
<span class="citizen-ui-icon mw-ui-icon-wikimedia-userAvatar"></span>
|
||||
<span>Toggle personal menu</span>
|
||||
</summary>
|
||||
</details>
|
||||
<div id="citizen-userMenu__card" class="citizen-menu__card">
|
||||
<div class="citizen-menu__card-content">
|
||||
<div class="citizen-userInfo">
|
||||
<div class="citizen-userInfo-title">
|
||||
<div>Not logged in</div>
|
||||
|
||||
</div>
|
||||
<div class="citizen-userInfo-text">
|
||||
<div>Your IP address will be publicly visible if you make any edits.</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav
|
||||
id="p-user-interface-preferences"
|
||||
class="citizen-menu mw-portlet mw-portlet-user-interface-preferences"
|
||||
aria-label="user-interface-preferences"
|
||||
|
||||
|
||||
>
|
||||
<div class="citizen-menu__heading">
|
||||
user-interface-preferences
|
||||
</div>
|
||||
<div class="citizen-menu__content">
|
||||
|
||||
<ul class="citizen-menu__content-list">
|
||||
|
||||
<li id="ca-uls" class="mw-list-item active"><a href="#" class="uls-trigger"><span class="citizen-ui-icon mw-ui-icon-wikimedia-language mw-ui-icon-wikimedia-wikimedia-language"></span> <span>English</span></a></li>
|
||||
</ul>
|
||||
|
||||
</div></nav>
|
||||
|
||||
<nav
|
||||
id="p-personal"
|
||||
class="citizen-menu mw-portlet mw-portlet-personal"
|
||||
aria-label="Personal tools"
|
||||
title="User menu"
|
||||
|
||||
>
|
||||
<div class="citizen-menu__heading">
|
||||
Personal tools
|
||||
</div>
|
||||
<div class="citizen-menu__content">
|
||||
|
||||
<ul class="citizen-menu__content-list">
|
||||
|
||||
<li id="pt-anontalk" class="mw-list-item"><a href="/wiki/Special:MyTalk" title="Discussion about edits from this IP address [n]" accesskey="n"><span class="citizen-ui-icon mw-ui-icon-userTalk mw-ui-icon-wikimedia-userTalk"></span> <span>Talk</span></a></li><li id="pt-anoncontribs" class="mw-list-item"><a href="/wiki/Special:MyContributions" title="A list of edits made from this IP address [y]" accesskey="y"><span class="citizen-ui-icon mw-ui-icon-userContributions mw-ui-icon-wikimedia-userContributions"></span> <span>Contributions</span></a></li><li id="pt-createaccount" class="mw-list-item"><a href="/wiki/Special:CreateAccount?returnto=Events" title="You are encouraged to create an account and log in; however, it is not mandatory"><span class="citizen-ui-icon mw-ui-icon-userAdd mw-ui-icon-wikimedia-userAdd"></span> <span>Create account</span></a></li><li id="pt-login" class="mw-list-item"><a href="/wiki/Special:UserLogin?returnto=Events" title="You are encouraged to log in; however, it is not mandatory [o]" accesskey="o"><span class="citizen-ui-icon mw-ui-icon-logIn mw-ui-icon-wikimedia-logIn"></span> <span>Log in</span></a></li>
|
||||
</ul>
|
||||
|
||||
</div></nav>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div> </div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="citizen-page-container">
|
||||
<div class="citizen-sitenotice-container"><div id="siteNotice"><!-- CentralNotice --></div></div>
|
||||
<main class="mw-body" id="content">
|
||||
<header class="mw-body-header citizen-page-header" id="citizen-page-header">
|
||||
<div class="citizen-page-header-inner">
|
||||
|
||||
<div class="citizen-page-heading">
|
||||
<div class="firstHeading-container">
|
||||
<h1 id="firstHeading" class="firstHeading mw-first-heading"><span class="mw-page-title-main">Events</span></h1>
|
||||
|
||||
<div class="mw-indicators">
|
||||
</div> </div>
|
||||
<div id="siteSub">From hololive Dreams Wiki</div>
|
||||
</div>
|
||||
|
||||
<div class="citizen-page-actions">
|
||||
|
||||
<div class="citizen-share-trigger citizen-header__item">
|
||||
<button
|
||||
type="button"
|
||||
id="citizen-share"
|
||||
class="citizen-share citizen-cdx-button--size-large cdx-button cdx-button--weight-quiet cdx-button--icon-only"
|
||||
title="Share this page"
|
||||
aria-label="Share this page"
|
||||
aria-haspopup="dialog"
|
||||
>
|
||||
<span class="citizen-ui-icon mw-ui-icon-wikimedia-share"></span>
|
||||
<span>Share this page</span>
|
||||
</button>
|
||||
<dialog
|
||||
id="citizen-share-dialog"
|
||||
class="citizen-share-dialog"
|
||||
aria-label="Share this page"
|
||||
>
|
||||
<div id="citizen-share-dialog-content" class="citizen-share-dialog__content">
|
||||
<div class="citizen-share-skeleton" role="status" aria-busy="true" aria-live="polite">
|
||||
<div class="citizen-share-skeleton__bar citizen-share-skeleton__bar--short"></div>
|
||||
<div class="citizen-share-skeleton__bar citizen-share-skeleton__bar--wide"></div>
|
||||
<div class="citizen-share-skeleton__bar citizen-share-skeleton__bar--medium"></div>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
|
||||
|
||||
<nav
|
||||
id="p-views"
|
||||
class="citizen-menu mw-portlet mw-portlet-views"
|
||||
aria-label="Views"
|
||||
|
||||
|
||||
>
|
||||
<div class="citizen-menu__heading">
|
||||
Views
|
||||
</div>
|
||||
<div class="citizen-menu__content">
|
||||
|
||||
<ul class="citizen-menu__content-list">
|
||||
|
||||
<li id="ca-view" class="selected mw-list-item"><a href="/wiki/Events" class="citizen-cdx-button--size-large cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet"><span class="citizen-ui-icon mw-ui-icon-eye mw-ui-icon-wikimedia-eye"></span> <span>Read</span></a></li><li id="ca-ve-edit" class="citizen-ve-edit-merged mw-list-item"><a href="/wiki/Events?veaction=edit" rel="nofollow" class="citizen-cdx-button--size-large cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-primary cdx-button--action-progressive" title="Edit this page [v]" accesskey="v"><span class="citizen-ui-icon mw-ui-icon-edit mw-ui-icon-wikimedia-edit"></span> <span>Edit</span></a></li><li id="ca-edit" class="collapsible citizen-ve-edit-merged mw-list-item"><a href="/wiki/Events?action=edit" rel="nofollow" class="citizen-cdx-button--size-large cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-primary cdx-button--action-progressive" title="Edit the source code of this page [e]" accesskey="e"><span class="citizen-ui-icon mw-ui-icon-wikiText mw-ui-icon-wikimedia-wikiText"></span> <span>Edit source</span></a></li><li id="ca-history" class="mw-list-item"><a href="/wiki/Events?action=history" rel="nofollow" class="citizen-cdx-button--size-large cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet" title="Past revisions of this page [h]" accesskey="h"><span class="citizen-ui-icon mw-ui-icon-history mw-ui-icon-wikimedia-history"></span> <span>View history</span></a></li>
|
||||
</ul>
|
||||
|
||||
</div></nav>
|
||||
|
||||
<nav
|
||||
id="p-associated-pages"
|
||||
class="citizen-menu mw-portlet mw-portlet-associated-pages"
|
||||
aria-label="associated-pages"
|
||||
|
||||
|
||||
>
|
||||
<div class="citizen-menu__heading">
|
||||
associated-pages
|
||||
</div>
|
||||
<div class="citizen-menu__content">
|
||||
|
||||
<ul class="citizen-menu__content-list">
|
||||
|
||||
<li id="ca-nstab-main" class="selected mw-list-item"><a href="/wiki/Events" class="citizen-cdx-button--size-large cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet" title="View the content page [c]" accesskey="c"><span class="citizen-ui-icon mw-ui-icon-article mw-ui-icon-wikimedia-article"></span> <span>Page</span></a></li><li id="ca-talk" class="new mw-list-item"><a href="/wiki/Talk:Events?action=edit&redlink=1" rel="discussion" class="new citizen-cdx-button--size-large cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet" title="Discussion about the content page (page does not exist) [t]" accesskey="t"><span class="citizen-ui-icon mw-ui-icon-speechBubbles mw-ui-icon-wikimedia-speechBubbles"></span> <span>Discussion</span></a></li>
|
||||
</ul>
|
||||
|
||||
</div></nav>
|
||||
|
||||
<div
|
||||
id="citizen-page-more-dropdown"
|
||||
class="citizen-page-actions-more citizen-page-actions__item citizen-dropdown"
|
||||
>
|
||||
<details class="citizen-dropdown-details">
|
||||
<summary
|
||||
class="citizen-dropdown-summary citizen-cdx-button--size-large cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet cdx-button--icon-only"
|
||||
title="More actions"
|
||||
aria-details="citizen-page-actions-more__card">
|
||||
<span class="citizen-ui-icon mw-ui-icon-wikimedia-ellipsis"></span>
|
||||
<span>More actions</span>
|
||||
</summary>
|
||||
</details>
|
||||
<div id="citizen-page-actions-more__card" class="citizen-menu__card">
|
||||
<div class="citizen-menu__card-content">
|
||||
<nav
|
||||
id="p-cactions"
|
||||
class="citizen-menu mw-portlet mw-portlet-cactions"
|
||||
aria-label="More"
|
||||
title="More options"
|
||||
|
||||
>
|
||||
<div class="citizen-menu__heading">
|
||||
More
|
||||
</div>
|
||||
<div class="citizen-menu__content">
|
||||
|
||||
<ul class="citizen-menu__content-list">
|
||||
|
||||
<li id="ca-purge" class="mw-list-item"><a href="/wiki/Events?action=purge" rel="nofollow"><span class="citizen-ui-icon mw-ui-icon-reload mw-ui-icon-wikimedia-reload"></span> <span>Purge</span></a></li>
|
||||
</ul>
|
||||
|
||||
</div></nav>
|
||||
|
||||
<nav
|
||||
id="p-tb"
|
||||
class="citizen-menu mw-portlet mw-portlet-tb"
|
||||
aria-label="Tools"
|
||||
|
||||
|
||||
>
|
||||
<div class="citizen-menu__heading">
|
||||
Tools
|
||||
</div>
|
||||
<div class="citizen-menu__content">
|
||||
|
||||
<ul class="citizen-menu__content-list">
|
||||
|
||||
<li id="t-whatlinkshere" class="mw-list-item"><a href="/wiki/Special:WhatLinksHere/Events" rel="nofollow" title="A list of all wiki pages that link here [j]" accesskey="j"><span class="citizen-ui-icon mw-ui-icon-articleRedirect mw-ui-icon-wikimedia-articleRedirect"></span> <span>What links here</span></a></li><li id="t-recentchangeslinked" class="mw-list-item"><a href="/wiki/Special:RecentChangesLinked/Events" rel="nofollow" title="Recent changes in pages linked from this page [k]" accesskey="k"><span class="citizen-ui-icon mw-ui-icon-recentChanges mw-ui-icon-wikimedia-recentChanges"></span> <span>Related changes</span></a></li><li id="t-print" class="mw-list-item"><a href="javascript:print();" rel="alternate" title="Printable version of this page [p]" accesskey="p"><span class="citizen-ui-icon mw-ui-icon-printer mw-ui-icon-wikimedia-printer"></span> <span>Printable version</span></a></li><li id="t-permalink" class="mw-list-item"><a href="/wiki/Events?oldid=8471" rel="nofollow" title="Permanent link to this revision of this page"><span class="citizen-ui-icon mw-ui-icon-link mw-ui-icon-wikimedia-link"></span> <span>Permanent link</span></a></li><li id="t-info" class="mw-list-item"><a href="/wiki/Events?action=info" rel="nofollow" title="More information about this page"><span class="citizen-ui-icon mw-ui-icon-infoFilled mw-ui-icon-wikimedia-infoFilled"></span> <span>Page information</span></a></li><li id="t-urlshortener" class="mw-list-item"><a href="/wiki/Special:UrlShortener?url=https%3A%2F%2Fholodori.wiki%2Fwiki%2FEvents" rel="nofollow"><span class="citizen-ui-icon mw-ui-icon-link mw-ui-icon-wikimedia-link"></span> <span>Get shortened URL</span></a></li><li id="t-cite" class="mw-list-item"><a href="/wiki/Special:CiteThisPage?page=Events&id=8471&wpFormIdentifier=titleform" rel="nofollow" title="Information on how to cite this page"><span class="citizen-ui-icon mw-ui-icon-quotes mw-ui-icon-wikimedia-quotes"></span> <span>Cite this page</span></a></li><li id="n-bucket" class="mw-list-item"><a href="/wiki/Events?action=bucket" title="Bucket" rel="nofollow"><span>View buckets</span></a></li><li id="t-createredirect" class="mw-list-item"><a href="/wiki/Special:CreateRedirect/Events" rel="nofollow"><span>Create Redirect</span></a></li>
|
||||
</ul>
|
||||
|
||||
</div></nav>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div id="citizen-page-header-sticky-sentinel"></div>
|
||||
|
||||
<div class="citizen-body-container">
|
||||
<div id="bodyContent" class="citizen-body">
|
||||
<div id="contentSub"><div id="mw-content-subtitle"></div></div>
|
||||
|
||||
|
||||
<div id="mw-content-text" class="mw-body-content"><div class="mw-content-ltr mw-parser-output" lang="en" dir="ltr"><section id="citizen-section-0" class="citizen-section"><p>Events are in-game campaigns that allow players to earn rewards by completing tasks. Each event has a corresponding <a href="/wiki/Gacha?action=edit&redlink=1" class="new" title="Gacha (page does not exist)">Gacha</a> and story that can be unlocked with event points.
|
||||
</p>
|
||||
</section><section id="citizen-section-1" class="citizen-section"><div class="mw-heading mw-heading2 citizen-section-heading"><h2 id="Gameplay">Gameplay</h2><span class="mw-editsection">
|
||||
<a role="button" href="/wiki/Events?veaction=edit&section=1" title="Edit section: Gameplay" rel="nofollow" class="cdx-button cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet cdx-button--icon-only mw-editsection-visualeditor">
|
||||
<span class="citizen-ui-icon mw-ui-icon-edit mw-ui-icon-wikimedia-edit"></span><span>edit</span>
|
||||
</a><a role="button" href="/wiki/Events?action=edit&section=1" title="Edit section's source code: Gameplay" rel="nofollow" class="cdx-button cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet cdx-button--icon-only ">
|
||||
<span class="citizen-ui-icon mw-ui-icon-wikiText mw-ui-icon-wikimedia-wikiText"></span><span>edit source</span>
|
||||
</a>
|
||||
</span></div>
|
||||
<p><small><i>For more information, see <a href="/wiki/Point_Rally_Events?action=edit&redlink=1" class="new" title="Point Rally Events (page does not exist)">Point Rally Events</a> and <a href="/wiki/Score_Challenge_Events?action=edit&redlink=1" class="new" title="Score Challenge Events (page does not exist)">Score Challenge Events</a>.</i></small><br/>
|
||||
In Events, players may complete <a href="/wiki/Lives?action=edit&redlink=1" class="new" title="Lives (page does not exist)">Lives</a> or <a href="/wiki/Minigames?action=edit&redlink=1" class="new" title="Minigames (page does not exist)">Minigames</a> to obtain limited-time Event Points that determine ranking and event badges that may be exchanged for items. The High Score Ranking is based on the scores players achieve on the Event Set Pieces, which are a selection of songs released alongside the event. Each Set Piece has a separate High Score Ranking, and each ranking rewards its own decorative badge.
|
||||
</p>
|
||||
<ul><li><b>Point Rally Events</b>: Aim for a high score by challenging the Event Set Pieces and earn Event Pts, which can unlock various rewards and a limited-time <a href="/wiki/Event_Story?action=edit&redlink=1" class="new" title="Event Story (page does not exist)">Event Story</a>. Players are ranked by total Event Pts and high score for each Set Piece.</li>
|
||||
<li><b>Score Challenge Events</b>: Aim for a high score by challenging the Event Set Pieces and place in the Total High Score Ranking. Players are also ranked by high score for each Set Piece.</li>
|
||||
<li><b>Mission Events</b>: Complete various missions to unlock rewards like <a href="/wiki/Lesson_Pts?action=edit&redlink=1" class="new" title="Lesson Pts (page does not exist)">Lesson Pts</a>, <a href="/wiki/Hologold?action=edit&redlink=1" class="new" title="Hologold (page does not exist)">Hologold</a>, etc.</li></ul>
|
||||
</section><section id="citizen-section-2" class="citizen-section"><div class="mw-heading mw-heading2 citizen-section-heading"><h2 id="Current_Events">Current Events</h2><span class="mw-editsection">
|
||||
<a role="button" href="/wiki/Events?veaction=edit&section=2" title="Edit section: Current Events" rel="nofollow" class="cdx-button cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet cdx-button--icon-only mw-editsection-visualeditor">
|
||||
<span class="citizen-ui-icon mw-ui-icon-edit mw-ui-icon-wikimedia-edit"></span><span>edit</span>
|
||||
</a><a role="button" href="/wiki/Events?action=edit&section=2" title="Edit section's source code: Current Events" rel="nofollow" class="cdx-button cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet cdx-button--icon-only ">
|
||||
<span class="citizen-ui-icon mw-ui-icon-wikiText mw-ui-icon-wikimedia-wikiText"></span><span>edit source</span>
|
||||
</a>
|
||||
</span></div>
|
||||
<div>
|
||||
<table class="wikitable" style="width:100%; text-align:center;">
|
||||
<tbody><tr>
|
||||
<th style="width:10%; text-align:center;">Logo
|
||||
</th>
|
||||
<th style="width:20%; text-align:center;">Event
|
||||
</th>
|
||||
<th style="width:15%; text-align:center;">Type
|
||||
</th>
|
||||
<th style="width:10%; text-align:center;">Start Date
|
||||
</th>
|
||||
<th style="width:10%; text-align:center;">End Date
|
||||
</th>
|
||||
<th style="width:30%; text-align:center;">Featured Members
|
||||
</th></tr>
|
||||
<tr>
|
||||
<td><center><span typeof="mw:File"><span><img src="//static.wikitide.net/holodoriwiki/thumb/6/60/Ultimate_Summer_For_Me_Logo.png/175px-Ultimate_Summer_For_Me_Logo.png" decoding="async" loading="lazy" width="175" height="109" class="mw-file-element" srcset="//static.wikitide.net/holodoriwiki/thumb/6/60/Ultimate_Summer_For_Me_Logo.png/263px-Ultimate_Summer_For_Me_Logo.png 1.5x, //static.wikitide.net/holodoriwiki/thumb/6/60/Ultimate_Summer_For_Me_Logo.png/350px-Ultimate_Summer_For_Me_Logo.png 2x" data-file-width="1024" data-file-height="640"/></span></span></center>
|
||||
</td>
|
||||
<td><a href="/wiki/Ultimate_Summer_For_Me%3F?action=edit&redlink=1" class="new" title="Ultimate Summer For Me? (page does not exist)">Ultimate Summer For Me?</a>
|
||||
</td>
|
||||
<td>Spotlight
|
||||
</td>
|
||||
<td>08/17/2026 8:00PM (JST)
|
||||
</td>
|
||||
<td>08/27/2026 7:59PM (JST)
|
||||
</td>
|
||||
<td><span typeof="mw:File"><a href="/wiki/Mori_Calliope" title="Mori Calliope"><img src="//static.wikitide.net/holodoriwiki/thumb/8/8b/Mori_Calliope_Chibi.png/45px-Mori_Calliope_Chibi.png" decoding="async" loading="lazy" width="45" height="45" class="mw-file-element" srcset="//static.wikitide.net/holodoriwiki/thumb/8/8b/Mori_Calliope_Chibi.png/68px-Mori_Calliope_Chibi.png 1.5x, //static.wikitide.net/holodoriwiki/thumb/8/8b/Mori_Calliope_Chibi.png/90px-Mori_Calliope_Chibi.png 2x" data-file-width="256" data-file-height="256"/></a></span><span typeof="mw:File"><a href="/wiki/Nakiri_Ayame" title="Nakiri Ayame"><img src="//static.wikitide.net/holodoriwiki/thumb/1/16/Nakiri_Ayame_Chibi.png/45px-Nakiri_Ayame_Chibi.png" decoding="async" loading="lazy" width="45" height="45" class="mw-file-element" srcset="//static.wikitide.net/holodoriwiki/thumb/1/16/Nakiri_Ayame_Chibi.png/68px-Nakiri_Ayame_Chibi.png 1.5x, //static.wikitide.net/holodoriwiki/thumb/1/16/Nakiri_Ayame_Chibi.png/90px-Nakiri_Ayame_Chibi.png 2x" data-file-width="256" data-file-height="256"/></a></span><span typeof="mw:File"><a href="/wiki/Kureiji_Ollie" title="Kureiji Ollie"><img src="//static.wikitide.net/holodoriwiki/thumb/e/e5/Kureiji_Ollie_Chibi.png/45px-Kureiji_Ollie_Chibi.png" decoding="async" loading="lazy" width="45" height="45" class="mw-file-element" srcset="//static.wikitide.net/holodoriwiki/thumb/e/e5/Kureiji_Ollie_Chibi.png/68px-Kureiji_Ollie_Chibi.png 1.5x, //static.wikitide.net/holodoriwiki/thumb/e/e5/Kureiji_Ollie_Chibi.png/90px-Kureiji_Ollie_Chibi.png 2x" data-file-width="256" data-file-height="256"/></a></span><span typeof="mw:File"><a href="/wiki/Himemori_Luna" title="Himemori Luna"><img src="//static.wikitide.net/holodoriwiki/thumb/3/31/Himemori_Luna_Chibi.png/45px-Himemori_Luna_Chibi.png" decoding="async" loading="lazy" width="45" height="45" class="mw-file-element" srcset="//static.wikitide.net/holodoriwiki/thumb/3/31/Himemori_Luna_Chibi.png/68px-Himemori_Luna_Chibi.png 1.5x, //static.wikitide.net/holodoriwiki/thumb/3/31/Himemori_Luna_Chibi.png/90px-Himemori_Luna_Chibi.png 2x" data-file-width="256" data-file-height="256"/></a></span><span typeof="mw:File"><a href="/wiki/Ninomae_Ina%27nis" title="Ninomae Ina'nis"><img src="//static.wikitide.net/holodoriwiki/thumb/2/2a/Ninomae_Ina%27nis_Chibi.png/45px-Ninomae_Ina%27nis_Chibi.png" decoding="async" loading="lazy" width="45" height="45" class="mw-file-element" srcset="//static.wikitide.net/holodoriwiki/thumb/2/2a/Ninomae_Ina%27nis_Chibi.png/68px-Ninomae_Ina%27nis_Chibi.png 1.5x, //static.wikitide.net/holodoriwiki/thumb/2/2a/Ninomae_Ina%27nis_Chibi.png/90px-Ninomae_Ina%27nis_Chibi.png 2x" data-file-width="256" data-file-height="256"/></a></span>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td><center><span typeof="mw:File"><span><img src="//static.wikitide.net/holodoriwiki/thumb/4/48/Training_Support_Missions_Logo.png/200px-Training_Support_Missions_Logo.png" decoding="async" loading="lazy" width="200" height="137" class="mw-file-element" srcset="//static.wikitide.net/holodoriwiki/thumb/4/48/Training_Support_Missions_Logo.png/300px-Training_Support_Missions_Logo.png 1.5x, //static.wikitide.net/holodoriwiki/thumb/4/48/Training_Support_Missions_Logo.png/400px-Training_Support_Missions_Logo.png 2x" data-file-width="3180" data-file-height="2184"/></span></span></center>
|
||||
</td>
|
||||
<td><a href="/wiki/Training_Support_Missions?action=edit&redlink=1" class="new" title="Training Support Missions (page does not exist)">Training Support Missions</a>
|
||||
</td>
|
||||
<td>Mission
|
||||
</td>
|
||||
<td>08/07/2026 11:00AM (JST)
|
||||
</td>
|
||||
<td>08/30/2026 3:59AM (JST)
|
||||
</td>
|
||||
<td>N/A
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td><center><span typeof="mw:File"><span><img src="//static.wikitide.net/holodoriwiki/thumb/3/37/Beginner_Mission_Logo.png/200px-Beginner_Mission_Logo.png" decoding="async" loading="lazy" width="200" height="137" class="mw-file-element" srcset="//static.wikitide.net/holodoriwiki/thumb/3/37/Beginner_Mission_Logo.png/300px-Beginner_Mission_Logo.png 1.5x, //static.wikitide.net/holodoriwiki/thumb/3/37/Beginner_Mission_Logo.png/400px-Beginner_Mission_Logo.png 2x" data-file-width="3180" data-file-height="2184"/></span></span></center>
|
||||
</td>
|
||||
<td><a href="/wiki/Beginner_Mission?action=edit&redlink=1" class="new" title="Beginner Mission (page does not exist)">Beginner Mission</a>
|
||||
</td>
|
||||
<td>Mission
|
||||
</td>
|
||||
<td>Game Launch
|
||||
</td>
|
||||
<td>Unknown
|
||||
</td>
|
||||
<td>N/A
|
||||
</td></tr></tbody></table>
|
||||
</div>
|
||||
</section><section id="citizen-section-3" class="citizen-section"><div class="mw-heading mw-heading2 citizen-section-heading"><h2 id="Past_Events">Past Events</h2><span class="mw-editsection">
|
||||
<a role="button" href="/wiki/Events?veaction=edit&section=3" title="Edit section: Past Events" rel="nofollow" class="cdx-button cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet cdx-button--icon-only mw-editsection-visualeditor">
|
||||
<span class="citizen-ui-icon mw-ui-icon-edit mw-ui-icon-wikimedia-edit"></span><span>edit</span>
|
||||
</a><a role="button" href="/wiki/Events?action=edit&section=3" title="Edit section's source code: Past Events" rel="nofollow" class="cdx-button cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet cdx-button--icon-only ">
|
||||
<span class="citizen-ui-icon mw-ui-icon-wikiText mw-ui-icon-wikimedia-wikiText"></span><span>edit source</span>
|
||||
</a>
|
||||
</span></div>
|
||||
<table class="wikitable" style="width:100%; text-align:center;">
|
||||
<tbody><tr>
|
||||
<th style="width:10%; text-align:center;">Logo
|
||||
</th>
|
||||
<th style="width:20%; text-align:center;">Event
|
||||
</th>
|
||||
<th style="width:15%; text-align:center;">Type
|
||||
</th>
|
||||
<th style="width:10%; text-align:center;">Start Date
|
||||
</th>
|
||||
<th style="width:10%; text-align:center;">End Date
|
||||
</th>
|
||||
<th style="width:30%; text-align:center;">Featured Members
|
||||
</th></tr>
|
||||
<tr>
|
||||
<td><center><span typeof="mw:File"><span><img src="//static.wikitide.net/holodoriwiki/thumb/5/5b/Synced_Summer_Sparkles_Logo.png/175px-Synced_Summer_Sparkles_Logo.png" decoding="async" loading="lazy" width="175" height="109" class="mw-file-element" srcset="//static.wikitide.net/holodoriwiki/thumb/5/5b/Synced_Summer_Sparkles_Logo.png/263px-Synced_Summer_Sparkles_Logo.png 1.5x, //static.wikitide.net/holodoriwiki/thumb/5/5b/Synced_Summer_Sparkles_Logo.png/350px-Synced_Summer_Sparkles_Logo.png 2x" data-file-width="1024" data-file-height="640"/></span></span></center>
|
||||
</td>
|
||||
<td><a href="/wiki/Synced_Summer_Sparkles" title="Synced Summer Sparkles">Synced Summer Sparkles</a>
|
||||
</td>
|
||||
<td>Score Challenge
|
||||
</td>
|
||||
<td>08/07/2026 12:00PM (JST)
|
||||
</td>
|
||||
<td>08/15/2026 7:59PM (JST)
|
||||
</td>
|
||||
<td><span typeof="mw:File"><a href="/wiki/Hoshimachi_Suisei" title="Hoshimachi Suisei"><img src="//static.wikitide.net/holodoriwiki/thumb/d/df/Hoshimachi_Suisei_Chibi.png/45px-Hoshimachi_Suisei_Chibi.png" decoding="async" loading="lazy" width="45" height="45" class="mw-file-element" srcset="//static.wikitide.net/holodoriwiki/thumb/d/df/Hoshimachi_Suisei_Chibi.png/68px-Hoshimachi_Suisei_Chibi.png 1.5x, //static.wikitide.net/holodoriwiki/thumb/d/df/Hoshimachi_Suisei_Chibi.png/90px-Hoshimachi_Suisei_Chibi.png 2x" data-file-width="256" data-file-height="256"/></a></span><span typeof="mw:File"><a href="/wiki/Sakura_Miko" title="Sakura Miko"><img src="//static.wikitide.net/holodoriwiki/thumb/7/75/Sakura_Miko_Chibi.png/45px-Sakura_Miko_Chibi.png" decoding="async" loading="lazy" width="45" height="45" class="mw-file-element" srcset="//static.wikitide.net/holodoriwiki/thumb/7/75/Sakura_Miko_Chibi.png/68px-Sakura_Miko_Chibi.png 1.5x, //static.wikitide.net/holodoriwiki/thumb/7/75/Sakura_Miko_Chibi.png/90px-Sakura_Miko_Chibi.png 2x" data-file-width="256" data-file-height="256"/></a></span>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td><center><span typeof="mw:File"><span><img src="//static.wikitide.net/holodoriwiki/thumb/8/8d/Hello%21_A_Brand_New_Summer%21_Logo.png/175px-Hello%21_A_Brand_New_Summer%21_Logo.png" decoding="async" loading="lazy" width="175" height="109" class="mw-file-element" srcset="//static.wikitide.net/holodoriwiki/thumb/8/8d/Hello%21_A_Brand_New_Summer%21_Logo.png/263px-Hello%21_A_Brand_New_Summer%21_Logo.png 1.5x, //static.wikitide.net/holodoriwiki/thumb/8/8d/Hello%21_A_Brand_New_Summer%21_Logo.png/350px-Hello%21_A_Brand_New_Summer%21_Logo.png 2x" data-file-width="1024" data-file-height="640"/></span></span></center>
|
||||
</td>
|
||||
<td><a href="/wiki/Hello!_A_Brand_New_Summer!" title="Hello! A Brand New Summer!">Hello! A Brand New Summer!</a>
|
||||
</td>
|
||||
<td>Point Rally
|
||||
</td>
|
||||
<td>07/28/2026 12:00PM (JST)
|
||||
</td>
|
||||
<td>08/05/2026 7:59PM (JST)
|
||||
</td>
|
||||
<td><span typeof="mw:File"><a href="/wiki/Oozora_Subaru" title="Oozora Subaru"><img src="//static.wikitide.net/holodoriwiki/thumb/d/d6/Oozora_Subaru_Chibi.png/45px-Oozora_Subaru_Chibi.png" decoding="async" loading="lazy" width="45" height="45" class="mw-file-element" srcset="//static.wikitide.net/holodoriwiki/thumb/d/d6/Oozora_Subaru_Chibi.png/68px-Oozora_Subaru_Chibi.png 1.5x, //static.wikitide.net/holodoriwiki/thumb/d/d6/Oozora_Subaru_Chibi.png/90px-Oozora_Subaru_Chibi.png 2x" data-file-width="256" data-file-height="256"/></a></span><span typeof="mw:File"><a href="/wiki/Shiranui_Flare" title="Shiranui Flare"><img src="//static.wikitide.net/holodoriwiki/thumb/4/45/Shiranui_Flare_Chibi.png/45px-Shiranui_Flare_Chibi.png" decoding="async" loading="lazy" width="45" height="45" class="mw-file-element" srcset="//static.wikitide.net/holodoriwiki/thumb/4/45/Shiranui_Flare_Chibi.png/68px-Shiranui_Flare_Chibi.png 1.5x, //static.wikitide.net/holodoriwiki/thumb/4/45/Shiranui_Flare_Chibi.png/90px-Shiranui_Flare_Chibi.png 2x" data-file-width="256" data-file-height="256"/></a></span><span typeof="mw:File"><a href="/wiki/Shirogane_Noel" title="Shirogane Noel"><img src="//static.wikitide.net/holodoriwiki/thumb/0/0d/Shirogane_Noel_Chibi.png/45px-Shirogane_Noel_Chibi.png" decoding="async" loading="lazy" width="45" height="45" class="mw-file-element" srcset="//static.wikitide.net/holodoriwiki/thumb/0/0d/Shirogane_Noel_Chibi.png/68px-Shirogane_Noel_Chibi.png 1.5x, //static.wikitide.net/holodoriwiki/thumb/0/0d/Shirogane_Noel_Chibi.png/90px-Shirogane_Noel_Chibi.png 2x" data-file-width="256" data-file-height="256"/></a></span><span typeof="mw:File"><a href="/wiki/Tsunomaki_Watame" title="Tsunomaki Watame"><img src="//static.wikitide.net/holodoriwiki/thumb/1/13/Tsunomaki_Watame_Chibi.png/45px-Tsunomaki_Watame_Chibi.png" decoding="async" loading="lazy" width="45" height="45" class="mw-file-element" srcset="//static.wikitide.net/holodoriwiki/thumb/1/13/Tsunomaki_Watame_Chibi.png/68px-Tsunomaki_Watame_Chibi.png 1.5x, //static.wikitide.net/holodoriwiki/thumb/1/13/Tsunomaki_Watame_Chibi.png/90px-Tsunomaki_Watame_Chibi.png 2x" data-file-width="256" data-file-height="256"/></a></span><span typeof="mw:File"><a href="/wiki/Otonose_Kanade" title="Otonose Kanade"><img src="//static.wikitide.net/holodoriwiki/thumb/0/0e/Otonose_Kanade_Chibi.png/45px-Otonose_Kanade_Chibi.png" decoding="async" loading="lazy" width="45" height="45" class="mw-file-element" srcset="//static.wikitide.net/holodoriwiki/thumb/0/0e/Otonose_Kanade_Chibi.png/68px-Otonose_Kanade_Chibi.png 1.5x, //static.wikitide.net/holodoriwiki/thumb/0/0e/Otonose_Kanade_Chibi.png/90px-Otonose_Kanade_Chibi.png 2x" data-file-width="256" data-file-height="256"/></a></span>
|
||||
</td></tr>
|
||||
</tbody></table>
|
||||
<!--
|
||||
NewPP limit report
|
||||
Parsed by mw163
|
||||
Cached time: 20260817025926
|
||||
Cache expiry: 1296000
|
||||
Reduced expiry: false
|
||||
Complications: []
|
||||
CPU time usage: 0.080 seconds
|
||||
Real time usage: 0.102 seconds
|
||||
Preprocessor visited node count: 127/1000000
|
||||
Revision size: 3342/2097152 bytes
|
||||
Post‐expand include size: 1380/2097152 bytes
|
||||
Template argument size: 0/2097152 bytes
|
||||
Highest expansion depth: 3/100
|
||||
Expensive parser function count: 0/99
|
||||
Unstrip recursion depth: 0/20
|
||||
Unstrip post‐expand size: 0/5000000 bytes
|
||||
Lua time usage: 0.019/10.000 seconds
|
||||
Lua memory usage: 991827/52428800 bytes
|
||||
Bucket time usage: 0.000/10 seconds
|
||||
-->
|
||||
<!--
|
||||
Transclusion expansion time report (%,ms,calls,template)
|
||||
100.00% 47.208 1 -total
|
||||
97.80% 46.170 12 Template:TalentIcon
|
||||
-->
|
||||
|
||||
<!-- Saved in parser cache with key holodoriwiki:pcache:1149:|#|:idhash:canonical and timestamp 20260817025926 and revision id 8471. Rendering was triggered because: page_view
|
||||
-->
|
||||
</section></div><noscript><img src="https://holodori.wiki/wiki/Special:CentralAutoLogin/start?useformat=desktop&type=1x1&usesul3=1" alt="" width="1" height="1" style="border: none; position: absolute;"></noscript>
|
||||
<div class="printfooter" data-nosnippet="">Retrieved from "<a dir="ltr" href="https://holodori.wiki/wiki/Events?oldid=8471">https://holodori.wiki/wiki/Events?oldid=8471</a>"</div></div>
|
||||
</div>
|
||||
|
||||
<div class="citizen-page-sidebar">
|
||||
<nav
|
||||
id="citizen-sidebar-lastmod"
|
||||
class="citizen-menu"
|
||||
aria-label="Last modified"
|
||||
|
||||
|
||||
>
|
||||
<div class="citizen-menu__heading">
|
||||
Last modified
|
||||
</div>
|
||||
<div class="citizen-menu__content">
|
||||
|
||||
<ul class="citizen-menu__content-list">
|
||||
<li id="lm-time" class="mw-list-item"><a data-mw="interface" id="citizen-lastmod-relative" href="/wiki/Events?diff=" title="This page was last edited on 17 August 2026, at 02:59." data-timestamp="1786935545"><span class="citizen-ui-icon mw-ui-icon-history mw-ui-icon-wikimedia-history"></span><span>17 August 2026</span></a></li>
|
||||
|
||||
</ul>
|
||||
|
||||
</div></nav>
|
||||
|
||||
<div id="citizen-toc" class="citizen-toc citizen-dropdown">
|
||||
<details class="citizen-dropdown-details">
|
||||
<summary
|
||||
class="citizen-dropdown-summary"
|
||||
title="Contents"
|
||||
aria-details="mw-panel-toc">
|
||||
<span class="citizen-ui-icon mw-ui-icon-wikimedia-listBullet"></span>
|
||||
<span>Contents</span>
|
||||
</summary>
|
||||
</details>
|
||||
<nav id="mw-panel-toc" class="citizen-toc-card citizen-menu__card" aria-labelledby="mw-panel-toc-label">
|
||||
<div class="citizen-menu__card-content">
|
||||
<a class="citizen-toc-top citizen-toc-link cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet" title="Back to top" href="#top" role="button">
|
||||
<div class="citizen-ui-icon mw-ui-icon-wikimedia-arrowUp"></div>
|
||||
<div class="citizen-toc-text">Back to top</div>
|
||||
</a>
|
||||
<div id="mw-panel-toc-label" class="citizen-menu__heading">Contents</div>
|
||||
<div class="citizen-toc-contents" id="mw-panel-toc-list">
|
||||
<div class="citizen-toc-indicator"></div>
|
||||
<ul class="citizen-toc-list">
|
||||
<li id="toc-Gameplay"
|
||||
class="citizen-toc-list-item citizen-toc-level-1">
|
||||
<a class="citizen-toc-link cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet" href="#Gameplay" role="button">
|
||||
<div class="citizen-toc-content">
|
||||
<div class="citizen-toc-text">
|
||||
<span class="citizen-toc-numb">1</span>
|
||||
<span class="citizen-toc-heading">Gameplay</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<ul id="toc-Gameplay-sublist" class="citizen-toc-list">
|
||||
</ul>
|
||||
</li>
|
||||
<li id="toc-Current_Events"
|
||||
class="citizen-toc-list-item citizen-toc-level-1">
|
||||
<a class="citizen-toc-link cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet" href="#Current_Events" role="button">
|
||||
<div class="citizen-toc-content">
|
||||
<div class="citizen-toc-text">
|
||||
<span class="citizen-toc-numb">2</span>
|
||||
<span class="citizen-toc-heading">Current Events</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<ul id="toc-Current_Events-sublist" class="citizen-toc-list">
|
||||
</ul>
|
||||
</li>
|
||||
<li id="toc-Past_Events"
|
||||
class="citizen-toc-list-item citizen-toc-level-1">
|
||||
<a class="citizen-toc-link cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet" href="#Past_Events" role="button">
|
||||
<div class="citizen-toc-content">
|
||||
<div class="citizen-toc-text">
|
||||
<span class="citizen-toc-numb">3</span>
|
||||
<span class="citizen-toc-heading">Past Events</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<ul id="toc-Past_Events-sublist" class="citizen-toc-list">
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div> </div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="citizen-page-footer">
|
||||
<div id="catlinks" class="catlinks catlinks-allhidden" data-mw="interface"></div>
|
||||
<div class="page-info">
|
||||
<section id="footer-info-lastmod" class="page-info__item">
|
||||
<div class="page-info__label">Last modified</div>
|
||||
<div class="page-info__text"> This page was last edited on 17 August 2026, at 02:59.</div>
|
||||
</section>
|
||||
<section id="footer-info-copyright" class="page-info__item">
|
||||
<div class="page-info__label">Copyright</div>
|
||||
<div class="page-info__text">Content is available under Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) unless otherwise noted.</div>
|
||||
</section>
|
||||
</div>
|
||||
</footer> </div>
|
||||
|
||||
</main>
|
||||
<div id='mw-data-after-content'>
|
||||
<div class="mw-cookiewarning-container"><div class="mw-cookiewarning-text"><span>Cookies help us deliver our services. By using our services, you agree to our use of cookies.</span></div><form method="POST"><div class='oo-ui-layout oo-ui-horizontalLayout'><span class='oo-ui-widget oo-ui-widget-enabled oo-ui-buttonElement oo-ui-buttonElement-framed oo-ui-labelElement oo-ui-flaggedElement-progressive oo-ui-buttonWidget'><a role='button' tabindex='0' href='https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies' rel='nofollow' class='oo-ui-buttonElement-button'><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon oo-ui-image-progressive'></span><span class='oo-ui-labelElement-label'>More information</span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator oo-ui-image-progressive'></span></a></span><span class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-buttonElement oo-ui-buttonElement-framed oo-ui-labelElement oo-ui-flaggedElement-primary oo-ui-flaggedElement-progressive oo-ui-buttonInputWidget'><button type='submit' tabindex='0' name='disablecookiewarning' value='OK' class='oo-ui-inputWidget-input oo-ui-buttonElement-button'><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon oo-ui-image-invert'></span><span class='oo-ui-labelElement-label'>OK</span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator oo-ui-image-invert'></span></button></span></div></form></div><div class="read-more-container"></div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<footer class="mw-footer citizen-footer" >
|
||||
<div class="citizen-footer__container">
|
||||
<section class="citizen-footer__content">
|
||||
<div class="citizen-footer__siteinfo">
|
||||
<div id="footer-sitetitle" class="citizen-footer__sitetitle mw-wiki-title">
|
||||
<img class="mw-logo-icon" src="https://static.wikitide.net/holodoriwiki/c/c4/HolodoriWikiLogo.png
|
||||
" alt="" aria-hidden="true" loading="lazy" height="48" width="48">
|
||||
<div class="mw-logo-wordmark">hololive Dreams Wiki</div>
|
||||
</div>
|
||||
<p id="footer-desc" class="citizen-footer__desc">holodori Wiki is an unofficial wiki documenting the content of <i>hololive Dreams</i>. We aim to provide all sorts of information about any holodori content, both in-game content such as songs and gacha cards, or external content such as talent information.</p>
|
||||
</div>
|
||||
<nav id="footer-places" >
|
||||
<ul>
|
||||
<li id="footer-places-privacy"><a href="https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy">Privacy policy</a></li>
|
||||
<li id="footer-places-about"><a href="/wiki/Project:About">About hololive Dreams Wiki</a></li>
|
||||
<li id="footer-places-disclaimers"><a href="/wiki/Project:General_disclaimer">Disclaimers</a></li>
|
||||
<li id="footer-places-termsofservice"><a href="https://meta.miraheze.org/wiki/Special:MyLanguage/Terms_of_Use">Terms of Use</a></li>
|
||||
<li id="footer-places-donate"><a href="https://meta.miraheze.org/wiki/Special:MyLanguage/Donate">Donate to Miraheze</a></li>
|
||||
<li id="footer-places-mobileview"><a href="https://holodori.wiki/wiki/Events?mobileaction=toggle_view_mobile" class="noprint stopMobileRedirectToggle">Mobile view</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</section>
|
||||
<section class="citizen-footer__bottom">
|
||||
<div id="footer-tagline">The official hololive rhythm game and RPG.</div>
|
||||
<nav id="footer-icons" class="noprint">
|
||||
<ul>
|
||||
<li id="footer-mirahezeico"><a href="https://meta.miraheze.org/wiki/Special:MyLanguage/Miraheze_Meta" class="cdx-button cdx-button--fake-button cdx-button--size-large cdx-button--fake-button--enabled"><img src="https://static.wikitide.net/commonswiki/f/fe/Powered_by_Miraheze_(no_box).svg" alt="Hosted by Miraheze" width="88" height="31" loading="lazy"></a></li>
|
||||
<li id="footer-copyrightico"><a href="https://creativecommons.org/licenses/by-sa/4.0/" class="cdx-button cdx-button--fake-button cdx-button--size-large cdx-button--fake-button--enabled"><img src="https://meta.miraheze.org/1.45/resources/assets/licenses/cc-by-sa.png" alt="Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)" width="88" height="31" loading="lazy"></a></li>
|
||||
<li id="footer-poweredbyico"><a href="https://www.mediawiki.org/" class="cdx-button cdx-button--fake-button cdx-button--size-large cdx-button--fake-button--enabled"><picture><source media="(min-width: 500px)" srcset="/1.45/resources/assets/poweredby_mediawiki.svg" width="88" height="31"><img src="/1.45/resources/assets/mediawiki_compact.svg" alt="Powered by MediaWiki" lang="en" width="25" height="25" loading="lazy"></picture></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</section>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
<div class="citizen-sticky-header-container">
|
||||
<div id="citizen-sticky-header" class="citizen-sticky-header">
|
||||
<div class="citizen-sticky-header-background"></div>
|
||||
<div class="citizen-sticky-header-inner">
|
||||
<div class="citizen-sticky-header-start">
|
||||
<a
|
||||
href="#top"
|
||||
title="Back to top"
|
||||
class="citizen-sticky-header-backtotop cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--size-large cdx-button--weight-quiet"
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="citizen-ui-icon mw-ui-icon-wikimedia-arrowUp"></div>
|
||||
<div class="citizen-sticky-header-page-info">
|
||||
<div class="citizen-sticky-header-page-title"><span class="mw-page-title-main">Events</span></div>
|
||||
<div class="citizen-sticky-header-page-tagline">From hololive Dreams Wiki</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="citizen-sticky-header-end" aria-hidden="true">
|
||||
<button class="cdx-button cdx-button--weight-quiet cdx-button--action-default cdx-button--size-large cdx-button--icon-only" id="citizen-share-sticky-header" tabindex="-1" data-mw-citizen-click-target="#citizen-share"><span class="citizen-ui-icon mw-ui-icon-wikimedia-share mw-ui-icon-wikimedia-wikimedia-share"></span><span></span>
|
||||
</button> <button class="cdx-button cdx-button--weight-quiet cdx-button--action-default cdx-button--size-large cdx-button--icon-only" id="ca-view-sticky-header" tabindex="-1" data-mw-citizen-click-target="#ca-view > a"><span class="citizen-ui-icon mw-ui-icon-eye mw-ui-icon-wikimedia-eye"></span><span></span>
|
||||
</button> <button class="cdx-button cdx-button--weight-quiet cdx-button--action-default cdx-button--size-large cdx-button--icon-only" id="ca-subject-sticky-header" tabindex="-1" data-mw-citizen-click-target="[id^='ca-nstab-']:not(.selected) > a"><span class="citizen-ui-icon mw-ui-icon-article mw-ui-icon-wikimedia-article"></span><span></span>
|
||||
</button> <button class="cdx-button cdx-button--weight-quiet cdx-button--action-default cdx-button--size-large cdx-button--icon-only" id="ca-history-sticky-header" tabindex="-1" data-mw-citizen-click-target="#ca-history > a"><span class="citizen-ui-icon mw-ui-icon-wikimedia-history mw-ui-icon-wikimedia-wikimedia-history"></span><span></span>
|
||||
</button> <button class="cdx-button cdx-button--weight-quiet cdx-button--action-default cdx-button--size-large cdx-button--icon-only" id="ca-ve-edit-sticky-header" tabindex="-1" data-mw-citizen-click-target="#ca-ve-edit > a"><span class="citizen-ui-icon mw-ui-icon-wikimedia-edit mw-ui-icon-wikimedia-wikimedia-edit"></span><span></span>
|
||||
</button> <button class="cdx-button cdx-button--weight-quiet cdx-button--action-default cdx-button--size-large cdx-button--icon-only" id="ca-edit-sticky-header" tabindex="-1" data-mw-citizen-click-target="#ca-edit > a"><span class="citizen-ui-icon mw-ui-icon-wikimedia-wikiText mw-ui-icon-wikimedia-wikimedia-wikiText"></span><span></span>
|
||||
</button> <button class="cdx-button cdx-button--weight-quiet cdx-button--action-default cdx-button--size-large cdx-button--icon-only" id="ca-viewsource-sticky-header" tabindex="-1" data-mw-citizen-click-target="#ca-viewsource > a"><span class="citizen-ui-icon mw-ui-icon-wikimedia-editLock mw-ui-icon-wikimedia-wikimedia-editLock"></span><span></span>
|
||||
</button> <button class="cdx-button cdx-button--weight-quiet cdx-button--action-default cdx-button--size-large cdx-button--icon-only" id="ca-addsection-sticky-header" tabindex="-1" data-mw-citizen-click-target="#ca-addsection > a"><span class="citizen-ui-icon mw-ui-icon-speechBubbleAdd mw-ui-icon-wikimedia-speechBubbleAdd"></span><span></span>
|
||||
</button> <button class="cdx-button cdx-button--weight-quiet cdx-button--action-default cdx-button--size-large cdx-button--icon-only" id="ca-talk-sticky-header" tabindex="-1" data-mw-citizen-click-target="#ca-talk > a"><span class="citizen-ui-icon mw-ui-icon-speechBubbles mw-ui-icon-wikimedia-speechBubbles"></span><span></span>
|
||||
</button> <div id="citizen-sticky-header-languages" class="citizen-sticky-header-dropdown-container"></div>
|
||||
<div id="citizen-sticky-header-more" class="citizen-sticky-header-dropdown-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>(RLQ=window.RLQ||[]).push(function(){mw.config.set({"wgHostname":"mw151","wgBackendResponseTime":183,"wgPageParseReport":{"limitreport":{"cputime":"0.080","walltime":"0.102","ppvisitednodes":{"value":127,"limit":1000000},"revisionsize":{"value":3342,"limit":2097152},"postexpandincludesize":{"value":1380,"limit":2097152},"templateargumentsize":{"value":0,"limit":2097152},"expansiondepth":{"value":3,"limit":100},"expensivefunctioncount":{"value":0,"limit":99},"unstrip-depth":{"value":0,"limit":20},"unstrip-size":{"value":0,"limit":5000000},"timingprofile":["100.00% 47.208 1 -total"," 97.80% 46.170 12 Template:TalentIcon"]},"scribunto":{"limitreport-timeusage":{"value":"0.019","limit":"10.000"},"limitreport-memusage":{"value":991827,"limit":52428800}},"bucket":{"limitreport-run-time":{"value":"0.000","limit":"10"}},"cachereport":{"origin":"mw163","timestamp":"20260817025926","ttl":1296000,"transientcontent":false}}});});</script>
|
||||
<script>
|
||||
var _paq = window._paq = window._paq || [];
|
||||
if ( 1 ) {
|
||||
_paq.push(['disableCookies']);
|
||||
}
|
||||
if ( 0 ) {
|
||||
_paq.push(['setRequestMethod', 'GET']);
|
||||
}
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function() {
|
||||
var u = "https://analytics.wikitide.net/";
|
||||
_paq.push(['setTrackerUrl', u+'matomo.php']);
|
||||
_paq.push(['setDocumentTitle', "holodoriwiki" + " - " + "Events"]);
|
||||
_paq.push(['setSiteId', 30939]);
|
||||
if ( 1 ) {
|
||||
_paq.push(['setCustomDimension', 1, "Anonymous"]);
|
||||
}
|
||||
if ( 1 ) {
|
||||
_paq.push(['addTracker', u + 'matomo.php', 1]);
|
||||
}
|
||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
</script>
|
||||
<noscript><p><img src="https://analytics.wikitide.net/matomo.php?idsite=30939&rec=1&action_name=Events" style="border: 0;" alt="" /></p></noscript>
|
||||
</body>
|
||||
</html>
|
||||
@@ -112,6 +112,15 @@ const SOURCES: SourceSpec[] = [
|
||||
url: "https://fategrandorder.fandom.com/api.php?action=parse&page=Event_List&prop=text&formatversion=2&format=json",
|
||||
parserId: "fandom",
|
||||
},
|
||||
{
|
||||
id: "holodori-holodoriwiki-events",
|
||||
game: "holodori",
|
||||
// The rendered page, the same call as `ba-bawiki-events` above and for the
|
||||
// same reason: holodori.wiki is Miraheze, so `/w/` and `?action=` are the
|
||||
// closed routes here and `/wiki/` is the one `*` is allowed.
|
||||
url: "https://holodori.wiki/wiki/Events",
|
||||
parserId: "holodoriwiki",
|
||||
},
|
||||
];
|
||||
|
||||
function toAdapter(spec: SourceSpec): Adapter {
|
||||
|
||||
@@ -410,3 +410,72 @@ export function parseSlashDateTimeRange(
|
||||
end: { iso: endIso, precision: "exact" },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Named timezone abbreviations this file will convert from, in milliseconds.
|
||||
*
|
||||
* Deliberately only the ones a source actually states, and deliberately only
|
||||
* ones with a fixed offset. An abbreviation absent here parses to null, which
|
||||
* is the same answer this file gives every other missing fact — and the reason
|
||||
* the map is not pre-populated with the obvious candidates is that half of them
|
||||
* are not fixed: `CST` names three different zones and `PT` shifts by an hour
|
||||
* twice a year, so a plausible-looking entry is a wrong date waiting for the
|
||||
* source to use it.
|
||||
*/
|
||||
const NAMED_ZONE_OFFSET_MS: Record<string, number> = {
|
||||
// Japan observes no daylight saving, so UTC+9 holds all year.
|
||||
jst: 9 * 60 * 60 * 1000,
|
||||
};
|
||||
|
||||
/**
|
||||
* "08/17/2026 8:00PM (JST)" → 2026-08-17T11:00:00.000Z, exact precision.
|
||||
*
|
||||
* One boundary, not a range: the hololive Dreams wiki gives Start Date and End
|
||||
* Date their own columns, so there is nothing to split and the whole cell is
|
||||
* anchored at both ends.
|
||||
*
|
||||
* Three things separate this from the readers above:
|
||||
*
|
||||
* - **A 12-hour clock**, which is the detail most likely to be got wrong
|
||||
* silently. `12:00PM` is noon and `12:00AM` is midnight — the hour is not
|
||||
* `12 + 12` in the first case and not `12` in the second — and a naive
|
||||
* reading puts an event's start twelve hours out without ever failing.
|
||||
* - **The zone is named, not offset.** `parseOrdinalDateTimeRange` reads a
|
||||
* stated `(UTC-5)`; here the source writes `(JST)`, so the abbreviation is
|
||||
* resolved through `NAMED_ZONE_OFFSET_MS` and an unrecognised one returns
|
||||
* null rather than being read as UTC.
|
||||
* - **The zone is required.** A cell stating a wall clock and no zone is a
|
||||
* missing fact, and this page always states one — so the day a row loses it,
|
||||
* that row should vanish rather than silently land nine hours off.
|
||||
*
|
||||
* Month-first, like `parseShortSlashRange`: the source is written for an
|
||||
* English-speaking audience and its own rows settle it — `08/17/2026` and
|
||||
* `08/27/2026` are both readable either way, but `08/30/2026` is not a day-first
|
||||
* date at all.
|
||||
*/
|
||||
export function parseSlashClockZone(input: string): ParsedInstant | null {
|
||||
const re =
|
||||
/^\s*(\d{1,2})\/(\d{1,2})\/(\d{4})\s+(\d{1,2}):(\d{2})\s*([AP])M\s*\(([A-Za-z]+)\)\s*$/i;
|
||||
const m = re.exec(input);
|
||||
if (!m) return null;
|
||||
|
||||
const offsetMs = NAMED_ZONE_OFFSET_MS[(m[7] ?? "").toLowerCase()];
|
||||
if (offsetMs === undefined) return null;
|
||||
|
||||
const hour12 = Number(m[4]);
|
||||
if (hour12 < 1 || hour12 > 12) return null;
|
||||
const pm = (m[6] ?? "").toUpperCase() === "P";
|
||||
// Noon and midnight are the two readings a 12-hour clock gets wrong: 12PM is
|
||||
// hour 12 and 12AM is hour 0, so the wrap happens before the PM shift.
|
||||
const hour = (hour12 % 12) + (pm ? 12 : 0);
|
||||
|
||||
const value = offsetIso(
|
||||
Number(m[3]),
|
||||
Number(m[1]),
|
||||
Number(m[2]),
|
||||
hour,
|
||||
Number(m[5]),
|
||||
offsetMs,
|
||||
);
|
||||
return value === null ? null : { iso: value, precision: "exact" };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
import { eventId, type EventType, type GachaEvent } from "../../shared/schema.ts";
|
||||
import { parseSlashClockZone, type ParsedInstant } from "../dates.ts";
|
||||
import { text } from "../html.ts";
|
||||
import type { ParseContext } from "../adapters/types.ts";
|
||||
import { inferType } from "./game8.ts";
|
||||
import type { SourceParser } from "./types.ts";
|
||||
|
||||
/**
|
||||
* The hololive Dreams wiki's event schedule (`holodori.wiki`, a Miraheze wiki).
|
||||
*
|
||||
* The rendered page, not the API — the same call `bawiki.ts` makes and for the
|
||||
* same reason: this is Miraheze, whose `robots.txt` disallows `/w/` and
|
||||
* `/*?action=` while allowing `/wiki/`, so the route `fandom.ts` takes is the
|
||||
* one that is closed here. `/wiki/Events` answers our own User-Agent with a
|
||||
* `200`, carries no `Content-Signal`, and the wiki is CC BY-SA 4.0.
|
||||
*
|
||||
* The page is two `wikitable`s under two `<h2>`s:
|
||||
*
|
||||
* <h2>Current Events</h2>
|
||||
* <table class="wikitable">
|
||||
* <tr><th>Logo<th>Event<th>Type<th>Start Date<th>End Date<th>Featured Members
|
||||
* <tr><td><img><td>Ultimate Summer For Me?<td>Spotlight
|
||||
* <td>08/17/2026 8:00PM (JST)<td>08/27/2026 7:59PM (JST)<td>…
|
||||
* <h2>Past Events</h2>
|
||||
* <table class="wikitable"> … same columns …
|
||||
*
|
||||
* Four things about it decide the code below.
|
||||
*
|
||||
* **The page states its timezone, on every single cell.** All eight boundaries
|
||||
* in the captured fixture end `(JST)`, which is why this is the only wiki source
|
||||
* here publishing `exact` precision on both sides without a per-region timer.
|
||||
* It is worth guarding rather than assuming: `parseSlashClockZone` requires the
|
||||
* zone, so a row that ever loses it drops out instead of quietly landing nine
|
||||
* hours off.
|
||||
*
|
||||
* **A section is fenced by its `<h2>`, and only `Current Events` is ours.**
|
||||
* Unlike `bluearchive.wiki`, this page says outright which rows it considers
|
||||
* live, and the two tables are otherwise identical — a reader that took every
|
||||
* `wikitable` would put the whole back catalogue on the calendar. Rows are still
|
||||
* checked against `ctx.now` on top of that, because the heading is maintained by
|
||||
* hand and "Current" goes stale a few days before someone moves the row.
|
||||
*
|
||||
* **Not every row is datable, and that is the page being honest.** `Beginner
|
||||
* Mission` runs from `Game Launch` to `Unknown` — a permanent tutorial chore
|
||||
* with no schedule. Its start is what decides it: an event ID is built from a
|
||||
* start date, so a row without one cannot be published at all, and inventing
|
||||
* launch day to fill the hole would put a permanent fixture on a calendar of
|
||||
* deadlines. An `Unknown` *end* is different and is kept — that is `endsAt:
|
||||
* null`, the honest answer this codebase exists to preserve.
|
||||
*
|
||||
* **The Type column is a controlled vocabulary, so it beats the title.**
|
||||
* `Spotlight` is this game's word for a rate-up banner — the row carries a
|
||||
* `Featured Members` column to prove it — and no amount of reading
|
||||
* "Ultimate Summer For Me?" would ever produce that. See `TYPES` below.
|
||||
*/
|
||||
|
||||
const HEADING = /<h2\b[^>]*>([\s\S]*?)<\/h2>/gi;
|
||||
const TABLE = /<table\b[^>]*>([\s\S]*?)<\/table>/i;
|
||||
const ROW = /<tr\b[^>]*>([\s\S]*?)<\/tr>/gi;
|
||||
const CELL = /<t([dh])\b[^>]*>([\s\S]*?)<\/t\1>/gi;
|
||||
/** Non-global deliberately: `.exec` on `ROW` would advance a shared `lastIndex`. */
|
||||
const FIRST_ROW = /<tr\b[^>]*>([\s\S]*?)<\/tr>/i;
|
||||
/**
|
||||
* The event's own article, when it has one.
|
||||
*
|
||||
* `Special:` is excluded because Miraheze's robots.txt disallows it, exactly as
|
||||
* in `bawiki.ts`. Excluding a query string matters more here than it does there,
|
||||
* though, and not as a nicety: **every title on this page is currently a red
|
||||
* link.** The wiki has no article for any of these events yet, so it writes
|
||||
* `/wiki/Ultimate_Summer_For_Me%3F?action=edit&redlink=1` — a *create this
|
||||
* page* form, which is both the wrong place to send a reader and a `?action=`
|
||||
* URL that this wiki's robots.txt disallows outright. Refusing a href with a
|
||||
* query is what keeps one out of the feed, and the fallback to the events page
|
||||
* is the right answer until the articles exist.
|
||||
*/
|
||||
const ARTICLE_LINK = /<a\b[^>]*href="(\/wiki\/(?!Special:)[^"#?]+)"/i;
|
||||
|
||||
/**
|
||||
* The site's own event vocabulary, mapped onto our `EventType`.
|
||||
*
|
||||
* A fixed list rather than word-matching, because this column is a controlled
|
||||
* vocabulary: the wiki writes one of these four words and nothing else. Two of
|
||||
* them are not derivable from the title in any form —
|
||||
*
|
||||
* - **`Spotlight`** is the gacha. Rate-up banners are what the `Featured
|
||||
* Members` column on the same row lists, and "spotlight" is the game's term
|
||||
* for the window they run in.
|
||||
* - **`Point Rally`** and **`Score Challenge`** are the game's two ranked
|
||||
* scoring formats — play the event's Set Pieces for points against a
|
||||
* leaderboard. `challenge` is the bucket for a scored cycle you grind, and
|
||||
* putting the two formats in different buckets would be describing a
|
||||
* difference the game does not draw.
|
||||
*
|
||||
* `Mission` maps to nothing better than `other`, which is stated here rather
|
||||
* than left to the fallback so that a *new* word in this column is the only
|
||||
* thing that ever reaches `inferType` — that is the case worth noticing, and
|
||||
* the fallback is what keeps it from being a crash.
|
||||
*/
|
||||
const TYPES: Record<string, EventType> = {
|
||||
spotlight: "banner",
|
||||
"point rally": "challenge",
|
||||
"score challenge": "challenge",
|
||||
mission: "other",
|
||||
};
|
||||
|
||||
interface Columns {
|
||||
title: number;
|
||||
type: number | undefined;
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Where each column sits, read off the header row rather than counted.
|
||||
*
|
||||
* Counting is how a silent drop starts here: the first column is a logo and the
|
||||
* last is a cast list, so a table that gains or loses one of those shifts every
|
||||
* date one place and every row fails to parse — an empty lane with no error
|
||||
* anywhere. Returning null is also what tells a `wikitable` that is not a
|
||||
* schedule from one that is.
|
||||
*/
|
||||
function columns(headers: string[]): Columns | null {
|
||||
const at = (name: string) => {
|
||||
const i = headers.indexOf(name);
|
||||
return i < 0 ? undefined : i;
|
||||
};
|
||||
|
||||
const title = at("event");
|
||||
const start = at("start date");
|
||||
const end = at("end date");
|
||||
if (title === undefined || start === undefined || end === undefined) {
|
||||
return null;
|
||||
}
|
||||
return { title, start, end, type: at("type") };
|
||||
}
|
||||
|
||||
/**
|
||||
* The schedule table under the `Current Events` heading, and where its columns
|
||||
* sit.
|
||||
*
|
||||
* Bounded by the next `<h2>`, which is what keeps `Past Events` — an identically
|
||||
* shaped table directly below — off the calendar. Matching the heading by its
|
||||
* text rather than by position is the same choice `bawiki.ts` makes about tabs:
|
||||
* position works today and starts reading the archive the day the page is
|
||||
* reordered.
|
||||
*/
|
||||
function currentSchedule(html: string): { cols: Columns; body: string } | null {
|
||||
let section: string | null = null;
|
||||
for (const m of html.matchAll(HEADING)) {
|
||||
if (text(m[1] ?? "").toLowerCase() !== "current events") continue;
|
||||
const from = m.index + m[0].length;
|
||||
const next = html.slice(from).search(/<h2\b/i);
|
||||
section = next < 0 ? html.slice(from) : html.slice(from, from + next);
|
||||
break;
|
||||
}
|
||||
if (section === null) return null;
|
||||
|
||||
const body = TABLE.exec(section)?.[1];
|
||||
if (body === undefined) return null;
|
||||
|
||||
// The header row only, rather than every `<th>` in the table. A row header
|
||||
// appearing further down would otherwise join the list and shift every
|
||||
// resolved index by one — which is the silent drop that reading columns by
|
||||
// name is supposed to rule out, arriving by a different door.
|
||||
const first = FIRST_ROW.exec(body)?.[1] ?? "";
|
||||
const headers = [...first.matchAll(/<th\b[^>]*>([\s\S]*?)<\/th>/gi)].map((h) =>
|
||||
text(h[1] ?? "").toLowerCase(),
|
||||
);
|
||||
const cols = columns(headers);
|
||||
return cols === null ? null : { cols, body };
|
||||
}
|
||||
|
||||
export function parseHolodoriWikiEventsPage(
|
||||
html: string,
|
||||
ctx: ParseContext,
|
||||
): GachaEvent[] {
|
||||
const schedule = currentSchedule(html.replace(/\s+/g, " "));
|
||||
if (schedule === null) return [];
|
||||
|
||||
const { cols } = schedule;
|
||||
const nowMs = Date.parse(ctx.now);
|
||||
const out: GachaEvent[] = [];
|
||||
|
||||
for (const row of schedule.body.matchAll(ROW)) {
|
||||
const cells = [...(row[1] ?? "").matchAll(CELL)].map((c) => ({
|
||||
tag: c[1] ?? "",
|
||||
html: c[2] ?? "",
|
||||
}));
|
||||
if (cells.length === 0 || cells.some((c) => c.tag === "h")) continue;
|
||||
|
||||
const titleCell = cells[cols.title]?.html ?? "";
|
||||
const title = text(titleCell);
|
||||
if (title.length === 0) continue;
|
||||
|
||||
// No start, no event. `Game Launch` is the real value this drops, and it is
|
||||
// not a date the page failed to write down — it is a permanent tutorial
|
||||
// chore, which is not what a calendar of deadlines is for.
|
||||
const start = parseSlashClockZone(text(cells[cols.start]?.html ?? ""));
|
||||
if (start === null) continue;
|
||||
|
||||
// `Unknown` lands here, and is kept rather than dropped. It is the whole
|
||||
// point of `endPrecision: "unknown"`: the heading has already said this
|
||||
// event is running, so the honest report is that it is live and its end has
|
||||
// not been announced. That differs from `bawiki.ts`, which drops a
|
||||
// started-but-undated row — it has to, because it is reading an archive
|
||||
// with no heading to tell a live row from a finished one.
|
||||
const end: ParsedInstant | null = parseSlashClockZone(
|
||||
text(cells[cols.end]?.html ?? ""),
|
||||
);
|
||||
|
||||
if (end !== null) {
|
||||
if (end.iso <= start.iso) continue;
|
||||
// "Current Events" is maintained by hand and goes stale a few days before
|
||||
// anyone moves a row down to "Past Events". The dates are the fact; the
|
||||
// heading is someone's housekeeping.
|
||||
if (Date.parse(end.iso) < nowMs) continue;
|
||||
}
|
||||
|
||||
const type = cols.type === undefined
|
||||
? ""
|
||||
: text(cells[cols.type]?.html ?? "");
|
||||
|
||||
const href = ARTICLE_LINK.exec(titleCell)?.[1];
|
||||
|
||||
// Both boundaries are stated to the minute in a named zone, so there is no
|
||||
// day-precision penalty here — only the one an unannounced end carries.
|
||||
const confidence = end === null ? 0.95 - 0.15 : 0.95;
|
||||
|
||||
out.push({
|
||||
id: eventId(ctx.game, title, start.iso),
|
||||
game: ctx.game,
|
||||
title,
|
||||
type: TYPES[type.toLowerCase()] ?? inferType(`${title} ${type}`),
|
||||
summary: type.length > 0 ? type : null,
|
||||
startsAt: start.iso,
|
||||
startPrecision: start.precision,
|
||||
endsAt: end === null ? null : end.iso,
|
||||
endPrecision: end === null ? "unknown" : end.precision,
|
||||
// hololive Dreams launched worldwide simultaneously on one service, and
|
||||
// every boundary on this page is stated once, in JST, with no per-region
|
||||
// column anywhere. So there is one global end instant and nothing
|
||||
// region-scoped to report — see `games.ts` for the server clock itself.
|
||||
regionScoped: false,
|
||||
regionEnds: null,
|
||||
sourceUrl:
|
||||
href === undefined
|
||||
? ctx.sourceUrl
|
||||
: new URL(href, ctx.sourceUrl).toString(),
|
||||
sourceId: ctx.sourceId,
|
||||
status: "published",
|
||||
confidence: Math.round(confidence * 100) / 100,
|
||||
extractionMethod: "parser",
|
||||
version: 1,
|
||||
firstSeenAt: ctx.now,
|
||||
updatedAt: ctx.now,
|
||||
});
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
export const holodoriWikiParser: SourceParser = {
|
||||
id: "holodoriwiki",
|
||||
label: "hololive Dreams Wiki (holodori.wiki)",
|
||||
|
||||
/**
|
||||
* Asserts the two things the parser navigates by: the `Current Events`
|
||||
* heading, and a table under it headed with the columns it reads. A renamed
|
||||
* heading or a renamed column fails the run loudly rather than emptying the
|
||||
* lane, which is the whole reason `canParse` is checked separately from a
|
||||
* zero-event parse.
|
||||
*/
|
||||
canParse(html: string): boolean {
|
||||
return currentSchedule(html.replace(/\s+/g, " ")) !== null;
|
||||
},
|
||||
|
||||
parse: parseHolodoriWikiEventsPage,
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import { arknightsWikiParser } from "./akwiki.ts";
|
||||
import { blueArchiveWikiParser } from "./bawiki.ts";
|
||||
import { fandomParser } from "./fandom.ts";
|
||||
import { game8Parser } from "./game8.ts";
|
||||
import { holodoriWikiParser } from "./holodori.ts";
|
||||
import { wikiGgParser } from "./wikigg.ts";
|
||||
import type { SourceParser } from "./types.ts";
|
||||
|
||||
@@ -16,6 +17,7 @@ export const PARSERS: SourceParser[] = [
|
||||
arknightsWikiParser,
|
||||
fandomParser,
|
||||
blueArchiveWikiParser,
|
||||
holodoriWikiParser,
|
||||
];
|
||||
|
||||
export function parserById(id: string): SourceParser | undefined {
|
||||
@@ -27,4 +29,5 @@ export { arknightsWikiParser } from "./akwiki.ts";
|
||||
export { blueArchiveWikiParser } from "./bawiki.ts";
|
||||
export { fandomParser } from "./fandom.ts";
|
||||
export { game8Parser } from "./game8.ts";
|
||||
export { holodoriWikiParser } from "./holodori.ts";
|
||||
export { wikiGgParser } from "./wikigg.ts";
|
||||
|
||||
@@ -95,6 +95,16 @@ export const GAMES: Record<GameId, GameMeta> = {
|
||||
// readers' day keys, and a wrong guess ticks the wrong box every night.
|
||||
ba: { id: "ba", name: "Blue Archive", short: "Blue Archive", hue: "#3FCBDD" , studio: "Nexon Games", dailyTasks: "Daily missions, AP" },
|
||||
fgo: { id: "fgo", name: "Fate/Grand Order", short: "FGO", hue: "#1D3A8F", studio: "Lasengle", dailyTasks: "Daily missions, AP" },
|
||||
// hololive Dreams runs a single worldwide service on a Japanese clock, and
|
||||
// both halves of that come from the source rather than from habit: every
|
||||
// boundary on holodori.wiki is stated once, in `(JST)`, with no per-region
|
||||
// column anywhere, and the game launched worldwide simultaneously. The reset
|
||||
// hour is evidenced the way Arknights' is — `Training Support Missions` ends
|
||||
// at 3:59AM JST, one minute before a 04:00 local reset — so the default hour
|
||||
// stands and only the offset is overridden. Setting it now costs nothing: no
|
||||
// reader has a day key for a game the app has never shipped, whereas adding
|
||||
// the same override later would re-label ticks they had already logged.
|
||||
holodori: { id: "holodori", name: "hololive Dreams", short: "holodori", hue: "#5FD3F3", studio: "QualiArts / COVER", dailyTasks: "Daily missions, stamina", resetOffsets: { asia: 9, america: 9, europe: 9 } },
|
||||
};
|
||||
|
||||
export const GAME_LIST: GameMeta[] = Object.values(GAMES);
|
||||
|
||||
@@ -13,6 +13,7 @@ export const GameId = z.enum([
|
||||
"r1999", // Reverse: 1999
|
||||
"ba", // Blue Archive
|
||||
"fgo", // Fate/Grand Order
|
||||
"holodori", // hololive Dreams
|
||||
]);
|
||||
export type GameId = z.infer<typeof GameId>;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { arknightsWikiParser } from "../../src/ingest/parsers/akwiki.ts";
|
||||
import { blueArchiveWikiParser } from "../../src/ingest/parsers/bawiki.ts";
|
||||
import { fandomParser, renderedHtml } from "../../src/ingest/parsers/fandom.ts";
|
||||
import { inferType } from "../../src/ingest/parsers/game8.ts";
|
||||
import { holodoriWikiParser } from "../../src/ingest/parsers/holodori.ts";
|
||||
import { GachaEvent, type EventType } from "../../src/shared/schema.ts";
|
||||
|
||||
/**
|
||||
@@ -40,6 +41,7 @@ const CASES: Array<{ adapter: Adapter; fixture: string }> = [
|
||||
{ adapter: adapter("r1999-fandom-events"), fixture: "fixtures/r1999/fandom-events-2026-08-17" },
|
||||
{ adapter: adapter("ba-bawiki-events"), fixture: "fixtures/ba/bawiki-events-2026-08-17" },
|
||||
{ adapter: adapter("fgo-fandom-events"), fixture: "fixtures/fgo/fandom-events-2026-08-18" },
|
||||
{ adapter: adapter("holodori-holodoriwiki-events"), fixture: "fixtures/holodori/holodoriwiki-events-2026-08-18" },
|
||||
];
|
||||
|
||||
async function runAdapter(adapter: Adapter, fixture: string) {
|
||||
@@ -705,3 +707,215 @@ describe("blue archive wiki", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hololive Dreams wiki", () => {
|
||||
const fixture = "fixtures/holodori/holodoriwiki-events-2026-08-18";
|
||||
const holo = adapter("holodori-holodoriwiki-events");
|
||||
|
||||
function parse(html: string) {
|
||||
return holo.parse(html, {
|
||||
now: NOW,
|
||||
sourceUrl: holo.url,
|
||||
sourceId: holo.id,
|
||||
game: holo.game,
|
||||
});
|
||||
}
|
||||
|
||||
/** A stand-in page with the two-heading shape this parser navigates. */
|
||||
function paged(current: string, past: string): string {
|
||||
return `<div class="mw-parser-output">
|
||||
<h2>Current Events</h2><table class="wikitable">${current}</table>
|
||||
<h2>Past Events</h2><table class="wikitable">${past}</table>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
const HEADER =
|
||||
"<tr><th>Logo</th><th>Event</th><th>Type</th><th>Start Date</th><th>End Date</th><th>Featured Members</th></tr>";
|
||||
|
||||
const row = (title: string, type: string, start: string, end: string) =>
|
||||
`<tr><td></td><td>${title}</td><td>${type}</td><td>${start}</td><td>${end}</td><td></td></tr>`;
|
||||
|
||||
test("publishes Current Events and never the Past Events table below it", async () => {
|
||||
// Counted independently off the fixture: the Current table holds three
|
||||
// rows and the Past table two, and the two tables are identically shaped —
|
||||
// so a reader that took every `wikitable` would put the back catalogue on
|
||||
// the calendar with nothing to distinguish it.
|
||||
const events = await runAdapter(holo, fixture);
|
||||
expect(events.map((e) => e.title)).toEqual([
|
||||
"Ultimate Summer For Me?",
|
||||
"Training Support Missions",
|
||||
]);
|
||||
for (const e of events) {
|
||||
expect(e.title).not.toMatch(/Synced Summer Sparkles|Brand New Summer/);
|
||||
}
|
||||
});
|
||||
|
||||
test("converts JST to UTC on both boundaries, to the minute", async () => {
|
||||
// This is the only wiki source here that states a timezone on every cell,
|
||||
// which is what earns `exact` on both sides without a per-region timer.
|
||||
const [spotlight, missions] = await runAdapter(holo, fixture);
|
||||
|
||||
// 08/17/2026 8:00PM (JST) → 11:00Z the same day.
|
||||
expect(spotlight?.startsAt).toBe("2026-08-17T11:00:00.000Z");
|
||||
// 08/27/2026 7:59PM (JST) → 10:59Z.
|
||||
expect(spotlight?.endsAt).toBe("2026-08-27T10:59:00.000Z");
|
||||
// 08/30/2026 3:59AM (JST) → 18:59Z on the 29th: a small-hours JST boundary
|
||||
// lands on the *previous* UTC day, which is exactly the shift that makes
|
||||
// storing the source's own wall clock unusable.
|
||||
expect(missions?.endsAt).toBe("2026-08-29T18:59:00.000Z");
|
||||
|
||||
for (const e of await runAdapter(holo, fixture)) {
|
||||
expect(e.startPrecision).toBe("exact");
|
||||
expect(e.endPrecision).toBe("exact");
|
||||
expect(e.confidence).toBe(0.95);
|
||||
}
|
||||
});
|
||||
|
||||
test("reads 12PM as noon and 12AM as midnight", async () => {
|
||||
// The failure this guards is silent: a naive 12-hour reading puts a start
|
||||
// twelve hours out and still produces a valid-looking instant.
|
||||
const events = parse(
|
||||
paged(
|
||||
HEADER +
|
||||
row("Noon", "Mission", "08/20/2026 12:00PM (JST)", "08/21/2026 12:00AM (JST)"),
|
||||
HEADER,
|
||||
),
|
||||
);
|
||||
expect(events).toHaveLength(1);
|
||||
// 12:00 JST → 03:00Z, not 00:00Z.
|
||||
expect(events[0]?.startsAt).toBe("2026-08-20T03:00:00.000Z");
|
||||
// 00:00 JST on the 21st → 15:00Z on the 20th, not noon.
|
||||
expect(events[0]?.endsAt).toBe("2026-08-20T15:00:00.000Z");
|
||||
});
|
||||
|
||||
test("drops a row whose start is not a date, and keeps one whose end is not", async () => {
|
||||
// Both appear on the real page. `Beginner Mission` runs "Game Launch" →
|
||||
// "Unknown": no start means no event ID and no place on a calendar of
|
||||
// deadlines. An unannounced *end* is the opposite — it is a fact worth
|
||||
// publishing, and inventing one is the failure this product exists to
|
||||
// prevent.
|
||||
expect((await runAdapter(holo, fixture)).map((e) => e.title)).not.toContain(
|
||||
"Beginner Mission",
|
||||
);
|
||||
|
||||
const events = parse(
|
||||
paged(
|
||||
HEADER +
|
||||
row("Open ended", "Mission", "08/20/2026 12:00PM (JST)", "Unknown") +
|
||||
row("No start", "Mission", "Game Launch", "08/21/2026 12:00PM (JST)"),
|
||||
HEADER,
|
||||
),
|
||||
);
|
||||
expect(events.map((e) => e.title)).toEqual(["Open ended"]);
|
||||
expect(events[0]?.endsAt).toBeNull();
|
||||
expect(events[0]?.endPrecision).toBe("unknown");
|
||||
// An unannounced end is weaker evidence and the gate should see that.
|
||||
expect(events[0]?.confidence).toBe(0.8);
|
||||
});
|
||||
|
||||
test("requires the stated timezone rather than falling back to UTC", async () => {
|
||||
// A cell that loses its `(JST)` is a missing fact, and a missing fact is a
|
||||
// dropped row here — not a nine-hour error nobody can see.
|
||||
const events = parse(
|
||||
paged(
|
||||
HEADER +
|
||||
row("Zoneless", "Mission", "08/20/2026 12:00PM", "08/21/2026 12:00PM") +
|
||||
row("Unknown zone", "Mission", "08/20/2026 12:00PM (XYZ)", "08/21/2026 12:00PM (XYZ)"),
|
||||
HEADER,
|
||||
),
|
||||
);
|
||||
expect(events).toEqual([]);
|
||||
});
|
||||
|
||||
test("takes the type from the source's own column, not from the title", async () => {
|
||||
// "Spotlight" is this game's word for a rate-up banner, and no reading of
|
||||
// "Ultimate Summer For Me?" would ever produce it.
|
||||
const [spotlight, missions] = await runAdapter(holo, fixture);
|
||||
expect(spotlight?.type).toBe("banner");
|
||||
expect(spotlight?.summary).toBe("Spotlight");
|
||||
expect(missions?.type).toBe("other");
|
||||
|
||||
const events = parse(
|
||||
paged(
|
||||
HEADER +
|
||||
row("Rally", "Point Rally", "08/20/2026 12:00PM (JST)", "08/28/2026 12:00PM (JST)") +
|
||||
row("Scored", "Score Challenge", "08/20/2026 12:00PM (JST)", "08/28/2026 12:00PM (JST)"),
|
||||
HEADER,
|
||||
),
|
||||
);
|
||||
// The game's two ranked scoring formats land in the same bucket, because
|
||||
// the game does not draw a distinction between them that we can act on.
|
||||
expect(events.map((e) => e.type)).toEqual(["challenge", "challenge"]);
|
||||
});
|
||||
|
||||
test("a stale Current Events heading does not keep a finished event live", async () => {
|
||||
// The heading is maintained by hand and lags the dates by a few days. The
|
||||
// dates are the fact; the heading is someone's housekeeping.
|
||||
const events = parse(
|
||||
paged(
|
||||
HEADER +
|
||||
row("Over", "Mission", "07/01/2026 12:00PM (JST)", "07/20/2026 12:00PM (JST)"),
|
||||
HEADER,
|
||||
),
|
||||
);
|
||||
expect(events).toEqual([]);
|
||||
});
|
||||
|
||||
test("columns are resolved by name, so an inserted column moves nothing", async () => {
|
||||
// The first column is a logo and the last a cast list. Counting from either
|
||||
// end means one added column shifts every date one place, every row fails
|
||||
// to parse, and the lane empties with no error anywhere.
|
||||
const events = parse(
|
||||
`<div class="mw-parser-output"><h2>Current Events</h2><table class="wikitable">
|
||||
<tr><th>Logo</th><th>Banner</th><th>Event</th><th>Type</th><th>Start Date</th><th>End Date</th></tr>
|
||||
<tr><td></td><td></td><td>Shifted</td><td>Spotlight</td><td>08/20/2026 12:00PM (JST)</td><td>08/28/2026 12:00PM (JST)</td></tr>
|
||||
</table></div>`,
|
||||
);
|
||||
expect(events.map((e) => e.title)).toEqual(["Shifted"]);
|
||||
expect(events[0]?.startsAt).toBe("2026-08-20T03:00:00.000Z");
|
||||
});
|
||||
|
||||
test("a renamed heading or column fails the run instead of emptying the lane", () => {
|
||||
const good = paged(HEADER, HEADER);
|
||||
expect(holodoriWikiParser.canParse(good)).toBe(true);
|
||||
|
||||
// The heading the parser navigates by.
|
||||
expect(holodoriWikiParser.canParse(good.replace("Current Events", "Live Events"))).toBe(false);
|
||||
// A column it reads.
|
||||
expect(holodoriWikiParser.canParse(good.replace(/<th>Start Date<\/th>/g, "<th>Begins</th>"))).toBe(false);
|
||||
});
|
||||
|
||||
test("links to the events page while every article is still a red link", async () => {
|
||||
// The wiki has no article for any of these events yet, so each title links
|
||||
// to `?action=edit&redlink=1` — a create-page form, and a `?action=` URL
|
||||
// this wiki's robots.txt disallows. Neither belongs in the feed.
|
||||
for (const e of await runAdapter(holo, fixture)) {
|
||||
expect(e.sourceUrl).toBe("https://holodori.wiki/wiki/Events");
|
||||
expect(e.sourceUrl).not.toMatch(/action=|redlink|Special:/);
|
||||
}
|
||||
|
||||
// A real article, once one exists, is linked.
|
||||
const events = parse(
|
||||
paged(
|
||||
HEADER +
|
||||
row(
|
||||
'<a href="/wiki/Ultimate_Summer" title="Ultimate Summer">Ultimate Summer</a>',
|
||||
"Spotlight",
|
||||
"08/20/2026 12:00PM (JST)",
|
||||
"08/28/2026 12:00PM (JST)",
|
||||
),
|
||||
HEADER,
|
||||
),
|
||||
);
|
||||
expect(events[0]?.sourceUrl).toBe("https://holodori.wiki/wiki/Ultimate_Summer");
|
||||
});
|
||||
|
||||
test("reports one global end rather than inventing per-region ones", async () => {
|
||||
// One worldwide service on a Japanese clock: the page states each boundary
|
||||
// once, in JST, with no per-region column anywhere.
|
||||
for (const e of await runAdapter(holo, fixture)) {
|
||||
expect(e.regionScoped).toBe(false);
|
||||
expect(e.regionEnds).toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
parseMonthDayYear,
|
||||
parseOpenRange,
|
||||
parseOrdinalDateTimeRange,
|
||||
parseSlashClockZone,
|
||||
parseSlashDateTimeRange,
|
||||
parseYearFirstSlashRange,
|
||||
} from "../src/ingest/dates.ts";
|
||||
@@ -367,3 +368,51 @@ describe("parseIsoDay", () => {
|
||||
expect(parseIsoDay("")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseSlashClockZone", () => {
|
||||
test("converts a stated JST wall clock to UTC", () => {
|
||||
expect(parseSlashClockZone("08/17/2026 8:00PM (JST)")).toEqual({
|
||||
iso: "2026-08-17T11:00:00.000Z",
|
||||
precision: "exact",
|
||||
});
|
||||
});
|
||||
|
||||
test("a small-hours JST boundary lands on the previous UTC day", () => {
|
||||
// The shift that makes storing the source's own wall clock unusable.
|
||||
expect(parseSlashClockZone("08/30/2026 3:59AM (JST)")?.iso).toBe(
|
||||
"2026-08-29T18:59:00.000Z",
|
||||
);
|
||||
});
|
||||
|
||||
test("12PM is noon and 12AM is midnight", () => {
|
||||
expect(parseSlashClockZone("08/20/2026 12:00PM (JST)")?.iso).toBe(
|
||||
"2026-08-20T03:00:00.000Z",
|
||||
);
|
||||
expect(parseSlashClockZone("08/21/2026 12:00AM (JST)")?.iso).toBe(
|
||||
"2026-08-20T15:00:00.000Z",
|
||||
);
|
||||
});
|
||||
|
||||
test("an unstated or unknown zone is null, never UTC", () => {
|
||||
expect(parseSlashClockZone("08/20/2026 12:00PM")).toBeNull();
|
||||
// Not in the table, and deliberately: `CST` names three different zones.
|
||||
expect(parseSlashClockZone("08/20/2026 12:00PM (CST)")).toBeNull();
|
||||
});
|
||||
|
||||
test("rejects a non-date and an impossible calendar day", () => {
|
||||
expect(parseSlashClockZone("Game Launch")).toBeNull();
|
||||
expect(parseSlashClockZone("Unknown")).toBeNull();
|
||||
// Validated on the stated local fields, before the offset shifts anything:
|
||||
// converting first would quietly turn Feb 30 into a real instant in March.
|
||||
expect(parseSlashClockZone("02/30/2026 12:00PM (JST)")).toBeNull();
|
||||
expect(parseSlashClockZone("08/20/2026 13:00PM (JST)")).toBeNull();
|
||||
});
|
||||
|
||||
test("is a whole-cell reader, not a scanner", () => {
|
||||
// Anchored at both ends: letting this match mid-prose is how a reader
|
||||
// starts finding dates inside sentences.
|
||||
expect(
|
||||
parseSlashClockZone("Starts 08/20/2026 12:00PM (JST) after maintenance"),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user