From 10f808894cf1b18042bd50b5861372b2180546a4 Mon Sep 17 00:00:00 2001 From: 52191314 Date: Sat, 8 Aug 2026 07:28:06 +0700 Subject: [PATCH 1/2] feat(multisrc): add Novel Nice as new plugin (boxnovel rebrand) BoxNovel rebranded to Novel Nice (novelnice.com). Add it as a new madara instance (id novelnice, v2.2.0) instead of touching the existing boxnovel entry, so existing installs keep working and the rebrand installs fresh. - sources.json: new novelnice entry (same site, useNewChapterEndpoint) - filters/novelnice.json + icon: copied from boxnovel (same genre list) Fixes #2369 --- .../multisrc/madara/filters/novelnice.json | 324 ++++++++++++++++++ plugins/multisrc/madara/sources.json | 9 + .../static/multisrc/madara/novelnice/icon.png | Bin 0 -> 626 bytes 3 files changed, 333 insertions(+) create mode 100644 plugins/multisrc/madara/filters/novelnice.json create mode 100644 public/static/multisrc/madara/novelnice/icon.png diff --git a/plugins/multisrc/madara/filters/novelnice.json b/plugins/multisrc/madara/filters/novelnice.json new file mode 100644 index 000000000..3d11b419f --- /dev/null +++ b/plugins/multisrc/madara/filters/novelnice.json @@ -0,0 +1,324 @@ +{ + "filters": { + "genre[]": { + "type": "Checkbox", + "label": "Genre", + "value": [], + "options": [ + { + "label": "Action", + "value": "action" + }, + { + "label": "Adventure", + "value": "adventure" + }, + { + "label": "Anime & Comics", + "value": "anime-comics" + }, + { + "label": "Comedy", + "value": "comedy" + }, + { + "label": "Drama", + "value": "drama" + }, + { + "label": "Eastern", + "value": "eastern" + }, + { + "label": "Fan-fiction", + "value": "fan-fiction" + }, + { + "label": "Fanfiction", + "value": "fanfiction" + }, + { + "label": "Fantasy", + "value": "fantasy" + }, + { + "label": "Game", + "value": "game" + }, + { + "label": "Games", + "value": "games" + }, + { + "label": "Gender Bender", + "value": "gender-bender" + }, + { + "label": "General", + "value": "general" + }, + { + "label": "Harem", + "value": "harem" + }, + { + "label": "Historical", + "value": "historical" + }, + { + "label": "Horror", + "value": "horror" + }, + { + "label": "Isekai", + "value": "isekai" + }, + { + "label": "Josei", + "value": "josei" + }, + { + "label": "LitRPG", + "value": "litrpg" + }, + { + "label": "Magic", + "value": "magic" + }, + { + "label": "Magical Realism", + "value": "magical-realism" + }, + { + "label": "Martial Arts", + "value": "martial-arts" + }, + { + "label": "Mature", + "value": "mature" + }, + { + "label": "Mecha", + "value": "mecha" + }, + { + "label": "Modern Life", + "value": "modern-life" + }, + { + "label": "Mystery", + "value": "mystery" + }, + { + "label": "Other", + "value": "other" + }, + { + "label": "Psychological", + "value": "psychological" + }, + { + "label": "Reincarnation", + "value": "reincarnation" + }, + { + "label": "Romance", + "value": "romance" + }, + { + "label": "School Life", + "value": "school-life" + }, + { + "label": "Sci-fi", + "value": "sci-fi" + }, + { + "label": "Seinen", + "value": "seinen" + }, + { + "label": "Shoujo", + "value": "shoujo" + }, + { + "label": "Shoujo Ai", + "value": "shoujo-ai" + }, + { + "label": "Shounen", + "value": "shounen" + }, + { + "label": "Shounen Ai", + "value": "shounen-ai" + }, + { + "label": "Slice of Life", + "value": "slice-of-life" + }, + { + "label": "Smut", + "value": "smut" + }, + { + "label": "Sports", + "value": "sports" + }, + { + "label": "Supernatural", + "value": "supernatural" + }, + { + "label": "System", + "value": "system" + }, + { + "label": "Thriller", + "value": "thriller" + }, + { + "label": "Tragedy", + "value": "tragedy" + }, + { + "label": "Urban", + "value": "urban" + }, + { + "label": "Urban Life", + "value": "urban-life" + }, + { + "label": "Video Games", + "value": "video-games" + }, + { + "label": "War", + "value": "war" + }, + { + "label": "Wuxia", + "value": "wuxia" + }, + { + "label": "Xianxia", + "value": "xianxia" + }, + { + "label": "Xuanhuan", + "value": "xuanhuan" + }, + { + "label": "Yaoi", + "value": "yaoi" + }, + { + "label": "Yuri", + "value": "yuri" + } + ] + }, + "op": { + "type": "Switch", + "label": "having all selected genres", + "value": false + }, + "author": { + "type": "Text", + "label": "Author", + "value": "" + }, + "artist": { + "type": "Text", + "label": "Artist", + "value": "" + }, + "release": { + "type": "Text", + "label": "Year of Released", + "value": "" + }, + "adult": { + "type": "Picker", + "label": "Adult content", + "value": "", + "options": [ + { + "label": "All", + "value": "" + }, + { + "label": "None adult content", + "value": "0" + }, + { + "label": "Only adult content", + "value": "1" + } + ] + }, + "status[]": { + "type": "Checkbox", + "label": "Status", + "value": [], + "options": [ + { + "label": "OnGoing", + "value": "on-going" + }, + { + "label": "Completed", + "value": "end" + }, + { + "label": "Canceled", + "value": "canceled" + }, + { + "label": "On Hold", + "value": "on-hold" + }, + { + "label": "Upcoming", + "value": "upcoming" + } + ] + }, + "m_orderby": { + "type": "Picker", + "label": "Order by", + "value": "", + "options": [ + { + "label": "Relevance", + "value": "" + }, + { + "label": "Latest", + "value": "latest" + }, + { + "label": "A-Z", + "value": "alphabet" + }, + { + "label": "Rating", + "value": "rating" + }, + { + "label": "Trending", + "value": "trending" + }, + { + "label": "Most Views", + "value": "views" + }, + { + "label": "New", + "value": "new-manga" + } + ] + } + } +} diff --git a/plugins/multisrc/madara/sources.json b/plugins/multisrc/madara/sources.json index 6f4b088e4..62b243d4f 100644 --- a/plugins/multisrc/madara/sources.json +++ b/plugins/multisrc/madara/sources.json @@ -651,5 +651,14 @@ "useNewChapterEndpoint": true, "versionIncrements": 2 } + }, + { + "id": "novelnice", + "sourceSite": "https://novelnice.com/", + "sourceName": "Novel Nice", + "options": { + "lang": "English", + "useNewChapterEndpoint": true + } } ] diff --git a/public/static/multisrc/madara/novelnice/icon.png b/public/static/multisrc/madara/novelnice/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..539ebb8df6e0a1b64415aed71a7c2f5c19fc93e4 GIT binary patch literal 626 zcmV-&0*(ENP)JyW)1P--Wm0q{!!<#pjm4uL zf*=%z6$Ax2dO$0C|JU3oouwu3UV}`?h&=|25f2(y^Zp%0V5rr<(=A!mB#euPgr4Q$G@T#bpPPmzD(q2tWV=5P$##AOHafb{FK&DLE?G^7mc% zkldIM0_K7c9fn4Btyn1tFoDj;DG%HQQ&2vfk$4PQ+d?3-z2q)jJ1+Z3P~My}89IlH zGm3_`1%f1!^dBhFs9-_T$L^!x4Q|P4NS6s#yh*RIH8GKO-Vvr&zdGXMYp M07*qoM6N<$f(e5!jQ{`u literal 0 HcmV?d00001 From 06641b85ff5ac591c76049b2ac0967148c4d73f9 Mon Sep 17 00:00:00 2001 From: 52191314 Date: Sat, 8 Aug 2026 09:39:37 +0700 Subject: [PATCH 2/2] fix(madara): fetch all chapter pages for paginated chapter lists Some sites cap the ajax/chapters response at 200 entries and paginate via ?t=N links (data-page). Detect the pagination links on the first page and fetch the remaining pages, concatenating the HTML before parsing, so the full chapter list is returned instead of the cap. --- plugins/multisrc/madara/template.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/plugins/multisrc/madara/template.ts b/plugins/multisrc/madara/template.ts index ed392b0f1..91b01be6c 100644 --- a/plugins/multisrc/madara/template.ts +++ b/plugins/multisrc/madara/template.ts @@ -304,6 +304,30 @@ export class MadaraPlugin implements Plugin.PluginBase { method: 'POST', referrer: this.site + novelPath, }).then((res: Response) => res.text()); + + const $firstPage = parseHTML(html); + const pageLinks = $firstPage('.pagination a[data-page]'); + if (pageLinks.length > 0) { + const maxPage = Math.max( + ...pageLinks + .map((_, el) => + parseInt($firstPage(el).attr('data-page') || '1', 10), + ) + .get(), + ); + const lastHref = pageLinks.last().attr('href') || ''; + const queryIndex = lastHref.indexOf('?'); + if (queryIndex !== -1) { + const queryTemplate = lastHref.slice(queryIndex).replace(/\d+$/, ''); + for (let page = 2; page <= maxPage; page++) { + const pageHtml = await fetchApi( + this.site + novelPath + 'ajax/chapters/' + queryTemplate + page, + { method: 'POST', referrer: this.site + novelPath }, + ).then((res: Response) => res.text()); + if (pageHtml && pageHtml !== '0') html += pageHtml; + } + } + } } else { const novelId = loadedCheerio('.rating-post-id').attr('value') ||