From 0e92630eeaf7dfddaacf0ab5ab49984064aa8994 Mon Sep 17 00:00:00 2001 From: "The Couch Potato (on Mars)" <116996397+martian-potato@users.noreply.github.com> Date: Mon, 17 Nov 2025 19:48:26 +0000 Subject: [PATCH] fix: Broken Chapter Scraper Due to Layout Change (Fenrir) (#1884) * fenrir: Fix broken chapter scrapper due to layout change Also remove comment nodes to reduce chapter size. Signed-off-by: Martian Potato <2260cse01024@manarat.ac.bd> * fenrir: Add type annotations Signed-off-by: Martian Potato <2260cse01024@manarat.ac.bd> --------- Signed-off-by: Martian Potato <2260cse01024@manarat.ac.bd> --- plugins/english/fenrirrealm.ts | 72 ++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/plugins/english/fenrirrealm.ts b/plugins/english/fenrirrealm.ts index a935442..fb6bee7 100644 --- a/plugins/english/fenrirrealm.ts +++ b/plugins/english/fenrirrealm.ts @@ -1,16 +1,44 @@ import { fetchApi } from '@libs/fetch'; import { Plugin } from '@/types/plugin'; +import { Node } from 'domhandler'; import { load as loadCheerio } from 'cheerio'; import { Filters, FilterTypes } from '@libs/filterInputs'; import { storage } from '@libs/storage'; import { defaultCover } from '@libs/defaultCover'; +type APINovel = { + title: string; + slug: string; + cover: string; + description: string; + status: string; + genres: { name: string }[]; +}; + +type APIChapter = { + locked: { price: number } | null; + group: null | { + index: number; + slug: string; + }; + title: string; + number: number; + created_at: string; +}; + +type ChapterInfo = { + name: string; + path: string; + releaseTime: string; + chapterNumber: number; +}; + class FenrirRealmPlugin implements Plugin.PluginBase { id = 'fenrir'; name = 'Fenrir Realm'; icon = 'src/en/fenrirrealm/icon.png'; site = 'https://fenrirealm.com'; - version = '1.0.11'; + version = '1.0.12'; imageRequestInit?: Plugin.ImageRequestInit | undefined = undefined; hideLocked = storage.get('hideLocked'); @@ -48,7 +76,7 @@ class FenrirRealmPlugin implements Plugin.PluginBase { }), ); - return res.data.map(r => this.parseNovelFromApi(r)); + return res.data.map((r: APINovel) => this.parseNovelFromApi(r)); } async parseNovel(novelPath: string): Promise { @@ -88,14 +116,14 @@ class FenrirRealmPlugin implements Plugin.PluginBase { ).then(r => r.json()); if (this.hideLocked) { - chapters = chapters.filter(c => !c.locked?.price); + chapters = chapters.filter((c: APIChapter) => !c.locked?.price); } novel.chapters = chapters - .map(c => ({ + .map((c: APIChapter) => ({ name: (c.locked?.price ? '🔒 ' : '') + - (c.group?.index === null ? '' : 'Vol ' + c.group.index + ' ') + + (c.group?.index === null ? '' : 'Vol ' + c.group?.index + ' ') + 'Chapter ' + c.number + (c.title && c.title.trim() != 'Chapter ' + c.number @@ -103,13 +131,15 @@ class FenrirRealmPlugin implements Plugin.PluginBase { : ''), path: novelPath + - (c.group?.index === null ? '' : '/' + c.group.slug) + + (c.group?.index === null ? '' : '/' + c.group?.slug) + '/chapter-' + c.number, releaseTime: c.created_at, - chapterNumber: c.number + c.group?.index * 1000000000000, + chapterNumber: c.number + (c.group?.index || 0) * 1000000000000, })) - .sort((a, b) => a.chapterNumber - b.chapterNumber); + .sort( + (a: ChapterInfo, b: ChapterInfo) => a.chapterNumber - b.chapterNumber, + ); return novel; } @@ -117,7 +147,15 @@ class FenrirRealmPlugin implements Plugin.PluginBase { const page = await fetchApi(this.site + '/series/' + chapterPath, {}).then( r => r.text(), ); - return loadCheerio(page)('#reader-area').html() || ''; + const chapter = loadCheerio(page)('[id^="reader-area-"]'); + chapter + .contents() + .filter((_, node: Node) => { + return node.type === 'comment'; + }) + .remove(); + + return chapter.html() || ''; } async searchNovels( @@ -128,10 +166,12 @@ class FenrirRealmPlugin implements Plugin.PluginBase { `${this.site}/api/series/filter?page=${pageNo}&per_page=20&search=${encodeURIComponent(searchTerm)}`, ) .then(r => r.json()) - .then(r => r.data.map(novel => this.parseNovelFromApi(novel))); + .then(r => + r.data.map((novel: APINovel) => this.parseNovelFromApi(novel)), + ); } - parseNovelFromApi(apiData) { + parseNovelFromApi(apiData: APINovel) { return { name: apiData.title, path: apiData.slug, @@ -249,11 +289,19 @@ class FenrirRealmPlugin implements Plugin.PluginBase { export default new FenrirRealmPlugin(); +type GenreData = { + name: string; + id: number; +}; + //paste into console on site to load async function getUpdatedGenres() { const data = await fetch( 'https://fenrirealm.com/api/novels/taxonomy/genres', ).then(d => d.json()); - const genreData = data.map(g => ({ label: g.name, value: g.id.toString() })); + const genreData = data.map((g: GenreData) => ({ + label: g.name, + value: g.id.toString(), + })); console.log(JSON.stringify(genreData)); }