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:
@@ -34,9 +34,6 @@ let metaTags = {};
|
||||
// document.title. comix's SPA rewrites this on client routing but never
|
||||
// og:title, so the comix adapter reads it instead. Reassigned per test.
|
||||
let docTitle = "";
|
||||
// img[alt] elements comix's coverFromPage() scans. Reassigned per test; each
|
||||
// entry is {alt, src}.
|
||||
let pageImages = [];
|
||||
globalThis.document = {
|
||||
querySelector(sel) {
|
||||
const m = sel.match(/^meta\[property="([^"]+)"\]$/);
|
||||
@@ -44,11 +41,8 @@ globalThis.document = {
|
||||
const v = metaTags[m[1]];
|
||||
return v == null ? null : { getAttribute: () => v };
|
||||
},
|
||||
querySelectorAll(sel) {
|
||||
if (sel !== "img[alt]") return [];
|
||||
return pageImages.map((img) => ({
|
||||
getAttribute: (attr) => img[attr] ?? null,
|
||||
}));
|
||||
querySelectorAll() {
|
||||
return [];
|
||||
},
|
||||
addEventListener() {},
|
||||
get title() {
|
||||
@@ -100,7 +94,7 @@ test("stripBuildHash ignores suffixes that are not exactly 8 hex chars", () => {
|
||||
// ============================================================
|
||||
|
||||
test("asura.detect reads a series page, stripping the hash from the id only", () => {
|
||||
metaTags = { "og:title": "Solo Leveling | Asura Scans", "og:image": "https://cdn.example/x.jpg" };
|
||||
metaTags = { "og:title": "Solo Leveling | Asura Scans" };
|
||||
const p = asura.detect(loc("https://asurascans.com/comics/solo-leveling-059befe1"));
|
||||
assert.equal(p.type, "series");
|
||||
assert.equal(p.site, "asura");
|
||||
@@ -108,12 +102,11 @@ test("asura.detect reads a series page, stripping the hash from the id only", ()
|
||||
// seriesUrl keeps the hash: navigation needs the current one (stale ones 302).
|
||||
assert.equal(p.seriesUrl, "https://asurascans.com/comics/solo-leveling-059befe1");
|
||||
assert.equal(p.title, "Solo Leveling");
|
||||
assert.equal(p.cover, "https://cdn.example/x.jpg");
|
||||
assert.equal(p.chapterNum, null);
|
||||
});
|
||||
|
||||
test("asura.detect reads a chapter page including a decimal number", () => {
|
||||
metaTags = { "og:title": "Solo Leveling Chapter 12.5 - Read Online | Asura Scans", "og:image": "" };
|
||||
metaTags = { "og:title": "Solo Leveling Chapter 12.5 - Read Online | Asura Scans" };
|
||||
const url = "https://asurascans.com/comics/solo-leveling-059befe1/chapter/12.5";
|
||||
const p = asura.detect(loc(url));
|
||||
assert.equal(p.type, "chapter");
|
||||
@@ -150,7 +143,7 @@ test("asura.latestChapterFromAnchors returns null when nothing matches", () => {
|
||||
// ============================================================
|
||||
|
||||
test("demonic.detect reads a series page", () => {
|
||||
metaTags = { "og:title": "The World After The Fall", "og:image": "https://cdn.example/y.jpg" };
|
||||
metaTags = { "og:title": "The World After The Fall" };
|
||||
const p = demonic.detect(loc("https://demonicscans.org/manga/the-world-after-the-fall"));
|
||||
assert.equal(p.type, "series");
|
||||
assert.equal(p.site, "demonic");
|
||||
@@ -159,7 +152,7 @@ test("demonic.detect reads a series page", () => {
|
||||
});
|
||||
|
||||
test("demonic.detect reads a chapter page and strips the suffix from the title", () => {
|
||||
metaTags = { "og:title": "The World After The Fall Chapter 3", "og:image": "" };
|
||||
metaTags = { "og:title": "The World After The Fall Chapter 3" };
|
||||
const p = demonic.detect(loc("https://demonicscans.org/title/the-world-after-the-fall/chapter/3/1"));
|
||||
assert.equal(p.type, "chapter");
|
||||
assert.equal(p.chapterNum, 3);
|
||||
@@ -247,26 +240,6 @@ test("comix parses decimal chapter numbers", () => {
|
||||
assert.equal(p.chapterNum, 80.5);
|
||||
});
|
||||
|
||||
test("comix.detect reads the cover from an img whose alt matches the cleaned title", () => {
|
||||
metaTags = { "og:title": COMIX_STALE_HOME };
|
||||
docTitle = "Dungeons and Crayons";
|
||||
pageImages = [
|
||||
{ alt: "Some Other Series", src: "https://cdn.example/other.jpg" },
|
||||
{ alt: "Dungeons and Crayons", src: "https://cdn.example/cover.jpg" },
|
||||
];
|
||||
const p = comix.detect(loc("https://comix.to/title/n8we-dungeons-and-crayons"));
|
||||
assert.equal(p.cover, "https://cdn.example/cover.jpg");
|
||||
});
|
||||
|
||||
test("comix.detect leaves cover empty when no img alt matches the title", () => {
|
||||
metaTags = {};
|
||||
docTitle = "Dungeons and Crayons";
|
||||
pageImages = [{ alt: "Some Other Series", src: "https://cdn.example/other.jpg" }];
|
||||
const p = comix.detect(loc("https://comix.to/title/n8we-dungeons-and-crayons"));
|
||||
assert.equal(p.cover, "");
|
||||
pageImages = [];
|
||||
});
|
||||
|
||||
test("comix ignores unrelated paths", () => {
|
||||
assert.equal(comix.detect(loc("https://comix.to/browse")).type, "other");
|
||||
});
|
||||
@@ -308,23 +281,18 @@ const KAGANE_SERIES = "019f84bc-9ba0-7ed9-86f5-8b905ec7c28b";
|
||||
const KAGANE_BOOK = "019fa2e0-6dbd-73ca-b40b-fe06ab75eb0e";
|
||||
|
||||
test("kagane detects a series page", () => {
|
||||
metaTags = {
|
||||
"og:title": "Infinite Decryption: The Strongest Level 0",
|
||||
"og:image": "https://kagane.to/api/v2/image/abc/compressed",
|
||||
};
|
||||
metaTags = { "og:title": "Infinite Decryption: The Strongest Level 0" };
|
||||
const p = kagane.detect(loc("https://kagane.to/series/" + KAGANE_SERIES));
|
||||
assert.equal(p.type, "series");
|
||||
assert.equal(p.site, "kagane");
|
||||
assert.equal(p.seriesId, KAGANE_SERIES);
|
||||
assert.equal(p.title, "Infinite Decryption: The Strongest Level 0");
|
||||
assert.equal(p.cover, "https://kagane.to/api/v2/image/abc/compressed");
|
||||
assert.equal(p.seriesUrl, "https://kagane.to/series/" + KAGANE_SERIES);
|
||||
});
|
||||
|
||||
test("kagane reads the chapter number out of og:title", () => {
|
||||
metaTags = {
|
||||
"og:title": "Infinite Decryption: The Strongest Level 0 - Chapter 41 - Episode 41",
|
||||
"og:image": "https://kagane.to/api/v2/image/abc/compressed",
|
||||
};
|
||||
const p = kagane.detect(
|
||||
loc("https://kagane.to/series/" + KAGANE_SERIES + "/reader/" + KAGANE_BOOK)
|
||||
@@ -341,10 +309,7 @@ test("kagane reads the chapter number out of og:title", () => {
|
||||
// no episode name, because the book carries volume_no and an empty title.
|
||||
// Captured live 2026-08-08 from SP Baby.
|
||||
test("kagane reads through a Volume-numbered chapter suffix", () => {
|
||||
metaTags = {
|
||||
"og:title": "SP Baby - Volume 1 Chapter 1",
|
||||
"og:image": "https://kagane.to/api/v2/image/abc/compressed",
|
||||
};
|
||||
metaTags = { "og:title": "SP Baby - Volume 1 Chapter 1" };
|
||||
const p = kagane.detect(
|
||||
loc("https://kagane.to/series/" + KAGANE_SERIES + "/reader/" + KAGANE_BOOK)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user