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:
2026-08-10 04:20:23 +07:00
parent 92eba07da7
commit cc0fa92a1a
6 changed files with 55 additions and 119 deletions
+2 -17
View File
@@ -4,8 +4,7 @@ const test = require("node:test");
const assert = require("node:assert");
// ============================================================
// Minimal browser stub. Same shape as logic.test.js, plus a meta[name=...]
// branch: novelfull ships no og: tags, so its cover comes from name="image".
// Minimal browser stub. Same shape as logic.test.js.
// document.body stays UNDEFINED so the boot block waits for a DOMContentLoaded
// that never fires and no network call is ever made.
// ============================================================
@@ -20,20 +19,14 @@ globalThis.localStorage = {
globalThis.location = { href: "about:blank", hostname: "", pathname: "/", origin: "" };
let metaTags = {};
let namedMetas = {};
let elements = {};
globalThis.document = {
querySelector(sel) {
let m = sel.match(/^meta\[property="([^"]+)"\]$/);
const m = sel.match(/^meta\[property="([^"]+)"\]$/);
if (m) {
const v = metaTags[m[1]];
return v == null ? null : { getAttribute: () => v };
}
m = sel.match(/^meta\[name="([^"]+)"\]$/);
if (m) {
const v = namedMetas[m[1]];
return v == null ? null : { getAttribute: () => v };
}
const text = elements[sel];
return text == null ? null : { textContent: text };
},
@@ -58,7 +51,6 @@ function loc(href) {
function reset() {
metaTags = {};
namedMetas = {};
elements = {};
}
@@ -68,21 +60,18 @@ function reset() {
test("novelfull.detect reads a series page", () => {
reset();
namedMetas = { image: "https://novelfull.com/uploads/thumbs/ri.jpg" };
elements = { "h3.title": "Reverend Insanity" };
const p = novelfull.detect(loc("https://novelfull.com/reverend-insanity.html"));
assert.equal(p.type, "series");
assert.equal(p.site, "novelfull");
assert.equal(p.seriesId, "reverend-insanity");
assert.equal(p.title, "Reverend Insanity");
assert.equal(p.cover, "https://novelfull.com/uploads/thumbs/ri.jpg");
assert.equal(p.seriesUrl, "https://novelfull.com/reverend-insanity.html");
assert.equal(p.chapterNum, null);
});
test("novelfull.detect reads a chapter page and points seriesUrl at the series", () => {
reset();
namedMetas = { image: "https://novelfull.com/uploads/thumbs/ri.jpg" };
elements = { "a.truyen-title": "Reverend Insanity" };
const url = "https://novelfull.com/reverend-insanity/chapter-2334-fang-yuan.html";
const p = novelfull.detect(loc(url));
@@ -120,14 +109,12 @@ test("novelfull.latestChapterFromAnchors takes the max and ignores other series"
test("lightnovelworld.detect reads a series page", () => {
reset();
metaTags = { "og:image": "https://lightnovelworld.net/wp-content/uploads/awe.webp" };
elements = { "h1.entry-title": "A Will Eternal" };
const p = lightnovelworld.detect(loc("https://lightnovelworld.net/novel/a-will-eternal/"));
assert.equal(p.type, "series");
assert.equal(p.site, "lightnovelworld");
assert.equal(p.seriesId, "a-will-eternal");
assert.equal(p.title, "A Will Eternal");
assert.equal(p.cover, "https://lightnovelworld.net/wp-content/uploads/awe.webp");
});
test("lightnovelworld.detect strips the chapter suffix off the heading", () => {
@@ -141,8 +128,6 @@ test("lightnovelworld.detect strips the chapter suffix off the heading", () => {
assert.equal(p.chapterLabel, "Chapter 1298");
assert.equal(p.title, "A Will Eternal");
assert.equal(p.seriesUrl, "https://lightnovelworld.net/novel/a-will-eternal/");
// Chapter pages have no cover; the merge in bookmarkCurrent keeps the stored one.
assert.equal(p.cover, "");
});
test("lightnovelworld.detect returns other for non-series paths", () => {