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 33d402d6..861de4eb 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 @@ -41,7 +41,6 @@ internal abstract class MangaReaderParser( private var tagCache: ArrayMap? = null private val mutex = Mutex() - protected open var lastSearchPage = 1 override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt index b4d6918c..8d454c84 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt @@ -25,7 +25,7 @@ internal abstract class OtakuSanctuaryParser( SortOrder.NEWEST, ) - protected open val listeurl = "Manga/Newest" + protected open val listUrl = "Manga/Newest" protected open val datePattern = "dd/MM/yyyy" protected open val lang = "" @@ -40,60 +40,71 @@ internal abstract class OtakuSanctuaryParser( "Done", ) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() - - val doc = if (!tags.isNullOrEmpty()) { - val url = buildString { - - append("https://$domain/Genre/MangaGenrePartial?id=") - append(tag?.key.orEmpty()) - append("&lang=$lang") - append("&offset=") - append(page * pageSize) - append("&pagesize=$pageSize") - } + override val isMultipleTagsSupported = false + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val doc = + when (filter) { + + is MangaListFilter.Search -> { + if (page > 1) { + return emptyList() + } + val url = buildString { + append("https://") + append(domain) + append("/Home/Search?search=") + append(filter.query.urlEncoded()) + } + webClient.httpGet(url).parseHtml().requireElementById("collection-manga") + } - webClient.httpGet(url).parseHtml() - } else if (!query.isNullOrEmpty()) { - if (page > 1) { - return emptyList() - } + is MangaListFilter.Advanced -> { + + if (filter.tags.isNotEmpty()) { + val url = buildString { + append("https://") + append(domain) + append("/Genre/MangaGenrePartial?id=") + filter.tags.oneOrThrowIfMany()?.let { + append(it.key) + } + append("&lang=") + append(lang) + append("&offset=") + append(page * pageSize) + append("&pagesize=") + append(pageSize) + } + webClient.httpGet(url).parseHtml() + + } else { + val payload = HashMap() + payload["Lang"] = lang + payload["Page"] = page.toString() + payload["Type"] = "Include" + when (filter.sortOrder) { + SortOrder.NEWEST -> payload["Dir"] = "CreatedDate" + SortOrder.UPDATED -> payload["Dir"] = "NewPostedDate" + else -> payload["Dir"] = "NewPostedDate" + } + webClient.httpPost("https://$domain/$listUrl", payload).parseHtml() + } - val url = buildString { - append("https://$domain/Home/Search?search=") - append(query.urlEncoded()) - } + } - webClient.httpGet(url).parseHtml() - } else { - val url = "https://$domain/$listeurl" - val payload = HashMap() - payload["Lang"] = lang - payload["Page"] = page.toString() - payload["Type"] = "Include" - when (sortOrder) { - SortOrder.NEWEST -> payload["Dir"] = "CreatedDate" - SortOrder.UPDATED -> payload["Dir"] = "NewPostedDate" - else -> payload["Dir"] = "NewPostedDate" + null -> { + val payload = HashMap() + payload["Lang"] = lang + payload["Page"] = page.toString() + payload["Type"] = "Include" + payload["Dir"] = "NewPostedDate" + webClient.httpPost("https://$domain/$listUrl", payload).parseHtml() + } } - webClient.httpPost(url, payload).parseHtml() - } - - - val mangas = if (!query.isNullOrEmpty()) { - doc.requireElementById("collection-manga").select("div.picture-card") - } else { - doc.select("div.picture-card") - } - return mangas.map { div -> + return doc.select("div.picture-card").map { div -> val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( id = generateUid(href), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/Bakai.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/Bakai.kt index aee33c96..3ecd17ad 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/Bakai.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/Bakai.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.parsers.site.pt import okhttp3.Headers +import org.jsoup.nodes.Document import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.PagedMangaParser @@ -16,79 +17,102 @@ internal class Bakai(context: MangaLoaderContext) : PagedMangaParser(context, Ma override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) override val configKeyDomain = ConfigKey.Domain("bakai.org") override val headers: Headers = Headers.Builder() - .add("User-Agent", UserAgents.CHROME_MOBILE) + .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("&quick=1&type=cms_records1&page=") - append(page.toString()) - } else if (!tags.isNullOrEmpty()) { - append("/search/?tags=") - for (tag in tags) { - append(tag.key) - append(",") + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + + when (filter) { + + is MangaListFilter.Search -> { + val url = buildString { + append("https://") + append(domain) + append("/search1/?q=") + append(filter.query.urlEncoded()) + append("&quick=1&type=cms_records1&updated_after=any&sortby=newest&page=") + append(page.toString()) } - append("&quick=1&type=cms_records1&page=") - append(page.toString()) - } else { - append("/hentai/") - append("page/") - append(page.toString()) + return parseMangaListQueryOrTags(webClient.httpGet(url).parseHtml()) } - } - val doc = webClient.httpGet(url).parseHtml() - if (!tags.isNullOrEmpty() or !query.isNullOrEmpty()) { - return doc.select("ol.ipsStream li.ipsStreamItem") - .map { div -> - val href = div.selectFirstOrThrow("div.ipsStreamItem_snippet a").attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - title = div.selectFirstOrThrow("h2.ipsStreamItem_title").text(), - altTitle = null, - url = href, - publicUrl = href.toAbsoluteUrl(domain), - rating = RATING_UNKNOWN, - isNsfw = true, - coverUrl = div.selectFirstOrThrow("span.ipsThumb img").attrAsAbsoluteUrl("src"), - tags = setOf(), - state = null, - author = null, - source = source, - ) + + is MangaListFilter.Advanced -> { + if (filter.tags.isNotEmpty()) { + val url = buildString { + append("https://") + append(domain) + append("/search1/?tags=") + append(filter.tags.joinToString(separator = ",") { it.key }) + append("&updated_after=any&sortby=newest&search_and_or=and&page=") + append(page.toString()) + } + return parseMangaListQueryOrTags(webClient.httpGet(url).parseHtml()) + } else { + val url = buildString { + append("https://") + append(domain) + append("/hentai/page/") + append(page.toString()) + } + return parseMangaList(webClient.httpGet(url).parseHtml()) } - } else { - return doc.select("section.ipsType_normal li.ipsGrid_span4") - .map { div -> - val href = div.selectFirstOrThrow("h2.ipsType_pageTitle a").attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - title = div.selectFirstOrThrow("h2.ipsType_pageTitle").text(), - altTitle = null, - url = href, - publicUrl = href.toAbsoluteUrl(domain), - rating = RATING_UNKNOWN, - isNsfw = true, - coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"), - tags = setOf(), - state = null, - author = null, - source = source, - ) + } + + null -> { + val url = buildString { + append("https://") + append(domain) + append("/hentai/page/") + append(page.toString()) } + return parseMangaList(webClient.httpGet(url).parseHtml()) + } } } + private fun parseMangaList(doc: Document): List { + return doc.select("section.ipsType_normal li.ipsGrid_span4") + .map { div -> + val href = div.selectFirstOrThrow("h2.ipsType_pageTitle a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + title = div.selectFirstOrThrow("h2.ipsType_pageTitle").text(), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + isNsfw = true, + coverUrl = div.selectFirst("img")?.src().orEmpty(), + tags = setOf(), + state = null, + author = null, + source = source, + ) + } + } + + private fun parseMangaListQueryOrTags(doc: Document): List { + return doc.select("ol.ipsStream li.ipsStreamItem") + .mapNotNull { div -> + val href = + div.selectFirst(".ipsStreamItem_snippet a")?.attrAsRelativeUrl("href") ?: return@mapNotNull null + Manga( + id = generateUid(href), + title = div.selectFirstOrThrow("h2.ipsStreamItem_title").text(), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + isNsfw = true, + coverUrl = div.selectFirst(".ipsStreamItem_snippet img")?.src().orEmpty(), + tags = setOf(), + state = null, + author = null, + source = source, + ) + } + } + override suspend fun getAvailableTags(): Set { val doc = webClient.httpGet("https://$domain").parseHtml() return doc.requireElementById("elNavigation_17_menu").select("li.ipsMenu_item a").mapNotNullToSet { a -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt index 688b63bb..f51e1d62 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt @@ -17,51 +17,78 @@ internal class BrMangas(context: MangaLoaderContext) : PagedMangaParser(context, override val configKeyDomain = ConfigKey.Domain("www.brmangas.net") + override val isMultipleTagsSupported = false + override val headers: Headers = Headers.Builder() .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("category/") - append(tag?.key.orEmpty()) - if (page > 1) { - append("/page/$page/") - } - } else if (!query.isNullOrEmpty()) { - if (page > 1) { - append("/page/$page/") + append("https://") + append(domain) + append('/') + when (filter) { + is MangaListFilter.Search -> { + if (page > 1) { + append("/page/$page/") + } + append("/?s=") + append(filter.query.urlEncoded()) } - append("/?s=") - append(query.urlEncoded()) - } else { - when (sortOrder) { - SortOrder.POPULARITY -> append("/") - SortOrder.UPDATED -> append("manga/") - else -> append("manga/") + + is MangaListFilter.Advanced -> { + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append("category/") + append(it.key) + if (page > 1) { + append("/page/$page/") + } + } + } else { + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("/") + SortOrder.UPDATED -> append("manga/") + else -> append("manga/") + } + if (page > 1) { + append("page/$page/") + } + } + } - if (page > 1) { - append("page/$page/") + + null -> { + append("manga/") + if (page > 1) { + append("page/$page/") + } } } + } val doc = webClient.httpGet(url).parseHtml() - val item = if (sortOrder == SortOrder.POPULARITY) { - doc.select("div.listagem")[1].select("div.item") // To remove the 6 mangas updated on the home page - } else { - doc.select("div.listagem div.item") - } + val item = + when (filter) { + is MangaListFilter.Search -> { + doc.select("div.listagem div.item") + } + + is MangaListFilter.Advanced -> { + if (filter.sortOrder == SortOrder.POPULARITY && filter.tags.isEmpty()) { + doc.select("div.listagem")[1].select("div.item") // To remove the 6 mangas updated on the home page + } else { + doc.select("div.listagem div.item") + } + } + + null -> doc.select("div.listagem div.item") + + } return item.map { div -> val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerMangaOnline.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerMangaOnline.kt index 2f548ea3..9a8e824f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerMangaOnline.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerMangaOnline.kt @@ -7,7 +7,6 @@ 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.lang.IllegalArgumentException import java.text.SimpleDateFormat import java.util.* @@ -18,30 +17,48 @@ class LerMangaOnline(context: MangaLoaderContext) : PagedMangaParser(context, Ma override val configKeyDomain = ConfigKey.Domain("lermangaonline.com.br") - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - if (!query.isNullOrEmpty()) { - throw IllegalArgumentException("Search is not supported by this source") - } - val tag = tags.oneOrThrowIfMany() + override val isMultipleTagsSupported = false + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val url = buildString { append("https://") append(domain) - append("/") - if (!tags.isNullOrEmpty()) { - append(tag?.key.orEmpty()) - append("/") - } - if (page > 1) { - append("page/") - append(page) - append("/") + append('/') + when (filter) { + is MangaListFilter.Search -> { + if (page > 1) { + append("page/") + append(page.toString()) + append("/") + } + append("?s=") + append(filter.query.urlEncoded()) + } + + is MangaListFilter.Advanced -> { + filter.tags.oneOrThrowIfMany()?.let { + append(it.key) + append('/') + } + + if (page > 1) { + append("page/") + append(page.toString()) + append('/') + } + } + + null -> { + if (page > 1) { + append("page/") + append(page.toString()) + append('/') + } + } } } + return parseManga(webClient.httpGet(url).parseHtml()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MangaOnline.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MangaOnline.kt index dd41b478..c7f4a735 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MangaOnline.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MangaOnline.kt @@ -16,37 +16,40 @@ class MangaOnline(context: MangaLoaderContext) : PagedMangaParser(context, Manga override val configKeyDomain = ConfigKey.Domain("mangaonline.biz") - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override val isMultipleTagsSupported = false + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val url = buildString { append("https://") append(domain) - when { - !tags.isNullOrEmpty() -> { - append("/genero/") - append(tag?.key.orEmpty()) - append("/") - } + when (filter) { - !query.isNullOrEmpty() -> { + is MangaListFilter.Search -> { append("/search/") - append(query.urlEncoded()) - append("/") + append(filter.query.urlEncoded()) + append('/') } - else -> { - append("/manga/") + is MangaListFilter.Advanced -> { + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append("/genero/") + append(it.key) + append('/') + } + } else { + append("/manga/") + } } + + null -> append("/manga/") + } if (page > 1) { append("page/") - append(page) - append("/") + append(page.toString()) + append('/') } } val doc = webClient.httpGet(url).parseHtml() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt index 55564083..3dc110c3 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt @@ -8,7 +8,6 @@ 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 org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed import java.text.DateFormat import java.text.SimpleDateFormat import java.util.* @@ -19,44 +18,71 @@ class YugenMangas(context: MangaLoaderContext) : PagedMangaParser(context, Manga override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL, SortOrder.UPDATED) override val configKeyDomain = ConfigKey.Domain("yugenmangas.net.br") - 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 json = - if (!query.isNullOrEmpty()) { - if (page > 1) { - return emptyList() - } - val url = buildString { - append("https://api.") - append(domain) - append("/api/series/list/?query=") - append(query.urlEncoded()) + when (filter) { + + is MangaListFilter.Search -> { + + val url = buildString { + append("https://api.") + append(domain) + append("/api/series/list/?query=") + append(filter.query.urlEncoded()) + } + webClient.httpGet(url).parseJsonArray() } - webClient.httpGet(url).parseJsonArray() - } else { - if (page > 1) { - return emptyList() + + is MangaListFilter.Advanced -> { + + + if (filter.sortOrder == SortOrder.UPDATED) { + val url = buildString { + append("https://api.") + append(domain) + append("/api/latest_updates/") + } + webClient.httpGet(url).parseJsonArray() + } else { + val url = buildString { + append("https://api.") + append(domain) + append("/api/all_series/") + } + webClient.httpGet(url).parseJson().getJSONArray("series") + } + } - val url = buildString { - append("https://api.") - append(domain) - append("/api/all_series/?page=1") + + null -> { + val url = buildString { + append("https://api.") + append(domain) + append("/api/latest_updates/") + } + webClient.httpGet(url).parseJsonArray() } - webClient.httpGet(url).parseJson().getJSONArray("series") } return json.mapJSON { j -> val slug = j.getString("slug") + val cover = if (!j.getString("cover").startsWith("https://")) { + // Some covers don't have the "/" so we ensure that the URL will be spelled correctly. + "https://$domain/media/" + j.getString("cover").removePrefix("/") + } else { + j.getString("cover") + } Manga( id = generateUid(slug), url = slug, publicUrl = slug, title = j.getString("name"), - coverUrl = j.getString("cover"), + coverUrl = cover, altTitle = null, rating = RATING_UNKNOWN, tags = emptySet(), @@ -90,7 +116,7 @@ class YugenMangas(context: MangaLoaderContext) : PagedMangaParser(context, Manga else -> null } }, - chapters = chapterManga.mapJSONIndexed { i, j -> + chapters = chapterManga.mapJSON { j -> val url = "https://api.$domain/api/serie/${manga.url}/chapter/${j.getString("slug")}/images/imgs/" MangaChapter( id = generateUid(url), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt index 3af8d4ac..ebbf48ed 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt @@ -23,6 +23,10 @@ internal abstract class SinmhParser( SortOrder.POPULARITY, ) + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) + + override val isMultipleTagsSupported = false + protected open val searchUrl = "search/" protected open val listUrl = "list/" @@ -41,40 +45,55 @@ internal abstract class SinmhParser( "已完结", ) - 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("/$searchUrl?keywords=") - append(query.urlEncoded()) + append('/') + when (filter) { + + is MangaListFilter.Search -> { + append(searchUrl) + append("?keywords=") + append(filter.query.urlEncoded()) append("&page=") append(page) } - !tags.isNullOrEmpty() -> { - append("/$listUrl") - append(tag?.key.orEmpty()) - append("/$page/") - } + is MangaListFilter.Advanced -> { + append(listUrl) + filter.tags.oneOrThrowIfMany()?.let { + append(it.key) + } - else -> { + filter.states.oneOrThrowIfMany()?.let { + append( + when (it) { + MangaState.ONGOING -> "-lianzai" + MangaState.FINISHED -> "-wanjie" + else -> "" + }, + ) + } - append("/$listUrl") - when (sortOrder) { + if (filter.tags.isNotEmpty() && filter.states.isNotEmpty()) { + append('/') + } + + when (filter.sortOrder) { SortOrder.POPULARITY -> append("click/") SortOrder.UPDATED -> append("update/") - else -> append("") + else -> append("/") } - append("?page=") - append(page) + append(page.toString()) + append('/') + } + + null -> { + append(listUrl) + append("update/") + append(page.toString()) + append('/') } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/MangaAy.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/MangaAy.kt index d55cb8a3..8e3f0a6d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/MangaAy.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/MangaAy.kt @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.parsers.site.tr import androidx.collection.ArrayMap import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import org.jsoup.nodes.Document import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.PagedMangaParser @@ -19,75 +20,111 @@ class MangaAy(context: MangaLoaderContext) : PagedMangaParser(context, MangaSour override val configKeyDomain = ConfigKey.Domain("manga-ay.com") - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() - if (!query.isNullOrEmpty() || !tags.isNullOrEmpty()) { - if (page > 1) { - return emptyList() - } - val url = "https://$domain/arama" - val doc = webClient.httpPost( - url, - mapOf( - "title" to query?.urlEncoded().orEmpty(), - "genres" to tag?.key.orEmpty(), - ), - ).parseHtml() - return doc.select(".table tr").map { tr -> - val a = tr.selectFirstOrThrow("a") - val href = a.attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - url = href, - publicUrl = a.attrAsAbsoluteUrl("href"), - title = a.text(), - coverUrl = "", - altTitle = null, - rating = RATING_UNKNOWN, - tags = emptySet(), - description = null, - state = null, - author = null, - isNsfw = isNsfwSource, - source = source, + override val isMultipleTagsSupported = false + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + + when (filter) { + is MangaListFilter.Search -> { + if (page > 1) { + return emptyList() + } + return parseMangaListQueryOrTags( + webClient.httpPost( + "https://$domain/arama", + mapOf("title" to filter.query.urlEncoded(), "genres" to ""), + ).parseHtml(), ) } - } else { - val url = buildString { - append("https://") - append(domain) - append("/seriler") - if (page > 1) { - append("/") - append(page) + + is MangaListFilter.Advanced -> { + + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + if (page > 1) { + return emptyList() + } + return parseMangaListQueryOrTags( + webClient.httpPost( + "https://$domain/arama", + mapOf("title" to "", "genres" to it.key), + ).parseHtml(), + ) + } + } else { + val url = buildString { + append("https://") + append(domain) + append("/seriler") + if (page > 1) { + append("/") + append(page) + } + } + return parseMangaList(webClient.httpGet(url).parseHtml()) } + } - val doc = webClient.httpGet(url).parseHtml().requireElementById("ecommerce-products") - return doc.select(".card").map { div -> - val a = div.selectFirstOrThrow("a") - val href = a.attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - url = href, - publicUrl = a.attrAsAbsoluteUrl("href"), - title = div.selectLastOrThrow(".item-name").text(), - coverUrl = div.selectFirst("img")?.src().orEmpty(), - altTitle = null, - rating = RATING_UNKNOWN, - tags = emptySet(), - description = null, - state = null, - author = null, - isNsfw = isNsfwSource, - source = source, - ) + + null -> { + val url = buildString { + append("https://") + append(domain) + append("/seriler") + if (page > 1) { + append("/") + append(page) + } + } + return parseMangaList(webClient.httpGet(url).parseHtml()) } } + + return emptyList() + } + + private fun parseMangaList(doc: Document): List { + return doc.requireElementById("ecommerce-products").select(".card").map { div -> + val a = div.selectFirstOrThrow("a") + val href = a.attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + url = href, + publicUrl = a.attrAsAbsoluteUrl("href"), + title = div.selectLastOrThrow(".item-name").text(), + coverUrl = div.selectFirst("img")?.src().orEmpty(), + altTitle = null, + rating = RATING_UNKNOWN, + tags = emptySet(), + description = null, + state = null, + author = null, + isNsfw = isNsfwSource, + source = source, + ) + } + } + + private fun parseMangaListQueryOrTags(doc: Document): List { + return doc.select(".table tr").map { tr -> + val a = tr.selectFirstOrThrow("a") + val href = a.attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + url = href, + publicUrl = a.attrAsAbsoluteUrl("href"), + title = a.text(), + coverUrl = "", + altTitle = null, + rating = RATING_UNKNOWN, + tags = emptySet(), + description = null, + state = null, + author = null, + isNsfw = isNsfwSource, + source = source, + ) + } } private var tagCache: ArrayMap? = null diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt index 381331bf..5c686374 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt @@ -15,20 +15,29 @@ internal class SadScans(context: MangaLoaderContext) : MangaParser(context, Mang override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) override val configKeyDomain = ConfigKey.Domain("sadscans.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(), @@ -64,7 +73,7 @@ internal class SadScans(context: MangaLoaderContext) : MangaParser(context, Mang chapters = doc.select(".chap-section .chap") .mapChapters(reversed = true) { i, div -> val a = div.selectFirstOrThrow("a") - val url = a.attrAsRelativeUrl("href").toAbsoluteUrl(domain) + val url = "/" + a.attrAsRelativeUrl("href").toAbsoluteUrl(domain) MangaChapter( id = generateUid(url), name = a.text(), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt index 1d16a585..0d732396 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt @@ -1,11 +1,13 @@ package org.koitharu.kotatsu.parsers.site.tr +import org.jsoup.nodes.Document 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 java.lang.IllegalArgumentException import java.text.DateFormat import java.text.SimpleDateFormat import java.util.* @@ -16,100 +18,134 @@ class TrWebtoon(context: MangaLoaderContext) : override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("trwebtoon.com") - override val availableSortOrders: Set - get() = EnumSet.of(SortOrder.POPULARITY, SortOrder.ALPHABETICAL, SortOrder.UPDATED) - - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() - val url = if (sortOrder == SortOrder.UPDATED && query.isNullOrEmpty() && tags.isNullOrEmpty()) { - buildString { - append("https://") - append(domain) - append("/son-eklenenler") - append("?page=") - append(page) + override val availableSortOrders: Set = + EnumSet.of(SortOrder.POPULARITY, SortOrder.ALPHABETICAL, SortOrder.UPDATED) + + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) + + 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("/webtoon-listesi?page=") + append(page.toString()) + append("&q=") + append(filter.query.urlEncoded()) + append("&sort=views&short_type=DESC") + } + return parseMangaList(webClient.httpGet(url).parseHtml()) } - } else { - buildString { - append("https://") - append(domain) - append("/webtoon-listesi") - append("?page=") - append(page) - when { - !query.isNullOrEmpty() -> { - append("&q=") - append(query.urlEncoded()) - } - !tags.isNullOrEmpty() -> { - append("&genre=") - append(tag?.key.orEmpty()) + is MangaListFilter.Advanced -> { + + if (filter.sortOrder == SortOrder.UPDATED) { + if (filter.tags.isNotEmpty()) { + throw IllegalArgumentException("Sort order updated + Tags or States is not supported by this source") + } + val url = buildString { + append("https://") + append(domain) + append("/son-eklenenler?page=") + append(page.toString()) } + return parseMangaListUpdated(webClient.httpGet(url).parseHtml()) + } else { + val url = buildString { + append("https://") + append(domain) + append("/webtoon-listesi?page=") + append(page.toString()) + filter.tags.oneOrThrowIfMany()?.let { + append("&genre=") + append(it.key) + } + filter.states.oneOrThrowIfMany()?.let { + append("&status=") + append( + when (it) { + MangaState.ONGOING -> "continues" + MangaState.FINISHED -> "complated" + else -> "" + }, + ) + } + append("&sort=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("views&short_type=DESC") + SortOrder.ALPHABETICAL -> append("name&short_type=ASC") + else -> append("views&short_type=DESC") + } + } + + return parseMangaList(webClient.httpGet(url).parseHtml()) } - append("&sort=") - when (sortOrder) { - SortOrder.POPULARITY -> append("views&short_type=DESC") - SortOrder.ALPHABETICAL -> append("name&short_type=ASC") - else -> append("views&short_type=DESC") + + } + + null -> { + val url = buildString { + append("https://") + append(domain) + append("/son-eklenenler?page=") + append(page.toString()) } + return parseMangaListUpdated(webClient.httpGet(url).parseHtml()) } } + } - val doc = webClient.httpGet(url).parseHtml() - val mangas = if (sortOrder == SortOrder.UPDATED && query.isNullOrEmpty() && tags.isNullOrEmpty()) { - doc.select(".page-content div.bslist_item").map { li -> - val href = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - url = href, - publicUrl = href.toAbsoluteUrl(domain), - coverUrl = li.selectFirst(".figure img")?.src().orEmpty(), - title = li.selectFirst(".title")?.text().orEmpty(), - altTitle = null, - rating = RATING_UNKNOWN, - tags = emptySet(), - author = null, - state = when (doc.selectFirst("d-inline .badge")?.text()) { - "Devam Ediyor", "Güncel" -> MangaState.ONGOING - "Tamamlandı" -> MangaState.FINISHED - else -> null - }, - source = source, - isNsfw = isNsfwSource, - ) - } - } else { - doc.select(".row .col-xl-4 .card-body").map { li -> - val href = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - url = href, - publicUrl = href.toAbsoluteUrl(domain), - coverUrl = li.selectFirst("img")?.src().orEmpty(), - title = li.selectFirst(".table-responsive a")?.text().orEmpty(), - altTitle = null, - rating = li.selectFirst(".row .col-xl-4 .mt-2 .my-1 .text-muted")?.text()?.substringBefore("/") - ?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, - tags = emptySet(), - author = null, - state = when (doc.selectLast(".row .col-xl-4 .mt-2 .rounded-pill")?.text()) { - "Devam Ediyor", "Güncel" -> MangaState.ONGOING - "Tamamlandı" -> MangaState.FINISHED - else -> null - }, - source = source, - isNsfw = isNsfwSource, - ) - } + + private fun parseMangaList(doc: Document): List { + return doc.select(".row .col-xl-4 .card-body").map { li -> + val href = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(domain), + coverUrl = li.selectFirst("img")?.src().orEmpty(), + title = li.selectFirst(".table-responsive a")?.text().orEmpty(), + altTitle = null, + rating = li.selectFirst(".row .col-xl-4 .mt-2 .my-1 .text-muted")?.text()?.substringBefore("/") + ?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = when (doc.selectLast(".row .col-xl-4 .mt-2 .rounded-pill")?.text()) { + "Devam Ediyor", "Güncel" -> MangaState.ONGOING + "Tamamlandı" -> MangaState.FINISHED + else -> null + }, + source = source, + isNsfw = isNsfwSource, + ) } + } - return mangas + private fun parseMangaListUpdated(doc: Document): List { + return doc.select(".page-content div.bslist_item").map { li -> + val href = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(domain), + coverUrl = li.selectFirst(".figure img")?.src().orEmpty(), + title = li.selectFirst(".title")?.text().orEmpty(), + altTitle = null, + rating = RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = when (doc.selectFirst("d-inline .badge")?.text()) { + "Devam Ediyor", "Güncel" -> MangaState.ONGOING + "Tamamlandı" -> MangaState.FINISHED + else -> null + }, + source = source, + isNsfw = isNsfwSource, + ) + } } override suspend fun getAvailableTags(): Set { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/YaoiFlix.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/YaoiFlix.kt index ef915b6f..cb41dc3a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/YaoiFlix.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/YaoiFlix.kt @@ -16,43 +16,50 @@ class YaoiFlix(context: MangaLoaderContext) : PagedMangaParser(context, MangaSou override val configKeyDomain = ConfigKey.Domain("www.yaoiflix.pro") - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override val isMultipleTagsSupported = false + + 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) { append("/page/") - append(page) + append(page.toString()) } append("/?s=") - append(query.urlEncoded()) + append(filter.query.urlEncoded()) } - !tags.isNullOrEmpty() -> { - append("/dizi-kategori/") - append(tag?.key.orEmpty()) - append("/") - if (page > 1) { - append("page/") - append(page) - append("/") + is MangaListFilter.Advanced -> { + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append("/dizi-kategori/") + append(it.key) + append("/") + if (page > 1) { + append("page/") + append(page.toString()) + append('/') + } + } + } else { + append("/tum-seriler/") + if (page > 1) { + append("page/") + append(page.toString()) + append('/') + } } } - else -> { + null -> { append("/tum-seriler/") if (page > 1) { append("page/") - append(page) - append("/") + append(page.toString()) + append('/') } } }