{
val domain = domain
@@ -80,7 +81,7 @@ internal abstract class ChanParser(
description = root.getElementById("description")?.html()?.substringBeforeLast("
- val href = tr?.selectFirst("a")?.attrAsRelativeUrlOrNull("href")
+ val href = tr.selectFirst("a")?.attrAsRelativeUrlOrNull("href")
?: return@mapChapters null
MangaChapter(
id = generateUid(href),
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/HenChanParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/HenChanParser.kt
index a9ec7b7e..9d25acb4 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/HenChanParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/HenChanParser.kt
@@ -12,13 +12,14 @@ import java.util.*
internal class HenChanParser(context: MangaLoaderContext) : ChanParser(context, MangaSource.HENCHAN) {
override val configKeyDomain = ConfigKey.Domain(
- "x.henchan.pro",
+ "xxxx.henchan.pro",
+ "xxl.hentaichan.live",
"xxx.henchan.pro",
"y.hentaichan.live",
"xxx.hentaichan.live",
"xx.hentaichan.live",
+ "x.henchan.pro",
"hentaichan.live",
- "hentaichan.pro",
)
override val availableSortOrders: Set = EnumSet.of(
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/YaoiChanParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/YaoiChanParser.kt
index ca87f21b..9676c21e 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/YaoiChanParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/YaoiChanParser.kt
@@ -13,6 +13,7 @@ import org.koitharu.kotatsu.parsers.util.*
internal class YaoiChanParser(context: MangaLoaderContext) : ChanParser(context, MangaSource.YAOICHAN) {
override val configKeyDomain = ConfigKey.Domain(
+ "v2.yaoi-chan.me",
"v1.yaoi-chan.me",
"yaoi-chan.me",
)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/rulib/MangaLibParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/rulib/MangaLibParser.kt
index adcad3b9..20b3e65b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/rulib/MangaLibParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/rulib/MangaLibParser.kt
@@ -44,6 +44,7 @@ internal open class MangaLibParser(
page: Int,
query: String?,
tags: Set?,
+ tagsExclude: Set?,
sortOrder: SortOrder,
): List {
if (!query.isNullOrEmpty()) {
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScanVfOrg.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt
similarity index 88%
rename from src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScanVfOrg.kt
rename to src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt
index 026e537c..6142f05a 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScanVfOrg.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt
@@ -1,11 +1,10 @@
-package org.koitharu.kotatsu.parsers.site.fr
+package org.koitharu.kotatsu.parsers.site.scan
import androidx.collection.ArrayMap
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
@@ -13,14 +12,17 @@ import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
import java.util.*
-@MangaSourceParser("SCANVFORG", "ScanVfOrg", "fr")
-internal class ScanVfOrg(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaSource.SCANVFORG, 0) {
+internal abstract class ScanParser(
+ context: MangaLoaderContext,
+ source: MangaSource,
+ domain: String,
+ pageSize: Int = 0,
+) : PagedMangaParser(context, source, pageSize) {
override val availableSortOrders: Set =
EnumSet.of(SortOrder.ALPHABETICAL, SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.RATING)
-
- override val configKeyDomain = ConfigKey.Domain("scanvf.org")
+ override val isSearchSupported = false
+ override val configKeyDomain = ConfigKey.Domain(domain)
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List {
@@ -69,7 +71,7 @@ internal class ScanVfOrg(context: MangaLoaderContext) :
id = generateUid(href),
url = href,
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
- coverUrl = div.selectFirst("img")?.src().orEmpty(),
+ coverUrl = div.selectFirst("img")?.attr("data-src")?.replace("\t", "").orEmpty(),
title = div.selectFirstOrThrow(".link-series h3").text().orEmpty(),
altTitle = null,
rating = RATING_UNKNOWN,
@@ -111,16 +113,16 @@ internal class ScanVfOrg(context: MangaLoaderContext) :
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val dateFormat = SimpleDateFormat("MM-dd-yyyy", sourceLocale)
val tagMap = getOrCreateTagMap()
- val selectTag = doc.select(".card-series-detail .col-6:contains(Categories) div")
+ val selectTag = doc.select(".card-series-detail .col-6:contains(Categorie) div")
val tags = selectTag.mapNotNullToSet { tagMap[it.text()] }
return manga.copy(
rating = doc.selectFirst(".card-series-detail .rate-value span")?.ownText()?.toFloatOrNull()?.div(5f)
?: RATING_UNKNOWN,
tags = tags,
- author = doc.selectFirst(".card-series-detail .col-6:contains(Auteur) div")?.text(),
+ author = doc.selectFirst(".card-series-detail .col-6:contains(Autore) div")?.text(),
altTitle = doc.selectFirst(".card div.col-12.mb-4 h2")?.text().orEmpty(),
description = doc.selectFirst(".card div.col-12.mb-4 p")?.html().orEmpty(),
- chapters = doc.select(".list-books .col-chapter").mapChapters(reversed = true) { i, div ->
+ chapters = doc.select(".chapters-list .col-chapter").mapChapters(reversed = true) { i, div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
MangaChapter(
id = generateUid(href),
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/fr/ScanVfOrg.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/fr/ScanVfOrg.kt
new file mode 100644
index 00000000..41e367d7
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/fr/ScanVfOrg.kt
@@ -0,0 +1,10 @@
+package org.koitharu.kotatsu.parsers.site.scan.fr
+
+import org.koitharu.kotatsu.parsers.MangaLoaderContext
+import org.koitharu.kotatsu.parsers.MangaSourceParser
+import org.koitharu.kotatsu.parsers.model.MangaSource
+import org.koitharu.kotatsu.parsers.site.scan.ScanParser
+
+@MangaSourceParser("SCANVFORG", "ScanVf.org", "fr")
+internal class ScanVfOrg(context: MangaLoaderContext) :
+ ScanParser(context, MangaSource.SCANVFORG, "scanvf.org")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/it/ScanIta.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/it/ScanIta.kt
new file mode 100644
index 00000000..27997be0
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/it/ScanIta.kt
@@ -0,0 +1,10 @@
+package org.koitharu.kotatsu.parsers.site.scan.it
+
+import org.koitharu.kotatsu.parsers.MangaLoaderContext
+import org.koitharu.kotatsu.parsers.MangaSourceParser
+import org.koitharu.kotatsu.parsers.model.MangaSource
+import org.koitharu.kotatsu.parsers.site.scan.ScanParser
+
+@MangaSourceParser("SCANITA", "ScanIta.org", "it")
+internal class ScanIta(context: MangaLoaderContext) :
+ ScanParser(context, MangaSource.SCANITA, "scanita.org")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt
index 0d732396..f2477e69 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt
@@ -19,7 +19,7 @@ class TrWebtoon(context: MangaLoaderContext) :
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("trwebtoon.com")
override val availableSortOrders: Set =
- EnumSet.of(SortOrder.POPULARITY, SortOrder.ALPHABETICAL, SortOrder.UPDATED)
+ EnumSet.of(SortOrder.POPULARITY, SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL_DESC, SortOrder.UPDATED)
override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
@@ -77,6 +77,7 @@ class TrWebtoon(context: MangaLoaderContext) :
when (filter.sortOrder) {
SortOrder.POPULARITY -> append("views&short_type=DESC")
SortOrder.ALPHABETICAL -> append("name&short_type=ASC")
+ SortOrder.ALPHABETICAL_DESC -> append("name&short_type=DESC")
else -> append("views&short_type=DESC")
}
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/YaoiFlix.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/YaoiFlix.kt
index cb41dc3a..0135f814 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/YaoiFlix.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/YaoiFlix.kt
@@ -14,7 +14,7 @@ class YaoiFlix(context: MangaLoaderContext) : PagedMangaParser(context, MangaSou
override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED)
- override val configKeyDomain = ConfigKey.Domain("www.yaoiflix.pro")
+ override val configKeyDomain = ConfigKey.Domain("www.yaoiflix.live")
override val isMultipleTagsSupported = false
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt
index a5cd7080..d681cc0b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt
@@ -67,7 +67,13 @@ class HentaiUkrParser(context: MangaLoaderContext) : MangaParser(context, MangaS
)
}
- override suspend fun getList(offset: Int, query: String?, tags: Set?, sortOrder: SortOrder): List {
+ override suspend fun getList(
+ offset: Int,
+ query: String?,
+ tags: Set?,
+ tagsExclude: Set?,
+ sortOrder: SortOrder,
+ ): List {
// Get all manga
val json = allManga.get().toMutableList()
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HoneyMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HoneyMangaParser.kt
index 0184d186..85550e8a 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HoneyMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HoneyMangaParser.kt
@@ -80,6 +80,7 @@ class HoneyMangaParser(context: MangaLoaderContext) : PagedMangaParser(context,
page: Int,
query: String?,
tags: Set?,
+ tagsExclude: Set?,
sortOrder: SortOrder,
): List {
val body = JSONObject()
@@ -157,9 +158,7 @@ class HoneyMangaParser(context: MangaLoaderContext) : PagedMangaParser(context,
}
override suspend fun getPages(chapter: MangaChapter): List {
- val body = JSONObject()
- body.put("chapterId", chapter.url)
- val content = webClient.httpPost(framesApi, body).parseJson().getJSONObject("resourceIds")
+ val content = webClient.httpGet("$framesApi/${chapter.url}").parseJson().getJSONObject("resourceIds")
val baseUrl = imageStorageUrl.tryGet().getOrDefault(IMAGE_BASEURL_FALLBACK)
return List(content.length()) { i ->
val item = content.getString(i.toString())
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/MangaInUaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/MangaInUaParser.kt
index 3b1fc2c6..99799bd1 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/MangaInUaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/MangaInUaParser.kt
@@ -34,6 +34,7 @@ class MangaInUaParser(context: MangaLoaderContext) : PagedMangaParser(
page: Int,
query: String?,
tags: Set?,
+ tagsExclude: Set?,
sortOrder: SortOrder,
): List {
val url = when {
@@ -94,7 +95,7 @@ class MangaInUaParser(context: MangaLoaderContext) : PagedMangaParser(
"news_id" to linkToComics.attrOrThrow("data-news_id"),
"news_category" to linkToComics.attrOrThrow("data-news_category"),
"this_link" to "",
- "user_hashs" to userHash,
+ "user_hash" to userHash,
),
).parseHtml()
val chapterNodes = chaptersDoc.select(".ltcitems")
@@ -105,7 +106,7 @@ class MangaInUaParser(context: MangaLoaderContext) : PagedMangaParser(
largeCoverUrl = root.selectFirst("div.item__full-sidebar--poster")?.selectFirst("img")
?.attrAsAbsoluteUrlOrNull("src"),
chapters = chapterNodes.mapChapters { _, item ->
- val href = item?.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: return@mapChapters null
+ val href = item.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: return@mapChapters null
val isAlternative = item.styleValueOrNull("background") != null
val name = item.selectFirst("a")?.text().orEmpty()
if (!isAlternative) i++
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt
index 5b398558..d5ea7409 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt
@@ -23,7 +23,7 @@ private const val SEARCH_PAGE_SIZE = 10
@MangaSourceParser("HENTAIVN", "HentaiVN", "vi", type = ContentType.HENTAI)
class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.HENTAIVN) {
- override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("hentaivn.autos", "hentaivn.tv")
+ override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("hentaivn.red", "hentaivn.autos", "hentaivn.tv")
// hentaivn has created 2 different interfaces for mobile and desktop, and Cloudflare detects whether it's mobile or not even with a desktop user agent.
override val headers: Headers = Headers.Builder().add("User-Agent", UserAgents.CHROME_MOBILE).build()
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt
index bd61e0aa..98669335 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt
@@ -16,6 +16,7 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context,
override val availableSortOrders: Set = EnumSet.of(
SortOrder.ALPHABETICAL,
+ SortOrder.ALPHABETICAL_DESC,
SortOrder.UPDATED,
SortOrder.NEWEST,
SortOrder.POPULARITY,
@@ -76,6 +77,7 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context,
SortOrder.UPDATED -> append("-updated_at")
SortOrder.NEWEST -> append("-created_at")
SortOrder.ALPHABETICAL -> append("name")
+ SortOrder.ALPHABETICAL_DESC -> append("-name")
else -> append("-updated_at")
}
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyentranhLHParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyentranhLHParser.kt
index 02fa5fc0..e8c1effe 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyentranhLHParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyentranhLHParser.kt
@@ -17,7 +17,7 @@ import java.util.*
class TruyentranhLHParser(context: MangaLoaderContext) :
PagedMangaParser(context, source = MangaSource.TRUYENTRANHLH, pageSize = 18) {
- override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("truyentranhlh.net")
+ override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("truyenlh.com")
override val availableSortOrders: Set = EnumSet.allOf(SortOrder::class.java)
override val availableStates: Set =
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED)
@@ -46,6 +46,7 @@ class TruyentranhLHParser(context: MangaLoaderContext) :
SortOrder.RATING -> "like"
SortOrder.POPULARITY -> "top"
SortOrder.ALPHABETICAL -> "az"
+ SortOrder.ALPHABETICAL_DESC -> "za"
},
)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/Nettruyenmax.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/Nettruyenmax.kt
index 070093fb..f9164c5c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/Nettruyenmax.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/Nettruyenmax.kt
@@ -5,6 +5,6 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser
-@MangaSourceParser("NETTRUYENMAX", "NettruyenMax", "vi")
+@MangaSourceParser("NETTRUYENMAX", "NettruyenBing", "vi")
internal class Nettruyenmax(context: MangaLoaderContext) :
- WpComicsParser(context, MangaSource.NETTRUYENMAX, "www.nettruyenus.com", 36)
+ WpComicsParser(context, MangaSource.NETTRUYENMAX, "www.nettruyenbing.com", 36)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/Nhattruyenmin.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/Nhattruyenmin.kt
index daf35440..b471d526 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/Nhattruyenmin.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/Nhattruyenmin.kt
@@ -5,6 +5,6 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser
-@MangaSourceParser("NHATTRUYENMIN", "NhattruyenMin", "vi")
+@MangaSourceParser("NHATTRUYENMIN", "NhattruyenPlus", "vi")
internal class Nhattruyenmin(context: MangaLoaderContext) :
- WpComicsParser(context, MangaSource.NHATTRUYENMIN, "nhattruyenmin.com")
+ WpComicsParser(context, MangaSource.NHATTRUYENMIN, "nhattruyenplus.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ZeistMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ZeistMangaParser.kt
index 7fd208ae..eef2cc74 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ZeistMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ZeistMangaParser.kt
@@ -213,6 +213,7 @@ internal abstract class ZeistMangaParser(
?: doc.selectFirst("div.y6x11p:contains(Yazar) .dt")
val desc = doc.getElementById("synopsis") ?: doc.getElementById("Sinopse") ?: doc.getElementById("sinopas")
+ ?: doc.selectFirst(".sinopsis")
val chaptersDeferred = async { loadChapters(manga.url, doc) }
manga.copy(
author = author?.text(),
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ar/Mangaatrend.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ar/Mangaatrend.kt
index e1f0b591..ebf47a34 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ar/Mangaatrend.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ar/Mangaatrend.kt
@@ -7,6 +7,6 @@ import org.koitharu.kotatsu.parsers.site.zeistmanga.ZeistMangaParser
@MangaSourceParser("MANGAATREND", "MangaATrend", "ar")
internal class Mangaatrend(context: MangaLoaderContext) :
- ZeistMangaParser(context, MangaSource.MANGAATREND, "mangaatrend.net") {
+ ZeistMangaParser(context, MangaSource.MANGAATREND, "www.mangaatrend.net") {
override val selectPage = "#seoneurons-target img"
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/id/HyoManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/id/HyoManga.kt
deleted file mode 100644
index d0ee010b..00000000
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/id/HyoManga.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.koitharu.kotatsu.parsers.site.zeistmanga.id
-
-import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.model.MangaSource
-import org.koitharu.kotatsu.parsers.site.zeistmanga.ZeistMangaParser
-
-@MangaSourceParser("HYOMANGA", "HyoManga", "id")
-internal class HyoManga(context: MangaLoaderContext) :
- ZeistMangaParser(context, MangaSource.HYOMANGA, "www.hyomanga.my.id") {
- override val mangaCategory = "Manga"
-}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/id/KomikGes.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/id/KomikGes.kt
new file mode 100644
index 00000000..1d143e0e
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/id/KomikGes.kt
@@ -0,0 +1,67 @@
+package org.koitharu.kotatsu.parsers.site.zeistmanga.id
+
+import org.jsoup.nodes.Document
+import org.koitharu.kotatsu.parsers.MangaLoaderContext
+import org.koitharu.kotatsu.parsers.MangaSourceParser
+import org.koitharu.kotatsu.parsers.model.MangaChapter
+import org.koitharu.kotatsu.parsers.model.MangaPage
+import org.koitharu.kotatsu.parsers.model.MangaSource
+import org.koitharu.kotatsu.parsers.site.zeistmanga.ZeistMangaParser
+import org.koitharu.kotatsu.parsers.util.*
+import org.koitharu.kotatsu.parsers.util.json.toJSONList
+import java.text.SimpleDateFormat
+
+@MangaSourceParser("KOMIKGES", "KomikGes", "id")
+internal class KomikGes(context: MangaLoaderContext) :
+ ZeistMangaParser(context, MangaSource.KOMIKGES, "www.komikges.my.id") {
+
+ override suspend fun loadChapters(mangaUrl: String, doc: Document): List {
+ val feed = doc.selectFirstOrThrow(".episode-list script").html().substringAfter("('").substringBefore("');")
+ val url = buildString {
+ append("https://")
+ append(domain)
+ append("/feeds/posts/default/-/")
+ append(feed)
+ append("?alt=json&orderby=published&max-results=9999")
+ }
+ val json =
+ webClient.httpGet(url).parseJson().getJSONObject("feed").getJSONArray("entry").toJSONList().reversed()
+ val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
+ return json.mapIndexedNotNull { i, j ->
+ val name = j.getJSONObject("title").getString("\$t")
+ val href =
+ j.getJSONArray("link").toJSONList().first { it.getString("rel") == "alternate" }.getString("href")
+ val dateText = j.getJSONObject("published").getString("\$t").substringBefore("T")
+ val slug = mangaUrl.substringAfterLast('/')
+ val slugChapter = href.substringAfterLast('/')
+ if (slug == slugChapter) {
+ return@mapIndexedNotNull null
+ }
+ MangaChapter(
+ id = generateUid(href),
+ url = href,
+ name = name,
+ number = i + 1,
+ branch = null,
+ uploadDate = dateFormat.tryParse(dateText),
+ scanlator = null,
+ source = source,
+ )
+ }
+ }
+
+ override suspend fun getPages(chapter: MangaChapter): List {
+ val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
+ return doc.selectFirstOrThrow("script:containsData(let data_content =)").data()
+ .split("src\\x3d\\x22").drop(1)
+ .map { img ->
+ val url = img.substringBefore("\\x22")
+ MangaPage(
+ id = generateUid(url),
+ url = url,
+ preview = null,
+ source = source,
+ )
+ }
+ }
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/id/MonzeeKomik.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/id/MonzeeKomik.kt
new file mode 100644
index 00000000..36e2d10f
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/id/MonzeeKomik.kt
@@ -0,0 +1,74 @@
+package org.koitharu.kotatsu.parsers.site.zeistmanga.id
+
+import org.jsoup.nodes.Document
+import org.koitharu.kotatsu.parsers.MangaLoaderContext
+import org.koitharu.kotatsu.parsers.MangaSourceParser
+import org.koitharu.kotatsu.parsers.model.MangaChapter
+import org.koitharu.kotatsu.parsers.model.MangaSource
+import org.koitharu.kotatsu.parsers.site.zeistmanga.ZeistMangaParser
+import org.koitharu.kotatsu.parsers.util.domain
+import org.koitharu.kotatsu.parsers.util.generateUid
+import org.koitharu.kotatsu.parsers.util.json.toJSONList
+import org.koitharu.kotatsu.parsers.util.parseJson
+import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
+import org.koitharu.kotatsu.parsers.util.tryParse
+import java.text.SimpleDateFormat
+
+@MangaSourceParser("MONZEEKOMIK", "MonzeeKomik", "id")
+internal class MonzeeKomik(context: MangaLoaderContext) :
+ ZeistMangaParser(context, MangaSource.MONZEEKOMIK, "www.monzeekomik.my.id") {
+ override val selectPage = "article#reader img"
+
+ override suspend fun loadChapters(mangaUrl: String, doc: Document): List {
+
+
+ val chapterRegex = """clwd\.run\('([^']+)'""".toRegex()
+ val scriptSelector = "#clwd > script"
+ val script = doc.selectFirstOrThrow(scriptSelector)
+ val feedFind = chapterRegex
+ .find(script.html())
+ ?.groupValues?.get(1)
+ ?: throw Exception("Failed to find chapter feed")
+
+ val feedClean = feedFind.removeSuffix(")") // clean
+
+ val feed = if (feedClean == "Reincarnation Colosseum") // hot fix
+ {
+ "I Have 90 Billion Licking Gold"
+ } else {
+ feedClean
+ }
+
+ val url = buildString {
+ append("https://")
+ append(domain)
+ append("/feeds/posts/default/-/")
+ append(feed)
+ append("?alt=json&orderby=published&max-results=9999")
+ }
+ val json =
+ webClient.httpGet(url).parseJson().getJSONObject("feed").getJSONArray("entry").toJSONList().reversed()
+ val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
+ return json.mapIndexedNotNull { i, j ->
+ val name = j.getJSONObject("title").getString("\$t")
+ val href =
+ j.getJSONArray("link").toJSONList().first { it.getString("rel") == "alternate" }.getString("href")
+ val dateText = j.getJSONObject("published").getString("\$t").substringBefore("T")
+ val slug = mangaUrl.substringAfterLast('/')
+ val slugChapter = href.substringAfterLast('/')
+ if (slug == slugChapter) {
+ return@mapIndexedNotNull null
+ }
+ MangaChapter(
+ id = generateUid(href),
+ url = href,
+ name = name,
+ number = i + 1,
+ branch = null,
+ uploadDate = dateFormat.tryParse(dateText),
+ scanlator = null,
+ source = source,
+ )
+ }
+ }
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/pt/ZScanlation.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/pt/ZScanlation.kt
new file mode 100644
index 00000000..708163e5
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/pt/ZScanlation.kt
@@ -0,0 +1,15 @@
+package org.koitharu.kotatsu.parsers.site.zeistmanga.pt
+
+import org.koitharu.kotatsu.parsers.MangaLoaderContext
+import org.koitharu.kotatsu.parsers.MangaSourceParser
+import org.koitharu.kotatsu.parsers.model.ContentType
+import org.koitharu.kotatsu.parsers.model.MangaSource
+import org.koitharu.kotatsu.parsers.site.zeistmanga.ZeistMangaParser
+
+@MangaSourceParser("ZSCANLATION", "ZScanlation", "pt", ContentType.HENTAI)
+internal class ZScanlation(context: MangaLoaderContext) :
+ ZeistMangaParser(context, MangaSource.ZSCANLATION, "www.zscanlation.com") {
+ override val sateOngoing: String = "Em Lançamento"
+ override val sateFinished: String = "Completo"
+ override val sateAbandoned: String = "Dropado"
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt
index 6c928a53..aff9aa4f 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt
@@ -72,6 +72,7 @@ internal abstract class ZMangaParser(
SortOrder.POPULARITY -> append("popular")
SortOrder.UPDATED -> append("update")
SortOrder.ALPHABETICAL -> append("title")
+ SortOrder.ALPHABETICAL_DESC -> append("titlereverse")
SortOrder.NEWEST -> append("latest")
SortOrder.RATING -> append("rating")
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/id/NeuManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/id/NeuManga.kt
deleted file mode 100644
index ad303a69..00000000
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/id/NeuManga.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.koitharu.kotatsu.parsers.site.zmanga.id
-
-import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.model.MangaSource
-import org.koitharu.kotatsu.parsers.site.zmanga.ZMangaParser
-
-@MangaSourceParser("NEU_MANGA", "NeuManga.net", "id")
-internal class NeuManga(context: MangaLoaderContext) :
- ZMangaParser(context, MangaSource.NEU_MANGA, "neumanga.net")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/MangaParserEnv.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/MangaParserEnv.kt
index 8d2045b6..d3e5ee7f 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/MangaParserEnv.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/MangaParserEnv.kt
@@ -68,6 +68,15 @@ fun Set?.oneOrThrowIfMany(): MangaState? {
}
}
+@InternalParsersApi
+fun Set?.oneOrThrowIfMany(): ContentRating? {
+ return when {
+ isNullOrEmpty() -> null
+ size == 1 -> first()
+ else -> throw IllegalArgumentException(ErrorMessages.FILTER_MULTIPLE_CONTENT_RATING_NOT_SUPPORTED)
+ }
+}
+
val MangaParser.domain: String
get() {
return config[configKeyDomain]
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
index b086e735..813d53ae 100644
--- a/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
@@ -28,7 +28,7 @@ internal class MangaParserTest {
@MangaSources
fun list(source: MangaSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val list = parser.getList(0, sortOrder = SortOrder.POPULARITY, tags = null)
+ val list = parser.getList(0, sortOrder = SortOrder.POPULARITY, tags = null, tagsExclude = null)
checkMangaList(list, "list")
assert(list.all { it.source == source })
}
@@ -61,7 +61,11 @@ internal class MangaParserTest {
offset = 0,
filter = MangaListFilter.Advanced(
sortOrder = SortOrder.POPULARITY,
- tags = emptySet(), locale = null, states = emptySet(),
+ tags = emptySet(),
+ locale = null,
+ states = emptySet(),
+ tagsExclude = emptySet(),
+ contentRating = emptySet(),
),
).minByOrNull {
it.title.length
@@ -92,7 +96,7 @@ internal class MangaParserTest {
assert(tags.all { it.source == source })
val tag = tags.last()
- val list = parser.getList(offset = 0, tags = setOf(tag), sortOrder = null)
+ val list = parser.getList(offset = 0, tags = setOf(tag), null, sortOrder = null)
checkMangaList(list, "${tag.title} (${tag.key})")
assert(list.all { it.source == source })
}
@@ -101,17 +105,13 @@ internal class MangaParserTest {
@MangaSources
fun tagsMultiple(source: MangaSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
+ if (!parser.isMultipleTagsSupported) return@runTest
val tags = parser.getAvailableTags().shuffled().take(2).toSet()
- val list = try {
- parser.getList(offset = 0, tags = tags, sortOrder = null)
- } catch (e: IllegalArgumentException) {
- if (e.message == "Multiple genres are not supported by this source") {
- return@runTest
- } else {
- throw e
- }
- }
+ val filter = MangaListFilter.Advanced.Builder(parser.availableSortOrders.first())
+ .tags(tags)
+ .build()
+ val list = parser.getList(0, filter)
checkMangaList(list, "${tags.joinToString { it.title }} (${tags.joinToString { it.key }})")
assert(list.all { it.source == source })
}
@@ -127,8 +127,10 @@ internal class MangaParserTest {
val filter = MangaListFilter.Advanced(
sortOrder = parser.availableSortOrders.first(),
tags = setOf(),
+ tagsExclude = setOf(),
locale = locales.random(),
states = setOf(),
+ contentRating = setOf(),
)
val list = parser.getList(offset = 0, filter)
checkMangaList(list, filter.locale.toString())
@@ -140,7 +142,7 @@ internal class MangaParserTest {
@MangaSources
fun details(source: MangaSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val list = parser.getList(0, sortOrder = SortOrder.POPULARITY, tags = null)
+ val list = parser.getList(0, sortOrder = SortOrder.POPULARITY, tags = null, tagsExclude = null)
val manga = list[3]
parser.getDetails(manga).apply {
assert(!chapters.isNullOrEmpty()) { "Chapters are null or empty" }
@@ -169,7 +171,7 @@ internal class MangaParserTest {
@MangaSources
fun pages(source: MangaSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val list = parser.getList(0, sortOrder = SortOrder.UPDATED, tags = null)
+ val list = parser.getList(0, sortOrder = SortOrder.UPDATED, tags = null, tagsExclude = null)
val manga = list.first()
val chapter = parser.getDetails(manga).chapters?.firstOrNull() ?: error("Chapter is null at ${manga.publicUrl}")
val pages = parser.getPages(chapter)