From 37e9b33c24a29c8d7a2e86b06e9d4d9da745bff8 Mon Sep 17 00:00:00 2001 From: devi Date: Thu, 30 Nov 2023 18:46:10 +0100 Subject: [PATCH] add filter : LikeMangaParser, FmTeam, MadthemeParser , MangaReaderParser Fix GetList : NicovideoSeigaParser, Manga18Parser add filter & multitags on MangaboxParser --- .../kotatsu/parsers/site/fr/FmTeam.kt | 88 +++++++++----- .../parsers/site/ja/NicovideoSeigaParser.kt | 45 +++++--- .../parsers/site/likemanga/LikeMangaParser.kt | 85 +++++++++----- .../parsers/site/madtheme/MadthemeParser.kt | 69 +++++++---- .../parsers/site/madtheme/all/ManhuaScan.kt | 64 +++++++---- .../parsers/site/madtheme/en/MangaBuddy.kt | 1 - .../parsers/site/madtheme/en/TooniTube.kt | 1 - .../parsers/site/madtheme/en/ToonilyMe.kt | 1 - .../parsers/site/manga18/Manga18Parser.kt | 62 +++++----- .../parsers/site/manga18/en/Hentai3zCc.kt | 47 +------- .../parsers/site/manga18/es/Tumanhwas.kt | 1 - .../parsers/site/mangabox/MangaboxParser.kt | 92 ++++++++------- .../parsers/site/mangabox/en/Mangabat.kt | 6 - .../parsers/site/mangabox/en/Mangairo.kt | 99 ++++++++-------- .../parsers/site/mangabox/en/Mangakakalot.kt | 96 ++++++++++------ .../site/mangabox/en/MangakakalotTv.kt | 108 +++++++++++++----- .../parsers/site/mangabox/en/Manganato.kt | 3 - .../site/mangareader/MangaReaderParser.kt | 99 +++++++++------- 18 files changed, 570 insertions(+), 397 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FmTeam.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FmTeam.kt index 73f68a4f..8b81136b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FmTeam.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FmTeam.kt @@ -18,51 +18,83 @@ internal class FmTeam(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.FMTEAM, 0) { override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) override val configKeyDomain = ConfigKey.Domain("fmteam.fr") - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { if (page > 1) { return emptyList() } - val jsonManga = if (!query.isNullOrEmpty()) { - //3 letters minimum - webClient.httpGet("https://$domain/api/search/${query.urlEncoded()}").parseJson().getJSONArray("comics") - } else { - webClient.httpGet("https://$domain/api/comics").parseJson().getJSONArray("comics") - } + var foundTag = true + var foundState = true + + val manga = ArrayList() + + when (filter) { + is MangaListFilter.Search -> { + val jsonManga = webClient.httpGet("https://$domain/api/search/${filter.query.urlEncoded()}").parseJson() + .getJSONArray("comics") + for (i in 0 until jsonManga.length()) { + val j = jsonManga.getJSONObject(i) + val href = "/api" + j.getString("url") + manga.add(addManga(href, j)) + } + } + + is MangaListFilter.Advanced -> { + val jsonManga = webClient.httpGet("https://$domain/api/comics").parseJson().getJSONArray("comics") + for (i in 0 until jsonManga.length()) { + + val j = jsonManga.getJSONObject(i) + val href = "/api" + j.getString("url") - val manga = ArrayList(jsonManga.length()) - for (i in 0 until jsonManga.length()) { - val j = jsonManga.getJSONObject(i) - val href = "/api" + j.getString("url") - when { - !tags.isNullOrEmpty() -> { - val a = j.getJSONArray("genres").toString() - var found = true - tags.forEach { - if (!a.contains(it.key, ignoreCase = true)) { - found = false + if (filter.tags.isNotEmpty() && filter.states.isEmpty()) { + val a = j.getJSONArray("genres").toString() + foundTag = false + filter.tags.forEach { + if (a.contains(it.key, ignoreCase = true)) { + foundTag = true + } } } - if (found) { - manga.add( - addManga(href, j), - ) + + if (filter.states.isNotEmpty()) { + val a = j.getString("status") + foundState = false + filter.states.oneOrThrowIfMany()?.let { + if (a.contains( + when (it) { + MangaState.ONGOING -> "En cours" + MangaState.FINISHED -> "Terminé" + else -> "" + }, + ignoreCase = true, + ) + ) { + foundState = true + } + } + + } + + if (foundState && foundTag) { + manga.add(addManga(href, j)) } } + } - else -> { + null -> { + val jsonManga = webClient.httpGet("https://$domain/api/comics").parseJson().getJSONArray("comics") + for (i in 0 until jsonManga.length()) { + val j = jsonManga.getJSONObject(i) + val href = "/api" + j.getString("url") manga.add( addManga(href, j), ) } } } + return manga } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt index 17cff2ea..06eafe33 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt @@ -39,26 +39,37 @@ class NicovideoSeigaParser(context: MangaLoaderContext) : SortOrder.POPULARITY, ) + override val isMultipleTagsSupported = false + override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("nicovideo.jp") @InternalParsersApi - override suspend fun getList( - offset: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { + override suspend fun getList(offset: Int, filter: MangaListFilter?): List { val page = (offset / 20f).toIntUp().inc() val domain = getDomain("seiga") - val url = when { - !query.isNullOrEmpty() -> return if (offset == 0) getSearchList(query, page) else emptyList() - tags.isNullOrEmpty() -> "https://$domain/manga/list?page=$page&sort=${getSortKey(sortOrder)}" - tags.size == 1 -> "https://$domain/manga/list?category=${tags.first().key}&page=$page" + - "&sort=${getSortKey(sortOrder)}" - - tags.size > 1 -> throw IllegalArgumentException("This source supports only 1 category") - else -> "https://$domain/manga/list?page=$page&sort=${getSortKey(sortOrder)}" - } + val url = + when (filter) { + is MangaListFilter.Search -> { + return if (offset == 0) getSearchList(filter.query, page) else emptyList() + } + + is MangaListFilter.Advanced -> { + + + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany().let { + "https://$domain/manga/list?category=${it?.key}&page=$page&sort=${getSortKey(filter.sortOrder)}" + } + + } else { + "https://$domain/manga/list?page=$page&sort=${getSortKey(filter.sortOrder)}" + } + + } + + null -> "https://$domain/manga/list?page=$page" + } + val doc = webClient.httpGet(url).parseHtml() val comicList = doc.body().select("#comic_list > ul > li") ?: doc.parseFailed("Container not found") val items = comicList.select("div > .description > div > div") @@ -145,12 +156,12 @@ class NicovideoSeigaParser(context: MangaLoaderContext) : override suspend fun getAvailableTags(): Set { val doc = webClient.httpGet("https://${getDomain("seiga")}/manga/list").parseHtml() - val root = doc.body().selectOrThrow("#mg_category_list > ul > li") + val root = doc.body().selectOrThrow("#mg_category_list > ul > li").drop(1) return root.mapToSet { li -> val a = li.selectFirstOrThrow("a") MangaTag( title = a.text(), - key = a.attrAsRelativeUrlOrNull("href").orEmpty(), + key = a.attrAsRelativeUrl("href").substringAfter("category=").substringBefore("&"), source = source, ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/likemanga/LikeMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/likemanga/LikeMangaParser.kt index 10f3df59..8c359f1a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/likemanga/LikeMangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/likemanga/LikeMangaParser.kt @@ -23,44 +23,75 @@ internal abstract class LikeMangaParser( override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST) + + override val availableStates: Set = + EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED) + override val configKeyDomain = ConfigKey.Domain(domain) + 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) - append("/?act=search&f") - append("[sortby]".urlEncoded()) - append("=") - when (sortOrder) { - SortOrder.POPULARITY -> append("hot") - SortOrder.UPDATED -> append("lastest-chap") - SortOrder.NEWEST -> append("lastest-manga") - else -> append("lastest-chap") + append("/?act=search") + + when (filter) { + is MangaListFilter.Search -> { + append("&f") + append("[keyword]".urlEncoded()) + append("=") + append(filter.query.urlEncoded()) + } + + is MangaListFilter.Advanced -> { + append("&f") + append("[sortby]".urlEncoded()) + append("=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("hot") + SortOrder.UPDATED -> append("lastest-chap") + SortOrder.NEWEST -> append("lastest-manga") + else -> append("lastest-chap") + } + + if (filter.tags.isNotEmpty()) { + append("&f") + append("[genres]".urlEncoded()) + append("=") + filter.tags.oneOrThrowIfMany()?.let { + append(it.key) + } + } + + filter.states.oneOrThrowIfMany()?.let { + append("&f") + append("[status]".urlEncoded()) + append("=") + append( + when (it) { + MangaState.ONGOING -> "in-process" + MangaState.FINISHED -> "complete" + MangaState.PAUSED -> "pause" + else -> "all" + }, + ) + } + } + + null -> { + append("&f") + append("[sortby]".urlEncoded()) + append("=lastest-chap") + } } + if (page > 1) { append("&pageNum=") append(page) } - if (!tags.isNullOrEmpty()) { - append("&f") - append("[genres]".urlEncoded()) - append("=") - append(tag?.key.orEmpty()) - } - if (!query.isNullOrEmpty()) { - append("&f") - append("[keyword]".urlEncoded()) - append("=") - append(query.urlEncoded()) - } + } val doc = webClient.httpGet(url).parseHtml() return doc.select("div.card-body div.video").map { div -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt index 2e795f13..0c7d3665 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt @@ -29,6 +29,8 @@ internal abstract class MadthemeParser( SortOrder.RATING, ) + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) + protected open val listUrl = "search/" protected open val datePattern = "MMM dd, yyyy" @@ -52,35 +54,52 @@ internal abstract class MadthemeParser( "COMPLETED", ) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) - append("/$listUrl?sort=") - when (sortOrder) { - SortOrder.POPULARITY -> append("views") - SortOrder.UPDATED -> append("updated_at") - SortOrder.ALPHABETICAL -> append("name") // On some sites without tags or searches, the alphabetical option is empty. - SortOrder.NEWEST -> append("created_at") - SortOrder.RATING -> append("rating") - } - if (!query.isNullOrEmpty()) { - append("&q=") - append(query.urlEncoded()) - } + append('/') + append(listUrl) + when (filter) { - if (!tags.isNullOrEmpty()) { - for (tag in tags) { - append("&") - append("genre[]".urlEncoded()) - append("=") - append(tag.key) + is MangaListFilter.Search -> { + append("?sort=updated_at&q=") + append(filter.query.urlEncoded()) } + + is MangaListFilter.Advanced -> { + + append("?sort=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("views") + SortOrder.UPDATED -> append("updated_at") + SortOrder.ALPHABETICAL -> append("name") // On some sites without tags or searches, the alphabetical option is empty. + SortOrder.NEWEST -> append("created_at") + SortOrder.RATING -> append("rating") + } + if (filter.tags.isNotEmpty()) { + filter.tags.forEach { + append("&") + append("genre[]".urlEncoded()) + append("=") + append(it.key) + } + } + + filter.states.oneOrThrowIfMany()?.let { + append("&status=") + append( + when (it) { + MangaState.ONGOING -> "ongoing" + MangaState.FINISHED -> "completed" + else -> "all" + }, + ) + } + + } + + null -> append("?sort=updated_at") } append("&page=") @@ -117,7 +136,7 @@ internal abstract class MadthemeParser( override suspend fun getAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() - return doc.select("div.genres label.checkbox").mapNotNullToSet { checkbox -> + return doc.select("div.genres .checkbox").mapNotNullToSet { checkbox -> val key = checkbox.selectFirstOrThrow("input").attr("value") ?: return@mapNotNullToSet null val name = checkbox.selectFirstOrThrow("span.radio__label").text() MangaTag( diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/all/ManhuaScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/all/ManhuaScan.kt index 031861c5..341b086b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/all/ManhuaScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/all/ManhuaScan.kt @@ -7,44 +7,58 @@ import org.koitharu.kotatsu.parsers.site.madtheme.MadthemeParser import org.koitharu.kotatsu.parsers.util.* import java.util.Locale -@MangaSourceParser("MANHUASCAN", "ManhuaScan", "") +@MangaSourceParser("MANHUASCAN", "ManhuaScan.io", "") internal class ManhuaScan(context: MangaLoaderContext) : MadthemeParser(context, MangaSource.MANHUASCAN, "manhuascan.io") { override val sourceLocale: Locale = Locale.ENGLISH override val listUrl = "search" - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) append('/') append(listUrl) - append("?sort=") - when (sortOrder) { - SortOrder.POPULARITY -> append("views") - SortOrder.UPDATED -> append("updated_at") - SortOrder.ALPHABETICAL -> append("name") - SortOrder.NEWEST -> append("created_at") - SortOrder.RATING -> append("rating") - } + when (filter) { - if (!query.isNullOrEmpty()) { - append("&q=") - append(query.urlEncoded()) - } + is MangaListFilter.Search -> { + append("?sort=updated_at&q=") + append(filter.query.urlEncoded()) + } + + is MangaListFilter.Advanced -> { + + append("?sort=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("views") + SortOrder.UPDATED -> append("updated_at") + SortOrder.ALPHABETICAL -> append("name") + SortOrder.NEWEST -> append("created_at") + SortOrder.RATING -> append("rating") + } + if (filter.tags.isNotEmpty()) { + filter.tags.forEach { + append("&") + append("include[]".urlEncoded()) + append("=") + append(it.key) + } + } + + filter.states.oneOrThrowIfMany()?.let { + append("&status=") + append( + when (it) { + MangaState.ONGOING -> "ongoing" + MangaState.FINISHED -> "completed" + else -> "all" + }, + ) + } - if (!tags.isNullOrEmpty()) { - for (tag in tags) { - append("&") - append("include[]".urlEncoded()) - append("=") - append(tag.key) } + + null -> append("?sort=updated_at") } append("&page=") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/MangaBuddy.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/MangaBuddy.kt index e08c1bd8..23a8331b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/MangaBuddy.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/MangaBuddy.kt @@ -34,6 +34,5 @@ internal class MangaBuddy(context: MangaLoaderContext) : ) } return pages - } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/TooniTube.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/TooniTube.kt index a00a728d..34aa29ea 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/TooniTube.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/TooniTube.kt @@ -9,6 +9,5 @@ import org.koitharu.kotatsu.parsers.site.madtheme.MadthemeParser @MangaSourceParser("TOONITUBE", "TooniTube", "en", ContentType.HENTAI) internal class TooniTube(context: MangaLoaderContext) : MadthemeParser(context, MangaSource.TOONITUBE, "toonitube.com") { - override val selectDesc = "div.summary div.section-body p.content" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/ToonilyMe.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/ToonilyMe.kt index 4dd02782..ef25c4b7 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/ToonilyMe.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/ToonilyMe.kt @@ -9,6 +9,5 @@ import org.koitharu.kotatsu.parsers.site.madtheme.MadthemeParser @MangaSourceParser("TOONILY_ME", "Toonily.Me", "en", ContentType.HENTAI) internal class ToonilyMe(context: MangaLoaderContext) : MadthemeParser(context, MangaSource.TOONILY_ME, "toonily.me") { - override val selectDesc = "div.summary div.section-body p.content" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt index 3ec81dc2..7703908d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt @@ -26,6 +26,8 @@ internal abstract class Manga18Parser( SortOrder.ALPHABETICAL, ) + override val isMultipleTagsSupported = false + protected open val listUrl = "list-manga/" protected open val tagUrl = "manga-list/" protected open val datePattern = "dd-MM-yyyy" @@ -47,49 +49,53 @@ internal abstract class Manga18Parser( "Completed", ) - 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() -> { - append("/$listUrl") + append('/') + when (filter) { + + is MangaListFilter.Search -> { + append(listUrl) append(page.toString()) append("?search=") - append(query.urlEncoded()) - append("&") + append(filter.query.urlEncoded()) + append("&order_by=latest") } - !tags.isNullOrEmpty() -> { - append("/$tagUrl") - append(tag?.key.orEmpty()) - append("/") + is MangaListFilter.Advanced -> { + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append(tagUrl) + append(it.key) + append("/") + } + } else { + append(listUrl) + } + append(page.toString()) - append("?") + append("?order_by=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("views") + SortOrder.UPDATED -> append("lastest") + SortOrder.ALPHABETICAL -> append("name") + else -> append("latest") + } } - else -> { - append("/$listUrl") + null -> { + append(listUrl) append(page.toString()) - append("?") + append("?order_by=latest") } } - append("order_by=") - when (sortOrder) { - SortOrder.POPULARITY -> append("views") - SortOrder.UPDATED -> append("lastest") - SortOrder.ALPHABETICAL -> append("name") - else -> append("latest") - } } - val doc = webClient.httpGet(url).parseHtml() + return parseMangaList(webClient.httpGet(url).parseHtml()) + } + protected open fun parseMangaList(doc: Document): List { return doc.select("div.story_item").map { div -> val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/Hentai3zCc.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/Hentai3zCc.kt index d7016dd5..71a72bc8 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/Hentai3zCc.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/Hentai3zCc.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.parsers.site.manga18.en +import org.jsoup.nodes.Document import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.* @@ -10,51 +11,7 @@ import org.koitharu.kotatsu.parsers.util.* internal class Hentai3zCc(context: MangaLoaderContext) : Manga18Parser(context, MangaSource.HENTAI3ZCC, "hentai3z.cc") { - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() - val url = buildString { - append("https://") - append(domain) - val pages = page + 1 - when { - !query.isNullOrEmpty() -> { - append("/$listUrl") - append(pages.toString()) - append("?search=") - append(query.urlEncoded()) - append("&") - } - - !tags.isNullOrEmpty() -> { - append("/$tagUrl") - append(tag?.key.orEmpty()) - append("/") - append(pages.toString()) - append("?") - } - - else -> { - append("/$listUrl") - append(pages.toString()) - append("?") - } - } - append("order_by=") - when (sortOrder) { - SortOrder.POPULARITY -> append("views") - SortOrder.UPDATED -> append("lastest") - SortOrder.ALPHABETICAL -> append("name") - else -> append("latest") - } - } - val doc = webClient.httpGet(url).parseHtml() - - + override fun parseMangaList(doc: Document): List { return doc.select("div.story_item").map { div -> val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/es/Tumanhwas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/es/Tumanhwas.kt index ee087215..8c8101dc 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/es/Tumanhwas.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/es/Tumanhwas.kt @@ -9,7 +9,6 @@ import org.koitharu.kotatsu.parsers.site.manga18.Manga18Parser @MangaSourceParser("TUMANHWAS", "Tumanhwas", "es", ContentType.HENTAI) internal class Tumanhwas(context: MangaLoaderContext) : Manga18Parser(context, MangaSource.TUMANHWAS, "tumanhwas.club") { - override val selectTag = "div.item:contains(Géneros) div.info_value a" override val selectAlt = "div.item:contains(Títulos alternativos) div.info_value" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt index 0954d696..1c17d28d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.coroutineScope import org.jsoup.nodes.Document import org.koitharu.kotatsu.parsers.MangaLoaderContext 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 java.text.DateFormat @@ -21,9 +22,12 @@ internal abstract class MangaboxParser( SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, + SortOrder.ALPHABETICAL, ) - protected open val listUrl = "/genre-all" + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) + + protected open val listUrl = "/advanced_search" protected open val searchUrl = "/search/story/" protected open val datePattern = "MMM dd,yy" @@ -36,50 +40,64 @@ internal abstract class MangaboxParser( @JvmField protected val ongoing: Set = setOf( - "Ongoing", + "ongoing", ) @JvmField protected val finished: Set = setOf( - "Completed", + "completed", ) - 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(listUrl) + append("/?s=all") + when (filter) { - if (!query.isNullOrEmpty()) { - append(searchUrl) - append(query.urlEncoded()) - append("?page=") - append(page.toString()) - - } else if (!tags.isNullOrEmpty()) { - append("/") - append(tag?.key.orEmpty()) - append("/") - append(page.toString()) - } else { - append("$listUrl/") - if (page > 1) { - append(page.toString()) + is MangaListFilter.Search -> { + append("&keyw=") + append(filter.query.urlEncoded()) } - when (sortOrder) { - SortOrder.POPULARITY -> append("?type=topview") - SortOrder.UPDATED -> append("") - SortOrder.NEWEST -> append("?type=newest") - else -> append("") + + is MangaListFilter.Advanced -> { + + if (filter.tags.isNotEmpty()) { + append("&g_i=") + filter.tags.forEach { + append("_") + append(it.key) + append("_") + } + } + + filter.states.oneOrThrowIfMany()?.let { + append("&sts=") + append( + when (it) { + MangaState.ONGOING -> "ongoing" + MangaState.FINISHED -> "completed" + else -> "" + }, + ) + } + + append("&orby=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("topview") + SortOrder.UPDATED -> append("") + SortOrder.NEWEST -> append("newest") + SortOrder.ALPHABETICAL -> append("az") + else -> append("") + } } - } + null -> {} + } + append("&page=") + append(page.toString()) } val doc = webClient.httpGet(url).parseHtml() @@ -109,7 +127,8 @@ internal abstract class MangaboxParser( override suspend fun getAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() - return doc.select(selectTagMap).mapNotNullToSet { a -> + val tags = doc.select(selectTagMap).drop(1) // remove all tags + return tags.mapNotNullToSet { a -> val key = a.attr("href").removeSuffix('/').substringAfterLast('/') val name = a.attr("title").replace(" Manga", "") MangaTag( @@ -129,25 +148,18 @@ internal abstract class MangaboxParser( 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 stateDiv = doc.select(selectState).text() - val state = stateDiv.let { - when (it) { + when (it.lowercase()) { in ongoing -> MangaState.ONGOING in finished -> MangaState.FINISHED else -> null } } - val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "") - val aut = doc.body().select(selectAut).eachText().joinToString() - manga.copy( tags = doc.body().select(selectTag).mapNotNullToSet { a -> MangaTag( diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangabat.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangabat.kt index a4e6b1da..23efaaba 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangabat.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangabat.kt @@ -9,13 +9,7 @@ import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser @MangaSourceParser("HMANGABAT", "MangaBat", "en") internal class Mangabat(context: MangaLoaderContext) : MangaboxParser(context, MangaSource.HMANGABAT) { - override val configKeyDomain = ConfigKey.Domain("h.mangabat.com", "readmangabat.com") - override val otherDomain = "readmangabat.com" - - override val searchUrl = "/search/manga/" - - override val listUrl = "/manga-list-all" override val selectTagMap = "div.panel-category p.pn-category-row:not(.pn-category-row-border) a" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt index 51eb5e02..70241a79 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt @@ -6,6 +6,7 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.model.MangaListFilter import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaState import org.koitharu.kotatsu.parsers.model.MangaTag @@ -13,73 +14,87 @@ import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser import org.koitharu.kotatsu.parsers.util.* +import java.util.EnumSet @MangaSourceParser("MANGAIRO", "MangaIro", "en") internal class Mangairo(context: MangaLoaderContext) : MangaboxParser(context, MangaSource.MANGAIRO) { - override val configKeyDomain = ConfigKey.Domain("w.mangairo.com", "chap.mangairo.com") - override val otherDomain = "chap.mangairo.com" - override val datePattern = "MMM-dd-yy" override val listUrl = "/manga-list" override val searchUrl = "/list/search/" - override val selectDesc = "div#story_discription p" override val selectState = "ul.story_info_right li:contains(Status) a" override val selectAlt = "ul.story_info_right li:contains(Alter) h2" override val selectAut = "ul.story_info_right li:contains(Author) a" override val selectTag = "ul.story_info_right li:contains(Genres) a" - override val selectChapter = "div.chapter_list li" override val selectDate = "p" - override val selectPage = "div.panel-read-story img" - - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override val availableSortOrders: Set = EnumSet.of( + SortOrder.UPDATED, + SortOrder.POPULARITY, + SortOrder.NEWEST, + ) + override val isMultipleTagsSupported = false + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) + when (filter) { - if (!query.isNullOrEmpty()) { - append(searchUrl) - append(query.urlEncoded()) - append("?page=") - append(page.toString()) - - - } else { + is MangaListFilter.Search -> { + append(searchUrl) + append(filter.query.urlEncoded()) + append("?page=") + } - append("$listUrl/") + is MangaListFilter.Advanced -> { + append(listUrl) + append("/type-") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("topview") + SortOrder.UPDATED -> append("latest") + SortOrder.NEWEST -> append("newest") + else -> append("latest") + } - append("/type-") - when (sortOrder) { - SortOrder.POPULARITY -> append("topview") - SortOrder.UPDATED -> append("latest") - SortOrder.NEWEST -> append("newest") - else -> append("latest") + append("/ctg-") + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append(it.key) + } + } else { + append("all") + } + + append("/state-") + if (filter.states.isNotEmpty()) { + filter.states.oneOrThrowIfMany()?.let { + append( + when (it) { + MangaState.ONGOING -> "ongoing" + MangaState.FINISHED -> "completed" + else -> "all" + }, + ) + } + } else { + append("all") + } + + append("/page-") } - if (!tags.isNullOrEmpty()) { - append("/ctg-") - append(tag?.key.orEmpty()) - } else { - append("/ctg-all") + null -> { + append(listUrl) + append("/type-latest/ctg-all/state-all/page-") } - append("/state-all/page-") - append(page.toString()) } + append(page.toString()) } - val doc = webClient.httpGet(url).parseHtml() - return doc.select("div.story-item").map { div -> val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( @@ -115,13 +130,9 @@ internal class Mangairo(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 stateDiv = doc.select(selectState).text() - val state = stateDiv.let { when (it) { in ongoing -> MangaState.ONGOING @@ -131,9 +142,7 @@ internal class Mangairo(context: MangaLoaderContext) : } val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "") - val aut = doc.body().select(selectAut).eachText().joinToString() - manga.copy( tags = doc.body().select(selectTag).mapNotNullToSet { a -> MangaTag( @@ -151,6 +160,4 @@ internal class Mangairo(context: MangaLoaderContext) : isNsfw = manga.isNsfw, ) } - - } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt index 99324654..ec3c171a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt @@ -8,54 +8,69 @@ import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser import org.koitharu.kotatsu.parsers.util.* import java.text.SimpleDateFormat +import java.util.EnumSet -@MangaSourceParser("MANGAKAKALOT", "Mangakakalot", "en") +@MangaSourceParser("MANGAKAKALOT", "Mangakakalot.com", "en") internal class Mangakakalot(context: MangaLoaderContext) : MangaboxParser(context, MangaSource.MANGAKAKALOT) { - override val configKeyDomain = ConfigKey.Domain("mangakakalot.com", "chapmanganato.com") - + override val availableSortOrders: Set = EnumSet.of( + SortOrder.UPDATED, + SortOrder.POPULARITY, + SortOrder.NEWEST, + ) + override val isMultipleTagsSupported = false override val otherDomain = "chapmanganato.com" - override val listUrl = "/manga_list" - 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) { - if (!query.isNullOrEmpty()) { - append(searchUrl) - append(query.replace(" ", "_").urlEncoded()) - append("?page=") - append(page.toString()) - - } else { - append("$listUrl/") - when (sortOrder) { - SortOrder.POPULARITY -> append("?type=topview") - SortOrder.UPDATED -> append("?type=latest") - SortOrder.NEWEST -> append("?type=newest") - else -> append("?type=latest") + is MangaListFilter.Search -> { + append(searchUrl) + append(filter.query.urlEncoded()) + append("?page=") } - if (!tags.isNullOrEmpty()) { - append("&category=") - append(tag?.key.orEmpty()) - } else { - append("&category=all") + + is MangaListFilter.Advanced -> { + append(listUrl) + append("?type=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("topview") + SortOrder.UPDATED -> append("latest") + SortOrder.NEWEST -> append("newest") + else -> append("latest") + } + if (filter.tags.isNotEmpty()) { + append("&category=") + filter.tags.oneOrThrowIfMany()?.let { + append(it.key) + } + } + + filter.states.oneOrThrowIfMany()?.let { + append("&state=") + append( + when (it) { + MangaState.ONGOING -> "ongoing" + MangaState.FINISHED -> "completed" + else -> "all" + }, + ) + } + + append("&page=") } - append("&state=all&page=") - append(page) + null -> { + append(listUrl) + append("?type=latest&page=") + } } - - + append(page.toString()) } val doc = webClient.httpGet(url).parseHtml() @@ -81,8 +96,21 @@ internal class Mangakakalot(context: MangaLoaderContext) : } } - override suspend fun getChapters(doc: Document): List { + override suspend fun getAvailableTags(): Set { + val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() + val tags = doc.select("ul.tag li a").drop(1) + return tags.mapNotNullToSet { a -> + val key = a.attr("href").substringAfterLast("category=").substringBefore("&") + val name = a.attr("title").replace(" Manga", "") + MangaTag( + key = key, + title = name, + source = source, + ) + } + } + override suspend fun getChapters(doc: Document): List { return doc.body().select(selectChapter).mapChapters(reversed = true) { i, li -> val a = li.selectFirstOrThrow("a") val href = a.attrAsRelativeUrl("href") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt index 0531c005..2c545e17 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt @@ -1,11 +1,14 @@ package org.koitharu.kotatsu.parsers.site.mangabox.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.config.ConfigKey import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser import org.koitharu.kotatsu.parsers.util.* +import java.util.EnumSet @MangaSourceParser("MANGAKAKALOTTV", "Mangakakalot.tv", "en") internal class MangakakalotTv(context: MangaLoaderContext) : @@ -14,40 +17,61 @@ internal class MangakakalotTv(context: MangaLoaderContext) : override val configKeyDomain = ConfigKey.Domain("ww6.mangakakalot.tv") override val searchUrl = "/search/" override val listUrl = "/manga_list" + override val availableSortOrders: Set = EnumSet.of( + SortOrder.UPDATED, + SortOrder.POPULARITY, + SortOrder.NEWEST, + ) + 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(searchUrl) - append(query.urlEncoded()) - append("?page=") - append(page.toString()) - } else { - append("$listUrl/") - append("?type=") - when (sortOrder) { - SortOrder.POPULARITY -> append("topview") - SortOrder.UPDATED -> append("latest") - SortOrder.NEWEST -> append("newest") - else -> append("latest") - } - if (!tags.isNullOrEmpty()) { - append("&category=") - append(tag?.key.orEmpty()) + when (filter) { + + is MangaListFilter.Search -> { + append(searchUrl) + append(filter.query.urlEncoded()) + append("?page=") } - if (page > 1) { + + is MangaListFilter.Advanced -> { + append(listUrl) + append("?type=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("topview") + SortOrder.UPDATED -> append("latest") + SortOrder.NEWEST -> append("newest") + else -> append("latest") + } + if (filter.tags.isNotEmpty()) { + append("&category=") + filter.tags.oneOrThrowIfMany()?.let { + append(it.key) + } + } + + filter.states.oneOrThrowIfMany()?.let { + append("&state=") + append( + when (it) { + MangaState.ONGOING -> "Ongoing" + MangaState.FINISHED -> "Completed" + else -> "all" + }, + ) + } + append("&page=") - append(page.toString()) + } + + null -> { + append(listUrl) + append("?type=latest&page=") } } + append(page.toString()) } val doc = webClient.httpGet(url).parseHtml() return doc.select("div.list-truyen-item-wrap").ifEmpty { @@ -71,6 +95,38 @@ internal class MangakakalotTv(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 stateDiv = doc.select(selectState).text().replace("Status : ", "") + val state = stateDiv.let { + when (it.lowercase()) { + in ongoing -> MangaState.ONGOING + in finished -> MangaState.FINISHED + else -> null + } + } + val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "") + val aut = doc.body().select(selectAut).eachText().joinToString() + manga.copy( + tags = doc.body().select(selectTag).mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").substringAfterLast("category=").substringBefore("&"), + title = a.text().toTitleCase(), + source = source, + ) + }, + description = desc, + altTitle = alt, + author = aut, + state = state, + chapters = chaptersDeferred.await(), + isNsfw = manga.isNsfw, + ) + } + override val selectTagMap = "ul.tag li a" override suspend fun getAvailableTags(): Set { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Manganato.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Manganato.kt index c9f09346..3022f7d4 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Manganato.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Manganato.kt @@ -9,9 +9,6 @@ import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser @MangaSourceParser("MANGANATO", "Manganato", "en") internal class Manganato(context: MangaLoaderContext) : MangaboxParser(context, MangaSource.MANGANATO) { - override val configKeyDomain = ConfigKey.Domain("chapmanganato.com", "manganato.com") - override val otherDomain = "chapmanganato.com" - } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt index a28442d6..33d402d6 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt @@ -32,6 +32,9 @@ internal abstract class MangaReaderParser( override val availableSortOrders: Set get() = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.ALPHABETICAL, SortOrder.NEWEST) + override val availableStates: Set + get() = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED) + protected open val listUrl = "/manga" protected open val datePattern = "MMMM d, yyyy" protected open val isNetShieldProtected = false @@ -40,52 +43,62 @@ internal abstract class MangaReaderParser( private val mutex = Mutex() protected open var lastSearchPage = 1 - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - if (!query.isNullOrEmpty()) { - if (page > lastSearchPage) { - return emptyList() - } - - val url = buildString { - append("https://") - append(domain) - append("/page/") - append(page) - append("/?s=") - append(query.urlEncoded()) - } - - val docs = webClient.httpGet(url).parseHtml() - lastSearchPage = docs.selectFirst(".pagination .next") - ?.previousElementSibling() - ?.text()?.toIntOrNull() ?: 1 - return parseMangaList(docs) - } - - val sortQuery = when (sortOrder) { - SortOrder.ALPHABETICAL -> "title" - SortOrder.NEWEST -> "latest" - SortOrder.POPULARITY -> "popular" - SortOrder.UPDATED -> "update" - else -> "" - } - val tagKey = "genre[]".urlEncoded() - val tagQuery = - if (tags.isNullOrEmpty()) "" else tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" } + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) - append(listUrl) - append("/?order=") - append(sortQuery) - append(tagQuery) - append("&page=") - append(page) + + when (filter) { + + is MangaListFilter.Search -> { + append("/page/") + append(page.toString()) + append("/?s=") + append(filter.query.urlEncoded()) + } + + is MangaListFilter.Advanced -> { + append(listUrl) + + append("/?order=") + append( + when (filter.sortOrder) { + SortOrder.ALPHABETICAL -> "title" + SortOrder.NEWEST -> "latest" + SortOrder.POPULARITY -> "popular" + SortOrder.UPDATED -> "update" + else -> "" + }, + ) + + val tagKey = "genre[]".urlEncoded() + val tagQuery = + if (filter.tags.isEmpty()) "" + else filter.tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" } + append(tagQuery) + + if (filter.states.isNotEmpty()) { + filter.states.oneOrThrowIfMany()?.let { + append("&status=") + when (it) { + MangaState.ONGOING -> append("ongoing") + MangaState.FINISHED -> append("completed") + MangaState.PAUSED -> append("hiatus") + else -> append("") + } + } + } + + append("&page=") + append(page.toString()) + } + + null -> { + append(listUrl) + append("/?order=update&page=") + append(page.toString()) + } + } } return parseMangaList(webClient.httpGet(url).parseHtml()) }