Skip to content

fix(english/genesis): restore chapter reading via the site's content endpoint - #2684

Open
RibatTRW wants to merge 4 commits into
lnreader:masterfrom
RibatTRW:fm/lnreader-genesis-chapter
Open

RibatTRW wants to merge 4 commits into
lnreader:masterfrom
RibatTRW:fm/lnreader-genesis-chapter

Conversation

@RibatTRW

@RibatTRW RibatTRW commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Intent

Fix the broken genesis source so a reader can read its chapters again, and include screenshots of the working fix in the PR.

plugins/english/genesis.ts lists and opens novels, but reading a chapter fails: the plugin's own live check fails parseChapter with Cannot read properties of undefined (reading 'chapter_content'), while the site's Directus API answers 200 with about 77 KB. So the site is up and serving chapters - the chapter object the plugin reads simply no longer exposes chapter_content. This is one of the plugins this fleet's plugin health sweep confirmed as genuinely broken today (not a site-side block).

Make sure the fix's pull request shows a photo of the plugin working correctly inside the plugin playground, and run that screenshot process headless if possible.

What Changed

  • parseChapter now requests /api/chapters/{id}/content directly instead of scraping a Supabase key from the site's scripts and querying the Supabase chapters table. A failed response, an error payload, or missing or 'None' content throws "This chapter is locked on the site".
  • Chapter listing keeps only chapters with status === 'released'. Locked chapters get a /viewer/locked/{id} path, which parseChapter rejects before any fetch.
  • Drops the cheerio import, adds status to the listing type, and bumps the plugin version from 2.0.1 to 2.0.3.

Risk Assessment

⚠️ Medium: The change rewires chapter reads from the authenticated Supabase query to an unauthenticated site endpoint, and the paid-chapter protection rests on the listing lock gate plus the server's 401 refusal, which the user explicitly accepted; no new defect was found in the diff beyond the already-declined items.

Testing

Baseline and target were both run live: base reproduces the reported parseChapter failure, and target passes popularNovels, searchNovels, parseNovel and parseChapter against genesistudio.com. Adversarial scenarios (listed-locked chapter, stale /viewer/ path for a locked chapter, nonexistent id) all refuse with the locked message, and the hideLocked setting hides locked chapters. The playground was driven headless and produced three screenshots showing free chapter content, the locked refusal, and the novel chapter list. The released-status filter could not be exercised because no unreleased chapter exists on the live site. Overall result: the fix works as intended.

  • Live validation: ✅ go - 8 of 9 scenarios driven live against the product
Scenario Result Live Evidence
Live check on the fixed plugin: popular, search, novel and chapter all pass (npm run check:plugin) ✅ pass live Live check output on target commit (npm run check:plugin -- plugins/english/genesis.ts): popularNovels PASS 245 novels, searchNovels PASS, parseNovel PASS 5 chapters, parseChapter PASS 9120 chars
Baseline reproduction: the base commit's parseChapter fails with the reported error ✅ pass live Live check output on base commit: parseChapter FAIL 'Cannot read properties of undefined (reading chapter_content)' (base genesis.ts run via temporary copy, removed afterwards)
Reader opens a free chapter and gets the full text (parseChapter /viewer/104793 returns body) ✅ pass live Driver /tmp/gdriver/run.cjs against live site: body 9453 chars; playground screenshot 02-parse-chapter-free-chapter.png shows the chapter text rendered
Adversarial: a chapter listed as locked refuses with the locked message instead of fetching a body ✅ pass live Driver run: parseChapter('/viewer/locked/101543') throws 'This chapter is locked on the site'; playground screenshot 03-parse-chapter-locked-refused.png
Adversarial: a stale /viewer/<id> path for a locked chapter is refused by the server's 401 response ✅ pass live Driver run: parseChapter('/viewer/101543') throws 'This chapter is locked on the site'; curl confirms the content endpoint returns HTTP 401 'Authentication required' with chapter_content null for lock…
Adversarial: a nonexistent chapter id is refused rather than returning empty text ✅ pass live Driver run: parseChapter('/viewer/999999999') throws 'This chapter is locked on the site'; curl returns HTTP 404 'Chapter not found or not yet released'
Novel chapter list shows locked chapters with the lock icon and locked path, consistent across the whole novel ✅ pass live Driver run: parseNovel('/novels/BAL') returns 157 chapters; every 🔒-named chapter has a /viewer/locked/ path and vice versa; playground screenshot 04-parse-novel-chapter-list.png shows 157 total chapt…
hideLocked setting removes locked chapters from the listing ✅ pass live Driver run: plugin instance with hideLocked=true, extractChapters for BAL returns zero 🔒 entries. The flag was set on the instance directly rather than through the playground settings UI.
Listing drops chapters whose status is not released (the recorded status=released filter) ⏸️ untested no Live data has no unreleased chapters, and this environment cannot create one on the real site. The filter is present in extractChapters but its negative branch was not exercised live.
  • Evidence: Playground: Genesis Parse Chapter renders a free chapter (/viewer/104793, 9453 characters) (local file: ~/.no-mistakes/evidence/01M4ZVC32WPHMCZ39CV2EB131R/02-parse-chapter-free-chapter.png)
  • Evidence: Playground: Genesis Parse Chapter refuses a locked chapter (/viewer/locked/101543) with 'This chapter is locked on the site' (local file: ~/.no-mistakes/evidence/01M4ZVC32WPHMCZ39CV2EB131R/03-parse-chapter-locked-refused.png)
  • Evidence: Playground: Genesis Parse Novel for /novels/BAL shows the novel with 157 total chapters (local file: ~/.no-mistakes/evidence/01M4ZVC32WPHMCZ39CV2EB131R/04-parse-novel-chapter-list.png)
