Skip to content

fix(english): rewrite KDT Novels as standalone plugin for new site - #2696

Merged
rajarsheechatterjee merged 2 commits into
lnreader:masterfrom
RibatTRW:fm/lnreader-kdtnovels-fix
Oct 19, 2026
Merged

rajarsheechatterjee merged 2 commits into
lnreader:masterfrom
RibatTRW:fm/lnreader-kdtnovels-fix

Conversation

@RibatTRW

@RibatTRW RibatTRW commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Summary

KDT Novels still returns no results after #1983. That PR moved the source to kdtnovels.net as a LightNovelWP (WordPress theme) entry, but the site has since replaced its WordPress theme with its own Next.js app, so none of the template's selectors match anymore. This PR replaces the generated entry with a standalone KDT Novels plugin for the new site, keeping the same plugin id so existing library entries carry over.

What Changed

  • Added a standalone plugins/english/kdtnovels.ts (v2.0.0, same kdtnovels id) for the new kdtnovels.net site, which is now a Next.js site and no longer uses the LightNovelWP theme. It covers popular and search listings, the novel page with chapters from every volume (read from the embedded React Server Components payload), and chapter text from article.reader-content. It sanitizes chapter HTML attributes.
  • Removed the kdtnovels entry from plugins/multisrc/lightnovelwp/sources.json, so the generator no longer produces the broken plugin with the same id.
  • Moved the icon from public/static/multisrc/lightnovelwp/kdtnovels/icon.png to public/static/src/en/kdtnovels/icon.png (100% rename) to match the standalone plugin's icon path.

Why KDT Novels still didn't work after #1983

#1983 moved KDT Novels to kdtnovels.net as a LightNovelWP (WordPress theme) source. Since then the site has replaced that WordPress theme with its own Next.js app. Wayback snapshots still show the LightNovelWP theme on 2026-08-21, and the Next.js site by 2026-09-23. The LightNovelWP template's selectors no longer match anything, and the template returns empty results rather than an error. So in the app, and in the playground, every call came back empty:

  • Popular/Latest: /series/?page=1&order=latest loads, but has no <article> cards, so no novels are listed
  • Search: /page/1/?s=… is now a 404, so there are no results
  • Parse novel: no title, cover or chapters (.ts-post-image / .eplister are gone)
  • Parse chapter: empty (.epcontent is gone; the text is now in article.reader-content)

To check the template itself, I fetched another LightNovelWP source (Hyacinth in Bloom) in the same session, and it still lists novels normally.

