From 6c95c9c089797949a5012cea4ca1e9af497417c0 Mon Sep 17 00:00:00 2001 From: Gourab0002 Date: Sat, 24 Oct 2026 19:21:38 +0530 Subject: [PATCH 1/2] fix(arabic/galaxynovels): load the full chapter list from the manifest pack Novel pages now leave data-index-url empty and only render the first 30 chapters, so the plugin fell back to HTML parsing and showed 30 chapters. When the index URL is empty, read data-manifest-url and load the full chapter list from its pack_url. Bump version to 1.1.1. Closes #2667 Co-Authored-By: Grok Bot (AI agent) --- plugins/arabic/galaxynovels.ts | 41 ++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/plugins/arabic/galaxynovels.ts b/plugins/arabic/galaxynovels.ts index 155c1dae7..27ba2218f 100644 --- a/plugins/arabic/galaxynovels.ts +++ b/plugins/arabic/galaxynovels.ts @@ -33,7 +33,7 @@ type ChapterContentResponse = { class GalaxyNovels implements Plugin.PluginBase { id = 'galaxynovels'; name = 'Galaxy Novels'; - version = '1.1.0'; + version = '1.1.1'; icon = 'src/ar/galaxynovels/icon.png'; site = 'https://galaxynovels.com/'; @@ -106,8 +106,7 @@ class GalaxyNovels implements Plugin.PluginBase { const coverLink = $el.find('a.wor-novel-card__cover'); const href = coverLink.attr('href'); const img = $el.find('img.wor-cover-img'); - const cover = - img.attr('data-src') || img.attr('src') || undefined; + const cover = img.attr('data-src') || img.attr('src') || undefined; const title = $el.find('h3 a').text().trim(); if (!href || !title) return; @@ -149,10 +148,23 @@ class GalaxyNovels implements Plugin.PluginBase { const status = statusMap[statusText] || NovelStatus.Unknown; const chaptersContainer = $('[data-wor-chapters-container]'); - const chaptersIndexUrl = chaptersContainer.attr('data-index-url'); + let chaptersIndexUrl = chaptersContainer.attr('data-index-url'); + const chaptersManifestUrl = chaptersContainer.attr('data-manifest-url'); let chapters: Plugin.ChapterItem[] = []; + if (!chaptersIndexUrl && chaptersManifestUrl) { + // The page only renders 30 chapters; the full list is in the manifest's pack + try { + const manifest = await this.fetchJson<{ pack_url?: string }>( + chaptersManifestUrl, + ); + chaptersIndexUrl = manifest.pack_url; + } catch { + // fallback to HTML parsing + } + } + if (chaptersIndexUrl) { try { const indexUrl = chaptersIndexUrl.startsWith('http') @@ -174,8 +186,12 @@ class GalaxyNovels implements Plugin.PluginBase { if (chapters.length === 0) { $('article.wor-novel-chapter-item').each((_, el) => { const $el = $(el); - const chapterLink = $el.find('h3 a').attr('href') || $el.find('a.wor-novel-chapter-item__num').attr('href'); - const chapterName = $el.find('h3 a').text().trim() || $el.find('a.wor-novel-chapter-item__num').text().trim(); + const chapterLink = + $el.find('h3 a').attr('href') || + $el.find('a.wor-novel-chapter-item__num').attr('href'); + const chapterName = + $el.find('h3 a').text().trim() || + $el.find('a.wor-novel-chapter-item__num').text().trim(); const chapterId = $el.attr('data-chapter-id'); const timeEl = $el.find('time'); const releaseTime = timeEl.attr('datetime')?.split('T')[0] || ''; @@ -228,8 +244,9 @@ class GalaxyNovels implements Plugin.PluginBase { const url = `${this.baseUrl}${chapterPath}`; const html = await this.fetchHtml(url); const $ = loadCheerio(html); - const content = - $('article.wor-chapter-content, .wor-chapter-text, .entry-content').html(); + const content = $( + 'article.wor-chapter-content, .wor-chapter-text, .entry-content', + ).html(); return content || '

