--- status: approved --- # 003 — Tapas source A second source (spec 001 §4.1): covers of a [Tapas](https://tapas.io) series, comics and novels. Design approved by the user in chat on 2026-10-15 ("Comics and novels"). Everything else — inspection, dedupe, Wayback history, hosting, download, history — is the existing pipeline. ## 1. Verified facts (2026-10-15) Checked against *Villains Are Destined to Die* (comic, id 186414) and its novel (id 266599), a mature series (id 286112) and Wayback captures since 2020-10. Fixtures in `test/fixtures/tapas/` and `test/fixtures/wayback/tapas-*`. - **Series page:** `https://tapas.io/series//info` (its own canonical URL). `/series/` serves the episode reader (canonical `/episode/`). `/series//info` redirects to the slug form. Slugs may contain capitals and underscores (`/series/RU_Screwed`). - **Series id:** on the info page `pageMeta : { id: , type: 'series_id' }`; on the reader `seriesId: `. - **Comic or novel:** the stats row links `/static-landing/genre?category=COMIC` or `…=NOVEL`. `og:type` is `comicpanda:webcomic_series` for both. - **Title:** the cover image's `alt` in `.thumb-wrapper`; `og:title` is "Read | Tapas Web Comics". - **Images** (`us-a.tapas.io/sa/<xx>/<uuid>[_<size>].<ext>`, no token, no Referer needed): - **Portrait cover:** the `<img>` in `.thumb-wrapper` (both the info page and the 2020 reader page), `…_z.jpg` = 400×600. Without the suffix: the original, 960×1440. - **Banner:** `og:image` (also the `js-top-banner` / `header-layer` background), 1280×460, no suffix. - Lists use `_m` (180 px) of other images; `pc/…` images are signed promotions of other series. Neither is used. - Older pages use `d30womf5coomej.cloudfront.net/sa/…`; the same paths answer on `us-a.tapas.io`. - **Mature series:** without a signed-in, age-verified account, `/series/…/info` redirects to `/account/verify-age?from=https://tapas.io/series/<slug>/info`, a page with no series data. The userscript's `GM_xmlhttpRequest` sends the user's own tapas.io cookies, so a signed-in adult gets the real page there. - **Language:** English only (MangaBaka `en`). - **Wayback:** `tapas.io/series/<slug>` captured about monthly since 2020; the prefix also matches `<slug>-novel` and other longer slugs. ## 2. Behaviour - **Links accepted:** `tapas.io/series/<slug>`, `…/info`, `…/<id>`, `…/<id>/info`, and `tapas.io/episode/<id>` (the reader page names its series id). `www.` and `m.` hosts too. Fetched as `https://tapas.io/series/<slug or id>/info`; an episode link first fetches the episode page for its series id. - **Series:** `externalId` = the numeric id, `title`, `language: "en"`, `canonicalUrl` = `https://tapas.io/series/<slug>/info`, `flags.novel`, `archiveUrls` = [`tapas.io/series/<slug>`]. - **Current covers:** portrait (role `poster`, suffix removed) and banner (role `banner`). The poster is the suggested main cover as for Webtoons. - **Mature (age check):** the series is resolved from the `from=` link (slug; title from the slug), `mature: true`, no current covers, and a warning: Tapas shows this series only to signed-in adults; use the userscript while signed in, or tick the Wayback history. History still runs. - **Image URLs:** `normalizeImageUrl` maps both image hosts to `us-a.tapas.io`, drops `_<letters>` size suffixes after the UUID and any query. - **Same series:** a link is the same series when it is a tapas.io `/series/<slug>[/info][/]` with the same slug (case-insensitive) or `/series/<id>[/info]` with the same id. This also filters Wayback rows (`-novel`). - **Hosts:** fetch `tapas.io`, `www.tapas.io`, `m.tapas.io`, `us-a.tapas.io`, `d30womf5coomej.cloudfront.net`; images `us-a.tapas.io`, `d30womf5coomej.cloudfront.net`. The userscript `@connect`s `tapas.io` and the CloudFront host. - **Chrome:** source-neutral where it named WEBTOON: the link hint ("WEBTOON or Tapas for now"), the "No rehosting" warning ("the source's own image URL"), the userscript description. ## 3. Testing Unit tests on the captured pages, no network: link parsing (every form, rejects), page parsing (comic, novel, reader, 2020 archive, age check), URL normalisation, same-series (slug, id, `-novel` rejected), and the source end to end through the fake transport (info page, episode link, age check, `/series/<id>` redirect). Then a real run in headless Chrome for one comic and one novel. ## 4. Risks - Tapas markup changes; the parser looks for two stable hooks (`thumb-wrapper`, `og:image`) and the page type link. - The info page may show a mature badge to signed-in users that we never saw; mature is only known from the age check.