Details

  • Remove kdtnovels from plugins/multisrc/lightnovelwp/sources.json and add a standalone plugins/english/kdtnovels.ts. It keeps the same plugin id (kdtnovels) and the novel path format series/<slug>/. Old WordPress-era novel URLs still resolve on the new site. I couldn't check old chapter URLs, because no archived chapter list from the old site exists.
  • Version 2.0.0 (the generated LightNovelWP plugin was 1.1.12). The icon moves to public/static/src/en/kdtnovels/icon.png.
  • Listing and search use /search/?page=N[&genre=…][&q=…]. A genre filter is included. The site has no popularity ordering, so both listings are sorted by most recently updated.
  • The novel page provides title, cover, author/artist, genres, summary, and status (the Completed/Hiatus badge; series without a badge are listed as ongoing on the site's series index).
  • The chapter list for every volume comes from the page's embedded React Server Components payload, because the HTML only renders the expanded volume. Locked VIP chapters are prefixed with 🔒.
  • Chapter text comes from article.reader-content. Illustration placeholders (span.kdt-ilus[data-ilus]) become <img> tags, and locked chapters throw a clear "This chapter is locked" error instead of returning empty text.
  • Chapter HTML gets the same attribute sanitizing as the LightNovelWP template: on* attributes are removed, as are href/src values that are javascript:, vbscript: or non-image data: URLs. Illustration images are built with .attr('src', …), not by interpolating data-ilus into an HTML string.
  • The shared LightNovelWP template is unchanged.

Additional live checks (outside the pipeline)

  • npm run check:plugin -- plugins/english/kdtnovels.ts: all 4 checks pass (24 novels, search 1 result, 62 chapters, 1391 chars).
  • Extra live runs: listing page 5 (11 novels) and past the end (0), genre filter, a search with no results, Ongoing/Completed/Hiatus novels, a 4-volume novel (all 110 chapters returned), plus a free chapter, an illustrations-only chapter, and a locked chapter.
  • npm run build:full, eslint and prettier pass.

Plugin playground screenshots (fixed plugin, live site)

Popular/Latest listing:

Plugin playground: KDT Novels listing

Search wistoria:

Plugin playground: KDT Novels search

Novel detail with the full chapter list (Volume 2 is locked VIP content):

Plugin playground: KDT Novels novel detail and chapters

Chapter text:

Plugin playground: KDT Novels chapter text


This PR was written with an AI coding agent (Claude). I reviewed the change and tested it as described above.

🤖 Generated with Claude Code

Risk Assessment

✅ Low: The change replaces one broken scraper with a self-contained standalone plugin, removes the old multisrc entry, and moves the icon. It keeps the plugin id and bumps the version, and I found no concrete defect in the parsing, sanitization or pagination paths.

Testing

I ran the live checker on plugins/english/kdtnovels.ts, and all four checks passed. I then ran the plugin playground and drove it with headless Chromium: Popular, Parse Novel and Parse Chapter all returned real data. Search passed in the live checker only; in the playground I only opened the Search tab. Screenshots are in the evidence directory. The worktree is clean.

  • Live validation: ✅ go - 4 of 4 scenarios driven live against the product
Scenario Result Live Evidence
Live checker: popular, search, novel and chapter all pass against kdtnovels.net ✅ pass live check:plugin output: popularNovels 24 novels, searchNovels 1 result, parseNovel 62 chapters, parseChapter 1391 chars
Playground Popular: Fetch lists KDT Novels titles with covers ✅ pass live 01-popular.png
Playground Parse Novel: opening a novel shows cover, status, author, artist, genres, summary and 62 chapters ✅ pass live 03-parse-novel.png, 04-parse-novel-chapters.png
Playground Parse Chapter: opening a chapter and fetching returns about 119k characters of readable text ✅ pass live 05-parse-chapter.png
  • Evidence: Playground: KDT Novels popular list (local file: ~/.no-mistakes/evidence/01M54XERNGCMSP2RSPAH2VS6Q4/01-popular.png)
  • Evidence: Playground: parse novel details (local file: ~/.no-mistakes/evidence/01M54XERNGCMSP2RSPAH2VS6Q4/03-parse-novel.png)
  • Evidence: Playground: summary and chapter list (local file: ~/.no-mistakes/evidence/01M54XERNGCMSP2RSPAH2VS6Q4/04-parse-novel-chapters.png)
  • Evidence: Playground: parse chapter content (local file: ~/.no-mistakes/evidence/01M54XERNGCMSP2RSPAH2VS6Q4/05-parse-chapter.png)

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 - 4 of 4 scenarios driven live against the product
Scenario Result Live Evidence
Live checker: popular, search, novel and chapter all pass against kdtnovels.net ✅ pass live check:plugin output: popularNovels 24 novels, searchNovels 1 result, parseNovel 62 chapters, parseChapter 1391 chars
Playground Popular: Fetch lists KDT Novels titles with covers ✅ pass live 01-popular.png
Playground Parse Novel: opening a novel shows cover, status, author, artist, genres, summary and 62 chapters ✅ pass live 03-parse-novel.png, 04-parse-novel-chapters.png
Playground Parse Chapter: opening a chapter and fetching returns about 119k characters of readable text ✅ pass live 05-parse-chapter.png
  • npm run check:plugin -- plugins/english/kdtnovels.ts
  • npm run dev:start with headless Chromium driven over the Chrome DevTools Protocol: Fetch on Popular, open a novel, Fetch on Parse Novel, open a chapter, Fetch on Parse Chapter
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

KDT Novels replaced its LightNovelWP WordPress theme with a custom Next.js
site (between late August and late September 2026), so the lightnovelwp
template from lnreader#1983 matched nothing: listing, search, novel and chapter
calls all returned empty results without an error.

Move the source out of lightnovelwp into a standalone plugin that keeps
the same id and novel path format (series/<slug>/), and parses:
- listing and search from /search/ (with a genre filter),
- novel details, status badge and credits from the series page,
- the full chapter list of every volume from the embedded React Server
  Components payload (the HTML only renders the expanded volume),
  marking locked VIP chapters with 🔒,
- chapter text from article.reader-content, turning the illustration
  placeholders into images and reporting locked chapters as such.
@greptile-apps

greptile-apps Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 3/5

[Medium impact] Migrates a novel-scraping plugin from shared template to standalone.

Restore the chapter HTML safety checks before merging.

Findings

  1. P1 Security Chapter HTML can run code ▶

Summary

Replaces the generated KDT Novels source with a standalone plugin for the Next.js site.

  • Readers can browse and search KDT Novels on its new site.
  • KDT Novels series pages now provide full chapter lists and readable chapters.

Reviews (1) · Last reviewed commit: "fix(english/kdtnovels): rewrite as stand..." · Reviewed by Greptile

Comment on lines +197 to +203
content.find('script, style, noscript').remove();
// Illustrations are placeholders that the site's script swaps for images.
content.find('.kdt-ilus[data-ilus]').each((_, el) => {
const src = this.absolute($(el).attr('data-ilus'));
$(el).replaceWith(src ? `<img src="${src}">` : '');
});
return content.html() || '';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 security Chapter HTML can run code

parseChapter drops the event-handler and script-URL checks that the previous LightNovelWPPlugin applied. A malicious or compromised chapter page can now return <img src=x onerror="alert(1)"> unchanged. The playground inserts that HTML with dangerouslySetInnerHTML, allowing it to run code inside the playground page.

The data-ilus replacement also turns encoded quotes into executable attributes: x&quot; onerror=&quot;alert(1) becomes <img src="x" onerror="alert(1)">.

Restore the previous checks after replacing illustrations. Build each image with .attr('src', src) rather than inserting the value into HTML.

How this was verified: The old template removes event attributes and script URLs, while the new method returns them to a page that inserts the result directly with dangerouslySetInnerHTML.

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 b9ca6e5. parseChapter now builds each illustration with $('<img>').attr('src', src) instead of interpolating data-ilus into an HTML string. After that it applies the same attribute sanitizing as the LightNovelWP template: it removes on* attributes, and it removes href/src values that start with javascript: or vbscript: (control characters and spaces are stripped first) or that are data: URLs not of an image type.

I checked this by running parseChapter on a mock chapter page containing <img src=x onerror="alert(2)">, javascript: and tab-obfuscated JaVa Script: links, a data:text/html link, an onload on a data:image img, an onclick paragraph, and data-ilus="x&quot; onerror=&quot;alert(1)". The previous commit returned 7 handlers or script URLs; this one returns none. The encoded-quote value now ends up only as the literal src text. Real chapters are unchanged: the live check passes, and text, illustration and locked chapters behave as before.

parseChapter returned the chapter HTML without the event-handler and
script-URL checks the LightNovelWP template applied, and built the
illustration images by interpolating data-ilus into an HTML string, so
encoded quotes in it became attributes.

Build each illustration with .attr('src', ...), then drop on* attributes
and href/src values that are javascript:, vbscript: or non-image data:
URLs, the same way the LightNovelWP template does.
@rajarsheechatterjee
rajarsheechatterjee merged commit f1258c1 into lnreader:master Oct 19, 2026
3 checks passed
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.

2 participants