add filter : LikeMangaParser, FmTeam, MadthemeParser , MangaReaderParser

Fix GetList : NicovideoSeigaParser, Manga18Parser

add filter & multitags on MangaboxParser
pull/401/head
devi 2 years ago
parent 7c2ac033d7
commit 37e9b33c24

@ -18,51 +18,83 @@ internal class FmTeam(context: MangaLoaderContext) :
PagedMangaParser(context, MangaSource.FMTEAM, 0) { PagedMangaParser(context, MangaSource.FMTEAM, 0) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
override val configKeyDomain = ConfigKey.Domain("fmteam.fr") override val configKeyDomain = ConfigKey.Domain("fmteam.fr")
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
if (page > 1) { if (page > 1) {
return emptyList() return emptyList()
} }
val jsonManga = if (!query.isNullOrEmpty()) { var foundTag = true
//3 letters minimum var foundState = true
webClient.httpGet("https://$domain/api/search/${query.urlEncoded()}").parseJson().getJSONArray("comics")
} else { val manga = ArrayList<Manga>()
webClient.httpGet("https://$domain/api/comics").parseJson().getJSONArray("comics")
} when (filter) {
is MangaListFilter.Search -> {
val jsonManga = webClient.httpGet("https://$domain/api/search/${filter.query.urlEncoded()}").parseJson()
.getJSONArray("comics")
for (i in 0 until jsonManga.length()) {
val j = jsonManga.getJSONObject(i)
val href = "/api" + j.getString("url")
manga.add(addManga(href, j))
}
}
is MangaListFilter.Advanced -> {
val jsonManga = webClient.httpGet("https://$domain/api/comics").parseJson().getJSONArray("comics")
for (i in 0 until jsonManga.length()) {
val j = jsonManga.getJSONObject(i)
val href = "/api" + j.getString("url")
val manga = ArrayList<Manga>(jsonManga.length()) if (filter.tags.isNotEmpty() && filter.states.isEmpty()) {
for (i in 0 until jsonManga.length()) { val a = j.getJSONArray("genres").toString()
val j = jsonManga.getJSONObject(i) foundTag = false
val href = "/api" + j.getString("url") filter.tags.forEach {
when { if (a.contains(it.key, ignoreCase = true)) {
!tags.isNullOrEmpty() -> { foundTag = true
val a = j.getJSONArray("genres").toString() }
var found = true
tags.forEach {
if (!a.contains(it.key, ignoreCase = true)) {
found = false
} }
} }
if (found) {
manga.add( if (filter.states.isNotEmpty()) {
addManga(href, j), val a = j.getString("status")
) foundState = false
filter.states.oneOrThrowIfMany()?.let {
if (a.contains(
when (it) {
MangaState.ONGOING -> "En cours"
MangaState.FINISHED -> "Terminé"
else -> ""
},
ignoreCase = true,
)
) {
foundState = true
}
}
}
if (foundState && foundTag) {
manga.add(addManga(href, j))
} }
} }
}
else -> { null -> {
val jsonManga = webClient.httpGet("https://$domain/api/comics").parseJson().getJSONArray("comics")
for (i in 0 until jsonManga.length()) {
val j = jsonManga.getJSONObject(i)
val href = "/api" + j.getString("url")
manga.add( manga.add(
addManga(href, j), addManga(href, j),
) )
} }
} }
} }
return manga return manga
} }

