Fix pagination

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 } return SortOrder.values().first { it in supported }
} }
@JvmField
protected val webClient: WebClient = OkHttpWebClient(context.httpClient, source) protected val webClient: WebClient = OkHttpWebClient(context.httpClient, source)
/** /**

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.parsers package org.koitharu.kotatsu.parsers
import androidx.annotation.RestrictTo
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
@ -8,44 +9,47 @@ import org.koitharu.kotatsu.parsers.util.Paginator
@InternalParsersApi @InternalParsersApi
abstract class PagedMangaParser( abstract class PagedMangaParser(
context: MangaLoaderContext, context: MangaLoaderContext,
source: MangaSource, source: MangaSource,
pageSize: Int, @RestrictTo(RestrictTo.Scope.TESTS) @JvmField internal val pageSize: Int,
searchPageSize: Int = pageSize, searchPageSize: Int = pageSize,
) : MangaParser(context, source) { ) : MangaParser(context, source) {
protected val paginator = Paginator(pageSize) @JvmField
protected val searchPaginator = Paginator(searchPageSize) protected val paginator = Paginator(pageSize)
override suspend fun getList(offset: Int, query: String): List<Manga> { @JvmField
return getList(searchPaginator, offset, query, null, defaultSortOrder) protected val searchPaginator = Paginator(searchPageSize)
}
override suspend fun getList(offset: Int, query: String): List<Manga> {
override suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga> { return getList(searchPaginator, offset, query, null, defaultSortOrder)
return getList(paginator, offset, null, tags, sortOrder ?: defaultSortOrder) }
}
override suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga> {
@InternalParsersApi return getList(paginator, offset, null, tags, sortOrder ?: defaultSortOrder)
@Deprecated("You should use getListPage for PagedMangaParser", level = DeprecationLevel.HIDDEN) }
final override suspend fun getList(
offset: Int, @InternalParsersApi
query: String?, @Deprecated("You should use getListPage for PagedMangaParser", level = DeprecationLevel.HIDDEN)
tags: Set<MangaTag>?, final override suspend fun getList(
sortOrder: SortOrder, offset: Int,
): List<Manga> = throw UnsupportedOperationException("You should use getListPage for PagedMangaParser") query: String?,
tags: Set<MangaTag>?,
abstract suspend fun getListPage(page: Int, query: String?, tags: Set<MangaTag>?, sortOrder: SortOrder): List<Manga> sortOrder: SortOrder,
): List<Manga> = throw UnsupportedOperationException("You should use getListPage for PagedMangaParser")
private suspend fun getList(
paginator: Paginator, abstract suspend fun getListPage(page: Int, query: String?, tags: Set<MangaTag>?, sortOrder: SortOrder): List<Manga>
offset: Int,
query: String?, private suspend fun getList(
tags: Set<MangaTag>?, paginator: Paginator,
sortOrder: SortOrder, offset: Int,
): List<Manga> { query: String?,
val page = paginator.getPage(offset) tags: Set<MangaTag>?,
val list = getListPage(page, query, tags, sortOrder) sortOrder: SortOrder,
paginator.onListReceived(offset, page, list.size) ): List<Manga> {
return list 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 abstract val defaultValue: T
class Domain( class Domain(
override val defaultValue: String, @JvmField vararg val presetValues: String,
@JvmField val presetValues: Array<String>?, ) : ConfigKey<String>("domain") {
) : ConfigKey<String>("domain")
init {
require(presetValues.isNotEmpty()) { "You must provide at least one domain" }
}
override val defaultValue: String
get() = presetValues.first()
}
class ShowSuspiciousContent( class ShowSuspiciousContent(
override val defaultValue: Boolean, override val defaultValue: Boolean,

@ -21,7 +21,7 @@ import java.util.*
@MangaSourceParser("ANIBEL", "Anibel", "be") @MangaSourceParser("ANIBEL", "Anibel", "be")
internal class AnibelParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.ANIBEL) { 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( override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.NEWEST, SortOrder.NEWEST,

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

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

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

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

@ -23,7 +23,7 @@ private const val CHAPTERS_LIMIT = 99999
@MangaSourceParser("COMICK_FUN", "ComicK") @MangaSourceParser("COMICK_FUN", "ComicK")
internal class ComickFunParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.COMICK_FUN) { 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( override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.POPULARITY, SortOrder.POPULARITY,

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

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

@ -28,7 +28,10 @@ internal class ExHentaiParser(
) )
override val configKeyDomain: ConfigKey.Domain 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 override val authUrl: String
get() = "https://${domain}/bounce_login.php" get() = "https://${domain}/bounce_login.php"

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

@ -20,7 +20,7 @@ internal class JapScanParser(context: MangaLoaderContext) : PagedMangaParser(con
override val sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL) 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() override val headers: Headers = Headers.Builder()
.add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0") .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") @MangaSourceParser("MANGADEX", "MangaDex")
internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.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( override val sortOrders: EnumSet<SortOrder> = EnumSet.of(
SortOrder.UPDATED, SortOrder.UPDATED,

@ -22,7 +22,7 @@ class MangaInUaParser(context: MangaLoaderContext) : PagedMangaParser(
override val sortOrders: Set<SortOrder> override val sortOrders: Set<SortOrder>
get() = Collections.singleton(SortOrder.UPDATED) 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( override suspend fun getListPage(
page: Int, page: Int,

@ -13,7 +13,7 @@ import java.util.*
@MangaSourceParser("MANGATOWN", "MangaTown", "en") @MangaSourceParser("MANGATOWN", "MangaTown", "en")
internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.MANGATOWN) { 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( override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL,

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

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

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

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

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

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

@ -17,7 +17,7 @@ import java.util.*
class TruyentranhLHParser(context: MangaLoaderContext) : class TruyentranhLHParser(context: MangaLoaderContext) :
PagedMangaParser(context, source = MangaSource.TRUYENTRANHLH, pageSize = 18) { 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) override val sortOrders: Set<SortOrder> = EnumSet.allOf(SortOrder::class.java)
private val mutex = Mutex() private val mutex = Mutex()

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

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

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

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

@ -10,6 +10,6 @@ internal class SelfMangaParser(
context: MangaLoaderContext, context: MangaLoaderContext,
) : GroupleParser(context, MangaSource.SELFMANGA, 3) { ) : 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 sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
override val configKeyDomain = ConfigKey.Domain(domain, null) override val configKeyDomain = ConfigKey.Domain(domain)
override suspend fun getListPage( override suspend fun getListPage(
page: Int, page: Int,

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

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

@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("MANGA_DISTRICT", "Manga District", "en") @MangaSourceParser("MANGA_DISTRICT", "Manga District", "en")
internal class MangaDistrict(context: MangaLoaderContext) : 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/" override val tagPrefix = "publication-genre/"

@ -11,7 +11,10 @@ import java.text.SimpleDateFormat
import java.util.* import java.util.*
@MangaSourceParser("NEATMANGA", "NeatManga", "en") @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" override val datePattern = "dd MMMM yyyy"

@ -13,7 +13,7 @@ import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
@MangaSourceParser("PRISMA_SCANS", "Prisma Scans", "pt") @MangaSourceParser("PRISMA_SCANS", "Prisma Scans", "pt")
internal class PrismaScansParser(context: MangaLoaderContext) : 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 tagPrefix = "manga-genre/"
override val datePattern = "MMM dd, yyyy" override val datePattern = "MMM dd, yyyy"

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

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

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

@ -8,5 +8,5 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
@MangaSourceParser("MANGACHAN", "Манга-тян", "ru") @MangaSourceParser("MANGACHAN", "Манга-тян", "ru")
internal class MangaChanParser(context: MangaLoaderContext) : ChanParser(context, MangaSource.MANGACHAN) { 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") @MangaSourceParser("YAOICHAN", "Яой-тян", "ru")
internal class YaoiChanParser(context: MangaLoaderContext) : ChanParser(context, MangaSource.YAOICHAN) { 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 { override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()

@ -9,6 +9,6 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
@MangaSourceParser("HENTAILIB", "HentaiLib", "ru") @MangaSourceParser("HENTAILIB", "HentaiLib", "ru")
internal class HentaiLibParser(context: MangaLoaderContext) : MangaLibParser(context, MangaSource.HENTAILIB) { 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 override fun isNsfw(doc: Document) = true
} }

@ -25,7 +25,7 @@ internal open class MangaLibParser(
source: MangaSource, source: MangaSource,
) : PagedMangaParser(context, source, pageSize = 60), MangaParserAuthProvider { ) : 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 override val authUrl: String
get() = "https://${domain}/login" get() = "https://${domain}/login"

@ -9,6 +9,6 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
@MangaSourceParser("YAOILIB", "YaoiLib", "ru") @MangaSourceParser("YAOILIB", "YaoiLib", "ru")
internal class YaoiLibParser(context: MangaLoaderContext) : MangaLibParser(context, MangaSource.YAOILIB) { 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 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.domain
import org.koitharu.kotatsu.parsers.util.medianOrNull import org.koitharu.kotatsu.parsers.util.medianOrNull
import org.koitharu.kotatsu.parsers.util.mimeType 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) @ExtendWith(AuthCheckExtension::class)
@ -34,6 +37,11 @@ internal class MangaParserTest {
val parser = source.newParser(context) val parser = source.newParser(context)
val page1 = parser.getList(0, sortOrder = null, tags = null) val page1 = parser.getList(0, sortOrder = null, tags = null)
val page2 = parser.getList(page1.size, 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(page1.isNotEmpty()) { "Page 1 is empty" }
assert(page2.isNotEmpty()) { "Page 2 is empty" } assert(page2.isNotEmpty()) { "Page 2 is empty" }
assert(page1 != page2) { "Pages are equal" } assert(page1 != page2) { "Pages are equal" }

Loading…
Cancel
Save