Skip to content

fix(english): report Cloudflare challenge in WTR-LAB plugin - #2651

Open
RibatTRW wants to merge 5 commits into
lnreader:masterfrom
RibatTRW:fm/lnreader-wtrlab-nextdata
Open

RibatTRW wants to merge 5 commits into
lnreader:masterfrom
RibatTRW:fm/lnreader-wtrlab-nextdata

Conversation

@RibatTRW

@RibatTRW RibatTRW commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Intent

Can we do a plugin PR for https://wtr-lab.com/ - the previous PRs submitted by other users did some, seems to fix the issue, let's do our own fix; the older PRs can be referenced for help.

The problem: when the WTR-LAB plugin is opened in the LNReader app, a log reports "Could not find NEXT_DATA on novel finder page".

https://wtr-lab.com/en - this should be the correct website where all the books are hosted.

Follow-up on the PR: show the WebView hint only when Cloudflare's own marker is in the response, instead of on every 403 or 503 (the recommended option, chosen with: "lets do the recommended").

Summary

When a user opens WTR-LAB, the plugin shows the error message "Could not find NEXT_DATA on novel finder page".
The cause is a Cloudflare challenge page. The plugin reads this page as the novel finder page.
This PR shows a clear error message when Cloudflare sends a challenge. This error message tells the user to open WTR-LAB in WebView.
For other failed responses, the plugin shows a plain HTTP error message.
This PR does not bypass the challenge.

Root cause

wtr-lab.com uses a Cloudflare managed challenge.
In our tests, each request got HTTP 403 with the header cf-mitigated: challenge.
The plugin did not examine the response. It read the challenge page as site data.
The site pages did not change. With a browser cf_clearance cookie, the old code (v1.2.5) loads all pages correctly.

Changes

  • A new check, assertNotChallenged, finds the cf-mitigated: challenge header. Then it shows the WebView error message.
  • fetchSite uses this check. For other failed responses, it shows Request failed (HTTP <status>): <url>.
  • Both error messages keep the HTTP status.
  • Requests that have no other use on failure use fetchSite. These are the novel finder, Latest, the novel page, the chapter list and the chapter page.
  • Requests that already handle failures use only the challenge check. Their other failure handling does not change.
  • The chapter list does not hide the challenge error message. Before this change, it returned an empty list.
  • The plugin version changes from 1.2.5 to 1.2.6.
Each request to wtr-lab.com and its check
Request Check Behavior on other failures
Novel finder page and _next/data JSON fetchSite HTTP error message
Latest (api/home/recent) fetchSite HTTP error message
Novel page fetchSite HTTP error message
Chapter list (api/chapters) fetchSite Logged, list ends (no change)
Chapter page (__NEXT_DATA__ fallback, key page) fetchSite HTTP error message
Tokens page Challenge check only Tokens stay empty (no change)
Key scripts Challenge check only Tries the next script (no change)
Reader API (api/reader/get) Challenge check only Tries the next translation mode (no change)
Chapter content (content_url) Challenge check only Returns no content (no change)
Sign-in link and session check Challenge check only Status note (no change)

The translate request goes to Google, not to wtr-lab.com. It does not use the check.

Testing

Check Result Note
Plugin calls on the live site, no cookie Pass Popular, Latest, search and novel page show the WebView error message.
Plugin calls on the live site, browser cf_clearance cookie Pass Popular, Latest, search, novel (529 chapters) and chapter text load. Tested on a0bcfa1.
Local test server: plain HTTP 403, and HTTP 403 with cf-mitigated: challenge Pass The plain HTTP 403 gives the HTTP error message. Only the marked response gives the WebView error message. Run by the no-mistakes Test step.
Novel that does not exist, cf_clearance cookie Pass Shows Request failed (HTTP 404): <url>, not the WebView error message.
Plugin playground, cf_clearance cookie Pass Popular, search, novel page (150 chapters) and chapter text load. Tested on 56cd88d. Covers do not load in the playground.
npm run check:plugin -- plugins/english/wtrlab.ts Inconclusive Cloudflare sends HTTP 403. The result on master is the same.
npx prettier --check, npm run build:compile Pass
ESLint on wtrlab.ts 0 errors, 3 warnings The 3 no-explicit-any warnings are also on master. Refer to Notes.
Real LNReader app Not tested
Other networks or regions Not tested All tests used one network.