@ -39,26 +39,37 @@ class NicovideoSeigaParser(context: MangaLoaderContext) :
SortOrder.POPULARITY, SortOrder.POPULARITY,
) )
override val isMultipleTagsSupported = false
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("nicovideo.jp") override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("nicovideo.jp")
@InternalParsersApi @InternalParsersApi
override suspend fun getList( override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
offset: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val page = (offset / 20f).toIntUp().inc() val page = (offset / 20f).toIntUp().inc()
val domain = getDomain("seiga") val domain = getDomain("seiga")
val url = when { val url =
!query.isNullOrEmpty() -> return if (offset == 0) getSearchList(query, page) else emptyList() when (filter) {
tags.isNullOrEmpty() -> "https://$domain/manga/list?page=$page&sort=${getSortKey(sortOrder)}" is MangaListFilter.Search -> {
tags.size == 1 -> "https://$domain/manga/list?category=${tags.first().key}&page=$page" + return if (offset == 0) getSearchList(filter.query, page) else emptyList()
"&sort=${getSortKey(sortOrder)}" }
tags.size > 1 -> throw IllegalArgumentException("This source supports only 1 category") is MangaListFilter.Advanced -> {
else -> "https://$domain/manga/list?page=$page&sort=${getSortKey(sortOrder)}"
}
if (filter.tags.isNotEmpty()) {
filter.tags.oneOrThrowIfMany().let {
"https://$domain/manga/list?category=${it?.key}&page=$page&sort=${getSortKey(filter.sortOrder)}"
}
} else {
"https://$domain/manga/list?page=$page&sort=${getSortKey(filter.sortOrder)}"
}
}
null -> "https://$domain/manga/list?page=$page"
}
val doc = webClient.httpGet(url).parseHtml() val doc = webClient.httpGet(url).parseHtml()
val comicList = doc.body().select("#comic_list > ul > li") ?: doc.parseFailed("Container not found") val comicList = doc.body().select("#comic_list > ul > li") ?: doc.parseFailed("Container not found")
val items = comicList.select("div > .description > div > div") val items = comicList.select("div > .description > div > div")
@ -145,12 +156,12 @@ class NicovideoSeigaParser(context: MangaLoaderContext) :
override suspend fun getAvailableTags(): Set<MangaTag> { override suspend fun getAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://${getDomain("seiga")}/manga/list").parseHtml() val doc = webClient.httpGet("https://${getDomain("seiga")}/manga/list").parseHtml()
val root = doc.body().selectOrThrow("#mg_category_list > ul > li") val root = doc.body().selectOrThrow("#mg_category_list > ul > li").drop(1)
return root.mapToSet { li -> return root.mapToSet { li ->
val a = li.selectFirstOrThrow("a") val a = li.selectFirstOrThrow("a")
MangaTag( MangaTag(
title = a.text(), title = a.text(),
key = a.attrAsRelativeUrlOrNull("href").orEmpty(), key = a.attrAsRelativeUrl("href").substringAfter("category=").substringBefore("&"),
source = source, source = source,
) )
} }

@ -23,44 +23,75 @@ internal abstract class LikeMangaParser(
override val availableSortOrders: Set<SortOrder> = override val availableSortOrders: Set<SortOrder> =
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST) EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST)
override val availableStates: Set<MangaState> =
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED)
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)
override val isMultipleTagsSupported = false override val isMultipleTagsSupported = false
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
append("/?act=search&f") append("/?act=search")
append("[sortby]".urlEncoded())
append("=") when (filter) {
when (sortOrder) { is MangaListFilter.Search -> {
SortOrder.POPULARITY -> append("hot") append("&f")
SortOrder.UPDATED -> append("lastest-chap") append("[keyword]".urlEncoded())
SortOrder.NEWEST -> append("lastest-manga") append("=")
else -> append("lastest-chap") append(filter.query.urlEncoded())
}
is MangaListFilter.Advanced -> {
append("&f")
append("[sortby]".urlEncoded())
append("=")
when (filter.sortOrder) {
SortOrder.POPULARITY -> append("hot")
SortOrder.UPDATED -> append("lastest-chap")
SortOrder.NEWEST -> append("lastest-manga")
else -> append("lastest-chap")
}
if (filter.tags.isNotEmpty()) {
append("&f")
append("[genres]".urlEncoded())
append("=")
filter.tags.oneOrThrowIfMany()?.let {
append(it.key)
}
}
filter.states.oneOrThrowIfMany()?.let {
append("&f")
append("[status]".urlEncoded())
append("=")
append(
when (it) {
MangaState.ONGOING -> "in-process"
MangaState.FINISHED -> "complete"
MangaState.PAUSED -> "pause"
else -> "all"
},
)
}
}
null -> {
append("&f")
append("[sortby]".urlEncoded())
append("=lastest-chap")
}
} }
if (page > 1) { if (page > 1) {
append("&pageNum=") append("&pageNum=")
append(page) append(page)
} }
if (!tags.isNullOrEmpty()) {
append("&f")
append("[genres]".urlEncoded())
append("=")
append(tag?.key.orEmpty())
}
if (!query.isNullOrEmpty()) {
append("&f")
append("[keyword]".urlEncoded())
append("=")
append(query.urlEncoded())
}
} }
val doc = webClient.httpGet(url).parseHtml() val doc = webClient.httpGet(url).parseHtml()
return doc.select("div.card-body div.video").map { div -> return doc.select("div.card-body div.video").map { div ->

@ -29,6 +29,8 @@ internal abstract class MadthemeParser(
SortOrder.RATING, SortOrder.RATING,
) )
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
protected open val listUrl = "search/" protected open val listUrl = "search/"
protected open val datePattern = "MMM dd, yyyy" protected open val datePattern = "MMM dd, yyyy"
@ -52,35 +54,52 @@ internal abstract class MadthemeParser(
"COMPLETED", "COMPLETED",
) )
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
append("/$listUrl?sort=") append('/')
when (sortOrder) { append(listUrl)
SortOrder.POPULARITY -> append("views") when (filter) {
SortOrder.UPDATED -> append("updated_at")
SortOrder.ALPHABETICAL -> append("name") // On some sites without tags or searches, the alphabetical option is empty.
SortOrder.NEWEST -> append("created_at")
SortOrder.RATING -> append("rating")
}
if (!query.isNullOrEmpty()) {
append("&q=")
append(query.urlEncoded())
}
if (!tags.isNullOrEmpty()) { is MangaListFilter.Search -> {
for (tag in tags) { append("?sort=updated_at&q=")
append("&") append(filter.query.urlEncoded())
append("genre[]".urlEncoded())
append("=")
append(tag.key)
} }
is MangaListFilter.Advanced -> {
append("?sort=")
when (filter.sortOrder) {
SortOrder.POPULARITY -> append("views")
SortOrder.UPDATED -> append("updated_at")
SortOrder.ALPHABETICAL -> append("name") // On some sites without tags or searches, the alphabetical option is empty.
SortOrder.NEWEST -> append("created_at")
SortOrder.RATING -> append("rating")
}
if (filter.tags.isNotEmpty()) {
filter.tags.forEach {
append("&")
append("genre[]".urlEncoded())
append("=")
append(it.key)
}
}
filter.states.oneOrThrowIfMany()?.let {
append("&status=")
append(
when (it) {
MangaState.ONGOING -> "ongoing"
MangaState.FINISHED -> "completed"
else -> "all"
},
)
}
}
null -> append("?sort=updated_at")
} }
append("&page=") append("&page=")
@ -117,7 +136,7 @@ internal abstract class MadthemeParser(
override suspend fun getAvailableTags(): Set<MangaTag> { override suspend fun getAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml()
return doc.select("div.genres label.checkbox").mapNotNullToSet { checkbox -> return doc.select("div.genres .checkbox").mapNotNullToSet { checkbox ->
val key = checkbox.selectFirstOrThrow("input").attr("value") ?: return@mapNotNullToSet null val key = checkbox.selectFirstOrThrow("input").attr("value") ?: return@mapNotNullToSet null
val name = checkbox.selectFirstOrThrow("span.radio__label").text() val name = checkbox.selectFirstOrThrow("span.radio__label").text()
MangaTag( MangaTag(

@ -7,44 +7,58 @@ import org.koitharu.kotatsu.parsers.site.madtheme.MadthemeParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.Locale import java.util.Locale
@MangaSourceParser("MANHUASCAN", "ManhuaScan", "") @MangaSourceParser("MANHUASCAN", "ManhuaScan.io", "")
internal class ManhuaScan(context: MangaLoaderContext) : internal class ManhuaScan(context: MangaLoaderContext) :
MadthemeParser(context, MangaSource.MANHUASCAN, "manhuascan.io") { MadthemeParser(context, MangaSource.MANHUASCAN, "manhuascan.io") {
override val sourceLocale: Locale = Locale.ENGLISH override val sourceLocale: Locale = Locale.ENGLISH
override val listUrl = "search" override val listUrl = "search"
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
append('/') append('/')
append(listUrl) append(listUrl)
append("?sort=") when (filter) {
when (sortOrder) {
SortOrder.POPULARITY -> append("views")
SortOrder.UPDATED -> append("updated_at")
SortOrder.ALPHABETICAL -> append("name")
SortOrder.NEWEST -> append("created_at")
SortOrder.RATING -> append("rating")
}
if (!query.isNullOrEmpty()) { is MangaListFilter.Search -> {
append("&q=") append("?sort=updated_at&q=")
append(query.urlEncoded()) append(filter.query.urlEncoded())
} }
is MangaListFilter.Advanced -> {
append("?sort=")
when (filter.sortOrder) {
SortOrder.POPULARITY -> append("views")
SortOrder.UPDATED -> append("updated_at")
SortOrder.ALPHABETICAL -> append("name")
SortOrder.NEWEST -> append("created_at")
SortOrder.RATING -> append("rating")
}
if (filter.tags.isNotEmpty()) {
filter.tags.forEach {
append("&")
append("include[]".urlEncoded())
append("=")
append(it.key)
}
}
filter.states.oneOrThrowIfMany()?.let {
append("&status=")
append(
when (it) {
MangaState.ONGOING -> "ongoing"
MangaState.FINISHED -> "completed"
else -> "all"
},
)
}
if (!tags.isNullOrEmpty()) {
for (tag in tags) {
append("&")
append("include[]".urlEncoded())
append("=")
append(tag.key)
} }
null -> append("?sort=updated_at")
} }
append("&page=") append("&page=")

@ -34,6 +34,5 @@ internal class MangaBuddy(context: MangaLoaderContext) :
) )
} }
return pages return pages
} }
} }

