Repository navigation
fix(english): move Novel Bin to standalone novelbin.xyz JSON API plugin - #2793
rajarsheechatterjee merged 3 commits into
Conversation
Novel Bin now runs on novelbin.xyz, a single-page app that loads its data as JSON. novelbin.com, which the readnovelfull entry targeted, returns HTTP 503, and chapters of novels such as "I Got My Cheat Skill by Acting My Way into a Horror Protagonist Role" failed with "Chapter not found (404)". Replace the readnovelfull entry with a standalone plugin that reads the site's own /api/cdn/ JSON route: popular, latest, genre and status listings, novel details, chapter list, chapter text, and search over the site's search index (loaded once per session). The id stays "novelbin" and the version moves to 3.0.0. Paths saved by the old plugin (b/<slug> and b/<slug>/<chapter-slug>) still resolve, since the novel slugs and chapter title slugs match. Fixes lnreader#2791
[Medium impact] The change reaches Novel Bin’s browsing and reading workflows. The PR appears safe to merge, with non-blocking fixes for exported covers, browse errors, and repeated search downloads. FindingsSummaryMoves Novel Bin from the shared HTML parser to a standalone
Reviews (1) · Last reviewed commit: "fix(english/novelbin): move Novel Bin to..." · Reviewed by Greptile |
| if (path.startsWith('b/')) { | ||
| return this.site + 'novel/' + this.getSlug(path); | ||
| } | ||
| return this.site + path; |
There was a problem hiding this comment.
resolveUrl adds the site address to absolute cover URLs. EPUB export passes sourceNovel.cover through this method, turning an absolute cover into https://novelbin.xyz/https://.... The image then cannot load. This also affects defaultCover, which is an absolute URL. Return absolute URLs unchanged before joining relative paths.
| return this.site + path; | |
| if (/^https?:\/\//i.test(path)) return path; | |
| return this.site + path; |
Knowledge Base Used: Plugin authoring contract
There was a problem hiding this comment.
Fixed in 86e2bdc. resolveUrl now returns http(s) URLs unchanged, so covers and defaultCover keep working (EPUB export included). Old b/ paths still map to the novel page. A regression check confirms an absolute cover URL passes through unchanged.
| // Missing files (unknown slugs, pages past the end) come back as 403/404. | ||
| if (!response.ok) return null; |
There was a problem hiding this comment.
fetchJson treats every HTTP error as a missing file, including rate limits and server failures. If both list requests fail with 429 or 503, popularNovels returns an empty page instead of reporting the failure. Keep the expected 403/404 fallback, but throw an error with the status for other failures so users do not mistake an outage for an empty catalog.
| // Missing files (unknown slugs, pages past the end) come back as 403/404. | |
| if (!response.ok) return null; | |
| // Missing files (unknown slugs, pages past the end) come back as 403/404. | |
| if (response.status === 403 || response.status === 404) return null; | |
| if (!response.ok) { | |
| throw new Error( | |
| `Could not reach site (${response.status}: ${response.statusText}).`, | |
| ); | |
| } |
Knowledge Base Used:
There was a problem hiding this comment.
Fixed in 86e2bdc, with a follow-up in 266c810. fetchJson returns null only for 403/404. That is how the site answers missing files (unknown slugs, pages past the end), so the web_ → plain list fallback still works. Any other non-OK status (429, 5xx) now throws Could not reach site (<status>: …) instead of showing an empty catalog. 266c810 also separates a Cloudflare challenge (a cf-mitigated: challenge header, or a non-JSON 403) from a missing file and tells the user to open the site in WebView. Regression checks cover 503 and 429 on listings, 500 on a chapter, the 403/404 fallback on web_ lists, and the challenge case.
| if (!this.searchIndex) { | ||
| this.searchIndex = await this.fetchList<SearchEntry[]>('search'); |
There was a problem hiding this comment.
searchIndex is filled only after the download finishes. Overlapping first searches therefore each download the roughly 7.5 MB index. The search input accepts Enter while a search is still running, so users can trigger this through the existing page. Cache the pending promise as well as the finished index, and clear it on failure.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Fixed in 86e2bdc. searchNovels now caches the pending download promise, so overlapping first searches share one fetch of the ~7.5 MB index. If the download fails, the cached promise is cleared and the next search retries. Regression checks confirm three concurrent searches trigger one download, every waiter sees a failed download, and the next search retries.
…ch download - resolveUrl returns http(s) URLs unchanged, so covers and defaultCover survive EPUB export; old b/ paths still map to the novel page. - fetchJson returns null only for 403/404 (missing files, which keeps the web_ -> plain list fallback); other errors such as 429 or 5xx now throw with the status instead of showing an empty catalog. - searchNovels caches the pending search-index download so overlapping first searches fetch the ~7.5 MB index once, and clears it on failure so the next search retries.
Summary
Fixes #2791.
Novel Bin now runs on novelbin.xyz. novelbin.com, which the plugin used through the ReadNovelFull template, returns HTTP 503 on every page from here, and the plugin failed to load chapters with "Chapter not found (404)".
novelbin.xyz is a different kind of site: a JavaScript app that loads its data as JSON. The ReadNovelFull template can't parse it, so this PR replaces the
novelbintemplate entry with a standalone plugin that reads the site's own/api/cdn/JSON route.novelbin, so installed users get the update. The version moves to 3.0.0 because the site changed.b/<slug>andb/<slug>/<chapter-slug>) still open. After a refresh, the novel's chapter list uses the new paths.The novel from #2710 ("10,000x Rebate System: Every Return Makes Me Stronger") also loads on the new site, Chapter 1 included, so that report is likely fixed too. #2733 describes the same symptom without naming a novel.
Screenshots (plugin playground, live novelbin.xyz)
Latest novels:
Search for the #2791 novel:
Novel details:
Chapter list:
#2791 Chapter 1 text:
A path saved by the old plugin (
b/shadow-slave/chapter-10-first-man-down) still loads:Review follow-ups (
86e2bdc,266c810):resolveUrlreturns absolute cover URLs unchanged.a009841and pass now.This PR was written with an AI agent (Claude Code), so please weigh the review accordingly.
What Changed
plugins/english/novelbin.tsplugin that talks to the novelbin.xyz JSON API for popular/latest listings, search, novel details, chapter lists and chapter content. It keeps absolute URLs as they are, reports site outages and Cloudflare challenges fromfetchJson, and shares the search download path.novelbinentry fromplugins/multisrc/readnovelfull/sources.json, so the ReadNovelFull template no longer generates this source.public/static/multisrc/readnovelfull/novelbin/icon.pngtopublic/static/src/en/novelbin/icon.png(100% rename).🤖 Generated with Claude Code
Risk Assessment
✅ Low: The standalone novelbin.xyz plugin matches the intent. The challenge-detection fix handles Cloudflare 403s separately from the site's JSON 403 "missing file" responses, and the legacy b/ paths are still handled.
Testing
I ran the live check against novelbin.xyz.
popularNovels,searchNovels,parseNovel(272 chapters) andparseChapter(18,456 chars) all passed. I confirmed the live site answers a missing slug with 403 andapplication/json, which the plugin maps to null. The Cloudflare-challenge and 500 behaviours were only driven withfetchstubbed, not against the live site, so they are untested live. I did not run the exact issue novel through the app or on Android.Evidence: Challenge-handling transcript
Pipeline
Updates from git push no-mistakes
✅ **Intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 1 issue found → auto-fixed ✅
plugins/english/novelbin.ts:125- fetchJson treats every 403 as 'file missing' and returns null. A Cloudflare/anti-bot 403 from the site would be reported as 'Chapter not found, it may have been removed' (parseChapter, ~line 273), 'Novel not found' (getNovel), or silently as an empty list (popularNovels/fetchList), and an empty chapter list in parseNovel. That is the same misleading error the issue complained about. Siblings: getChapterList returns [] on null, fetchList falls back on null, searchNovels index load. Only 404 should map to null, or the 403 should be distinguished by a body/header check.🔧 Fix applied.
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
npm cinpm run check:plugin -- plugins/english/novelbin.ts(live novelbin.xyz)curlof a nonexistent/api/cdn/novel/<slug>.json(returned 403application/json)Bundled the real plugin with esbuild and drovefetchJsonandparseChapterwithfetchstubbed to return Cloudflare-style and error responses✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.