diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ImHentai.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ImHentai.kt index 845259d3..3c9e0e39 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ImHentai.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ImHentai.kt @@ -24,43 +24,50 @@ internal class ImHentai(context: MangaLoaderContext) : override val isMultipleTagsSupported = false - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) + when (filter) { + is MangaListFilter.Search -> { + append("/search/?key=") + append(filter.query.urlEncoded()) + append("&page=") + append(page) + } + + is MangaListFilter.Advanced -> { - if (!query.isNullOrEmpty()) { - append("/search/?key=") - append(query.urlEncoded()) - append("&page=") - append(page) - } else if (!tags.isNullOrEmpty()) { - append("/tag/") - append(tag?.key.orEmpty()) - append("/") - when (sortOrder) { - SortOrder.UPDATED -> append("") - SortOrder.POPULARITY -> append("popular/") - else -> append("") + val tag = filter.tags.oneOrThrowIfMany() + if (filter.tags.isNotEmpty()) { + append("/tag/") + append(tag?.key.orEmpty()) + append("/") + when (filter.sortOrder) { + SortOrder.UPDATED -> append("") + SortOrder.POPULARITY -> append("popular/") + else -> append("") + } + append("?page=") + append(page) + } else { + append("/search/?page=") + append(page) + when (filter.sortOrder) { + SortOrder.UPDATED -> append("<=1&pp=0") + SortOrder.POPULARITY -> append("<=0&pp=1") + SortOrder.RATING -> append("<=0&pp=0") + else -> append("<=1&pp=0") + } + } } - append("?page=") - append(page) - } else { - append("/search/?page=") - append(page) - when (sortOrder) { - SortOrder.UPDATED -> append("<=1&pp=0") - SortOrder.POPULARITY -> append("<=0&pp=1") - SortOrder.RATING -> append("<=0&pp=0") - else -> append("<=1&pp=0") + + null -> { + append("/search/?lt=1&pp=0&page=") + append(page) } } + } val doc = webClient.httpGet(url).parseHtml() return doc.select("div.galleries div.thumb").map { div -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/AnimeBootstrapParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/AnimeBootstrapParser.kt index c5a89e1d..28a02ff6 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/AnimeBootstrapParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/AnimeBootstrapParser.kt @@ -11,8 +11,6 @@ import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* import java.util.* -// see https://themewagon.com/themes/free-bootstrap-4-html5-gaming-anime-website-template-anime/ - internal abstract class AnimeBootstrapParser( context: MangaLoaderContext, source: MangaSource, @@ -40,13 +38,7 @@ internal abstract class AnimeBootstrapParser( searchPaginator.firstPage = 1 } - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) @@ -55,23 +47,31 @@ internal abstract class AnimeBootstrapParser( append(page.toString()) append("&type=all") - if (!query.isNullOrEmpty()) { - append("&search=") - append(query.urlEncoded()) - } + when (filter) { + is MangaListFilter.Search -> { + append("&search=") + append(filter.query.urlEncoded()) + } - if (!tags.isNullOrEmpty()) { - append("&categorie=") - append(tag?.key.orEmpty()) - } + is MangaListFilter.Advanced -> { + + filter.tags.oneOrThrowIfMany()?.let { + append("&categorie=") + append(it.key) + } + + append("&sort=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("view") + SortOrder.UPDATED -> append("updated") + SortOrder.ALPHABETICAL -> append("default") + SortOrder.NEWEST -> append("published") + else -> append("updated") + } - append("&sort=") - when (sortOrder) { - SortOrder.POPULARITY -> append("view") - SortOrder.UPDATED -> append("updated") - SortOrder.ALPHABETICAL -> append("default") - SortOrder.NEWEST -> append("published") - else -> append("updated") + } + + null -> append("&sort=updated") } } val doc = webClient.httpGet(url).parseHtml() @@ -115,11 +115,8 @@ internal abstract class AnimeBootstrapParser( override suspend fun getDetails(manga: Manga): Manga = coroutineScope { val fullUrl = manga.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() - val chaptersDeferred = async { getChapters(doc) } - val desc = doc.selectFirstOrThrow(selectDesc).html() - val state = if (doc.select(selectState).isNullOrEmpty()) { MangaState.FINISHED } else { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/fr/PapScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/fr/PapScan.kt index 0833785d..667cc56a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/fr/PapScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/fr/PapScan.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.parsers.site.animebootstrap.fr - import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import org.jsoup.nodes.Document @@ -13,20 +12,14 @@ import java.text.SimpleDateFormat import java.util.EnumSet import java.util.Locale - @MangaSourceParser("PAPSCAN", "PapScan", "fr") internal class PapScan(context: MangaLoaderContext) : AnimeBootstrapParser(context, MangaSource.PAPSCAN, "papscan.com") { - override val sourceLocale: Locale = Locale.ENGLISH - override val isMultipleTagsSupported = false - override val listUrl = "/liste-manga" - override val selectState = "div.anime__details__widget li:contains(En cours)" override val selectTag = "div.anime__details__widget li:contains(Genre) a" - override val selectChapter = "ul.chapters li" override val availableSortOrders: Set = EnumSet.of( @@ -34,40 +27,39 @@ internal class PapScan(context: MangaLoaderContext) : SortOrder.ALPHABETICAL, ) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - - val tag = tags.oneOrThrowIfMany() - + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) append("/filterList") append("?page=") append(page.toString()) + when (filter) { + is MangaListFilter.Search -> { + append("&alpha=") + append(filter.query.urlEncoded()) + } - if (!query.isNullOrEmpty()) { - append("&alpha=") - append(query.urlEncoded()) - } + is MangaListFilter.Advanced -> { - if (!tags.isNullOrEmpty()) { - append("&cat=") - append(tag?.key.orEmpty()) - } - append("&sortBy=") - when (sortOrder) { - SortOrder.POPULARITY -> append("views") - SortOrder.ALPHABETICAL -> append("name") - else -> append("updated") + filter.tags.oneOrThrowIfMany()?.let { + append("&cat=") + append(it.key) + } + + append("&sortBy=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("views") + SortOrder.ALPHABETICAL -> append("name") + else -> append("updated") + } + + } + + null -> append("&sortBy=updated") } } val doc = webClient.httpGet(url).parseHtml() - return doc.select("div.product__item").map { div -> val href = div.selectFirstOrThrow("h5 a").attrAsRelativeUrl("href") Manga( @@ -103,17 +95,13 @@ internal class PapScan(context: MangaLoaderContext) : override suspend fun getDetails(manga: Manga): Manga = coroutineScope { val fullUrl = manga.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() - val chaptersDeferred = async { getChapters(doc) } - val desc = doc.selectFirstOrThrow(selectDesc).html() - val state = if (doc.select(selectState).isNullOrEmpty()) { MangaState.FINISHED } else { MangaState.ONGOING } - manga.copy( tags = doc.body().select(selectTag).mapNotNullToSet { a -> MangaTag( @@ -145,5 +133,4 @@ internal class PapScan(context: MangaLoaderContext) : ) } } - } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/KomikzoId.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/KomikzoId.kt index cd7d64a1..6e2da189 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/KomikzoId.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/KomikzoId.kt @@ -5,6 +5,6 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.site.animebootstrap.AnimeBootstrapParser -@MangaSourceParser("KOMIKZOID", "Komikzo Id", "id") +@MangaSourceParser("KOMIKZOID", "KomikzoId", "id") internal class KomikzoId(context: MangaLoaderContext) : AnimeBootstrapParser(context, MangaSource.KOMIKZOID, "komikzoid.xyz") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/NeuManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/NeuManga.kt index 625459f6..4912109e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/NeuManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/NeuManga.kt @@ -1,12 +1,10 @@ package org.koitharu.kotatsu.parsers.site.animebootstrap.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.animebootstrap.AnimeBootstrapParser - -@MangaSourceParser("NEUMANGA", "NeuManga", "id") +@MangaSourceParser("NEUMANGA", "NeuManga.xyz", "id") internal class NeuManga(context: MangaLoaderContext) : AnimeBootstrapParser(context, MangaSource.NEUMANGA, "neumanga.xyz") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/SekteKomik.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/SekteKomik.kt index 86f48685..574cba24 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/SekteKomik.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/SekteKomik.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.parsers.site.animebootstrap.id - import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.MangaSource diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt index 95a12125..54d4e545 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt @@ -19,32 +19,66 @@ import java.util.* internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.FLIXSCANS, 18) { override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) + override val availableStates: Set = EnumSet.allOf(MangaState::class.java) override val configKeyDomain = ConfigKey.Domain("flixscans.com") - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val json = if (!query.isNullOrEmpty()) { - if (page > 1) { - return emptyList() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + + val json = when (filter) { + is MangaListFilter.Search -> { + if (page > 1) { + return emptyList() + } + val url = "https://api.$domain/api/v1/search/serie" + val body = JSONObject() + body.put("title", filter.query.urlEncoded()) + webClient.httpPost(url, body).parseJson().getJSONArray("data") } - val url = "https://api.$domain/api/v1/search/serie" - val body = JSONObject() - body.put("title", query.urlEncoded()) - webClient.httpPost(url, body).parseJson().getJSONArray("data") - } else if (!tags.isNullOrEmpty()) { - if (page > 1) { - return emptyList() + + is MangaListFilter.Advanced -> { + val url = buildString { + append("https://api.") + append(domain) + append("/api/v1/") + + if (filter.tags.isNotEmpty() || filter.states.isNotEmpty()) { + if (page > 1) { + return emptyList() + } + append("search/advance?=") + if (filter.tags.isNotEmpty()) { + val tagQuery = filter.tags.joinToString(separator = ",") { it.key } + append("&genres=") + append(tagQuery) + } + if (filter.states.isNotEmpty()) { + filter.states.oneOrThrowIfMany()?.let { + append("&status=") + append( + when (it) { + MangaState.ONGOING -> "ongoing" + MangaState.FINISHED -> "completed" + MangaState.ABANDONED -> "droped" + MangaState.PAUSED -> "onhold" + }, + ) + } + } + append("&serie_type=webtoon") + + } else { + append("webtoon/homepage/latest/home?page=") + append(page.toString()) + } + } + + webClient.httpGet(url).parseJson().getJSONArray("data") + } + + null -> { + val url = "https://api.$domain/api/v1/webtoon/homepage/latest/home?page=$page" + webClient.httpGet(url).parseJson().getJSONArray("data") } - val tagQuery = tags.joinToString(separator = ",") { it.key } - val url = "https://api.$domain/api/v1/search/advance?=&genres=$tagQuery&serie_type=webtoon" - webClient.httpGet(url).parseJson().getJSONArray("data") - } else { - val url = "https://api.$domain/api/v1/webtoon/homepage/latest/home?page=$page" - webClient.httpGet(url).parseJson().getJSONArray("data") } return json.mapJSON { j -> val href = "https://$domain/series/${j.getString("prefix")}-${j.getString("id")}-${j.getString("slug")}" @@ -62,6 +96,8 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context state = when (j.getString("status")) { "ongoing" -> MangaState.ONGOING "completed" -> MangaState.FINISHED + "onhold" -> MangaState.PAUSED + "droped" -> MangaState.ABANDONED else -> null }, author = null, @@ -77,9 +113,9 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context return tagsList.mapNotNullToSet { idTag -> val id = idTag.toInt() val idKey = json.getJSONObject(id).getInt("id") - val key = json.get(idKey).toString() + val key = json.getInt(idKey).toString() val idName = json.getJSONObject(id).getInt("name") - val name = json.get(idName).toString() + val name = json.getString(idName) MangaTag( key = key, title = name, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/MangaStorm.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/MangaStorm.kt index 74aec220..4859e841 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/MangaStorm.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/MangaStorm.kt @@ -13,7 +13,7 @@ import java.util.* @MangaSourceParser("MANGASTORM", "MangaStorm", "ar") internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.MANGASTORM, 30) { - override val availableSortOrders: Set = EnumSet.of(SortOrder.POPULARITY) + override val availableSortOrders: Set = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED) override val configKeyDomain = ConfigKey.Domain("mangastorm.org") override val isMultipleTagsSupported = false @@ -21,35 +21,44 @@ internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(contex .add("User-Agent", UserAgents.CHROME_DESKTOP) .build() - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() - val url = - if (!tags.isNullOrEmpty()) { - buildString { - append("https://") - append(domain) - append("/categories/") - append(tag?.key.orEmpty()) - append("?page=") + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val url = buildString { + append("https://") + append(domain) + when (filter) { + is MangaListFilter.Search -> { + append("/mangas?page=") append(page) + append("&query=") + append(filter.query.urlEncoded()) + } + + is MangaListFilter.Advanced -> { + + if (filter.tags.isNotEmpty()) { + val tag = filter.tags.oneOrThrowIfMany() + append("/categories/") + append(tag?.key.orEmpty()) + append("?page=") + append(page) + } else { + if (filter.sortOrder == SortOrder.POPULARITY) { + append("/mangas?page=") + append(page) + } else { + if (page > 1) { + return emptyList() + } + } + } } - } else { - buildString { - append("https://") - append(domain) + + null -> { append("/mangas?page=") append(page) - if (!query.isNullOrEmpty()) { - append("&query=") - append(query.urlEncoded()) - } } } + } val doc = webClient.httpGet(url).parseHtml() return doc.select("div.row div.col").map { div -> val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") @@ -74,9 +83,7 @@ internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(contex override suspend fun getDetails(manga: Manga): Manga { val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() - val root = doc.selectFirstOrThrow(".card-body .col-lg-9") - return manga.copy( altTitle = null, state = null, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt index a2805601..252c6fcb 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt @@ -17,46 +17,71 @@ import java.util.* internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.TEAMXNOVEL, 10) { override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) + override val availableStates: Set = + EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED) + override val configKeyDomain = ConfigKey.Domain("team11x11.com") override val isMultipleTagsSupported = false - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val url = buildString { append("https://") append(domain) - if (!tags.isNullOrEmpty()) { - append("/series?genre=") - append(tag?.key.orEmpty()) - if (page > 1) { - append("&page=") - append(page) - } - } else if (!query.isNullOrEmpty()) { - append("/series?search=") - append(query.urlEncoded()) - if (page > 1) { - append("&page=") - append(page) - } - } else { - when (sortOrder) { - SortOrder.POPULARITY -> append("/series") - SortOrder.UPDATED -> append("/") - else -> append("/") + when (filter) { + + is MangaListFilter.Search -> { + append("/series?search=") + append(filter.query.urlEncoded()) + if (page > 1) { + append("&page=") + append(page.toString()) + } } - if (page > 1) { - append("?page=") - append(page) + + is MangaListFilter.Advanced -> { + if (filter.tags.isNotEmpty()) { + val tag = filter.tags.oneOrThrowIfMany() + append("/series?genre=") + append(tag?.key.orEmpty()) + if (page > 1) { + append("&page=") + append(page.toString()) + } + append("&") + } else { + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("/series") + SortOrder.UPDATED -> append("/") + else -> append("/") + } + if (page > 1) { + append("?page=") + append(page.toString()) + append("&") + } else { + append("?") + } + } + + if (filter.sortOrder == SortOrder.POPULARITY || filter.tags.isNotEmpty()) { + filter.states.oneOrThrowIfMany()?.let { + append("status=") + append( + when (it) { + MangaState.ONGOING -> "مستمرة" + MangaState.FINISHED -> "مكتمل" + MangaState.ABANDONED -> "متوقف" + else -> "مستمرة" + }, + ) + } + } } + + null -> append("/?page=$page") } } - val doc = webClient.httpGet(url).parseHtml() return doc.select("div.listupd .bs .bsx").ifEmpty { doc.select("div.post-body .box") @@ -74,7 +99,8 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex tags = emptySet(), state = when (div.selectFirst(".status")?.text()) { "مستمرة" -> MangaState.ONGOING - "متوقف", "مكتمل" -> MangaState.FINISHED + "مكتمل" -> MangaState.FINISHED + "متوقف" -> MangaState.ABANDONED else -> null }, author = null, @@ -111,7 +137,8 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex altTitle = null, state = when (doc.selectFirstOrThrow(".full-list-info:contains(الحالة:) a").text()) { "مستمرة" -> MangaState.ONGOING - "متوقف", "مكتمل" -> MangaState.FINISHED + "مكتمل" -> MangaState.FINISHED + "متوقف" -> MangaState.ABANDONED else -> null }, tags = doc.select(".review-author-info a").mapNotNullToSet { a -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/BeeToon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/BeeToon.kt index cb073d18..c5b6591d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/BeeToon.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/BeeToon.kt @@ -19,43 +19,41 @@ internal class BeeToon(context: MangaLoaderContext) : override val isMultipleTagsSupported = false - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) - when { - !query.isNullOrEmpty() -> { + when (filter) { + is MangaListFilter.Search -> { if (page > 1) { return emptyList() } append("/?s=") - append(query.urlEncoded()) + append(filter.query.urlEncoded()) } - !tags.isNullOrEmpty() -> { - append("/genre/") - append(tag?.key.orEmpty()) - append("/page-") - append(page) - append("/") - } + is MangaListFilter.Advanced -> { - else -> { - when (sortOrder) { - SortOrder.UPDATED -> append("/latest-update/") - SortOrder.POPULARITY -> append("/popular-manga/") - else -> append("/latest-update/") + if (filter.tags.isNotEmpty()) { + val tag = filter.tags.oneOrThrowIfMany() + append("/genre/") + append(tag?.key.orEmpty()) + append("/page-") + append(page) + append("/") + } else { + when (filter.sortOrder) { + SortOrder.UPDATED -> append("/latest-update/") + SortOrder.POPULARITY -> append("/popular-manga/") + else -> append("/latest-update/") + } + append("page-") + append(page) + append("/") } - append("page-") - append(page) - append("/") } + + null -> append("/latest-update/page-$page/") } } val doc = webClient.httpGet(url).parseHtml() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt index 7d36dab3..e01e0060 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt @@ -19,11 +19,25 @@ internal class CloneMangaParser(context: MangaLoaderContext) : MangaParser(conte override val configKeyDomain = ConfigKey.Domain("manga.clone-army.org") @InternalParsersApi - override suspend fun getList(offset: Int, query: String?, tags: Set?, sortOrder: SortOrder): List { - if (query != null || offset > 0) { - return emptyList() + override suspend fun getList(offset: Int, filter: MangaListFilter?): List { + + val link = when (filter) { + is MangaListFilter.Search -> { + return emptyList() + } + + is MangaListFilter.Advanced -> { + if (offset > 0) { + return emptyList() + } + + "https://$domain/viewer_landing.php" + } + + null -> "https://$domain/viewer_landing.php" + } - val link = "https://$domain/viewer_landing.php" + val doc = webClient.httpGet(link).parseHtml() val mangas = doc.getElementsByClass("comicPreviewContainer") return mangas.mapNotNull { item -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt index 1b1b40e2..042beaef 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt @@ -8,6 +8,7 @@ import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.util.* +import java.lang.IllegalArgumentException import java.text.SimpleDateFormat import java.util.* @@ -17,6 +18,8 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex override val availableSortOrders: Set = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST) + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) + override val configKeyDomain = ConfigKey.Domain("comicextra.me") override val isMultipleTagsSupported = false @@ -25,38 +28,62 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex .add("User-Agent", UserAgents.CHROME_DESKTOP) .build() - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { - append("https://$domain/") - if (!tags.isNullOrEmpty()) { - append(tag?.key.orEmpty()) - if (page > 1) { - append("/") - append(page) - } - } else if (!query.isNullOrEmpty()) { - append("comic-search?key=") - append(query.urlEncoded()) - if (page > 1) { - append("&page=") - append(page) + append("https://") + append(domain) + append("/") + when (filter) { + is MangaListFilter.Search -> { + append("comic-search?key=") + append(filter.query.urlEncoded()) + if (page > 1) { + append("&page=") + append(page.toString()) + } } - } else { - when (sortOrder) { - SortOrder.POPULARITY -> append("popular-comic/") - SortOrder.UPDATED -> append("new-comic/") - SortOrder.NEWEST -> append("recent-comic/") - else -> append("new-comic/") + + is MangaListFilter.Advanced -> { + if (filter.tags.isNotEmpty() && filter.states.isEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append(it.key) + } + } else if (filter.tags.isEmpty() && filter.states.isNotEmpty()) { + filter.states.oneOrThrowIfMany()?.let { + append( + when (it) { + MangaState.ONGOING -> "/ongoing-comic" + MangaState.FINISHED -> "/completed-comic" + else -> "/ongoing-comic" + }, + ) + } + + } else if (filter.tags.isNotEmpty() && filter.states.isNotEmpty()) { + throw IllegalArgumentException("Source does not support tag + states filters") + } else { + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("popular-comic") + SortOrder.UPDATED -> append("new-comic") + SortOrder.NEWEST -> append("recent-comic") + else -> append("new-comic") + } + } + + if (page > 1) { + append("/") + append(page.toString()) + } } - if (page > 1) { - append(page) + + null -> { + append("popular-comic") + if (page > 1) { + append("/") + append(page.toString()) + } } + } } val doc = webClient.httpGet(url).parseHtml() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt index 634dc9ec..2200b663 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt @@ -1,8 +1,13 @@ package org.koitharu.kotatsu.parsers.site.en +import androidx.collection.ArraySet +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope import okhttp3.Headers import org.json.JSONArray import org.jsoup.nodes.Document +import org.jsoup.nodes.Element import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.PagedMangaParser @@ -22,85 +27,128 @@ internal class DynastyScans(context: MangaLoaderContext) : PagedMangaParser(cont .add("User-Agent", UserAgents.CHROME_DESKTOP) .build() - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val url = buildString { - append("https://") - append(domain) - if (!query.isNullOrEmpty()) { - append("/search?q=") - append(query.urlEncoded()) - append("&") - append("classes[]".urlEncoded()) - append("=Serie&page=") - append(page.toString()) - } else if (!tags.isNullOrEmpty()) { - append("/tags/") - for (tag in tags) { - append(tag.key) + override val isMultipleTagsSupported = false + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + when (filter) { + is MangaListFilter.Search -> { + val url = buildString { + append("https://") + append(domain) + append("/search?q=") + append(filter.query.urlEncoded()) + append("&") + append("classes[]".urlEncoded()) + append("=Serie&page=") + append(page.toString()) } - append("?view=groupings&page=") - append(page.toString()) - } else { - append("/series?view=cover&page=") - append(page.toString()) + return parseMangaListQuery(webClient.httpGet(url).parseHtml()) } - } - val doc = webClient.httpGet(url).parseHtml() - - // There are no images on the search page - if (!query.isNullOrEmpty()) { - return doc.select("dl.chapter-list dd") - .map { div -> - val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - title = div.selectFirstOrThrow("a").text(), - altTitle = null, - url = href, - publicUrl = href.toAbsoluteUrl(domain), - rating = RATING_UNKNOWN, - isNsfw = false, - coverUrl = "", - tags = div.select("span.tags a").mapNotNullToSet { a -> - MangaTag( - key = a.attr("href").removeSuffix('/').substringAfterLast('/'), - title = a.text(), - source = source, - ) - }, - state = null, - author = null, - source = source, - ) + + is MangaListFilter.Advanced -> { + + val url = buildString { + append("https://") + append(domain) + if (filter.tags.isNotEmpty()) { + append("/tags/") + filter.tags.oneOrThrowIfMany()?.let { + append(it.key) + } + append("?view=groupings") + } else { + append("/series?view=cover") + + } + + append("&page=") + append(page.toString()) } - } else { - return doc.select("li.span2") - .map { div -> - val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - title = div.selectFirstOrThrow("div.caption").text(), - altTitle = null, - url = href, - publicUrl = href.toAbsoluteUrl(domain), - rating = RATING_UNKNOWN, - isNsfw = false, - coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"), - tags = setOf(), - state = null, - author = null, - source = source, - ) + return parseMangaList(webClient.httpGet(url).parseHtml()) + } + + null -> { + val url = buildString { + append("https://") + append(domain) + append("/series?view=cover&page=") + append(page.toString()) } + return parseMangaList(webClient.httpGet(url).parseHtml()) + } } } - override suspend fun getAvailableTags(): Set = emptySet() + + private fun parseMangaList(doc: Document): List { + return doc.select("li.span2") + .map { div -> + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + title = div.selectFirstOrThrow("div.caption").text(), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + isNsfw = false, + coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"), + tags = setOf(), + state = null, + author = null, + source = source, + ) + } + } + + private fun parseMangaListQuery(doc: Document): List { + return doc.select("dl.chapter-list dd") + .map { div -> + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + title = div.selectFirstOrThrow("a").text(), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + isNsfw = false, + coverUrl = "", + tags = div.select("span.tags a").mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix('/').substringAfterLast('/'), + title = a.text(), + source = source, + ) + }, + state = null, + author = null, + source = source, + ) + } + } + + override suspend fun getAvailableTags(): Set { + return coroutineScope { + (1..3).map { page -> + async { getTags(page) } + } + }.awaitAll().flattenTo(ArraySet(360)) + } + + private suspend fun getTags(page: Int): Set { + val url = "https://$domain/tags?page=$page" + val root = webClient.httpGet(url).parseHtml() + return root.selectFirstOrThrow(".tag-list ").parseTags() + } + + private fun Element.parseTags() = select("a").mapToSet { + MangaTag( + key = it.attr("href").removeSuffix('/').substringAfterLast('/'), + title = it.text(), + source = source, + ) + } override suspend fun getDetails(manga: Manga): Manga { val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() @@ -110,19 +158,14 @@ internal class DynastyScans(context: MangaLoaderContext) : PagedMangaParser(cont altTitle = null, state = when (root.select("h2.tag-title small").last()?.text()) { "— Ongoing" -> MangaState.ONGOING - "— Completed" -> MangaState.FINISHED + "— Completed", "— Completed and Licensed" -> MangaState.FINISHED + "— Dropped", "— Licensed and Removed", "— Abandoned" -> MangaState.ABANDONED + "— On Hiatus" -> MangaState.PAUSED else -> null }, coverUrl = root.selectFirst("img.thumbnail")?.src() .orEmpty(), // It is needed if the manga was found via the search. - tags = root.select("div.tag-tags a").mapNotNullToSet { a -> - val href = a.attr("href").removeSuffix('/').substringAfterLast('/') - MangaTag( - key = href, - title = a.text(), - source = source, - ) - }, + tags = root.selectFirstOrThrow("div.tag-tags").parseTags(), author = null, description = null, chapters = chapters, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Fakku.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Fakku.kt deleted file mode 100644 index b231da15..00000000 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Fakku.kt +++ /dev/null @@ -1,148 +0,0 @@ -package org.koitharu.kotatsu.parsers.site.en - -import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope -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.* -import org.koitharu.kotatsu.parsers.util.* -import org.koitharu.kotatsu.parsers.util.json.mapJSONToSet -import java.util.* - -@MangaSourceParser("FAKKU", "Fakku", "en", ContentType.HENTAI) -internal class Fakku(context: MangaLoaderContext) : - PagedMangaParser(context, MangaSource.FAKKU, pageSize = 25) { - - override val availableSortOrders: Set = - EnumSet.of(SortOrder.ALPHABETICAL, SortOrder.NEWEST, SortOrder.UPDATED) - - override val configKeyDomain = ConfigKey.Domain("fakku.cc") - - override val isMultipleTagsSupported = false - - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() - val url = buildString { - append("https://") - append(domain) - when { - !query.isNullOrEmpty() -> { - append("/search?q=") - append(query.urlEncoded()) - append("&") - } - - !tags.isNullOrEmpty() -> { - append("/tags/") - append(tag?.key.orEmpty()) - append("?") - } - - else -> { - append("?") - } - } - append("page=") - append(page) - append("&sort=") - when (sortOrder) { - SortOrder.ALPHABETICAL -> append("title") - SortOrder.NEWEST -> append("created_at") - SortOrder.UPDATED -> append("published_at") - else -> append("published_at") - } - } - val doc = webClient.httpGet(url).parseHtml() - return doc.select("div.entries .entry a").map { a -> - val href = a.attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - url = href, - publicUrl = href.toAbsoluteUrl(domain), - coverUrl = a.selectFirst("img")?.src().orEmpty(), - title = a.selectFirst(".title")?.text().orEmpty(), - altTitle = null, - rating = RATING_UNKNOWN, - tags = emptySet(), - author = null, - state = null, - source = source, - isNsfw = isNsfwSource, - ) - } - } - - override suspend fun getAvailableTags(): Set { - val root = webClient.httpGet("https://$domain/tags").parseHtml() - return root.select("div.entries .entry a").mapToSet { - MangaTag( - key = it.attr("href").substringAfterLast("/"), - title = it.selectFirstOrThrow(".name").text(), - source = source, - ) - } - } - - override suspend fun getDetails(manga: Manga): Manga = coroutineScope { - val fullUrl = manga.url.toAbsoluteUrl(domain) - val doc = webClient.httpGet(fullUrl).parseHtml() - val genreDeferred = async { - webClient.httpGet(manga.url.toAbsoluteUrl(domain) + ".json").parseJson() - } - val genre = genreDeferred.await() - manga.copy( - author = doc.selectFirst("tr.artists a")?.text(), - tags = if (genre.toString().contains("tags")) { - genre.getJSONArray("tags").mapJSONToSet { - MangaTag( - key = it.getString("slug"), - title = it.getString("name"), - source = source, - ) - } - } else { - emptySet() - }, - chapters = listOf( - MangaChapter( - id = manga.id, - name = manga.title, - number = 1, - url = manga.url + "/1", - scanlator = null, - uploadDate = 0, - branch = null, - source = source, - ), - ), - ) - } - - override suspend fun getPages(chapter: MangaChapter): List { - val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml() - val totalPages = doc.selectFirstOrThrow(".total").text().toInt() - val rawUrl = chapter.url.substringBeforeLast("/") - return (1..totalPages).map { - val url = "$rawUrl/$it" - MangaPage( - id = generateUid(url), - url = url, - preview = null, - source = source, - ) - } - } - - override suspend fun getPageUrl(page: MangaPage): String { - val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml() - val root = doc.body() - return root.selectFirstOrThrow(".page img").attrAsAbsoluteUrl("src") - } -} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/KskMoe.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/KskMoe.kt deleted file mode 100644 index e9c22fe8..00000000 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/KskMoe.kt +++ /dev/null @@ -1,187 +0,0 @@ -package org.koitharu.kotatsu.parsers.site.en - -import androidx.collection.ArraySet -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import org.json.JSONArray -import org.jsoup.nodes.Element -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.* -import org.koitharu.kotatsu.parsers.util.* -import org.koitharu.kotatsu.parsers.util.json.mapJSON -import java.text.SimpleDateFormat -import java.util.* - -@MangaSourceParser("KSKMOE", "Ksk.moe", "en", ContentType.HENTAI) -internal class KskMoe(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.KSKMOE, 35) { - - override val availableSortOrders: Set = - EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.ALPHABETICAL) - override val configKeyDomain = ConfigKey.Domain("ksk.moe") - override val isMultipleTagsSupported = false - - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() - - val url = buildString { - append("https://") - append(domain) - - if (!tags.isNullOrEmpty()) { - append("/tags/") - append(tag?.key.orEmpty()) - } else { - append("/browse") - } - - if (page > 1) { - append("/page/") - append(page) - } - - when (sortOrder) { - SortOrder.POPULARITY -> append("?sort=32") - SortOrder.UPDATED -> append("") - SortOrder.NEWEST -> append("?sort=16") - SortOrder.ALPHABETICAL -> append("?sort=1") - else -> append("") - } - - if (!query.isNullOrEmpty()) { - append("?s=") - append(query.urlEncoded()) - } - } - val doc = webClient.httpGet(url).parseHtml() - - if (!doc.html().contains("pagination") && page > 1) { - return emptyList() - } - return doc.requireElementById("galleries").select("article").map { div -> - val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - title = div.selectLastOrThrow("h3 span").text(), - altTitle = null, - url = href, - publicUrl = href.toAbsoluteUrl(domain), - rating = RATING_UNKNOWN, - isNsfw = true, - coverUrl = div.selectFirstOrThrow("img").src()?.toAbsoluteUrl(domain).orEmpty(), - tags = div.select("footer span").mapNotNullToSet { span -> - MangaTag( - key = span.text().urlEncoded(), - title = span.text(), - source = source, - ) - }, - state = null, - author = null, - source = source, - ) - } - } - - override suspend fun getAvailableTags(): Set { - return coroutineScope { - (1..2).map { page -> - async { getTags(page) } - } - }.awaitAll().flattenTo(ArraySet(360)) - } - - private suspend fun getTags(page: Int): Set { - val url = if (page == 1) { - "https://$domain/tags" - } else { - "https://$domain/tags/page/$page" - } - val root = webClient.httpGet(url).parseHtml().body().getElementById("tags") - return root?.parseTags().orEmpty() - } - - private fun Element.parseTags() = select("section.tags div a").mapToSet { a -> - MangaTag( - key = a.attr("href").substringAfterLast("/tags/"), - title = a.selectFirstOrThrow("span").text(), - source = source, - ) - } - - private val date = SimpleDateFormat("dd.MM.yyyy hh:mm 'UTC'", Locale.US) - override suspend fun getDetails(manga: Manga): Manga { - val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() - - return manga.copy( - tags = doc.requireElementById("metadata").select("main div:contains(Tag) a").mapNotNullToSet { a -> - MangaTag( - key = a.attr("href").substringAfterLast("/tags/"), - title = a.selectFirstOrThrow("span").text(), - source = source, - ) - }, - author = doc.requireElementById("metadata").selectFirstOrThrow("main div:contains(Artist) a span").text(), - chapters = listOf( - MangaChapter( - id = generateUid(manga.id), - name = manga.title, - number = 1, - url = manga.url, - scanlator = null, - uploadDate = date.tryParse(doc.selectFirstOrThrow("time.updated").text()), - branch = null, - source = source, - ), - ), - ) - } - - override suspend fun getPages(chapter: MangaChapter): List { - val fullUrl = chapter.url - .replace("/view/", "/read/") - .let { "$it/1" } - .toAbsoluteUrl(domain) - val document = webClient.httpGet(fullUrl).parseHtml() - - val id = fullUrl - .substringAfter("/read/") - .substringBeforeLast("/") - - val cdnUrl = document.selectFirst("meta[itemprop=image]") - ?.attr("content") - ?.toHttpUrlOrNull() - ?.host - .let { "https://" + (it ?: domain) } - - val script = document.select("script:containsData(window.metadata)").html() - - val rawJson = script - .substringAfter("original:") - .substringBefore("resampled:") - .substringBeforeLast(",") - - return JSONArray(rawJson).mapJSON { - val fileName = it.getString("n") - - val url = "$cdnUrl/original/$id/$fileName" - val preview = "$cdnUrl/t/$id/320/$fileName" - - MangaPage( - id = generateUid(url), - url = url, - preview = preview, - source = source, - ) - } - } -} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt index 81327a2d..c1cf6a1e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt @@ -25,42 +25,49 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context .add("User-Agent", UserAgents.CHROME_DESKTOP) .build() - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() - - val url = if (!query.isNullOrEmpty()) { - if (page > 1) { - return emptyList() - } - buildString { - append("https://$domain/search/?search=") - append(query.urlEncoded()) - } - } else { - buildString { - append("https://$domain/browse-comics/?results=") - append(page) - append("&filter=") - when (sortOrder) { - SortOrder.POPULARITY -> append("views") - SortOrder.UPDATED -> append("Updated") - SortOrder.NEWEST -> append("New") - else -> append("Updated") + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + + val url = buildString { + append("https://") + append(domain) + when (filter) { + is MangaListFilter.Search -> { + if (page > 1) { + return emptyList() + } + append("/search/?search=") + append(filter.query.urlEncoded()) } - if (!tags.isNullOrEmpty()) { - append("&genre=") - append(tag?.key.orEmpty()) + + is MangaListFilter.Advanced -> { + + append("/browse-comics/?results=") + append(page) + + append("&filter=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("views") + SortOrder.UPDATED -> append("Updated") + SortOrder.NEWEST -> append("New") + else -> append("Updated") + } + + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append("&genre=") + append(it.key) + } + } + } + + null -> { + append("/browse-comics/?results=") + append(page) + append("&filter=Updated") } } } - val doc = webClient.httpGet(url).parseHtml() - return doc.select("li.novel-item").map { div -> val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt index 4cb4ed8f..6aad3269 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt @@ -22,35 +22,69 @@ internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(contex SortOrder.UPDATED, ) - private val regexTag = Regex("[^\\-]+-[^\\-]+-[^\\-]+-[^\\-]+-[^\\-]+-[^\\-]+") - - override suspend fun getList( - offset: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val sortKey = when (sortOrder) { - SortOrder.ALPHABETICAL -> "?name.az" - SortOrder.RATING -> "?rating.za" - SortOrder.UPDATED -> "?last_chapter_time.za" - else -> "" - } + override val availableStates: Set = EnumSet.of( + MangaState.ONGOING, + MangaState.FINISHED, + ) + + override val isMultipleTagsSupported = false + + override suspend fun getList(offset: Int, filter: MangaListFilter?): List { val page = (offset / 30) + 1 - val url = when { - !query.isNullOrEmpty() -> { - if (offset != 0) { - return emptyList() + val url = buildString { + append("https://") + append(domain) + when (filter) { + is MangaListFilter.Search -> { + append("/search?name=") + append(filter.query.urlEncoded()) + append("&page=") + append(page.toString()) } - "/search?name=${query.urlEncoded()}".toAbsoluteUrl(domain) - } - tags.isNullOrEmpty() -> "/directory/$page.htm$sortKey".toAbsoluteUrl(domain) - tags.size == 1 -> "/directory/${tags.first().key}/$page.htm$sortKey".toAbsoluteUrl(domain) - else -> tags.joinToString( - prefix = "/search?page=$page".toAbsoluteUrl(domain), - ) { tag -> - "&genres[${tag.key}]=1" + is MangaListFilter.Advanced -> { + append("/directory/") + append("0-") + + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append(it.key) + } + } else { + append("0") + } + append("-0-") + + if (filter.states.isNotEmpty()) { + filter.states.oneOrThrowIfMany()?.let { + append( + when (it) { + MangaState.ONGOING -> "ongoing" + MangaState.FINISHED -> "completed" + else -> "0" + }, + ) + } + } else { + append("0") + } + + append("-0-0/") + append(page.toString()) + append(".htm") + + append( + when (filter.sortOrder) { + SortOrder.POPULARITY -> "" + SortOrder.UPDATED -> "?last_chapter_time.za" + SortOrder.ALPHABETICAL -> "?name.az" + SortOrder.RATING -> "?rating.za" + else -> "?last_chapter_time.za" + }, + ) + } + + null -> append("/directory/$page.htm?last_chapter_time.za") } } val doc = webClient.httpGet(url).parseHtml() @@ -81,7 +115,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(contex tags = li.selectFirst("p.keyWord")?.select("a")?.mapNotNullToSet tags@{ x -> MangaTag( title = x.attr("title").toTitleCase(), - key = x.attr("href").parseTagKey() ?: return@tags null, + key = x.attr("href").substringAfter("/directory/0-").substringBefore("-0-"), source = MangaSource.MANGATOWN, ) }.orEmpty(), @@ -106,7 +140,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(contex }?.select("a")?.mapNotNull { a -> MangaTag( title = a.attr("title").toTitleCase(), - key = a.attr("href").parseTagKey() ?: return@mapNotNull null, + key = a.attr("href").substringAfter("/directory/0-").substringBefore("-0-"), source = MangaSource.MANGATOWN, ) }.orEmpty(), @@ -165,10 +199,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(contex ?.nextElementSibling() ?: doc.parseFailed("Root not found") return root.select("li").mapNotNullToSet { li -> val a = li.selectFirst("a") ?: return@mapNotNullToSet null - val key = a.attr("href").parseTagKey() - if (key.isNullOrEmpty()) { - return@mapNotNullToSet null - } + val key = a.attr("href").substringAfter("/directory/0-").substringBefore("-0-") MangaTag( source = MangaSource.MANGATOWN, key = key, @@ -211,6 +242,4 @@ internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(contex ) } } - - private fun String.parseTagKey() = split('/').findLast { regexTag matches it } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Mangaowl.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Mangaowl.kt index 71f1d23a..a5e6fe0e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Mangaowl.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Mangaowl.kt @@ -24,6 +24,7 @@ internal class Mangaowl(context: MangaLoaderContext) : SortOrder.UPDATED, SortOrder.RATING, ) + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) override val configKeyDomain = ConfigKey.Domain("mangaowl.to") @@ -31,46 +32,56 @@ internal class Mangaowl(context: MangaLoaderContext) : .add("User-Agent", UserAgents.CHROME_DESKTOP) .build() - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val sort = when (sortOrder) { - SortOrder.POPULARITY -> "view_count" - SortOrder.UPDATED -> "-modified_at" - SortOrder.NEWEST -> "created_at" - SortOrder.RATING -> "rating" - else -> "modified_at" - } + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val url = buildString { append("https://") append(domain) - when { - !query.isNullOrEmpty() -> { - append("/8-search") - append("?q=") - append(query.urlEncoded()) + when (filter) { + is MangaListFilter.Search -> { + append("/10-search?q=") + append(filter.query.urlEncoded()) append("&page=") append(page.toString()) } - !tags.isNullOrEmpty() -> { - append("/8-genres/") - for (tag in tags) { - append(tag.key) - } + is MangaListFilter.Advanced -> { + + append("/10-comics") append("?page=") append(page.toString()) + + filter.tags.forEach { tag -> + append("&genres=") + append(tag.key) + } + + filter.states.oneOrThrowIfMany()?.let { + append("&status=") + append( + when (it) { + MangaState.ONGOING -> "ongoing" + MangaState.FINISHED -> "completed" + else -> "" + }, + ) + } + + append("&ordering=") + append( + when (filter.sortOrder) { + SortOrder.POPULARITY -> "view_count" + SortOrder.UPDATED -> "-modified_at" + SortOrder.NEWEST -> "created_at" + SortOrder.RATING -> "rating" + else -> "modified_at" + }, + ) } - else -> { - append("/8-comics") - append("?page=") + null -> { + append("/10-comics?ordering=-modified_at&page=") append(page.toString()) - append("&ordering=") - append(sort) } } } @@ -95,9 +106,9 @@ internal class Mangaowl(context: MangaLoaderContext) : } override suspend fun getAvailableTags(): Set { - val doc = webClient.httpGet("https://$domain/8-genres").parseHtml() + val doc = webClient.httpGet("https://$domain/10-genres").parseHtml() return doc.select("div.genres-container span.genre-item a").mapNotNullToSet { a -> - val key = a.attr("href").substringAfterLast("/") + val key = a.attr("href").removeSuffix('/').substringAfterLast('/').substringBefore("-") MangaTag( key = key, title = a.text(), @@ -112,7 +123,7 @@ internal class Mangaowl(context: MangaLoaderContext) : manga.copy( tags = doc.body().select("div.comic-attrs div.column.my-2:contains(Genres) a").mapNotNullToSet { a -> MangaTag( - key = a.attr("href").removeSuffix("/").substringAfterLast('/'), + key = a.attr("href").removeSuffix("/").substringAfterLast('/').substringBefore("-"), title = a.text().toTitleCase().replace(",", ""), source = source, ) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt index 76f77453..27b1d5e9 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt @@ -9,14 +9,26 @@ import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* import java.util.* -@MangaSourceParser("MANHWA18", "Manhwa18", "en", type = ContentType.HENTAI) +@MangaSourceParser("MANHWA18", "Manhwa18.net", "en", type = ContentType.HENTAI) class Manhwa18Parser(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.MANHWA18, pageSize = 18, searchPageSize = 18) { override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwa18.net") override val availableSortOrders: Set - get() = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.ALPHABETICAL, SortOrder.NEWEST) + get() = EnumSet.of( + SortOrder.UPDATED, + SortOrder.POPULARITY, + SortOrder.ALPHABETICAL, + SortOrder.NEWEST, + SortOrder.RATING, + ) + + override val availableStates: Set = EnumSet.of( + MangaState.ONGOING, + MangaState.FINISHED, + MangaState.PAUSED, + ) private val tagsMap = SuspendLazy(::parseTags) @@ -29,6 +41,82 @@ class Manhwa18Parser(context: MangaLoaderContext) : ) } + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + + val url = buildString { + append("https://") + append(domain) + append("/tim-kiem?page=") + append(page.toString()) + + when (filter) { + is MangaListFilter.Search -> { + append("&q=") + append(filter.query.urlEncoded()) + } + + is MangaListFilter.Advanced -> { + + append("&accept_genres=") + if (filter.tags.isNotEmpty()) { + append( + filter.tags.joinToString(",") { it.key }, + ) + } + + append("&sort=") + append( + when (filter.sortOrder) { + SortOrder.ALPHABETICAL -> "az" + SortOrder.POPULARITY -> "top" + SortOrder.UPDATED -> "update" + SortOrder.NEWEST -> "new" + SortOrder.RATING -> "like" + }, + ) + + filter.states.oneOrThrowIfMany()?.let { + append("&status=") + append( + when (it) { + MangaState.ONGOING -> "1" + MangaState.FINISHED -> "3" + MangaState.PAUSED -> "2" + else -> "" + }, + ) + } + } + + null -> append("&sort=update") + } + } + + val docs = webClient.httpGet(url).parseHtml() + + return docs.select(".card-body .thumb-item-flow") + .map { + val titleElement = it.selectFirstOrThrow(".thumb_attr.series-title > a") + val absUrl = titleElement.attrAsAbsoluteUrl("href") + Manga( + id = generateUid(absUrl.toRelativeUrl(domain)), + title = titleElement.text(), + altTitle = null, + url = absUrl.toRelativeUrl(domain), + publicUrl = absUrl, + rating = RATING_UNKNOWN, + isNsfw = true, + coverUrl = it.selectFirst("div.img-in-ratio")?.attrAsAbsoluteUrl("data-bg").orEmpty(), + tags = emptySet(), + state = null, + author = null, + largeCoverUrl = null, + description = null, + source = MangaSource.MANHWA18, + ) + } + } + override suspend fun getDetails(manga: Manga): Manga { val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val cardInfoElement = docs.selectFirst("div.series-information") @@ -45,6 +133,7 @@ class Manhwa18Parser(context: MangaLoaderContext) : when (it.text().lowercase()) { "on going" -> MangaState.ONGOING "completed" -> MangaState.FINISHED + "on hold" -> MangaState.PAUSED else -> null } } @@ -99,60 +188,6 @@ class Manhwa18Parser(context: MangaLoaderContext) : return cal.time.time } - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val sortQuery = when (sortOrder) { - SortOrder.ALPHABETICAL -> "az" - SortOrder.POPULARITY -> "top" - SortOrder.UPDATED -> "update" - SortOrder.NEWEST -> "new" - else -> "" - } - - val tagQuery = tags?.joinToString(",") { it.key }.orEmpty() - val url = buildString { - append("https://") - append(domain) - append("/tim-kiem?page=") - append(page) - if (!query.isNullOrEmpty()) { - append("&q=") - append(query.urlEncoded()) - } - append("&accept_genres=$tagQuery") - append("&sort=") - append(sortQuery) - } - - val docs = webClient.httpGet(url).parseHtml() - - return docs.select(".card-body .thumb-item-flow") - .map { - val titleElement = it.selectFirstOrThrow(".thumb_attr.series-title > a") - val absUrl = titleElement.attrAsAbsoluteUrl("href") - Manga( - id = generateUid(absUrl.toRelativeUrl(domain)), - title = titleElement.text(), - altTitle = null, - url = absUrl.toRelativeUrl(domain), - publicUrl = absUrl, - rating = RATING_UNKNOWN, - isNsfw = true, - coverUrl = it.selectFirst("div.img-in-ratio")?.attrAsAbsoluteUrl("data-bg").orEmpty(), - tags = emptySet(), - state = null, - author = null, - largeCoverUrl = null, - description = null, - source = MangaSource.MANHWA18, - ) - } - } - override suspend fun getPages(chapter: MangaChapter): List { val chapterUrl = chapter.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(chapterUrl).parseHtml() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt index 8bb514ca..970bd688 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt @@ -21,29 +21,42 @@ class ManhwasMen(context: MangaLoaderContext) : override val availableSortOrders: Set get() = EnumSet.of(SortOrder.POPULARITY) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val url = buildString { append("https://") append(domain) append("/manga-list") append("?page=") - append(page) - when { - !query.isNullOrEmpty() -> { + append(page.toString()) + when (filter) { + is MangaListFilter.Search -> { append("&search=") - append(query.urlEncoded()) + append(filter.query.urlEncoded()) } - !tags.isNullOrEmpty() -> { - append("&genero=") - append(tag?.key.orEmpty()) + is MangaListFilter.Advanced -> { + + filter.tags.oneOrThrowIfMany()?.let { + append("&genero=") + append(it.key) + } + + filter.states.oneOrThrowIfMany()?.let { + append("&estado=") + append( + when (it) { + MangaState.ONGOING -> "ongoing" + MangaState.FINISHED -> "complete" + else -> "" + }, + ) + } } + + null -> {} } } val doc = webClient.httpGet(url).parseHtml() @@ -89,9 +102,9 @@ class ManhwasMen(context: MangaLoaderContext) : ) }, description = doc.select(".sinopsis").html(), - state = when (doc.selectLast(".anime-type-peli")?.text()?.lowercase()) { + state = when (doc.selectLast("span.anime-type-peli")?.text()?.lowercase()) { "ongoing" -> MangaState.ONGOING - "completed" -> MangaState.FINISHED + "complete" -> MangaState.FINISHED else -> null }, chapters = doc.select(".episodes-list li").mapChapters(reversed = true) { i, li -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt index 1659a2c5..bbd415dd 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt @@ -15,20 +15,28 @@ internal class Po2Scans(context: MangaLoaderContext) : MangaParser(context, Mang override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) override val configKeyDomain = ConfigKey.Domain("po2scans.com") - override suspend fun getList(offset: Int, query: String?, tags: Set?, sortOrder: SortOrder): List { + override suspend fun getList(offset: Int, filter: MangaListFilter?): List { if (offset > 0) { return emptyList() } val url = buildString { - append("https://$domain/series") - if (!query.isNullOrEmpty()) { - append("?search=") - append(query.urlEncoded()) + append("https://") + append(domain) + append("/series") + when (filter) { + is MangaListFilter.Search -> { + append("?search=") + append(filter.query.urlEncoded()) + } + + is MangaListFilter.Advanced -> {} + + null -> {} } } val doc = webClient.httpGet(url).parseHtml() return doc.select(".series-list").map { div -> - val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + val href = "/" + div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( id = generateUid(href), title = div.selectFirstOrThrow("h2").text(), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Pururin.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Pururin.kt index 1940df62..ce48030f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Pururin.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Pururin.kt @@ -24,37 +24,43 @@ internal class Pururin(context: MangaLoaderContext) : override val isMultipleTagsSupported = false - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) - if (!query.isNullOrEmpty()) { - append("/search?q=") - append(query.urlEncoded()) - append("&page=") - append(page) - } else { - append("/browse") - if (!tags.isNullOrEmpty()) { - append("/tags/content/") - append(tag?.key.orEmpty()) - append("/") + when (filter) { + is MangaListFilter.Search -> { + append("/search?q=") + append(filter.query.urlEncoded()) + append("&page=") + append(page.toString()) } - append("?page=") - append(page) - append("&sort=") - when (sortOrder) { - SortOrder.UPDATED -> append("") - SortOrder.POPULARITY -> append("most-viewed") - SortOrder.RATING -> append("highest-rated") - SortOrder.ALPHABETICAL -> append("title") - else -> append("") + + is MangaListFilter.Advanced -> { + append("/browse") + + filter.tags.oneOrThrowIfMany()?.let { + append("/tags/content/") + append(it.key) + append("/") + } + + append("?page=") + append(page) + + append("&sort=") + when (filter.sortOrder) { + SortOrder.UPDATED -> append("") + SortOrder.POPULARITY -> append("most-viewed") + SortOrder.RATING -> append("highest-rated") + SortOrder.ALPHABETICAL -> append("title") + else -> append("") + } + } + + null -> { + append("/browse?page=") + append(page) } } }