From a3b7a8f2ef4fd7ce3bd6fd21f9f0e7d97d22e26d Mon Sep 17 00:00:00 2001 From: xMohnad Date: Wed, 25 Jun 2025 21:28:49 +0000 Subject: [PATCH 1/3] mangareader: Add Eros Scan (ES) manga source (#177) --- dart/manga/multisrc/mangareader/sources.dart | 3 +++ .../mangareader/src/en/erosscans/erosscans.dart | 14 ++++++++++++++ .../mangareader/src/en/erosscans/icon.png | Bin 0 -> 15985 bytes 3 files changed, 17 insertions(+) create mode 100644 dart/manga/multisrc/mangareader/src/en/erosscans/erosscans.dart create mode 100644 dart/manga/multisrc/mangareader/src/en/erosscans/icon.png diff --git a/dart/manga/multisrc/mangareader/sources.dart b/dart/manga/multisrc/mangareader/sources.dart index 4ec45e5d..883e8df1 100644 --- a/dart/manga/multisrc/mangareader/sources.dart +++ b/dart/manga/multisrc/mangareader/sources.dart @@ -47,6 +47,7 @@ import 'src/es/gremorymangas/gremorymangas.dart'; import 'src/es/ryujinmanga/ryujinmanga.dart'; import 'src/es/senpaiediciones/senpaiediciones.dart'; import 'src/es/skymangas/skymangas.dart'; +import 'src/es/erosscans/erosscans.dart'; import 'src/fr/flamescansfr/flamescansfr.dart'; import 'src/fr/mangasscans/mangasscans.dart'; import 'src/fr/rimuscans/rimuscans.dart'; @@ -177,6 +178,8 @@ List _mangareaderSourcesList = rizzcomicSource, //Berserker Scan (ES) berserkerscanSource, + // Eros Scan (ES) + erosscansSource, //Cartel de Manhwas (ES) carteldemanhwasSource, //De Todo Un Poco Scan (ES) diff --git a/dart/manga/multisrc/mangareader/src/en/erosscans/erosscans.dart b/dart/manga/multisrc/mangareader/src/en/erosscans/erosscans.dart new file mode 100644 index 00000000..b3d748fb --- /dev/null +++ b/dart/manga/multisrc/mangareader/src/en/erosscans/erosscans.dart @@ -0,0 +1,14 @@ +import '../../../../../../../model/source.dart'; + +Source get erosscansSource => _erosscansSource; + +Source _erosscansSource = Source( + name: "Eros Scans", + baseUrl: "https://eros-void.xyz", + lang: "en", + typeSource: "mangareader", + iconUrl: + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/multisrc/mangareader/src/en/erosscans/icon.png", + dateFormat: "MMMM dd, yyyy", + dateFormatLocale: "en_us", +); diff --git a/dart/manga/multisrc/mangareader/src/en/erosscans/icon.png b/dart/manga/multisrc/mangareader/src/en/erosscans/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4ae8e0cec65639d6c7c19fe558c0b34e67ac7a80 GIT binary patch literal 15985 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 literal 0 HcmV?d00001 From 227de96db12553fde9e12974a69825be11fbf44b Mon Sep 17 00:00:00 2001 From: xMohnad Date: Thu, 26 Jun 2025 08:37:50 +0000 Subject: [PATCH 2/3] refactor: change ManhwaZ icon path --- javascript/manga/src/en/manhwaz.js | 692 +++++++++++++++-------------- 1 file changed, 357 insertions(+), 335 deletions(-) diff --git a/javascript/manga/src/en/manhwaz.js b/javascript/manga/src/en/manhwaz.js index ad6e9d7b..1e762f90 100644 --- a/javascript/manga/src/en/manhwaz.js +++ b/javascript/manga/src/en/manhwaz.js @@ -1,9 +1,10 @@ +// prettier-ignore const mangayomiSources = [{ "name": "ManhwaZ", "lang": "en", "baseUrl": "https://manhwaz.com", "apiUrl": "", - "iconUrl": "https://manhwaz.com/favicon.ico", + "iconUrl": "https://manhwaz.com/apple-touch-icon.png", "typeSource": "single", "itemType": 0, "version": "0.1.0", @@ -12,368 +13,389 @@ const mangayomiSources = [{ }]; class DefaultExtension extends MProvider { - getHeaders(url) { - return { - "Referer": this.source.baseUrl, - "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) { + return { + Referer: this.source.baseUrl, + "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", + }; + } - // Helper method to parse manga list from page - mangaListFromPage(res, selector = ".page-item-detail") { - const doc = new Document(res.body); - const list = []; + // Helper method to parse manga list from page + mangaListFromPage(res, selector = ".page-item-detail") { + const doc = new Document(res.body); + const list = []; - // Look for manga items using the specified selector - const mangaElements = doc.select(selector); + // Look for manga items using the specified selector + const mangaElements = doc.select(selector); - for (const element of mangaElements) { - let linkElement, titleElement, imageElement; - let name = ""; - let imageUrl = ""; - let link = ""; + for (const element of mangaElements) { + let linkElement, titleElement, imageElement; + let name = ""; + let imageUrl = ""; + let link = ""; - if (selector === "#slide-top > .item") { - // Popular manga from homepage - linkElement = element.selectFirst(".info-item a"); - if (linkElement) { - name = linkElement.text; - link = linkElement.attr("href"); - } - imageElement = element.selectFirst(".img-item img"); - } else { - // Latest updates and search results - linkElement = element.selectFirst(".item-summary a"); - if (linkElement) { - name = linkElement.text; - link = linkElement.attr("href"); - } - imageElement = element.selectFirst(".item-thumb img"); - } - - if (imageElement) { - imageUrl = this.getImageUrl(imageElement); - } - - if (name && link) { - list.push({ name, imageUrl, link }); - } + if (selector === "#slide-top > .item") { + // Popular manga from homepage + linkElement = element.selectFirst(".info-item a"); + if (linkElement) { + name = linkElement.text; + link = linkElement.attr("href"); } + imageElement = element.selectFirst(".img-item img"); + } else { + // Latest updates and search results + linkElement = element.selectFirst(".item-summary a"); + if (linkElement) { + name = linkElement.text; + link = linkElement.attr("href"); + } + imageElement = element.selectFirst(".item-thumb img"); + } - // Check for next page - const hasNextPage = doc.selectFirst("ul.pager a[rel=next]") !== null; + if (imageElement) { + imageUrl = this.getImageUrl(imageElement); + } - return { "list": list, hasNextPage }; + if (name && link) { + list.push({ name, imageUrl, link }); + } } - // Helper method to get image URL with fallbacks - getImageUrl(imageElement) { - if (imageElement.attr("data-src")) { - return imageElement.attr("data-src"); - } else if (imageElement.attr("data-lazy-src")) { - return imageElement.attr("data-lazy-src"); - } else if (imageElement.attr("srcset")) { - return imageElement.attr("srcset").split(" ")[0]; - } else if (imageElement.attr("data-cfsrc")) { - return imageElement.attr("data-cfsrc"); + // Check for next page + const hasNextPage = doc.selectFirst("ul.pager a[rel=next]") !== null; + + return { list: list, hasNextPage }; + } + + // Helper method to get image URL with fallbacks + getImageUrl(imageElement) { + if (imageElement.attr("data-src")) { + return imageElement.attr("data-src"); + } else if (imageElement.attr("data-lazy-src")) { + return imageElement.attr("data-lazy-src"); + } else if (imageElement.attr("srcset")) { + return imageElement.attr("srcset").split(" ")[0]; + } else if (imageElement.attr("data-cfsrc")) { + return imageElement.attr("data-cfsrc"); + } else { + return imageElement.attr("src") || ""; + } + } + + // Convert status text to status code + toStatus(status) { + const statusLower = status?.toLowerCase() || ""; + if (statusLower.includes("ongoing") || statusLower.includes("publishing")) { + return 0; + } else if ( + statusLower.includes("completed") || + statusLower.includes("complete") + ) { + return 1; + } else if (statusLower.includes("hiatus")) { + return 2; + } else if ( + statusLower.includes("cancelled") || + statusLower.includes("dropped") + ) { + return 3; + } else { + return 5; // unknown + } + } + + // Parse relative date string to milliseconds + parseRelativeDate(dateStr) { + if (!dateStr) return String(new Date().valueOf()); + + try { + const lowerDateStr = dateStr.toLowerCase().trim(); + const now = new Date(); + + // Extract number and unit + const match = lowerDateStr.match( + /(\d+)\s*(second|minute|hour|day|week|month|year)s?\s*ago/, + ); + if (!match) { + // Try to parse as regular date + const date = new Date(dateStr); + return String(date.valueOf()); + } + + const value = parseInt(match[1]); + const unit = match[2]; + + const calendar = new Date(now); + + switch (unit) { + case "second": + calendar.setSeconds(calendar.getSeconds() - value); + break; + case "minute": + calendar.setMinutes(calendar.getMinutes() - value); + break; + case "hour": + calendar.setHours(calendar.getHours() - value); + break; + case "day": + calendar.setDate(calendar.getDate() - value); + break; + case "week": + calendar.setDate(calendar.getDate() - value * 7); + break; + case "month": + calendar.setMonth(calendar.getMonth() - value); + break; + case "year": + calendar.setFullYear(calendar.getFullYear() - value); + break; + default: + return String(now.valueOf()); + } + + return String(calendar.valueOf()); + } catch (e) { + return String(new Date().valueOf()); + } + } + + async getPopular(page) { + const url = `${this.source.baseUrl}/genre/manhwa?page=${page}&m_orderby=views`; + const res = await new Client().get(url, this.getHeaders()); + return this.mangaListFromPage(res, ".page-item-detail"); + } + + get supportsLatest() { + return true; + } + + async getLatestUpdates(page) { + const url = `${this.source.baseUrl}/?page=${page}`; + const res = await new Client().get(url, this.getHeaders()); + return this.mangaListFromPage(res, ".page-item-detail"); + } + + async search(query, page, filters) { + if (query && query.trim()) { + // Search with query + const url = `${this.source.baseUrl}/search?s=${encodeURIComponent(query)}&page=${page}`; + const res = await new Client().get(url, this.getHeaders()); + return this.mangaListFromPage(res, ".page-item-detail"); + } + + // Filter-based search + let url = this.source.baseUrl; + let hasGenreFilter = false; + + // Process filters + if (filters && filters.length > 0) { + const genreFilter = filters.find( + (f) => f.type === "select" && f.name === "genre", + ); + const orderByFilter = filters.find( + (f) => f.type === "select" && f.name === "orderby", + ); + + if (genreFilter && genreFilter.state > 0) { + const selectedGenre = genreFilter.values[genreFilter.state]; + if (selectedGenre && selectedGenre.value) { + url += "/" + selectedGenre.value; + hasGenreFilter = true; + } + } + + // Add order by parameter for genre pages + if (hasGenreFilter && orderByFilter && orderByFilter.state > 0) { + const selectedOrder = orderByFilter.values[orderByFilter.state]; + if (selectedOrder && selectedOrder.value) { + url += `?m_orderby=${selectedOrder.value}`; + url += `&page=${page}`; } else { - return imageElement.attr("src") || ""; + url += `?page=${page}`; } + } else { + url += `?page=${page}`; + } + } else { + url += `?page=${page}`; } - // Convert status text to status code - toStatus(status) { - const statusLower = status?.toLowerCase() || ""; - if (statusLower.includes("ongoing") || statusLower.includes("publishing")) { - return 0; - } else if (statusLower.includes("completed") || statusLower.includes("complete")) { - return 1; - } else if (statusLower.includes("hiatus")) { - return 2; - } else if (statusLower.includes("cancelled") || statusLower.includes("dropped")) { - return 3; - } else { - return 5; // unknown - } + const res = await new Client().get(url, this.getHeaders()); + return this.mangaListFromPage(res, ".page-item-detail"); + } + + async getDetail(url) { + // Ensure we have the full URL + const fullUrl = url.startsWith("http") + ? url + : `${this.source.baseUrl}${url}`; + const res = await new Client().get(fullUrl, this.getHeaders()); + const doc = new Document(res.body); + + // Extract manga details based on Kotlin implementation + const title = doc.selectFirst("div.post-title h1")?.text || ""; + + const descElement = doc.selectFirst("div.summary__content"); + const description = descElement?.text?.trim() || ""; + + const imageElement = doc.selectFirst("div.summary_image img"); + const imageUrl = imageElement ? this.getImageUrl(imageElement) : ""; + + // Extract author + const authorElement = doc.selectFirst( + "div.post-content_item .summary-heading:contains(Author) + .summary-content", + ); + const author = authorElement?.text?.trim() || ""; + + // Extract status + const statusElement = doc.selectFirst( + "div.summary-heading:contains(status) + div.summary-content", + ); + const statusText = statusElement?.text?.toLowerCase() || ""; + const status = this.toStatus(statusText); + + // Extract genres + const genre = []; + const genreElements = doc.select("div.genres-content a[rel=tag]"); + for (const genreEl of genreElements) { + const genreText = genreEl.text?.trim(); + if (genreText) { + genre.push(genreText); + } } - // Parse relative date string to milliseconds - parseRelativeDate(dateStr) { - if (!dateStr) return String(new Date().valueOf()); + // Extract chapters + const chapters = []; + const chapterElements = doc.select("li.wp-manga-chapter"); - try { - const lowerDateStr = dateStr.toLowerCase().trim(); - const now = new Date(); + for (const chapterEl of chapterElements) { + const chapterLink = chapterEl.selectFirst("a"); + if (!chapterLink) continue; - // Extract number and unit - const match = lowerDateStr.match(/(\d+)\s*(second|minute|hour|day|week|month|year)s?\s*ago/); - if (!match) { - // Try to parse as regular date - const date = new Date(dateStr); - return String(date.valueOf()); - } + const chapterUrl = chapterLink.attr("href"); + const chapterName = chapterLink.text?.trim() || ""; - const value = parseInt(match[1]); - const unit = match[2]; + // Try to get upload date + const dateElement = chapterEl.selectFirst("span.chapter-release-date"); + const dateUpload = this.parseRelativeDate(dateElement?.text); - const calendar = new Date(now); - - switch (unit) { - case "second": - calendar.setSeconds(calendar.getSeconds() - value); - break; - case "minute": - calendar.setMinutes(calendar.getMinutes() - value); - break; - case "hour": - calendar.setHours(calendar.getHours() - value); - break; - case "day": - calendar.setDate(calendar.getDate() - value); - break; - case "week": - calendar.setDate(calendar.getDate() - (value * 7)); - break; - case "month": - calendar.setMonth(calendar.getMonth() - value); - break; - case "year": - calendar.setFullYear(calendar.getFullYear() - value); - break; - default: - return String(now.valueOf()); - } - - return String(calendar.valueOf()); - } catch (e) { - return String(new Date().valueOf()); - } + if (chapterName && chapterUrl) { + chapters.push({ + name: chapterName, + url: chapterUrl, + dateUpload, + }); + } } - async getPopular(page) { - const url = `${this.source.baseUrl}/genre/manhwa?page=${page}&m_orderby=views`; - const res = await new Client().get(url, this.getHeaders()); - return this.mangaListFromPage(res, ".page-item-detail"); - } + return { + title, + description, + imageUrl, + status, + author, + genre, + chapters, + }; + } - get supportsLatest() { - return true; - } + async getPageList(url) { + // Ensure we have the full URL + const fullUrl = url.startsWith("http") + ? url + : `${this.source.baseUrl}${url}`; + const res = await new Client().get(fullUrl, this.getHeaders()); + const doc = new Document(res.body); - async getLatestUpdates(page) { - const url = `${this.source.baseUrl}/?page=${page}`; - const res = await new Client().get(url, this.getHeaders()); - return this.mangaListFromPage(res, ".page-item-detail"); - } + const pages = []; - async search(query, page, filters) { - if (query && query.trim()) { - // Search with query - const url = `${this.source.baseUrl}/search?s=${encodeURIComponent(query)}&page=${page}`; - const res = await new Client().get(url, this.getHeaders()); - return this.mangaListFromPage(res, ".page-item-detail"); + // Look for images in page break containers as per Kotlin implementation + const imageElements = doc.select("div.page-break img"); + + for (const img of imageElements) { + const imageUrl = this.getImageUrl(img); + + if (imageUrl) { + // Handle relative URLs + let finalUrl = imageUrl; + if (imageUrl.startsWith("//")) { + finalUrl = "https:" + imageUrl; + } else if (imageUrl.startsWith("/")) { + finalUrl = this.source.baseUrl + imageUrl; } - // Filter-based search - let url = this.source.baseUrl; - let hasGenreFilter = false; - - // Process filters - if (filters && filters.length > 0) { - const genreFilter = filters.find(f => f.type === "select" && f.name === "genre"); - const orderByFilter = filters.find(f => f.type === "select" && f.name === "orderby"); - - if (genreFilter && genreFilter.state > 0) { - const selectedGenre = genreFilter.values[genreFilter.state]; - if (selectedGenre && selectedGenre.value) { - url += "/" + selectedGenre.value; - hasGenreFilter = true; - } - } - - // Add order by parameter for genre pages - if (hasGenreFilter && orderByFilter && orderByFilter.state > 0) { - const selectedOrder = orderByFilter.values[orderByFilter.state]; - if (selectedOrder && selectedOrder.value) { - url += `?m_orderby=${selectedOrder.value}`; - url += `&page=${page}`; - } else { - url += `?page=${page}`; - } - } else { - url += `?page=${page}`; - } - } else { - url += `?page=${page}`; - } - - const res = await new Client().get(url, this.getHeaders()); - return this.mangaListFromPage(res, ".page-item-detail"); + pages.push(finalUrl); + } } - async getDetail(url) { - // Ensure we have the full URL - const fullUrl = url.startsWith("http") ? url : `${this.source.baseUrl}${url}`; - const res = await new Client().get(fullUrl, this.getHeaders()); - const doc = new Document(res.body); + return pages; + } - // Extract manga details based on Kotlin implementation - const title = doc.selectFirst("div.post-title h1")?.text || ""; + getFilterList() { + return [ + { + type: "header", + name: "Note: Filters only work when search query is empty", + }, + { + type: "separator", + }, + { + type: "select", + name: "genre", + label: "Genre", + values: [ + { name: "All", value: "" }, + { name: "Completed", value: "completed" }, + { name: "Action", value: "genre/action" }, + { name: "Adult", value: "genre/adult" }, + { name: "Adventure", value: "genre/adventure" }, + { name: "Comedy", value: "genre/comedy" }, + { name: "Drama", value: "genre/drama" }, + { name: "Ecchi", value: "genre/ecchi" }, + { name: "Fantasy", value: "genre/fantasy" }, + { name: "Harem", value: "genre/harem" }, + { name: "Historical", value: "genre/historical" }, + { name: "Horror", value: "genre/horror" }, + { name: "Isekai", value: "genre/isekai" }, + { name: "Josei", value: "genre/josei" }, + { name: "Manhwa", value: "genre/manhwa" }, + { name: "Martial Arts", value: "genre/martial-arts" }, + { name: "Mature", value: "genre/mature" }, + { name: "Mecha", value: "genre/mecha" }, + { name: "Mystery", value: "genre/mystery" }, + { name: "Psychological", value: "genre/psychological" }, + { name: "Romance", value: "genre/romance" }, + { name: "School Life", value: "genre/school-life" }, + { name: "Sci-fi", value: "genre/sci-fi" }, + { name: "Seinen", value: "genre/seinen" }, + { name: "Shoujo", value: "genre/shoujo" }, + { name: "Shounen", value: "genre/shounen" }, + { name: "Slice of Life", value: "genre/slice-of-life" }, + { name: "Sports", value: "genre/sports" }, + { name: "Supernatural", value: "genre/supernatural" }, + { name: "Tragedy", value: "genre/tragedy" }, + { name: "Webtoons", value: "genre/webtoons" }, + ], + state: 0, + }, + { + type: "select", + name: "orderby", + label: "Order By", + values: [ + { name: "Latest", value: "latest" }, + { name: "Rating", value: "rating" }, + { name: "Most Views", value: "views" }, + { name: "New", value: "new" }, + ], + state: 0, + }, + ]; + } - const descElement = doc.selectFirst("div.summary__content"); - const description = descElement?.text?.trim() || ""; - - const imageElement = doc.selectFirst("div.summary_image img"); - const imageUrl = imageElement ? this.getImageUrl(imageElement) : ""; - - // Extract author - const authorElement = doc.selectFirst("div.post-content_item .summary-heading:contains(Author) + .summary-content"); - const author = authorElement?.text?.trim() || ""; - - // Extract status - const statusElement = doc.selectFirst("div.summary-heading:contains(status) + div.summary-content"); - const statusText = statusElement?.text?.toLowerCase() || ""; - const status = this.toStatus(statusText); - - // Extract genres - const genre = []; - const genreElements = doc.select("div.genres-content a[rel=tag]"); - for (const genreEl of genreElements) { - const genreText = genreEl.text?.trim(); - if (genreText) { - genre.push(genreText); - } - } - - // Extract chapters - const chapters = []; - const chapterElements = doc.select("li.wp-manga-chapter"); - - for (const chapterEl of chapterElements) { - const chapterLink = chapterEl.selectFirst("a"); - if (!chapterLink) continue; - - const chapterUrl = chapterLink.attr("href"); - const chapterName = chapterLink.text?.trim() || ""; - - // Try to get upload date - const dateElement = chapterEl.selectFirst("span.chapter-release-date"); - const dateUpload = this.parseRelativeDate(dateElement?.text); - - if (chapterName && chapterUrl) { - chapters.push({ - name: chapterName, - url: chapterUrl, - dateUpload - }); - } - } - - return { - title, - description, - imageUrl, - status, - author, - genre, - chapters - }; - } - - async getPageList(url) { - // Ensure we have the full URL - const fullUrl = url.startsWith("http") ? url : `${this.source.baseUrl}${url}`; - const res = await new Client().get(fullUrl, this.getHeaders()); - const doc = new Document(res.body); - - const pages = []; - - // Look for images in page break containers as per Kotlin implementation - const imageElements = doc.select("div.page-break img"); - - for (const img of imageElements) { - const imageUrl = this.getImageUrl(img); - - if (imageUrl) { - // Handle relative URLs - let finalUrl = imageUrl; - if (imageUrl.startsWith("//")) { - finalUrl = "https:" + imageUrl; - } else if (imageUrl.startsWith("/")) { - finalUrl = this.source.baseUrl + imageUrl; - } - - pages.push(finalUrl); - } - } - - return pages; - } - - getFilterList() { - return [ - { - type: "header", - name: "Note: Filters only work when search query is empty" - }, - { - type: "separator" - }, - { - type: "select", - name: "genre", - label: "Genre", - values: [ - { name: "All", value: "" }, - { name: "Completed", value: "completed" }, - { name: "Action", value: "genre/action" }, - { name: "Adult", value: "genre/adult" }, - { name: "Adventure", value: "genre/adventure" }, - { name: "Comedy", value: "genre/comedy" }, - { name: "Drama", value: "genre/drama" }, - { name: "Ecchi", value: "genre/ecchi" }, - { name: "Fantasy", value: "genre/fantasy" }, - { name: "Harem", value: "genre/harem" }, - { name: "Historical", value: "genre/historical" }, - { name: "Horror", value: "genre/horror" }, - { name: "Isekai", value: "genre/isekai" }, - { name: "Josei", value: "genre/josei" }, - { name: "Manhwa", value: "genre/manhwa" }, - { name: "Martial Arts", value: "genre/martial-arts" }, - { name: "Mature", value: "genre/mature" }, - { name: "Mecha", value: "genre/mecha" }, - { name: "Mystery", value: "genre/mystery" }, - { name: "Psychological", value: "genre/psychological" }, - { name: "Romance", value: "genre/romance" }, - { name: "School Life", value: "genre/school-life" }, - { name: "Sci-fi", value: "genre/sci-fi" }, - { name: "Seinen", value: "genre/seinen" }, - { name: "Shoujo", value: "genre/shoujo" }, - { name: "Shounen", value: "genre/shounen" }, - { name: "Slice of Life", value: "genre/slice-of-life" }, - { name: "Sports", value: "genre/sports" }, - { name: "Supernatural", value: "genre/supernatural" }, - { name: "Tragedy", value: "genre/tragedy" }, - { name: "Webtoons", value: "genre/webtoons" } - ], - state: 0 - }, - { - type: "select", - name: "orderby", - label: "Order By", - values: [ - { name: "Latest", value: "latest" }, - { name: "Rating", value: "rating" }, - { name: "Most Views", value: "views" }, - { name: "New", value: "new" } - ], - state: 0 - } - ]; - } - - getSourcePreferences() { - return []; - } + getSourcePreferences() { + return []; + } } From 7dd8d703e9a2c01cf2c49c866bdaf83fc6592c31 Mon Sep 17 00:00:00 2001 From: xMohnad Date: Thu, 26 Jun 2025 09:19:23 +0000 Subject: [PATCH 3/3] feat(manhwaz): make baseUrl configurable via source preferences --- javascript/manga/src/en/manhwaz.js | 43 +++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/javascript/manga/src/en/manhwaz.js b/javascript/manga/src/en/manhwaz.js index 1e762f90..fe673011 100644 --- a/javascript/manga/src/en/manhwaz.js +++ b/javascript/manga/src/en/manhwaz.js @@ -161,7 +161,7 @@ class DefaultExtension extends MProvider { } async getPopular(page) { - const url = `${this.source.baseUrl}/genre/manhwa?page=${page}&m_orderby=views`; + const url = `${this.getBaseUrl()}/genre/manhwa?page=${page}&m_orderby=views`; const res = await new Client().get(url, this.getHeaders()); return this.mangaListFromPage(res, ".page-item-detail"); } @@ -171,7 +171,7 @@ class DefaultExtension extends MProvider { } async getLatestUpdates(page) { - const url = `${this.source.baseUrl}/?page=${page}`; + const url = `${this.getBaseUrl()}/?page=${page}`; const res = await new Client().get(url, this.getHeaders()); return this.mangaListFromPage(res, ".page-item-detail"); } @@ -179,13 +179,13 @@ class DefaultExtension extends MProvider { async search(query, page, filters) { if (query && query.trim()) { // Search with query - const url = `${this.source.baseUrl}/search?s=${encodeURIComponent(query)}&page=${page}`; + const url = `${this.getBaseUrl()}/search?s=${encodeURIComponent(query)}&page=${page}`; const res = await new Client().get(url, this.getHeaders()); return this.mangaListFromPage(res, ".page-item-detail"); } // Filter-based search - let url = this.source.baseUrl; + let url = this.getBaseUrl(); let hasGenreFilter = false; // Process filters @@ -227,9 +227,7 @@ class DefaultExtension extends MProvider { async getDetail(url) { // Ensure we have the full URL - const fullUrl = url.startsWith("http") - ? url - : `${this.source.baseUrl}${url}`; + const fullUrl = url.startsWith("http") ? url : `${this.getBaseUrl()}${url}`; const res = await new Client().get(fullUrl, this.getHeaders()); const doc = new Document(res.body); @@ -302,9 +300,7 @@ class DefaultExtension extends MProvider { async getPageList(url) { // Ensure we have the full URL - const fullUrl = url.startsWith("http") - ? url - : `${this.source.baseUrl}${url}`; + const fullUrl = url.startsWith("http") ? url : `${this.getBaseUrl()}${url}`; const res = await new Client().get(fullUrl, this.getHeaders()); const doc = new Document(res.body); @@ -322,7 +318,7 @@ class DefaultExtension extends MProvider { if (imageUrl.startsWith("//")) { finalUrl = "https:" + imageUrl; } else if (imageUrl.startsWith("/")) { - finalUrl = this.source.baseUrl + imageUrl; + finalUrl = this.getBaseUrl() + imageUrl; } pages.push(finalUrl); @@ -395,7 +391,30 @@ class DefaultExtension extends MProvider { ]; } + getBaseUrl() { + const preference = new SharedPreferences(); + var base_url = preference.get("domain_url"); + if (base_url.length == 0) { + return this.source.baseUrl; + } + if (base_url.endsWith("/")) { + return base_url.slice(0, -1); + } + return base_url; + } + getSourcePreferences() { - return []; + return [ + { + key: "domain_url", + editTextPreference: { + title: "Edit URL", + summary: "", + value: this.source.baseUrl, + dialogTitle: "URL", + dialogMessage: "", + }, + }, + ]; } }