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) {
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 suspend fun getListPage(
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
if (page > 1) {
return emptyList()
}
val jsonManga = if (!query.isNullOrEmpty()) {
//3 letters minimum
webClient.httpGet("https://$domain/api/search/${query.urlEncoded()}").parseJson().getJSONArray("comics")
} else {
webClient.httpGet("https://$domain/api/comics").parseJson().getJSONArray("comics")
var foundTag = true
var foundState = true
val manga = ArrayList<Manga>()
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))
}
}
val manga = ArrayList<Manga>(jsonManga.length())
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")
when {
!tags.isNullOrEmpty() -> {
if (filter.tags.isNotEmpty() && filter.states.isEmpty()) {
val a = j.getJSONArray("genres").toString()
var found = true
tags.forEach {
if (!a.contains(it.key, ignoreCase = true)) {
found = false
foundTag = false
filter.tags.forEach {
if (a.contains(it.key, ignoreCase = true)) {
foundTag = true
}
}
if (found) {
manga.add(
addManga(href, j),
}
if (filter.states.isNotEmpty()) {
val a = j.getString("status")
foundState = false
filter.states.oneOrThrowIfMany()?.let {
if (a.contains(
when (it) {
MangaState.ONGOING -> "En cours"
MangaState.FINISHED -> "Terminé"
else -> ""
},
ignoreCase = true,
)
) {
foundState = true
}
}
}
else -> {
if (foundState && foundTag) {
manga.add(addManga(href, j))
}
}
}
null -> {
val jsonManga = webClient.httpGet("https://$domain/api/comics").parseJson().getJSONArray("comics")
for (i in 0 until jsonManga.length()) {
val j = jsonManga.getJSONObject(i)
val href = "/api" + j.getString("url")
manga.add(
addManga(href, j),
)
}
}
}
return manga
}

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

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

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

@ -7,44 +7,58 @@ import org.koitharu.kotatsu.parsers.site.madtheme.MadthemeParser
import org.koitharu.kotatsu.parsers.util.*
import java.util.Locale
@MangaSourceParser("MANHUASCAN", "ManhuaScan", "")
@MangaSourceParser("MANHUASCAN", "ManhuaScan.io", "")
internal class ManhuaScan(context: MangaLoaderContext) :
MadthemeParser(context, MangaSource.MANHUASCAN, "manhuascan.io") {
override val sourceLocale: Locale = Locale.ENGLISH
override val listUrl = "search"
override suspend fun getListPage(
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
val url = buildString {
append("https://")
append(domain)
append('/')
append(listUrl)
when (filter) {
is MangaListFilter.Search -> {
append("?sort=updated_at&q=")
append(filter.query.urlEncoded())
}
is MangaListFilter.Advanced -> {
append("?sort=")
when (sortOrder) {
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 (!query.isNullOrEmpty()) {
append("&q=")
append(query.urlEncoded())
}
if (!tags.isNullOrEmpty()) {
for (tag in tags) {
if (filter.tags.isNotEmpty()) {
filter.tags.forEach {
append("&")
append("include[]".urlEncoded())
append("=")
append(tag.key)
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=")

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

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

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

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

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.parsers.site.manga18.en
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.*
@ -10,51 +11,7 @@ import org.koitharu.kotatsu.parsers.util.*
internal class Hentai3zCc(context: MangaLoaderContext) :
Manga18Parser(context, MangaSource.HENTAI3ZCC, "hentai3z.cc") {
override suspend fun getListPage(
page: Int,
query: String?,
tags: Set<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()
override fun parseMangaList(doc: Document): List<Manga> {
return doc.select("div.story_item").map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
Manga(

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

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

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

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

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

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

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

@ -32,6 +32,9 @@ internal abstract class MangaReaderParser(
override val availableSortOrders: Set<SortOrder>
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 datePattern = "MMMM d, yyyy"
protected open val isNetShieldProtected = false
@ -40,52 +43,62 @@ internal abstract class MangaReaderParser(
private val mutex = Mutex()
protected open var lastSearchPage = 1
override suspend fun getListPage(
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
if (!query.isNullOrEmpty()) {
if (page > lastSearchPage) {
return emptyList()
}
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
val url = buildString {
append("https://")
append(domain)
when (filter) {
is MangaListFilter.Search -> {
append("/page/")
append(page)
append(page.toString())
append("/?s=")
append(query.urlEncoded())
append(filter.query.urlEncoded())
}
val docs = webClient.httpGet(url).parseHtml()
lastSearchPage = docs.selectFirst(".pagination .next")
?.previousElementSibling()
?.text()?.toIntOrNull() ?: 1
return parseMangaList(docs)
}
is MangaListFilter.Advanced -> {
append(listUrl)
val sortQuery = when (sortOrder) {
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 (tags.isNullOrEmpty()) "" else tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" }
val url = buildString {
append("https://")
append(domain)
append(listUrl)
append("/?order=")
append(sortQuery)
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)
append(page.toString())
}
null -> {
append(listUrl)
append("/?order=update&page=")
append(page.toString())
}
}
}
return parseMangaList(webClient.httpGet(url).parseHtml())
}

Loading…
Cancel
Save