From 8d6b275c695239aa1cf9a539507255974be63419 Mon Sep 17 00:00:00 2001 From: Raiyn Aydin Date: Wed, 16 Sep 2026 19:13:49 +0800 Subject: [PATCH 1/7] fix(english/novelight): page chapters from the site's own ranges `parseNovel` counted `#select-pagination-chapter > option` elements for `totalPages`, while `parsePage` recomputed it from the last numeric option in the whole document, and then mapped each app page onto a site page with `totalPages - page + 1`. The two derivations could disagree, and the reversed mapping made the site's partial page the FIRST page instead of the last: on live novels page 1 returned 2, 6 and 49 chapters instead of 50. Because that partition is anchored at the newest chapter, every added chapter shifted all page boundaries, which the app's per-page chapter cache turns into duplicated or missing chapters. Both `parseNovel` and `parsePage` now derive the chapter count from the largest range end in the site's page select (`ceil(total / 50)` pages), the same shape as the sibling `mynovels` plugin for this CMS. `parsePage` fetches only the site pages whose ranges overlap the requested 50-chapter chunk and slices exactly that chunk, so pages 1..totalPages partition the chapter list: every page but the last holds 50 chapters, and the partial page is last. Anchoring the chunks at the oldest chapter also keeps already-served pages stable when new chapters are added. Locked chapters are filtered after slicing to keep positions stable; name, URL, date and locked-chapter handling are unchanged. Authored with AI assistance. --- plugins/english/novelight.ts | 148 +++++++++++++++++++++++++++++------ 1 file changed, 126 insertions(+), 22 deletions(-) diff --git a/plugins/english/novelight.ts b/plugins/english/novelight.ts index 92e0211db..2f22c2c93 100644 --- a/plugins/english/novelight.ts +++ b/plugins/english/novelight.ts @@ -7,10 +7,25 @@ import { defaultCover } from '@libs/defaultCover'; import dayjs from 'dayjs'; import { storage } from '@libs/storage'; +const CHAPTERS_PER_PAGE = 50; + +type ChapterPaginationOption = { + value: string; + start: number; + end: number; +}; + +type RawChapter = { + name: string; + path: string; + releaseTime?: string; + isLocked: boolean; +}; + class Novelight implements Plugin.PagePlugin { id = 'novelight'; name = 'Novelight'; - version = '1.1.5'; + version = '1.1.6'; icon = 'src/en/novelight/icon.png'; site = 'https://novelight.net/'; @@ -91,12 +106,19 @@ class Novelight implements Plugin.PagePlugin { const loadedCheerio = parseHTML(body); + // The chapter select tiles the chapter list, so the largest range end is + // the novel's chapter count. `parsePage` derives the same value from the + // same markup, keeping `totalPages` and the served pages in agreement. + const totalChapters = this.parseTotalChapters( + this.parseChapterPagination(loadedCheerio), + ); + const novel: Plugin.SourceNovel & { totalPages: number } = { path: novelPath, name: loadedCheerio('h1').text() || 'Untitled', cover: this.site + loadedCheerio('.poster > img').attr('src'), summary: loadedCheerio('section.text-info.section > p').text(), - totalPages: loadedCheerio('#select-pagination-chapter > option').length, + totalPages: Math.ceil(totalChapters / CHAPTERS_PER_PAGE) || 1, chapters: [], }; @@ -139,19 +161,47 @@ class Novelight implements Plugin.PagePlugin { return novel; } - async parsePage(novelPath: string, page: string): Promise { - const rawBody = await fetchApi(this.site + novelPath).then(r => r.text()); - const csrftoken = rawBody?.match(/window\.CSRF_TOKEN = "([^"]+)"/)?.[1]; - const bookId = rawBody?.match(/const OBJECT_BY_COMMENT = ([0-9]+)/)?.[1]; - const totalPages = parseInt( - rawBody - ?.match(/