Fix pagination

pull/180/head
Koitharu 3 years ago
parent 46f8b8e700
commit 3d7a39cf67
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -47,6 +47,7 @@ abstract class MangaParser @InternalParsersApi constructor(
return SortOrder.values().first { it in supported }
}
@JvmField
protected val webClient: WebClient = OkHttpWebClient(context.httpClient, source)
/**

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.parsers
import androidx.annotation.RestrictTo
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
@ -8,44 +9,47 @@ import org.koitharu.kotatsu.parsers.util.Paginator
@InternalParsersApi
abstract class PagedMangaParser(
context: MangaLoaderContext,
source: MangaSource,
pageSize: Int,
searchPageSize: Int = pageSize,
context: MangaLoaderContext,
source: MangaSource,
@RestrictTo(RestrictTo.Scope.TESTS) @JvmField internal val pageSize: Int,
searchPageSize: Int = pageSize,
) : MangaParser(context, source) {
protected val paginator = Paginator(pageSize)
protected val searchPaginator = Paginator(searchPageSize)
override suspend fun getList(offset: Int, query: String): List<Manga> {
return getList(searchPaginator, offset, query, null, defaultSortOrder)
}
override suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga> {
return getList(paginator, offset, null, tags, sortOrder ?: defaultSortOrder)
}
@InternalParsersApi
@Deprecated("You should use getListPage for PagedMangaParser", level = DeprecationLevel.HIDDEN)
final override suspend fun getList(
offset: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> = throw UnsupportedOperationException("You should use getListPage for PagedMangaParser")
abstract suspend fun getListPage(page: Int, query: String?, tags: Set<MangaTag>?, sortOrder: SortOrder): List<Manga>
private suspend fun getList(
paginator: Paginator,
offset: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val page = paginator.getPage(offset)
val list = getListPage(page, query, tags, sortOrder)
paginator.onListReceived(offset, page, list.size)
return list
}
@JvmField
protected val paginator = Paginator(pageSize)
@JvmField
protected val searchPaginator = Paginator(searchPageSize)
override suspend fun getList(offset: Int, query: String): List<Manga> {
return getList(searchPaginator, offset, query, null, defaultSortOrder)
}
override suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga> {
return getList(paginator, offset, null, tags, sortOrder ?: defaultSortOrder)
}
@InternalParsersApi
@Deprecated("You should use getListPage for PagedMangaParser", level = DeprecationLevel.HIDDEN)
final override suspend fun getList(
offset: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> = throw UnsupportedOperationException("You should use getListPage for PagedMangaParser")
abstract suspend fun getListPage(page: Int, query: String?, tags: Set<MangaTag>?, sortOrder: SortOrder): List<Manga>
private suspend fun getList(
paginator: Paginator,
offset: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val page = paginator.getPage(offset)
val list = getListPage(page, query, tags, sortOrder)
paginator.onListReceived(offset, page, list.size)
return list
}
}

@ -7,9 +7,16 @@ sealed class ConfigKey<T>(
abstract val defaultValue: T
class Domain(
override val defaultValue: String,
@JvmField val presetValues: Array<String>?,
) : ConfigKey<String>("domain")
@JvmField vararg val presetValues: String,
) : ConfigKey<String>("domain") {
init {
require(presetValues.isNotEmpty()) { "You must provide at least one domain" }
}
override val defaultValue: String
get() = presetValues.first()
}
class ShowSuspiciousContent(
override val defaultValue: Boolean,

@ -21,7 +21,7 @@ import java.util.*
@MangaSourceParser("ANIBEL", "Anibel", "be")
internal class AnibelParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.ANIBEL) {
override val configKeyDomain = ConfigKey.Domain("anibel.net", null)
override val configKeyDomain = ConfigKey.Domain("anibel.net")
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.NEWEST,

@ -35,16 +35,13 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
override val configKeyDomain = ConfigKey.Domain(
"bato.to",
arrayOf(
"bato.to",
"mto.to",
"hto.to",
"mangatoto.com",
"battwo.com",
"batotwo.com",
"comiko.net",
"batotoo.com",
),
"mto.to",
"hto.to",
"mangatoto.com",
"battwo.com",
"batotwo.com",
"comiko.net",
"batotoo.com",
)
override suspend fun getListPage(

@ -26,7 +26,7 @@ internal class BentomangaParser(context: MangaLoaderContext) : PagedMangaParser(
SortOrder.ALPHABETICAL,
)
override val configKeyDomain = ConfigKey.Domain("www.bentomanga.com", null)
override val configKeyDomain = ConfigKey.Domain("bentomanga.com", "www.bentomanga.com")
override val headers: Headers = Headers.Builder()
.add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0")
@ -118,7 +118,7 @@ internal class BentomangaParser(context: MangaLoaderContext) : PagedMangaParser(
parseChapters(root)
} else {
coroutineScope {
val result = ArrayList<MangaChapter>(parseChapters(root))
val result = ArrayList(parseChapters(root))
result.ensureCapacity(result.size * max)
(2..max).map { i ->
async {

@ -20,7 +20,7 @@ class BlogTruyenParser(context: MangaLoaderContext) :
PagedMangaParser(context, MangaSource.BLOGTRUYEN, pageSize = 20) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("blogtruyen.vn", null)
get() = ConfigKey.Domain("blogtruyen.vn")
override val sortOrders: Set<SortOrder>
get() = EnumSet.of(SortOrder.UPDATED)
@ -182,7 +182,6 @@ class BlogTruyenParser(context: MangaLoaderContext) :
val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
val pages = ArrayList<MangaPage>()
val referer = chapter.url.toAbsoluteUrl(domain)
doc.select("#content > img").forEach { img ->
pages.add(
MangaPage(

@ -1,33 +1,26 @@
package org.koitharu.kotatsu.parsers.site
import org.koitharu.kotatsu.parsers.InternalParsersApi
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
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 java.util.*
@MangaSourceParser("CLONEMANGA", "CloneManga", "en")
internal class CloneMangaParser(context: MangaLoaderContext) : PagedMangaParser(
context,
MangaSource.CLONEMANGA,
pageSize = 1,
) {
internal class CloneMangaParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.CLONEMANGA) {
override val sortOrders: Set<SortOrder> = Collections.singleton(
SortOrder.POPULARITY,
)
override val configKeyDomain = ConfigKey.Domain("manga.clone-army.org", null)
override val configKeyDomain = ConfigKey.Domain("manga.clone-army.org")
override suspend fun getListPage(
page: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
if (query != null || page > 1) {
@InternalParsersApi
override suspend fun getList(offset: Int, query: String?, tags: Set<MangaTag>?, sortOrder: SortOrder): List<Manga> {
if (query != null || offset > 0) {
return emptyList()
}
val link = "https://${domain}/viewer_landing.php"

@ -23,7 +23,7 @@ private const val CHAPTERS_LIMIT = 99999
@MangaSourceParser("COMICK_FUN", "ComicK")
internal class ComickFunParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.COMICK_FUN) {
override val configKeyDomain = ConfigKey.Domain("comick.app", null)
override val configKeyDomain = ConfigKey.Domain("comick.app")
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.POPULARITY,

@ -16,7 +16,7 @@ import java.util.*
@MangaSourceParser("DESUME", "Desu.me", "ru")
internal class DesuMeParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.DESUME, 20) {
override val configKeyDomain = ConfigKey.Domain("desu.me", null)
override val configKeyDomain = ConfigKey.Domain("desu.me")
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,

@ -13,7 +13,7 @@ import java.util.*
class DoujinDesuParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.DOUJINDESU, pageSize = 18) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("212.32.226.234", null)
get() = ConfigKey.Domain("212.32.226.234")
override val sortOrders: Set<SortOrder>
get() = EnumSet.of(SortOrder.UPDATED, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.POPULARITY)

@ -28,7 +28,10 @@ internal class ExHentaiParser(
)
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain(if (isAuthorized) DOMAIN_AUTHORIZED else DOMAIN_UNAUTHORIZED, null)
get() = ConfigKey.Domain(
if (isAuthorized) DOMAIN_AUTHORIZED else DOMAIN_UNAUTHORIZED,
if (isAuthorized) DOMAIN_UNAUTHORIZED else DOMAIN_AUTHORIZED,
)
override val authUrl: String
get() = "https://${domain}/bounce_login.php"

@ -42,7 +42,7 @@ class HoneyMangaParser(context: MangaLoaderContext) : PagedMangaParser(context,
.build()
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("honey-manga.com.ua", null)
get() = ConfigKey.Domain("honey-manga.com.ua")
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.POPULARITY,
@ -58,7 +58,7 @@ class HoneyMangaParser(context: MangaLoaderContext) : PagedMangaParser(context,
body.put("sortOrder", "ASC")
val chapterRequest = webClient.httpPost(chapterApi, body).parseJson()
return manga.copy(
chapters = chapterRequest.getJSONArray("data").mapJSONIndexed() { i, jo ->
chapters = chapterRequest.getJSONArray("data").mapJSONIndexed { i, jo ->
MangaChapter(
id = generateUid(jo.getString("id")),
name = buildString {

@ -20,7 +20,7 @@ internal class JapScanParser(context: MangaLoaderContext) : PagedMangaParser(con
override val sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
override val configKeyDomain = ConfigKey.Domain("www.japscan.lol", arrayOf("www.japscan.lol", "japscan.ws"))
override val configKeyDomain = ConfigKey.Domain("www.japscan.lol", "japscan.ws")
override val headers: Headers = Headers.Builder()
.add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0")

@ -28,7 +28,7 @@ private const val LOCALE_FALLBACK = "en"
@MangaSourceParser("MANGADEX", "MangaDex")
internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.MANGADEX) {
override val configKeyDomain = ConfigKey.Domain("mangadex.org", null)
override val configKeyDomain = ConfigKey.Domain("mangadex.org")
override val sortOrders: EnumSet<SortOrder> = EnumSet.of(
SortOrder.UPDATED,

@ -22,7 +22,7 @@ class MangaInUaParser(context: MangaLoaderContext) : PagedMangaParser(
override val sortOrders: Set<SortOrder>
get() = Collections.singleton(SortOrder.UPDATED)
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manga.in.ua", null)
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manga.in.ua")
override suspend fun getListPage(
page: Int,

@ -13,7 +13,7 @@ import java.util.*
@MangaSourceParser("MANGATOWN", "MangaTown", "en")
internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.MANGATOWN) {
override val configKeyDomain = ConfigKey.Domain("www.mangatown.com", null)
override val configKeyDomain = ConfigKey.Domain("www.mangatown.com")
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.ALPHABETICAL,

@ -11,10 +11,9 @@ import java.util.*
@MangaSourceParser("MANHWA18", "Manhwa18", "en")
class Manhwa18Parser(context: MangaLoaderContext) :
PagedMangaParser(context, MangaSource.MANHWA18, pageSize = 20, searchPageSize = 20) {
PagedMangaParser(context, MangaSource.MANHWA18, pageSize = 18, searchPageSize = 18) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("manhwa18.net", null)
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwa18.net")
override val sortOrders: Set<SortOrder>
get() = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.ALPHABETICAL, SortOrder.NEWEST)

@ -18,7 +18,7 @@ import java.util.*
class NHentaiParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.NHENTAI, pageSize = 25) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("nhentai.net", null)
get() = ConfigKey.Domain("nhentai.net")
override val sortOrders: Set<SortOrder>
get() = EnumSet.of(SortOrder.NEWEST, SortOrder.POPULARITY)

@ -18,11 +18,11 @@ import java.util.*
class NetTruyenParser(context: MangaLoaderContext) :
PagedMangaParser(context, MangaSource.NETTRUYEN, pageSize = 36) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain(
"www.nettruyento.com",
arrayOf("www.nettruyento.com", "nettruyento.com", "nettruyenin.com"),
)
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain(
"www.nettruyento.com",
"nettruyento.com",
"nettruyenin.com",
)
override val sortOrders: Set<SortOrder>
get() = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.RATING)

@ -33,7 +33,7 @@ class NicovideoSeigaParser(context: MangaLoaderContext) :
SortOrder.POPULARITY,
)
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("nicovideo.jp", null)
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("nicovideo.jp")
@InternalParsersApi
override suspend fun getList(

@ -19,7 +19,7 @@ internal abstract class NineMangaParser(
defaultDomain: String,
) : PagedMangaParser(context, source, pageSize = 26), Interceptor {
override val configKeyDomain = ConfigKey.Domain(defaultDomain, null)
override val configKeyDomain = ConfigKey.Domain(defaultDomain)
init {
context.cookieJar.insertCookies(domain, "ninemanga_template_desk=yes")
@ -102,12 +102,12 @@ internal abstract class NineMangaParser(
val infoRoot = root.selectFirstOrThrow("div.bookintro")
return manga.copy(
tags = infoRoot.getElementsByAttributeValue("itemprop", "genre").first()?.select("a")?.mapToSet { a ->
MangaTag(
title = a.text().toTitleCase(),
key = a.attr("href").substringBetween("/", "."),
source = source,
)
}.orEmpty(),
MangaTag(
title = a.text().toTitleCase(),
key = a.attr("href").substringBetween("/", "."),
source = source,
)
}.orEmpty(),
author = infoRoot.getElementsByAttributeValue("itemprop", "author").first()?.text(),
state = parseStatus(infoRoot.select("li a.red").text()),
description = infoRoot.getElementsByAttributeValue("itemprop", "description").first()?.html()

@ -19,8 +19,8 @@ internal class NudeMoonParser(
) : MangaParser(context, MangaSource.NUDEMOON), MangaParserAuthProvider {
override val configKeyDomain = ConfigKey.Domain(
defaultValue = "nude-moon.org",
presetValues = arrayOf("nude-moon.org", "nude-moon.net"),
"nude-moon.org",
"nude-moon.net",
)
override val authUrl: String
get() = "https://${domain}/index.php"

@ -35,7 +35,7 @@ internal class RemangaParser(
override val headers
get() = getApiHeaders()
override val configKeyDomain = ConfigKey.Domain("remanga.org", arrayOf("remanga.org", "реманга.орг"))
override val configKeyDomain = ConfigKey.Domain("remanga.org", "реманга.орг")
override val authUrl: String
get() = "https://${domain}/user/login"

@ -17,7 +17,7 @@ import java.util.*
class TruyentranhLHParser(context: MangaLoaderContext) :
PagedMangaParser(context, source = MangaSource.TRUYENTRANHLH, pageSize = 18) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("truyentranhlh.net", null)
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("truyentranhlh.net")
override val sortOrders: Set<SortOrder> = EnumSet.allOf(SortOrder::class.java)
private val mutex = Mutex()

@ -20,7 +20,7 @@ class UnionMangasParser(context: MangaLoaderContext) : PagedMangaParser(context,
SortOrder.POPULARITY,
)
override val configKeyDomain = ConfigKey.Domain("unionleitor.top", emptyArray())
override val configKeyDomain = ConfigKey.Domain("unionleitor.top")
override suspend fun getListPage(
page: Int,

@ -20,7 +20,6 @@ internal class AllHentaiParser(
override val configKeyDomain = ConfigKey.Domain(
"2023.allhen.online",
null,
)
override val defaultIsNsfw = true

@ -12,7 +12,7 @@ internal class MintMangaParser(
override val configKeyDomain = ConfigKey.Domain(
"mintmanga.live",
arrayOf("mintmanga.live", "mintmanga.com"),
"mintmanga.com",
)
}

@ -12,7 +12,7 @@ internal class ReadmangaParser(
override val configKeyDomain = ConfigKey.Domain(
"readmanga.live",
arrayOf("readmanga.io", "readmanga.live", "readmanga.me"),
"readmanga.io",
"readmanga.me",
)
}

@ -10,6 +10,6 @@ internal class SelfMangaParser(
context: MangaLoaderContext,
) : GroupleParser(context, MangaSource.SELFMANGA, 3) {
override val configKeyDomain = ConfigKey.Domain("selfmanga.live", null)
override val configKeyDomain = ConfigKey.Domain("selfmanga.live")
}

@ -24,7 +24,7 @@ abstract class Madara5Parser @InternalParsersApi constructor(
override val sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
override val configKeyDomain = ConfigKey.Domain(domain, null)
override val configKeyDomain = ConfigKey.Domain(domain)
override suspend fun getListPage(
page: Int,

@ -12,7 +12,8 @@ internal abstract class Madara6Parser(
context: MangaLoaderContext,
source: MangaSource,
domain: String,
) : MadaraParser(context, source, domain) {
pageSize: Int = 12,
) : MadaraParser(context, source, domain, pageSize) {
override val datePattern: String = "dd MMMM yyyy"

@ -19,9 +19,10 @@ internal abstract class MadaraParser(
context: MangaLoaderContext,
source: MangaSource,
domain: String,
) : PagedMangaParser(context, source, pageSize = 12) {
pageSize: Int = 12,
) : PagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain, null)
override val configKeyDomain = ConfigKey.Domain(domain)
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
@ -60,8 +61,7 @@ internal abstract class MadaraParser(
return doc.select("div.row.c-tabs-item__content").ifEmpty {
doc.select("div.page-item-detail.manga")
}.map { div ->
val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href")
?: div.parseFailed("Link not found")
val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found")
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary")
Manga(
id = generateUid(href),
@ -70,8 +70,7 @@ internal abstract class MadaraParser(
coverUrl = div.selectFirst("img")?.src().orEmpty(),
title = (summary?.selectFirst("h3") ?: summary?.selectFirst("h4"))?.text().orEmpty(),
altTitle = null,
rating = div.selectFirst("span.total_votes")?.ownText()
?.toFloatOrNull()?.div(5f) ?: -1f,
rating = div.selectFirst("span.total_votes")?.ownText()?.toFloatOrNull()?.div(5f) ?: -1f,
tags = summary?.selectFirst(".mg_genres")?.select("a")?.mapNotNullToSet { a ->
MangaTag(
key = a.attr("href").removeSuffix('/').substringAfterLast('/'),
@ -80,10 +79,8 @@ internal abstract class MadaraParser(
)
}.orEmpty(),
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(),
state = when (
summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")
?.ownText()?.trim()?.lowercase()
) {
state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText()?.trim()
?.lowercase()) {
"ongoing" -> MangaState.ONGOING
"completed" -> MangaState.FINISHED
else -> null
@ -106,8 +103,7 @@ internal abstract class MadaraParser(
val keySet = HashSet<String>(list.size)
return list.mapNotNullToSet { li ->
val a = li.selectFirst("a") ?: return@mapNotNullToSet null
val href = a.attr("href").removeSuffix("/")
.substringAfterLast(tagPrefix, "")
val href = a.attr("href").removeSuffix("/").substringAfterLast(tagPrefix, "")
if (href.isEmpty() || !keySet.add(href)) {
return@mapNotNullToSet null
}
@ -125,34 +121,26 @@ internal abstract class MadaraParser(
val fullUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
val chaptersDeferred = async { getChapters(manga, doc) }
val root = doc.body().selectFirst("div.profile-manga")
?.selectFirst("div.summary_content")
?.selectFirst("div.post-content")
?: throw ParseException("Root not found", fullUrl)
val root2 = doc.body().selectFirst("div.content-area")
?.selectFirst("div.c-page")
val root = doc.body().selectFirst("div.profile-manga")?.selectFirst("div.summary_content")
?.selectFirst("div.post-content") ?: throw ParseException("Root not found", fullUrl)
val root2 = doc.body().selectFirst("div.content-area")?.selectFirst("div.c-page")
?: throw ParseException("Root2 not found", fullUrl)
manga.copy(
tags = root.selectFirst("div.genres-content")?.select("a")
?.mapNotNullToSet { a ->
MangaTag(
key = a.attr("href").removeSuffix("/").substringAfterLast('/'),
title = a.text().toTitleCase(),
source = source,
)
} ?: manga.tags,
description = root2.selectFirst("div.description-summary")
?.selectFirst("div.summary__content")
?.select("p")
?.filterNot { it.ownText().startsWith("A brief description") }
?.joinToString { it.html() },
tags = root.selectFirst("div.genres-content")?.select("a")?.mapNotNullToSet { a ->
MangaTag(
key = a.attr("href").removeSuffix("/").substringAfterLast('/'),
title = a.text().toTitleCase(),
source = source,
)
} ?: manga.tags,
description = root2.selectFirst("div.description-summary")?.selectFirst("div.summary__content")?.select("p")
?.filterNot { it.ownText().startsWith("A brief description") }?.joinToString { it.html() },
chapters = chaptersDeferred.await(),
)
}
protected open suspend fun getChapters(manga: Manga, doc: Document): List<MangaChapter> {
val root2 = doc.body().selectFirstOrThrow("div.content-area")
.selectFirstOrThrow("div.c-page")
val root2 = doc.body().selectFirstOrThrow("div.content-area").selectFirstOrThrow("div.c-page")
val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
return root2.select("li").mapChapters(reversed = true) { i, li ->
val a = li.selectFirst("a")
@ -176,8 +164,7 @@ internal abstract class MadaraParser(
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
val root = doc.body().selectFirst("div.main-col-inner")
?.selectFirst("div.reading-content")
val root = doc.body().selectFirst("div.main-col-inner")?.selectFirst("div.reading-content")
?: throw ParseException("Root not found", fullUrl)
return root.select("div.page-break").map { div ->
val img = div.selectFirst("img") ?: div.parseFailed("Page image not found")
@ -233,8 +220,7 @@ internal abstract class MadaraParser(
} else {
it
}
}
.let { dateFormat.tryParse(it.joinToString(" ")) }
}.let { dateFormat.tryParse(it.joinToString(" ")) }
}
else -> dateFormat.tryParse(date)
@ -292,20 +278,18 @@ internal abstract class MadaraParser(
}
private fun createRequestTemplate() =
(
"action=madara_load_more&page=1&template=madara-core%2Fcontent%2Fcontent-search&vars%5Bs%5D=&vars%5B" +
"orderby%5D=meta_value_num&vars%5Bpaged%5D=1&vars%5Btemplate%5D=search&vars%5Bmeta_query" +
"%5D%5B0%5D%5Brelation%5D=AND&vars%5Bmeta_query%5D%5Brelation%5D=OR&vars%5Bpost_type" +
"%5D=wp-manga&vars%5Bpost_status%5D=publish&vars%5Bmeta_key%5D=_latest_update&vars%5Border" +
"%5D=desc&vars%5Bmanga_archives_item_layout%5D=default"
).split('&')
.map {
val pos = it.indexOf('=')
it.substring(0, pos) to it.substring(pos + 1)
}.toMutableMap()
("action=madara_load_more&page=1&template=madara-core%2Fcontent%2Fcontent-search&vars%5Bs%5D=&vars%5B" + "orderby%5D=meta_value_num&vars%5Bpaged%5D=1&vars%5Btemplate%5D=search&vars%5Bmeta_query" + "%5D%5B0%5D%5Brelation%5D=AND&vars%5Bmeta_query%5D%5Brelation%5D=OR&vars%5Bpost_type" + "%5D=wp-manga&vars%5Bpost_status%5D=publish&vars%5Bmeta_key%5D=_latest_update&vars%5Border" + "%5D=desc&vars%5Bmanga_archives_item_layout%5D=default").split(
'&',
).map {
val pos = it.indexOf('=')
it.substring(0, pos) to it.substring(pos + 1)
}.toMutableMap()
@MangaSourceParser("MANGAWEEBS", "MangaWeebs", "en")
class MangaWeebs(context: MangaLoaderContext) : MadaraParser(context, MangaSource.MANGAWEEBS, "mangaweebs.in") {
class MangaWeebs(context: MangaLoaderContext) : MadaraParser(
context, MangaSource.MANGAWEEBS, "mangaweebs.in",
pageSize = 20,
) {
override val datePattern = "dd MMMM HH:mm"
}
@ -316,10 +300,16 @@ internal abstract class MadaraParser(
}
@MangaSourceParser("PIANMANGA", "PianManga", "en")
class PianManga(context: MangaLoaderContext) : MadaraParser(context, MangaSource.PIANMANGA, "pianmanga.me")
class PianManga(context: MangaLoaderContext) : MadaraParser(
context, MangaSource.PIANMANGA, "pianmanga.me",
pageSize = 10,
)
@MangaSourceParser("MANGAROSIE", "MangaRosie", "en")
class MangaRosie(context: MangaLoaderContext) : MadaraParser(context, MangaSource.MANGAROSIE, "mangarosie.in")
class MangaRosie(context: MangaLoaderContext) : MadaraParser(
context, MangaSource.MANGAROSIE, "mangarosie.in",
pageSize = 16,
)
@MangaSourceParser("MANGATX", "MangaTx", "en")
class MangaTx(context: MangaLoaderContext) : MadaraParser(context, MangaSource.MANGATX, "mangatx.com")
@ -333,10 +323,16 @@ internal abstract class MadaraParser(
class AquaManga(context: MangaLoaderContext) : MadaraParser(context, MangaSource.AQUAMANGA, "aquamanga.com")
@MangaSourceParser("MANGALEK", "MangaLek", "ar")
class MangaLek(context: MangaLoaderContext) : MadaraParser(context, MangaSource.MANGALEK, "mangalek.com")
class MangaLek(context: MangaLoaderContext) : MadaraParser(
context, MangaSource.MANGALEK, "mangalek.com",
pageSize = 10,
)
@MangaSourceParser("HARIMANGA", "HariManga", "en")
class HariManga(context: MangaLoaderContext) : MadaraParser(context, MangaSource.HARIMANGA, "harimanga.com") {
class HariManga(context: MangaLoaderContext) : MadaraParser(
context, MangaSource.HARIMANGA, "harimanga.com",
pageSize = 10,
) {
override val datePattern = "MM/dd/yyyy"
}
@ -350,10 +346,16 @@ internal abstract class MadaraParser(
class FreeManga(context: MangaLoaderContext) : MadaraParser(context, MangaSource.FREEMANGA, "freemanga.me")
@MangaSourceParser("MANGA_KOMI", "MangaKomi", "en")
class MangaKomi(context: MangaLoaderContext) : MadaraParser(context, MangaSource.MANGA_KOMI, "mangakomi.io")
class MangaKomi(context: MangaLoaderContext) : MadaraParser(
context, MangaSource.MANGA_KOMI, "mangakomi.io",
pageSize = 18,
)
@MangaSourceParser("MANHWACLAN", "ManhwaClan", "en")
class ManhwaClan(context: MangaLoaderContext) : MadaraParser(context, MangaSource.MANHWACLAN, "manhwaclan.com")
class ManhwaClan(context: MangaLoaderContext) : MadaraParser(
context, MangaSource.MANHWACLAN, "manhwaclan.com",
pageSize = 10,
)
@MangaSourceParser("MANGA_3S", "Manga3s", "en")
class Manga3s(context: MangaLoaderContext) : MadaraParser(context, MangaSource.MANGA_3S, "manga3s.com") {
@ -361,7 +363,10 @@ internal abstract class MadaraParser(
}
@MangaSourceParser("MANHWAKOOL", "Manhwa Kool", "en")
class ManhwaKool(context: MangaLoaderContext) : MadaraParser(context, MangaSource.MANHWAKOOL, "manhwakool.com") {
class ManhwaKool(context: MangaLoaderContext) : MadaraParser(
context, MangaSource.MANHWAKOOL, "manhwakool.com",
pageSize = 10,
) {
override val datePattern: String = "MM/dd"
}
@ -382,14 +387,16 @@ internal abstract class MadaraParser(
}
@MangaSourceParser("BAKAMAN", "BakaMan", "th")
class BakaMan(context: MangaLoaderContext) : MadaraParser(context, MangaSource.BAKAMAN, "bakaman.net") {
class BakaMan(context: MangaLoaderContext) : MadaraParser(
context, MangaSource.BAKAMAN, "bakaman.net",
pageSize = 18,
) {
override val isNsfwSource = false
}
@MangaSourceParser("HENTAI20", "Hentai20", "en")
class Hentai20(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.HENTAI20, "hentai20.io") {
class Hentai20(context: MangaLoaderContext) : MadaraParser(context, MangaSource.HENTAI20, "hentai20.io") {
override val tagPrefix = "manga-genre/"
@ -398,7 +405,7 @@ internal abstract class MadaraParser(
@MangaSourceParser("ALLPORN_COMIC", "All Porn Comic", "en")
class AllPornComic(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.ALLPORN_COMIC, "allporncomic.com") {
MadaraParser(context, MangaSource.ALLPORN_COMIC, "allporncomic.com", pageSize = 24) {
override val tagPrefix = "porncomic-genre/"
@ -426,10 +433,16 @@ internal abstract class MadaraParser(
}
@MangaSourceParser("MANGACV", "Manga Cv", "en")
class MangaCv(context: MangaLoaderContext) : MadaraParser(context, MangaSource.MANGACV, "mangacv.com")
class MangaCv(context: MangaLoaderContext) : MadaraParser(
context, MangaSource.MANGACV, "mangacv.com",
pageSize = 10,
)
@MangaSourceParser("TOONILY", "Toonily", "en")
class Toonily(context: MangaLoaderContext) : MadaraParser(context, MangaSource.TOONILY, "toonily.com") {
class Toonily(context: MangaLoaderContext) : MadaraParser(
context, MangaSource.TOONILY, "toonily.com",
pageSize = 18,
) {
override val tagPrefix = "webtoon-genre/"
@ -438,7 +451,7 @@ internal abstract class MadaraParser(
@MangaSourceParser("MANGA_MANHUA", "Manga Manhua", "en")
class MangaManhua(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGA_MANHUA, "mangamanhua.online")
MadaraParser(context, MangaSource.MANGA_MANHUA, "mangamanhua.online", pageSize = 10)
@MangaSourceParser("MANGA_247", "247MANGA", "en")
class Manga247(context: MangaLoaderContext) : MadaraParser(context, MangaSource.MANGA_247, "247manga.com") {
@ -449,7 +462,10 @@ internal abstract class MadaraParser(
class Manga365(context: MangaLoaderContext) : MadaraParser(context, MangaSource.MANGA_365, "365manga.com")
@MangaSourceParser("MANGACLASH", "Mangaclash", "en")
class Mangaclash(context: MangaLoaderContext) : MadaraParser(context, MangaSource.MANGACLASH, "mangaclash.com") {
class Mangaclash(context: MangaLoaderContext) : MadaraParser(
context, MangaSource.MANGACLASH, "mangaclash.com",
pageSize = 18,
) {
override val datePattern = "MM/dd/yyyy"
}

@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("MANGA_DISTRICT", "Manga District", "en")
internal class MangaDistrict(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGA_DISTRICT, "mangadistrict.com") {
MadaraParser(context, MangaSource.MANGA_DISTRICT, "mangadistrict.com", pageSize = 30) {
override val tagPrefix = "publication-genre/"

@ -11,7 +11,10 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("NEATMANGA", "NeatManga", "en")
internal class NeatManga(context: MangaLoaderContext) : MadaraParser(context, MangaSource.NEATMANGA, "neatmangas.com") {
internal class NeatManga(context: MangaLoaderContext) : MadaraParser(
context, MangaSource.NEATMANGA, "neatmangas.com",
pageSize = 20,
) {
override val datePattern = "dd MMMM yyyy"

@ -13,7 +13,7 @@ import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
@MangaSourceParser("PRISMA_SCANS", "Prisma Scans", "pt")
internal class PrismaScansParser(context: MangaLoaderContext) :
Madara6Parser(context, MangaSource.PRISMA_SCANS, "prismascans.net") {
Madara6Parser(context, MangaSource.PRISMA_SCANS, "prismascans.net", 10) {
override val tagPrefix = "manga-genre/"
override val datePattern = "MMM dd, yyyy"

@ -14,37 +14,37 @@ import java.util.*
@MangaSourceParser("TATAKAE_SCANS", "Tatakae Scans", "pt")
internal class TatakaeScansParser(context: MangaLoaderContext) :
Madara6Parser(context, MangaSource.TATAKAE_SCANS, "tatakaescan.com") {
Madara6Parser(context, MangaSource.TATAKAE_SCANS, "tatakaescan.com", pageSize = 10) {
override val datePattern: String = "dd 'de' MMMMM 'de' yyyy"
override val datePattern: String = "dd 'de' MMMMM 'de' yyyy"
override fun parseDetails(manga: Manga, body: Element, chapters: List<MangaChapter>): Manga {
val root = body.selectFirstOrThrow(".site-content")
val postContent = root.selectFirstOrThrow(".post-content")
val tags = postContent.getElementsContainingOwnText("Gênero")
.firstOrNull()?.tableValue()
?.getElementsByAttributeValueContaining("href", tagPrefix)
?.mapToSet { a -> a.asMangaTag() } ?: manga.tags
return manga.copy(
largeCoverUrl = root.selectFirst("picture")
?.selectFirst("img[data-src]")
?.attrAsAbsoluteUrlOrNull("data-src"),
description = (root.selectFirst(".detail-content")
?: root.selectFirstOrThrow(".manga-excerpt")).html(),
author = postContent.getElementsContainingOwnText("Autor")
.firstOrNull()?.tableValue()?.text()?.trim(),
state = postContent.getElementsContainingOwnText("Status")
.firstOrNull()?.tableValue()?.text()?.asMangaState(),
tags = tags,
isNsfw = body.hasClass("adult-content"),
chapters = chapters,
)
}
override fun parseDetails(manga: Manga, body: Element, chapters: List<MangaChapter>): Manga {
val root = body.selectFirstOrThrow(".site-content")
val postContent = root.selectFirstOrThrow(".post-content")
val tags = postContent.getElementsContainingOwnText("Gênero")
.firstOrNull()?.tableValue()
?.getElementsByAttributeValueContaining("href", tagPrefix)
?.mapToSet { a -> a.asMangaTag() } ?: manga.tags
return manga.copy(
largeCoverUrl = root.selectFirst("picture")
?.selectFirst("img[data-src]")
?.attrAsAbsoluteUrlOrNull("data-src"),
description = (root.selectFirst(".detail-content")
?: root.selectFirstOrThrow(".manga-excerpt")).html(),
author = postContent.getElementsContainingOwnText("Autor")
.firstOrNull()?.tableValue()?.text()?.trim(),
state = postContent.getElementsContainingOwnText("Status")
.firstOrNull()?.tableValue()?.text()?.asMangaState(),
tags = tags,
isNsfw = body.hasClass("adult-content"),
chapters = chapters,
)
}
override fun String.asMangaState() = when (trim().lowercase(Locale.ROOT)) {
"em lançamento" -> MangaState.ONGOING
override fun String.asMangaState() = when (trim().lowercase(Locale.ROOT)) {
"em lançamento" -> MangaState.ONGOING
else -> null
}
else -> null
}
}

@ -30,6 +30,7 @@ internal abstract class MangaReaderParser(
abstract val listUrl: String
abstract val tableMode: Boolean
protected open val isNsfwSource = false
open val chapterDateFormat = SimpleDateFormat("MMM d, yyyy", idLocale)
private var tagCache: ArrayMap<String, MangaTag>? = null
@ -70,7 +71,7 @@ internal abstract class MangaReaderParser(
description = mangaInfo?.selectFirst("div.entry-content")?.html(),
state = mangaState,
author = mangaInfo?.selectFirst(".infotable td:contains(Author)")?.lastElementSibling()?.text(),
isNsfw = docs.selectFirst(".restrictcontainer") != null,
isNsfw = manga.isNsfw || docs.selectFirst(".restrictcontainer") != null,
tags = tags.orEmpty(),
chapters = chapters,
)
@ -91,7 +92,7 @@ internal abstract class MangaReaderParser(
description = docs.selectFirst(".info-right div.entry-content > p")?.html(),
state = mangaState,
author = docs.selectFirst(".info-left .tsinfo div:contains(Author)")?.lastElementChild()?.text(),
isNsfw = docs.selectFirst(".info-right .alr") != null,
isNsfw = manga.isNsfw || docs.selectFirst(".info-right .alr") != null,
tags = tags,
chapters = chapters,
)
@ -162,7 +163,7 @@ internal abstract class MangaReaderParser(
altTitle = null,
publicUrl = a.attrAsAbsoluteUrl("href"),
rating = rating,
isNsfw = false,
isNsfw = isNsfwSource,
coverUrl = it.selectFirst("img.ts-post-image")?.imageUrl().orEmpty(),
tags = emptySet(),
state = null,
@ -230,33 +231,27 @@ internal abstract class MangaReaderParser(
@MangaSourceParser("MANHWALAND", "Manhwaland", "id")
class ManhwaLandParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.MANHWALAND, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("manhwaland.us", arrayOf("manhwaland.us", "manhwaland.guru"))
override val listUrl: String
get() = "/manga"
override val tableMode: Boolean
get() = false
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwaland.us", "manhwaland.guru")
override val listUrl: String = "/manga"
override val tableMode: Boolean = false
}
@MangaSourceParser("SEKAIKOMIK", "Sekaikomik", "id")
class SekaikomikParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.SEKAIKOMIK, pageSize = 20, searchPageSize = 100) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("sekaikomik.pro", null)
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("sekaikomik.pro")
override val listUrl: String
get() = "/manga"
override val tableMode: Boolean
get() = false
override val listUrl: String = "/manga"
override val tableMode: Boolean = false
override val chapterDateFormat: SimpleDateFormat = SimpleDateFormat("MMMM D, yyyy", idLocale)
}
@MangaSourceParser("MANHWAINDO", "Manhwaindo", "id")
class ManhwaIndoParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.MANHWAINDO, pageSize = 20, searchPageSize = 10) {
MangaReaderParser(context, MangaSource.MANHWAINDO, pageSize = 30, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("manhwaindo.id", null)
get() = ConfigKey.Domain("manhwaindo.id")
override val chapterDateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.ENGLISH)
override val listUrl: String get() = "/series"
@ -267,7 +262,7 @@ internal abstract class MangaReaderParser(
class ManhwalistParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.MANHWALIST, pageSize = 24, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("manhwalist.in", null)
get() = ConfigKey.Domain("manhwalist.in")
override val listUrl: String = "/manga"
override val tableMode: Boolean get() = false
@ -276,9 +271,9 @@ internal abstract class MangaReaderParser(
@MangaSourceParser("KIRYUU", "Kiryuu", "id")
class KiryuuParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.KIRYUU, pageSize = 20, searchPageSize = 10) {
MangaReaderParser(context, MangaSource.KIRYUU, pageSize = 30, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("kiryuu.id", null)
get() = ConfigKey.Domain("kiryuu.id")
override val listUrl: String
get() = "/manga"
@ -291,7 +286,7 @@ internal abstract class MangaReaderParser(
class TurktoonParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.TURKTOON, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("turktoon.com", null)
get() = ConfigKey.Domain("turktoon.com")
override val listUrl: String
get() = "/manga"
@ -316,7 +311,7 @@ internal abstract class MangaReaderParser(
class WestmangaParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.WESTMANGA, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("westmanga.info", null)
get() = ConfigKey.Domain("westmanga.info")
override val listUrl: String
get() = "/manga"
@ -327,9 +322,9 @@ internal abstract class MangaReaderParser(
@MangaSourceParser("TEMPESTFANSUB", "Tempestfansub", "tr")
class TempestfansubParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.TEMPESTFANSUB, pageSize = 40, searchPageSize = 40) {
MangaReaderParser(context, MangaSource.TEMPESTFANSUB, pageSize = 25, searchPageSize = 40) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("tempestscans.com", null)
get() = ConfigKey.Domain("tempestscans.com")
override val listUrl: String get() = "/manga"
override val tableMode: Boolean get() = true
@ -344,7 +339,7 @@ internal abstract class MangaReaderParser(
tags = infoElement?.select(".wd-full .mgen > a")
?.mapNotNullToSet { getOrCreateTagMap()[it.text()] }
.orEmpty(),
isNsfw = docs.selectFirst(".postbody .alr") != null,
isNsfw = manga.isNsfw || docs.selectFirst(".postbody .alr") != null,
)
}
}
@ -353,7 +348,7 @@ internal abstract class MangaReaderParser(
class ManhwadesuParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.MANHWADESU, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("manhwadesu.pro", arrayOf("manhwadesu.pro", "manhwadesu.org"))
get() = ConfigKey.Domain("manhwadesu.pro", "manhwadesu.org")
override val listUrl: String get() = "/komik"
override val tableMode: Boolean get() = false
@ -363,7 +358,7 @@ internal abstract class MangaReaderParser(
class MangaTaleParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.MANGATALE, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("mangatale.co", null)
get() = ConfigKey.Domain("mangatale.co")
override val listUrl: String get() = "/manga"
override val tableMode: Boolean get() = false
@ -379,7 +374,7 @@ internal abstract class MangaReaderParser(
tags = infoElement?.select(".wd-full .mgen > a")
?.mapNotNullToSet { getOrCreateTagMap()[it.text()] }
.orEmpty(),
isNsfw = docs.selectFirst(".postbody .alr") != null,
isNsfw = manga.isNsfw || docs.selectFirst(".postbody .alr") != null,
)
}
}
@ -388,7 +383,7 @@ internal abstract class MangaReaderParser(
class DragonTranslationParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.DRAGONTRANSLATION, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("dragontranslation.com", null)
get() = ConfigKey.Domain("dragontranslation.com")
override val listUrl: String get() = "/manga"
override val tableMode: Boolean get() = false
@ -404,7 +399,7 @@ internal abstract class MangaReaderParser(
tags = infoElement?.select(".wd-full .mgen > a")
?.mapNotNullToSet { getOrCreateTagMap()[it.text()] }
.orEmpty(),
isNsfw = docs.selectFirst(".postbody .alr") != null,
isNsfw = manga.isNsfw || docs.selectFirst(".postbody .alr") != null,
)
}
}
@ -413,7 +408,7 @@ internal abstract class MangaReaderParser(
class AsuraTRParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.ASURATR, pageSize = 30, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("asurascanstr.com", null)
get() = ConfigKey.Domain("asurascanstr.com")
override val listUrl: String get() = "/manga"
override val tableMode: Boolean get() = false
@ -429,16 +424,16 @@ internal abstract class MangaReaderParser(
tags = infoElement?.select(".wd-full .mgen > a")
?.mapNotNullToSet { getOrCreateTagMap()[it.text()] }
.orEmpty(),
isNsfw = docs.selectFirst(".postbody .alr") != null,
isNsfw = manga.isNsfw || docs.selectFirst(".postbody .alr") != null,
)
}
}
@MangaSourceParser("KOMIKTAP", "KomikTap", "id")
class KomikTapParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.KOMIKTAP, pageSize = 10, searchPageSize = 10) {
MangaReaderParser(context, MangaSource.KOMIKTAP, pageSize = 25, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("92.87.6.124", arrayOf("92.87.6.124", "komiktap.in"))
get() = ConfigKey.Domain("92.87.6.124", "komiktap.in")
override val listUrl: String
get() = "/manga"
@ -450,9 +445,9 @@ internal abstract class MangaReaderParser(
@MangaSourceParser("KUMAPOI", "KumaPoi", "id")
class KumaPoiParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.KUMAPOI, pageSize = 15, searchPageSize = 10) {
MangaReaderParser(context, MangaSource.KUMAPOI, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("kumapoi.me", null)
get() = ConfigKey.Domain("kumapoi.me")
override val listUrl: String
get() = "/manga"
@ -466,7 +461,7 @@ internal abstract class MangaReaderParser(
class AsuraScansParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.ASURASCANS, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("asurascans.com", null)
get() = ConfigKey.Domain("asurascans.com")
override val listUrl: String
get() = "/manga"
@ -484,16 +479,16 @@ internal abstract class MangaReaderParser(
tags = infoElement?.select(".wd-full .mgen > a")
?.mapNotNullToSet { getOrCreateTagMap()[it.text()] }
.orEmpty(),
isNsfw = docs.selectFirst(".postbody .alr") != null,
isNsfw = manga.isNsfw || docs.selectFirst(".postbody .alr") != null,
)
}
}
@MangaSourceParser("TOONHUNTER", "Toon Hunter", "th")
class ToonHunterParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.TOONHUNTER, pageSize = 20, searchPageSize = 10) {
MangaReaderParser(context, MangaSource.TOONHUNTER, pageSize = 30, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("toonhunter.com", null)
get() = ConfigKey.Domain("toonhunter.com")
override val listUrl: String
get() = "/manga"
@ -508,7 +503,7 @@ internal abstract class MangaReaderParser(
chapters = chapters,
description = infoElement?.selectFirst("div.entry-content")?.html(),
author = infoElement?.selectFirst(".flex-wrap div:contains(Author)")?.lastElementSibling()?.text(),
isNsfw = docs.selectFirst(".postbody .alr") != null,
isNsfw = manga.isNsfw || docs.selectFirst(".postbody .alr") != null,
)
}
}
@ -517,7 +512,7 @@ internal abstract class MangaReaderParser(
class CosmicScansParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.COSMICSCANS, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("cosmicscans.com", null)
get() = ConfigKey.Domain("cosmicscans.com")
override val listUrl: String
get() = "/manga"
@ -543,7 +538,7 @@ internal abstract class MangaReaderParser(
class KomikLokalParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.KOMIKLOKAL, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("komikmirror.art", null)
get() = ConfigKey.Domain("komikmirror.art")
override val listUrl: String
get() = "/manga"
@ -567,12 +562,10 @@ internal abstract class MangaReaderParser(
class KomiKavParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.KOMIKAV, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("komikav.com", null)
get() = ConfigKey.Domain("komikav.com")
override val listUrl: String
get() = "/manga"
override val tableMode: Boolean
get() = false
override val listUrl: String = "/manga"
override val tableMode: Boolean = false
override val chapterDateFormat: SimpleDateFormat = SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH)
@ -593,12 +586,11 @@ internal abstract class MangaReaderParser(
class KomikDewasaParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.KOMIKDEWASA, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("komikdewasa.us", arrayOf("komikdewasa.us", "komikdewasa.info"))
get() = ConfigKey.Domain("komikdewasa.us", "komikdewasa.info")
override val listUrl: String
get() = "/manga"
override val tableMode: Boolean
get() = false
override val listUrl: String = "/manga"
override val tableMode: Boolean = false
override val isNsfwSource: Boolean = true
override val chapterDateFormat: SimpleDateFormat = SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH)
@ -615,7 +607,7 @@ internal abstract class MangaReaderParser(
class MangasusuParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.MANGASUSU, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("mangasusu.co.in", null)
get() = ConfigKey.Domain("mangasusu.co.in")
override val listUrl: String
get() = "/project"
@ -629,7 +621,7 @@ internal abstract class MangaReaderParser(
class KomikLabParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.KOMIKLAB, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("komiklab.com", null)
get() = ConfigKey.Domain("komiklab.com")
override val listUrl: String
get() = "/project"
@ -643,7 +635,7 @@ internal abstract class MangaReaderParser(
class KomikIndoParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.KOMIKINDO, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("komikindo.co", null)
get() = ConfigKey.Domain("komikindo.co")
override val listUrl: String
get() = "/project"
@ -657,7 +649,7 @@ internal abstract class MangaReaderParser(
class KomikMangaParser(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.KOMIKMANGA, pageSize = 20, searchPageSize = 10) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("komikhentai.co", null)
get() = ConfigKey.Domain("komikhentai.co")
override val listUrl: String
get() = "/project"

@ -9,52 +9,55 @@ import org.koitharu.kotatsu.parsers.util.*
@MangaSourceParser("HENCHAN", "Хентай-тян", "ru")
internal class HenChanParser(context: MangaLoaderContext) : ChanParser(context, MangaSource.HENCHAN) {
override val configKeyDomain = ConfigKey.Domain(
"y.hentaichan.live",
arrayOf("y.hentaichan.live", "xxx.hentaichan.live", "xx.hentaichan.live", "hentaichan.live", "hentaichan.pro"),
)
override val configKeyDomain = ConfigKey.Domain(
"y.hentaichan.live",
"xxx.hentaichan.live",
"xx.hentaichan.live",
"hentaichan.live",
"hentaichan.pro",
)
override suspend fun getList(
offset: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
return super.getList(offset, query, tags, sortOrder).map {
it.copy(
coverUrl = it.coverUrl.replace("_blur", ""),
isNsfw = true,
)
}
}
override suspend fun getList(
offset: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
return super.getList(offset, query, tags, sortOrder).map {
it.copy(
coverUrl = it.coverUrl.replace("_blur", ""),
isNsfw = true,
)
}
}
override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val root = doc.body().requireElementById("dle-content")
val readLink = manga.url.replace("manga", "online")
return manga.copy(
description = root.getElementById("description")?.html()?.substringBeforeLast("<div"),
largeCoverUrl = root.getElementById("cover")?.absUrl("src"),
tags = root.selectFirst("div.sidetags")?.select("li.sidetag")?.mapToSet {
val a = it.children().last() ?: doc.parseFailed("Invalid tag")
MangaTag(
title = a.text().toTitleCase(),
key = a.attr("href").substringAfterLast('/'),
source = source,
)
} ?: manga.tags,
chapters = listOf(
MangaChapter(
id = generateUid(readLink),
url = readLink,
source = source,
number = 1,
uploadDate = 0L,
name = manga.title,
scanlator = null,
branch = null,
),
),
)
}
override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val root = doc.body().requireElementById("dle-content")
val readLink = manga.url.replace("manga", "online")
return manga.copy(
description = root.getElementById("description")?.html()?.substringBeforeLast("<div"),
largeCoverUrl = root.getElementById("cover")?.absUrl("src"),
tags = root.selectFirst("div.sidetags")?.select("li.sidetag")?.mapToSet {
val a = it.children().last() ?: doc.parseFailed("Invalid tag")
MangaTag(
title = a.text().toTitleCase(),
key = a.attr("href").substringAfterLast('/'),
source = source,
)
} ?: manga.tags,
chapters = listOf(
MangaChapter(
id = generateUid(readLink),
url = readLink,
source = source,
number = 1,
uploadDate = 0L,
name = manga.title,
scanlator = null,
branch = null,
),
),
)
}
}

@ -8,5 +8,5 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
@MangaSourceParser("MANGACHAN", "Манга-тян", "ru")
internal class MangaChanParser(context: MangaLoaderContext) : ChanParser(context, MangaSource.MANGACHAN) {
override val configKeyDomain = ConfigKey.Domain("manga-chan.me", null)
override val configKeyDomain = ConfigKey.Domain("manga-chan.me")
}

@ -11,7 +11,7 @@ import org.koitharu.kotatsu.parsers.util.*
@MangaSourceParser("YAOICHAN", "Яой-тян", "ru")
internal class YaoiChanParser(context: MangaLoaderContext) : ChanParser(context, MangaSource.YAOICHAN) {
override val configKeyDomain = ConfigKey.Domain("yaoi-chan.me", null)
override val configKeyDomain = ConfigKey.Domain("yaoi-chan.me")
override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()

@ -9,6 +9,6 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
@MangaSourceParser("HENTAILIB", "HentaiLib", "ru")
internal class HentaiLibParser(context: MangaLoaderContext) : MangaLibParser(context, MangaSource.HENTAILIB) {
override val configKeyDomain = ConfigKey.Domain("hentailib.me", null)
override val configKeyDomain = ConfigKey.Domain("hentailib.me")
override fun isNsfw(doc: Document) = true
}
}

@ -25,7 +25,7 @@ internal open class MangaLibParser(
source: MangaSource,
) : PagedMangaParser(context, source, pageSize = 60), MangaParserAuthProvider {
override val configKeyDomain = ConfigKey.Domain("mangalib.me", null)
override val configKeyDomain = ConfigKey.Domain("mangalib.me")
override val authUrl: String
get() = "https://${domain}/login"

@ -9,6 +9,6 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
@MangaSourceParser("YAOILIB", "YaoiLib", "ru")
internal class YaoiLibParser(context: MangaLoaderContext) : MangaLibParser(context, MangaSource.YAOILIB) {
override val configKeyDomain = ConfigKey.Domain("yaoilib.me", null)
override val configKeyDomain = ConfigKey.Domain("yaoilib.me")
override fun isNsfw(doc: Document) = true
}
}

@ -11,7 +11,10 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.medianOrNull
import org.koitharu.kotatsu.parsers.util.mimeType
import org.koitharu.kotatsu.test_util.*
import org.koitharu.kotatsu.test_util.isDistinct
import org.koitharu.kotatsu.test_util.isDistinctBy
import org.koitharu.kotatsu.test_util.isUrlAbsolute
import org.koitharu.kotatsu.test_util.maxDuplicates
@ExtendWith(AuthCheckExtension::class)
@ -34,6 +37,11 @@ internal class MangaParserTest {
val parser = source.newParser(context)
val page1 = parser.getList(0, sortOrder = null, tags = null)
val page2 = parser.getList(page1.size, sortOrder = null, tags = null)
if (parser is PagedMangaParser) {
assert(parser.pageSize == page1.size) {
"Page size is ${page1.size} but ${parser.pageSize} expected"
}
}
assert(page1.isNotEmpty()) { "Page 1 is empty" }
assert(page2.isNotEmpty()) { "Page 2 is empty" }
assert(page1 != page2) { "Pages are equal" }

Loading…
Cancel
Save