diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt index 42687275..f4eb9d16 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt @@ -35,21 +35,6 @@ internal abstract class MadaraParser( // Change these values only if the site does not support manga listings via ajax protected open val withoutAjax = false - override val filterCapabilities: MangaListFilterCapabilities - get() = MangaListFilterCapabilities( - isMultipleTagsSupported = true, - isTagsExclusionSupported = !withoutAjax, - isSearchSupported = true, - isSearchWithFiltersSupported = true, - isYearSupported = true, - ) - - override suspend fun getFilterOptions() = MangaListFilterOptions( - availableTags = fetchAvailableTags(), - availableStates = EnumSet.allOf(MangaState::class.java), - availableContentRating = EnumSet.of(ContentRating.SAFE, ContentRating.ADULT), - ) - override val availableSortOrders: Set = setupAvailableSortOrders() private fun setupAvailableSortOrders(): Set { @@ -79,6 +64,21 @@ internal abstract class MadaraParser( } } + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = !withoutAjax, + isSearchSupported = true, + isSearchWithFiltersSupported = true, + isYearSupported = true, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.allOf(MangaState::class.java), + availableContentRating = EnumSet.of(ContentRating.SAFE, ContentRating.ADULT), + ) + override val authUrl: String get() = "https://${domain}" 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 23e563e2..1790b7bf 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 @@ -34,9 +34,17 @@ internal abstract class MadthemeParser( SortOrder.RATING, ) - protected open val listUrl = "search/" - protected open val datePattern = "MMM dd, yyyy" + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isSearchSupported = true, + isSearchWithFiltersSupported = true, + ) + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), + ) init { paginator.firstPage = 1 @@ -46,27 +54,17 @@ internal abstract class MadthemeParser( @JvmField protected val ongoing: Set = setOf( - "On Going", - "Ongoing", - "ONGOING", + "on going", + "ongoing", ) @JvmField protected val finished: Set = setOf( - "Completed", - "COMPLETED", + "completed", ) - override val filterCapabilities: MangaListFilterCapabilities - get() = MangaListFilterCapabilities( - isMultipleTagsSupported = true, - isSearchSupported = true, - ) - - override suspend fun getFilterOptions() = MangaListFilterOptions( - availableTags = fetchAvailableTags(), - availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), - ) + protected open val listUrl = "search/" + protected open val datePattern = "MMM dd, yyyy" override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val url = buildString { @@ -74,49 +72,43 @@ internal abstract class MadthemeParser( append(domain) append('/') append(listUrl) - when { - !filter.query.isNullOrEmpty() -> { - append("?sort=updated_at&q=") - append(filter.query.urlEncoded()) - } + append("?page=") + append(page.toString()) - else -> { - - append("?sort=") - when (order) { - 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") - else -> append("updated_at") - } - 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" - }, - ) - } + filter.query?.let { + append("&q=") + append(filter.query.urlEncoded()) + } + append("&sort=") + when (order) { + 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") + else -> append("updated_at") + } + if (filter.tags.isNotEmpty()) { + filter.tags.forEach { + append("&") + append("genre[]".urlEncoded()) + append("=") + append(it.key) } } - append("&page=") - append(page.toString()) + filter.states.oneOrThrowIfMany()?.let { + append("&status=") + append( + when (it) { + MangaState.ONGOING -> "ongoing" + MangaState.FINISHED -> "completed" + else -> "all" + }, + ) + } } val doc = webClient.httpGet(url).parseHtml() @@ -128,10 +120,9 @@ internal abstract class MadthemeParser( url = href, publicUrl = href.toAbsoluteUrl(div.host ?: domain), coverUrl = div.selectFirst("img")?.src().orEmpty(), - title = div.selectFirstOrThrow("div.meta").selectFirst("div.title")?.text().orEmpty(), + title = div.selectFirst("div.meta")?.selectFirst("div.title")?.text().orEmpty(), altTitle = null, - rating = div.selectFirstOrThrow("div.meta span.score").ownText().toFloatOrNull()?.div(5f) - ?: RATING_UNKNOWN, + rating = div.selectFirst("div.meta span.score")?.ownText()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, tags = doc.body().select("div.meta div.genres span").mapNotNullToSet { span -> MangaTag( key = span.attr("class"), @@ -151,7 +142,7 @@ internal abstract class MadthemeParser( val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() 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() + val name = checkbox.selectFirst("span.radio__label")?.text() ?: key MangaTag( key = key, title = name, @@ -171,12 +162,12 @@ internal abstract class MadthemeParser( val chaptersDeferred = async { getChapters(doc) } - val desc = doc.selectFirstOrThrow(selectDesc).html() + val desc = doc.selectFirst(selectDesc)?.html() val stateDiv = doc.selectFirst(selectState) val state = stateDiv?.let { - when (it.text()) { + when (it.text().lowercase()) { in ongoing -> MangaState.ONGOING in finished -> MangaState.FINISHED else -> null @@ -195,8 +186,8 @@ internal abstract class MadthemeParser( source = source, ) }, - description = desc, - altTitle = alt, + description = desc.orEmpty(), + altTitle = alt.orEmpty(), state = state, chapters = chaptersDeferred.await(), isNsfw = nsfw || manga.isNsfw, @@ -218,7 +209,7 @@ internal abstract class MadthemeParser( val dateText = li.selectFirst(selectDate)?.text() MangaChapter( id = generateUid(href), - name = li.selectFirstOrThrow(".chapter-title").text(), + name = li.selectFirst(".chapter-title")?.text() ?: "Chapters : ${i + 1f}", number = i + 1f, volume = 0, url = href, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/ManhuaScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/ManhuaScan.kt index f9a5c911..ba82819d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/ManhuaScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/ManhuaScan.kt @@ -1,124 +1,12 @@ package org.koitharu.kotatsu.parsers.site.madtheme.en -import org.jsoup.nodes.Document import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.site.madtheme.MadthemeParser -import org.koitharu.kotatsu.parsers.util.* -import java.text.SimpleDateFormat -import java.util.* @Broken @MangaSourceParser("MANHUASCAN", "kaliscan.io", "en") internal class ManhuaScan(context: MangaLoaderContext) : - MadthemeParser(context, MangaParserSource.MANHUASCAN, "manhuascan.io") { - override val sourceLocale: Locale = Locale.ENGLISH - override val listUrl = "search" - - override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { - val url = buildString { - append("https://") - append(domain) - append('/') - append(listUrl) - when { - - !filter.query.isNullOrEmpty() -> { - append("?sort=updated_at&q=") - append(filter.query.urlEncoded()) - } - - else -> { - - append("?sort=") - when (order) { - SortOrder.POPULARITY -> append("views") - SortOrder.UPDATED -> append("updated_at") - SortOrder.ALPHABETICAL -> append("name") - SortOrder.NEWEST -> append("created_at") - SortOrder.RATING -> append("rating") - else -> append("updated_at") - } - 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" - }, - ) - } - - } - } - - append("&page=") - append(page.toString()) - } - - val doc = webClient.httpGet(url).parseHtml() - - return doc.select("div.book-item").map { div -> - val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - url = href, - publicUrl = href.toAbsoluteUrl(div.host ?: domain), - coverUrl = div.selectFirst("img")?.src().orEmpty(), - title = div.selectFirstOrThrow("div.meta").selectFirst("div.title")?.text().orEmpty(), - altTitle = null, - rating = div.selectFirstOrThrow("div.meta span.score").ownText().toFloatOrNull()?.div(5f) - ?: RATING_UNKNOWN, - tags = doc.body().select("div.meta div.genres span").mapNotNullToSet { span -> - MangaTag( - key = span.attr("class"), - title = span.text().toTitleCase(), - source = source, - ) - }, - author = null, - state = null, - source = source, - isNsfw = isNsfwSource, - ) - } - } - - override suspend fun getChapters(doc: Document): List { - val dateFormat = SimpleDateFormat(datePattern, sourceLocale) - val id = doc.selectFirstOrThrow("script:containsData(bookId)").data().substringAfter("bookId = ") - .substringBefore(";") - val docChapter = webClient.httpGet("https://$domain/service/backend/chaplist/?manga_id=$id").parseHtml() - return docChapter.select(selectChapter).mapChapters(reversed = true) { i, li -> - val a = li.selectFirstOrThrow("a") - val href = a.attrAsRelativeUrl("href") - val dateText = li.selectFirst(selectDate)?.text() - MangaChapter( - id = generateUid(href), - name = li.selectFirstOrThrow(".chapter-title").text(), - number = i + 1f, - volume = 0, - url = href, - uploadDate = parseChapterDate( - dateFormat, - dateText, - ), - source = source, - scanlator = null, - branch = null, - ) - } - } -} + MadthemeParser(context, MangaParserSource.MANHUASCAN, "manhuascan.io") 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 49bad9bd..ce636f62 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 @@ -31,10 +31,15 @@ internal abstract class Manga18Parser( SortOrder.ALPHABETICAL, ) - protected open val listUrl = "list-manga/" - protected open val tagUrl = "manga-list/" - protected open val datePattern = "dd-MM-yyyy" + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isSearchSupported = true, + isSearchWithFiltersSupported = true, + ) + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + ) init { paginator.firstPage = 1 @@ -52,52 +57,48 @@ internal abstract class Manga18Parser( "Completed", ) - override val filterCapabilities: MangaListFilterCapabilities - get() = MangaListFilterCapabilities( - isSearchSupported = true, - ) - - override suspend fun getFilterOptions() = MangaListFilterOptions( - availableTags = fetchAvailableTags(), - ) + protected open val listUrl = "list-manga/" + protected open val tagUrl = "manga-list/" + protected open val datePattern = "dd-MM-yyyy" override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val url = buildString { append("https://") append(domain) append('/') - when { - !filter.query.isNullOrEmpty() -> { + if (filter.tags.isNotEmpty() && filter.query != null) { + throw IllegalArgumentException("Search is not supported with tags") + } + + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append(tagUrl) + append(it.key) + append('/') + append(page.toString()) + } + } + + if (filter.query != null) { + filter.query.let { append(listUrl) append(page.toString()) append("?search=") append(filter.query.urlEncoded()) append("&order_by=latest") } + } - else -> { - if (filter.tags.isNotEmpty()) { - filter.tags.oneOrThrowIfMany()?.let { - append(tagUrl) - append(it.key) - append("/") - } - } else { - append(listUrl) - } - - append(page.toString()) - append("?order_by=") - when (order) { - SortOrder.POPULARITY -> append("views") - SortOrder.UPDATED -> append("lastest") - SortOrder.ALPHABETICAL -> append("name") - else -> append("latest") - } - } + append("?order_by=") + when (order) { + SortOrder.POPULARITY -> append("views") + SortOrder.UPDATED -> append("lastest") + SortOrder.ALPHABETICAL -> append("name") + else -> append("latest") } } + return parseMangaList(webClient.httpGet(url).parseHtml()) } @@ -109,7 +110,7 @@ internal abstract class Manga18Parser( url = href, publicUrl = href.toAbsoluteUrl(div.host ?: domain), coverUrl = div.selectFirst("img")?.src().orEmpty(), - title = div.selectFirstOrThrow("div.mg_info").selectFirst("div.mg_name a")?.text().orEmpty(), + title = div.selectFirst("div.mg_info")?.selectFirst("div.mg_name a")?.text().orEmpty(), altTitle = null, rating = RATING_UNKNOWN, tags = emptySet(), @@ -145,13 +146,9 @@ internal abstract class Manga18Parser( val fullUrl = manga.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() val body = doc.body().selectFirstOrThrow("div.detail_listInfo") - val chaptersDeferred = async { getChapters(doc) } - - val desc = doc.selectFirstOrThrow(selectDesc).html() - + val desc = doc.selectFirst(selectDesc)?.html() val stateDiv = body.selectFirst(selectState) - val state = stateDiv?.let { when (it.text()) { in ongoing -> MangaState.ONGOING @@ -159,10 +156,8 @@ internal abstract class Manga18Parser( else -> null } } - val alt = body.selectFirst(selectAlt)?.text().takeIf { it != "Updating" || it.isNotEmpty() } val author = body.selectFirst(selectAuthor)?.text().takeIf { it != "Updating" } - manga.copy( tags = doc.body().select(selectTag).mapNotNullToSet { a -> MangaTag( @@ -171,7 +166,7 @@ internal abstract class Manga18Parser( source = source, ) }, - description = desc, + description = desc.orEmpty(), altTitle = alt, author = author, state = state, @@ -206,12 +201,10 @@ internal abstract class Manga18Parser( override suspend fun getPages(chapter: MangaChapter): List { val fullUrl = chapter.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() - val script = doc.selectFirstOrThrow("script:containsData(slides_p_path)") - val urlencoed = script.data().substringAfter('[').substringBefore(",]").replace("\"", "").split(",") - return urlencoed.map { url -> + val urlEncoded = script.data().substringAfter('[').substringBefore(",]").replace("\"", "").split(",") + return urlEncoded.map { url -> val img = context.decodeBase64(url).toString(Charsets.UTF_8) - MangaPage( id = generateUid(img), url = img, 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 53c29f7c..84e563a1 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 @@ -25,7 +25,7 @@ internal class Hentai3zCc(context: MangaLoaderContext) : ?.replace("cover_thumb_2.webp", "cover_250x350.jpg") ?.replace("admin.manga18.us", "bk.18porncomic.com") .orEmpty(), - title = div.selectFirstOrThrow("div.mg_info").selectFirst("div.mg_name a")?.text().orEmpty(), + title = div.selectFirst("div.mg_info")?.selectFirst("div.mg_name a")?.text().orEmpty(), altTitle = null, rating = RATING_UNKNOWN, tags = emptySet(), 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 d51efd2f..0ddbe2c8 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 @@ -30,10 +30,18 @@ internal abstract class MangaboxParser( SortOrder.ALPHABETICAL, ) - protected open val listUrl = "/advanced_search" - protected open val searchUrl = "/search/story/" - protected open val datePattern = "MMM dd,yy" + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = true, + isSearchSupported = true, + isSearchWithFiltersSupported = true, + ) + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), + ) init { paginator.firstPage = 1 @@ -50,17 +58,9 @@ internal abstract class MangaboxParser( "completed", ) - override val filterCapabilities: MangaListFilterCapabilities - get() = MangaListFilterCapabilities( - isMultipleTagsSupported = true, - isTagsExclusionSupported = true, - isSearchSupported = true, - ) - - override suspend fun getFilterOptions() = MangaListFilterOptions( - availableTags = fetchAvailableTags(), - availableStates = 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" override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val url = buildString { @@ -68,55 +68,50 @@ internal abstract class MangaboxParser( append(domain) append(listUrl) append("/?s=all") - when { - !filter.query.isNullOrEmpty() -> { - append("&keyw=") - append(filter.query.replace(" ", "_").urlEncoded()) + filter.query?.let { + append("&keyw=") + append(filter.query.replace(" ", "_").urlEncoded()) + } + + if (filter.tags.isNotEmpty()) { + append("&g_i=") + filter.tags.forEach { + append("_") + append(it.key) + append("_") } + } - else -> { - - if (filter.tags.isNotEmpty()) { - append("&g_i=") - filter.tags.forEach { - append("_") - append(it.key) - append("_") - } - } - - if (filter.tagsExclude.isNotEmpty()) { - append("&g_e=") - filter.tagsExclude.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 (order) { - SortOrder.POPULARITY -> append("topview") - SortOrder.UPDATED -> append("") - SortOrder.NEWEST -> append("newest") - SortOrder.ALPHABETICAL -> append("az") - else -> append("") - } + if (filter.tagsExclude.isNotEmpty()) { + append("&g_e=") + filter.tagsExclude.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 (order) { + SortOrder.POPULARITY -> append("topview") + SortOrder.UPDATED -> append("") + SortOrder.NEWEST -> append("newest") + SortOrder.ALPHABETICAL -> append("az") + else -> append("") + } + append("&page=") append(page.toString()) } @@ -132,7 +127,7 @@ internal abstract class MangaboxParser( url = href, publicUrl = href.toAbsoluteUrl(div.host ?: domain), coverUrl = div.selectFirst("img")?.src().orEmpty(), - title = div.selectFirstOrThrow("h3").text().orEmpty(), + title = div.selectFirst("h3")?.text().orEmpty(), altTitle = null, rating = RATING_UNKNOWN, tags = emptySet(), @@ -170,7 +165,7 @@ internal abstract class MangaboxParser( val fullUrl = manga.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() val chaptersDeferred = async { getChapters(doc) } - val desc = doc.selectFirstOrThrow(selectDesc).html() + val desc = doc.selectFirst(selectDesc)?.html() val stateDiv = doc.select(selectState).text() val state = stateDiv.let { when (it.lowercase()) { 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 edad676e..f10cc12a 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 @@ -35,6 +35,7 @@ internal class Mangairo(context: MangaLoaderContext) : get() = super.filterCapabilities.copy( isTagsExclusionSupported = false, isMultipleTagsSupported = false, + isSearchWithFiltersSupported = false, ) override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { @@ -125,7 +126,7 @@ internal class Mangairo(context: MangaLoaderContext) : val fullUrl = manga.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() val chaptersDeferred = async { getChapters(doc) } - val desc = doc.selectFirstOrThrow(selectDesc).html() + val desc = doc.selectFirst(selectDesc)?.html() val stateDiv = doc.select(selectState).text() val state = stateDiv.let { when (it) { 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 a574746b..8b9285eb 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 @@ -23,6 +23,7 @@ internal class Mangakakalot(context: MangaLoaderContext) : get() = super.filterCapabilities.copy( isTagsExclusionSupported = false, isMultipleTagsSupported = false, + isSearchWithFiltersSupported = false, ) override val otherDomain = "chapmanganato.com" override val listUrl = "/manga_list" @@ -85,7 +86,7 @@ internal class Mangakakalot(context: MangaLoaderContext) : url = href, publicUrl = href.toAbsoluteUrl(div.host ?: domain), coverUrl = div.selectFirst("img")?.src().orEmpty(), - title = div.selectFirstOrThrow("h3").text().orEmpty(), + title = div.selectFirst("h3")?.text().orEmpty(), altTitle = null, rating = RATING_UNKNOWN, tags = emptySet(), 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 5debf593..4941e55f 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 @@ -26,6 +26,7 @@ internal class MangakakalotTv(context: MangaLoaderContext) : get() = super.filterCapabilities.copy( isTagsExclusionSupported = false, isMultipleTagsSupported = false, + isSearchWithFiltersSupported = false, ) override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangadventure/MangAdventureParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangadventure/MangAdventureParser.kt index d7ae5404..293cbd2f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangadventure/MangAdventureParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangadventure/MangAdventureParser.kt @@ -18,6 +18,7 @@ internal abstract class MangAdventureParser( domain: String, pageSize: Int = 25, ) : PagedMangaParser(context, source, pageSize) { + override val configKeyDomain = ConfigKey.Domain(domain) override val userAgentKey = ConfigKey.UserAgent(UserAgents.KOTATSU) @@ -41,6 +42,7 @@ internal abstract class MangAdventureParser( isMultipleTagsSupported = true, isTagsExclusionSupported = true, isSearchSupported = true, + isSearchWithFiltersSupported = true, ) override suspend fun getFilterOptions() = MangaListFilterOptions( @@ -62,42 +64,37 @@ internal abstract class MangAdventureParser( val url = apiUrl.addEncodedPathSegment("series") .addEncodedQueryParameter("limit", pageSize.toString()) .addEncodedQueryParameter("page", page.toString()) - when { - !filter.query.isNullOrEmpty() -> { - url.addQueryParameter("title", filter.query) - } - else -> { - url.addQueryParameter( - "categories", - buildString { - if (filter.tags.isNotEmpty() && filter.tagsExclude.isNotEmpty()) { - filter.tags.joinTo(this, ",", postfix = ",") { it.key } - filter.tagsExclude.joinTo(this, ",") { "-" + it.key } - } else if (filter.tags.isNotEmpty()) { - filter.tags.joinTo(this, ",") { it.key } - } else if (filter.tagsExclude.isNotEmpty()) { - filter.tagsExclude.joinTo(this, ",") { "-" + it.key } - } - }, - ) - when (filter.states.oneOrThrowIfMany()) { - null -> url.addEncodedQueryParameter("status", "any") - MangaState.ONGOING -> url.addEncodedQueryParameter("status", "ongoing") - MangaState.FINISHED -> url.addEncodedQueryParameter("status", "completed") - MangaState.ABANDONED -> url.addEncodedQueryParameter("status", "canceled") - MangaState.PAUSED -> url.addEncodedQueryParameter("status", "hiatus") - else -> throw IllegalArgumentException(ERROR_UNSUPPORTED_STATE) - } - when (order) { - SortOrder.ALPHABETICAL -> url.addEncodedQueryParameter("sort", "title") - SortOrder.ALPHABETICAL_DESC -> url.addEncodedQueryParameter("sort", "-title") - SortOrder.UPDATED -> url.addEncodedQueryParameter("sort", "-latest_upload") - SortOrder.POPULARITY -> url.addEncodedQueryParameter("sort", "-views") - else -> throw IllegalArgumentException(ERROR_UNSUPPORTED_SORT_ORDER) - } + filter.query?.let { + url.addQueryParameter("title", filter.query) + } + + url.addQueryParameter( + "categories", + buildString { + filter.tags.joinTo(this, ",", postfix = ",") { it.key } + filter.tagsExclude.joinTo(this, ",") { "-" + it.key } + }, + ) + + filter.states.oneOrThrowIfMany()?.let { + when (it) { + MangaState.ONGOING -> url.addEncodedQueryParameter("status", "ongoing") + MangaState.FINISHED -> url.addEncodedQueryParameter("status", "completed") + MangaState.ABANDONED -> url.addEncodedQueryParameter("status", "canceled") + MangaState.PAUSED -> url.addEncodedQueryParameter("status", "hiatus") + else -> url.addEncodedQueryParameter("status", "any") } } + + when (order) { + SortOrder.ALPHABETICAL -> url.addEncodedQueryParameter("sort", "title") + SortOrder.ALPHABETICAL_DESC -> url.addEncodedQueryParameter("sort", "-title") + SortOrder.UPDATED -> url.addEncodedQueryParameter("sort", "-latest_upload") + SortOrder.POPULARITY -> url.addEncodedQueryParameter("sort", "-views") + else -> url.addEncodedQueryParameter("sort", "-latest_upload") + } + return runCatchingCancellable { getManga(url.get()) }.getOrElse { if (it is NotFoundException) emptyList() else throw it } @@ -213,12 +210,4 @@ internal abstract class MangAdventureParser( protected suspend fun HttpUrl.Builder.get() = webClient.httpGet(build()).body?.string()?.let(::JSONObject) - - private companion object { - private const val ERROR_UNSUPPORTED_STATE = - "The selected state is not supported by this source" - - private const val ERROR_UNSUPPORTED_SORT_ORDER = - "The selected sort order is not supported by this source" - } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangaworld/MangaWorldParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangaworld/MangaWorldParser.kt index 1b8a5229..5aa9ffae 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangaworld/MangaWorldParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangaworld/MangaWorldParser.kt @@ -18,8 +18,10 @@ internal abstract class MangaWorldParser( override val availableSortOrders: Set = EnumSet.of( SortOrder.POPULARITY, + SortOrder.POPULARITY_ASC, SortOrder.ALPHABETICAL, SortOrder.NEWEST, + SortOrder.NEWEST_ASC, SortOrder.ALPHABETICAL_DESC, SortOrder.UPDATED, ) @@ -33,11 +35,20 @@ internal abstract class MangaWorldParser( get() = MangaListFilterCapabilities( isMultipleTagsSupported = true, isSearchSupported = true, + isSearchWithFiltersSupported = true, + isYearSupported = true, ) override suspend fun getFilterOptions() = MangaListFilterOptions( availableTags = fetchAvailableTags(), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED, MangaState.PAUSED), + availableContentTypes = EnumSet.of( + ContentType.MANGA, + ContentType.MANHUA, + ContentType.MANHWA, + ContentType.ONE_SHOT, + ContentType.OTHER, + ), ) override fun onCreateConfig(keys: MutableCollection>) { @@ -46,43 +57,83 @@ internal abstract class MangaWorldParser( } override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { + + if (order == SortOrder.UPDATED) { + if (filter.query != null || filter.tags.isNotEmpty() || filter.states.isNotEmpty() || filter.types.isNotEmpty() || filter.year != 0) { + throw IllegalArgumentException("Sorting by update with filters is not supported by this source.") + + } + return parseMangaList(webClient.httpGet("https://$domain/?page=$page").parseHtml()) + } + val url = buildString { append("https://") append(domain) - append("/archive?") - when { - !filter.query.isNullOrEmpty() -> { - append("keyword=") - append(filter.query.urlEncoded()) - } + append("/archive?&page=") + append(page.toString()) - else -> { - if (filter.tags.isEmpty() && filter.states.isEmpty() && order == SortOrder.UPDATED) return parseMangaList( - webClient.httpGet("https://$domain/?page=$page").parseHtml(), - ) - - if (filter.tags.isNotEmpty()) { - filter.tags.joinTo(this, "&") { it.key.substringAfter("archive?") } - } - - when (order) { - SortOrder.POPULARITY -> append("&sort=most_read") - SortOrder.ALPHABETICAL -> append("&sort=a-z") - SortOrder.NEWEST -> append("&sort=newest") - SortOrder.ALPHABETICAL_DESC -> append("&sort=z-a") - else -> append("&sort=a-z") - } - when (filter.states.oneOrThrowIfMany()) { - MangaState.ONGOING -> append("&status=ongoing") - MangaState.FINISHED -> append("&status=completed") - MangaState.ABANDONED -> append("&status=dropped") - MangaState.PAUSED -> append("&status=paused") - else -> Unit - } + filter.query?.let { + append("&keyword=") + append(filter.query.urlEncoded()) + } + + filter.tags.forEach { + append("&genre=") + append(it.key) + } + + when (order) { + SortOrder.POPULARITY -> append("&sort=most_read") + SortOrder.POPULARITY_ASC -> append("&sort=less_read") + SortOrder.ALPHABETICAL -> append("&sort=a-z") + SortOrder.NEWEST -> append("&sort=newest") + SortOrder.NEWEST_ASC -> append("&sort=oldest") + SortOrder.ALPHABETICAL_DESC -> append("&sort=z-a") + else -> append("&sort=a-z") + } + + filter.states.forEach { + when (it) { + MangaState.ONGOING -> append("&status=ongoing") + MangaState.FINISHED -> append("&status=completed") + MangaState.ABANDONED -> append("&status=dropped") + MangaState.PAUSED -> append("&status=paused") + else -> {} } } - append("&page=$page") + + filter.types.forEach { + append("&type=") + append( + when (it) { + ContentType.MANGA -> "manga" + ContentType.MANHUA -> "manhua" + ContentType.MANHWA -> "manhwa" + ContentType.ONE_SHOT -> "oneshot" + ContentType.OTHER -> "thai&type=vietnamese" + else -> "" + }, + ) + } + + if (filter.year != 0) { + append("&year=") + append(filter.year) + } + + // author ( not query but same to tags ) + // filter.author.forEach { + // append("&author=") + // append(it.key) + // } + + // artist ( not query but same to tags ) + // filter.artist.forEach { + // append("&artist=") + // append(it.key) + // } + } val doc = webClient.httpGet(url).parseHtml() return parseMangaList(doc) @@ -104,11 +155,11 @@ internal abstract class MangaWorldParser( tags = tags, author = div.selectFirst(".author a")?.text(), state = - when (div.selectFirst(".status a")?.text()) { - "In corso" -> MangaState.ONGOING - "Finito" -> MangaState.FINISHED - "Droppato" -> MangaState.ABANDONED - "In pausa" -> MangaState.PAUSED + when (div.selectFirst(".status a")?.text()?.lowercase()) { + "in corso" -> MangaState.ONGOING + "finito" -> MangaState.FINISHED + "droppato" -> MangaState.ABANDONED + "in pausa" -> MangaState.PAUSED else -> null }, source = source, @@ -120,23 +171,13 @@ internal abstract class MangaWorldParser( private suspend fun fetchAvailableTags(): Set { val doc = webClient.httpGet("https://$domain/").parseHtml() - val genres = doc.select("div[aria-labelledby=genresDropdown] a").mapNotNullToSet { + return doc.select("div[aria-labelledby=genresDropdown] a").mapNotNullToSet { MangaTag( - key = it.attr("href"), + key = it.attr("href").substringAfterLast('='), title = it.text().toTitleCase(sourceLocale), source = source, ) } - - val types = doc.select("div[aria-labelledby=typesDropdown] a").mapNotNullToSet { - MangaTag( - key = it.attr("href"), - title = it.text().toTitleCase(sourceLocale), - source = source, - ) - } - - return genres + types } override suspend fun getDetails(manga: Manga): Manga { @@ -154,7 +195,7 @@ internal abstract class MangaWorldParser( val url = a.attrAsRelativeUrl("href").toAbsoluteUrl(domain) MangaChapter( id = generateUid(url), - name = a.selectFirstOrThrow("span.d-inline-block").text(), + name = a.selectFirst("span.d-inline-block")?.text() ?: "Chapter : ${i + 1f}", number = i + 1f, volume = 0, url = "$url?style=list", diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt index 1f0f8439..738189b0 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt @@ -33,9 +33,15 @@ internal abstract class MmrcmsParser( SortOrder.ALPHABETICAL_DESC, ) - protected open val listUrl = "filterList" - protected open val tagUrl = "manga-list" - protected open val datePattern = "dd MMM. yyyy" + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isSearchSupported = true, + isSearchWithFiltersSupported = true, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + ) init { paginator.firstPage = 1 @@ -63,69 +69,54 @@ internal abstract class MmrcmsParser( ) protected open val imgUpdated = "/cover/cover_250x350.jpg" + protected open val listUrl = "filterList" + protected open val tagUrl = "manga-list" + protected open val datePattern = "dd MMM. yyyy" - override val filterCapabilities: MangaListFilterCapabilities - get() = MangaListFilterCapabilities( - isSearchSupported = true, - ) + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { - override suspend fun getFilterOptions() = MangaListFilterOptions( - availableTags = fetchAvailableTags(), - ) + if (order == SortOrder.UPDATED) { + if (filter.query != null || filter.tags.isNotEmpty()) { + throw IllegalArgumentException("Sorting by update with filters is not supported by this source.") - override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { - when { - !filter.query.isNullOrEmpty() -> { - val url = buildString { - append("https://") - append(domain) - append('/') - append(listUrl) - append("/?page=") - append(page.toString()) - append("&asc=true&author=&tag=&alpha=") - append(filter.query.urlEncoded()) - append("&cat=&sortBy=views") - } - return parseMangaList(webClient.httpGet(url).parseHtml()) } + val url = buildString { + append("https://") + append(domain) + append("/latest-release?page=") + append(page.toString()) + } + return parseMangaListUpdated(webClient.httpGet(url).parseHtml()) + } - else -> { + val url = buildString { + append("https://") + append(domain) + append('/') + append(listUrl) + append("/?page=") + append(page.toString()) + + append("&author=&tag=&alpha=") + filter.query?.let { + append(filter.query.urlEncoded()) + } - if (order == SortOrder.UPDATED) { - val url = buildString { - append("https://") - append(domain) - append("/latest-release?page=") - append(page.toString()) - } - return parseMangaListUpdated(webClient.httpGet(url).parseHtml()) + append("&cat=") + filter.tags.oneOrThrowIfMany()?.let { + append(it.key) + } - } else { - val url = buildString { - append("https://") - append(domain) - append('/') - append(listUrl) - append("/?page=") - append(page.toString()) - append("&author=&tag=&alpha=&cat=") - filter.tags.oneOrThrowIfMany()?.let { - append(it.key) - } - append("&sortBy=") - when (order) { - SortOrder.POPULARITY -> append("views&asc=false") - SortOrder.POPULARITY_ASC -> append("views&asc=true") - SortOrder.ALPHABETICAL -> append("name&asc=true") - SortOrder.ALPHABETICAL_DESC -> append("name&asc=false") - else -> append("name") - } - } - return parseMangaList(webClient.httpGet(url).parseHtml()) - } + append("&sortBy=") + when (order) { + SortOrder.POPULARITY -> append("views&asc=false") + SortOrder.POPULARITY_ASC -> append("views&asc=true") + SortOrder.ALPHABETICAL -> append("name&asc=true") + SortOrder.ALPHABETICAL_DESC -> append("name&asc=false") + else -> append("name&asc=true") } } + return parseMangaList(webClient.httpGet(url).parseHtml()) } protected open fun parseMangaList(doc: Document): List { @@ -136,9 +127,9 @@ internal abstract class MmrcmsParser( url = href, publicUrl = href.toAbsoluteUrl(div.host ?: domain), coverUrl = div.selectFirst("img")?.src().orEmpty(), - title = div.selectFirstOrThrow("div.media-body h5").text().orEmpty(), + title = div.selectFirst("div.media-body h5")?.text().orEmpty(), altTitle = null, - rating = div.selectFirstOrThrow("span").ownText().toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, + rating = div.selectFirst("span")?.ownText()?.toFloat()?.div(5f) ?: RATING_UNKNOWN, tags = emptySet(), author = null, state = null, @@ -157,7 +148,7 @@ internal abstract class MmrcmsParser( url = href, publicUrl = href.toAbsoluteUrl(div.host ?: domain), coverUrl = "https://$domain/uploads/manga/$deeplink$imgUpdated", - title = div.selectFirstOrThrow("h3 a").text().orEmpty(), + title = div.selectFirst("h3 a")?.text().orEmpty(), altTitle = null, rating = RATING_UNKNOWN, tags = emptySet(), @@ -233,7 +224,7 @@ internal abstract class MmrcmsParser( val dateText = li.selectFirst(selectDate)?.text() MangaChapter( id = generateUid(href), - name = li.selectFirstOrThrow("h5").text(), + name = li.selectFirst("h5")?.text() ?: "Chapter : ${i + 1f}", number = i + 1f, volume = 0, url = href, 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 b0f19835..17975b00 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 @@ -20,15 +20,6 @@ internal abstract class OtakuSanctuaryParser( override val configKeyDomain = ConfigKey.Domain(domain) - override val filterCapabilities: MangaListFilterCapabilities - get() = MangaListFilterCapabilities( - isSearchSupported = true, - ) - - override suspend fun getFilterOptions() = MangaListFilterOptions( - availableTags = fetchAvailableTags(), - ) - override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) keys.add(userAgentKey) @@ -39,6 +30,15 @@ internal abstract class OtakuSanctuaryParser( SortOrder.NEWEST, ) + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isSearchSupported = true, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + ) + protected open val listUrl = "Manga/Newest" protected open val datePattern = "dd/MM/yyyy" protected open val lang = "" @@ -110,10 +110,10 @@ internal abstract class OtakuSanctuaryParser( id = generateUid(href), url = href, publicUrl = href.toAbsoluteUrl(div.host ?: domain), - coverUrl = div.selectFirstOrThrow("img").src().orEmpty(), - title = div.selectFirstOrThrow("h4").text().orEmpty(), + coverUrl = div.selectFirst("img")?.src().orEmpty(), + title = div.selectFirst("h4")?.text().orEmpty(), altTitle = null, - rating = div.selectFirst(".rating")?.ownText()?.toFloatOrNull()?.div(10f) ?: RATING_UNKNOWN, + rating = div.selectFirst(".rating")?.ownText()?.toFloat()?.div(10f) ?: RATING_UNKNOWN, tags = emptySet(), author = null, state = null, @@ -147,7 +147,7 @@ internal abstract class OtakuSanctuaryParser( val fullUrl = manga.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() - val desc = doc.selectFirstOrThrow(selectDesc).html() + val desc = doc.selectFirst(selectDesc)?.html() val stateDiv = doc.selectFirst(selectState) 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 cf60ee0e..6a45cc7c 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 @@ -14,12 +14,17 @@ import java.util.* @MangaSourceParser("BRMANGAS", "BrMangas", "pt") internal class BrMangas(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.BRMANGAS, 25) { - override val availableSortOrders: Set = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED) - override val configKeyDomain = ConfigKey.Domain("www.brmangas.net") override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + + override val availableSortOrders: Set = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED) + override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isSearchSupported = true, @@ -29,11 +34,6 @@ internal class BrMangas(context: MangaLoaderContext) : PagedMangaParser(context, availableTags = fetchAvailableTags(), ) - override fun onCreateConfig(keys: MutableCollection>) { - super.onCreateConfig(keys) - keys.add(userAgentKey) - } - override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val url = buildString { append("https://") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerManga.kt index 0c6a9dc9..e6b82061 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerManga.kt @@ -11,6 +11,13 @@ import java.util.* @MangaSourceParser("LERMANGA", "LerManga", "pt") internal class LerManga(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.LERMANGA, 24) { + override val configKeyDomain = ConfigKey.Domain("lermanga.org") + + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + override val availableSortOrders: Set = EnumSet.of( SortOrder.UPDATED, @@ -23,8 +30,6 @@ internal class LerManga(context: MangaLoaderContext) : PagedMangaParser(context, SortOrder.RATING_ASC, ) - override val configKeyDomain = ConfigKey.Domain("lermanga.org") - override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities() @@ -32,11 +37,6 @@ internal class LerManga(context: MangaLoaderContext) : PagedMangaParser(context, availableTags = fetchAvailableTags(), ) - override fun onCreateConfig(keys: MutableCollection>) { - super.onCreateConfig(keys) - keys.add(userAgentKey) - } - override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val url = buildString { append("https://") 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 f0f3d021..dd103ffd 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 @@ -16,8 +16,6 @@ import java.util.* internal class LerMangaOnline(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.LERMANGAONLINE, 20) { - override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) - override val configKeyDomain = ConfigKey.Domain("lermangaonline.com.br") override fun onCreateConfig(keys: MutableCollection>) { @@ -25,6 +23,8 @@ internal class LerMangaOnline(context: MangaLoaderContext) : keys.add(userAgentKey) } + override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) + override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isSearchSupported = true, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LuratoonScansParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LuratoonScansParser.kt index 8aeec24b..ae92fbf2 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LuratoonScansParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LuratoonScansParser.kt @@ -19,12 +19,12 @@ internal class LuratoonScansParser(context: MangaLoaderContext) : SinglePageMangaParser(context, MangaParserSource.RANDOMSCANS), Interceptor { - override val availableSortOrders = setOf(SortOrder.ALPHABETICAL) - override val configKeyDomain = ConfigKey.Domain("luratoons.com") override fun getRequestHeaders(): Headers = Headers.Builder().add("User-Agent", config[userAgentKey]).build() + override val availableSortOrders = setOf(SortOrder.ALPHABETICAL) + override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities() 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 3c956d86..23137e8c 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 @@ -12,8 +12,6 @@ import java.util.* @MangaSourceParser("MANGAONLINE", "MangaOnline.biz", "pt") internal class MangaOnline(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAONLINE, 20) { - override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) - override val configKeyDomain = ConfigKey.Domain("mangaonline.biz") override fun onCreateConfig(keys: MutableCollection>) { @@ -21,6 +19,9 @@ internal class MangaOnline(context: MangaLoaderContext) : PagedMangaParser(conte keys.add(userAgentKey) } + override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) + + override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isSearchSupported = true, @@ -39,7 +40,6 @@ internal class MangaOnline(context: MangaLoaderContext) : PagedMangaParser(conte !filter.query.isNullOrEmpty() -> { append("/search/") append(filter.query.urlEncoded()) - append('/') } else -> { @@ -47,16 +47,15 @@ internal class MangaOnline(context: MangaLoaderContext) : PagedMangaParser(conte filter.tags.oneOrThrowIfMany()?.let { append("/genero/") append(it.key) - append('/') } } else { - append("/manga/") + append("/manga") } } } if (page > 1) { - append("page/") + append("/page/") append(page.toString()) append('/') } @@ -69,10 +68,10 @@ internal class MangaOnline(context: MangaLoaderContext) : PagedMangaParser(conte id = generateUid(href), url = href, publicUrl = a.attrAsAbsoluteUrl("href"), - title = div.selectLastOrThrow(".data h3").text(), + title = div.selectLast(".data h3")?.text().orEmpty(), coverUrl = div.selectFirst("img")?.src().orEmpty(), altTitle = null, - rating = div.selectFirst(".rating")?.ownText()?.toFloatOrNull()?.div(10f) ?: RATING_UNKNOWN, + rating = div.selectFirst(".rating")?.ownText()?.toFloat()?.div(10f) ?: RATING_UNKNOWN, tags = emptySet(), description = null, state = null, @@ -98,7 +97,7 @@ internal class MangaOnline(context: MangaLoaderContext) : PagedMangaParser(conte val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.ROOT) return manga.copy( - description = doc.selectLastOrThrow(".data p").html(), + description = doc.selectLast(".data p")?.html(), tags = doc.selectFirst(".sgeneros")?.select("a")?.mapNotNullToSet { a -> MangaTag( key = a.attr("href").removeSuffix("/").substringAfterLast("/", ""), @@ -109,7 +108,7 @@ internal class MangaOnline(context: MangaLoaderContext) : PagedMangaParser(conte chapters = doc.select(".episodios li a").mapChapters(reversed = true) { i, a -> val href = a.attrAsRelativeUrl("href") val title = a.html().substringBeforeLast(" = EnumSet.of(SortOrder.POPULARITY) - override val configKeyDomain = ConfigKey.Domain("www.muitohentai.com") override fun onCreateConfig(keys: MutableCollection>) { @@ -20,6 +18,8 @@ internal class MuitoHentai(context: MangaLoaderContext) : PagedMangaParser(conte keys.add(userAgentKey) } + override val availableSortOrders: Set = EnumSet.of(SortOrder.POPULARITY) + override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isSearchSupported = true, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/OnePieceEx.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/OnePieceEx.kt index a4547b68..f224747b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/OnePieceEx.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/OnePieceEx.kt @@ -13,20 +13,20 @@ import java.util.* @MangaSourceParser("ONEPIECEEX", "OnePieceEx", "pt") internal class OnePieceEx(context: MangaLoaderContext) : SinglePageMangaParser(context, MangaParserSource.ONEPIECEEX) { - override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) - override val configKeyDomain = ConfigKey.Domain("onepieceex.net") - override val filterCapabilities: MangaListFilterCapabilities - get() = MangaListFilterCapabilities() - - override suspend fun getFilterOptions() = MangaListFilterOptions() - override fun onCreateConfig(keys: MutableCollection>) { super.onCreateConfig(keys) keys.add(userAgentKey) } + override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) + + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities() + + override suspend fun getFilterOptions() = MangaListFilterOptions() + override suspend fun getList(order: SortOrder, filter: MangaListFilter): List { return listOf( Manga( 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 58c2c456..37cf6113 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 @@ -16,9 +16,15 @@ import java.util.* internal class YugenMangas(context: MangaLoaderContext) : SinglePageMangaParser(context, MangaParserSource.YUGENMANGAS) { - override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.ALPHABETICAL) override val configKeyDomain = ConfigKey.Domain("yugenmangasbr.voblog.xyz") + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + + override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.ALPHABETICAL) + override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isSearchSupported = true, @@ -26,11 +32,6 @@ internal class YugenMangas(context: MangaLoaderContext) : override suspend fun getFilterOptions() = MangaListFilterOptions() - override fun onCreateConfig(keys: MutableCollection>) { - super.onCreateConfig(keys) - keys.add(userAgentKey) - } - override suspend fun getList(order: SortOrder, filter: MangaListFilter): List { val json = when { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt index 58ff0645..887172c5 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt @@ -98,7 +98,7 @@ internal abstract class ScanParser( url = href, publicUrl = href.toAbsoluteUrl(div.host ?: domain), coverUrl = div.selectFirst("img")?.attr("data-src")?.replace("\t", "").orEmpty(), - title = div.selectFirstOrThrow(".link-series h3, .item-title").text().orEmpty(), + title = div.selectFirst(".link-series h3, .item-title")?.text().orEmpty(), altTitle = null, rating = RATING_UNKNOWN, tags = emptySet(), @@ -154,12 +154,13 @@ internal abstract class ScanParser( val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") MangaChapter( id = generateUid(href), - name = div.selectFirstOrThrow("h5").html().substringBefore(""), + name = div.selectFirst("h5")?.html()?.substringBefore("") + .orEmpty(), number = i + 1f, volume = 0, url = href, scanlator = null, - uploadDate = dateFormat.tryParse(doc.selectFirstOrThrow("h5 div").text()), + uploadDate = dateFormat.tryParse(doc.selectFirst("h5 div")?.text()), branch = null, source = source, ) 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 874f669c..c8e3cf26 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 @@ -28,8 +28,15 @@ internal abstract class SinmhParser( SortOrder.POPULARITY, ) - protected open val searchUrl = "search/" - protected open val listUrl = "list/" + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isSearchSupported = true, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), + ) init { paginator.firstPage = 1 @@ -46,15 +53,8 @@ internal abstract class SinmhParser( "已完结", ) - override val filterCapabilities: MangaListFilterCapabilities - get() = MangaListFilterCapabilities( - isSearchSupported = true, - ) - - override suspend fun getFilterOptions() = MangaListFilterOptions( - availableTags = fetchAvailableTags(), - availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), - ) + protected open val searchUrl = "search/" + protected open val listUrl = "list/" override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val url = buildString { @@ -93,7 +93,7 @@ internal abstract class SinmhParser( when (order) { SortOrder.POPULARITY -> append("click/") SortOrder.UPDATED -> append("update/") - else -> append("/") + else -> append('/') } append(page.toString()) append('/') @@ -113,7 +113,7 @@ internal abstract class SinmhParser( coverUrl = div.selectFirst("img")?.src().orEmpty(), title = div.selectFirst("p > a, h3 > a")?.text().orEmpty(), altTitle = null, - rating = div.selectFirst("span.total_votes")?.ownText()?.toFloatOrNull()?.div(5f) ?: -1f, + rating = div.selectFirst("span.total_votes")?.ownText()?.toFloat()?.div(5f) ?: RATING_UNKNOWN, tags = emptySet(), author = null, state = null, @@ -142,13 +142,9 @@ internal abstract class SinmhParser( override suspend fun getDetails(manga: Manga): Manga = coroutineScope { val fullUrl = manga.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).parseHtml() - val body = doc.body() - val chapters = getChapters(doc) - - val desc = body.selectFirst(selectDesc)?.html() - - val state = body.selectFirst(selectState)?.let { + val desc = doc.selectFirst(selectDesc)?.html() + val state = doc.selectFirst(selectState)?.let { when (it.text()) { in ongoing -> MangaState.ONGOING in finished -> MangaState.FINISHED @@ -174,11 +170,11 @@ internal abstract class SinmhParser( protected open suspend fun getChapters(doc: Document): List { return doc.body().select(selectChapter).mapChapters { i, li -> - val href = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") - val name = li.selectFirstOrThrow("a").text() + val a = li.selectFirstOrThrow("a") + val href = a.attrAsRelativeUrl("href") MangaChapter( id = generateUid(href), - name = name, + name = a.text(), number = i + 1f, volume = 0, url = href, 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 8e17fb33..ff2d6899 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 @@ -16,8 +16,6 @@ import java.util.* @MangaSourceParser("MANGAAY", "MangaAy", "tr") internal class MangaAy(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAAY, 45) { - override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) - override val configKeyDomain = ConfigKey.Domain("manga-ay.com") override fun onCreateConfig(keys: MutableCollection>) { @@ -25,6 +23,8 @@ internal class MangaAy(context: MangaLoaderContext) : PagedMangaParser(context, keys.add(userAgentKey) } + override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) + override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isSearchSupported = true, @@ -68,7 +68,7 @@ internal class MangaAy(context: MangaLoaderContext) : PagedMangaParser(context, append(domain) append("/seriler") if (page > 1) { - append("/") + append('/') append(page) } } @@ -89,7 +89,7 @@ internal class MangaAy(context: MangaLoaderContext) : PagedMangaParser(context, id = generateUid(href), url = href, publicUrl = a.attrAsAbsoluteUrl("href"), - title = div.selectLastOrThrow(".item-name").text(), + title = div.selectLast(".item-name")?.text().orEmpty(), coverUrl = div.selectFirst("img")?.src().orEmpty(), altTitle = null, rating = RATING_UNKNOWN, 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 4a0c4171..1179f436 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 @@ -12,27 +12,29 @@ import java.util.* @MangaSourceParser("SADSCANS", "SadScans", "tr") internal class SadScans(context: MangaLoaderContext) : SinglePageMangaParser(context, MangaParserSource.SADSCANS) { - override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) override val configKeyDomain = ConfigKey.Domain("sadscans.com") + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + + override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) + override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isSearchSupported = true, + isSearchWithFiltersSupported = true, ) override suspend fun getFilterOptions() = MangaListFilterOptions() - override fun onCreateConfig(keys: MutableCollection>) { - super.onCreateConfig(keys) - keys.add(userAgentKey) - } - override suspend fun getList(order: SortOrder, filter: MangaListFilter): List { val url = buildString { append("https://") append(domain) append("/series") - if (!filter.query.isNullOrEmpty()) { + filter.query?.let { append("?search=") append(filter.query.urlEncoded()) } 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 515695b1..e3035964 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 @@ -34,6 +34,7 @@ internal class TrWebtoon(context: MangaLoaderContext) : override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isSearchSupported = true, + isSearchWithFiltersSupported = true, ) override suspend fun getFilterOptions() = MangaListFilterOptions( @@ -42,67 +43,56 @@ internal class TrWebtoon(context: MangaLoaderContext) : ) override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { - when { - !filter.query.isNullOrEmpty() -> { - val url = buildString { - append("https://") - append(domain) - append("/webtoon-listesi?page=") - append(page.toString()) + if (order == SortOrder.UPDATED) { + if (filter.tags.isNotEmpty() || filter.states.isNotEmpty() || filter.query != null) { + throw IllegalArgumentException("Sorting by update with filters 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.query?.let { append("&q=") append(filter.query.urlEncoded()) - append("&sort=views&short_type=DESC") } - return parseMangaList(webClient.httpGet(url).parseHtml()) - } - else -> { - - if (order == 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 (order) { - SortOrder.POPULARITY -> append("views&short_type=DESC") - SortOrder.POPULARITY_ASC -> append("views&short_type=ASC") - SortOrder.ALPHABETICAL -> append("name&short_type=ASC") - SortOrder.ALPHABETICAL_DESC -> append("name&short_type=DESC") - else -> append("views&short_type=DESC") - } - } - - return parseMangaList(webClient.httpGet(url).parseHtml()) + 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 (order) { + SortOrder.POPULARITY -> append("views&short_type=DESC") + SortOrder.POPULARITY_ASC -> append("views&short_type=ASC") + SortOrder.ALPHABETICAL -> append("name&short_type=ASC") + SortOrder.ALPHABETICAL_DESC -> append("name&short_type=DESC") + else -> append("views&short_type=DESC") + } } + + return parseMangaList(webClient.httpGet(url).parseHtml()) } } @@ -180,7 +170,7 @@ internal class TrWebtoon(context: MangaLoaderContext) : ) }, description = doc.select("p.movie__plot").html(), - state = when (doc.selectFirstOrThrow(".movie__credits span.rounded-pill").text()) { + state = when (doc.selectFirst(".movie__credits span.rounded-pill")?.text()) { "Devam Ediyor", "Güncel" -> MangaState.ONGOING "Tamamlandı" -> MangaState.FINISHED else -> null @@ -189,14 +179,14 @@ internal class TrWebtoon(context: MangaLoaderContext) : val url = tr.selectFirstOrThrow("a").attrAsRelativeUrl("href") MangaChapter( id = generateUid(url), - name = tr.selectFirstOrThrow("a").text(), + name = tr.selectFirst("a")?.text() ?: "Chapter : ${i + 1f}", number = i + 1f, volume = 0, url = url, scanlator = null, uploadDate = parseChapterDate( SimpleDateFormat("dd/MM/yyyy", sourceLocale), - tr.selectLastOrThrow("td").selectFirstOrThrow("span").text(), + tr.selectLast("td")?.selectFirst("span")?.text(), ), branch = null, source = source, 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 3b082568..98634c0a 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 @@ -12,8 +12,6 @@ import java.util.* @MangaSourceParser("YAOIFLIX", "YaoiFlix", "tr", ContentType.HENTAI) internal class YaoiFlix(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.YAOIFLIX, 8) { - override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) - override val configKeyDomain = ConfigKey.Domain("www.yaoiflix.dev") override fun onCreateConfig(keys: MutableCollection>) { @@ -21,6 +19,8 @@ internal class YaoiFlix(context: MangaLoaderContext) : PagedMangaParser(context, keys.add(userAgentKey) } + override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) + override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isSearchSupported = true, @@ -75,7 +75,7 @@ internal class YaoiFlix(context: MangaLoaderContext) : PagedMangaParser(context, id = generateUid(href), url = href, publicUrl = a.attrAsAbsoluteUrl("href"), - title = div.selectLastOrThrow(".name").text(), + title = div.selectLast(".name")?.text().orEmpty(), coverUrl = div.selectFirst("img")?.src().orEmpty(), altTitle = null, rating = RATING_UNKNOWN, @@ -118,7 +118,7 @@ internal class YaoiFlix(context: MangaLoaderContext) : PagedMangaParser(context, val href = a.attrAsRelativeUrl("href") MangaChapter( id = generateUid(href), - name = div.selectFirstOrThrow(".name").text(), + name = div.selectFirst(".name")?.text() ?: "Chapter : ${i + 1f}", number = i + 1f, volume = 0, url = href, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/MangaInUaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/MangaInUaParser.kt index f144447f..ef0755ba 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/MangaInUaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/MangaInUaParser.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.parsers.site.uk -import org.koitharu.kotatsu.parsers.ErrorMessages import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.PagedMangaParser @@ -43,16 +42,28 @@ internal class MangaInUaParser(context: MangaLoaderContext) : PagedMangaParser( ) override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { - val url = when { - !filter.query.isNullOrEmpty() -> ("/index.php?do=search&subaction=search&search_start=$page&full_search=1&story=${filter.query}&titleonly=3").toAbsoluteUrl( - domain, - ) + val url = buildString { + append("https://") + append(domain) + when { + + !filter.query.isNullOrEmpty() -> { + append("/index.php?do=search&subaction=search&search_start=$page&full_search=1&story=${filter.query}&titleonly=3") + } + + else -> { - filter.tags.isEmpty() -> "/mangas/page/$page".toAbsoluteUrl(domain) - filter.tags.size == 1 -> "${filter.tags.first().key}/page/$page" - filter.tags.size > 1 -> throw IllegalArgumentException(ErrorMessages.FILTER_MULTIPLE_GENRES_NOT_SUPPORTED) - else -> "/mangas/page/$page".toAbsoluteUrl(domain) + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append("${it.key}/page/$page") + } + } else { + append("/mangas/page/$page") + } + } + } } + val doc = webClient.httpGet(url).parseHtml() val container = doc.body().requireElementById("site-content") val items = container.select("div.col-6") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt index 1749d8ba..1a4f86bf 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt @@ -21,9 +21,6 @@ internal class BlogTruyenParser(context: MangaLoaderContext) : override val configKeyDomain: ConfigKey.Domain get() = ConfigKey.Domain("blogtruyenmoi.com") - override val availableSortOrders: Set - get() = EnumSet.of(SortOrder.UPDATED) - override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) override fun onCreateConfig(keys: MutableCollection>) { @@ -31,8 +28,8 @@ internal class BlogTruyenParser(context: MangaLoaderContext) : keys.add(userAgentKey) } - private val dateFormat = SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.US) - private var cacheTags = SuspendLazy(::fetchTags) + override val availableSortOrders: Set + get() = EnumSet.of(SortOrder.UPDATED) override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( @@ -43,6 +40,9 @@ internal class BlogTruyenParser(context: MangaLoaderContext) : availableTags = cacheTags.get().values.toSet(), ) + private val dateFormat = SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.US) + private var cacheTags = SuspendLazy(::fetchTags) + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { return when { !filter.query.isNullOrEmpty() -> { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVNParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVNParser.kt index 6e2e6916..c4475387 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVNParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVNParser.kt @@ -21,9 +21,6 @@ internal class BlogTruyenVNParser(context: MangaLoaderContext) : override val configKeyDomain: ConfigKey.Domain get() = ConfigKey.Domain("blogtruyen.vn") - override val availableSortOrders: Set - get() = EnumSet.of(SortOrder.UPDATED) - override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) override fun onCreateConfig(keys: MutableCollection>) { @@ -31,8 +28,8 @@ internal class BlogTruyenVNParser(context: MangaLoaderContext) : keys.add(userAgentKey) } - private val dateFormat = SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.US) - private var cacheTags = SuspendLazy(::fetchTags) + override val availableSortOrders: Set + get() = EnumSet.of(SortOrder.UPDATED) override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( @@ -43,6 +40,9 @@ internal class BlogTruyenVNParser(context: MangaLoaderContext) : availableTags = cacheTags.get().values.toSet(), ) + private val dateFormat = SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.US) + private var cacheTags = SuspendLazy(::fetchTags) + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { return when { !filter.query.isNullOrEmpty() -> { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt index 415cc152..0c49ac20 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt @@ -13,6 +13,15 @@ import java.util.* @MangaSourceParser("LXMANGA", "LxManga", "vi") internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.LXMANGA, 60) { + override val configKeyDomain = ConfigKey.Domain("lxmanga.click") + + override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) + + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + override val availableSortOrders: Set = EnumSet.of( SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL_DESC, @@ -21,9 +30,6 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context, SortOrder.POPULARITY, ) - override val configKeyDomain = ConfigKey.Domain("lxmanga.click") - - override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( @@ -35,11 +41,6 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context, availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), ) - override fun onCreateConfig(keys: MutableCollection>) { - super.onCreateConfig(keys) - keys.add(userAgentKey) - } - override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val url = buildString { append("https://") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt index 60774c2b..13dabdbd 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt @@ -13,29 +13,44 @@ import java.util.* @MangaSourceParser("TRUYENQQ", "Truyenqq", "vi") internal class Truyenqq(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.TRUYENQQ, 42) { - override val availableSortOrders: Set = - EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST) - override val configKeyDomain = ConfigKey.Domain("truyenqqto.com") + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + + override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) + + override val availableSortOrders: Set = + EnumSet.of( + SortOrder.UPDATED, + SortOrder.UPDATED_ASC, + SortOrder.POPULARITY, + SortOrder.POPULARITY_ASC, + SortOrder.NEWEST, + SortOrder.NEWEST_ASC, + ) + override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isMultipleTagsSupported = true, + isTagsExclusionSupported = true, isSearchSupported = true, ) override suspend fun getFilterOptions() = MangaListFilterOptions( availableTags = fetchAvailableTags(), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), + availableContentTypes = EnumSet.of( + ContentType.MANGA, + ContentType.MANHWA, + ContentType.MANHUA, + ContentType.COMICS, + ContentType.OTHER, + ), ) - override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) - - override fun onCreateConfig(keys: MutableCollection>) { - super.onCreateConfig(keys) - keys.add(userAgentKey) - } - override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val url = when { !filter.query.isNullOrEmpty() -> { @@ -54,31 +69,58 @@ internal class Truyenqq(context: MangaLoaderContext) : PagedMangaParser(context, append(domain) append("/tim-kiem-nang-cao/trang-") append(page.toString()) - append(".html?country=0&sort=") + append(".html?country=") + + if (filter.types.isNotEmpty()) { + filter.types.oneOrThrowIfMany()?.let { + append( + when (it) { + ContentType.MANHUA -> '1' + ContentType.OTHER -> '2' // Việt Nam + ContentType.MANHWA -> '3' + ContentType.MANGA -> '4' + ContentType.COMICS -> '5' + else -> '0' // all + }, + ) + } + } else append('0') + + + append("&sort=") when (order) { - SortOrder.POPULARITY -> append("4") - SortOrder.UPDATED -> append("2") - SortOrder.NEWEST -> append("0") - else -> append("2") + SortOrder.NEWEST -> append('0') + SortOrder.NEWEST_ASC -> append('1') + SortOrder.UPDATED -> append('2') + SortOrder.UPDATED_ASC -> append('3') + SortOrder.POPULARITY -> append('4') + SortOrder.POPULARITY_ASC -> append('5') + else -> append('2') } + + append("&status=") if (filter.states.isNotEmpty()) { filter.states.oneOrThrowIfMany()?.let { - append("&status=") + append( when (it) { - MangaState.ONGOING -> "0" - MangaState.FINISHED -> "1" + MangaState.ONGOING -> '0' + MangaState.FINISHED -> '1' else -> "-1" }, ) } } else { - append("&status=-1") + append("-1") } append("&category=") append(filter.tags.joinToString(separator = ",") { it.key }) - append("¬category=&minchapter=0") + + append("¬category=") + append(filter.tagsExclude.joinToString(separator = ",") { it.key }) + + append("&minchapter=0") } } } @@ -87,13 +129,13 @@ internal class Truyenqq(context: MangaLoaderContext) : PagedMangaParser(context, val href = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( id = generateUid(href), - title = li.selectFirstOrThrow(".book_name").text(), + title = li.selectFirst(".book_name")?.text().orEmpty(), altTitle = null, url = href, publicUrl = href.toAbsoluteUrl(domain), rating = RATING_UNKNOWN, isNsfw = isNsfwSource, - coverUrl = li.selectFirstOrThrow("img").src().orEmpty(), + coverUrl = li.selectFirst("img")?.src().orEmpty(), tags = emptySet(), state = null, author = null, @@ -126,7 +168,7 @@ internal class Truyenqq(context: MangaLoaderContext) : PagedMangaParser(context, source = source, ) }, - state = when (doc.selectFirstOrThrow(".status p.col-xs-9").text()) { + state = when (doc.selectFirst(".status p.col-xs-9")?.text()) { "Đang Cập Nhật" -> MangaState.ONGOING "Hoàn Thành" -> MangaState.FINISHED else -> null @@ -137,7 +179,7 @@ internal class Truyenqq(context: MangaLoaderContext) : PagedMangaParser(context, val a = div.selectFirstOrThrow("a") val href = a.attrAsRelativeUrl("href") val name = a.text() - val dateText = div.selectFirstOrThrow(".time-chap").text() + val dateText = div.selectFirst(".time-chap")?.text() MangaChapter( id = generateUid(href), name = name, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt index 16d73e09..3989b25a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt @@ -24,9 +24,6 @@ internal abstract class VmpParser( override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) - protected open val listUrl = "xxx/" - protected open val geneUrl = "genero/" - override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isSearchSupported = true, @@ -41,6 +38,9 @@ internal abstract class VmpParser( searchPaginator.firstPage = 1 } + protected open val listUrl = "xxx/" + protected open val geneUrl = "genero/" + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val url = buildString { append("https://") @@ -83,7 +83,7 @@ internal abstract class VmpParser( url = href, publicUrl = href.toAbsoluteUrl(div.host ?: domain), coverUrl = div.selectFirst("img")?.src().orEmpty(), - title = div.selectFirstOrThrow("h2").text().orEmpty(), + title = div.selectFirst("h2")?.text().orEmpty(), altTitle = null, rating = RATING_UNKNOWN, tags = emptySet(), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/WpComicsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/WpComicsParser.kt index 138bb63c..71d61f17 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/WpComicsParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/WpComicsParser.kt @@ -38,8 +38,15 @@ internal abstract class WpComicsParser( SortOrder.RATING, ) - protected open val listUrl = "/tim-truyen" - protected open val datePattern = "dd/MM/yy" + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isSearchSupported = true, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), + ) init { paginator.firstPage = 1 @@ -62,15 +69,8 @@ internal abstract class WpComicsParser( "完結済み", ) - override val filterCapabilities: MangaListFilterCapabilities - get() = MangaListFilterCapabilities( - isSearchSupported = true, - ) - - override suspend fun getFilterOptions() = MangaListFilterOptions( - availableTags = fetchAvailableTags(), - availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), - ) + protected open val listUrl = "/tim-truyen" + protected open val datePattern = "dd/MM/yy" override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val response = diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ZeistMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ZeistMangaParser.kt index 0b8beec7..2953e077 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ZeistMangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ZeistMangaParser.kt @@ -32,13 +32,16 @@ internal abstract class ZeistMangaParser( override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) - protected open val datePattern = "yyyy-MM-dd" - override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isSearchSupported = true, ) + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED), + ) + @JvmField protected val ongoing: Set = hashSetOf( "ongoing", @@ -82,10 +85,7 @@ internal abstract class ZeistMangaParser( protected open val mangaCategory: String = "Series" - override suspend fun getFilterOptions() = MangaListFilterOptions( - availableTags = fetchAvailableTags(), - availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED), - ) + protected open val datePattern = "yyyy-MM-dd" override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val startIndex = maxMangaResults * (page - 1) + 1 diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zh/Baozimh.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zh/Baozimh.kt index 3714c145..822b09bd 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zh/Baozimh.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zh/Baozimh.kt @@ -17,8 +17,6 @@ import java.util.* internal class Baozimh(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.BAOZIMH, pageSize = 36) { - override val availableSortOrders: Set = EnumSet.of(SortOrder.POPULARITY) - override val configKeyDomain = ConfigKey.Domain("www.baozimh.com") override fun onCreateConfig(keys: MutableCollection>) { @@ -26,7 +24,7 @@ internal class Baozimh(context: MangaLoaderContext) : keys.add(userAgentKey) } - private val tagsMap = SuspendLazy(::parseTags) + override val availableSortOrders: Set = EnumSet.of(SortOrder.POPULARITY) override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( @@ -36,8 +34,16 @@ internal class Baozimh(context: MangaLoaderContext) : override suspend fun getFilterOptions() = MangaListFilterOptions( availableTags = tagsMap.get().values.toSet(), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), + availableContentTypes = EnumSet.of( + ContentType.MANGA, + ContentType.MANHWA, + ContentType.MANHUA, + ContentType.COMICS, + ), ) + private val tagsMap = SuspendLazy(::parseTags) + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { when { !filter.query.isNullOrEmpty() -> { @@ -55,20 +61,33 @@ internal class Baozimh(context: MangaLoaderContext) : val url = buildString { append("https://") append(domain) - append("/api/bzmhq/amp_comic_list?filter=*®ion=all") + append("/api/bzmhq/amp_comic_list?filter=*®ion=") + + if (filter.types.isNotEmpty()) { + filter.types.oneOrThrowIfMany().let { + append( + when (it) { + ContentType.MANGA -> "jp" + ContentType.MANHWA -> "kr" + ContentType.MANHUA -> "cn" + ContentType.COMICS -> "en" + else -> "all" + }, + ) + } + } else append("all") + + append("&type=") if (filter.tags.isNotEmpty()) { filter.tags.oneOrThrowIfMany()?.let { - append("&type=") append(it.key) } - } else { - append("&type=all") - } + } else append("all") + append("&state=") if (filter.states.isNotEmpty()) { filter.states.oneOrThrowIfMany()?.let { - append("&state=") append( when (it) { MangaState.ONGOING -> "serial" @@ -77,9 +96,7 @@ internal class Baozimh(context: MangaLoaderContext) : }, ) } - } else { - append("&state=all") - } + } else append("all") append("&limit=36&page=") append(page.toString()) @@ -118,7 +135,7 @@ internal class Baozimh(context: MangaLoaderContext) : url = href, publicUrl = href, coverUrl = div.selectFirst("amp-img")?.src().orEmpty(), - title = div.selectFirstOrThrow(".comics-card__title h3").text(), + title = div.selectFirst(".comics-card__title h3")?.text().orEmpty(), altTitle = null, rating = RATING_UNKNOWN, tags = emptySet(), @@ -148,7 +165,7 @@ internal class Baozimh(context: MangaLoaderContext) : override suspend fun getDetails(manga: Manga): Manga { val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() - val state = doc.selectFirstOrThrow(".tag-list span.tag").text() + val state = doc.selectFirst(".tag-list span.tag")?.text() val tagMap = tagsMap.get() val selectTag = doc.select(".tag-list span.tag").drop(1) val tags = selectTag.mapNotNullToSet { tagMap[it.text()] } @@ -174,7 +191,7 @@ internal class Baozimh(context: MangaLoaderContext) : val url = a.attrAsRelativeUrl("href").toAbsoluteUrl(domain) MangaChapter( id = generateUid(url), - name = a.selectFirstOrThrow("span").text(), + name = a.selectFirst("span")?.text() ?: "Chapter ${i + 1f}", number = i + 1f, volume = 0, url = url, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt index 475c0022..db6512d0 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt @@ -35,18 +35,24 @@ internal abstract class ZMangaParser( SortOrder.ALPHABETICAL_DESC, ) - protected open val listUrl = "advanced-search/" - protected open val datePattern = "MMMM d, yyyy" - override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isMultipleTagsSupported = true, isSearchSupported = true, + isSearchWithFiltersSupported = true, + isYearSupported = true, ) override suspend fun getFilterOptions() = MangaListFilterOptions( availableTags = fetchAvailableTags(), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), + availableContentTypes = EnumSet.of( + ContentType.MANGA, + ContentType.MANHWA, + ContentType.MANHUA, + ContentType.ONE_SHOT, + ContentType.DOUJINSHI, + ), ) init { @@ -66,6 +72,11 @@ internal abstract class ZMangaParser( "Completed", ) + protected open val listUrl = "advanced-search/" + protected open val datePattern = "MMMM d, yyyy" + + // https://komikindo.info/advanced-search/?title=the&author=the&artist=the&yearx=2020&status=ongoing&type=Manga&order=update + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val url = buildString { append("https://") @@ -78,44 +89,69 @@ internal abstract class ZMangaParser( append('/') } - when { + append("?order=") + when (order) { + SortOrder.POPULARITY -> append("popular") + SortOrder.UPDATED -> append("update") + SortOrder.ALPHABETICAL -> append("title") + SortOrder.ALPHABETICAL_DESC -> append("titlereverse") + SortOrder.NEWEST -> append("latest") + SortOrder.RATING -> append("rating") + else -> append("update") + } - !filter.query.isNullOrEmpty() -> { - append("&title=") - append(filter.query.urlEncoded()) - } + filter.query?.let { + append("&title=") + append(filter.query.urlEncoded()) + } - else -> { - - append("?order=") - when (order) { - SortOrder.POPULARITY -> append("popular") - SortOrder.UPDATED -> append("update") - SortOrder.ALPHABETICAL -> append("title") - SortOrder.ALPHABETICAL_DESC -> append("titlereverse") - SortOrder.NEWEST -> append("latest") - SortOrder.RATING -> append("rating") - else -> null - } - - 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 -> "" - }, - ) - } - } + // author + // filter.author?.let { + // append("&author=") + // append(filter.author.urlEncoded()) + // } + + // artist + // filter.artist?.let { + // append("&artist=") + // append(filter.artist.urlEncoded()) + // } + + if (filter.year != 0) { + append("&yearx=") + append(filter.year) + } + + filter.types.oneOrThrowIfMany()?.let { + append("&type=") + append( + when (it) { + ContentType.MANGA -> "Manga" + ContentType.MANHWA -> "Manhwa" + ContentType.MANHUA -> "Manhua" + ContentType.ONE_SHOT -> "One-shot" + ContentType.DOUJINSHI -> "Doujinshi" + else -> "" + }, + ) + } + + 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 -> "" + }, + ) } }