From 70675987e959427061f4a9960a5f3eb4b7ccf863 Mon Sep 17 00:00:00 2001 From: xMohnad Date: Tue, 17 Jun 2025 00:11:23 +0300 Subject: [PATCH 01/13] feat(ar/kolnovel): add initial source integration for KolNovel --- javascript/novel/src/ar/kolnovel.js | 94 +++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 javascript/novel/src/ar/kolnovel.js diff --git a/javascript/novel/src/ar/kolnovel.js b/javascript/novel/src/ar/kolnovel.js new file mode 100644 index 00000000..0e0b6e9b --- /dev/null +++ b/javascript/novel/src/ar/kolnovel.js @@ -0,0 +1,94 @@ +// prettier-ignore +const mangayomiSources = [{ + "name": "ملوك الروايات", + "lang": "ar", + "baseUrl": "https://kolnovel.com", + "apiUrl": "", + "iconUrl": "https://www.google.com/s2/favicons?sz=256&domain=https://kolnovel.com", + "typeSource": "single", + "itemType": 2, + "version": "0.0.1", + "pkgPath": "novel/src/ar/kolnovel.js", + "notes": "" +}]; + +class DefaultExtension extends MProvider { + headers = { + Referer: this.source.baseUrl, + Origin: this.source.baseUrl, + "Sec-Fetch-Mode": "cors", + "Accept-Encoding": "gzip, deflate", + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", + }; + + getHeaders(url) { + throw new Error("getHeaders not implemented"); + } + + cleanTitle(title) { + title = title.replace(/[-@%&]*\b(?:kol|كول)\b[-@%&]*/gi, ""); + title = title.replace(/[-@%&]+/g, " "); + title = title.replace(/\s+/g, " ").trim(); + return title; + } + + novelFromElement(res) { + const doc = new Document(res.body); + const elements = doc.select("div.listupd article"); + const list = []; + for (const el of elements) { + const name = this.cleanTitle(el.selectFirst("h2 a").text); + const imageUrl = el.selectFirst("img").getSrc; + const link = el.selectFirst("h2 a").getHref; + list.push({ name, imageUrl, link }); + } + const hasNextPage = doc.selectFirst("div.hpage > a.r").text == "Next "; + return { list, hasNextPage }; + } + + async getPopular(page) { + const res = await new Client().get( + `${this.source.baseUrl}/series/?page=${page}&order=popular`, + this.headers, + ); + return this.novelFromElement(res); + } + + async getLatestUpdates(page) { + const res = await new Client().get( + `${this.source.baseUrl}/series/?page=${page}&order=update`, + this.headers, + ); + return this.novelFromElement(res); + } + + async search(query, page, filters) { + throw new Error("search not implemented"); + } + async getDetail(url) { + throw new Error("getDetail not implemented"); + } + // For novel html content + async getHtmlContent(name, url) { + throw new Error("getHtmlContent not implemented"); + } + // Clean html up for reader + async cleanHtmlContent(html) { + throw new Error("cleanHtmlContent not implemented"); + } + // For anime episode video list + async getVideoList(url) { + throw new Error("getVideoList not implemented"); + } + // For manga chapter pages + async getPageList(url) { + throw new Error("getPageList not implemented"); + } + getFilterList() { + throw new Error("getFilterList not implemented"); + } + getSourcePreferences() { + throw new Error("getSourcePreferences not implemented"); + } +} From b46c9e05eb86ab072b38857796de20d2b72780fe Mon Sep 17 00:00:00 2001 From: xMohnad Date: Tue, 17 Jun 2025 10:10:01 +0000 Subject: [PATCH 02/13] =?UTF-8?q?refactor(kolnovel):=20update=20regex=20an?= =?UTF-8?q?d=20skip=20cleanup=20if=20title=20doesn't=20contain=20'kol'=20o?= =?UTF-8?q?r=20'=D9=83=D9=88=D9=84'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- javascript/novel/src/ar/kolnovel.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/javascript/novel/src/ar/kolnovel.js b/javascript/novel/src/ar/kolnovel.js index 0e0b6e9b..67f49433 100644 --- a/javascript/novel/src/ar/kolnovel.js +++ b/javascript/novel/src/ar/kolnovel.js @@ -27,8 +27,9 @@ class DefaultExtension extends MProvider { } cleanTitle(title) { - title = title.replace(/[-@%&]*\b(?:kol|كول)\b[-@%&]*/gi, ""); - title = title.replace(/[-@%&]+/g, " "); + if (!/[-_&@#%^)(*\s]*(كول|kol)/i.test(title)) return title; + title = title.replace(/[-_&@#%^)(*\s]*(كول|kol)/i, ""); + title = title.replace(/[-–_&@#%^)(*+،؛:]+/g, " "); title = title.replace(/\s+/g, " ").trim(); return title; } From 25367a6c4ee8d6bfdac4dcdc8f8c05ef0fbc8cfe Mon Sep 17 00:00:00 2001 From: xMohnad Date: Tue, 17 Jun 2025 14:16:17 +0000 Subject: [PATCH 03/13] kolnovel: add source preferences for switching between official and free sources --- javascript/novel/src/ar/kolnovel.js | 65 ++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/javascript/novel/src/ar/kolnovel.js b/javascript/novel/src/ar/kolnovel.js index 67f49433..d3c3fff2 100644 --- a/javascript/novel/src/ar/kolnovel.js +++ b/javascript/novel/src/ar/kolnovel.js @@ -14,14 +14,16 @@ const mangayomiSources = [{ class DefaultExtension extends MProvider { headers = { - Referer: this.source.baseUrl, - Origin: this.source.baseUrl, + Referer: this.activeSiteUrl, + Origin: this.activeSiteUrl, "Sec-Fetch-Mode": "cors", "Accept-Encoding": "gzip, deflate", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", }; + defaultKolBookUrl = "https://kolbook.xyz"; + getHeaders(url) { throw new Error("getHeaders not implemented"); } @@ -50,7 +52,7 @@ class DefaultExtension extends MProvider { async getPopular(page) { const res = await new Client().get( - `${this.source.baseUrl}/series/?page=${page}&order=popular`, + `${this.activeSiteUrl}/series/?page=${page}&order=popular`, this.headers, ); return this.novelFromElement(res); @@ -58,7 +60,7 @@ class DefaultExtension extends MProvider { async getLatestUpdates(page) { const res = await new Client().get( - `${this.source.baseUrl}/series/?page=${page}&order=update`, + `${this.activeSiteUrl}/series/?page=${page}&order=update`, this.headers, ); return this.novelFromElement(res); @@ -89,7 +91,60 @@ class DefaultExtension extends MProvider { getFilterList() { throw new Error("getFilterList not implemented"); } + + getSanitizedUrl(prefKey) { + const preference = new SharedPreferences(); + let url = preference.get(prefKey) || this.source.baseUrl; + return url.endsWith("/") ? url.slice(0, -1) : url; + } + + get activeSiteUrl() { + return this.getSanitizedUrl("selected_site_url"); + } + + get kolNovelUrl() { + return this.getSanitizedUrl("kolnovel_custom_url"); + } + + get kolBookUrl() { + return this.getSanitizedUrl("kolbook_custom_url"); + } + getSourcePreferences() { - throw new Error("getSourcePreferences not implemented"); + return [ + { + key: "kolnovel_custom_url", + editTextPreference: { + title: "المصدر الرئيسي", + summary: "يوفر كافة الفصول، لكن بعض المحتوى يتطلب اشتراكًا.", + value: this.source.baseUrl, + dialogTitle: "URL", + dialogMessage: "", + }, + }, + { + key: "kolbook_custom_url", + editTextPreference: { + title: "المصدر المجاني", + summary: "لا يتطلب اشتراكًا، ولكن قد لا بحتوي على كافة الفصول.", + value: this.defaultKolBookUrl, + dialogTitle: "URL", + dialogMessage: "", + }, + }, + { + key: "selected_site_url", + listPreference: { + title: "أختر المصدر.", + summary: "", + valueIndex: 0, + entries: [ + "المصدر الرسمي (قد يتطلب اشتراك)", + "المصدر المجانية (بدون اشتراك)", + ], + entryValues: [this.kolNovelUrl, this.kolBookUrl], + }, + }, + ]; } } From 089784b075ae78878598141e012681696a811c8d Mon Sep 17 00:00:00 2001 From: xMohnad Date: Thu, 19 Jun 2025 10:04:35 +0000 Subject: [PATCH 04/13] refactor(kolnovel): replace property getters with methods --- javascript/novel/src/ar/kolnovel.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/javascript/novel/src/ar/kolnovel.js b/javascript/novel/src/ar/kolnovel.js index d3c3fff2..bd06257e 100644 --- a/javascript/novel/src/ar/kolnovel.js +++ b/javascript/novel/src/ar/kolnovel.js @@ -14,8 +14,8 @@ const mangayomiSources = [{ class DefaultExtension extends MProvider { headers = { - Referer: this.activeSiteUrl, - Origin: this.activeSiteUrl, + Referer: this.getActiveSiteUrl(), + Origin: this.getActiveSiteUrl(), "Sec-Fetch-Mode": "cors", "Accept-Encoding": "gzip, deflate", "User-Agent": @@ -52,7 +52,7 @@ class DefaultExtension extends MProvider { async getPopular(page) { const res = await new Client().get( - `${this.activeSiteUrl}/series/?page=${page}&order=popular`, + `${this.getActiveSiteUrl()}/series/?page=${page}&order=popular`, this.headers, ); return this.novelFromElement(res); @@ -60,7 +60,7 @@ class DefaultExtension extends MProvider { async getLatestUpdates(page) { const res = await new Client().get( - `${this.activeSiteUrl}/series/?page=${page}&order=update`, + `${this.getActiveSiteUrl()}/series/?page=${page}&order=update`, this.headers, ); return this.novelFromElement(res); @@ -98,15 +98,15 @@ class DefaultExtension extends MProvider { return url.endsWith("/") ? url.slice(0, -1) : url; } - get activeSiteUrl() { + getActiveSiteUrl() { return this.getSanitizedUrl("selected_site_url"); } - get kolNovelUrl() { + getKolNovelUrl() { return this.getSanitizedUrl("kolnovel_custom_url"); } - get kolBookUrl() { + getKolBookUrl() { return this.getSanitizedUrl("kolbook_custom_url"); } @@ -142,7 +142,7 @@ class DefaultExtension extends MProvider { "المصدر الرسمي (قد يتطلب اشتراك)", "المصدر المجانية (بدون اشتراك)", ], - entryValues: [this.kolNovelUrl, this.kolBookUrl], + entryValues: [this.getKolNovelUrl(), this.getKolBookUrl()], }, }, ]; From 867bd5eda7b06b8c2386b9e569776c128cbc3a14 Mon Sep 17 00:00:00 2001 From: xMohnad Date: Thu, 19 Jun 2025 14:27:31 +0000 Subject: [PATCH 05/13] kolnovel: fix crash by removing dynamic method calls from entryValues --- javascript/novel/src/ar/kolnovel.js | 43 ++++++++++++++--------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/javascript/novel/src/ar/kolnovel.js b/javascript/novel/src/ar/kolnovel.js index bd06257e..9e0edf61 100644 --- a/javascript/novel/src/ar/kolnovel.js +++ b/javascript/novel/src/ar/kolnovel.js @@ -92,22 +92,19 @@ class DefaultExtension extends MProvider { throw new Error("getFilterList not implemented"); } - getSanitizedUrl(prefKey) { - const preference = new SharedPreferences(); - let url = preference.get(prefKey) || this.source.baseUrl; - return url.endsWith("/") ? url.slice(0, -1) : url; - } - getActiveSiteUrl() { - return this.getSanitizedUrl("selected_site_url"); - } + const preference = new SharedPreferences(); + const selectedSiteKey = + preference.get("selected_site_key") || "kolnovel_custom_url"; + let url; + if (selectedSiteKey === "kolnovel_custom_url") { + url = preference.get(selectedSiteKey) || this.source.baseUrl; + } else { + // kolbook_custom_url + url = preference.get(selectedSiteKey) || this.defaultKolBookUrl; + } - getKolNovelUrl() { - return this.getSanitizedUrl("kolnovel_custom_url"); - } - - getKolBookUrl() { - return this.getSanitizedUrl("kolbook_custom_url"); + return url.endsWith("/") ? url.slice(0, -1) : url; } getSourcePreferences() { @@ -115,21 +112,21 @@ class DefaultExtension extends MProvider { { key: "kolnovel_custom_url", editTextPreference: { - title: "المصدر الرئيسي", - summary: "يوفر كافة الفصول، لكن بعض المحتوى يتطلب اشتراكًا.", + title: "-تعديل الرابط -الرئيسي", + summary: "", value: this.source.baseUrl, - dialogTitle: "URL", - dialogMessage: "", + dialogTitle: "تعديل", + dialogMessage: `Defaul URL ${this.source.baseUrl}`, }, }, { key: "kolbook_custom_url", editTextPreference: { - title: "المصدر المجاني", - summary: "لا يتطلب اشتراكًا، ولكن قد لا بحتوي على كافة الفصول.", + title: "-تعديل الرابط -المجاني", + summary: "", value: this.defaultKolBookUrl, - dialogTitle: "URL", - dialogMessage: "", + dialogTitle: "تعديل", + dialogMessage: `Defaul URL ${this.defaultKolBookUrl}`, }, }, { @@ -142,7 +139,7 @@ class DefaultExtension extends MProvider { "المصدر الرسمي (قد يتطلب اشتراك)", "المصدر المجانية (بدون اشتراك)", ], - entryValues: [this.getKolNovelUrl(), this.getKolBookUrl()], + entryValues: ["kolnovel_custom_url", "kolbook_custom_url"], }, }, ]; From 79073a837e52f88eebe91a21a70e13f72e202eb3 Mon Sep 17 00:00:00 2001 From: xMohnad Date: Thu, 19 Jun 2025 14:29:52 +0000 Subject: [PATCH 06/13] kolnovel: fix crash by removing problematic headers --- javascript/novel/src/ar/kolnovel.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/javascript/novel/src/ar/kolnovel.js b/javascript/novel/src/ar/kolnovel.js index 9e0edf61..32f1fc99 100644 --- a/javascript/novel/src/ar/kolnovel.js +++ b/javascript/novel/src/ar/kolnovel.js @@ -14,8 +14,6 @@ const mangayomiSources = [{ class DefaultExtension extends MProvider { headers = { - Referer: this.getActiveSiteUrl(), - Origin: this.getActiveSiteUrl(), "Sec-Fetch-Mode": "cors", "Accept-Encoding": "gzip, deflate", "User-Agent": @@ -130,7 +128,7 @@ class DefaultExtension extends MProvider { }, }, { - key: "selected_site_url", + key: "selected_site_key", listPreference: { title: "أختر المصدر.", summary: "", From 2bac9ab03acfde6e892d18ba92769c76b817d363 Mon Sep 17 00:00:00 2001 From: xMohnad Date: Fri, 20 Jun 2025 14:05:11 +0000 Subject: [PATCH 07/13] kolnovel: Implement novel details parsing --- javascript/novel/src/ar/kolnovel.js | 104 +++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 2 deletions(-) diff --git a/javascript/novel/src/ar/kolnovel.js b/javascript/novel/src/ar/kolnovel.js index 32f1fc99..c6ce1d8e 100644 --- a/javascript/novel/src/ar/kolnovel.js +++ b/javascript/novel/src/ar/kolnovel.js @@ -67,9 +67,85 @@ class DefaultExtension extends MProvider { async search(query, page, filters) { throw new Error("search not implemented"); } - async getDetail(url) { - throw new Error("getDetail not implemented"); + + toStatus(status) { + return ( + { + Ongoing: 0, + Completed: 1, + Hiatus: 2, + }[status] ?? 5 // 5 => unknown + ); } + + async getDetail(url) { + const res = await new Client().get(url, this.headers); + const doc = new Document(res.body); + const info = doc.selectFirst("div.sertoinfo"); + const subInfo = info.selectFirst("div.sertoauth"); + const rewName = info.selectFirst("h1.entry-title").text; + + const name = this.cleanTitle(rewName); + const imageUrl = doc.selectFirst("img.attachment-post-thumbnail")?.getSrc; + const scanlator = subInfo.selectFirst(".serl:contains('المترجم') a").text; + + let description = + info.selectFirst("div.sersys.entry-content p").text + "\n\n"; + const lang = subInfo.selectFirst( + ".serl:contains('اللغة الأم') .serval", + )?.text; + if (lang) description += `اللغة الأم: ${lang}\n`; + const releaseYear = subInfo.selectFirst( + ".serl:contains('صدر في سنة') .serval", + )?.text; + if (releaseYear) description += `سنة الصدور: ${releaseYear}\n`; + const types = subInfo + .select(".serl:contains('نوع') a") + .map((el) => el.text) + .join(", "); + if (types) description += `الانواع: ${types}\n`; + const altTitle = this.cleanTitle(info.selectFirst("span.alter")?.text); + if (altTitle) description += `اسم آخر للعمل: ${altTitle}\n`; + + const genre = info.select("div.sertogenre a").map((el) => el.text); + const author = subInfo.selectFirst(".serl:contains('الكاتب') a").text; + const status = this.toStatus(info.selectFirst("div.sertostat span").text); + + const chapters = []; + for (const el of doc.select("div.sertobody div.bixbox ul li > a")) { + const url = el.getHref; + const dateUpload = this.parseDate(el.selectFirst("div.epl-date").text); + + // Chapter name + let title = el.selectFirst("div.epl-title").text.trim(); + const num = el.selectFirst("div.epl-num").text.trim(); + + if (title.includes(num)) title = title.replace(num, "").trim(); + if (title.includes(rewName)) title = title.replace(rewName, "").trim(); + if (title.includes(name)) title = title.replace(name, "").trim(); + + const numMatch = num.match(/(?:الفصل|chapter)\s+(\d+(?:\.\d+)?)/i); + if (numMatch) + title = title.replace(numMatch[0], "").replace(numMatch[1], "").trim(); + + title = title.replace(/\s{2,}/g, " ").trim(); + const finalName = + title && num ? `${num}: ${title}` : title ? title : num ? num : "?"; + + chapters.push({ name: finalName, url, dateUpload, scanlator }); + } + + return { + name, + imageUrl, + description, + genre, + author, + status, + chapters, + }; + } + // For novel html content async getHtmlContent(name, url) { throw new Error("getHtmlContent not implemented"); @@ -142,4 +218,28 @@ class DefaultExtension extends MProvider { }, ]; } + + parseDate(date) { + const months = { + يناير: "January", + فبراير: "February", + مارس: "March", + أبريل: "April", + ابريل: "April", + مايو: "May", + يونيو: "June", + يوليو: "July", + أغسطس: "August", + اغسطس: "August", + سبتمبر: "September", + أكتوبر: "October", + اكتوبر: "October", + نوفمبر: "November", + ديسمبر: "December", + }; + const [monthAr, day, year] = date.split(/[\s,]+/); + const monthEnglish = months[monthAr] || ""; + if (!monthEnglish) return ""; + return new Date(`${monthEnglish} ${day}, ${year}`).getTime().toString(); + } } From d3738b94c7efc88ad5b361e1313ce99f96b122e4 Mon Sep 17 00:00:00 2001 From: xMohnad Date: Fri, 20 Jun 2025 17:42:10 +0000 Subject: [PATCH 08/13] kolnovel: implement getHtmlContent and cleanHtmlContent using API --- javascript/novel/src/ar/kolnovel.js | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/javascript/novel/src/ar/kolnovel.js b/javascript/novel/src/ar/kolnovel.js index c6ce1d8e..4d03b657 100644 --- a/javascript/novel/src/ar/kolnovel.js +++ b/javascript/novel/src/ar/kolnovel.js @@ -146,22 +146,27 @@ class DefaultExtension extends MProvider { }; } + extractIdFromUrl(url) { + const match = url.match(/-(\d+)\/?$/); + return match ? match[1] : null; + } + // For novel html content async getHtmlContent(name, url) { - throw new Error("getHtmlContent not implemented"); + const id = this.extractIdFromUrl(url); + const res = await new Client().get( + `${this.getActiveSiteUrl()}/wp-json/wp/v2/posts/${id}`, + this.headers, + ); + + return this.cleanHtmlContent(JSON.parse(res.body)); } + // Clean html up for reader async cleanHtmlContent(html) { - throw new Error("cleanHtmlContent not implemented"); - } - // For anime episode video list - async getVideoList(url) { - throw new Error("getVideoList not implemented"); - } - // For manga chapter pages - async getPageList(url) { - throw new Error("getPageList not implemented"); + return `