@ -9,6 +9,5 @@ import org.koitharu.kotatsu.parsers.site.madtheme.MadthemeParser
@MangaSourceParser("TOONITUBE", "TooniTube", "en", ContentType.HENTAI) @MangaSourceParser("TOONITUBE", "TooniTube", "en", ContentType.HENTAI)
internal class TooniTube(context: MangaLoaderContext) : internal class TooniTube(context: MangaLoaderContext) :
MadthemeParser(context, MangaSource.TOONITUBE, "toonitube.com") { MadthemeParser(context, MangaSource.TOONITUBE, "toonitube.com") {
override val selectDesc = "div.summary div.section-body p.content" override val selectDesc = "div.summary div.section-body p.content"
} }

@ -9,6 +9,5 @@ import org.koitharu.kotatsu.parsers.site.madtheme.MadthemeParser
@MangaSourceParser("TOONILY_ME", "Toonily.Me", "en", ContentType.HENTAI) @MangaSourceParser("TOONILY_ME", "Toonily.Me", "en", ContentType.HENTAI)
internal class ToonilyMe(context: MangaLoaderContext) : internal class ToonilyMe(context: MangaLoaderContext) :
MadthemeParser(context, MangaSource.TOONILY_ME, "toonily.me") { MadthemeParser(context, MangaSource.TOONILY_ME, "toonily.me") {
override val selectDesc = "div.summary div.section-body p.content" override val selectDesc = "div.summary div.section-body p.content"
} }

