Repository navigation
fix(english): rewrite KDT Novels as standalone plugin for new site - #2696
Conversation
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.
[Medium impact] Migrates a novel-scraping plugin from shared template to standalone. Restore the chapter HTML safety checks before merging. Findings
SummaryReplaces the generated KDT Novels source with a standalone plugin for the Next.js site.
Reviews (1) · Last reviewed commit: "fix(english/kdtnovels): rewrite as stand..." · Reviewed by Greptile |
| 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() || ''; |
There was a problem hiding this comment.
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" onerror="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.
There was a problem hiding this comment.
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" onerror="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.
Summary
KDT Novels still returns no results after #1983. That PR moved the source to
kdtnovels.netas 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
plugins/english/kdtnovels.ts(v2.0.0, samekdtnovelsid) for the newkdtnovels.netsite, 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 fromarticle.reader-content. It sanitizes chapter HTML attributes.kdtnovelsentry fromplugins/multisrc/lightnovelwp/sources.json, so the generator no longer produces the broken plugin with the same id.public/static/multisrc/lightnovelwp/kdtnovels/icon.pngtopublic/static/src/en/kdtnovels/icon.png(100% rename) to match the standalone plugin'siconpath.Why KDT Novels still didn't work after #1983
#1983 moved KDT Novels to
kdtnovels.netas 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:/series/?page=1&order=latestloads, but has no<article>cards, so no novels are listed/page/1/?s=…is now a 404, so there are no results.ts-post-image/.eplisterare gone).epcontentis gone; the text is now inarticle.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
kdtnovelsfromplugins/multisrc/lightnovelwp/sources.jsonand add a standaloneplugins/english/kdtnovels.ts. It keeps the same plugin id (kdtnovels) and the novel path formatseries/<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.2.0.0(the generated LightNovelWP plugin was1.1.12). The icon moves topublic/static/src/en/kdtnovels/icon.png./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.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.on*attributes are removed, as arehref/srcvalues that arejavascript:,vbscript:or non-imagedata:URLs. Illustration images are built with.attr('src', …), not by interpolatingdata-ilusinto an HTML string.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).npm run build:full,eslintandprettierpass.Plugin playground screenshots (fixed plugin, live site)
Popular/Latest listing:
Search
wistoria:Novel detail with the full chapter list (Volume 2 is locked VIP content):
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.
~/.no-mistakes/evidence/01M54XERNGCMSP2RSPAH2VS6Q4/01-popular.png)~/.no-mistakes/evidence/01M54XERNGCMSP2RSPAH2VS6Q4/03-parse-novel.png)~/.no-mistakes/evidence/01M54XERNGCMSP2RSPAH2VS6Q4/04-parse-novel-chapters.png)~/.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.
npm run check:plugin -- plugins/english/kdtnovels.tsnpm run dev:startwith 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.