Evidence: Live check output on target commit
| `popularNovels` | ✅ PASS | 245 novels |
| `searchNovels` | ✅ PASS | 1 results for "His Empire, My Revenge" |
| `parseNovel` | ✅ PASS | 5 chapters |
| `parseChapter` | ✅ PASS | 9120 chars |
Evidence: Live check output on base commit (reproduces the bug)
| `parseChapter` | ❌ FAIL | Cannot read properties of undefined (reading 'chapter_content') |

Screenshots

These come from this repo's plugin playground (headless Chromium, npm run dev:start), not from the LNReader app. The Android toolchain is not installed on the machine that made this change, so this fix has not been tested in the app. Each image shows Genesis results loaded through the playground against the live site.

  1. Popular listing, after Fetch: Plugin playground: Genesis popular listing
  2. Novel detail, His Empire, My Revenge (Parse Novel): Plugin playground: Genesis novel detail
  3. Chapter list with 5 chapters: Plugin playground: Genesis chapter list
  4. Chapter text, THE RETURN, 9,120 characters (Parse Chapter): Plugin playground: Genesis chapter text
  5. Search for "His Empire": Plugin playground: Genesis search

The listing's cover images do not load: the site's storage returns 400 for those cover URLs. That is unchanged by this fix.

Locked and released evidence

  • Server-side refusal: an anonymous request to /api/chapters/101541/content (chapter 90 of Become a Legend, listed isUnlocked: false, isPaid: true) returns HTTP 401 Authentication required, with chapter_content: "None". The paywall is enforced by the site, and this plugin's parseChapter reports that refusal as a locked chapter.
  • Listing status: the listing carries a status field, and every chapter observed has released (His Empire, My Revenge 5 of 5; Become a Legend 157 of 157). The listing keeps only released chapters, which restores the old status=eq.released filter. On the live data observed so far the filter removes nothing.

AI authorship

