Repository navigation
feat(russian): add HexNovels - #2761
Conversation
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>
[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. FindingsSummaryAdds HexNovels as a Russian source with browsing, search, novel details, chapter reading, and an icon.
Reviews (1) · Last reviewed commit: "feat(russian): add HexNovels" · Reviewed by Greptile |
| 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)) { |
There was a problem hiding this comment.
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
| 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>'; |
There was a problem hiding this comment.
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>
|
Note on the failing Plugin Live Check: it fails only at 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. |
Closes #2709
What changed
New Russian source: HexNovels (https://hexnovels.me), version 1.0.0.
plugins/russian/hexnovels.tspublic/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.
/content?page=Nand/content?search=…&page=N, parsed from the server-rendered cards. The site'spageis 0-based, sopageNo - 1is sent.h1, cover fromog: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./content/<slug>/<chapterId>→.typeset-content.How it was verified
npx eslint plugins/russian/hexnovels.ts: passnpx tsc --project tsconfig.production.json --noEmit: passnpm run build:full: pass, and hexnovels is in.dist/plugins.jsonnpm 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
{id:…,name:…,title:…,number:…,volume:…,branchId:…}) and the card markup, so a site redesign would break it.Checklist
check:pluginplus a manual live run)type(scope): descriptionPartly authored by an AI agent (Grok Bot), please review with that in mind.