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:
@@ -33,16 +33,11 @@
|
||||
// ============================================================
|
||||
// Site adapters
|
||||
//
|
||||
// Page type + IDs come from URL regex (most stable); title/cover come from
|
||||
// og: meta tags (with the novelfull name= meta as the exception).
|
||||
// Page type + IDs come from URL regex (most stable); the title comes from
|
||||
// og: meta tags or the page's own heading. Covers are never read here: the
|
||||
// backend acquires and serves them itself (ADR-0007).
|
||||
// ============================================================
|
||||
|
||||
|
||||
function meta(prop) {
|
||||
const el = document.querySelector('meta[property="' + prop + '"]');
|
||||
return el ? el.getAttribute("content") : null;
|
||||
}
|
||||
|
||||
// Chapter lists are read from two places: the page we are standing on, and
|
||||
// series pages fetched in the background. Both are reduced to {href, text}
|
||||
// pairs so each adapter needs only one rule for picking the latest chapter.
|
||||
@@ -66,12 +61,6 @@
|
||||
return out;
|
||||
}
|
||||
|
||||
// novelfull ships no og: tags at all — its cover lives on a name= meta.
|
||||
function metaName(name) {
|
||||
const el = document.querySelector('meta[name="' + name + '"]');
|
||||
return el ? el.getAttribute("content") : null;
|
||||
}
|
||||
|
||||
function escapeRe(s) {
|
||||
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
@@ -108,7 +97,6 @@
|
||||
// h3.title on a chapter page is the *chapter's* title; the breadcrumb
|
||||
// link back to the series page carries the series name.
|
||||
title: back ? (back.textContent || "").trim() : "",
|
||||
cover: metaName("image") || "",
|
||||
seriesUrl: loc.origin + "/" + m[1] + ".html",
|
||||
chapterLabel: "Chapter " + m[2],
|
||||
chapterNum: isNaN(num) ? null : num,
|
||||
@@ -124,7 +112,6 @@
|
||||
site: this.site,
|
||||
seriesId: m[1],
|
||||
title: h3 ? (h3.textContent || "").trim() : "",
|
||||
cover: metaName("image") || "",
|
||||
seriesUrl: loc.origin + "/" + m[1] + ".html",
|
||||
chapterLabel: null,
|
||||
chapterNum: null,
|
||||
@@ -157,9 +144,6 @@
|
||||
seriesId: m[1],
|
||||
// The heading is "<Series> Chapter <n>"; drop the suffix.
|
||||
title: heading.replace(/\s*Chapter\s+[0-9.]+\s*$/i, "").trim(),
|
||||
// Chapter pages carry no og:image. Empty is safe: every write merges
|
||||
// against the cached row, which keeps the cover the series page gave.
|
||||
cover: "",
|
||||
seriesUrl: "https://lightnovelworld.net/novel/" + m[1] + "/",
|
||||
chapterLabel: "Chapter " + m[2],
|
||||
chapterNum: isNaN(num) ? null : num,
|
||||
@@ -175,7 +159,6 @@
|
||||
site: this.site,
|
||||
seriesId: m[1],
|
||||
title: h1 ? (h1.textContent || "").trim() : "",
|
||||
cover: meta("og:image") || "",
|
||||
seriesUrl: "https://lightnovelworld.net/novel/" + m[1] + "/",
|
||||
chapterLabel: null,
|
||||
chapterNum: null,
|
||||
@@ -286,6 +269,10 @@
|
||||
async function apiPut(key, obj, { sendStatus = false } = {}) {
|
||||
const body = Object.assign({}, obj);
|
||||
if (!sendStatus) delete body.status;
|
||||
// Covers belong to the backend, which acquires and serves them itself
|
||||
// (ADR-0007) and ignores an incoming one; a third-party address must never
|
||||
// go back on the wire.
|
||||
delete body.cover;
|
||||
const res = await fetch(API_BASE + "/bookmarks/" + encodeURIComponent(key), {
|
||||
method: "PUT",
|
||||
headers: authHeaders({ "Content-Type": "application/json" }),
|
||||
@@ -613,7 +600,6 @@
|
||||
series_id: p.seriesId,
|
||||
title: p.title || (existing && existing.title) || p.seriesId,
|
||||
series_url: p.seriesUrl || (existing && existing.series_url) || "",
|
||||
cover: p.cover || (existing && existing.cover) || "",
|
||||
last_chapter: p.chapterLabel || (existing && existing.last_chapter) || "",
|
||||
last_chapter_num:
|
||||
p.chapterNum != null ? p.chapterNum : existing ? existing.last_chapter_num : null,
|
||||
@@ -636,7 +622,6 @@
|
||||
series_id: p.seriesId,
|
||||
title: existing.title || p.title || p.seriesId,
|
||||
series_url: existing.series_url || p.seriesUrl || "",
|
||||
cover: existing.cover || p.cover || "",
|
||||
last_chapter: p.chapterLabel || existing.last_chapter || "",
|
||||
last_chapter_num: p.chapterNum != null ? p.chapterNum : existing.last_chapter_num,
|
||||
last_chapter_url: p.chapterUrl || "",
|
||||
@@ -1147,7 +1132,15 @@
|
||||
return el("div", { class: "item" + heat }, [
|
||||
el("a", { class: "go", href: cont }, [
|
||||
b.cover
|
||||
? el("img", { class: "cover", src: b.cover, loading: "lazy", alt: "" })
|
||||
? el("img", {
|
||||
class: "cover",
|
||||
src: b.cover,
|
||||
loading: "lazy",
|
||||
alt: "",
|
||||
// A Cover that will not load shows the designed placeholder
|
||||
// rather than the browser's broken-image glyph (#47).
|
||||
onerror: (e) => e.target.replaceWith(el("div", { class: "cover ph" })),
|
||||
})
|
||||
: el("div", { class: "cover ph" }),
|
||||
]),
|
||||
el("div", { class: "meta" }, [
|
||||
|
||||
Reference in New Issue
Block a user