${html.title.rendered}



${html.content.rendered}`; } + getFilterList() { throw new Error("getFilterList not implemented"); } From 4ddaec1e282614016b837e8570dfc12a57ea099e Mon Sep 17 00:00:00 2001 From: xMohnad Date: Fri, 20 Jun 2025 17:52:27 +0000 Subject: [PATCH 09/13] kolnovel: drop kolbook source; not compatible with API --- javascript/novel/src/ar/kolnovel.js | 54 +++++++---------------------- 1 file changed, 13 insertions(+), 41 deletions(-) diff --git a/javascript/novel/src/ar/kolnovel.js b/javascript/novel/src/ar/kolnovel.js index 4d03b657..c2687b09 100644 --- a/javascript/novel/src/ar/kolnovel.js +++ b/javascript/novel/src/ar/kolnovel.js @@ -20,8 +20,6 @@ class DefaultExtension extends MProvider { "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", }; - defaultKolBookUrl = "https://kolbook.xyz"; - getHeaders(url) { throw new Error("getHeaders not implemented"); } @@ -50,7 +48,7 @@ class DefaultExtension extends MProvider { async getPopular(page) { const res = await new Client().get( - `${this.getActiveSiteUrl()}/series/?page=${page}&order=popular`, + `${this.getBaseUrl()}/series/?page=${page}&order=popular`, this.headers, ); return this.novelFromElement(res); @@ -58,7 +56,7 @@ class DefaultExtension extends MProvider { async getLatestUpdates(page) { const res = await new Client().get( - `${this.getActiveSiteUrl()}/series/?page=${page}&order=update`, + `${this.getBaseUrl()}/series/?page=${page}&order=update`, this.headers, ); return this.novelFromElement(res); @@ -155,7 +153,7 @@ class DefaultExtension extends MProvider { async getHtmlContent(name, url) { const id = this.extractIdFromUrl(url); const res = await new Client().get( - `${this.getActiveSiteUrl()}/wp-json/wp/v2/posts/${id}`, + `${this.getBaseUrl()}/wp-json/wp/v2/posts/${id}`, this.headers, ); @@ -171,56 +169,30 @@ class DefaultExtension extends MProvider { throw new Error("getFilterList not implemented"); } - getActiveSiteUrl() { + getBaseUrl() { const preference = new SharedPreferences(); - const selectedSiteKey = - preference.get("selected_site_key") || "kolnovel_custom_url"; - let url; - if (selectedSiteKey === "kolnovel_custom_url") { - url = preference.get(selectedSiteKey) || this.source.baseUrl; - } else { - // kolbook_custom_url - url = preference.get(selectedSiteKey) || this.defaultKolBookUrl; + var base_url = preference.get("base_url"); + if (base_url.length == 0) { + return this.source.baseUrl; } - - return url.endsWith("/") ? url.slice(0, -1) : url; + if (base_url.endsWith("/")) { + return base_url.slice(0, -1); + } + return base_url; } getSourcePreferences() { return [ { - key: "kolnovel_custom_url", + key: "base_url", editTextPreference: { - title: "-تعديل الرابط -الرئيسي", + title: "تعديل الرابط", summary: "", value: this.source.baseUrl, dialogTitle: "تعديل", dialogMessage: `Defaul URL ${this.source.baseUrl}`, }, }, - { - key: "kolbook_custom_url", - editTextPreference: { - title: "-تعديل الرابط -المجاني", - summary: "", - value: this.defaultKolBookUrl, - dialogTitle: "تعديل", - dialogMessage: `Defaul URL ${this.defaultKolBookUrl}`, - }, - }, - { - key: "selected_site_key", - listPreference: { - title: "أختر المصدر.", - summary: "", - valueIndex: 0, - entries: [ - "المصدر الرسمي (قد يتطلب اشتراك)", - "المصدر المجانية (بدون اشتراك)", - ], - entryValues: ["kolnovel_custom_url", "kolbook_custom_url"], - }, - }, ]; } From 827c01ff52b2d5f0d1fc526870255afd859d2434 Mon Sep 17 00:00:00 2001 From: xMohnad Date: Fri, 20 Jun 2025 20:00:15 +0000 Subject: [PATCH 10/13] kolnovel: Add novelFromJson method and implement search functionality --- javascript/novel/src/ar/kolnovel.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/javascript/novel/src/ar/kolnovel.js b/javascript/novel/src/ar/kolnovel.js index c2687b09..a685cecb 100644 --- a/javascript/novel/src/ar/kolnovel.js +++ b/javascript/novel/src/ar/kolnovel.js @@ -46,6 +46,17 @@ class DefaultExtension extends MProvider { return { list, hasNextPage }; } + novelFromJson(json) { + const list = []; + for (const el of json.series[0].all) { + const name = this.cleanTitle(el.post_title); + const imageUrl = el.post_image; + const link = el.post_link; + list.push({ name, imageUrl, link }); + } + return { list, hasNextPage: false }; + } + async getPopular(page) { const res = await new Client().get( `${this.getBaseUrl()}/series/?page=${page}&order=popular`, @@ -63,7 +74,17 @@ class DefaultExtension extends MProvider { } async search(query, page, filters) { - throw new Error("search not implemented"); + const keyword = query.trim().replace(/\s+/g, "+"); + if (keyword) { + const res = await new Client().post( + `${this.getBaseUrl()}/wp-admin/admin-ajax.php`, + { + "Content-Type": "application/x-www-form-urlencoded", + }, + `action=ts_ac_do_search&ts_ac_query=${keyword}`, + ); + return this.novelFromJson(JSON.parse(res.body)); + } } toStatus(status) { From 161163c62104e4df5f7fd96649fbc30106f3cd66 Mon Sep 17 00:00:00 2001 From: xMohnad Date: Sat, 21 Jun 2025 08:13:32 +0000 Subject: [PATCH 11/13] kolnovel: Add novel filtering support --- javascript/novel/src/ar/kolnovel.js | 248 +++++++++++++++++++++++++++- 1 file changed, 247 insertions(+), 1 deletion(-) diff --git a/javascript/novel/src/ar/kolnovel.js b/javascript/novel/src/ar/kolnovel.js index a685cecb..42b774a3 100644 --- a/javascript/novel/src/ar/kolnovel.js +++ b/javascript/novel/src/ar/kolnovel.js @@ -85,6 +85,26 @@ class DefaultExtension extends MProvider { ); return this.novelFromJson(JSON.parse(res.body)); } + + let url = `${this.getBaseUrl()}/series/?page=${page}`; + filters.forEach((filter) => { + if (filter.type === "GenreFilter") { + const genre = filter.state.filter((e) => e.state); + genre.forEach((gen) => (url += `${this.ll(url)}genre[]=${gen.value}`)); + } else if (filter.type === "TypeFilter") { + const type = filter.state.filter((e) => e.state); + type.forEach((ty) => (url += `${this.ll(url)}type[]=${ty.value}`)); + } else if (filter.type === "OrderFilter") { + if (filter.values?.[filter.state]?.value) + url += `${this.ll(url)}order=${filter.values[filter.state].value}`; + } else if (filter.type === "StatusFilter") { + if (filter.values?.[filter.state]?.value) + url += `${this.ll(url)}status=${filter.values[filter.state].value}`; + } + }); + + const res = await new Client().get(url, this.headers); + return this.novelFromElement(res); } toStatus(status) { @@ -187,7 +207,229 @@ class DefaultExtension extends MProvider { } getFilterList() { - throw new Error("getFilterList not implemented"); + return [ + { + type: "StatusFilter", + name: "الحالة", + type_name: "SelectFilter", + values: [ + { + type_name: "SelectOption", + name: "الكل", + value: "", + }, + { + type_name: "SelectOption", + name: "مستمر", + value: "ongoing", + }, + { + type_name: "SelectOption", + name: "متوقف مؤقتًا", + value: "hiatus", + }, + { + type_name: "SelectOption", + name: "مكتمل", + value: "completed", + }, + ], + state: 0, + }, + { + type: "OrderFilter", + name: "ترتيب حسب", + type_name: "SelectFilter", + values: [ + { + type_name: "SelectOption", + name: "الإعداد الأولي", + value: "", + }, + { + type_name: "SelectOption", + name: "A-Z", + value: "title", + }, + { + type_name: "SelectOption", + name: "Z-A", + value: "titlereverse", + }, + { + type_name: "SelectOption", + name: "أخر التحديثات", + value: "update", + }, + { + type_name: "SelectOption", + name: "أخر ما تم إضافته", + value: "latest", + }, + { + type_name: "SelectOption", + name: "الرائجة", + value: "popular", + }, + { + type_name: "SelectOption", + name: "التقييم", + value: "rating", + }, + ], + state: 0, + }, + { + type_name: "GroupFilter", + type: "GenreFilter", + name: "تصنيف", + state: [ + ["Romance", "romance"], + ["Shounen Ai", "shounen-ai"], + ["Wuxia", "wuxia"], + ["Xianxia", "xianxia"], + ["XUANHUAN", "xuanhuan"], + [ + "أبطال خارقين", + "%d8%a3%d8%a8%d8%b7%d8%a7%d9%84-%d8%ae%d8%a7%d8%b1%d9%82%d9%8a%d9%86", + ], + ["أساطير", "%d8%a3%d8%b3%d8%a7%d8%b7%d9%8a%d8%b1"], + ["أشباح", "%d8%a3%d8%b4%d8%a8%d8%a7%d8%ad"], + ["أكشن", "action"], + ["ألعاب", "%d8%a3%d9%84%d8%b9%d8%a7%d8%a8"], + ["إثارة", "excitement"], + ["إسلامي", "%d8%a5%d8%b3%d9%84%d8%a7%d9%85%d9%8a"], + ["إنتقال الى عالم أخر", "isekai"], + ["إيتشي", "etchi"], + ["اكاديمي", "%d8%a7%d9%83%d8%a7%d8%af%d9%8a%d9%85%d9%8a"], + ["اكشن", "%d8%a7%d9%83%d8%b4%d9%86"], + ["الإثارة", "%d8%a7%d9%84%d8%a5%d8%ab%d8%a7%d8%b1%d8%a9"], + ["الخيال العلمي", "sci-fi"], + ["الدراما", "%d8%a7%d9%84%d8%af%d8%b1%d8%a7%d9%85%d8%a7"], + [ + "المغامرات", + "%d8%a7%d9%84%d9%85%d8%ba%d8%a7%d9%85%d8%b1%d8%a7%d8%aa", + ], + ["انتقام", "%d8%a7%d9%86%d8%aa%d9%82%d8%a7%d9%85"], + ["بطل مضاد", "%d8%a8%d8%b7%d9%84-%d9%85%d8%b6%d8%a7%d8%af"], + ["بطل ناضج", "%d8%a8%d8%b7%d9%84-%d9%86%d8%a7%d8%b6%d8%ac"], + ["بقاء", "%d8%a8%d9%82%d8%a7%d8%a1"], + [ + "بناء مملكة", + "%d8%a8%d9%86%d8%a7%d8%a1-%d9%85%d9%85%d9%84%d9%83%d8%a9", + ], + ["بوليسي", "policy"], + ["تاريخ", "%d8%aa%d8%a7%d8%b1%d9%8a%d8%ae"], + ["تاريخي", "historical"], + ["تحقيقات", "%d8%aa%d8%ad%d9%82%d9%8a%d9%82"], + ["تشويق", "%d8%aa%d8%b4%d9%88%d9%8a%d9%82"], + ["تقمص شخصيات", "rpg"], + ["تلاعب", "%d8%aa%d9%84%d8%a7%d8%b9%d8%a8"], + ["تناسخ", "%d8%aa%d9%86%d8%a7%d8%b3%d8%ae"], + ["جريمة", "crime"], + ["جوسى", "josei"], + ["جوسي", "%d8%ac%d9%88%d8%b3%d9%8a"], + ["حريم", "harem"], + [ + "حل الألغاز", + "%d8%ad%d9%84-%d8%a7%d9%84%d8%a3%d9%84%d8%ba%d8%a7%d8%b2", + ], + ["حياة مدرسية", "school-life"], + [ + "خارق للطبيعة", + "%d8%ae%d8%a7%d8%b1%d9%82-%d9%84%d9%84%d8%b7%d8%a8%d9%8a%d8%b9%d8%a9", + ], + ["خيال", "%d8%ae%d9%8a%d8%a7%d9%84"], + ["خيال علمي", "%d8%ae%d9%8a%d8%a7%d9%84-%d8%b9%d9%84%d9%85%d9%8a"], + ["خيالي", "%d8%ae%d9%8a%d8%a7%d9%84%d9%8a"], + ["خيالي(فانتازيا)", "fantasy"], + ["دراما", "drama"], + ["درامي", "%d8%af%d8%b1%d8%a7%d9%85%d9%8a"], + ["رعب", "horror"], + ["رعب كوني", "%d8%b1%d8%b9%d8%a8-%d9%83%d9%88%d9%86%d9%8a"], + ["رعب نفسي", "%d8%b1%d8%b9%d8%a8-%d9%86%d9%81%d8%b3%d9%8a"], + ["رومانسي", "romantic"], + ["رومانسية", "%d8%b1%d9%88%d9%85%d8%a7%d9%86%d8%b3%d9%8a%d8%a9"], + ["رومنسية", "%d8%b1%d9%88%d9%85%d9%86%d8%b3%d9%8a%d8%a9"], + ["زنزانة", "%d8%b2%d9%86%d8%b2%d8%a7%d9%86%d8%a9"], + ["زيانشيا", "%d8%b2%d9%8a%d8%a7%d9%86%d8%b4%d9%8a%d8%a7"], + ["ستيم بانك", "%d8%b3%d8%aa%d9%8a%d9%85-%d8%a8%d8%a7%d9%86%d9%83"], + ["سحر", "magic"], + [ + "سفر بالزمن", + "%d8%b3%d9%81%d8%b1-%d8%a8%d8%a7%d9%84%d8%b2%d9%85%d9%86", + ], + [ + "سفر عبر الزمن", + "%d8%b3%d9%81%d8%b1-%d8%b9%d8%a8%d8%b1-%d8%a7%d9%84%d8%b2%d9%85%d9%86", + ], + ["سياسة", "%d8%b3%d9%8a%d8%a7%d8%b3%d8%a9"], + ["سينن", "senen"], + ["شريحة من الحياة", "slice-of-life"], + ["شعر", "%d8%b4%d8%b9%d8%b1"], + ["شوانهوان", "%d8%b4%d9%88%d8%a7%d9%86%d9%87%d9%88%d8%a7%d9%86"], + ["شوجو", "shojo"], + ["شونين", "shonen"], + ["طبي", "medical"], + ["ظواهر خارقة للطبيعة", "supernatural"], + ["عائلي", "%d8%b9%d8%a7%d8%a6%d9%84%d9%8a"], + ["عموض", "%d8%b9%d9%85%d9%88%d8%b6"], + ["غموض", "mysteries"], + ["فانتازي", "%d9%81%d8%a7%d9%86%d8%aa%d8%a7%d8%b2%d9%8a"], + ["فانتازيا", "%d9%81%d8%a7%d9%86%d8%aa%d8%a7%d8%b2%d9%8a%d8%a7"], + ["فانفيك", "%d9%81%d8%a7%d9%86%d9%81%d9%8a%d9%83"], + ["فنتازيا", "%d9%81%d9%86%d8%aa%d8%a7%d8%b2%d9%8a%d8%a7"], + ["فنون القتال", "martial-arts"], + ["فنون قتال", "%d9%81%d9%86%d9%88%d9%86-%d9%82%d8%aa%d8%a7%d9%84"], + ["قصة قصيرة", "%d9%82%d8%b5%d8%a9-%d9%82%d8%b5%d9%8a%d8%b1%d8%a9"], + ["قوة خارقة", "%d9%82%d9%88%d8%a9-%d8%ae%d8%a7%d8%b1%d9%82%d8%a9"], + ["قوى خارقة", "superpower"], + ["كوميدي", "comedy"], + ["كوميديا", "%d9%83%d9%88%d9%85%d9%8a%d8%af%d9%8a%d8%a7"], + ["كوميدية", "%d9%83%d9%88%d9%85%d9%8a%d8%af%d9%8a%d8%a9"], + ["مأساة", "%d9%85%d8%a3%d8%b3%d8%a7%d8%a9"], + ["مأساوي", "tragedy"], + ["مؤامرة", "%d9%85%d8%a4%d8%a7%d9%85%d8%b1%d8%a9"], + ["ما بعد الكارثة", "after-the-disaster"], + [ + "ما بعد نهاية العالم", + "%d9%85%d8%a7-%d8%a8%d8%b9%d8%af-%d9%86%d9%87%d8%a7%d9%8a%d8%a9-%d8%a7%d9%84%d8%b9%d8%a7%d9%84%d9%85", + ], + [ + "مضاد البطل", + "%d9%85%d8%b6%d8%a7%d8%af-%d8%a7%d9%84%d8%a8%d8%b7%d9%84", + ], + ["مغامرات", "%d9%85%d8%ba%d8%a7%d9%85%d8%b1%d8%a7%d8%aa"], + ["مغامرة", "adventure"], + ["ميكا", "mechanical"], + ["ناضج", "mature"], + ["نظام", "%d9%86%d8%b8%d8%a7%d9%85"], + ["نفسي", "psychological"], + ["ون شوت", "%d9%88%d9%86-%d8%b4%d9%88%d8%aa"], + ["ووكسيا", "%d9%88%d9%88%d9%83%d8%b3%d9%8a%d8%a7"], + ].map((x) => ({ type_name: "CheckBox", name: x[0], value: x[1] })), + }, + { + type_name: "GroupFilter", + type: "TypeFilter", + name: "النوع", + state: [ + ["إنجليزية", "english"], + ["رواية لايت", "light-novel"], + [ + "رواية مؤلفة", + "%d8%b1%d9%88%d8%a7%d9%8a%d8%a9-%d9%85%d8%a4%d9%84%d9%81%d8%a9", + ], + ["رواية ويب", "web-novel"], + ["صينية", "chinese"], + ["عربية", "arabic"], + ["كورية", "korean"], + ["مؤلفة", "%d9%85%d8%a4%d9%84%d9%81%d8%a9"], + ["ون شوت", "%d9%88%d9%86-%d8%b4%d9%88%d8%aa"], + ["يابانية", "japanese"], + ].map((x) => ({ type_name: "CheckBox", name: x[0], value: x[1] })), + }, + ]; } getBaseUrl() { @@ -240,4 +482,8 @@ class DefaultExtension extends MProvider { if (!monthEnglish) return ""; return new Date(`${monthEnglish} ${day}, ${year}`).getTime().toString(); } + + ll(url) { + return url.includes("?") ? "&" : "?"; + } } From e0b1d3da2a9451b8681ceedee2039f0e6f74f6ee Mon Sep 17 00:00:00 2001 From: xMohnad Date: Sat, 21 Jun 2025 08:31:04 +0000 Subject: [PATCH 12/13] kolnovel: avoid removing chapter number if it's inside parentheses --- javascript/novel/src/ar/kolnovel.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/javascript/novel/src/ar/kolnovel.js b/javascript/novel/src/ar/kolnovel.js index 42b774a3..ce061d76 100644 --- a/javascript/novel/src/ar/kolnovel.js +++ b/javascript/novel/src/ar/kolnovel.js @@ -164,8 +164,12 @@ class DefaultExtension extends MProvider { if (title.includes(name)) title = title.replace(name, "").trim(); const numMatch = num.match(/(?:الفصل|chapter)\s+(\d+(?:\.\d+)?)/i); - if (numMatch) - title = title.replace(numMatch[0], "").replace(numMatch[1], "").trim(); + if (numMatch) { + title = title.replace(numMatch[0], "").trim(); + if (!title.includes(`(${numMatch[1]})`)) { + title = title.replace(numMatch[1], "").trim(); + } + } title = title.replace(/\s{2,}/g, " ").trim(); const finalName = From 2006d0eec0b516e9eb427134906414efe3a00ea0 Mon Sep 17 00:00:00 2001 From: xMohnad Date: Sat, 21 Jun 2025 08:49:01 +0000 Subject: [PATCH 13/13] kolnovel: apply title cleaning for consistency in reader view --- javascript/novel/src/ar/kolnovel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/novel/src/ar/kolnovel.js b/javascript/novel/src/ar/kolnovel.js index ce061d76..1ed9ff2e 100644 --- a/javascript/novel/src/ar/kolnovel.js +++ b/javascript/novel/src/ar/kolnovel.js @@ -207,7 +207,7 @@ class DefaultExtension extends MProvider { // Clean html up for reader async cleanHtmlContent(html) { - return `

${html.title.rendered}



${html.content.rendered}`; + return `

${this.cleanTitle(html.title.rendered)}



${html.content.rendered}`; } getFilterList() {