The change, its commit messages, and this PR description were written by an AI agent (Claude Haiku 5.5) under firstmate's supervision. The captain approved the pipeline's review gate. No further human code review is recorded here.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - medium risk

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 8 of 9 scenarios driven live against the product
Scenario Result Live Evidence
Live check on the fixed plugin: popular, search, novel and chapter all pass (npm run check:plugin) ✅ pass live Live check output on target commit (npm run check:plugin -- plugins/english/genesis.ts): popularNovels PASS 245 novels, searchNovels PASS, parseNovel PASS 5 chapters, parseChapter PASS 9120 chars
Baseline reproduction: the base commit's parseChapter fails with the reported error ✅ pass live Live check output on base commit: parseChapter FAIL 'Cannot read properties of undefined (reading chapter_content)' (base genesis.ts run via temporary copy, removed afterwards)
Reader opens a free chapter and gets the full text (parseChapter /viewer/104793 returns body) ✅ pass live Driver /tmp/gdriver/run.cjs against live site: body 9453 chars; playground screenshot 02-parse-chapter-free-chapter.png shows the chapter text rendered
Adversarial: a chapter listed as locked refuses with the locked message instead of fetching a body ✅ pass live Driver run: parseChapter('/viewer/locked/101543') throws 'This chapter is locked on the site'; playground screenshot 03-parse-chapter-locked-refused.png
Adversarial: a stale /viewer/<id> path for a locked chapter is refused by the server's 401 response ✅ pass live Driver run: parseChapter('/viewer/101543') throws 'This chapter is locked on the site'; curl confirms the content endpoint returns HTTP 401 'Authentication required' with chapter_content null for lock…
Adversarial: a nonexistent chapter id is refused rather than returning empty text ✅ pass live Driver run: parseChapter('/viewer/999999999') throws 'This chapter is locked on the site'; curl returns HTTP 404 'Chapter not found or not yet released'
Novel chapter list shows locked chapters with the lock icon and locked path, consistent across the whole novel ✅ pass live Driver run: parseNovel('/novels/BAL') returns 157 chapters; every 🔒-named chapter has a /viewer/locked/ path and vice versa; playground screenshot 04-parse-novel-chapter-list.png shows 157 total chapt…
hideLocked setting removes locked chapters from the listing ✅ pass live Driver run: plugin instance with hideLocked=true, extractChapters for BAL returns zero 🔒 entries. The flag was set on the instance directly rather than through the playground settings UI.
Listing drops chapters whose status is not released (the recorded status=released filter) ⏸️ untested no Live data has no unreleased chapters, and this environment cannot create one on the real site. The filter is present in extractChapters but its negative branch was not exercised live.
  • npm ci (dependency install in worktree)
  • npm run check:plugin -- plugins/english/genesis.ts (target commit: popularNovels, searchNovels, parseNovel, parseChapter all PASS; parseChapter 9120 chars)
  • Temporary copy of base commit 49b4727 plugins/english/genesis.ts run through npm run check:plugin (reproduces parseChapter FAIL: Cannot read properties of undefined (reading 'chapter_content')); temp file removed
  • Bundled plugin with esbuild and driver script /tmp/gdriver/run.cjs against live genesistudio.com: popularNovels (245), searchNovels, parseNovel /novels/BAL (157 chapters, 🔒 prefix matches /viewer/locked/ path for all chapters), hideLocked filter removes locked chapters, parseChapter free chapter /viewer/104793 (9453 chars), parseChapter listed-locked /viewer/locked/101543 refuses, parseChapter stale /viewer/101543 refuses via HTTP 401, parseChapter nonexistent /viewer/999999999 refuses via HTTP 404
  • Site scan of all 245 novels' chapter listings: 33269 chapters, 18479 isUnlocked=false, 0 with status other than released
  • Playground: npx vite on 127.0.0.3:3000 (localhost mapped via Chromium host-resolver-rules, required because the dev proxy's CORS origin is hardcoded to http://localhost:3000), headless Chromium driven over CDP by /tmp/gdriver/play.mjs: Parse Chapter /viewer/104793 renders chapter text; Parse Chapter /viewer/locked/101543 on a fresh page shows 'This chapter is locked on the site'; Parse Novel /novels/BAL renders title, 157 total chapters, and Export EPUB
  • npm run clean:multisrc to remove generated multisrc plugin files; dev servers and headless Chromium stopped
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

The Supabase chapters table no longer allows anonymous reads ("permission
denied for table chapters"), so parseChapter got no chapter_content back.

parseChapter now reads the body from the site's own reader endpoint,
/api/chapters/<id>/content, which returns it as data.chapter_content. The
API-key scraping from the page scripts is removed because nothing uses it.

Bump version 2.0.1 -> 2.0.2.
@greptile-apps

greptile-apps Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable defect was established.

Summary

Restores Genesis chapter reads through the site's content endpoint.

  • Genesis lists released chapters and gives locked chapters their own paths.
  • Genesis chapter reading gets text from the site's content endpoint.

Acknowledged by PR author RibatTRW: anonymous requests, server-side refusal for paid chapters, and the listing lock gate are intentional. RibatTRW also states that the unreleased-chapter case could not be tested against live data.

Reviews (1) · Last reviewed commit: "no-mistakes(review): fix(english/genesis..." · Reviewed by Greptile

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