fix getlist on AnimeBootstrapParser, ImHentai, beetoon, CloneManga, MangaGeko, Po2Scans, Pururin
add filter FlixScans, TeamXNovel, ComicExtra, MangaOwl.to, MangaTown, Manhwa18.net, ManhwasMen fix getlist and add tag list on DynastyScans fix getlist and add SortOrder.UPDATED on MangaStorm
This commit is contained in:
@@ -24,43 +24,50 @@ internal class ImHentai(context: MangaLoaderContext) :
|
||||
|
||||
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("/search/?key=")
|
||||
append(query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page)
|
||||
} else if (!tags.isNullOrEmpty()) {
|
||||
append("/tag/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/")
|
||||
when (sortOrder) {
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.POPULARITY -> append("popular/")
|
||||
else -> append("")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search/?key=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
append("?page=")
|
||||
append(page)
|
||||
} else {
|
||||
append("/search/?page=")
|
||||
append(page)
|
||||
when (sortOrder) {
|
||||
SortOrder.UPDATED -> append("<=1&pp=0")
|
||||
SortOrder.POPULARITY -> append("<=0&pp=1")
|
||||
SortOrder.RATING -> append("<=0&pp=0")
|
||||
else -> append("<=1&pp=0")
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/tag/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.POPULARITY -> append("popular/")
|
||||
else -> append("")
|
||||
}
|
||||
append("?page=")
|
||||
append(page)
|
||||
} else {
|
||||
append("/search/?page=")
|
||||
append(page)
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.UPDATED -> append("<=1&pp=0")
|
||||
SortOrder.POPULARITY -> append("<=0&pp=1")
|
||||
SortOrder.RATING -> append("<=0&pp=0")
|
||||
else -> append("<=1&pp=0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/search/?lt=1&pp=0&page=")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select("div.galleries div.thumb").map { div ->
|
||||
|
||||
@@ -11,8 +11,6 @@ import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.util.*
|
||||
|
||||
// see https://themewagon.com/themes/free-bootstrap-4-html5-gaming-anime-website-template-anime/
|
||||
|
||||
internal abstract class AnimeBootstrapParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaSource,
|
||||
@@ -40,13 +38,7 @@ internal abstract class AnimeBootstrapParser(
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -55,23 +47,31 @@ internal abstract class AnimeBootstrapParser(
|
||||
append(page.toString())
|
||||
append("&type=all")
|
||||
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("&search=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("&search=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("&categorie=")
|
||||
append(tag?.key.orEmpty())
|
||||
}
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
append("&sort=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("view")
|
||||
SortOrder.UPDATED -> append("updated")
|
||||
SortOrder.ALPHABETICAL -> append("default")
|
||||
SortOrder.NEWEST -> append("published")
|
||||
else -> append("updated")
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("&categorie=")
|
||||
append(it.key)
|
||||
}
|
||||
|
||||
append("&sort=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("view")
|
||||
SortOrder.UPDATED -> append("updated")
|
||||
SortOrder.ALPHABETICAL -> append("default")
|
||||
SortOrder.NEWEST -> append("published")
|
||||
else -> append("updated")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> append("&sort=updated")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -115,11 +115,8 @@ internal abstract class AnimeBootstrapParser(
|
||||
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 state = if (doc.select(selectState).isNullOrEmpty()) {
|
||||
MangaState.FINISHED
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.koitharu.kotatsu.parsers.site.animebootstrap.fr
|
||||
|
||||
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import org.jsoup.nodes.Document
|
||||
@@ -13,20 +12,14 @@ import java.text.SimpleDateFormat
|
||||
import java.util.EnumSet
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
@MangaSourceParser("PAPSCAN", "PapScan", "fr")
|
||||
internal class PapScan(context: MangaLoaderContext) :
|
||||
AnimeBootstrapParser(context, MangaSource.PAPSCAN, "papscan.com") {
|
||||
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override val listUrl = "/liste-manga"
|
||||
|
||||
override val selectState = "div.anime__details__widget li:contains(En cours)"
|
||||
override val selectTag = "div.anime__details__widget li:contains(Genre) a"
|
||||
|
||||
override val selectChapter = "ul.chapters li"
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
@@ -34,40 +27,39 @@ internal class PapScan(context: MangaLoaderContext) :
|
||||
SortOrder.ALPHABETICAL,
|
||||
)
|
||||
|
||||
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("/filterList")
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("&alpha=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("&alpha=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("&cat=")
|
||||
append(tag?.key.orEmpty())
|
||||
}
|
||||
append("&sortBy=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("updated")
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("&cat=")
|
||||
append(it.key)
|
||||
}
|
||||
|
||||
append("&sortBy=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("updated")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> append("&sortBy=updated")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return doc.select("div.product__item").map { div ->
|
||||
val href = div.selectFirstOrThrow("h5 a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
@@ -103,17 +95,13 @@ internal class PapScan(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 state = if (doc.select(selectState).isNullOrEmpty()) {
|
||||
MangaState.FINISHED
|
||||
} else {
|
||||
MangaState.ONGOING
|
||||
}
|
||||
|
||||
manga.copy(
|
||||
tags = doc.body().select(selectTag).mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
@@ -145,5 +133,4 @@ internal class PapScan(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.site.animebootstrap.AnimeBootstrapParser
|
||||
|
||||
@MangaSourceParser("KOMIKZOID", "Komikzo Id", "id")
|
||||
@MangaSourceParser("KOMIKZOID", "KomikzoId", "id")
|
||||
internal class KomikzoId(context: MangaLoaderContext) :
|
||||
AnimeBootstrapParser(context, MangaSource.KOMIKZOID, "komikzoid.xyz")
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package org.koitharu.kotatsu.parsers.site.animebootstrap.id
|
||||
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.site.animebootstrap.AnimeBootstrapParser
|
||||
|
||||
|
||||
@MangaSourceParser("NEUMANGA", "NeuManga", "id")
|
||||
@MangaSourceParser("NEUMANGA", "NeuManga.xyz", "id")
|
||||
internal class NeuManga(context: MangaLoaderContext) :
|
||||
AnimeBootstrapParser(context, MangaSource.NEUMANGA, "neumanga.xyz")
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.koitharu.kotatsu.parsers.site.animebootstrap.id
|
||||
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
|
||||
@@ -19,32 +19,66 @@ import java.util.*
|
||||
internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.FLIXSCANS, 18) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
|
||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||
override val configKeyDomain = ConfigKey.Domain("flixscans.com")
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val json = if (!query.isNullOrEmpty()) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val json = when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
val url = "https://api.$domain/api/v1/search/serie"
|
||||
val body = JSONObject()
|
||||
body.put("title", filter.query.urlEncoded())
|
||||
webClient.httpPost(url, body).parseJson().getJSONArray("data")
|
||||
}
|
||||
val url = "https://api.$domain/api/v1/search/serie"
|
||||
val body = JSONObject()
|
||||
body.put("title", query.urlEncoded())
|
||||
webClient.httpPost(url, body).parseJson().getJSONArray("data")
|
||||
} else if (!tags.isNullOrEmpty()) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
append(domain)
|
||||
append("/api/v1/")
|
||||
|
||||
if (filter.tags.isNotEmpty() || filter.states.isNotEmpty()) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
append("search/advance?=")
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
val tagQuery = filter.tags.joinToString(separator = ",") { it.key }
|
||||
append("&genres=")
|
||||
append(tagQuery)
|
||||
}
|
||||
if (filter.states.isNotEmpty()) {
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
MangaState.ABANDONED -> "droped"
|
||||
MangaState.PAUSED -> "onhold"
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
append("&serie_type=webtoon")
|
||||
|
||||
} else {
|
||||
append("webtoon/homepage/latest/home?page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
|
||||
webClient.httpGet(url).parseJson().getJSONArray("data")
|
||||
}
|
||||
|
||||
null -> {
|
||||
val url = "https://api.$domain/api/v1/webtoon/homepage/latest/home?page=$page"
|
||||
webClient.httpGet(url).parseJson().getJSONArray("data")
|
||||
}
|
||||
val tagQuery = tags.joinToString(separator = ",") { it.key }
|
||||
val url = "https://api.$domain/api/v1/search/advance?=&genres=$tagQuery&serie_type=webtoon"
|
||||
webClient.httpGet(url).parseJson().getJSONArray("data")
|
||||
} else {
|
||||
val url = "https://api.$domain/api/v1/webtoon/homepage/latest/home?page=$page"
|
||||
webClient.httpGet(url).parseJson().getJSONArray("data")
|
||||
}
|
||||
return json.mapJSON { j ->
|
||||
val href = "https://$domain/series/${j.getString("prefix")}-${j.getString("id")}-${j.getString("slug")}"
|
||||
@@ -62,6 +96,8 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
state = when (j.getString("status")) {
|
||||
"ongoing" -> MangaState.ONGOING
|
||||
"completed" -> MangaState.FINISHED
|
||||
"onhold" -> MangaState.PAUSED
|
||||
"droped" -> MangaState.ABANDONED
|
||||
else -> null
|
||||
},
|
||||
author = null,
|
||||
@@ -77,9 +113,9 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
return tagsList.mapNotNullToSet { idTag ->
|
||||
val id = idTag.toInt()
|
||||
val idKey = json.getJSONObject(id).getInt("id")
|
||||
val key = json.get(idKey).toString()
|
||||
val key = json.getInt(idKey).toString()
|
||||
val idName = json.getJSONObject(id).getInt("name")
|
||||
val name = json.get(idName).toString()
|
||||
val name = json.getString(idName)
|
||||
MangaTag(
|
||||
key = key,
|
||||
title = name,
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.*
|
||||
@MangaSourceParser("MANGASTORM", "MangaStorm", "ar")
|
||||
internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.MANGASTORM, 30) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY)
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED)
|
||||
override val configKeyDomain = ConfigKey.Domain("mangastorm.org")
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
@@ -21,35 +21,44 @@ internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||
.build()
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
val url =
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/categories/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?page=")
|
||||
append(page)
|
||||
}
|
||||
} else {
|
||||
buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/mangas?page=")
|
||||
append(page)
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("&query=")
|
||||
append(query.urlEncoded())
|
||||
append("&query=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
append("/categories/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?page=")
|
||||
append(page)
|
||||
} else {
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
append("/mangas?page=")
|
||||
append(page)
|
||||
} else {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/mangas?page=")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select("div.row div.col").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
@@ -74,9 +83,7 @@ internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
|
||||
val root = doc.selectFirstOrThrow(".card-body .col-lg-9")
|
||||
|
||||
return manga.copy(
|
||||
altTitle = null,
|
||||
state = null,
|
||||
|
||||
@@ -17,46 +17,71 @@ import java.util.*
|
||||
internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.TEAMXNOVEL, 10) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
override val availableStates: Set<MangaState> =
|
||||
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("team11x11.com")
|
||||
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 (!tags.isNullOrEmpty()) {
|
||||
append("/series?genre=")
|
||||
append(tag?.key.orEmpty())
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
append(page)
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append("/series?search=")
|
||||
append(filter.query.urlEncoded())
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
} else if (!query.isNullOrEmpty()) {
|
||||
append("/series?search=")
|
||||
append(query.urlEncoded())
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
} else {
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("/series")
|
||||
SortOrder.UPDATED -> append("/")
|
||||
else -> append("/")
|
||||
}
|
||||
if (page > 1) {
|
||||
append("?page=")
|
||||
append(page)
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
append("/series?genre=")
|
||||
append(tag?.key.orEmpty())
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
append("&")
|
||||
} else {
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("/series")
|
||||
SortOrder.UPDATED -> append("/")
|
||||
else -> append("/")
|
||||
}
|
||||
if (page > 1) {
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
append("&")
|
||||
} else {
|
||||
append("?")
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.sortOrder == SortOrder.POPULARITY || filter.tags.isNotEmpty()) {
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "مستمرة"
|
||||
MangaState.FINISHED -> "مكتمل"
|
||||
MangaState.ABANDONED -> "متوقف"
|
||||
else -> "مستمرة"
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("/?page=$page")
|
||||
}
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select("div.listupd .bs .bsx").ifEmpty {
|
||||
doc.select("div.post-body .box")
|
||||
@@ -74,7 +99,8 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
tags = emptySet(),
|
||||
state = when (div.selectFirst(".status")?.text()) {
|
||||
"مستمرة" -> MangaState.ONGOING
|
||||
"متوقف", "مكتمل" -> MangaState.FINISHED
|
||||
"مكتمل" -> MangaState.FINISHED
|
||||
"متوقف" -> MangaState.ABANDONED
|
||||
else -> null
|
||||
},
|
||||
author = null,
|
||||
@@ -111,7 +137,8 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
altTitle = null,
|
||||
state = when (doc.selectFirstOrThrow(".full-list-info:contains(الحالة:) a").text()) {
|
||||
"مستمرة" -> MangaState.ONGOING
|
||||
"متوقف", "مكتمل" -> MangaState.FINISHED
|
||||
"مكتمل" -> MangaState.FINISHED
|
||||
"متوقف" -> MangaState.ABANDONED
|
||||
else -> null
|
||||
},
|
||||
tags = doc.select(".review-author-info a").mapNotNullToSet { a ->
|
||||
|
||||
@@ -19,43 +19,41 @@ internal class BeeToon(context: MangaLoaderContext) :
|
||||
|
||||
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)
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
append("/?s=")
|
||||
append(query.urlEncoded())
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/genre/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/page-")
|
||||
append(page)
|
||||
append("/")
|
||||
}
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
else -> {
|
||||
when (sortOrder) {
|
||||
SortOrder.UPDATED -> append("/latest-update/")
|
||||
SortOrder.POPULARITY -> append("/popular-manga/")
|
||||
else -> append("/latest-update/")
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
append("/genre/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/page-")
|
||||
append(page)
|
||||
append("/")
|
||||
} else {
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.UPDATED -> append("/latest-update/")
|
||||
SortOrder.POPULARITY -> append("/popular-manga/")
|
||||
else -> append("/latest-update/")
|
||||
}
|
||||
append("page-")
|
||||
append(page)
|
||||
append("/")
|
||||
}
|
||||
append("page-")
|
||||
append(page)
|
||||
append("/")
|
||||
}
|
||||
|
||||
null -> append("/latest-update/page-$page/")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -19,11 +19,25 @@ internal class CloneMangaParser(context: MangaLoaderContext) : MangaParser(conte
|
||||
override val configKeyDomain = ConfigKey.Domain("manga.clone-army.org")
|
||||
|
||||
@InternalParsersApi
|
||||
override suspend fun getList(offset: Int, query: String?, tags: Set<MangaTag>?, sortOrder: SortOrder): List<Manga> {
|
||||
if (query != null || offset > 0) {
|
||||
return emptyList()
|
||||
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val link = when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (offset > 0) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
"https://$domain/viewer_landing.php"
|
||||
}
|
||||
|
||||
null -> "https://$domain/viewer_landing.php"
|
||||
|
||||
}
|
||||
val link = "https://$domain/viewer_landing.php"
|
||||
|
||||
val doc = webClient.httpGet(link).parseHtml()
|
||||
val mangas = doc.getElementsByClass("comicPreviewContainer")
|
||||
return mangas.mapNotNull { item ->
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@@ -17,6 +18,8 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("comicextra.me")
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
@@ -25,38 +28,62 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||
.build()
|
||||
|
||||
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://$domain/")
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append(tag?.key.orEmpty())
|
||||
if (page > 1) {
|
||||
append("/")
|
||||
append(page)
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("comic-search?key=")
|
||||
append(filter.query.urlEncoded())
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
} else if (!query.isNullOrEmpty()) {
|
||||
append("comic-search?key=")
|
||||
append(query.urlEncoded())
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
append(page)
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty() && filter.states.isEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(it.key)
|
||||
}
|
||||
} else if (filter.tags.isEmpty() && filter.states.isNotEmpty()) {
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "/ongoing-comic"
|
||||
MangaState.FINISHED -> "/completed-comic"
|
||||
else -> "/ongoing-comic"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
} else if (filter.tags.isNotEmpty() && filter.states.isNotEmpty()) {
|
||||
throw IllegalArgumentException("Source does not support tag + states filters")
|
||||
} else {
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("popular-comic")
|
||||
SortOrder.UPDATED -> append("new-comic")
|
||||
SortOrder.NEWEST -> append("recent-comic")
|
||||
else -> append("new-comic")
|
||||
}
|
||||
}
|
||||
|
||||
if (page > 1) {
|
||||
append("/")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("popular-comic/")
|
||||
SortOrder.UPDATED -> append("new-comic/")
|
||||
SortOrder.NEWEST -> append("recent-comic/")
|
||||
else -> append("new-comic/")
|
||||
}
|
||||
if (page > 1) {
|
||||
append(page)
|
||||
|
||||
null -> {
|
||||
append("popular-comic")
|
||||
if (page > 1) {
|
||||
append("/")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package org.koitharu.kotatsu.parsers.site.en
|
||||
|
||||
import androidx.collection.ArraySet
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import okhttp3.Headers
|
||||
import org.json.JSONArray
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
@@ -22,85 +27,128 @@ internal class DynastyScans(context: MangaLoaderContext) : PagedMangaParser(cont
|
||||
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||
.build()
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("/search?q=")
|
||||
append(query.urlEncoded())
|
||||
append("&")
|
||||
append("classes[]".urlEncoded())
|
||||
append("=Serie&page=")
|
||||
append(page.toString())
|
||||
} else if (!tags.isNullOrEmpty()) {
|
||||
append("/tags/")
|
||||
for (tag in tags) {
|
||||
append(tag.key)
|
||||
}
|
||||
append("?view=groupings&page=")
|
||||
append(page.toString())
|
||||
} else {
|
||||
append("/series?view=cover&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
// There are no images on the search page
|
||||
if (!query.isNullOrEmpty()) {
|
||||
return doc.select("dl.chapter-list dd")
|
||||
.map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
title = div.selectFirstOrThrow("a").text(),
|
||||
altTitle = null,
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = "",
|
||||
tags = div.select("span.tags a").mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").removeSuffix('/').substringAfterLast('/'),
|
||||
title = a.text(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&")
|
||||
append("classes[]".urlEncoded())
|
||||
append("=Serie&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
} else {
|
||||
return doc.select("li.span2")
|
||||
.map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
title = div.selectFirstOrThrow("div.caption").text(),
|
||||
altTitle = null,
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"),
|
||||
tags = setOf(),
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
return parseMangaListQuery(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/tags/")
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(it.key)
|
||||
}
|
||||
append("?view=groupings")
|
||||
} else {
|
||||
append("/series?view=cover")
|
||||
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
null -> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/series?view=cover&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> = emptySet()
|
||||
|
||||
private fun parseMangaList(doc: Document): List<Manga> {
|
||||
return doc.select("li.span2")
|
||||
.map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
title = div.selectFirstOrThrow("div.caption").text(),
|
||||
altTitle = null,
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"),
|
||||
tags = setOf(),
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseMangaListQuery(doc: Document): List<Manga> {
|
||||
return doc.select("dl.chapter-list dd")
|
||||
.map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
title = div.selectFirstOrThrow("a").text(),
|
||||
altTitle = null,
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = "",
|
||||
tags = div.select("span.tags a").mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").removeSuffix('/').substringAfterLast('/'),
|
||||
title = a.text(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
return coroutineScope {
|
||||
(1..3).map { page ->
|
||||
async { getTags(page) }
|
||||
}
|
||||
}.awaitAll().flattenTo(ArraySet(360))
|
||||
}
|
||||
|
||||
private suspend fun getTags(page: Int): Set<MangaTag> {
|
||||
val url = "https://$domain/tags?page=$page"
|
||||
val root = webClient.httpGet(url).parseHtml()
|
||||
return root.selectFirstOrThrow(".tag-list ").parseTags()
|
||||
}
|
||||
|
||||
private fun Element.parseTags() = select("a").mapToSet {
|
||||
MangaTag(
|
||||
key = it.attr("href").removeSuffix('/').substringAfterLast('/'),
|
||||
title = it.text(),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
@@ -110,19 +158,14 @@ internal class DynastyScans(context: MangaLoaderContext) : PagedMangaParser(cont
|
||||
altTitle = null,
|
||||
state = when (root.select("h2.tag-title small").last()?.text()) {
|
||||
"— Ongoing" -> MangaState.ONGOING
|
||||
"— Completed" -> MangaState.FINISHED
|
||||
"— Completed", "— Completed and Licensed" -> MangaState.FINISHED
|
||||
"— Dropped", "— Licensed and Removed", "— Abandoned" -> MangaState.ABANDONED
|
||||
"— On Hiatus" -> MangaState.PAUSED
|
||||
else -> null
|
||||
},
|
||||
coverUrl = root.selectFirst("img.thumbnail")?.src()
|
||||
.orEmpty(), // It is needed if the manga was found via the search.
|
||||
tags = root.select("div.tag-tags a").mapNotNullToSet { a ->
|
||||
val href = a.attr("href").removeSuffix('/').substringAfterLast('/')
|
||||
MangaTag(
|
||||
key = href,
|
||||
title = a.text(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
tags = root.selectFirstOrThrow("div.tag-tags").parseTags(),
|
||||
author = null,
|
||||
description = null,
|
||||
chapters = chapters,
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
package org.koitharu.kotatsu.parsers.site.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.PagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import org.koitharu.kotatsu.parsers.util.json.mapJSONToSet
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("FAKKU", "Fakku", "en", ContentType.HENTAI)
|
||||
internal class Fakku(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.FAKKU, pageSize = 25) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.ALPHABETICAL, SortOrder.NEWEST, SortOrder.UPDATED)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("fakku.cc")
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
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)
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
append("/search?q=")
|
||||
append(query.urlEncoded())
|
||||
append("&")
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/tags/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?")
|
||||
}
|
||||
|
||||
else -> {
|
||||
append("?")
|
||||
}
|
||||
}
|
||||
append("page=")
|
||||
append(page)
|
||||
append("&sort=")
|
||||
when (sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> append("title")
|
||||
SortOrder.NEWEST -> append("created_at")
|
||||
SortOrder.UPDATED -> append("published_at")
|
||||
else -> append("published_at")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select("div.entries .entry a").map { a ->
|
||||
val href = a.attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
coverUrl = a.selectFirst("img")?.src().orEmpty(),
|
||||
title = a.selectFirst(".title")?.text().orEmpty(),
|
||||
altTitle = null,
|
||||
rating = RATING_UNKNOWN,
|
||||
tags = emptySet(),
|
||||
author = null,
|
||||
state = null,
|
||||
source = source,
|
||||
isNsfw = isNsfwSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val root = webClient.httpGet("https://$domain/tags").parseHtml()
|
||||
return root.select("div.entries .entry a").mapToSet {
|
||||
MangaTag(
|
||||
key = it.attr("href").substringAfterLast("/"),
|
||||
title = it.selectFirstOrThrow(".name").text(),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
val genreDeferred = async {
|
||||
webClient.httpGet(manga.url.toAbsoluteUrl(domain) + ".json").parseJson()
|
||||
}
|
||||
val genre = genreDeferred.await()
|
||||
manga.copy(
|
||||
author = doc.selectFirst("tr.artists a")?.text(),
|
||||
tags = if (genre.toString().contains("tags")) {
|
||||
genre.getJSONArray("tags").mapJSONToSet {
|
||||
MangaTag(
|
||||
key = it.getString("slug"),
|
||||
title = it.getString("name"),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
emptySet()
|
||||
},
|
||||
chapters = listOf(
|
||||
MangaChapter(
|
||||
id = manga.id,
|
||||
name = manga.title,
|
||||
number = 1,
|
||||
url = manga.url + "/1",
|
||||
scanlator = null,
|
||||
uploadDate = 0,
|
||||
branch = null,
|
||||
source = source,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
val totalPages = doc.selectFirstOrThrow(".total").text().toInt()
|
||||
val rawUrl = chapter.url.substringBeforeLast("/")
|
||||
return (1..totalPages).map {
|
||||
val url = "$rawUrl/$it"
|
||||
MangaPage(
|
||||
id = generateUid(url),
|
||||
url = url,
|
||||
preview = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPageUrl(page: MangaPage): String {
|
||||
val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
val root = doc.body()
|
||||
return root.selectFirstOrThrow(".page img").attrAsAbsoluteUrl("src")
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
package org.koitharu.kotatsu.parsers.site.en
|
||||
|
||||
import androidx.collection.ArraySet
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.json.JSONArray
|
||||
import org.jsoup.nodes.Element
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import org.koitharu.kotatsu.parsers.util.json.mapJSON
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("KSKMOE", "Ksk.moe", "en", ContentType.HENTAI)
|
||||
internal class KskMoe(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.KSKMOE, 35) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.ALPHABETICAL)
|
||||
override val configKeyDomain = ConfigKey.Domain("ksk.moe")
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
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)
|
||||
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("/tags/")
|
||||
append(tag?.key.orEmpty())
|
||||
} else {
|
||||
append("/browse")
|
||||
}
|
||||
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page)
|
||||
}
|
||||
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("?sort=32")
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.NEWEST -> append("?sort=16")
|
||||
SortOrder.ALPHABETICAL -> append("?sort=1")
|
||||
else -> append("")
|
||||
}
|
||||
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("?s=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
if (!doc.html().contains("pagination") && page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
return doc.requireElementById("galleries").select("article").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
title = div.selectLastOrThrow("h3 span").text(),
|
||||
altTitle = null,
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = true,
|
||||
coverUrl = div.selectFirstOrThrow("img").src()?.toAbsoluteUrl(domain).orEmpty(),
|
||||
tags = div.select("footer span").mapNotNullToSet { span ->
|
||||
MangaTag(
|
||||
key = span.text().urlEncoded(),
|
||||
title = span.text(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
return coroutineScope {
|
||||
(1..2).map { page ->
|
||||
async { getTags(page) }
|
||||
}
|
||||
}.awaitAll().flattenTo(ArraySet(360))
|
||||
}
|
||||
|
||||
private suspend fun getTags(page: Int): Set<MangaTag> {
|
||||
val url = if (page == 1) {
|
||||
"https://$domain/tags"
|
||||
} else {
|
||||
"https://$domain/tags/page/$page"
|
||||
}
|
||||
val root = webClient.httpGet(url).parseHtml().body().getElementById("tags")
|
||||
return root?.parseTags().orEmpty()
|
||||
}
|
||||
|
||||
private fun Element.parseTags() = select("section.tags div a").mapToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").substringAfterLast("/tags/"),
|
||||
title = a.selectFirstOrThrow("span").text(),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
|
||||
private val date = SimpleDateFormat("dd.MM.yyyy hh:mm 'UTC'", Locale.US)
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
|
||||
return manga.copy(
|
||||
tags = doc.requireElementById("metadata").select("main div:contains(Tag) a").mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").substringAfterLast("/tags/"),
|
||||
title = a.selectFirstOrThrow("span").text(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
author = doc.requireElementById("metadata").selectFirstOrThrow("main div:contains(Artist) a span").text(),
|
||||
chapters = listOf(
|
||||
MangaChapter(
|
||||
id = generateUid(manga.id),
|
||||
name = manga.title,
|
||||
number = 1,
|
||||
url = manga.url,
|
||||
scanlator = null,
|
||||
uploadDate = date.tryParse(doc.selectFirstOrThrow("time.updated").text()),
|
||||
branch = null,
|
||||
source = source,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val fullUrl = chapter.url
|
||||
.replace("/view/", "/read/")
|
||||
.let { "$it/1" }
|
||||
.toAbsoluteUrl(domain)
|
||||
val document = webClient.httpGet(fullUrl).parseHtml()
|
||||
|
||||
val id = fullUrl
|
||||
.substringAfter("/read/")
|
||||
.substringBeforeLast("/")
|
||||
|
||||
val cdnUrl = document.selectFirst("meta[itemprop=image]")
|
||||
?.attr("content")
|
||||
?.toHttpUrlOrNull()
|
||||
?.host
|
||||
.let { "https://" + (it ?: domain) }
|
||||
|
||||
val script = document.select("script:containsData(window.metadata)").html()
|
||||
|
||||
val rawJson = script
|
||||
.substringAfter("original:")
|
||||
.substringBefore("resampled:")
|
||||
.substringBeforeLast(",")
|
||||
|
||||
return JSONArray(rawJson).mapJSON {
|
||||
val fileName = it.getString("n")
|
||||
|
||||
val url = "$cdnUrl/original/$id/$fileName"
|
||||
val preview = "$cdnUrl/t/$id/320/$fileName"
|
||||
|
||||
MangaPage(
|
||||
id = generateUid(url),
|
||||
url = url,
|
||||
preview = preview,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,42 +25,49 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||
.build()
|
||||
|
||||
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 = if (!query.isNullOrEmpty()) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
buildString {
|
||||
append("https://$domain/search/?search=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
} else {
|
||||
buildString {
|
||||
append("https://$domain/browse-comics/?results=")
|
||||
append(page)
|
||||
append("&filter=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("Updated")
|
||||
SortOrder.NEWEST -> append("New")
|
||||
else -> append("Updated")
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
append("/search/?search=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("&genre=")
|
||||
append(tag?.key.orEmpty())
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
append("/browse-comics/?results=")
|
||||
append(page)
|
||||
|
||||
append("&filter=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("Updated")
|
||||
SortOrder.NEWEST -> append("New")
|
||||
else -> append("Updated")
|
||||
}
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("&genre=")
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/browse-comics/?results=")
|
||||
append(page)
|
||||
append("&filter=Updated")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return doc.select("li.novel-item").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
|
||||
@@ -22,35 +22,69 @@ internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(contex
|
||||
SortOrder.UPDATED,
|
||||
)
|
||||
|
||||
private val regexTag = Regex("[^\\-]+-[^\\-]+-[^\\-]+-[^\\-]+-[^\\-]+-[^\\-]+")
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(
|
||||
MangaState.ONGOING,
|
||||
MangaState.FINISHED,
|
||||
)
|
||||
|
||||
override suspend fun getList(
|
||||
offset: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val sortKey = when (sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "?name.az"
|
||||
SortOrder.RATING -> "?rating.za"
|
||||
SortOrder.UPDATED -> "?last_chapter_time.za"
|
||||
else -> ""
|
||||
}
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val page = (offset / 30) + 1
|
||||
val url = when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
if (offset != 0) {
|
||||
return emptyList()
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search?name=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
"/search?name=${query.urlEncoded()}".toAbsoluteUrl(domain)
|
||||
}
|
||||
|
||||
tags.isNullOrEmpty() -> "/directory/$page.htm$sortKey".toAbsoluteUrl(domain)
|
||||
tags.size == 1 -> "/directory/${tags.first().key}/$page.htm$sortKey".toAbsoluteUrl(domain)
|
||||
else -> tags.joinToString(
|
||||
prefix = "/search?page=$page".toAbsoluteUrl(domain),
|
||||
) { tag ->
|
||||
"&genres[${tag.key}]=1"
|
||||
is MangaListFilter.Advanced -> {
|
||||
append("/directory/")
|
||||
append("0-")
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(it.key)
|
||||
}
|
||||
} else {
|
||||
append("0")
|
||||
}
|
||||
append("-0-")
|
||||
|
||||
if (filter.states.isNotEmpty()) {
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
else -> "0"
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
append("0")
|
||||
}
|
||||
|
||||
append("-0-0/")
|
||||
append(page.toString())
|
||||
append(".htm")
|
||||
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> ""
|
||||
SortOrder.UPDATED -> "?last_chapter_time.za"
|
||||
SortOrder.ALPHABETICAL -> "?name.az"
|
||||
SortOrder.RATING -> "?rating.za"
|
||||
else -> "?last_chapter_time.za"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
null -> append("/directory/$page.htm?last_chapter_time.za")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -81,7 +115,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(contex
|
||||
tags = li.selectFirst("p.keyWord")?.select("a")?.mapNotNullToSet tags@{ x ->
|
||||
MangaTag(
|
||||
title = x.attr("title").toTitleCase(),
|
||||
key = x.attr("href").parseTagKey() ?: return@tags null,
|
||||
key = x.attr("href").substringAfter("/directory/0-").substringBefore("-0-"),
|
||||
source = MangaSource.MANGATOWN,
|
||||
)
|
||||
}.orEmpty(),
|
||||
@@ -106,7 +140,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(contex
|
||||
}?.select("a")?.mapNotNull { a ->
|
||||
MangaTag(
|
||||
title = a.attr("title").toTitleCase(),
|
||||
key = a.attr("href").parseTagKey() ?: return@mapNotNull null,
|
||||
key = a.attr("href").substringAfter("/directory/0-").substringBefore("-0-"),
|
||||
source = MangaSource.MANGATOWN,
|
||||
)
|
||||
}.orEmpty(),
|
||||
@@ -165,10 +199,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(contex
|
||||
?.nextElementSibling() ?: doc.parseFailed("Root not found")
|
||||
return root.select("li").mapNotNullToSet { li ->
|
||||
val a = li.selectFirst("a") ?: return@mapNotNullToSet null
|
||||
val key = a.attr("href").parseTagKey()
|
||||
if (key.isNullOrEmpty()) {
|
||||
return@mapNotNullToSet null
|
||||
}
|
||||
val key = a.attr("href").substringAfter("/directory/0-").substringBefore("-0-")
|
||||
MangaTag(
|
||||
source = MangaSource.MANGATOWN,
|
||||
key = key,
|
||||
@@ -211,6 +242,4 @@ internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(contex
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.parseTagKey() = split('/').findLast { regexTag matches it }
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ internal class Mangaowl(context: MangaLoaderContext) :
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.RATING,
|
||||
)
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("mangaowl.to")
|
||||
|
||||
@@ -31,46 +32,56 @@ internal class Mangaowl(context: MangaLoaderContext) :
|
||||
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||
.build()
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val sort = when (sortOrder) {
|
||||
SortOrder.POPULARITY -> "view_count"
|
||||
SortOrder.UPDATED -> "-modified_at"
|
||||
SortOrder.NEWEST -> "created_at"
|
||||
SortOrder.RATING -> "rating"
|
||||
else -> "modified_at"
|
||||
}
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
append("/8-search")
|
||||
append("?q=")
|
||||
append(query.urlEncoded())
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/10-search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/8-genres/")
|
||||
for (tag in tags) {
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
append("/10-comics")
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
|
||||
filter.tags.forEach { tag ->
|
||||
append("&genres=")
|
||||
append(tag.key)
|
||||
}
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
append("&ordering=")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> "view_count"
|
||||
SortOrder.UPDATED -> "-modified_at"
|
||||
SortOrder.NEWEST -> "created_at"
|
||||
SortOrder.RATING -> "rating"
|
||||
else -> "modified_at"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
append("/8-comics")
|
||||
append("?page=")
|
||||
null -> {
|
||||
append("/10-comics?ordering=-modified_at&page=")
|
||||
append(page.toString())
|
||||
append("&ordering=")
|
||||
append(sort)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,9 +106,9 @@ internal class Mangaowl(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/8-genres").parseHtml()
|
||||
val doc = webClient.httpGet("https://$domain/10-genres").parseHtml()
|
||||
return doc.select("div.genres-container span.genre-item a").mapNotNullToSet { a ->
|
||||
val key = a.attr("href").substringAfterLast("/")
|
||||
val key = a.attr("href").removeSuffix('/').substringAfterLast('/').substringBefore("-")
|
||||
MangaTag(
|
||||
key = key,
|
||||
title = a.text(),
|
||||
@@ -112,7 +123,7 @@ internal class Mangaowl(context: MangaLoaderContext) :
|
||||
manga.copy(
|
||||
tags = doc.body().select("div.comic-attrs div.column.my-2:contains(Genres) a").mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").removeSuffix("/").substringAfterLast('/'),
|
||||
key = a.attr("href").removeSuffix("/").substringAfterLast('/').substringBefore("-"),
|
||||
title = a.text().toTitleCase().replace(",", ""),
|
||||
source = source,
|
||||
)
|
||||
|
||||
@@ -9,14 +9,26 @@ import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("MANHWA18", "Manhwa18", "en", type = ContentType.HENTAI)
|
||||
@MangaSourceParser("MANHWA18", "Manhwa18.net", "en", type = ContentType.HENTAI)
|
||||
class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.MANHWA18, pageSize = 18, searchPageSize = 18) {
|
||||
|
||||
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwa18.net")
|
||||
|
||||
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,
|
||||
SortOrder.RATING,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(
|
||||
MangaState.ONGOING,
|
||||
MangaState.FINISHED,
|
||||
MangaState.PAUSED,
|
||||
)
|
||||
|
||||
private val tagsMap = SuspendLazy(::parseTags)
|
||||
|
||||
@@ -29,6 +41,82 @@ class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/tim-kiem?page=")
|
||||
append(page.toString())
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("&q=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
append("&accept_genres=")
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append(
|
||||
filter.tags.joinToString(",") { it.key },
|
||||
)
|
||||
}
|
||||
|
||||
append("&sort=")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "az"
|
||||
SortOrder.POPULARITY -> "top"
|
||||
SortOrder.UPDATED -> "update"
|
||||
SortOrder.NEWEST -> "new"
|
||||
SortOrder.RATING -> "like"
|
||||
},
|
||||
)
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "1"
|
||||
MangaState.FINISHED -> "3"
|
||||
MangaState.PAUSED -> "2"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("&sort=update")
|
||||
}
|
||||
}
|
||||
|
||||
val docs = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return docs.select(".card-body .thumb-item-flow")
|
||||
.map {
|
||||
val titleElement = it.selectFirstOrThrow(".thumb_attr.series-title > a")
|
||||
val absUrl = titleElement.attrAsAbsoluteUrl("href")
|
||||
Manga(
|
||||
id = generateUid(absUrl.toRelativeUrl(domain)),
|
||||
title = titleElement.text(),
|
||||
altTitle = null,
|
||||
url = absUrl.toRelativeUrl(domain),
|
||||
publicUrl = absUrl,
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = true,
|
||||
coverUrl = it.selectFirst("div.img-in-ratio")?.attrAsAbsoluteUrl("data-bg").orEmpty(),
|
||||
tags = emptySet(),
|
||||
state = null,
|
||||
author = null,
|
||||
largeCoverUrl = null,
|
||||
description = null,
|
||||
source = MangaSource.MANHWA18,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
val cardInfoElement = docs.selectFirst("div.series-information")
|
||||
@@ -45,6 +133,7 @@ class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
when (it.text().lowercase()) {
|
||||
"on going" -> MangaState.ONGOING
|
||||
"completed" -> MangaState.FINISHED
|
||||
"on hold" -> MangaState.PAUSED
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -99,60 +188,6 @@ class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
return cal.time.time
|
||||
}
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val sortQuery = when (sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "az"
|
||||
SortOrder.POPULARITY -> "top"
|
||||
SortOrder.UPDATED -> "update"
|
||||
SortOrder.NEWEST -> "new"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
val tagQuery = tags?.joinToString(",") { it.key }.orEmpty()
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/tim-kiem?page=")
|
||||
append(page)
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("&q=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
append("&accept_genres=$tagQuery")
|
||||
append("&sort=")
|
||||
append(sortQuery)
|
||||
}
|
||||
|
||||
val docs = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return docs.select(".card-body .thumb-item-flow")
|
||||
.map {
|
||||
val titleElement = it.selectFirstOrThrow(".thumb_attr.series-title > a")
|
||||
val absUrl = titleElement.attrAsAbsoluteUrl("href")
|
||||
Manga(
|
||||
id = generateUid(absUrl.toRelativeUrl(domain)),
|
||||
title = titleElement.text(),
|
||||
altTitle = null,
|
||||
url = absUrl.toRelativeUrl(domain),
|
||||
publicUrl = absUrl,
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = true,
|
||||
coverUrl = it.selectFirst("div.img-in-ratio")?.attrAsAbsoluteUrl("data-bg").orEmpty(),
|
||||
tags = emptySet(),
|
||||
state = null,
|
||||
author = null,
|
||||
largeCoverUrl = null,
|
||||
description = null,
|
||||
source = MangaSource.MANHWA18,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val chapterUrl = chapter.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(chapterUrl).parseHtml()
|
||||
|
||||
@@ -21,29 +21,42 @@ class ManhwasMen(context: MangaLoaderContext) :
|
||||
override val availableSortOrders: Set<SortOrder>
|
||||
get() = EnumSet.of(SortOrder.POPULARITY)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/manga-list")
|
||||
append("?page=")
|
||||
append(page)
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
append(page.toString())
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("&search=")
|
||||
append(query.urlEncoded())
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("&genero=")
|
||||
append(tag?.key.orEmpty())
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("&genero=")
|
||||
append(it.key)
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&estado=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "complete"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -89,9 +102,9 @@ class ManhwasMen(context: MangaLoaderContext) :
|
||||
)
|
||||
},
|
||||
description = doc.select(".sinopsis").html(),
|
||||
state = when (doc.selectLast(".anime-type-peli")?.text()?.lowercase()) {
|
||||
state = when (doc.selectLast("span.anime-type-peli")?.text()?.lowercase()) {
|
||||
"ongoing" -> MangaState.ONGOING
|
||||
"completed" -> MangaState.FINISHED
|
||||
"complete" -> MangaState.FINISHED
|
||||
else -> null
|
||||
},
|
||||
chapters = doc.select(".episodes-list li").mapChapters(reversed = true) { i, li ->
|
||||
|
||||
@@ -15,20 +15,28 @@ internal class Po2Scans(context: MangaLoaderContext) : MangaParser(context, Mang
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
|
||||
override val configKeyDomain = ConfigKey.Domain("po2scans.com")
|
||||
|
||||
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> {
|
||||
if (offset > 0) {
|
||||
return emptyList()
|
||||
}
|
||||
val url = buildString {
|
||||
append("https://$domain/series")
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("?search=")
|
||||
append(query.urlEncoded())
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/series")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("?search=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select(".series-list").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
val href = "/" + div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
title = div.selectFirstOrThrow("h2").text(),
|
||||
|
||||
@@ -24,37 +24,43 @@ internal class Pururin(context: MangaLoaderContext) :
|
||||
|
||||
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("/search?q=")
|
||||
append(query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page)
|
||||
} else {
|
||||
append("/browse")
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("/tags/content/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
append("?page=")
|
||||
append(page)
|
||||
append("&sort=")
|
||||
when (sortOrder) {
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.POPULARITY -> append("most-viewed")
|
||||
SortOrder.RATING -> append("highest-rated")
|
||||
SortOrder.ALPHABETICAL -> append("title")
|
||||
else -> append("")
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
append("/browse")
|
||||
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/tags/content/")
|
||||
append(it.key)
|
||||
append("/")
|
||||
}
|
||||
|
||||
append("?page=")
|
||||
append(page)
|
||||
|
||||
append("&sort=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.POPULARITY -> append("most-viewed")
|
||||
SortOrder.RATING -> append("highest-rated")
|
||||
SortOrder.ALPHABETICAL -> append("title")
|
||||
else -> append("")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/browse?page=")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user