Skip to content

fix(english): move Novel Bin to standalone novelbin.xyz JSON API plugin - #2793

Merged
rajarsheechatterjee merged 3 commits into
lnreader:masterfrom
RibatTRW:fm/lnreader-novelbin-chapter-404-2791
Oct 28, 2026
Merged

rajarsheechatterjee merged 3 commits into
lnreader:masterfrom
RibatTRW:fm/lnreader-novelbin-chapter-404-2791

Conversation

@RibatTRW

@RibatTRW RibatTRW commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

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 novelbin template entry with a standalone plugin that reads the site's own /api/cdn/ JSON route.

  • Id unchanged: the id stays novelbin, so installed users get the update. The version moves to 3.0.0 because the site changed.
  • Features: popular novels, paged latest novels, genre and status filters, novel details, chapter list, chapter text, and search.
  • Search filters the site's own search index, as the site does. The index (about 7.5 MB) downloads on the first search and stays in memory after that.
  • Existing library entries keep working. Novel slugs and chapter-title slugs are the same on novelbin.xyz, so paths saved by the old plugin (b/<slug> and b/<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:

Playground: Novel Bin latest novels

Search for the #2791 novel:

Playground: Novel Bin search

Novel details:

Playground: Novel Bin novel details

Chapter list:

Playground: Novel Bin chapter list

#2791 Chapter 1 text:

Playground: Novel Bin chapter 1 of the #2791 novel

A path saved by the old plugin (b/shadow-slave/chapter-10-first-man-down) still loads:

Playground: Novel Bin legacy chapter path

Review follow-ups (86e2bdc, 266c810):

  • resolveUrl returns absolute cover URLs unchanged.
  • Server errors and rate limits (429, 5xx) now raise an error instead of showing an empty catalog. A Cloudflare challenge raises "open it in WebView".
  • Overlapping searches share one download of the search index.
  • Each change is covered by a regression check. Those checks fail on a009841 and pass now.

This PR was written with an AI agent (Claude Code), so please weigh the review accordingly.

What Changed

  • Added a standalone plugins/english/novelbin.ts plugin 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 from fetchJson, and shares the search download path.
  • Removed the novelbin entry from plugins/multisrc/readnovelfull/sources.json, so the ReadNovelFull template no longer generates this source.
  • Moved the icon from public/static/multisrc/readnovelfull/novelbin/icon.png to public/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) and parseChapter (18,456 chars) all passed. I confirmed the live site answers a missing slug with 403 and application/json, which the plugin maps to null. The Cloudflare-challenge and 500 behaviours were only driven with fetch stubbed, not against the live site, so they are untested live. I did not run the exact issue novel through the app or on Android.

  • Live validation: ✅ go - 2 of 4 scenarios driven live against the product
