From 3f897b6a650d4959f5abc90f7658adaf7b8568ae Mon Sep 17 00:00:00 2001 From: Sulthan Zaki Date: Tue, 11 Aug 2026 20:23:51 +0700 Subject: [PATCH] Remove the client latest-chapter scan for lightnovelworld (#91) The scan was scoped to the stored series_id, which since #89 is the Series slug while chapter addresses carry a Chapter Slug, so on divergent novels it matched zero anchors. The Poll refreshes lnw Series on a one-hour cooldown, four times faster than the client throttle, so the scan optimised nothing while its unscoped alternative would have let a public wpdiscuz comment anchor write a Latest Chapter every Reader of the row shares. Delete the adapter method; the shared computation yields null for an adapter without a scanner, and the background refresh skips the Site before the due filter so no Series page is fetched and no freshness timestamp is recorded. --- userscript/novel-bookmark.user.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/userscript/novel-bookmark.user.js b/userscript/novel-bookmark.user.js index 0241f11..847647e 100644 --- a/userscript/novel-bookmark.user.js +++ b/userscript/novel-bookmark.user.js @@ -218,12 +218,6 @@ } return { type: "other" }; }, - latestChapterFromAnchors(anchors, seriesId) { - return maxChapter( - anchors, - new RegExp("lightnovelworld\\.net/" + escapeRe(seriesId) + "-chapter-([0-9.]+)/") - ); - }, }; const ADAPTERS = [novelfull, lightnovelworld]; @@ -342,10 +336,11 @@ // Highest chapter the site lists, or null when the markup yields nothing. // seriesId is only consulted by adapters whose pages carry other series' - // chapter links; the rest ignore it. + // chapter links. An adapter without a scanner (lightnovelworld — see the + // AGENTS.md entry) yields null, not an error. function computeLatestChapter(site, anchors, seriesId) { const a = adapterFor(site); - return a ? a.latestChapterFromAnchors(anchors, seriesId) : null; + return a && a.latestChapterFromAnchors ? a.latestChapterFromAnchors(anchors, seriesId) : null; } function currentSite() { @@ -861,6 +856,14 @@ const site = currentSite(); if (!site) return; + // A Site with no client scanner (lightnovelworld) is refreshed by the Poll + // on a cooldown shorter than the client throttle; fetching its pages here + // would be a megabyte-scale request whose result is discarded. Skipping + // before the due filter records no freshness timestamp and consumes no + // per-navigation batch slot. + const adapter = adapterFor(site); + if (!adapter.latestChapterFromAnchors) return; + const checked = loadLastChecked(); const now = Date.now(); const due = state.list @@ -871,7 +874,6 @@ .slice(0, LATEST_CHECK_BATCH); if (due.length === 0) return; - const adapter = adapterFor(site); for (const bm of due) { // Recorded even when the fetch fails, so a broken series is retried on // the next throttle window rather than on every single page load. @@ -1750,7 +1752,7 @@ // Exposes pure logic only — see userscript/test/novel-logic.test.js. // ============================================================ if (typeof window === "undefined" && typeof module === "object" && module.exports) { - module.exports = { novelfull, lightnovelworld, anchorsFromHTML, statusOf, kindOf, maxChapter, escapeRe, repairLnwStaleRow }; + module.exports = { novelfull, lightnovelworld, anchorsFromHTML, statusOf, kindOf, maxChapter, escapeRe, computeLatestChapter, repairLnwStaleRow }; } // ============================================================