Skip to content

feat(russian): add HexNovels - #2761

Merged
rajarsheechatterjee merged 2 commits into
lnreader:masterfrom
Gourab0002:feat/russian-hexnovels
Oct 24, 2026
Merged

rajarsheechatterjee merged 2 commits into
lnreader:masterfrom
Gourab0002:feat/russian-hexnovels

Conversation

@Gourab0002

Copy link
Copy Markdown
Contributor

Closes #2709

What changed

New Russian source: HexNovels (https://hexnovels.me), version 1.0.0.

  • plugins/russian/hexnovels.ts
  • public/static/src/ru/hexnovels/icon.png (96×96, from the site's apple-touch-icon)

How it works

The site is a TanStack Start app. Pages are server-rendered, and their data is embedded in the serialized router state. There's no public JSON API.

  • popularNovels / searchNovels: /content?page=N and /content?search=…&page=N, parsed from the server-rendered cards. The site's page is 0-based, so pageNo - 1 is sent.
  • parseNovel: title from h1, cover from og:image, summary from the meta description, and status from the embedded book object. The full chapter list (every chapter, not only the rendered ones) comes from the embedded state on /content/<slug>/chapters. Some novels have several translation branches, so the plugin uses the branch with the most chapters, sorted by volume then number.
  • parseChapter: /content/<slug>/<chapterId> → .typeset-content.

How it was verified

  • npx eslint plugins/russian/hexnovels.ts: pass
  • npx tsc --project tsconfig.production.json --noEmit: pass
  • npm run build:full: pass, and hexnovels is in .dist/plugins.json
  • Manual run of the bundled plugin against the live site:
    • popular page 1 → 40 novels
    • search "Нечестивый" → 5, "Повелитель тайн" → 40 (first result is the novel itself)
    • Lord of the Mysteries → 1432 chapters (Том 1. Глава 1 … Том 8. Глава 1432)
    • text chapters return full text, e.g. 17,330 chars
  • npm run check:plugin -- plugins/russian/hexnovels.ts: popularNovels PASS (40), searchNovels PASS, parseNovel PASS (440 chapters), parseChapter FAIL (74 chars). See the next section.

Known limitation (why parseChapter is short in the live check)

The live check opens chapter 1 of the top popular novel. Today that's "Монолог фармацевта (LN)", whose first chapter is "Том 1. Глава 0: Иллюстрации", an illustration-only chapter. The site renders that chapter as an empty <p></p>. Its images are fetched by the client and decrypted with a key, so they aren't in the page. I didn't try to reimplement that decryption. For these chapters the plugin returns a short notice ("Эта глава содержит только иллюстрации…") instead of a blank page. All text chapters load normally.

Risks

  • Parsing depends on the embedded router-state shape ({id:…,name:…,title:…,number:…,volume:…,branchId:…}) and the card markup, so a site redesign would break it.
  • Using only the largest translation branch means a few chapters that exist only in other branches are skipped (Lord of the Mysteries: 1432 of 1436).

Checklist

  • Update version code if an existing plugin was modified (new plugin, 1.0.0)
  • Test changes in Plugin Playground or the app (check:plugin plus a manual live run)
  • Reference related issues in the PR body
  • Commit messages follow type(scope): description

Partly authored by an AI agent (Grok Bot), please review with that in mind.

New source for hexnovels.me (TanStack Start SSR). Novel lists, search, novel info and the full chapter list are read from the server-rendered page and its embedded router state; chapter text from .typeset-content. Site pages are 0-based. With several translation branches, the branch with the most chapters is used.

Closes lnreader#2709

Co-Authored-By: Grok Bot (AI agent) <noreply@x.ai>
@greptile-apps

greptile-apps Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change reaches readers using the HexNovels source.

Fix the production chapter loops before merging; the published plugin otherwise returns empty chapter lists.

Findings

  1. P1 Published novels have no chapters ▶
  2. P2 Missing chapters become illustration notices ▶

Summary

Adds HexNovels as a Russian source with browsing, search, novel details, chapter reading, and an icon.

  • LNReader can browse and read novels from HexNovels.

Reviews (1) · Last reviewed commit: "feat(russian): add HexNovels" · Reviewed by Greptile

Comment thread plugins/russian/hexnovels.ts Outdated
const re =
/\{id:"([0-9a-f-]{36})",name:"(?:[^"\\]|\\.)*",title:"((?:[^"\\]|\\.)*)",number:([0-9.]+),volume:(\d+)[^}]*?branchId:"([0-9a-f-]{36})"[^}]*?createdAt:"([^"]+)"/g;
const byId = new Map<string, ChapterData>();
for (const m of chaptersHtml.matchAll(re)) {

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 Published novels have no chapters

build:compile targets ES5 with downlevelIteration disabled. TypeScript therefore turns these loops into indexed loops that read .length. Neither matchAll() nor Map.values() returns an array with that property, so the loops skip their bodies and parseNovel() returns no chapters. The spread of branches.values() has the same problem.

Wrap all three iterator results in Array.from() before looping or spreading.

Knowledge Base Used: Plugin build and manifest pipeline

Comment on lines +139 to +142
const content = $('.typeset-content').first();
// Illustration-only chapters load their (encrypted) images client-side
if (!content.text().trim() && !content.find('img').length) {
return '<p>Эта глава содержит только иллюстрации, они недоступны в приложении.</p>';

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 Missing chapters become illustration notices

parseChapter() returns the illustration notice even when .typeset-content is missing entirely. An HTTP-success page without that container, such as an incomplete response, is treated as a successfully loaded chapter. EPUB export then saves the notice as chapter content.

Check content.length and throw when the container is absent. Keep the notice for an existing, empty container.

Knowledge Base Used: Plugin authoring contract

build:compile targets ES5 without downlevelIteration, so for...of over matchAll()/Map.values() and spreading Map.values() compiled to .length loops that never ran, leaving parseNovel with no chapters. Wrap them in Array.from(). Also throw when the chapter container is missing instead of returning the illustration notice.

Co-Authored-By: Grok Bot (AI agent) <noreply@x.ai>
@Gourab0002

Copy link
Copy Markdown
Contributor Author

Note on the failing Plugin Live Check: it fails only at parseChapter (74 chars < 200). The live check always tests popular[0]'s first chapter, which on HexNovels is currently "Том 1. Глава 0: Иллюстрации", an illustrations-only chapter. The site serves those images encrypted (decrypted client-side in its reader), so the plugin shows a short notice instead of decrypting them.

Popular (40), search, and parseNovel (440 chapters) all pass, and text chapters load normally (e.g. Lord of the Mysteries, 1432 chapters, ch. 1 = 13,107 chars). The plugin's popular list and oldest-first chapter order match the site, so I didn't change them just to pick a different test chapter. Happy to adjust if you'd prefer a per-plugin override in the live-check script.

@rajarsheechatterjee
rajarsheechatterjee merged commit b8e2201 into lnreader:master Oct 24, 2026
2 of 3 checks passed
@Gourab0002
Gourab0002 deleted the feat/russian-hexnovels branch October 25, 2026 02:00
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.

please add plugin for hexnovels.me

2 participants