@ -26,6 +26,8 @@ internal abstract class Manga18Parser(
SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL,
) )
override val isMultipleTagsSupported = false
protected open val listUrl = "list-manga/" protected open val listUrl = "list-manga/"
protected open val tagUrl = "manga-list/" protected open val tagUrl = "manga-list/"
protected open val datePattern = "dd-MM-yyyy" protected open val datePattern = "dd-MM-yyyy"
@ -47,49 +49,53 @@ internal abstract class Manga18Parser(
"Completed", "Completed",
) )
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
when { append('/')
!query.isNullOrEmpty() -> { when (filter) {
append("/$listUrl")
is MangaListFilter.Search -> {
append(listUrl)
append(page.toString()) append(page.toString())
append("?search=") append("?search=")
append(query.urlEncoded()) append(filter.query.urlEncoded())
append("&") append("&order_by=latest")
} }
!tags.isNullOrEmpty() -> { is MangaListFilter.Advanced -> {
append("/$tagUrl") if (filter.tags.isNotEmpty()) {
append(tag?.key.orEmpty()) filter.tags.oneOrThrowIfMany()?.let {
append("/") append(tagUrl)
append(it.key)
append("/")
}
} else {
append(listUrl)
}
append(page.toString()) append(page.toString())
append("?") append("?order_by=")
when (filter.sortOrder) {
SortOrder.POPULARITY -> append("views")
SortOrder.UPDATED -> append("lastest")
SortOrder.ALPHABETICAL -> append("name")
else -> append("latest")
}
} }
else -> { null -> {
append("/$listUrl") append(listUrl)
append(page.toString()) append(page.toString())
append("?") append("?order_by=latest")
} }
} }
append("order_by=")
when (sortOrder) {
SortOrder.POPULARITY -> append("views")
SortOrder.UPDATED -> append("lastest")
SortOrder.ALPHABETICAL -> append("name")
else -> append("latest")
}
} }
val doc = webClient.httpGet(url).parseHtml() return parseMangaList(webClient.httpGet(url).parseHtml())
}
protected open fun parseMangaList(doc: Document): List<Manga> {
return doc.select("div.story_item").map { div -> return doc.select("div.story_item").map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
Manga( Manga(

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.parsers.site.manga18.en package org.koitharu.kotatsu.parsers.site.manga18.en
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
@ -10,51 +11,7 @@ import org.koitharu.kotatsu.parsers.util.*
internal class Hentai3zCc(context: MangaLoaderContext) : internal class Hentai3zCc(context: MangaLoaderContext) :
Manga18Parser(context, MangaSource.HENTAI3ZCC, "hentai3z.cc") { Manga18Parser(context, MangaSource.HENTAI3ZCC, "hentai3z.cc") {
override suspend fun getListPage( override fun parseMangaList(doc: Document): List<Manga> {
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString {
append("https://")
append(domain)
val pages = page + 1
when {
!query.isNullOrEmpty() -> {
append("/$listUrl")
append(pages.toString())
append("?search=")
append(query.urlEncoded())
append("&")
}
!tags.isNullOrEmpty() -> {
append("/$tagUrl")
append(tag?.key.orEmpty())
append("/")
append(pages.toString())
append("?")
}
else -> {
append("/$listUrl")
append(pages.toString())
append("?")
}
}
append("order_by=")
when (sortOrder) {
SortOrder.POPULARITY -> append("views")
SortOrder.UPDATED -> append("lastest")
SortOrder.ALPHABETICAL -> append("name")
else -> append("latest")
}
}
val doc = webClient.httpGet(url).parseHtml()
return doc.select("div.story_item").map { div -> return doc.select("div.story_item").map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
Manga( Manga(

@ -9,7 +9,6 @@ import org.koitharu.kotatsu.parsers.site.manga18.Manga18Parser
@MangaSourceParser("TUMANHWAS", "Tumanhwas", "es", ContentType.HENTAI) @MangaSourceParser("TUMANHWAS", "Tumanhwas", "es", ContentType.HENTAI)
internal class Tumanhwas(context: MangaLoaderContext) : internal class Tumanhwas(context: MangaLoaderContext) :
Manga18Parser(context, MangaSource.TUMANHWAS, "tumanhwas.club") { Manga18Parser(context, MangaSource.TUMANHWAS, "tumanhwas.club") {
override val selectTag = "div.item:contains(Géneros) div.info_value a" override val selectTag = "div.item:contains(Géneros) div.info_value a"
override val selectAlt = "div.item:contains(Títulos alternativos) div.info_value" override val selectAlt = "div.item:contains(Títulos alternativos) div.info_value"
} }

@ -5,6 +5,7 @@ import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.PagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.text.DateFormat import java.text.DateFormat
@ -21,9 +22,12 @@ internal abstract class MangaboxParser(
SortOrder.UPDATED, SortOrder.UPDATED,
SortOrder.POPULARITY, SortOrder.POPULARITY,
SortOrder.NEWEST, SortOrder.NEWEST,
SortOrder.ALPHABETICAL,
) )
protected open val listUrl = "/genre-all" override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
protected open val listUrl = "/advanced_search"
protected open val searchUrl = "/search/story/" protected open val searchUrl = "/search/story/"
protected open val datePattern = "MMM dd,yy" protected open val datePattern = "MMM dd,yy"
@ -36,50 +40,64 @@ internal abstract class MangaboxParser(
@JvmField @JvmField
protected val ongoing: Set<String> = setOf( protected val ongoing: Set<String> = setOf(
"Ongoing", "ongoing",
) )
@JvmField @JvmField
protected val finished: Set<String> = setOf( protected val finished: Set<String> = setOf(
"Completed", "completed",
) )
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
append(listUrl)
append("/?s=all")
when (filter) {
if (!query.isNullOrEmpty()) { is MangaListFilter.Search -> {
append(searchUrl) append("&keyw=")
append(query.urlEncoded()) append(filter.query.urlEncoded())
append("?page=")
append(page.toString())
} else if (!tags.isNullOrEmpty()) {
append("/")
append(tag?.key.orEmpty())
append("/")
append(page.toString())
} else {
append("$listUrl/")
if (page > 1) {
append(page.toString())
} }
when (sortOrder) {
SortOrder.POPULARITY -> append("?type=topview") is MangaListFilter.Advanced -> {
SortOrder.UPDATED -> append("")
SortOrder.NEWEST -> append("?type=newest") if (filter.tags.isNotEmpty()) {
else -> append("") append("&g_i=")
filter.tags.forEach {
append("_")
append(it.key)
append("_")
}
}
filter.states.oneOrThrowIfMany()?.let {
append("&sts=")
append(
when (it) {
MangaState.ONGOING -> "ongoing"
MangaState.FINISHED -> "completed"
else -> ""
},
)
}
append("&orby=")
when (filter.sortOrder) {
SortOrder.POPULARITY -> append("topview")
SortOrder.UPDATED -> append("")
SortOrder.NEWEST -> append("newest")
SortOrder.ALPHABETICAL -> append("az")
else -> append("")
}
} }
}
null -> {}
}
append("&page=")
append(page.toString())
} }
val doc = webClient.httpGet(url).parseHtml() val doc = webClient.httpGet(url).parseHtml()
@ -109,7 +127,8 @@ internal abstract class MangaboxParser(
override suspend fun getAvailableTags(): Set<MangaTag> { override suspend fun getAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml()
return doc.select(selectTagMap).mapNotNullToSet { a -> val tags = doc.select(selectTagMap).drop(1) // remove all tags
return tags.mapNotNullToSet { a ->
val key = a.attr("href").removeSuffix('/').substringAfterLast('/') val key = a.attr("href").removeSuffix('/').substringAfterLast('/')
val name = a.attr("title").replace(" Manga", "") val name = a.attr("title").replace(" Manga", "")
MangaTag( MangaTag(
@ -129,25 +148,18 @@ internal abstract class MangaboxParser(
override suspend fun getDetails(manga: Manga): Manga = coroutineScope { override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
val fullUrl = manga.url.toAbsoluteUrl(domain) val fullUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml() val doc = webClient.httpGet(fullUrl).parseHtml()
val chaptersDeferred = async { getChapters(doc) } val chaptersDeferred = async { getChapters(doc) }
val desc = doc.selectFirstOrThrow(selectDesc).html() val desc = doc.selectFirstOrThrow(selectDesc).html()
val stateDiv = doc.select(selectState).text() val stateDiv = doc.select(selectState).text()
val state = stateDiv.let { val state = stateDiv.let {
when (it) { when (it.lowercase()) {
in ongoing -> MangaState.ONGOING in ongoing -> MangaState.ONGOING
in finished -> MangaState.FINISHED in finished -> MangaState.FINISHED
else -> null else -> null
} }
} }
val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "") val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "")
val aut = doc.body().select(selectAut).eachText().joinToString() val aut = doc.body().select(selectAut).eachText().joinToString()
manga.copy( manga.copy(
tags = doc.body().select(selectTag).mapNotNullToSet { a -> tags = doc.body().select(selectTag).mapNotNullToSet { a ->
MangaTag( MangaTag(

@ -9,13 +9,7 @@ import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
@MangaSourceParser("HMANGABAT", "MangaBat", "en") @MangaSourceParser("HMANGABAT", "MangaBat", "en")
internal class Mangabat(context: MangaLoaderContext) : internal class Mangabat(context: MangaLoaderContext) :
MangaboxParser(context, MangaSource.HMANGABAT) { MangaboxParser(context, MangaSource.HMANGABAT) {
override val configKeyDomain = ConfigKey.Domain("h.mangabat.com", "readmangabat.com") override val configKeyDomain = ConfigKey.Domain("h.mangabat.com", "readmangabat.com")
override val otherDomain = "readmangabat.com" override val otherDomain = "readmangabat.com"
override val searchUrl = "/search/manga/"
override val listUrl = "/manga-list-all"
override val selectTagMap = "div.panel-category p.pn-category-row:not(.pn-category-row-border) a" override val selectTagMap = "div.panel-category p.pn-category-row:not(.pn-category-row-border) a"
} }

@ -6,6 +6,7 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaState import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
@ -13,73 +14,87 @@ import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.EnumSet
@MangaSourceParser("MANGAIRO", "MangaIro", "en") @MangaSourceParser("MANGAIRO", "MangaIro", "en")
internal class Mangairo(context: MangaLoaderContext) : internal class Mangairo(context: MangaLoaderContext) :
MangaboxParser(context, MangaSource.MANGAIRO) { MangaboxParser(context, MangaSource.MANGAIRO) {
override val configKeyDomain = ConfigKey.Domain("w.mangairo.com", "chap.mangairo.com") override val configKeyDomain = ConfigKey.Domain("w.mangairo.com", "chap.mangairo.com")
override val otherDomain = "chap.mangairo.com" override val otherDomain = "chap.mangairo.com"
override val datePattern = "MMM-dd-yy" override val datePattern = "MMM-dd-yy"
override val listUrl = "/manga-list" override val listUrl = "/manga-list"
override val searchUrl = "/list/search/" override val searchUrl = "/list/search/"
override val selectDesc = "div#story_discription p" override val selectDesc = "div#story_discription p"
override val selectState = "ul.story_info_right li:contains(Status) a" override val selectState = "ul.story_info_right li:contains(Status) a"
override val selectAlt = "ul.story_info_right li:contains(Alter) h2" override val selectAlt = "ul.story_info_right li:contains(Alter) h2"
override val selectAut = "ul.story_info_right li:contains(Author) a" override val selectAut = "ul.story_info_right li:contains(Author) a"
override val selectTag = "ul.story_info_right li:contains(Genres) a" override val selectTag = "ul.story_info_right li:contains(Genres) a"
override val selectChapter = "div.chapter_list li" override val selectChapter = "div.chapter_list li"
override val selectDate = "p" override val selectDate = "p"
override val selectPage = "div.panel-read-story img" override val selectPage = "div.panel-read-story img"
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
override suspend fun getListPage( SortOrder.UPDATED,
page: Int, SortOrder.POPULARITY,
query: String?, SortOrder.NEWEST,
tags: Set<MangaTag>?, )
sortOrder: SortOrder, override val isMultipleTagsSupported = false
): List<Manga> { override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
when (filter) {
if (!query.isNullOrEmpty()) { is MangaListFilter.Search -> {
append(searchUrl) append(searchUrl)
append(query.urlEncoded()) append(filter.query.urlEncoded())
append("?page=") append("?page=")
append(page.toString()) }
} else {
append("$listUrl/") is MangaListFilter.Advanced -> {
append(listUrl)
append("/type-")
when (filter.sortOrder) {
SortOrder.POPULARITY -> append("topview")
SortOrder.UPDATED -> append("latest")
SortOrder.NEWEST -> append("newest")
else -> append("latest")
}
append("/type-") append("/ctg-")
when (sortOrder) { if (filter.tags.isNotEmpty()) {
SortOrder.POPULARITY -> append("topview") filter.tags.oneOrThrowIfMany()?.let {
SortOrder.UPDATED -> append("latest") append(it.key)
SortOrder.NEWEST -> append("newest") }
else -> append("latest") } else {
append("all")
}
append("/state-")
if (filter.states.isNotEmpty()) {
filter.states.oneOrThrowIfMany()?.let {
append(
when (it) {
MangaState.ONGOING -> "ongoing"
MangaState.FINISHED -> "completed"
else -> "all"
},
)
}
} else {
append("all")
}
append("/page-")
} }
if (!tags.isNullOrEmpty()) { null -> {
append("/ctg-") append(listUrl)
append(tag?.key.orEmpty()) append("/type-latest/ctg-all/state-all/page-")
} else {
append("/ctg-all")
} }
append("/state-all/page-")
append(page.toString())
} }
append(page.toString())
} }
val doc = webClient.httpGet(url).parseHtml() val doc = webClient.httpGet(url).parseHtml()
return doc.select("div.story-item").map { div -> return doc.select("div.story-item").map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
Manga( Manga(
@ -115,13 +130,9 @@ internal class Mangairo(context: MangaLoaderContext) :
override suspend fun getDetails(manga: Manga): Manga = coroutineScope { override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
val fullUrl = manga.url.toAbsoluteUrl(domain) val fullUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml() val doc = webClient.httpGet(fullUrl).parseHtml()
val chaptersDeferred = async { getChapters(doc) } val chaptersDeferred = async { getChapters(doc) }
val desc = doc.selectFirstOrThrow(selectDesc).html() val desc = doc.selectFirstOrThrow(selectDesc).html()
val stateDiv = doc.select(selectState).text() val stateDiv = doc.select(selectState).text()
val state = stateDiv.let { val state = stateDiv.let {
when (it) { when (it) {
in ongoing -> MangaState.ONGOING in ongoing -> MangaState.ONGOING
@ -131,9 +142,7 @@ internal class Mangairo(context: MangaLoaderContext) :
} }
val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "") val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "")
val aut = doc.body().select(selectAut).eachText().joinToString() val aut = doc.body().select(selectAut).eachText().joinToString()
manga.copy( manga.copy(
tags = doc.body().select(selectTag).mapNotNullToSet { a -> tags = doc.body().select(selectTag).mapNotNullToSet { a ->
MangaTag( MangaTag(
@ -151,6 +160,4 @@ internal class Mangairo(context: MangaLoaderContext) :
isNsfw = manga.isNsfw, isNsfw = manga.isNsfw,
) )
} }
} }

@ -8,54 +8,69 @@ import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.EnumSet
@MangaSourceParser("MANGAKAKALOT", "Mangakakalot", "en") @MangaSourceParser("MANGAKAKALOT", "Mangakakalot.com", "en")
internal class Mangakakalot(context: MangaLoaderContext) : internal class Mangakakalot(context: MangaLoaderContext) :
MangaboxParser(context, MangaSource.MANGAKAKALOT) { MangaboxParser(context, MangaSource.MANGAKAKALOT) {
override val configKeyDomain = ConfigKey.Domain("mangakakalot.com", "chapmanganato.com") override val configKeyDomain = ConfigKey.Domain("mangakakalot.com", "chapmanganato.com")
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
SortOrder.POPULARITY,
SortOrder.NEWEST,
)
override val isMultipleTagsSupported = false
override val otherDomain = "chapmanganato.com" override val otherDomain = "chapmanganato.com"
override val listUrl = "/manga_list" override val listUrl = "/manga_list"
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
when (filter) {
if (!query.isNullOrEmpty()) { is MangaListFilter.Search -> {
append(searchUrl) append(searchUrl)
append(query.replace(" ", "_").urlEncoded()) append(filter.query.urlEncoded())
append("?page=") append("?page=")
append(page.toString())
} else {
append("$listUrl/")
when (sortOrder) {
SortOrder.POPULARITY -> append("?type=topview")
SortOrder.UPDATED -> append("?type=latest")
SortOrder.NEWEST -> append("?type=newest")
else -> append("?type=latest")
} }
if (!tags.isNullOrEmpty()) {
append("&category=") is MangaListFilter.Advanced -> {
append(tag?.key.orEmpty()) append(listUrl)
} else { append("?type=")
append("&category=all") when (filter.sortOrder) {
SortOrder.POPULARITY -> append("topview")
SortOrder.UPDATED -> append("latest")
SortOrder.NEWEST -> append("newest")
else -> append("latest")
}
if (filter.tags.isNotEmpty()) {
append("&category=")
filter.tags.oneOrThrowIfMany()?.let {
append(it.key)
}
}
filter.states.oneOrThrowIfMany()?.let {
append("&state=")
append(
when (it) {
MangaState.ONGOING -> "ongoing"
MangaState.FINISHED -> "completed"
else -> "all"
},
)
}
append("&page=")
} }
append("&state=all&page=")
append(page)
null -> {
append(listUrl)
append("?type=latest&page=")
}
} }
append(page.toString())
} }
val doc = webClient.httpGet(url).parseHtml() val doc = webClient.httpGet(url).parseHtml()
@ -81,8 +96,21 @@ internal class Mangakakalot(context: MangaLoaderContext) :
} }
} }
override suspend fun getChapters(doc: Document): List<MangaChapter> { override suspend fun getAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml()
val tags = doc.select("ul.tag li a").drop(1)
return tags.mapNotNullToSet { a ->
val key = a.attr("href").substringAfterLast("category=").substringBefore("&")
val name = a.attr("title").replace(" Manga", "")
MangaTag(
key = key,
title = name,
source = source,
)
}
}
override suspend fun getChapters(doc: Document): List<MangaChapter> {
return doc.body().select(selectChapter).mapChapters(reversed = true) { i, li -> return doc.body().select(selectChapter).mapChapters(reversed = true) { i, li ->
val a = li.selectFirstOrThrow("a") val a = li.selectFirstOrThrow("a")
val href = a.attrAsRelativeUrl("href") val href = a.attrAsRelativeUrl("href")

@ -1,11 +1,14 @@
package org.koitharu.kotatsu.parsers.site.mangabox.en package org.koitharu.kotatsu.parsers.site.mangabox.en
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.EnumSet
@MangaSourceParser("MANGAKAKALOTTV", "Mangakakalot.tv", "en") @MangaSourceParser("MANGAKAKALOTTV", "Mangakakalot.tv", "en")
internal class MangakakalotTv(context: MangaLoaderContext) : internal class MangakakalotTv(context: MangaLoaderContext) :
@ -14,40 +17,61 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
override val configKeyDomain = ConfigKey.Domain("ww6.mangakakalot.tv") override val configKeyDomain = ConfigKey.Domain("ww6.mangakakalot.tv")
override val searchUrl = "/search/" override val searchUrl = "/search/"
override val listUrl = "/manga_list" override val listUrl = "/manga_list"
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
SortOrder.POPULARITY,
SortOrder.NEWEST,
)
override val isMultipleTagsSupported = false
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val tag = tags.oneOrThrowIfMany()
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
if (!query.isNullOrEmpty()) { when (filter) {
append(searchUrl)
append(query.urlEncoded()) is MangaListFilter.Search -> {
append("?page=") append(searchUrl)
append(page.toString()) append(filter.query.urlEncoded())
} else { append("?page=")
append("$listUrl/")
append("?type=")
when (sortOrder) {
SortOrder.POPULARITY -> append("topview")
SortOrder.UPDATED -> append("latest")
SortOrder.NEWEST -> append("newest")
else -> append("latest")
}
if (!tags.isNullOrEmpty()) {
append("&category=")
append(tag?.key.orEmpty())
} }
if (page > 1) {
is MangaListFilter.Advanced -> {
append(listUrl)
append("?type=")
when (filter.sortOrder) {
SortOrder.POPULARITY -> append("topview")
SortOrder.UPDATED -> append("latest")
SortOrder.NEWEST -> append("newest")
else -> append("latest")
}
if (filter.tags.isNotEmpty()) {
append("&category=")
filter.tags.oneOrThrowIfMany()?.let {
append(it.key)
}
}
filter.states.oneOrThrowIfMany()?.let {
append("&state=")
append(
when (it) {
MangaState.ONGOING -> "Ongoing"
MangaState.FINISHED -> "Completed"
else -> "all"
},
)
}
append("&page=") append("&page=")
append(page.toString()) }
null -> {
append(listUrl)
append("?type=latest&page=")
} }
} }
append(page.toString())
} }
val doc = webClient.httpGet(url).parseHtml() val doc = webClient.httpGet(url).parseHtml()
return doc.select("div.list-truyen-item-wrap").ifEmpty { return doc.select("div.list-truyen-item-wrap").ifEmpty {
@ -71,6 +95,38 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
} }
} }
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
val fullUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
val chaptersDeferred = async { getChapters(doc) }
val desc = doc.selectFirstOrThrow(selectDesc).html()
val stateDiv = doc.select(selectState).text().replace("Status : ", "")
val state = stateDiv.let {
when (it.lowercase()) {
in ongoing -> MangaState.ONGOING
in finished -> MangaState.FINISHED
else -> null
}
}
val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "")
val aut = doc.body().select(selectAut).eachText().joinToString()
manga.copy(
tags = doc.body().select(selectTag).mapNotNullToSet { a ->
MangaTag(
key = a.attr("href").substringAfterLast("category=").substringBefore("&"),
title = a.text().toTitleCase(),
source = source,
)
},
description = desc,
altTitle = alt,
author = aut,
state = state,
chapters = chaptersDeferred.await(),
isNsfw = manga.isNsfw,
)
}
override val selectTagMap = "ul.tag li a" override val selectTagMap = "ul.tag li a"
override suspend fun getAvailableTags(): Set<MangaTag> { override suspend fun getAvailableTags(): Set<MangaTag> {

@ -9,9 +9,6 @@ import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
@MangaSourceParser("MANGANATO", "Manganato", "en") @MangaSourceParser("MANGANATO", "Manganato", "en")
internal class Manganato(context: MangaLoaderContext) : internal class Manganato(context: MangaLoaderContext) :
MangaboxParser(context, MangaSource.MANGANATO) { MangaboxParser(context, MangaSource.MANGANATO) {
override val configKeyDomain = ConfigKey.Domain("chapmanganato.com", "manganato.com") override val configKeyDomain = ConfigKey.Domain("chapmanganato.com", "manganato.com")
override val otherDomain = "chapmanganato.com" override val otherDomain = "chapmanganato.com"
} }

@ -32,6 +32,9 @@ internal abstract class MangaReaderParser(
override val availableSortOrders: Set<SortOrder> override val availableSortOrders: Set<SortOrder>
get() = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.ALPHABETICAL, SortOrder.NEWEST) get() = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.ALPHABETICAL, SortOrder.NEWEST)
override val availableStates: Set<MangaState>
get() = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED)
protected open val listUrl = "/manga" protected open val listUrl = "/manga"
protected open val datePattern = "MMMM d, yyyy" protected open val datePattern = "MMMM d, yyyy"
protected open val isNetShieldProtected = false protected open val isNetShieldProtected = false
@ -40,52 +43,62 @@ internal abstract class MangaReaderParser(
private val mutex = Mutex() private val mutex = Mutex()
protected open var lastSearchPage = 1 protected open var lastSearchPage = 1
override suspend fun getListPage( override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
if (!query.isNullOrEmpty()) {
if (page > lastSearchPage) {
return emptyList()
}
val url = buildString {
append("https://")
append(domain)
append("/page/")
append(page)
append("/?s=")
append(query.urlEncoded())
}
val docs = webClient.httpGet(url).parseHtml()
lastSearchPage = docs.selectFirst(".pagination .next")
?.previousElementSibling()
?.text()?.toIntOrNull() ?: 1
return parseMangaList(docs)
}
val sortQuery = when (sortOrder) {
SortOrder.ALPHABETICAL -> "title"
SortOrder.NEWEST -> "latest"
SortOrder.POPULARITY -> "popular"
SortOrder.UPDATED -> "update"
else -> ""
}
val tagKey = "genre[]".urlEncoded()
val tagQuery =
if (tags.isNullOrEmpty()) "" else tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" }
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
append(listUrl)
append("/?order=") when (filter) {
append(sortQuery)
append(tagQuery) is MangaListFilter.Search -> {
append("&page=") append("/page/")
append(page) append(page.toString())
append("/?s=")
append(filter.query.urlEncoded())
}
is MangaListFilter.Advanced -> {
append(listUrl)
append("/?order=")
append(
when (filter.sortOrder) {
SortOrder.ALPHABETICAL -> "title"
SortOrder.NEWEST -> "latest"
SortOrder.POPULARITY -> "popular"
SortOrder.UPDATED -> "update"
else -> ""
},
)
val tagKey = "genre[]".urlEncoded()
val tagQuery =
if (filter.tags.isEmpty()) ""
else filter.tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" }
append(tagQuery)
if (filter.states.isNotEmpty()) {
filter.states.oneOrThrowIfMany()?.let {
append("&status=")
when (it) {
MangaState.ONGOING -> append("ongoing")
MangaState.FINISHED -> append("completed")
MangaState.PAUSED -> append("hiatus")
else -> append("")
}
}
}
append("&page=")
append(page.toString())
}
null -> {
append(listUrl)
append("/?order=update&page=")
append(page.toString())
}
}
} }
return parseMangaList(webClient.httpGet(url).parseHtml()) return parseMangaList(webClient.httpGet(url).parseHtml())
} }

Loading…
Cancel
Save