Change configuration behavior

pull/6/head
Koitharu 4 years ago
parent 3589bee5f9
commit 6ab5743f66
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -5,6 +5,7 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import org.jsoup.HttpStatusException
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
import org.koitharu.kotatsu.parsers.exception.GraphQLException
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.await

@ -1,19 +1,18 @@
package org.koitharu.kotatsu.parsers
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
abstract class MangaParser {
abstract class MangaParser(val source: MangaSource) {
protected abstract val context: MangaLoaderContext
abstract val source: MangaSource
abstract val sortOrders: Set<SortOrder>
abstract val defaultDomain: String
val config by lazy { context.getConfig(source) }
protected val config by lazy { context.getConfig(source) }
protected abstract val configKeyDomain: ConfigKey.Domain
abstract suspend fun getList(
offset: Int,
@ -32,10 +31,14 @@ abstract class MangaParser {
open fun getFaviconUrl() = "https://${getDomain()}/favicon.ico"
open fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
keys.add(configKeyDomain)
}
/* Utils */
protected fun getDomain(): String {
return config.getDomain(defaultDomain)
fun getDomain(): String {
return config[configKeyDomain]
}
protected fun generateUid(url: String): Long {
@ -60,25 +63,19 @@ abstract class MangaParser {
protected fun String.withDomain(subdomain: String? = null) = when {
this.startsWith("//") -> buildString {
append("http")
if (config.isSslEnabled(true)) {
append('s')
}
append("https")
append(":")
append(this@withDomain)
}
this.startsWith("/") -> buildString {
append("http")
if (config.isSslEnabled(true)) {
append('s')
}
append("https")
append("://")
if (subdomain != null) {
append(subdomain)
append('.')
append(config.getDomain(defaultDomain).removePrefix("www."))
append(getDomain().removePrefix("www."))
} else {
append(config.getDomain(defaultDomain))
append(getDomain())
}
append(this@withDomain)
}

@ -1,6 +0,0 @@
package org.koitharu.kotatsu.parsers
interface MangaSourceConfig {
fun getDomain(defaultValue: String): String
fun isSslEnabled(defaultValue: Boolean): Boolean
}

@ -0,0 +1,13 @@
package org.koitharu.kotatsu.parsers.config
sealed class ConfigKey<T>(
val key: String,
) {
abstract val defaultValue: T
class Domain(
override val defaultValue: String,
val presetValues: Array<String>?,
) : ConfigKey<String>("domain")
}

@ -0,0 +1,6 @@
package org.koitharu.kotatsu.parsers.config
interface MangaSourceConfig {
operator fun <T> get(key: ConfigKey<T>): T
}

@ -0,0 +1,3 @@
package org.koitharu.kotatsu.parsers.model
internal const val RATING_UNKNOWN = -1f

@ -1,25 +1,98 @@
package org.koitharu.kotatsu.parsers.model
data class Manga(
class Manga(
val id: Long,
val title: String,
val altTitle: String? = null,
val altTitle: String?,
val url: String, // relative url for internal use
val publicUrl: String,
val rating: Float = NO_RATING, // normalized value [0..1] or -1
val isNsfw: Boolean = false,
val rating: Float, // normalized value [0..1] or -1
val isNsfw: Boolean,
val coverUrl: String,
val tags: Set<MangaTag>,
val state: MangaState?,
val author: String?,
val largeCoverUrl: String? = null,
val description: String? = null, // HTML
val tags: Set<MangaTag> = emptySet(),
val state: MangaState? = null,
val author: String? = null,
val chapters: List<MangaChapter>? = null,
val source: MangaSource,
) {
companion object {
val hasRating: Boolean
get() = rating in 0f..1f
const val NO_RATING = -1f
internal fun copy(
title: String = this.title,
altTitle: String? = this.altTitle,
publicUrl: String = this.publicUrl,
rating: Float = this.rating,
isNsfw: Boolean = this.isNsfw,
coverUrl: String = this.coverUrl,
tags: Set<MangaTag> = this.tags,
state: MangaState? = this.state,
author: String? = this.author,
largeCoverUrl: String? = this.largeCoverUrl,
description: String? = this.description,
chapters: List<MangaChapter>? = this.chapters,
) = Manga(
id = id,
title = title,
altTitle = altTitle,
url = url,
publicUrl = publicUrl,
rating = rating,
isNsfw = isNsfw,
coverUrl = coverUrl,
tags = tags,
state = state,
author = author,
largeCoverUrl = largeCoverUrl,
description = description,
chapters = chapters,
source = source,
)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Manga
if (id != other.id) return false
if (title != other.title) return false
if (altTitle != other.altTitle) return false
if (url != other.url) return false
if (publicUrl != other.publicUrl) return false
if (rating != other.rating) return false
if (isNsfw != other.isNsfw) return false
if (coverUrl != other.coverUrl) return false
if (tags != other.tags) return false
if (state != other.state) return false
if (author != other.author) return false
if (largeCoverUrl != other.largeCoverUrl) return false
if (description != other.description) return false
if (chapters != other.chapters) return false
if (source != other.source) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + title.hashCode()
result = 31 * result + (altTitle?.hashCode() ?: 0)
result = 31 * result + url.hashCode()
result = 31 * result + publicUrl.hashCode()
result = 31 * result + rating.hashCode()
result = 31 * result + isNsfw.hashCode()
result = 31 * result + coverUrl.hashCode()
result = 31 * result + tags.hashCode()
result = 31 * result + (state?.hashCode() ?: 0)
result = 31 * result + (author?.hashCode() ?: 0)
result = 31 * result + (largeCoverUrl?.hashCode() ?: 0)
result = 31 * result + (description?.hashCode() ?: 0)
result = 31 * result + (chapters?.hashCode() ?: 0)
result = 31 * result + source.hashCode()
return result
}
}

@ -1,6 +1,6 @@
package org.koitharu.kotatsu.parsers.model
data class MangaChapter(
class MangaChapter(
val id: Long,
val name: String,
val number: Int,
@ -14,4 +14,36 @@ data class MangaChapter(
override fun compareTo(other: MangaChapter): Int {
return number.compareTo(other.number)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MangaChapter
if (id != other.id) return false
if (name != other.name) return false
if (number != other.number) return false
if (url != other.url) return false
if (scanlator != other.scanlator) return false
if (uploadDate != other.uploadDate) return false
if (branch != other.branch) return false
if (source != other.source) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + name.hashCode()
result = 31 * result + number
result = 31 * result + url.hashCode()
result = 31 * result + (scanlator?.hashCode() ?: 0)
result = 31 * result + uploadDate.hashCode()
result = 31 * result + (branch?.hashCode() ?: 0)
result = 31 * result + source.hashCode()
return result
}
}

@ -1,9 +1,34 @@
package org.koitharu.kotatsu.parsers.model
data class MangaPage(
class MangaPage(
val id: Long,
val url: String,
val referer: String,
val preview: String?,
val source: MangaSource,
)
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MangaPage
if (id != other.id) return false
if (url != other.url) return false
if (referer != other.referer) return false
if (preview != other.preview) return false
if (source != other.source) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + url.hashCode()
result = 31 * result + referer.hashCode()
result = 31 * result + (preview?.hashCode() ?: 0)
result = 31 * result + source.hashCode()
return result
}
}

@ -1,7 +1,28 @@
package org.koitharu.kotatsu.parsers.model
data class MangaTag(
class MangaTag(
val title: String,
val key: String,
val source: MangaSource,
)
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MangaTag
if (title != other.title) return false
if (key != other.key) return false
if (source != other.source) return false
return true
}
override fun hashCode(): Int {
var result = title.hashCode()
result = 31 * result + key.hashCode()
result = 31 * result + source.hashCode()
return result
}
}

@ -5,17 +5,16 @@ import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed
import org.koitharu.kotatsu.parsers.util.json.stringIterator
import java.util.*
internal class AnibelParser(override val context: MangaLoaderContext) : MangaParser() {
internal class AnibelParser(override val context: MangaLoaderContext) : MangaParser(MangaSource.ANIBEL) {
override val source = MangaSource.ANIBEL
override val defaultDomain = "anibel.net"
override val configKeyDomain = ConfigKey.Domain("anibel.net", null)
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.NEWEST,
@ -73,6 +72,7 @@ internal class AnibelParser(override val context: MangaLoaderContext) : MangaPar
.withDomain("cdn") + "?width=200&height=280",
altTitle = title.getString("alt").takeUnless(String::isEmpty),
author = null,
isNsfw = false,
rating = jo.getDouble("rating").toFloat() / 10f,
url = href,
publicUrl = "https://${getDomain()}/$href",
@ -213,7 +213,8 @@ internal class AnibelParser(override val context: MangaLoaderContext) : MangaPar
.withDomain("cdn") + "?width=200&height=280",
altTitle = title.getString("en").takeUnless(String::isEmpty),
author = null,
rating = Manga.NO_RATING,
isNsfw = false,
rating = RATING_UNKNOWN,
url = href,
publicUrl = "https://${getDomain()}/$href",
tags = emptySet(),

@ -6,6 +6,7 @@ import org.json.JSONObject
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.nio.charset.StandardCharsets
@ -18,9 +19,7 @@ import javax.crypto.spec.SecretKeySpec
private const val PAGE_SIZE = 60
private const val PAGE_SIZE_SEARCH = 20
internal class BatoToParser(override val context: MangaLoaderContext) : MangaParser() {
override val source = MangaSource.BATOTO
internal class BatoToParser(override val context: MangaLoaderContext) : MangaParser(MangaSource.BATOTO) {
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.NEWEST,
@ -29,7 +28,10 @@ internal class BatoToParser(override val context: MangaLoaderContext) : MangaPar
SortOrder.ALPHABETICAL,
)
override val defaultDomain: String = "bato.to"
override val configKeyDomain = ConfigKey.Domain(
"bato.to",
arrayOf("bato.to", "mto.to", "mangatoto.com", "battwo.com", "batotwo.com", "comiko.net", "batotoo.com"),
)
override suspend fun getList(
offset: Int,
@ -193,7 +195,7 @@ internal class BatoToParser(override val context: MangaLoaderContext) : MangaPar
altTitle = div.selectFirst(".item-alias")?.text()?.takeUnless { it == title },
url = href,
publicUrl = a.absUrl("href"),
rating = Manga.NO_RATING,
rating = RATING_UNKNOWN,
isNsfw = false,
coverUrl = div.selectFirst("img[src]")?.absUrl("src").orEmpty(),
largeCoverUrl = null,

@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
import java.util.*
internal abstract class ChanParser : MangaParser() {
internal abstract class ChanParser(source: MangaSource) : MangaParser(source) {
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.NEWEST,
@ -64,6 +64,9 @@ internal abstract class ChanParser : MangaParser() {
)
}
}.getOrNull().orEmpty(),
rating = RATING_UNKNOWN,
state = null,
isNsfw = false,
source = source,
)
}

@ -6,6 +6,7 @@ import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.JSONIterator
@ -22,10 +23,10 @@ import java.util.*
private const val PAGE_SIZE = 20
private const val CHAPTERS_LIMIT = 99999
internal class ComickFunParser(override val context: MangaLoaderContext) : MangaParser() {
internal class ComickFunParser(override val context: MangaLoaderContext) : MangaParser(MangaSource.COMICK_FUN) {
override val configKeyDomain = ConfigKey.Domain("comick.fun", null)
override val defaultDomain = "comick.fun"
override val source = MangaSource.COMICK_FUN
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.POPULARITY,
SortOrder.UPDATED,

@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.site
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@ -10,11 +11,9 @@ import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed
import org.koitharu.kotatsu.parsers.util.json.mapJSONToSet
import java.util.*
internal class DesuMeParser(override val context: MangaLoaderContext) : MangaParser() {
internal class DesuMeParser(override val context: MangaLoaderContext) : MangaParser(MangaSource.DESUME) {
override val source = MangaSource.DESUME
override val defaultDomain = "desu.me"
override val configKeyDomain = ConfigKey.Domain("desu.me", null)
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
@ -71,6 +70,9 @@ internal class DesuMeParser(override val context: MangaLoaderContext) : MangaPar
},
rating = jo.getDouble("score").toFloat().coerceIn(0f, 1f),
id = generateUid(id),
isNsfw = false,
tags = emptySet(),
author = null,
description = jo.getString("description"),
)
}

@ -4,6 +4,7 @@ import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
@ -14,16 +15,16 @@ import kotlin.math.pow
private const val DOMAIN_UNAUTHORIZED = "e-hentai.org"
private const val DOMAIN_AUTHORIZED = "exhentai.org"
internal class ExHentaiParser(override val context: MangaLoaderContext) : MangaParser(), MangaParserAuthProvider {
override val source = MangaSource.EXHENTAI
internal class ExHentaiParser(
override val context: MangaLoaderContext,
) : MangaParser(MangaSource.EXHENTAI), MangaParserAuthProvider {
override val sortOrders: Set<SortOrder> = Collections.singleton(
SortOrder.NEWEST,
)
override val defaultDomain: String
get() = if (isAuthorized) DOMAIN_AUTHORIZED else DOMAIN_UNAUTHORIZED
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain(if (isAuthorized) DOMAIN_AUTHORIZED else DOMAIN_UNAUTHORIZED, null)
override val authUrl: String
get() = "https://${getDomain()}/bounce_login.php"
@ -118,7 +119,7 @@ internal class ExHentaiParser(override val context: MangaLoaderContext) : MangaP
altTitle = null,
url = href,
publicUrl = a.absUrl("href"),
rating = td2.selectFirst("div.ir")?.parseRating() ?: Manga.NO_RATING,
rating = td2.selectFirst("div.ir")?.parseRating() ?: RATING_UNKNOWN,
isNsfw = true,
coverUrl = td1.selectFirst("img")?.absUrl("src").orEmpty(),
tags = setOfNotNull(mainTag),
@ -239,7 +240,7 @@ internal class ExHentaiParser(override val context: MangaLoaderContext) : MangaP
p1 += 8
}
(80 - p1) / 80f
}.getOrDefault(Manga.NO_RATING)
}.getOrDefault(RATING_UNKNOWN)
}
private fun String.cleanupTitle(): String {

@ -14,7 +14,7 @@ private const val PAGE_SIZE = 70
private const val PAGE_SIZE_SEARCH = 50
private const val NSFW_ALERT = "сексуальные сцены"
internal abstract class GroupleParser : MangaParser() {
internal abstract class GroupleParser(source: MangaSource) : MangaParser(source) {
private val headers = Headers.Builder()
.add("User-Agent", "readmangafun")
@ -91,8 +91,9 @@ internal abstract class GroupleParser : MangaParser() {
?.substringBefore(' ')
?.toFloatOrNull()
?.div(10f)
}.getOrNull() ?: Manga.NO_RATING,
}.getOrNull() ?: RATING_UNKNOWN,
author = tileInfo?.selectFirst("a.person-link")?.text(),
isNsfw = false,
tags = runCatching {
tileInfo?.select("a.element-link")
?.mapToSet {

@ -1,16 +1,16 @@
package org.koitharu.kotatsu.parsers.site
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.parseHtml
import org.koitharu.kotatsu.parsers.util.toTitleCase
internal class HenChanParser(override val context: MangaLoaderContext) : ChanParser() {
internal class HenChanParser(override val context: MangaLoaderContext) : ChanParser(MangaSource.HENCHAN) {
override val defaultDomain = "hentaichan.live"
override val source = MangaSource.HENCHAN
override val configKeyDomain = ConfigKey.Domain("hentaichan.live", null)
override suspend fun getList(
offset: Int,

@ -2,13 +2,11 @@ package org.koitharu.kotatsu.parsers.site
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.MangaSource
internal class HentaiLibParser(context: MangaLoaderContext) : MangaLibParser(context) {
override val defaultDomain = "hentailib.me"
override val source = MangaSource.HENTAILIB
internal class HentaiLibParser(context: MangaLoaderContext) : MangaLibParser(context, MangaSource.HENTAILIB) {
override val configKeyDomain = ConfigKey.Domain("hentailib.me", null)
override fun isNsfw(doc: Document) = true
}

@ -1,10 +1,10 @@
package org.koitharu.kotatsu.parsers.site
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.MangaSource
internal class MangaChanParser(override val context: MangaLoaderContext) : ChanParser() {
internal class MangaChanParser(override val context: MangaLoaderContext) : ChanParser(MangaSource.MANGACHAN) {
override val defaultDomain = "manga-chan.me"
override val source = MangaSource.MANGACHAN
override val configKeyDomain = ConfigKey.Domain("manga-chan.me", null)
}

@ -5,6 +5,7 @@ import kotlinx.coroutines.coroutineScope
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.*
@ -16,10 +17,9 @@ private const val CONTENT_RATING =
"contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic"
private const val LOCALE_FALLBACK = "en"
internal class MangaDexParser(override val context: MangaLoaderContext) : MangaParser() {
internal class MangaDexParser(override val context: MangaLoaderContext) : MangaParser(MangaSource.MANGADEX) {
override val source = MangaSource.MANGADEX
override val defaultDomain = "mangadex.org"
override val configKeyDomain = ConfigKey.Domain("mangadex.org", null)
override val sortOrders: EnumSet<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
@ -86,7 +86,7 @@ internal class MangaDexParser(override val context: MangaLoaderContext) : MangaP
altTitle = attrs.optJSONObject("altTitles")?.selectByLocale(),
url = id,
publicUrl = "https://$domain/title/$id",
rating = Manga.NO_RATING,
rating = RATING_UNKNOWN,
isNsfw = attrs.getStringOrNull("contentRating") == "erotica",
coverUrl = cover?.plus(".256.jpg").orEmpty(),
largeCoverUrl = cover,
@ -114,7 +114,7 @@ internal class MangaDexParser(override val context: MangaLoaderContext) : MangaP
}
}
override suspend fun getDetails(manga: Manga): Manga = coroutineScope<Manga> {
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
val domain = getDomain()
val attrsDeferred = async {
context.httpGet(
@ -169,11 +169,11 @@ internal class MangaDexParser(override val context: MangaLoaderContext) : MangaP
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val domain = getDomain()
val chapter = context.httpGet("https://api.$domain/at-home/server/${chapter.url}?forcePort443=false")
val chapterJson = context.httpGet("https://api.$domain/at-home/server/${chapter.url}?forcePort443=false")
.parseJson()
.getJSONObject("chapter")
val pages = chapter.getJSONArray("data")
val prefix = "https://uploads.$domain/data/${chapter.getString("hash")}/"
val pages = chapterJson.getJSONArray("data")
val prefix = "https://uploads.$domain/data/${chapterJson.getString("hash")}/"
val referer = "https://$domain/"
return List(pages.length()) { i ->
val url = prefix + pages.getString(i)

@ -7,6 +7,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
@ -17,11 +18,12 @@ import org.koitharu.kotatsu.parsers.util.json.mapJSON
import java.text.SimpleDateFormat
import java.util.*
internal open class MangaLibParser(override val context: MangaLoaderContext) : MangaParser(), MangaParserAuthProvider {
internal open class MangaLibParser(
override val context: MangaLoaderContext,
source: MangaSource = MangaSource.MANGALIB,
) : MangaParser(source), MangaParserAuthProvider {
override val defaultDomain = "mangalib.me"
override val source = MangaSource.MANGALIB
override val configKeyDomain = ConfigKey.Domain("mangalib.me", null)
override val authUrl: String
get() = "https://${getDomain()}/login"
@ -69,11 +71,12 @@ internal open class MangaLibParser(override val context: MangaLoaderContext) : M
coverUrl = a.absUrl("data-src"),
altTitle = null,
author = null,
rating = Manga.NO_RATING,
rating = RATING_UNKNOWN,
url = href,
publicUrl = href.inContextOf(a),
tags = emptySet(),
state = null,
isNsfw = false,
source = source,
)
}
@ -269,8 +272,9 @@ internal open class MangaLibParser(override val context: MangaLoaderContext) : M
author = null,
tags = emptySet(),
rating = jo.getString("rate_avg")
.toFloatOrNull()?.div(5f) ?: Manga.NO_RATING,
.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
state = null,
isNsfw = false,
source = source,
coverUrl = covers.getString("thumbnail"),
largeCoverUrl = covers.getString("default"),

@ -2,17 +2,16 @@ package org.koitharu.kotatsu.parsers.site
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
import java.util.*
internal class MangaOwlParser(override val context: MangaLoaderContext) : MangaParser() {
internal class MangaOwlParser(override val context: MangaLoaderContext) : MangaParser(MangaSource.MANGAOWL) {
override val source = MangaSource.MANGAOWL
override val defaultDomain = "mangaowls.com"
override val configKeyDomain = ConfigKey.Domain("mangaowls.com", null)
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.POPULARITY,
@ -62,8 +61,11 @@ internal class MangaOwlParser(override val context: MangaLoaderContext) : MangaP
?.text()
?.toFloatOrNull()
?.div(10f)
}.getOrNull() ?: Manga.NO_RATING,
}.getOrNull() ?: RATING_UNKNOWN,
url = href,
isNsfw = false,
tags = emptySet(),
state = null,
publicUrl = href.withDomain(),
source = source,
)
@ -79,7 +81,7 @@ internal class MangaOwlParser(override val context: MangaLoaderContext) : MangaP
val trElement =
doc.getElementsByTag("script").find { trRegex.find(it.data()) != null } ?: parseFailed("Oops, tr not found")
val tr = trRegex.find(trElement.data())!!.groups[1]!!.value
val s = context.encodeBase64(defaultDomain.toByteArray())
val s = context.encodeBase64(getDomain().toByteArray())
return manga.copy(
description = info.selectFirst(".description")?.html(),
largeCoverUrl = info.select("img").first()?.let { img ->

@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.site
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@ -9,11 +10,9 @@ import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
internal class MangaTownParser(override val context: MangaLoaderContext) : MangaParser() {
internal class MangaTownParser(override val context: MangaLoaderContext) : MangaParser(MangaSource.MANGATOWN) {
override val source = MangaSource.MANGATOWN
override val defaultDomain = "www.mangatown.com"
override val configKeyDomain = ConfigKey.Domain("www.mangatown.com", null)
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.ALPHABETICAL,
@ -69,7 +68,7 @@ internal class MangaTownParser(override val context: MangaLoaderContext) : Manga
source = MangaSource.MANGATOWN,
altTitle = null,
rating = li.selectFirst("p.score")?.selectFirst("b")
?.ownText()?.toFloatOrNull()?.div(5f) ?: Manga.NO_RATING,
?.ownText()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
author = views.findText { x -> x.startsWith("Author:") }?.substringAfter(':')
?.trim(),
state = when (status) {
@ -85,6 +84,7 @@ internal class MangaTownParser(override val context: MangaLoaderContext) : Manga
)
}.orEmpty(),
url = href,
isNsfw = false,
publicUrl = href.inContextOf(a),
)
}

@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.site
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@ -11,11 +12,9 @@ import java.util.*
private const val PAGE_SIZE = 12
internal class MangareadParser(override val context: MangaLoaderContext) : MangaParser() {
internal class MangareadParser(override val context: MangaLoaderContext) : MangaParser(MangaSource.MANGAREAD) {
override val source = MangaSource.MANGAREAD
override val defaultDomain = "www.mangaread.org"
override val configKeyDomain = ConfigKey.Domain("www.mangaread.org", null)
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
@ -56,6 +55,7 @@ internal class MangareadParser(override val context: MangaLoaderContext) : Manga
publicUrl = href.inContextOf(div),
coverUrl = div.selectFirst("img")?.absUrl("data-src").orEmpty(),
title = summary?.selectFirst("h3")?.text().orEmpty(),
altTitle = null,
rating = div.selectFirst("span.total_votes")?.ownText()
?.toFloatOrNull()?.div(5f) ?: -1f,
tags = summary?.selectFirst(".mg_genres")?.select("a")?.mapToSet { a ->
@ -75,6 +75,7 @@ internal class MangareadParser(override val context: MangaLoaderContext) : Manga
else -> null
},
source = MangaSource.MANGAREAD,
isNsfw = false,
)
}
}

@ -1,10 +1,10 @@
package org.koitharu.kotatsu.parsers.site
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.MangaSource
internal class MintMangaParser(override val context: MangaLoaderContext) : GroupleParser() {
internal class MintMangaParser(override val context: MangaLoaderContext) : GroupleParser(MangaSource.MINTMANGA) {
override val source = MangaSource.MINTMANGA
override val defaultDomain: String = "mintmanga.live"
override val configKeyDomain = ConfigKey.Domain("mintmanga.live", null)
}

@ -4,6 +4,7 @@ import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@ -14,9 +15,11 @@ private const val PAGE_SIZE = 26
internal abstract class NineMangaParser(
final override val context: MangaLoaderContext,
final override val source: MangaSource,
final override val defaultDomain: String,
) : MangaParser() {
source: MangaSource,
defaultDomain: String,
) : MangaParser(source) {
override val configKeyDomain = ConfigKey.Domain(defaultDomain, null)
init {
context.cookieJar.insertCookies(getDomain(), "ninemanga_template_desk=yes")
@ -77,8 +80,9 @@ internal abstract class NineMangaParser(
title = dd?.selectFirst("a.bookname")?.text()?.toCamelCase().orEmpty(),
altTitle = null,
coverUrl = node.selectFirst("img")?.absUrl("src").orEmpty(),
rating = Manga.NO_RATING,
rating = RATING_UNKNOWN,
author = null,
isNsfw = false,
tags = emptySet(),
state = null,
source = source,

@ -4,6 +4,7 @@ import androidx.collection.SparseArrayCompat
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
@ -12,10 +13,11 @@ import java.text.SimpleDateFormat
import java.util.*
import java.util.regex.Pattern
internal class NudeMoonParser(override val context: MangaLoaderContext) : MangaParser(), MangaParserAuthProvider {
internal class NudeMoonParser(
override val context: MangaLoaderContext,
) : MangaParser(MangaSource.NUDEMOON), MangaParserAuthProvider {
override val source = MangaSource.NUDEMOON
override val defaultDomain = "nude-moon.net"
override val configKeyDomain = ConfigKey.Domain("nude-moon.net", null)
override val authUrl: String
get() = "https://${getDomain()}/index.php"
@ -89,7 +91,7 @@ internal class NudeMoonParser(override val context: MangaLoaderContext) : MangaP
}.orEmpty(),
source = source,
publicUrl = a.absUrl("href"),
rating = Manga.NO_RATING,
rating = RATING_UNKNOWN,
isNsfw = true,
description = row.selectFirst("div.description")?.html(),
state = null,

@ -1,10 +1,10 @@
package org.koitharu.kotatsu.parsers.site
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.MangaSource
internal class ReadmangaParser(override val context: MangaLoaderContext) : GroupleParser() {
internal class ReadmangaParser(override val context: MangaLoaderContext) : GroupleParser(MangaSource.READMANGA_RU) {
override val defaultDomain = "readmanga.io"
override val source = MangaSource.READMANGA_RU
override val configKeyDomain = ConfigKey.Domain("readmanga.io", null)
}

@ -7,6 +7,7 @@ import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@ -23,11 +24,11 @@ private const val PAGE_SIZE = 30
private const val STATUS_ONGOING = 1
private const val STATUS_FINISHED = 0
internal class RemangaParser(override val context: MangaLoaderContext) : MangaParser(), MangaParserAuthProvider {
internal class RemangaParser(
override val context: MangaLoaderContext,
) : MangaParser(MangaSource.REMANGA), MangaParserAuthProvider {
override val source = MangaSource.REMANGA
override val defaultDomain = "remanga.org"
override val configKeyDomain = ConfigKey.Domain("remanga.org", null)
override val authUrl: String
get() = "https://${getDomain()}/user/login"
@ -85,10 +86,12 @@ internal class RemangaParser(override val context: MangaLoaderContext) : MangaPa
publicUrl = "https://$domain$url",
title = jo.getString("rus_name"),
altTitle = jo.getString("en_name"),
rating = jo.getString("avg_rating").toFloatOrNull()?.div(10f) ?: Manga.NO_RATING,
rating = jo.getString("avg_rating").toFloatOrNull()?.div(10f) ?: RATING_UNKNOWN,
coverUrl = "https://api.$domain${img.getString("mid")}",
largeCoverUrl = "https://api.$domain${img.getString("high")}",
author = null,
isNsfw = false,
state = null,
tags = jo.optJSONArray("genres")?.mapJSONToSet { g ->
MangaTag(
title = g.getString("name").toTitleCase(),

@ -1,10 +1,10 @@
package org.koitharu.kotatsu.parsers.site
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.MangaSource
internal class SelfMangaParser(override val context: MangaLoaderContext) : GroupleParser() {
internal class SelfMangaParser(override val context: MangaLoaderContext) : GroupleParser(MangaSource.SELFMANGA) {
override val defaultDomain = "selfmanga.live"
override val source = MangaSource.SELFMANGA
override val configKeyDomain = ConfigKey.Domain("selfmanga.live", null)
}

@ -1,6 +1,7 @@
package org.koitharu.kotatsu.parsers.site
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
@ -8,10 +9,9 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.parseHtml
import org.koitharu.kotatsu.parsers.util.relUrl
internal class YaoiChanParser(override val context: MangaLoaderContext) : ChanParser() {
internal class YaoiChanParser(override val context: MangaLoaderContext) : ChanParser(MangaSource.YAOICHAN) {
override val source = MangaSource.YAOICHAN
override val defaultDomain = "yaoi-chan.me"
override val configKeyDomain = ConfigKey.Domain("yaoi-chan.me", null)
override suspend fun getDetails(manga: Manga): Manga {
val doc = context.httpGet(manga.url.withDomain()).parseHtml()

@ -32,7 +32,7 @@ class InMemoryCookieJar : CookieJar {
if (line.isBlank() || line.startsWith("# ")) {
continue
}
val (host, includeSubdomains, path, secure, expire, name, value) = line.split(Regex("\\s+"))
val (host, _, path, secure, expire, name, value) = line.split(Regex("\\s+"))
val domain = host.removePrefix("#HttpOnly_").trimStart('.')
val httpOnly = host.startsWith("#HttpOnly_")
val cookie = Cookie.Builder()

@ -4,6 +4,7 @@ import com.koushikdutta.quack.QuackContext
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.await
import java.util.concurrent.TimeUnit

@ -1,8 +1,9 @@
package org.koitharu.kotatsu.parsers
internal class SourceConfigMock : MangaSourceConfig {
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
override fun getDomain(defaultValue: String): String = defaultValue
internal class SourceConfigMock : MangaSourceConfig {
override fun isSslEnabled(defaultValue: Boolean): Boolean = defaultValue
override fun <T> get(key: ConfigKey<T>): T = key.defaultValue
}
Loading…
Cancel
Save