Notes

  • Prettier changed the format of three old code blocks in parseNovel (from fix(english): resolve debrand tokens for WTR-LAB #2644). These changes are format only. They do not change behavior.
  • ESLint showed a no-control-regex error on the token regex in resolveTokens (from fix(english): resolve debrand tokens for WTR-LAB #2644). This error is also on master. This PR adds one eslint-disable-next-line comment above that line. The regex does not change, so behavior does not change.
  • The challenge check uses only the cf-mitigated: challenge header. The app fetchApi returns the React Native fetch response without changes, so the header is available. We did not test this in the app.

An AI agent (Claude Code) wrote this PR and validated it with no-mistakes. No person reviewed or tested it beyond the items in this description.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 3 of 4 scenarios driven live against the product
Scenario Result Live Evidence
Open WTR-LAB (popular, latest, novel, chapter) against the live Cloudflare-challenged site and get the WebView hint instead of a NEXT_DATA error ✅ pass live pipeline test transcript '--- live'
A plain 403 without the Cloudflare marker gives a generic error and no WebView hint ✅ pass live pipeline test transcript '--- local plain403' (local server)
A 403 with cf-mitigated: challenge gives the WebView hint ✅ pass live pipeline test transcript '--- local cf403' (local server)
With a solved challenge, novels list and chapters parse correctly ⏸️ untested no This needs a browser cf_clearance cookie for wtr-lab.com, which was not available here. Provide one via the plugin's cookie setting and re-run.
  • npm ci
  • npm run check:plugin -- plugins/english/wtrlab.ts (stopped at siteReachability: HTTP 403 Cloudflare, INCONCLUSIVE)
  • Bundled the plugin with esbuild and called popularNovels, latest, parseNovel and parseChapter against live https://wtr-lab.com
  • Same plugin pointed at a local HTTP server returning a plain 403 and a 403 with cf-mitigated: challenge
✅ **Document** - passed

✅ No issues found.

🔧 **Lint** - 1 issue found → auto-fixed ✅
  • ⚠️ plugins/english/wtrlab.ts:38 - Pre-existing no-control-regex ESLint error at line 38 (control-character range in the macro-token regex). It was not introduced by this change, and fixing it would need a behavior-affecting regex edit.

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Push** - passed

✅ No issues found.

WTR-LAB now answers every request (pages, _next/data, /api) with a
Cloudflare managed challenge (HTTP 403, cf-mitigated: challenge) until it
is solved in WebView. The plugin parsed that "Just a moment..." page as
site markup, so browse/search threw "Could not find __NEXT_DATA__ on
novel finder page", Latest threw a JSON syntax error and parseNovel
returned an empty novel, hiding the real remedy.

Route the page/data fetches that read __NEXT_DATA__ (and the Latest
feed) through a fetchSite helper that throws a WebView hint on 403/503.
Once the challenge is cleared, the existing parsing works unchanged.
@greptile-apps

greptile-apps Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

[Medium risk] Adds error handling for Cloudflare challenges in a plugin.

The PR appears safe to merge, with a non-blocking issue in the recovery instructions for ordinary HTTP failures.

Findings

  1. P2 Wrong recovery instructions ▶

Summary

Adds clearer Cloudflare errors to WTR-LAB fetches and bumps the plugin to 1.2.6.

  • Chapter-list catches now let Cloudflare errors reach the user.
  • The reader checks the challenge header without removing its normal translation fallback.
  • The shared helper should distinguish real challenges from ordinary HTTP failures.
  • RibatTRW acknowledged that some fetch paths remain unguarded and that this change may not fix other causes of missing page data. The supplied review history also already acknowledged the formatting changes.

Reviews (1) · Last reviewed commit: "no-mistakes(review): propagate wtrlab Cl..." · Reviewed by Greptile

Comment thread plugins/english/wtrlab.ts Outdated
Comment on lines +119 to +121
if (res.status === 403 || res.status === 503) {
throw new Error(
`Cloudflare protection detected (HTTP ${res.status}). Please open the plugin in WebView to solve the challenge, then try again.`,

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 Wrong recovery instructions

fetchSite labels every HTTP 403 or 503 as a Cloudflare challenge. If the server returns a normal access denial or temporary outage, users are told to solve a challenge in WebView, which cannot fix that response. Check cf-mitigated: challenge, as parseChapter already does, before showing the Cloudflare message. Report other failed responses separately.

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.

Changed in a0bcfa1 (head is now 3f70416):

  • Detection now uses one rule: the cf-mitigated: challenge header, in a shared assertNotChallenged check. This is the same check parseChapter used.
  • fetchSite shows the WebView error message only for that marked response. Other failed responses throw Request failed (HTTP <status>): <url>. Both errors keep the HTTP status.
  • The other wtr-lab.com requests that already handle a failed response use only the challenge check. These are the tokens page, key scripts, reader API, content_url, sign-in and session check. Their other failure handling does not change.
  • Tested on the live site: a novel that does not exist now gives Request failed (HTTP 404): …, not the WebView error message.

(Reply written by an AI agent.)

…lenge

fetchSite labelled every 403/503 as a Cloudflare challenge, so a plain
origin denial or outage told the user to solve a challenge in WebView.
Detect the challenge with one rule, Cloudflare's own
`cf-mitigated: challenge` marker, in a shared assertNotChallenged helper,
and have fetchSite report any other failed response as a plain error
naming the HTTP status and URL. Both errors carry the status.

Every remaining fetch to wtr-lab.com now applies the same check:
- fetchSite (page or data with nothing to parse on failure): finder,
  _next/data, Latest, novel page, chapter page fallbacks, chapter list.
- challenge check only, keeping their existing failure handling: tokens
  page (non-fatal), key scripts (falls through to the next script),
  reader API (per-mode fallback), content_url payload (null fallback),
  sign-in redeem and session check (status notes).
The Google translate call is not a wtr-lab.com request and is unchanged.

The LNReader app's fetchApi returns React Native's fetch Response
unchanged, so response headers such as cf-mitigated reach the plugin.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant