add filter : LikeMangaParser, FmTeam, MadthemeParser , MangaReaderParser
Fix GetList : NicovideoSeigaParser, Manga18Parser add filter & multitags on MangaboxParser
This commit is contained in:
@@ -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>(jsonManga.length())
|
||||
for (i in 0 until jsonManga.length()) {
|
||||
val j = jsonManga.getJSONObject(i)
|
||||
val href = "/api" + j.getString("url")
|
||||
when {
|
||||
!tags.isNullOrEmpty() -> {
|
||||
val a = j.getJSONArray("genres").toString()
|
||||
var found = true
|
||||
tags.forEach {
|
||||
if (!a.contains(it.key, ignoreCase = true)) {
|
||||
found = false
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
if (filter.tags.isNotEmpty() && filter.states.isEmpty()) {
|
||||
val a = j.getJSONArray("genres").toString()
|
||||
foundTag = false
|
||||
filter.tags.forEach {
|
||||
if (a.contains(it.key, ignoreCase = true)) {
|
||||
foundTag = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
manga.add(
|
||||
addManga(href, j),
|
||||
)
|
||||
|
||||
if (filter.states.isNotEmpty()) {
|
||||
val a = j.getString("status")
|
||||
foundState = false
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
if (a.contains(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "En cours"
|
||||
MangaState.FINISHED -> "Terminé"
|
||||
else -> ""
|
||||
},
|
||||
ignoreCase = true,
|
||||
)
|
||||
) {
|
||||
foundState = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (foundState && foundTag) {
|
||||
manga.add(addManga(href, j))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
null -> {
|
||||
val jsonManga = webClient.httpGet("https://$domain/api/comics").parseJson().getJSONArray("comics")
|
||||
for (i in 0 until jsonManga.length()) {
|
||||
val j = jsonManga.getJSONObject(i)
|
||||
val href = "/api" + j.getString("url")
|
||||
manga.add(
|
||||
addManga(href, j),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return manga
|
||||
}
|
||||
|
||||
|
||||
@@ -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)}"
|
||||
}
|
||||
|
||||
} else {
|
||||
"https://$domain/manga/list?page=$page&sort=${getSortKey(filter.sortOrder)}"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> "https://$domain/manga/list?page=$page"
|
||||
}
|
||||
|
||||
tags.size > 1 -> throw IllegalArgumentException("This source supports only 1 category")
|
||||
else -> "https://$domain/manga/list?page=$page&sort=${getSortKey(sortOrder)}"
|
||||
}
|
||||
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("[sortby]".urlEncoded())
|
||||
append("=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("hot")
|
||||
SortOrder.UPDATED -> append("lastest-chap")
|
||||
SortOrder.NEWEST -> append("lastest-manga")
|
||||
else -> append("lastest-chap")
|
||||
append("/?act=search")
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("&f")
|
||||
append("[keyword]".urlEncoded())
|
||||
append("=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
append("&f")
|
||||
append("[sortby]".urlEncoded())
|
||||
append("=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("hot")
|
||||
SortOrder.UPDATED -> append("lastest-chap")
|
||||
SortOrder.NEWEST -> append("lastest-manga")
|
||||
else -> append("lastest-chap")
|
||||
}
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&f")
|
||||
append("[genres]".urlEncoded())
|
||||
append("=")
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&f")
|
||||
append("[status]".urlEncoded())
|
||||
append("=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "in-process"
|
||||
MangaState.FINISHED -> "complete"
|
||||
MangaState.PAUSED -> "pause"
|
||||
else -> "all"
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("&f")
|
||||
append("[sortby]".urlEncoded())
|
||||
append("=lastest-chap")
|
||||
}
|
||||
}
|
||||
|
||||
if (page > 1) {
|
||||
append("&pageNum=")
|
||||
append(page)
|
||||
}
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("&f")
|
||||
append("[genres]".urlEncoded())
|
||||
append("=")
|
||||
append(tag?.key.orEmpty())
|
||||
}
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("&f")
|
||||
append("[keyword]".urlEncoded())
|
||||
append("=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select("div.card-body div.video").map { div ->
|
||||
|
||||
@@ -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) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("updated_at")
|
||||
SortOrder.ALPHABETICAL -> append("name") // On some sites without tags or searches, the alphabetical option is empty.
|
||||
SortOrder.NEWEST -> append("created_at")
|
||||
SortOrder.RATING -> append("rating")
|
||||
}
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("&q=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
append('/')
|
||||
append(listUrl)
|
||||
when (filter) {
|
||||
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
for (tag in tags) {
|
||||
append("&")
|
||||
append("genre[]".urlEncoded())
|
||||
append("=")
|
||||
append(tag.key)
|
||||
is MangaListFilter.Search -> {
|
||||
append("?sort=updated_at&q=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
append("?sort=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("updated_at")
|
||||
SortOrder.ALPHABETICAL -> append("name") // On some sites without tags or searches, the alphabetical option is empty.
|
||||
SortOrder.NEWEST -> append("created_at")
|
||||
SortOrder.RATING -> append("rating")
|
||||
}
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.forEach {
|
||||
append("&")
|
||||
append("genre[]".urlEncoded())
|
||||
append("=")
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
else -> "all"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> append("?sort=updated_at")
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
@@ -117,7 +136,7 @@ internal abstract class MadthemeParser(
|
||||
|
||||
override suspend fun getAvailableTags(): Set<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)
|
||||
append("?sort=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("updated_at")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
SortOrder.NEWEST -> append("created_at")
|
||||
SortOrder.RATING -> append("rating")
|
||||
}
|
||||
when (filter) {
|
||||
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("&q=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
for (tag in tags) {
|
||||
append("&")
|
||||
append("include[]".urlEncoded())
|
||||
append("=")
|
||||
append(tag.key)
|
||||
is MangaListFilter.Search -> {
|
||||
append("?sort=updated_at&q=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
append("?sort=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("updated_at")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
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"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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())
|
||||
append("/")
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(tagUrl)
|
||||
append(it.key)
|
||||
append("/")
|
||||
}
|
||||
} else {
|
||||
append(listUrl)
|
||||
}
|
||||
|
||||
append(page.toString())
|
||||
append("?")
|
||||
append("?order_by=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("lastest")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
append("/$listUrl")
|
||||
null -> {
|
||||
append(listUrl)
|
||||
append(page.toString())
|
||||
append("?")
|
||||
append("?order_by=latest")
|
||||
}
|
||||
}
|
||||
append("order_by=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("lastest")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
protected open fun parseMangaList(doc: Document): List<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())
|
||||
}
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("?type=topview")
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.NEWEST -> append("?type=newest")
|
||||
else -> append("")
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&g_i=")
|
||||
filter.tags.forEach {
|
||||
append("_")
|
||||
append(it.key)
|
||||
append("_")
|
||||
}
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&sts=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
append("&orby=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("topview")
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.NEWEST -> append("newest")
|
||||
SortOrder.ALPHABETICAL -> append("az")
|
||||
else -> append("")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
|
||||
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -109,7 +127,8 @@ internal abstract class MangaboxParser(
|
||||
|
||||
override suspend fun getAvailableTags(): Set<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()) {
|
||||
append(searchUrl)
|
||||
append(query.urlEncoded())
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
append("$listUrl/")
|
||||
|
||||
append("/type-")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("topview")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("newest")
|
||||
else -> append("latest")
|
||||
is MangaListFilter.Search -> {
|
||||
append(searchUrl)
|
||||
append(filter.query.urlEncoded())
|
||||
append("?page=")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
append("/ctg-")
|
||||
append(tag?.key.orEmpty())
|
||||
} else {
|
||||
append("/ctg-all")
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(it.key)
|
||||
}
|
||||
} else {
|
||||
append("all")
|
||||
}
|
||||
|
||||
append("/state-")
|
||||
if (filter.states.isNotEmpty()) {
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
else -> "all"
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
append("all")
|
||||
}
|
||||
|
||||
append("/page-")
|
||||
}
|
||||
|
||||
null -> {
|
||||
append(listUrl)
|
||||
append("/type-latest/ctg-all/state-all/page-")
|
||||
}
|
||||
append("/state-all/page-")
|
||||
append(page.toString())
|
||||
}
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return doc.select("div.story-item").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
@@ -115,13 +130,9 @@ internal class Mangairo(context: MangaLoaderContext) :
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
|
||||
val chaptersDeferred = async { getChapters(doc) }
|
||||
|
||||
val desc = doc.selectFirstOrThrow(selectDesc).html()
|
||||
|
||||
val stateDiv = doc.select(selectState).text()
|
||||
|
||||
val state = stateDiv.let {
|
||||
when (it) {
|
||||
in ongoing -> MangaState.ONGOING
|
||||
@@ -131,9 +142,7 @@ internal class Mangairo(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "")
|
||||
|
||||
val aut = doc.body().select(selectAut).eachText().joinToString()
|
||||
|
||||
manga.copy(
|
||||
tags = doc.body().select(selectTag).mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
@@ -151,6 +160,4 @@ internal class Mangairo(context: MangaLoaderContext) :
|
||||
isNsfw = manga.isNsfw,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
append(searchUrl)
|
||||
append(query.replace(" ", "_").urlEncoded())
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
|
||||
} else {
|
||||
append("$listUrl/")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("?type=topview")
|
||||
SortOrder.UPDATED -> append("?type=latest")
|
||||
SortOrder.NEWEST -> append("?type=newest")
|
||||
else -> append("?type=latest")
|
||||
is MangaListFilter.Search -> {
|
||||
append(searchUrl)
|
||||
append(filter.query.urlEncoded())
|
||||
append("?page=")
|
||||
}
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("&category=")
|
||||
append(tag?.key.orEmpty())
|
||||
} else {
|
||||
append("&category=all")
|
||||
}
|
||||
append("&state=all&page=")
|
||||
append(page)
|
||||
|
||||
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=")
|
||||
}
|
||||
|
||||
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,40 +17,61 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
|
||||
override val configKeyDomain = ConfigKey.Domain("ww6.mangakakalot.tv")
|
||||
override val searchUrl = "/search/"
|
||||
override val listUrl = "/manga_list"
|
||||
override val availableSortOrders: Set<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()) {
|
||||
append(searchUrl)
|
||||
append(query.urlEncoded())
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
} else {
|
||||
append("$listUrl/")
|
||||
append("?type=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("topview")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("newest")
|
||||
else -> append("latest")
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append(searchUrl)
|
||||
append(filter.query.urlEncoded())
|
||||
append("?page=")
|
||||
}
|
||||
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.toString())
|
||||
}
|
||||
|
||||
null -> {
|
||||
append(listUrl)
|
||||
append("?type=latest&page=")
|
||||
}
|
||||
}
|
||||
append(page.toString())
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select("div.list-truyen-item-wrap").ifEmpty {
|
||||
@@ -71,6 +95,38 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
val chaptersDeferred = async { getChapters(doc) }
|
||||
val desc = doc.selectFirstOrThrow(selectDesc).html()
|
||||
val stateDiv = doc.select(selectState).text().replace("Status : ", "")
|
||||
val state = stateDiv.let {
|
||||
when (it.lowercase()) {
|
||||
in ongoing -> MangaState.ONGOING
|
||||
in finished -> MangaState.FINISHED
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "")
|
||||
val aut = doc.body().select(selectAut).eachText().joinToString()
|
||||
manga.copy(
|
||||
tags = doc.body().select(selectTag).mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").substringAfterLast("category=").substringBefore("&"),
|
||||
title = a.text().toTitleCase(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
description = desc,
|
||||
altTitle = alt,
|
||||
author = aut,
|
||||
state = state,
|
||||
chapters = chaptersDeferred.await(),
|
||||
isNsfw = manga.isNsfw,
|
||||
)
|
||||
}
|
||||
|
||||
override val selectTagMap = "ul.tag li a"
|
||||
|
||||
override suspend fun getAvailableTags(): Set<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()
|
||||
}
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/page/")
|
||||
append(page)
|
||||
append("/?s=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
|
||||
val docs = webClient.httpGet(url).parseHtml()
|
||||
lastSearchPage = docs.selectFirst(".pagination .next")
|
||||
?.previousElementSibling()
|
||||
?.text()?.toIntOrNull() ?: 1
|
||||
return parseMangaList(docs)
|
||||
}
|
||||
|
||||
val sortQuery = when (sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "title"
|
||||
SortOrder.NEWEST -> "latest"
|
||||
SortOrder.POPULARITY -> "popular"
|
||||
SortOrder.UPDATED -> "update"
|
||||
else -> ""
|
||||
}
|
||||
val tagKey = "genre[]".urlEncoded()
|
||||
val tagQuery =
|
||||
if (tags.isNullOrEmpty()) "" else tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" }
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append(listUrl)
|
||||
append("/?order=")
|
||||
append(sortQuery)
|
||||
append(tagQuery)
|
||||
append("&page=")
|
||||
append(page)
|
||||
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
append("/?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
append(listUrl)
|
||||
|
||||
append("/?order=")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "title"
|
||||
SortOrder.NEWEST -> "latest"
|
||||
SortOrder.POPULARITY -> "popular"
|
||||
SortOrder.UPDATED -> "update"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
|
||||
val tagKey = "genre[]".urlEncoded()
|
||||
val tagQuery =
|
||||
if (filter.tags.isEmpty()) ""
|
||||
else filter.tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" }
|
||||
append(tagQuery)
|
||||
|
||||
if (filter.states.isNotEmpty()) {
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
when (it) {
|
||||
MangaState.ONGOING -> append("ongoing")
|
||||
MangaState.FINISHED -> append("completed")
|
||||
MangaState.PAUSED -> append("hiatus")
|
||||
else -> append("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
null -> {
|
||||
append(listUrl)
|
||||
append("/?order=update&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user