Skip to content

feat(english): add Beast Novels source plugin - #2614

Open
RibatTRW wants to merge 2 commits into
lnreader:masterfrom
RibatTRW:fm/lnreader-beastnovels-2427
Open

RibatTRW wants to merge 2 commits into
lnreader:masterfrom
RibatTRW:fm/lnreader-beastnovels-2427

Conversation

@RibatTRW

@RibatTRW RibatTRW commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Closes #2427

Adds a standalone plugin for Beast Novels, an English translation site. It's a custom Laravel site, not a WordPress theme, so no multisrc template fits.

How it works

  • Catalog: /novels embeds the site's whole catalog (36 novels right now) as a JSON array that the page sorts and filters in the browser. The plugin parses that array for browsing, search and filters, so everything comes from one page. It finds the array by matching brackets, so the page's formatting around it doesn't matter. Pages after the first return nothing.
  • Filters: Sort (Popular / Latest / Updated), Status (Ongoing / Completed / Hiatus) and Genres (the site's five genres; a novel must have all the selected ones). They mirror the site's own filters. "Latest" in the app uses the site's updated order.
  • Search: matches title or author, case-insensitive. It uses the catalog because the site's /search/books JSON endpoint returns at most 7 results.
  • Novel page: title, cover, author, synopsis and genres come from /series/<slug>. That page comments out its status badge, so status comes from the catalog entry. If the catalog request fails, the novel still loads, just without a status.
  • Chapters: every free chapter is listed in reading order, with chapter numbers. Premium chapters (data-premium="1") redirect to the login page, so they aren't listed. Release dates are left out because the site only shows relative times like "1 week ago".
  • Chapter content: .chapter-content, with script/style/iframe elements and on* attributes removed. Attributes whose URL scheme is javascript:, vbscript: or a non-image data: are also removed; control characters and whitespace are stripped before that check, so java&#10;script: doesn't slip through.
  • Icon: the site's favicon, resized to 96x96.

Testing

  • npm run check:plugin -- plugins/english/beastnovels.ts: PASS for all four checks (popularNovels 36 novels, searchNovels, parseNovel 156 chapters, parseChapter 17700 chars).
  • Called the bundled plugin directly against the live site to check the sort, status and genre filters, page 2 returning nothing, search by title and by author, every field parseNovel returns (for Ode of the Brave: 56 free chapters out of 240), resolveUrl, and that the chapter HTML has no on*/javascript:.
  • After the review fixes: I stubbed fetch to check the catalog parser on pretty-printed, trailing-semicolon and no-space variants (with [, ], } and quotes inside a title), parseNovel when /novels returns HTTP 500, and the URL sanitizer on the &#10;, mixed-case/tab, vbscript: and data:text/html cases. I then re-ran check:plugin (all four PASS again).
  • eslint and prettier --check on the new file, and npm run build:compile, all pass.
  • Not tested in the playground or the app.

This PR was written by an AI agent (Claude). It has not had human review yet.

@greptile-apps

greptile-apps Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 3/5

[Medium risk] Adds a new content source plugin for a novel website.

Do not merge until chapter links cannot run script in the reader.

Findings

  1. P1 Security Chapter links can run script ▶
  2. P2 Catalog formatting breaks browsing ▶
  3. P2 Catalog outage hides chapters ▶

Summary

Adds a standalone Beast Novels source plugin for LNReader, since the site's custom pages need their own parser. Readers can browse and search its catalog, open novels, and read free chapters.

  • Browsing and search use the catalog embedded on the site's /novels page.
  • Novel pages supply details and free chapter lists; chapter pages supply cleaned reading text.

Reviews (1) · Last reviewed commit: "feat(english): add Beast Novels source p..."

Comment thread plugins/english/beastnovels.ts Outdated
if (el.type !== 'tag') return;
for (const attr of Object.keys(el.attribs)) {
const value = el.attribs[attr];
if (/^on/i.test(attr) || /^\s*javascript:/i.test(value)) {

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 links can run script

A chapter link such as href="java&#10;script:alert(1)" keeps a newline inside its URL after parsing. This check only catches whitespace before javascript:, so it leaves the link in the chapter HTML. A browser treats it as a script URL when a reader clicks it. Remove control characters before checking URLs, and reject unsafe URL schemes.

How this was verified: The URL check leaves embedded control characters intact, and the reader inserts the returned chapter HTML without another safety check.

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 4993319. Before any scheme check, isUnsafeUrl strips ASCII control characters and whitespace (U+0000–U+0020, U+007F) and lowercases the value. It then rejects javascript:, vbscript: and any data: URL that isn't data:image/. The parser has already decoded entities, so java&#10;script: is caught. Tested against synthetic chapter HTML with the &#10;, mixed-case/tab, vbscript: and data:text/html cases.

Comment thread plugins/english/beastnovels.ts Outdated

private async fetchCatalog(): Promise<BeastBook[]> {
const html = await this.fetchText(this.site + 'novels');
const match = html.match(/allBooks:\s*(\[.*\]),\s*\n/);

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 Catalog formatting breaks browsing

fetchCatalog requires the whole allBooks array on one line, followed by a comma and newline. If the site spreads the same valid array across lines or changes that punctuation, parsing throws and readers cannot browse or search. Parse the embedded data without relying on its whitespace layout.

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 4993319. fetchCatalog now finds allBooks:, then scans from the opening [ to its matching bracket, skipping brackets inside JSON strings, and passes that slice to JSON.parse. It no longer depends on whitespace or on what comes after the array. Tested on the live page (36 novels) and on synthetic pretty-printed, trailing-semicolon and no-space variants whose novel title contains [, ], } and escaped quotes.

Comment on lines +103 to +106
const [html, catalog] = await Promise.all([
this.fetchText(this.site + novelPath),
this.fetchCatalog(),
]);

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 Catalog outage hides chapters

parseNovel waits for /novels before it parses the series page. If /novels alone fails, Promise.all throws even when the series page loaded and contains the chapters. Let readers open the novel and its chapters without catalog status when that extra request fails.

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 4993319. In parseNovel, a failed catalog request now falls back to an empty list, so the novel details and chapters still come from the series page, just without the catalog-derived status. Tested by stubbing /novels to return HTTP 500: Ode of the Brave still returned its name, summary and 56 chapters, with no status.

- Strip control characters and whitespace before checking URL schemes;
  also reject vbscript: and non-image data: URLs.
- Parse the embedded catalog by bracket matching instead of a
  layout-dependent regex.
- Keep parseNovel working when the catalog request fails.

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.

Beast Novels

1 participant