From 0505c7333899d4121e8bdc948efe78f7f31f87bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartu=20=C3=96zen?= Date: Fri, 10 Jul 2026 00:23:20 +0300 Subject: [PATCH 1/6] Update Sbxh parser --- plugin/js/parsers/experimental/Sbxh1Parser.js | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/plugin/js/parsers/experimental/Sbxh1Parser.js b/plugin/js/parsers/experimental/Sbxh1Parser.js index 4ab90953..b051cfd9 100644 --- a/plugin/js/parsers/experimental/Sbxh1Parser.js +++ b/plugin/js/parsers/experimental/Sbxh1Parser.js @@ -1,7 +1,7 @@ "use strict"; parserFactory.registerUrlRule( - (url) => /^https?:\/\/(?:www\.)?sbxh\d+\.com(?:\/|$)/.test(url), + (url) => /^https?:\/\/(?:www\.)?(sbxh|toki)\d+\.com(?:\/|$)/.test(url), () => new Sbxh1Parser(), ); @@ -10,17 +10,22 @@ class Sbxh1Parser extends Parser { super(); } - getChapterUrls(dom) { + async getChapterUrls(dom) { let novelId = Sbxh1Parser.extractNovelId(dom.baseURI); if (novelId == null) { return []; } - - let chapterLinks = [ - ...dom.querySelectorAll(`a.novel-ep-link[href*="/novel/${novelId}/"]`), - ]; - if (chapterLinks.length === 0) { - chapterLinks = [...dom.querySelectorAll(`a[href*="/novel/${novelId}/"]`)]; + let chapterLinks = Sbxh1Parser.parseChapters(dom, novelId); + + let page = 1; + while (chapterLinks[chapterLinks.length - 1].querySelector(".ne-num").textContent !== "1화") { + page++; + const xml = (await HttpClient.wrapFetch(`${dom.baseURI}?epage=${page}`)).responseXML; + let newLinks = Sbxh1Parser.parseChapters(xml, novelId); + if (newLinks.length === 0) { + break; + } + chapterLinks.push(...newLinks); } let chaptersByUrl = new Map(); @@ -32,9 +37,12 @@ class Sbxh1Parser extends Parser { return; } let normalized = util.normalizeUrlForCompare(a.href); + let chapterNumber = a.querySelector(".ne-num").textContent; + let chapterTitle = a.querySelector(".ne-title").textContent; + let chapter = { sourceUrl: a.href, - title: Sbxh1Parser.cleanEpisodeTitle(a.textContent), + title: `${chapterNumber} - ${chapterTitle}`, episodeNumber: episodeNumber, }; let existing = chaptersByUrl.get(normalized); @@ -54,6 +62,16 @@ class Sbxh1Parser extends Parser { })); } + static parseChapters(dom, novelId) { + let chapterLinks = [ + ...dom.querySelectorAll(`a.novel-ep-link[href*="/novel/${novelId}/"]`), + ]; + if (chapterLinks.length === 0) { + chapterLinks = [...dom.querySelectorAll(`a[href*="/novel/${novelId}/"]`)]; + } + return chapterLinks; + } + findContent(dom) { return ( Parser.findConstrutedContent(dom) ?? @@ -299,15 +317,6 @@ class Sbxh1Parser extends Parser { ); } - static cleanEpisodeTitle(title) { - return title - .replace(/\bNEW\b/g, "") - .replace(/\d{2}\.\s*\d{2}\.\s*\d{2}\.?/g, "") - .replace(/\s+/g, " ") - .replace(/^(\d+\s*화)\1$/, "$1") - .trim(); - } - static extractEpisodeNumber(title) { let match = title.match(/(\d+)\s*화/); return match == null ? null : parseInt(match[1], 10); From 953ff8a77af0946300ab3791f66c35f59ca31d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartu=20=C3=96zen?= Date: Fri, 10 Jul 2026 00:50:08 +0300 Subject: [PATCH 2/6] Update credits --- package.json | 3 ++- readme.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index fbf984dd..f50319f1 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,8 @@ { "name": "ltmerletti"}, { "name": "fnx4"}, { "name": "Fox6935"}, - { "name": "ARYAN-9099" } + { "name": "ARYAN-9099" }, + { "name": "Bartuzen" } ], "license": "GPL-3.0-only", "bugs": { diff --git a/readme.md b/readme.md index e81cccf6..6bfa7fe1 100644 --- a/readme.md +++ b/readme.md @@ -824,6 +824,7 @@ Don't forget to give the project a star! Thanks again!
  • fnx4
  • Fox6935
  • ARYAN-9099
  • +
  • Bartuzen
  • From 2a2599cd2670320a2b3875770e594f9059919c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartu=20=C3=96zen?= Date: Fri, 10 Jul 2026 09:10:47 +0300 Subject: [PATCH 3/6] Include email in contributors --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f50319f1..9a75f235 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ { "name": "fnx4"}, { "name": "Fox6935"}, { "name": "ARYAN-9099" }, - { "name": "Bartuzen" } + { "name": "Bartuzen", "email": "git@bartuzen.dev" } ], "license": "GPL-3.0-only", "bugs": { From 8fd0b85ca8ff5733552cbdb107749229b0d56fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartu=20=C3=96zen?= Date: Fri, 10 Jul 2026 22:12:16 +0300 Subject: [PATCH 4/6] Update getChapterUrls function logic --- plugin/js/parsers/experimental/Sbxh1Parser.js | 76 ++++++------------- 1 file changed, 23 insertions(+), 53 deletions(-) diff --git a/plugin/js/parsers/experimental/Sbxh1Parser.js b/plugin/js/parsers/experimental/Sbxh1Parser.js index b051cfd9..06cf9d9c 100644 --- a/plugin/js/parsers/experimental/Sbxh1Parser.js +++ b/plugin/js/parsers/experimental/Sbxh1Parser.js @@ -15,54 +15,46 @@ class Sbxh1Parser extends Parser { if (novelId == null) { return []; } - let chapterLinks = Sbxh1Parser.parseChapters(dom, novelId); + let chapterLinks = []; - let page = 1; - while (chapterLinks[chapterLinks.length - 1].querySelector(".ne-num").textContent !== "1화") { + let page = 0; + // eslint-disable-next-line no-constant-condition + while (true) { page++; const xml = (await HttpClient.wrapFetch(`${dom.baseURI}?epage=${page}`)).responseXML; - let newLinks = Sbxh1Parser.parseChapters(xml, novelId); + let newLinks = Sbxh1Parser.extractChapterLinks(xml, novelId); if (newLinks.length === 0) { break; } - chapterLinks.push(...newLinks); - } - let chaptersByUrl = new Map(); - chapterLinks - .filter((a) => Sbxh1Parser.isEpisodeUrl(a.href, novelId)) - .forEach((a) => { - let episodeNumber = Sbxh1Parser.extractEpisodeNumber(a.textContent); - if (episodeNumber == null) { - return; - } - let normalized = util.normalizeUrlForCompare(a.href); + let newChapters = newLinks.map((a) => { let chapterNumber = a.querySelector(".ne-num").textContent; let chapterTitle = a.querySelector(".ne-title").textContent; - let chapter = { + return { sourceUrl: a.href, title: `${chapterNumber} - ${chapterTitle}`, - episodeNumber: episodeNumber, }; - let existing = chaptersByUrl.get(normalized); - if ( - existing == null || - Sbxh1Parser.isBetterEpisodeTitle(chapter.title, existing.title) - ) { - chaptersByUrl.set(normalized, chapter); - } }); - return [...chaptersByUrl.values()] - .sort((a, b) => a.episodeNumber - b.episodeNumber) - .map((a) => ({ - sourceUrl: a.sourceUrl, - title: a.title, - })); + const exists = newChapters.some((newCh) => + chapterLinks.some((ch) => ch.sourceUrl === newCh.sourceUrl) + ); + if (exists) { + break; + } + + chapterLinks.push(...newChapters); + + if (newChapters[newChapters.length - 1].title.startsWith("1화 -")) { + break; + } + } + + return chapterLinks.reverse(); } - static parseChapters(dom, novelId) { + static extractChapterLinks(dom, novelId) { let chapterLinks = [ ...dom.querySelectorAll(`a.novel-ep-link[href*="/novel/${novelId}/"]`), ]; @@ -310,28 +302,6 @@ class Sbxh1Parser extends Parser { return new URL(url).pathname.match(/^\/novel\/(\d+)/)?.[1] ?? null; } - static isEpisodeUrl(url, novelId) { - return ( - new URL(url).pathname.match(new RegExp(`^/novel/${novelId}/\\d+$`)) != - null - ); - } - - static extractEpisodeNumber(title) { - let match = title.match(/(\d+)\s*화/); - return match == null ? null : parseInt(match[1], 10); - } - - static isBetterEpisodeTitle(candidate, existing) { - let candidateHasNumber = /^\d+\s*화\b/.test(candidate); - let existingHasNumber = /^\d+\s*화\b/.test(existing); - return ( - (candidateHasNumber && !existingHasNumber) || - (candidateHasNumber === existingHasNumber && - candidate.length > existing.length) - ); - } - static extractViewerInfo(dom) { let scripts = [...dom.querySelectorAll("script")] .map((s) => s.textContent) From 9fd0a5e6c7f3cd4c209e0fa9210f2eaf91bd801f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartu=20=C3=96zen?= Date: Sun, 12 Jul 2026 15:28:16 +0300 Subject: [PATCH 5/6] Use URL API to build paginated request URL --- plugin/js/parsers/experimental/Sbxh1Parser.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugin/js/parsers/experimental/Sbxh1Parser.js b/plugin/js/parsers/experimental/Sbxh1Parser.js index 06cf9d9c..e5726def 100644 --- a/plugin/js/parsers/experimental/Sbxh1Parser.js +++ b/plugin/js/parsers/experimental/Sbxh1Parser.js @@ -21,7 +21,11 @@ class Sbxh1Parser extends Parser { // eslint-disable-next-line no-constant-condition while (true) { page++; - const xml = (await HttpClient.wrapFetch(`${dom.baseURI}?epage=${page}`)).responseXML; + + let nextUrl = new URL(dom.baseURI); + nextUrl.searchParams.set("epage", page); + const { responseXML: xml } = await HttpClient.wrapFetch(nextUrl.href); + let newLinks = Sbxh1Parser.extractChapterLinks(xml, novelId); if (newLinks.length === 0) { break; From bae3ba72d1a746fdba152703affae748857ca778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartu=20=C3=96zen?= Date: Mon, 13 Jul 2026 01:54:37 +0300 Subject: [PATCH 6/6] Update cover selector Otherwise it can pick random images for series that don't have a cover --- plugin/js/parsers/experimental/Sbxh1Parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/js/parsers/experimental/Sbxh1Parser.js b/plugin/js/parsers/experimental/Sbxh1Parser.js index e5726def..2ed72c65 100644 --- a/plugin/js/parsers/experimental/Sbxh1Parser.js +++ b/plugin/js/parsers/experimental/Sbxh1Parser.js @@ -118,7 +118,7 @@ class Sbxh1Parser extends Parser { } findCoverImageUrl(dom) { - return dom.querySelector("main img[alt]")?.src ?? null; + return dom.querySelector(".nd-thumb img[alt]")?.src ?? null; } findChapterTitle(dom) {