Content not available.

'; } @@ -254,9 +271,7 @@ class GalaxyNovels implements Plugin.PluginBase { const term = searchTerm.toLowerCase(); const filtered = searchIndex.items.filter( - n => - n.t.toLowerCase().includes(term) || - n.s.toLowerCase().includes(term), + n => n.t.toLowerCase().includes(term) || n.s.toLowerCase().includes(term), ); const limit = 20; @@ -265,9 +280,7 @@ class GalaxyNovels implements Plugin.PluginBase { return filtered.slice(offset, offset + limit).map(novel => ({ name: novel.t, path: novel.u, - cover: novel.c.startsWith('http') - ? novel.c - : `${this.baseUrl}${novel.c}`, + cover: novel.c.startsWith('http') ? novel.c : `${this.baseUrl}${novel.c}`, })); } } From a97a1d82f22af94eb4f96359890e3851a06b8924 Mon Sep 17 00:00:00 2001 From: Gourab0002 Date: Sat, 24 Oct 2026 19:28:04 +0530 Subject: [PATCH 2/2] fix(arabic/galaxynovels): use real chapter URLs and parse the reader text Chapter paths were built as /chapter-/, which 404s; real URLs are /chapter-// (the pack's url field). Use those, only call the id-based API for legacy id paths, and read content from .wor-reader-text-surface with the hidden decoy elements removed. Co-Authored-By: Grok Bot (AI agent) --- plugins/arabic/galaxynovels.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/plugins/arabic/galaxynovels.ts b/plugins/arabic/galaxynovels.ts index 27ba2218f..fe2a84a0a 100644 --- a/plugins/arabic/galaxynovels.ts +++ b/plugins/arabic/galaxynovels.ts @@ -174,7 +174,9 @@ class GalaxyNovels implements Plugin.PluginBase { chapters = index.chapters.map(ch => ({ name: ch.label + (ch.title ? `: ${ch.title}` : ''), - path: `${novelPath}chapter-${ch.id}/`, + path: ch.url + ? new URL(ch.url, this.site).pathname + : `${novelPath}chapter-${ch.id}/`, chapterNumber: ch.position, releaseTime: ch.date_iso?.split('T')[0] || '', })); @@ -192,15 +194,12 @@ class GalaxyNovels implements Plugin.PluginBase { const chapterName = $el.find('h3 a').text().trim() || $el.find('a.wor-novel-chapter-item__num').text().trim(); - const chapterId = $el.attr('data-chapter-id'); const timeEl = $el.find('time'); const releaseTime = timeEl.attr('datetime')?.split('T')[0] || ''; if (!chapterLink) return; - const path = chapterId - ? `${novelPath}chapter-${chapterId}/` - : new URL(chapterLink, this.site).pathname; + const path = new URL(chapterLink, this.site).pathname; const numMatch = path.match(/chapter-(\d+)/); const chapterNumber = numMatch ? parseInt(numMatch[1]) : 0; @@ -226,7 +225,8 @@ class GalaxyNovels implements Plugin.PluginBase { } async parseChapter(chapterPath: string): Promise { - const idMatch = chapterPath.match(/chapter-(\d+)/); + // Only legacy `chapter-/` paths carry the post id; real URLs are `chapter-//` + const idMatch = chapterPath.match(/chapter-(\d+)\/?$/); const chapterId = idMatch?.[1]; if (chapterId) { @@ -244,9 +244,12 @@ class GalaxyNovels implements Plugin.PluginBase { const url = `${this.baseUrl}${chapterPath}`; const html = await this.fetchHtml(url); const $ = loadCheerio(html); + $('[data-wor-decoy], [hidden]').remove(); const content = $( - 'article.wor-chapter-content, .wor-chapter-text, .entry-content', - ).html(); + '.wor-reader-text-surface, article.wor-chapter-content, .wor-chapter-text, .entry-content', + ) + .first() + .html(); return content || '

Content not available.

'; }