Scenario Result Live Evidence
Open a novel, list its chapters and read a chapter on novelbin.xyz ✅ pass live check:plugin output: popularNovels, searchNovels, parseNovel and parseChapter all PASS
Missing file (403 JSON) maps to null rather than an error ✅ pass live curl returned 403 application/json for an unknown slug; stubbed run returned null for 403 JSON
Cloudflare challenge (cf-mitigated header, or 403 non-JSON) throws the WebView security-check error ⏸️ untested no The prior payload recorded this only against a stubbed network response (live=false), so it did not establish a live result. The live site was not serving a challenge, and one cannot be triggered on d…
Other non-OK statuses (500) still throw 'Could not reach site' ⏸️ untested no The prior payload recorded this only against a stubbed network response (live=false), so it did not establish a live result. A real 500 cannot be triggered on demand against the live site.
Evidence: Challenge-handling transcript
https://novelbin.xyz/api/cdn/novel/x.json {
  headers: {
    Connection: 'keep-alive',
    Accept: '*/*',
    'Accept-Language': '*',
    'Sec-Fetch-Mode': 'cors',
    'Accept-Encoding': 'gzip, deflate'
  }
}
500 => threw: Could not reach site (500: ) try to open in webview.
https://novelbin.xyz/api/cdn/novel/x.json {
  headers: {
    Connection: 'keep-alive',
    Accept: '*/*',
    'Accept-Language': '*',
    'Sec-Fetch-Mode': 'cors',
    'Accept-Encoding': 'gzip, deflate'
  }
}
cf-mitigated challenge 403 html => threw: Site is showing a security check, please open it in WebView and complete it.
https://novelbin.xyz/api/cdn/novel/x.json {
  headers: {
    Connection: 'keep-alive',
    Accept: '*/*',
    'Accept-Language': '*',
    'Sec-Fetch-Mode': 'cors',
    'Accept-Encoding': 'gzip, deflate'
  }
}
403 html no header => threw: Site is showing a security check, please open it in WebView and complete it.
https://novelbin.xyz/api/cdn/novel/x.json {
  headers: {
    Connection: 'keep-alive',
    Accept: '*/*',
    'Accept-Language': '*',
    'Sec-Fetch-Mode': 'cors',
    'Accept-Encoding': 'gzip, deflate'
  }
}
200 with cf-mitigated => threw: Site is showing a security check, please open it in WebView and complete it.
https://novelbin.xyz/api/cdn/novel/x.json {
  headers: {
    Connection: 'keep-alive',
    Accept: '*/*',
    'Accept-Language': '*',
    'Sec-Fetch-Mode': 'cors',
    'Accept-Encoding': 'gzip, deflate'
  }
}
403 json (missing file) => returned null
https://novelbin.xyz/api/cdn/novel/919221.json {
  headers: {
    Connection: 'keep-alive',
    Accept: '*/*',
    'Accept-Language': '*',
    'Sec-Fetch-Mode': 'cors',
    'Accept-Encoding': 'gzip, deflate'
  }
}
parseChapter under challenge => threw: Site is showing a security check, please open it in WebView and complete it.

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.

  • Live validation: ✅ go - 2 of 4 scenarios driven live against the product
Scenario Result Live Evidence
Open a novel, list its chapters and read a chapter on novelbin.xyz ✅ pass live check:plugin output: popularNovels, searchNovels, parseNovel and parseChapter all PASS
Missing file (403 JSON) maps to null rather than an error ✅ pass live curl returned 403 application/json for an unknown slug; stubbed run returned null for 403 JSON
Cloudflare challenge (cf-mitigated header, or 403 non-JSON) throws the WebView security-check error ⏸️ untested no The prior payload recorded this only against a stubbed network response (live=false), so it did not establish a live result. The live site was not serving a challenge, and one cannot be triggered on d…
Other non-OK statuses (500) still throw 'Could not reach site' ⏸️ untested no The prior payload recorded this only against a stubbed network response (live=false), so it did not establish a live result. A real 500 cannot be triggered on demand against the live site.
  • npm ci
  • npm run check:plugin -- plugins/english/novelbin.ts (live novelbin.xyz)
  • curl of a nonexistent /api/cdn/novel/&lt;slug&gt;.json (returned 403 application/json)
  • Bundled the real plugin with esbuild and drove fetchJson and parseChapter with fetch stubbed to return Cloudflare-style and error responses
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

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
@greptile-apps

greptile-apps Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[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.

Findings

  1. P2 Exported covers disappear ▶
  2. P2 Failed requests look empty ▶
  3. P2 Search downloads repeat ▶

Summary

Moves Novel Bin from the shared HTML parser to a standalone novelbin.xyz JSON plugin.

  • Novel Bin browse and search now use novelbin.xyz’s JSON catalog.
  • Novel Bin chapter paths now open text from the new site's JSON files.

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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Exported covers disappear

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.

Suggested change
return this.site + path;
if (/^https?:\/\//i.test(path)) return path;
return this.site + path;

Knowledge Base Used: Plugin authoring contract

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread plugins/english/novelbin.ts Outdated
Comment on lines +129 to +130
// Missing files (unknown slugs, pages past the end) come back as 403/404.
if (!response.ok) return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Failed requests look empty

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.

Suggested change
// 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:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread plugins/english/novelbin.ts Outdated
Comment on lines +289 to +290
if (!this.searchIndex) {
this.searchIndex = await this.fetchList<SearchEntry[]>('search');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Search downloads repeat

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!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@rajarsheechatterjee
rajarsheechatterjee merged commit 06694db into lnreader:master Oct 28, 2026
3 checks passed
@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Oct 31, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[novelbin] Empty chapter: I Got My Cheat Skill by Acting My Way into a Horror Protagonist Role — Chapter 1: Becoming a Horror NPC

2 participants