feat(userscript): render Covers from the public route (#60)
Both userscripts stop scraping Covers and stop putting one on the wire. A scraped address has no rendering path left now that the backend acquires, stores and serves every Cover from its own origin (ADR-0007), and keeping one would reintroduce third-party URLs into exactly the place #47 came from. - Adapters no longer read og:image / meta[name=image], and comix's img[alt] cover scan and novelfull's metaName helper are deleted. - apiPut strips `cover` off every outgoing body, so a cached row's address (already ours) never travels back either. The server ignores the field regardless. - Both card renderers swap in the existing `.cover.ph` placeholder when the image fails to load, so a failure looks designed rather than broken - the other half of what #47 reported. - The deleted scraping's test cases go with it: the comix cover cases, the img[alt] and meta[name] stub branches, and the stale og:image fixtures. Export lists are unchanged; nothing cover-specific was exported. The cover route itself is already public and uncredentialed, with the immutable cache directive and 404-for-unknown covered by the tests that landed with #59.
This commit is contained in:
+11
-10
@@ -2,7 +2,7 @@ Guidance for OpenCode (and Claude Code) working under `userscript/`. See root `A
|
||||
|
||||
### Userscript structure (single IIFE, `manga-bookmark.user.js`)
|
||||
|
||||
1. **Site adapters** — one per host, `detect(location, document)` return page `type` + IDs. Identify type/IDs from **URL regex** (most stable); pull `title`/`cover` from **`og:title`/`og:image` meta tags**, not CSS classes.
|
||||
1. **Site adapters** — one per host, `detect(location, document)` return page `type` + IDs. Identify type/IDs from **URL regex** (most stable); pull `title` from **`og:title`** (or the page heading where a site ships no og: tags), not CSS classes. **No adapter reads a cover**: the backend acquires, stores and serves every Cover from its own origin (ADR-0007), the wire's `cover` is already an address on our origin, and `apiPut` strips any `cover` off an outgoing body.
|
||||
2. **API client** — `apiGet/apiPut/apiDelete` with bearer header; `localStorage` key `bmgr:manga:cache` for instant render + offline fallback.
|
||||
3. **Progress logic** — auto-upsert `last_chapter` only when `chapterNum >= stored last_chapter_num` (re-reading old chapters must not regress progress; unparseable -> set current). Manual panel override forces any value.
|
||||
4. **Retry queue** — every write go through `pushBookmark`/`pushDelete`, so
|
||||
@@ -52,26 +52,27 @@ Guidance for OpenCode (and Claude Code) working under `userscript/`. See root `A
|
||||
"Comix — Read Comics online for free" and after an in-page hop it is the
|
||||
*previous* series' name. `document.title` is the one thing client routing does
|
||||
update, so titles come from there, with the chapter page's `" · Ch.<n>"` tail
|
||||
stripped. Covers likewise: `og:image` is absent, so the cover is the `img`
|
||||
whose `alt` matches the cleaned title — verified live 2026-08-08.
|
||||
stripped. It publishes no `og:image` either, which is one of the reasons cover
|
||||
acquisition moved to the backend.
|
||||
- **kagane.to**: series `/series/<uuid>`, reader
|
||||
`/series/<uuid>/reader/<bookUuid>`. Reader URLs carry no chapter number, so
|
||||
the number comes out of `og:title`. Two shapes exist: `"<Series> - Chapter
|
||||
<n>[ - Episode <n>]"` and, for volume-numbered series, `"<Series> - Volume <v>
|
||||
Chapter <n>"` with no episode name — both must yield a bare series title, or
|
||||
the volume tail lands in the bookmark's title. Its covers are challenge- and
|
||||
CORP-protected, so the web UI proxies them; the userscript still stores the
|
||||
raw `og:image`. Behind a Cloudflare JS challenge, so the backend polls it
|
||||
the volume tail lands in the bookmark's title.
|
||||
Its covers are challenge- and CORP-protected, so nothing outside kagane.to can
|
||||
load one directly; the panel renders the backend's own cover address like every
|
||||
other Site. Behind a Cloudflare JS challenge, so the backend polls it
|
||||
through the headless browser.
|
||||
- **novelfull.com** (novel script): series `/<slug>.html`, chapter
|
||||
`/<slug>/chapter-<n>[-<title-slug>].html`. No `og:*` tags at all — title from
|
||||
`h3.title` (series) or `a.truyen-title` (chapter), cover from
|
||||
`meta[name="image"]`. Behind a Cloudflare JS challenge no TLS fingerprint
|
||||
`h3.title` (series) or `a.truyen-title` (chapter); the script reads no cover.
|
||||
Behind a Cloudflare JS challenge no TLS fingerprint
|
||||
clears, so the backend polls it through the headless browser.
|
||||
- **lightnovelworld.net** (novel script): series `/novel/<slug>/`, chapter
|
||||
`/<slug>-chapter-<n>/` — flat, at the site root. `h1.entry-title` is the clean
|
||||
title on a series page and `<Title> Chapter <n>` on a chapter page. Chapter
|
||||
pages carry no `og:image`. Its series page lists every chapter with an
|
||||
title on a series page and `<Title> Chapter <n>` on a chapter page. Its series
|
||||
page lists every chapter with an
|
||||
absolute href, so the backend polls it with the plain TLS client.
|
||||
|
||||
### Second script: `novel-bookmark.user.js`
|
||||
|
||||
Reference in New Issue
Block a user