Merge pull request #31 from KotatsuApp/master
[pull] master from KotatsuApp:master
This commit is contained in:
@@ -53,7 +53,7 @@ This library provides manga sources.
|
||||
3. Usage in code
|
||||
|
||||
```kotlin
|
||||
val parser = mangaLoaderContext.newParserInstance(MangaSource.MANGADEX)
|
||||
val parser = mangaLoaderContext.newParserInstance(MangaParserSourceMANGADEX)
|
||||
```
|
||||
|
||||
`mangaLoaderContext` is an implementation of the `MangaLoaderContext` class.
|
||||
@@ -62,7 +62,7 @@ This library provides manga sources.
|
||||
and [Non-Android](https://github.com/KotatsuApp/kotatsu-dl/blob/master/src/jvmMain/kotlin/org/koitharu/kotatsu_dl/logic/MangaLoaderContextImpl.kt)
|
||||
implementation.
|
||||
|
||||
Note that the `MangaSource.LOCAL` and `MangaSource.DUMMY` parsers cannot be instantiated.
|
||||
Note that the `MangaParserSourceLOCAL` and `MangaParserSourceDUMMY` parsers cannot be instantiated.
|
||||
|
||||
## Contribution
|
||||
|
||||
|
||||
10
build.gradle
10
build.gradle
@@ -2,8 +2,8 @@ import tasks.ReportGenerateTask
|
||||
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.9.23'
|
||||
id 'com.google.devtools.ksp' version '1.9.23-1.0.20'
|
||||
id 'org.jetbrains.kotlin.jvm' version '2.0.10-RC'
|
||||
id 'com.google.devtools.ksp' version '2.0.10-RC-1.0.23'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
@@ -53,12 +53,12 @@ afterEvaluate {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
||||
implementation 'com.squareup.okio:okio:3.9.0'
|
||||
api 'org.jsoup:jsoup:1.17.2'
|
||||
api 'org.jsoup:jsoup:1.18.1'
|
||||
implementation 'org.json:json:20240303'
|
||||
implementation 'androidx.collection:collection:1.4.0'
|
||||
implementation 'androidx.collection:collection:1.4.1'
|
||||
|
||||
ksp project(':kotatsu-parsers-ksp')
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.9.23'
|
||||
id 'org.jetbrains.kotlin.jvm' version '2.0.10-RC'
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -14,5 +14,5 @@ dependencies {
|
||||
implementation gradleApi()
|
||||
implementation 'org.simpleframework:simple-xml:2.7.1'
|
||||
implementation 'com.soywiz.korlibs.korte:korte-jvm:4.0.10'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC'
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@ kotlin {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.devtools.ksp:symbol-processing-api:1.9.23-1.0.20'
|
||||
implementation 'com.google.devtools.ksp:symbol-processing-api:2.0.10-RC-1.0.23'
|
||||
}
|
||||
|
||||
@@ -69,12 +69,9 @@ class ParserProcessor(
|
||||
"""
|
||||
package org.koitharu.kotatsu.parsers
|
||||
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@InternalParsersApi
|
||||
@Deprecated("", replaceWith = ReplaceWith("context.newParserInstance(this)"))
|
||||
fun MangaSource.newParser(context: MangaLoaderContext): MangaParser = when (this) {
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
|
||||
internal fun MangaParserSource.newParser(context: MangaLoaderContext): MangaParser = when (this) {
|
||||
|
||||
""".trimIndent(),
|
||||
)
|
||||
@@ -83,14 +80,12 @@ class ParserProcessor(
|
||||
"""
|
||||
package org.koitharu.kotatsu.parsers.model
|
||||
|
||||
enum class MangaSource(
|
||||
enum class MangaParserSource(
|
||||
val title: String,
|
||||
val locale: String,
|
||||
val contentType: ContentType,
|
||||
val isBroken: Boolean,
|
||||
) {
|
||||
LOCAL("Local", "", ContentType.OTHER, false),
|
||||
UNKNOWN("Unknown", "", ContentType.OTHER, true),
|
||||
): MangaSource {
|
||||
|
||||
""".trimIndent(),
|
||||
)
|
||||
@@ -102,9 +97,7 @@ class ParserProcessor(
|
||||
|
||||
factoryWriter?.write(
|
||||
"""
|
||||
MangaSource.LOCAL,
|
||||
MangaSource.UNKNOWN,
|
||||
MangaSource.DUMMY -> throw NotImplementedError("Manga parser ${'$'}name cannot be instantiated")
|
||||
MangaParserSource.DUMMY -> throw NotImplementedError("Manga parser ${'$'}name cannot be instantiated")
|
||||
}.also {
|
||||
require(it.source == this) {
|
||||
"Cannot instantiate manga parser: ${'$'}name mapped to ${'$'}{it.source}"
|
||||
@@ -166,7 +159,7 @@ class ParserProcessor(
|
||||
logger.warn("Source title duplication: \"$title\" is assigned to both $prevTitleClass and $className")
|
||||
}
|
||||
|
||||
factoryWriter?.write("\tMangaSource.$name -> $className(context)\n")
|
||||
factoryWriter?.write("\tMangaParserSource.$name -> $className(context)\n")
|
||||
val deprecationString =
|
||||
if (deprecation != null) {
|
||||
val reason =
|
||||
|
||||
@@ -5,6 +5,7 @@ import okhttp3.OkHttpClient
|
||||
import okhttp3.Response
|
||||
import org.koitharu.kotatsu.parsers.bitmap.Bitmap
|
||||
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import java.util.*
|
||||
|
||||
@@ -14,8 +15,7 @@ abstract class MangaLoaderContext {
|
||||
|
||||
abstract val cookieJar: CookieJar
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
fun newParserInstance(source: MangaSource): MangaParser = source.newParser(this)
|
||||
fun newParserInstance(source: MangaParserSource): MangaParser = source.newParser(this)
|
||||
|
||||
open fun encodeBase64(data: ByteArray): String = Base64.getEncoder().encodeToString(data)
|
||||
|
||||
@@ -42,7 +42,7 @@ abstract class MangaLoaderContext {
|
||||
*/
|
||||
abstract fun redrawImageResponse(
|
||||
response: Response,
|
||||
redraw: (image: Bitmap) -> Bitmap
|
||||
redraw: (image: Bitmap) -> Bitmap,
|
||||
): Response
|
||||
|
||||
/**
|
||||
@@ -50,6 +50,6 @@ abstract class MangaLoaderContext {
|
||||
*/
|
||||
abstract fun createBitmap(
|
||||
width: Int,
|
||||
height: Int
|
||||
height: Int,
|
||||
): Bitmap
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import java.util.*
|
||||
|
||||
abstract class MangaParser @InternalParsersApi constructor(
|
||||
@property:InternalParsersApi val context: MangaLoaderContext,
|
||||
val source: MangaSource,
|
||||
val source: MangaParserSource,
|
||||
) {
|
||||
|
||||
/**
|
||||
@@ -62,7 +62,7 @@ abstract class MangaParser @InternalParsersApi constructor(
|
||||
val config by lazy { context.getConfig(source) }
|
||||
|
||||
open val sourceLocale: Locale
|
||||
get() = source.locale?.let { Locale(it) } ?: Locale.ROOT
|
||||
get() = if (source.locale.isEmpty()) Locale.ROOT else Locale(source.locale)
|
||||
|
||||
val isNsfwSource = source.contentType == ContentType.HENTAI
|
||||
|
||||
@@ -244,7 +244,7 @@ abstract class MangaParser @InternalParsersApi constructor(
|
||||
return RelatedMangaFinder(listOf(this)).invoke(seed)
|
||||
}
|
||||
|
||||
protected fun getParser(source: MangaSource) = if (this.source == source) {
|
||||
protected fun getParser(source: MangaParserSource) = if (this.source == source) {
|
||||
this
|
||||
} else {
|
||||
context.newParserInstance(source)
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.util.Paginator
|
||||
@InternalParsersApi
|
||||
abstract class PagedMangaParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaSource,
|
||||
source: MangaParserSource,
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) @JvmField internal val pageSize: Int,
|
||||
searchPageSize: Int = pageSize,
|
||||
) : MangaParser(context, source) {
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.model
|
||||
|
||||
class Favicons internal constructor(
|
||||
favicons: Collection<Favicon>,
|
||||
@JvmField val referer: String,
|
||||
@JvmField val referer: String?,
|
||||
) : Collection<Favicon> {
|
||||
|
||||
private val icons = favicons.sortedDescending()
|
||||
@@ -47,4 +47,12 @@ class Favicons internal constructor(
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun empty() = Favicons(emptySet(), null)
|
||||
|
||||
fun single(url: String) = Favicons(setOf(Favicon(url, 0, null)), null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.koitharu.kotatsu.parsers.model
|
||||
|
||||
interface MangaSource {
|
||||
|
||||
val name: String
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import javax.crypto.spec.SecretKeySpec
|
||||
@MangaSourceParser("BATOTO", "Bato.To")
|
||||
internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
context = context,
|
||||
source = MangaSource.BATOTO,
|
||||
source = MangaParserSource.BATOTO,
|
||||
pageSize = 60,
|
||||
searchPageSize = 20,
|
||||
) {
|
||||
@@ -104,7 +104,12 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
|
||||
filter.locale?.let {
|
||||
append("&langs=")
|
||||
append(it.language)
|
||||
if (it.language == "in") {
|
||||
append("id")
|
||||
} else {
|
||||
append(it.language)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
append("&genres=")
|
||||
|
||||
@@ -21,7 +21,8 @@ import java.util.*
|
||||
private const val CHAPTERS_LIMIT = 99999
|
||||
|
||||
@MangaSourceParser("COMICK_FUN", "ComicK")
|
||||
internal class ComickFunParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.COMICK_FUN, 20) {
|
||||
internal class ComickFunParser(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.COMICK_FUN, 20) {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("comick.io", "comick.cc")
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ private const val DOMAIN_AUTHORIZED = "exhentai.org"
|
||||
@MangaSourceParser("EXHENTAI", "ExHentai", type = ContentType.HENTAI)
|
||||
internal class ExHentaiParser(
|
||||
context: MangaLoaderContext,
|
||||
) : PagedMangaParser(context, MangaSource.EXHENTAI, pageSize = 25), MangaParserAuthProvider {
|
||||
) : PagedMangaParser(context, MangaParserSource.EXHENTAI, pageSize = 25), MangaParserAuthProvider {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = setOf(SortOrder.NEWEST)
|
||||
override val isTagsExclusionSupported: Boolean = true
|
||||
|
||||
@@ -27,7 +27,7 @@ import kotlin.math.min
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
@MangaSourceParser("HITOMILA", "Hitomi.La", type = ContentType.HENTAI)
|
||||
class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.HITOMILA) {
|
||||
class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.HITOMILA) {
|
||||
override val configKeyDomain = ConfigKey.Domain("hitomi.la")
|
||||
|
||||
private val ltnBaseUrl get() = "https://${getDomain("ltn")}"
|
||||
|
||||
@@ -15,7 +15,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("IMHENTAI", "ImHentai", type = ContentType.HENTAI)
|
||||
internal class ImHentai(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.IMHENTAI, pageSize = 20) {
|
||||
PagedMangaParser(context, MangaParserSource.IMHENTAI, pageSize = 20) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.RATING)
|
||||
|
||||
@@ -21,7 +21,7 @@ import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
internal abstract class LineWebtoonsParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaSource,
|
||||
source: MangaParserSource,
|
||||
) : MangaParser(context, source) {
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
@@ -308,25 +308,25 @@ internal abstract class LineWebtoonsParser(
|
||||
}
|
||||
|
||||
@MangaSourceParser("LINEWEBTOONS_EN", "LineWebtoons English", "en", type = ContentType.MANGA)
|
||||
class English(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaSource.LINEWEBTOONS_EN)
|
||||
class English(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaParserSource.LINEWEBTOONS_EN)
|
||||
|
||||
@MangaSourceParser("LINEWEBTOONS_ZH", "LineWebtoons Chinese", "zh", type = ContentType.MANGA)
|
||||
class Chinese(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaSource.LINEWEBTOONS_ZH)
|
||||
class Chinese(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaParserSource.LINEWEBTOONS_ZH)
|
||||
|
||||
@MangaSourceParser("LINEWEBTOONS_TH", "LineWebtoons Thai", "th", type = ContentType.MANGA)
|
||||
class Thai(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaSource.LINEWEBTOONS_TH)
|
||||
class Thai(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaParserSource.LINEWEBTOONS_TH)
|
||||
|
||||
@MangaSourceParser("LINEWEBTOONS_ID", "LineWebtoons Indonesian", "id", type = ContentType.MANGA)
|
||||
class Indonesian(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaSource.LINEWEBTOONS_ID)
|
||||
class Indonesian(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaParserSource.LINEWEBTOONS_ID)
|
||||
|
||||
@MangaSourceParser("LINEWEBTOONS_ES", "LineWebtoons Spanish", "es", type = ContentType.MANGA)
|
||||
class Spanish(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaSource.LINEWEBTOONS_ES)
|
||||
class Spanish(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaParserSource.LINEWEBTOONS_ES)
|
||||
|
||||
@MangaSourceParser("LINEWEBTOONS_FR", "LineWebtoons French", "fr", type = ContentType.MANGA)
|
||||
class French(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaSource.LINEWEBTOONS_FR)
|
||||
class French(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaParserSource.LINEWEBTOONS_FR)
|
||||
|
||||
@MangaSourceParser("LINEWEBTOONS_DE", "LineWebtoons German", "de", type = ContentType.MANGA)
|
||||
class German(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaSource.LINEWEBTOONS_DE)
|
||||
class German(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaParserSource.LINEWEBTOONS_DE)
|
||||
|
||||
private inner class WebtoonsUrlSigner(private val secret: String) {
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ private const val CHAPTERS_MAX_COUNT = 10_000 // strange api behavior, looks lik
|
||||
private const val LOCALE_FALLBACK = "en"
|
||||
|
||||
@MangaSourceParser("MANGADEX", "MangaDex")
|
||||
internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.MANGADEX) {
|
||||
internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.MANGADEX) {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("mangadex.org")
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.koitharu.kotatsu.parsers.site.all
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
@@ -22,7 +24,7 @@ private const val MIN_SPLIT_COUNT = 5
|
||||
|
||||
internal abstract class MangaFireParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaSource,
|
||||
source: MangaParserSource,
|
||||
private val siteLang: String,
|
||||
) : PagedMangaParser(context, source, 30), Interceptor {
|
||||
|
||||
@@ -425,23 +427,25 @@ internal abstract class MangaFireParser(
|
||||
private fun Int.ceilDiv(other: Int) = (this + (other - 1)) / other
|
||||
|
||||
@MangaSourceParser("MANGAFIRE_EN", "MangaFire English", "en")
|
||||
class English(context: MangaLoaderContext) : MangaFireParser(context, MangaSource.MANGAFIRE_EN, "en")
|
||||
class English(context: MangaLoaderContext) : MangaFireParser(context, MangaParserSource.MANGAFIRE_EN, "en")
|
||||
|
||||
@MangaSourceParser("MANGAFIRE_ES", "MangaFire Spanish", "es")
|
||||
class Spanish(context: MangaLoaderContext) : MangaFireParser(context, MangaSource.MANGAFIRE_ES, "es")
|
||||
class Spanish(context: MangaLoaderContext) : MangaFireParser(context, MangaParserSource.MANGAFIRE_ES, "es")
|
||||
|
||||
@MangaSourceParser("MANGAFIRE_ESLA", "MangaFire Spanish (Latim)", "es")
|
||||
class SpanishLatim(context: MangaLoaderContext) : MangaFireParser(context, MangaSource.MANGAFIRE_ESLA, "es-la")
|
||||
class SpanishLatim(context: MangaLoaderContext) :
|
||||
MangaFireParser(context, MangaParserSource.MANGAFIRE_ESLA, "es-la")
|
||||
|
||||
@MangaSourceParser("MANGAFIRE_FR", "MangaFire French", "fr")
|
||||
class French(context: MangaLoaderContext) : MangaFireParser(context, MangaSource.MANGAFIRE_FR, "fr")
|
||||
class French(context: MangaLoaderContext) : MangaFireParser(context, MangaParserSource.MANGAFIRE_FR, "fr")
|
||||
|
||||
@MangaSourceParser("MANGAFIRE_JA", "MangaFire Japanese", "ja")
|
||||
class Japanese(context: MangaLoaderContext) : MangaFireParser(context, MangaSource.MANGAFIRE_JA, "ja")
|
||||
class Japanese(context: MangaLoaderContext) : MangaFireParser(context, MangaParserSource.MANGAFIRE_JA, "ja")
|
||||
|
||||
@MangaSourceParser("MANGAFIRE_PT", "MangaFire Portuguese", "pt")
|
||||
class Portuguese(context: MangaLoaderContext) : MangaFireParser(context, MangaSource.MANGAFIRE_PT, "pt")
|
||||
class Portuguese(context: MangaLoaderContext) : MangaFireParser(context, MangaParserSource.MANGAFIRE_PT, "pt")
|
||||
|
||||
@MangaSourceParser("MANGAFIRE_PTBR", "MangaFire Portuguese (Brazil)", "pt")
|
||||
class PortugueseBR(context: MangaLoaderContext) : MangaFireParser(context, MangaSource.MANGAFIRE_PTBR, "pt-br")
|
||||
class PortugueseBR(context: MangaLoaderContext) :
|
||||
MangaFireParser(context, MangaParserSource.MANGAFIRE_PTBR, "pt-br")
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("MANGAPARK", "MangaPark")
|
||||
internal class MangaPark(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.MANGAPARK, pageSize = 36) {
|
||||
PagedMangaParser(context, MangaParserSource.MANGAPARK, pageSize = 36) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.allOf(SortOrder::class.java)
|
||||
|
||||
@@ -257,7 +257,7 @@ internal class MangaPark(context: MangaLoaderContext) :
|
||||
.findAll(script)
|
||||
.mapNotNullTo(ArrayList()) {
|
||||
val url = it.groupValues.getOrNull(1) ?: return@mapNotNullTo null
|
||||
if (url.contains("/comic/") || url.contains("/manga/")) {
|
||||
if (url.contains("/comic/") || url.contains("/manga/") || url.contains("/image/mpup/")) {
|
||||
MangaPage(
|
||||
id = generateUid(url),
|
||||
url = url,
|
||||
|
||||
@@ -22,7 +22,7 @@ import java.util.*
|
||||
|
||||
internal abstract class MangaPlusParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaSource,
|
||||
source: MangaParserSource,
|
||||
private val sourceLang: String,
|
||||
) : MangaParser(context, source), Interceptor {
|
||||
|
||||
@@ -267,63 +267,63 @@ internal abstract class MangaPlusParser(
|
||||
@MangaSourceParser("MANGAPLUSPARSER_EN", "MANGA Plus English", "en")
|
||||
class English(context: MangaLoaderContext) : MangaPlusParser(
|
||||
context,
|
||||
MangaSource.MANGAPLUSPARSER_EN,
|
||||
MangaParserSource.MANGAPLUSPARSER_EN,
|
||||
"ENGLISH",
|
||||
)
|
||||
|
||||
@MangaSourceParser("MANGAPLUSPARSER_ES", "MANGA Plus Spanish", "es")
|
||||
class Spanish(context: MangaLoaderContext) : MangaPlusParser(
|
||||
context,
|
||||
MangaSource.MANGAPLUSPARSER_ES,
|
||||
MangaParserSource.MANGAPLUSPARSER_ES,
|
||||
"SPANISH",
|
||||
)
|
||||
|
||||
@MangaSourceParser("MANGAPLUSPARSER_FR", "MANGA Plus French", "fr")
|
||||
class French(context: MangaLoaderContext) : MangaPlusParser(
|
||||
context,
|
||||
MangaSource.MANGAPLUSPARSER_FR,
|
||||
MangaParserSource.MANGAPLUSPARSER_FR,
|
||||
"FRENCH",
|
||||
)
|
||||
|
||||
@MangaSourceParser("MANGAPLUSPARSER_ID", "MANGA Plus Indonesian", "id")
|
||||
class Indonesian(context: MangaLoaderContext) : MangaPlusParser(
|
||||
context,
|
||||
MangaSource.MANGAPLUSPARSER_ID,
|
||||
MangaParserSource.MANGAPLUSPARSER_ID,
|
||||
"INDONESIAN",
|
||||
)
|
||||
|
||||
@MangaSourceParser("MANGAPLUSPARSER_PTBR", "MANGA Plus Portuguese (Brazil)", "pt")
|
||||
class Portuguese(context: MangaLoaderContext) : MangaPlusParser(
|
||||
context,
|
||||
MangaSource.MANGAPLUSPARSER_PTBR,
|
||||
MangaParserSource.MANGAPLUSPARSER_PTBR,
|
||||
"PORTUGUESE_BR",
|
||||
)
|
||||
|
||||
@MangaSourceParser("MANGAPLUSPARSER_RU", "MANGA Plus Russian", "ru")
|
||||
class Russian(context: MangaLoaderContext) : MangaPlusParser(
|
||||
context,
|
||||
MangaSource.MANGAPLUSPARSER_RU,
|
||||
MangaParserSource.MANGAPLUSPARSER_RU,
|
||||
"RUSSIAN",
|
||||
)
|
||||
|
||||
@MangaSourceParser("MANGAPLUSPARSER_TH", "MANGA Plus Thai", "th")
|
||||
class Thai(context: MangaLoaderContext) : MangaPlusParser(
|
||||
context,
|
||||
MangaSource.MANGAPLUSPARSER_TH,
|
||||
MangaParserSource.MANGAPLUSPARSER_TH,
|
||||
"THAI",
|
||||
)
|
||||
|
||||
@MangaSourceParser("MANGAPLUSPARSER_VI", "MANGA Plus Vietnamese", "vi")
|
||||
class Vietnamese(context: MangaLoaderContext) : MangaPlusParser(
|
||||
context,
|
||||
MangaSource.MANGAPLUSPARSER_VI,
|
||||
MangaParserSource.MANGAPLUSPARSER_VI,
|
||||
"VIETNAMESE",
|
||||
)
|
||||
|
||||
@MangaSourceParser("MANGAPLUSPARSER_DE", "MANGA Plus German", "de")
|
||||
class German(context: MangaLoaderContext) : MangaPlusParser(
|
||||
context,
|
||||
MangaSource.MANGAPLUSPARSER_DE,
|
||||
MangaParserSource.MANGAPLUSPARSER_DE,
|
||||
"GERMAN",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import javax.crypto.spec.SecretKeySpec
|
||||
import kotlin.math.min
|
||||
|
||||
@MangaSourceParser("MANGAREADERTO", "MangaReader.To")
|
||||
class MangaReaderToParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.MANGAREADERTO, 16),
|
||||
class MangaReaderToParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAREADERTO, 16),
|
||||
Interceptor {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("mangareader.to")
|
||||
|
||||
@@ -17,7 +17,7 @@ import java.util.*
|
||||
|
||||
internal abstract class NineMangaParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaSource,
|
||||
source: MangaParserSource,
|
||||
defaultDomain: String,
|
||||
) : PagedMangaParser(context, source, pageSize = 26), Interceptor {
|
||||
|
||||
@@ -267,49 +267,49 @@ internal abstract class NineMangaParser(
|
||||
@MangaSourceParser("NINEMANGA_EN", "NineManga English", "en")
|
||||
class English(context: MangaLoaderContext) : NineMangaParser(
|
||||
context,
|
||||
MangaSource.NINEMANGA_EN,
|
||||
MangaParserSource.NINEMANGA_EN,
|
||||
"www.ninemanga.com",
|
||||
)
|
||||
|
||||
@MangaSourceParser("NINEMANGA_ES", "NineManga Español", "es")
|
||||
class Spanish(context: MangaLoaderContext) : NineMangaParser(
|
||||
context,
|
||||
MangaSource.NINEMANGA_ES,
|
||||
MangaParserSource.NINEMANGA_ES,
|
||||
"es.ninemanga.com",
|
||||
)
|
||||
|
||||
@MangaSourceParser("NINEMANGA_RU", "NineManga Русский", "ru")
|
||||
class Russian(context: MangaLoaderContext) : NineMangaParser(
|
||||
context,
|
||||
MangaSource.NINEMANGA_RU,
|
||||
MangaParserSource.NINEMANGA_RU,
|
||||
"ru.ninemanga.com",
|
||||
)
|
||||
|
||||
@MangaSourceParser("NINEMANGA_DE", "NineManga Deutsch", "de")
|
||||
class Deutsch(context: MangaLoaderContext) : NineMangaParser(
|
||||
context,
|
||||
MangaSource.NINEMANGA_DE,
|
||||
MangaParserSource.NINEMANGA_DE,
|
||||
"de.ninemanga.com",
|
||||
)
|
||||
|
||||
@MangaSourceParser("NINEMANGA_BR", "NineManga Brasil", "pt")
|
||||
class Brazil(context: MangaLoaderContext) : NineMangaParser(
|
||||
context,
|
||||
MangaSource.NINEMANGA_BR,
|
||||
MangaParserSource.NINEMANGA_BR,
|
||||
"br.ninemanga.com",
|
||||
)
|
||||
|
||||
@MangaSourceParser("NINEMANGA_IT", "NineManga Italiano", "it")
|
||||
class Italiano(context: MangaLoaderContext) : NineMangaParser(
|
||||
context,
|
||||
MangaSource.NINEMANGA_IT,
|
||||
MangaParserSource.NINEMANGA_IT,
|
||||
"it.ninemanga.com",
|
||||
)
|
||||
|
||||
@MangaSourceParser("NINEMANGA_FR", "NineManga Français", "fr")
|
||||
class Francais(context: MangaLoaderContext) : NineMangaParser(
|
||||
context,
|
||||
MangaSource.NINEMANGA_FR,
|
||||
MangaParserSource.NINEMANGA_FR,
|
||||
"fr.ninemanga.com",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,11 +18,11 @@ import org.koitharu.kotatsu.parsers.util.json.toJSONList
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("NINENINENINEHENTAI", "999Hentai", type = ContentType.HENTAI)
|
||||
@MangaSourceParser("NINENINENINEHENTAI", "AnimeH", type = ContentType.HENTAI)
|
||||
internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.NINENINENINEHENTAI, size), Interceptor {
|
||||
PagedMangaParser(context, MangaParserSource.NINENINENINEHENTAI, size), Interceptor {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("999hentai.net")
|
||||
override val configKeyDomain = ConfigKey.Domain("animeh.to")
|
||||
|
||||
override val availableSortOrders: EnumSet<SortOrder> = EnumSet.of(
|
||||
SortOrder.POPULARITY,
|
||||
@@ -377,7 +377,7 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
private suspend fun apiCall(query: String): JSONObject {
|
||||
return webClient.graphQLQuery("https://hapi.$domain/api", query).getJSONObject("data")
|
||||
return webClient.graphQLQuery("https://api.$domain/api", query).getJSONObject("data")
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -14,39 +14,16 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.exception.NotFoundException
|
||||
import org.koitharu.kotatsu.parsers.exception.ParseException
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.util.SoftSuspendLazy
|
||||
import org.koitharu.kotatsu.parsers.util.SuspendLazy
|
||||
import org.koitharu.kotatsu.parsers.util.domain
|
||||
import org.koitharu.kotatsu.parsers.util.generateUid
|
||||
import org.koitharu.kotatsu.parsers.util.json.getBooleanOrDefault
|
||||
import org.koitharu.kotatsu.parsers.util.json.getFloatOrDefault
|
||||
import org.koitharu.kotatsu.parsers.util.json.getIntOrDefault
|
||||
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
|
||||
import org.koitharu.kotatsu.parsers.util.json.mapJSON
|
||||
import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed
|
||||
import org.koitharu.kotatsu.parsers.util.mapChapters
|
||||
import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany
|
||||
import org.koitharu.kotatsu.parsers.util.parseJson
|
||||
import org.koitharu.kotatsu.parsers.util.splitTwoParts
|
||||
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
|
||||
import org.koitharu.kotatsu.parsers.util.urlEncoded
|
||||
import java.util.Calendar
|
||||
import java.util.EnumSet
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import org.koitharu.kotatsu.parsers.util.json.*
|
||||
import java.util.*
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
internal abstract class WebtoonsParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaSource,
|
||||
source: MangaParserSource,
|
||||
) : MangaParser(context, source) {
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
@@ -307,25 +284,25 @@ internal abstract class WebtoonsParser(
|
||||
}
|
||||
|
||||
@MangaSourceParser("WEBTOONS_EN", "Webtoons English", "en", type = ContentType.MANGA)
|
||||
class English(context: MangaLoaderContext) : WebtoonsParser(context, MangaSource.WEBTOONS_EN)
|
||||
class English(context: MangaLoaderContext) : WebtoonsParser(context, MangaParserSource.WEBTOONS_EN)
|
||||
|
||||
@MangaSourceParser("WEBTOONS_ID", "Webtoons Indonesia", "id", type = ContentType.MANGA)
|
||||
class Indonesian(context: MangaLoaderContext) : WebtoonsParser(context, MangaSource.WEBTOONS_ID)
|
||||
class Indonesian(context: MangaLoaderContext) : WebtoonsParser(context, MangaParserSource.WEBTOONS_ID)
|
||||
|
||||
@MangaSourceParser("WEBTOONS_ES", "Webtoons Spanish", "es", type = ContentType.MANGA)
|
||||
class Spanish(context: MangaLoaderContext) : WebtoonsParser(context, MangaSource.WEBTOONS_ES)
|
||||
class Spanish(context: MangaLoaderContext) : WebtoonsParser(context, MangaParserSource.WEBTOONS_ES)
|
||||
|
||||
@MangaSourceParser("WEBTOONS_FR", "Webtoons French", "fr", type = ContentType.MANGA)
|
||||
class French(context: MangaLoaderContext) : WebtoonsParser(context, MangaSource.WEBTOONS_FR)
|
||||
class French(context: MangaLoaderContext) : WebtoonsParser(context, MangaParserSource.WEBTOONS_FR)
|
||||
|
||||
@MangaSourceParser("WEBTOONS_TH", "Webtoons Thai", "th", type = ContentType.MANGA)
|
||||
class Thai(context: MangaLoaderContext) : WebtoonsParser(context, MangaSource.WEBTOONS_TH)
|
||||
class Thai(context: MangaLoaderContext) : WebtoonsParser(context, MangaParserSource.WEBTOONS_TH)
|
||||
|
||||
@MangaSourceParser("WEBTOONS_ZH", "Webtoons Chinese", "zh", type = ContentType.MANGA)
|
||||
class Chinese(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaSource.WEBTOONS_ZH)
|
||||
class Chinese(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaParserSource.WEBTOONS_ZH)
|
||||
|
||||
@MangaSourceParser("WEBTOONS_DE", "Webtoons German", "de", type = ContentType.MANGA)
|
||||
class German(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaSource.WEBTOONS_DE)
|
||||
class German(context: MangaLoaderContext) : LineWebtoonsParser(context, MangaParserSource.WEBTOONS_DE)
|
||||
|
||||
private inner class WebtoonsUrlSigner(private val secret: String) {
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.*
|
||||
|
||||
internal abstract class AnimeBootstrapParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaSource,
|
||||
source: MangaParserSource,
|
||||
domain: String,
|
||||
pageSize: Int = 24,
|
||||
) : PagedMangaParser(context, source, pageSize) {
|
||||
|
||||
@@ -10,13 +10,12 @@ import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.site.animebootstrap.AnimeBootstrapParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.EnumSet
|
||||
import java.util.Locale
|
||||
import java.util.*
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("PAPSCAN", "PapScan", "fr")
|
||||
internal class PapScan(context: MangaLoaderContext) :
|
||||
AnimeBootstrapParser(context, MangaSource.PAPSCAN, "papscan.com") {
|
||||
AnimeBootstrapParser(context, MangaParserSource.PAPSCAN, "papscan.com") {
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
override val isMultipleTagsSupported = false
|
||||
override val listUrl = "/liste-manga"
|
||||
|
||||
@@ -2,9 +2,9 @@ package org.koitharu.kotatsu.parsers.site.animebootstrap.id
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.animebootstrap.AnimeBootstrapParser
|
||||
|
||||
@MangaSourceParser("KOMIKZOID", "KomikzoId", "id")
|
||||
internal class KomikzoId(context: MangaLoaderContext) :
|
||||
AnimeBootstrapParser(context, MangaSource.KOMIKZOID, "komikzoid.id")
|
||||
AnimeBootstrapParser(context, MangaParserSource.KOMIKZOID, "komikzoid.id")
|
||||
|
||||
@@ -2,9 +2,9 @@ package org.koitharu.kotatsu.parsers.site.animebootstrap.id
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.animebootstrap.AnimeBootstrapParser
|
||||
|
||||
@MangaSourceParser("NEUMANGA", "NeuManga.xyz", "id")
|
||||
internal class NeuManga(context: MangaLoaderContext) :
|
||||
AnimeBootstrapParser(context, MangaSource.NEUMANGA, "neumanga.xyz")
|
||||
AnimeBootstrapParser(context, MangaParserSource.NEUMANGA, "neumanga.xyz")
|
||||
|
||||
@@ -2,9 +2,9 @@ package org.koitharu.kotatsu.parsers.site.animebootstrap.id
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.animebootstrap.AnimeBootstrapParser
|
||||
|
||||
@MangaSourceParser("SEKTEKOMIK", "SekteKomik", "id")
|
||||
internal class SekteKomik(context: MangaLoaderContext) :
|
||||
AnimeBootstrapParser(context, MangaSource.SEKTEKOMIK, "sektekomik.xyz")
|
||||
AnimeBootstrapParser(context, MangaParserSource.SEKTEKOMIK, "sektekomik.xyz")
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("FLIXSCANS", "FlixScans.net", "ar")
|
||||
internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.FLIXSCANS, 18) {
|
||||
internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.FLIXSCANS, 18) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
|
||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("MANGASTORM", "MangaStorm", "ar")
|
||||
internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.MANGASTORM, 30) {
|
||||
internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGASTORM, 30) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED)
|
||||
override val configKeyDomain = ConfigKey.Domain("mangastorm.org")
|
||||
|
||||
@@ -14,13 +14,13 @@ import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("TEAMXNOVEL", "TeamXNovel", "ar")
|
||||
internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.TEAMXNOVEL, 10) {
|
||||
internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.TEAMXNOVEL, 10) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
override val availableStates: Set<MangaState> =
|
||||
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("teamxnovel.com")
|
||||
override val configKeyDomain = ConfigKey.Domain("teamoney.site")
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
@@ -31,7 +31,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append("/series?search=")
|
||||
append("/?search=")
|
||||
append(filter.query.urlEncoded())
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
@@ -95,7 +95,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = div.selectFirstOrThrow("img").src().orEmpty(),
|
||||
coverUrl = div.selectFirstOrThrow("img").src()?.replace("thumbnail_", "").orEmpty(),
|
||||
tags = emptySet(),
|
||||
state = when (div.selectFirst(".status")?.text()) {
|
||||
"مستمرة" -> MangaState.ONGOING
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.*
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("ANIBEL", "Anibel", "be")
|
||||
internal class AnibelParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.ANIBEL) {
|
||||
internal class AnibelParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.ANIBEL) {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("anibel.net")
|
||||
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
package org.koitharu.kotatsu.parsers.site.cupfox
|
||||
|
||||
import org.jsoup.nodes.Document
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.util.*
|
||||
|
||||
internal abstract class CupFoxParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaParserSource,
|
||||
domain: String,
|
||||
pageSize: Int = 24,
|
||||
) : PagedMangaParser(context, source, pageSize) {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain(domain)
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.POPULARITY,
|
||||
SortOrder.UPDATED,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search/")
|
||||
append(filter.query.urlEncoded())
|
||||
append('/')
|
||||
append(page)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
append("/category/")
|
||||
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("order/hits/")
|
||||
SortOrder.UPDATED -> append("order/addtime/")
|
||||
else -> append("order/addtime/")
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "finish/1/"
|
||||
MangaState.FINISHED -> "finish/2/"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("tags/")
|
||||
append(it.key)
|
||||
append('/')
|
||||
}
|
||||
}
|
||||
|
||||
append("page/")
|
||||
append(page)
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/category/order/addtime/page/")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
protected open val selectMangas =
|
||||
"ul.row li, ul.stui-vodlist li, ul.clearfix li.dm-list, div.vod-list ul.row li, ul.ewave-vodlist li"
|
||||
protected open val selectMangasCover =
|
||||
"div.img-wrapper, div.stui-vodlist__thumb, a.stui-vodlist__thumb, div.ewave-vodlist__thumb, img.dm-thumb"
|
||||
|
||||
private fun parseMangaList(doc: Document): List<Manga> {
|
||||
return doc.select(selectMangas).map { li ->
|
||||
val href = li.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
title = li.selectFirst("h3, h4, p.dm-bn")?.text().orEmpty(),
|
||||
altTitle = null,
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = li.selectFirst(selectMangasCover)
|
||||
?.src().orEmpty(),
|
||||
tags = setOf(),
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
protected open val selectMangaDetailsAltTitle =
|
||||
"div.info span:contains(Autres noms), div.info span:contains(Biệt danh)"
|
||||
protected open val selectMangaDetailsTags =
|
||||
"div.info span a[href*=tags], p.data a[href*=tags], div.book-main-right p.info-text a[href*=tags]"
|
||||
protected open val selectMangaDetailsAuthor =
|
||||
"div.info span:contains(Auteur(s)), div.info span:contains(Tác giả), p.data span:contains(Auteur(s)), p.data span:contains(Autor), p.data span:contains(作者), div.book-main-right div.book-info:contains(作者) .info-text"
|
||||
protected open val selectMangaDescription =
|
||||
"div.vod-list:contains(Résumé) div.more-box, div.stui-pannel__head:contains(Résumé), div.book-desc div.info-text, div.info div.text:contains(Giới thiệu), #desc"
|
||||
protected open val selectMangaChapters =
|
||||
"div.episode-box ul li, ul.stui-content__playlist li a, ul.cnxh-ul li a, ul.ewave-content__playlist li a"
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
return manga.copy(
|
||||
altTitle = doc.selectFirst(selectMangaDetailsAltTitle)?.text()?.substringAfter(":"),
|
||||
state = null,
|
||||
tags = doc.select(selectMangaDetailsTags).mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").removeSuffix("/").substringAfterLast('/'),
|
||||
title = a.text().toTitleCase(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
author = doc.selectFirst(selectMangaDetailsAuthor)?.text()?.substringAfter(":"),
|
||||
description = doc.selectFirst(selectMangaDescription)
|
||||
?.html(),
|
||||
chapters = doc.select(selectMangaChapters)
|
||||
.mapChapters { i, li ->
|
||||
val a = li.selectFirstOrThrow("a")
|
||||
val href = a.attrAsRelativeUrl("href")
|
||||
MangaChapter(
|
||||
id = generateUid(href),
|
||||
name = a.text(),
|
||||
number = i + 1f,
|
||||
volume = 0,
|
||||
url = href,
|
||||
scanlator = null,
|
||||
uploadDate = 0L,
|
||||
branch = null,
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getRelatedManga(seed: Manga): List<Manga> {
|
||||
val doc = webClient.httpGet(seed.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
return doc.select("div.vod-list div.more-box li, ul.stui-vodlist__bd li, ul.about-yxul li, ul.ewave-vodlist__bd li")
|
||||
.map { li ->
|
||||
val href = li.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
title = li.selectFirst("h3, h4, p.dm-bn")?.text().orEmpty(),
|
||||
altTitle = null,
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = li.selectFirst(selectMangasCover)?.src().orEmpty(),
|
||||
tags = setOf(),
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
protected open val selectPages = "div.more-box li img, ul.main li img"
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
return doc.select(selectPages).map { img ->
|
||||
val url = img.src() ?: img.parseFailed("Image src not found")
|
||||
MangaPage(
|
||||
id = generateUid(url),
|
||||
url = url,
|
||||
preview = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
protected open val selectAvailableTags = "div.swiper-wrapper a[href*=tags], ul.stui-screen__list li a[href*=tags]"
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/category/").parseHtml()
|
||||
return doc.select(selectAvailableTags)
|
||||
.mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").removeSuffix("/").substringAfterLast('/'),
|
||||
title = a.text().toTitleCase(),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.koitharu.kotatsu.parsers.site.cupfox.de
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.cupfox.CupFoxParser
|
||||
|
||||
@MangaSourceParser("MANGAHAUS", "MangaHaus", "de")
|
||||
internal class MangaHaus(context: MangaLoaderContext) :
|
||||
CupFoxParser(context, MangaParserSource.MANGAHAUS, "www.mangahaus.com")
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.koitharu.kotatsu.parsers.site.cupfox.fr
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.cupfox.CupFoxParser
|
||||
|
||||
@MangaSourceParser("ENLIGNEMANGA", "EnLigneManga", "fr")
|
||||
internal class EnLigneManga(context: MangaLoaderContext) :
|
||||
CupFoxParser(context, MangaParserSource.ENLIGNEMANGA, "www.enlignemanga.com")
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.koitharu.kotatsu.parsers.site.cupfox.fr
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.cupfox.CupFoxParser
|
||||
|
||||
@MangaSourceParser("FRMANGA", "FrManga", "fr")
|
||||
internal class FrManga(context: MangaLoaderContext) :
|
||||
CupFoxParser(context, MangaParserSource.FRMANGA, "www.frmanga.com")
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.koitharu.kotatsu.parsers.site.cupfox.fr
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.cupfox.CupFoxParser
|
||||
|
||||
@MangaSourceParser("SEINEMANGA", "SeineManga", "fr")
|
||||
internal class SeineManga(context: MangaLoaderContext) :
|
||||
CupFoxParser(context, MangaParserSource.SEINEMANGA, "www.seinemanga.com")
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.koitharu.kotatsu.parsers.site.cupfox.ja
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.cupfox.CupFoxParser
|
||||
|
||||
@MangaSourceParser("MANGAKOINU", "MangaKoinu", "ja")
|
||||
internal class MangaKoinu(context: MangaLoaderContext) :
|
||||
CupFoxParser(context, MangaParserSource.MANGAKOINU, "www.mangakoinu.com")
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.koitharu.kotatsu.parsers.site.cupfox.vi
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.cupfox.CupFoxParser
|
||||
|
||||
@MangaSourceParser("OIOIVN", "OioiVn", "vi")
|
||||
internal class OioiVn(context: MangaLoaderContext) :
|
||||
CupFoxParser(context, MangaParserSource.OIOIVN, "oioivn.com")
|
||||
@@ -0,0 +1,177 @@
|
||||
package org.koitharu.kotatsu.parsers.site.en
|
||||
|
||||
import androidx.collection.ArrayMap
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import org.koitharu.kotatsu.parsers.util.json.toJSONList
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("ASURASCANS", "AsuraComic", "en")
|
||||
internal class AsuraScansParser(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.ASURASCANS, pageSize = 30) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.RATING,
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.NEWEST,
|
||||
SortOrder.ALPHABETICAL_DESC,
|
||||
SortOrder.ALPHABETICAL,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("asuracomic.net")
|
||||
|
||||
override val isMultipleTagsSupported = true
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/series?page=")
|
||||
append(page)
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("&name=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&genres=")
|
||||
append(filter.tags.joinToString(separator = ",") { it.key })
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "1"
|
||||
MangaState.FINISHED -> "3"
|
||||
MangaState.ABANDONED -> "4"
|
||||
MangaState.PAUSED -> "2"
|
||||
MangaState.UPCOMING -> "6"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
append("&types=-1&order=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.RATING -> append("rating")
|
||||
SortOrder.UPDATED -> append("update")
|
||||
SortOrder.NEWEST -> append("latest")
|
||||
SortOrder.ALPHABETICAL_DESC -> append("desc")
|
||||
SortOrder.ALPHABETICAL -> append("asc")
|
||||
else -> append("update")
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("&genres=&status=-1&order=update&types=-1")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select("div.grid > a[href]").map { a ->
|
||||
val href = "/" + a.attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
coverUrl = a.selectFirst("img")?.src().orEmpty(),
|
||||
title = a.selectFirst("div.block > span.block")?.text().orEmpty(),
|
||||
altTitle = null,
|
||||
rating = a.selectFirst("div.block label.ml-1")?.text()?.toFloatOrNull()?.div(10f) ?: RATING_UNKNOWN,
|
||||
tags = emptySet(),
|
||||
author = null,
|
||||
state = when (a.selectLastOrThrow("span.status").text()) {
|
||||
"Ongoing" -> MangaState.ONGOING
|
||||
"Completed" -> MangaState.FINISHED
|
||||
"Hiatus" -> MangaState.PAUSED
|
||||
"Dropped" -> MangaState.ABANDONED
|
||||
"Coming Soon" -> MangaState.UPCOMING
|
||||
else -> null
|
||||
},
|
||||
source = source,
|
||||
isNsfw = isNsfwSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var tagCache: ArrayMap<String, MangaTag>? = null
|
||||
private val mutex = Mutex()
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
return getOrCreateTagMap().values.toSet()
|
||||
}
|
||||
|
||||
private suspend fun getOrCreateTagMap(): Map<String, MangaTag> = mutex.withLock {
|
||||
tagCache?.let { return@withLock it }
|
||||
val tagMap = ArrayMap<String, MangaTag>()
|
||||
val json =
|
||||
webClient.httpGet("https://gg.$domain/api/series/filters").parseJson().getJSONArray("genres").toJSONList()
|
||||
for (el in json) {
|
||||
if (el.getString("name").isEmpty()) continue
|
||||
tagMap[el.getString("name")] = MangaTag(
|
||||
key = el.getInt("id").toString(),
|
||||
title = el.getString("name"),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
tagCache = tagMap
|
||||
return@withLock tagMap
|
||||
}
|
||||
|
||||
private val regexDate = """(\d+)(st|nd|rd|th)""".toRegex()
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
val tagMap = getOrCreateTagMap()
|
||||
val selectTag = doc.select("div[class^=space] > div.flex > button.text-white")
|
||||
val tags = selectTag.mapNotNullToSet { tagMap[it.text()] }
|
||||
return manga.copy(
|
||||
description = doc.selectFirst("span.font-medium.text-sm")?.text().orEmpty(),
|
||||
tags = tags,
|
||||
author = doc.selectFirst("div.grid > div:has(h3:eq(0):containsOwn(Author)) > h3:eq(1)")?.text(),
|
||||
chapters = doc.select("div.scrollbar-thumb-themecolor > div.group").mapChapters(reversed = true) { i, div ->
|
||||
val a = div.selectLastOrThrow("a")
|
||||
val urlRelative = "/series/" + a.attrAsRelativeUrl("href")
|
||||
val url = urlRelative.toAbsoluteUrl(domain)
|
||||
val date = div.selectFirst("h3:eq(1)")!!.ownText()
|
||||
val cleanDate = date.replace(regexDate, "$1")
|
||||
MangaChapter(
|
||||
id = generateUid(url),
|
||||
name = div.selectFirstOrThrow("h3:eq(0)").text(),
|
||||
number = i + 1f,
|
||||
volume = 0,
|
||||
url = url,
|
||||
scanlator = null,
|
||||
uploadDate = SimpleDateFormat("MMMM d yyyy", Locale.US)
|
||||
.tryParse(cleanDate),
|
||||
branch = null,
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
return doc.select("div > img[alt=chapter]").map { img ->
|
||||
val urlPage = img.src()?.toRelativeUrl(domain) ?: img.parseFailed("Image src not found")
|
||||
MangaPage(
|
||||
id = generateUid(urlPage),
|
||||
url = urlPage,
|
||||
preview = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("BEETOON", "BeeToon.net", "en")
|
||||
internal class BeeToon(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.BEETOON, pageSize = 30) {
|
||||
PagedMangaParser(context, MangaParserSource.BEETOON, pageSize = 30) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("CLONEMANGA", "CloneManga", "en")
|
||||
internal class CloneMangaParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.CLONEMANGA) {
|
||||
internal class CloneMangaParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.CLONEMANGA) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = Collections.singleton(
|
||||
SortOrder.POPULARITY,
|
||||
|
||||
@@ -13,14 +13,14 @@ import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("COMICEXTRA", "ComicExtra", "en")
|
||||
internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.COMICEXTRA, 25) {
|
||||
internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.COMICEXTRA, 25) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("comicextra.org")
|
||||
override val configKeyDomain = ConfigKey.Domain("comixextra.com")
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("DYNASTYSCANS", "DynastyScans", "en")
|
||||
internal class DynastyScans(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.DYNASTYSCANS, 117) {
|
||||
internal class DynastyScans(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.DYNASTYSCANS, 117) {
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
|
||||
override val configKeyDomain = ConfigKey.Domain("dynasty-scans.com")
|
||||
|
||||
@@ -38,8 +39,7 @@ internal class DynastyScans(context: MangaLoaderContext) : PagedMangaParser(cont
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&")
|
||||
append("classes[]".urlEncoded())
|
||||
append("=Serie&page=")
|
||||
append("classes[]=Series&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
return parseMangaListQuery(webClient.httpGet(url).parseHtml())
|
||||
@@ -154,6 +154,9 @@ internal class DynastyScans(context: MangaLoaderContext) : PagedMangaParser(cont
|
||||
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
val chapters = getChapters(doc)
|
||||
val root = doc.requireElementById("main")
|
||||
val licensedText = root.select("h4")
|
||||
.find { it.ownText() == "This manga has been licensed" }
|
||||
?.nextElementSibling()?.html()
|
||||
return manga.copy(
|
||||
altTitle = null,
|
||||
state = when (root.select("h2.tag-title small").last()?.text()) {
|
||||
@@ -167,7 +170,7 @@ internal class DynastyScans(context: MangaLoaderContext) : PagedMangaParser(cont
|
||||
.orEmpty(), // It is needed if the manga was found via the search.
|
||||
tags = root.selectFirstOrThrow("div.tag-tags").parseTags(),
|
||||
author = null,
|
||||
description = null,
|
||||
description = licensedText,
|
||||
chapters = chapters,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@ import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("FLIXSCANSORG", "FlixScans.org", "en")
|
||||
internal class FlixScansOrg(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.FLIXSCANSORG, 18) {
|
||||
internal class FlixScansOrg(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.FLIXSCANSORG, 18) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
|
||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||
|
||||
@@ -2,30 +2,31 @@ package org.koitharu.kotatsu.parsers.site.en
|
||||
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import okhttp3.Headers
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("MANGAGEKO", "MangaGeko", "en")
|
||||
internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.MANGAGEKO, 30) {
|
||||
internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAGEKO, 30) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("www.mgeko.com", "www.mangageko.com")
|
||||
override val configKeyDomain = ConfigKey.Domain("www.mgeko.cc", "www.mgeko.com", "www.mangageko.com")
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override val headers: Headers = Headers.Builder()
|
||||
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||
.build()
|
||||
private val userAgentKey = ConfigKey.UserAgent(context.getDefaultUserAgent())
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.koitharu.kotatsu.parsers.site.en
|
||||
|
||||
import org.koitharu.kotatsu.parsers.*
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
@@ -9,7 +11,8 @@ import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("MANGATOWN", "MangaTown", "en")
|
||||
internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.MANGATOWN, 30) {
|
||||
internal class MangaTownParser(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.MANGATOWN, 30) {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("www.mangatown.com")
|
||||
|
||||
@@ -102,7 +105,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c
|
||||
id = generateUid(href),
|
||||
title = a.attr("title"),
|
||||
coverUrl = a.selectFirst("img")?.absUrl("src").orEmpty(),
|
||||
source = MangaSource.MANGATOWN,
|
||||
source = MangaParserSource.MANGATOWN,
|
||||
altTitle = null,
|
||||
rating = li.selectFirst("p.score")?.selectFirst("b")
|
||||
?.ownText()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
|
||||
@@ -118,7 +121,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c
|
||||
MangaTag(
|
||||
title = x.attr("title").toTitleCase(),
|
||||
key = x.attr("href").substringAfter("/directory/0-").substringBefore("-0-"),
|
||||
source = MangaSource.MANGATOWN,
|
||||
source = MangaParserSource.MANGATOWN,
|
||||
)
|
||||
}.orEmpty(),
|
||||
url = href,
|
||||
@@ -143,7 +146,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c
|
||||
MangaTag(
|
||||
title = a.attr("title").toTitleCase(),
|
||||
key = a.attr("href").substringAfter("/directory/0-").substringBefore("-0-"),
|
||||
source = MangaSource.MANGATOWN,
|
||||
source = MangaParserSource.MANGATOWN,
|
||||
)
|
||||
}.orEmpty(),
|
||||
description = info?.getElementById("show")?.ownText(),
|
||||
@@ -156,7 +159,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c
|
||||
MangaChapter(
|
||||
id = generateUid(href),
|
||||
url = href,
|
||||
source = MangaSource.MANGATOWN,
|
||||
source = MangaParserSource.MANGATOWN,
|
||||
number = i + 1f,
|
||||
volume = 0,
|
||||
uploadDate = parseChapterDate(
|
||||
@@ -174,10 +177,10 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
val root = doc.body().selectFirstOrThrow("div.page_select")
|
||||
val isManga = root.select("select")
|
||||
val root = doc.body().selectFirst("div.page_select")
|
||||
val isManga = root?.select("select")
|
||||
|
||||
if (isManga.isEmpty()) {//Webtoon
|
||||
if (isManga.isNullOrEmpty()) {//Webtoon
|
||||
val imgElements = doc.select("div#viewer.read_img img.image")
|
||||
return imgElements.map {
|
||||
val href = it.attr("src")
|
||||
@@ -185,7 +188,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c
|
||||
id = generateUid(href),
|
||||
url = href,
|
||||
preview = null,
|
||||
source = MangaSource.MANGATOWN,
|
||||
source = MangaParserSource.MANGATOWN,
|
||||
)
|
||||
|
||||
}
|
||||
@@ -199,7 +202,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c
|
||||
id = generateUid(href),
|
||||
url = href,
|
||||
preview = null,
|
||||
source = MangaSource.MANGATOWN,
|
||||
source = MangaParserSource.MANGATOWN,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -224,7 +227,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c
|
||||
val a = li.selectFirst("a") ?: return@mapNotNullToSet null
|
||||
val key = a.attr("href").substringAfter("/directory/0-").substringBefore("-0-")
|
||||
MangaTag(
|
||||
source = MangaSource.MANGATOWN,
|
||||
source = MangaParserSource.MANGATOWN,
|
||||
key = key,
|
||||
title = a.text().toTitleCase(),
|
||||
)
|
||||
@@ -253,7 +256,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c
|
||||
MangaChapter(
|
||||
id = generateUid(href),
|
||||
url = href,
|
||||
source = MangaSource.MANGATOWN,
|
||||
source = MangaParserSource.MANGATOWN,
|
||||
number = i + 1f,
|
||||
volume = 0,
|
||||
uploadDate = parseChapterDate(
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.parsers.site.en
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import okhttp3.Headers
|
||||
import org.jsoup.nodes.Document
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
@@ -14,9 +15,10 @@ import org.koitharu.kotatsu.parsers.util.json.mapJSON
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("MANGAOWL", "MangaOwl.to", "en")
|
||||
internal class Mangaowl(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.MANGAOWL, pageSize = 24) {
|
||||
PagedMangaParser(context, MangaParserSource.MANGAOWL, pageSize = 24) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.POPULARITY,
|
||||
|
||||
@@ -11,7 +11,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("MANHWA18", "Manhwa18.net", "en", type = ContentType.HENTAI)
|
||||
class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.MANHWA18, pageSize = 18, searchPageSize = 18) {
|
||||
PagedMangaParser(context, MangaParserSource.MANHWA18, pageSize = 18, searchPageSize = 18) {
|
||||
|
||||
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwa18.net")
|
||||
|
||||
@@ -122,7 +122,7 @@ class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
author = null,
|
||||
largeCoverUrl = null,
|
||||
description = null,
|
||||
source = MangaSource.MANHWA18,
|
||||
source = MangaParserSource.MANHWA18,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -167,7 +167,7 @@ class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
scanlator = null,
|
||||
uploadDate = uploadDate,
|
||||
branch = null,
|
||||
source = MangaSource.MANHWA18,
|
||||
source = MangaParserSource.MANHWA18,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -210,7 +210,7 @@ class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
id = generateUid(url),
|
||||
url = url,
|
||||
preview = null,
|
||||
source = MangaSource.MANHWA18,
|
||||
source = MangaParserSource.MANHWA18,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("MANHWASMEN", "ManhwasMen", "en", type = ContentType.HENTAI)
|
||||
class ManhwasMen(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.MANHWASMEN, pageSize = 30, searchPageSize = 30) {
|
||||
PagedMangaParser(context, MangaParserSource.MANHWASMEN, pageSize = 30, searchPageSize = 30) {
|
||||
|
||||
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwas.men")
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("PO2SCANS", "Po2Scans", "en")
|
||||
internal class Po2Scans(context: MangaLoaderContext) : MangaParser(context, MangaSource.PO2SCANS) {
|
||||
internal class Po2Scans(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.PO2SCANS) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
|
||||
override val configKeyDomain = ConfigKey.Domain("po2scans.com")
|
||||
|
||||
@@ -15,7 +15,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("PURURIN", "Pururin", "en", ContentType.HENTAI)
|
||||
internal class Pururin(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.PURURIN, pageSize = 20) {
|
||||
PagedMangaParser(context, MangaParserSource.PURURIN, pageSize = 20) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.RATING, SortOrder.ALPHABETICAL)
|
||||
|
||||
@@ -23,21 +23,18 @@ import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.random.Random
|
||||
|
||||
private const val TOO_MANY_REQUESTS = 429
|
||||
private const val MAX_RETRY_COUNT = 5
|
||||
private val JSON_MEDIA_TYPE get() = "application/json; charset=utf-8".toMediaType()
|
||||
|
||||
@MangaSourceParser("REAPERCOMICS", "ReaperComics", "en")
|
||||
internal class ReaperComics(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.REAPERCOMICS, pageSize = 32) {
|
||||
PagedMangaParser(context, MangaParserSource.REAPERCOMICS, pageSize = 32) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.ALPHABETICAL)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("reaperscans.com")
|
||||
|
||||
private val userAgentKey = ConfigKey.UserAgent(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
||||
)
|
||||
private val userAgentKey = ConfigKey.UserAgent(context.getDefaultUserAgent())
|
||||
|
||||
private val baseHeaders: Headers
|
||||
get() = Headers.Builder().add("User-Agent", config[userAgentKey]).build()
|
||||
@@ -159,9 +156,9 @@ internal class ReaperComics(context: MangaLoaderContext) :
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
private fun chapterListNextPageSelector(): String = "button[wire:click*=nextPage]"
|
||||
private val chapterListNextPageSelector: String = "button[wire:click*=nextPage]"
|
||||
|
||||
private fun chapterListSelector() = "div[wire:id] > div > ul[role=list] > li"
|
||||
private val chapterListSelector: String = "div[wire:id] > div > ul[role=list] > li"
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val cachedChapters = chapterCache[manga.url]
|
||||
@@ -171,17 +168,18 @@ internal class ReaperComics(context: MangaLoaderContext) :
|
||||
|
||||
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
val simpleDateFormat = SimpleDateFormat("dd/MM/yyyy", sourceLocale)
|
||||
var totalChapters = (doc.selectFirst(selectTotalChapter)?.text()?.toIntOrNull() ?: 0) - 1
|
||||
var totalChapters = (doc.selectFirst(selectTotalChapter)?.text()?.toFloatOrNull() ?: 0f) - 1f
|
||||
val chapters = mutableSetOf<MangaChapter>()
|
||||
var hasNextPage = doc.selectFirst(chapterListNextPageSelector()) != null
|
||||
var hasNextPage = doc.selectFirst(chapterListNextPageSelector) != null
|
||||
chapters.addAll(
|
||||
doc.select(chapterListSelector()).mapChapters { _, li ->
|
||||
doc.select(chapterListSelector).mapChapters { _, li ->
|
||||
val a = li.selectFirstOrThrow("a")
|
||||
val chapterUrl = a.attr("href").toRelativeUrl(domain)
|
||||
MangaChapter(
|
||||
id = generateUid(chapterUrl),
|
||||
name = li.selectFirst("div.truncate p.truncate")?.text().orEmpty(),
|
||||
number = totalChapters--,
|
||||
volume = 0,
|
||||
url = chapterUrl,
|
||||
scanlator = null,
|
||||
uploadDate = parseChapterDate(
|
||||
@@ -219,7 +217,7 @@ internal class ReaperComics(context: MangaLoaderContext) :
|
||||
var pageToQuery = 2
|
||||
|
||||
// Javascript: (Math.random() + 1).toString(36).substring(8)
|
||||
val generateId = { ->
|
||||
val generateId = {
|
||||
"1.${
|
||||
Random.nextLong().toString(36)
|
||||
}".substring(10)
|
||||
@@ -249,13 +247,14 @@ internal class ReaperComics(context: MangaLoaderContext) :
|
||||
serverMemo = mergeLeft(serverMemo, responseData.serverMemo)
|
||||
val chaptersHtml = Jsoup.parse(responseData.effects.html, "https://$domain")
|
||||
chapters.addAll(
|
||||
chaptersHtml.select(chapterListSelector()).mapChapters { _, li ->
|
||||
chaptersHtml.select(chapterListSelector).mapChapters { _, li ->
|
||||
val a = li.selectFirstOrThrow("a")
|
||||
val chapterUrl = a.attr("href").toRelativeUrl(domain)
|
||||
MangaChapter(
|
||||
id = generateUid(chapterUrl),
|
||||
name = li.selectFirst("div.truncate p.truncate")?.text().orEmpty(),
|
||||
number = totalChapters--,
|
||||
volume = 0,
|
||||
url = chapterUrl,
|
||||
scanlator = null,
|
||||
uploadDate = parseChapterDate(
|
||||
@@ -267,7 +266,7 @@ internal class ReaperComics(context: MangaLoaderContext) :
|
||||
)
|
||||
},
|
||||
)
|
||||
hasNextPage = chaptersHtml.selectFirst(chapterListNextPageSelector()) != null
|
||||
hasNextPage = chaptersHtml.selectFirst(chapterListNextPageSelector) != null
|
||||
pageToQuery++
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("TEMPLESCANESP", "TempleScanEsp", "es", ContentType.HENTAI)
|
||||
internal class TempleScanEsp(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.TEMPLESCANESP, pageSize = 15) {
|
||||
PagedMangaParser(context, MangaParserSource.TEMPLESCANESP, pageSize = 15) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.NEWEST, SortOrder.UPDATED)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.*
|
||||
@MangaSourceParser("TUMANGAONLINE", "TuMangaOnline", "es")
|
||||
class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
context,
|
||||
source = MangaSource.TUMANGAONLINE,
|
||||
source = MangaParserSource.TUMANGAONLINE,
|
||||
pageSize = 24,
|
||||
) {
|
||||
|
||||
@@ -147,7 +147,8 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
return MangaChapter(
|
||||
id = generateUid(href),
|
||||
name = "One Shot",
|
||||
number = 1,
|
||||
number = 1f,
|
||||
volume = 0,
|
||||
url = href,
|
||||
scanlator = element.select("div.col-md-6.text-truncate").text(),
|
||||
branch = null,
|
||||
@@ -163,7 +164,8 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
return MangaChapter(
|
||||
id = generateUid(href),
|
||||
name = chName,
|
||||
number = number + 1,
|
||||
number = number + 1f,
|
||||
volume = 0,
|
||||
url = href,
|
||||
scanlator = element.select("div.col-md-6.text-truncate").text(),
|
||||
branch = null,
|
||||
@@ -205,6 +207,9 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
private suspend fun redirectToReadingPage(document: Document): Document {
|
||||
val script1 = document.selectFirst("script:containsData(uniqid)")
|
||||
val script2 = document.selectFirst("script:containsData(window.location.replace)")
|
||||
val script3 = document.selectFirst("script:containsData(redirectUrl)")
|
||||
val script4 = document.selectFirst("input#redir")
|
||||
val script5 = document.selectFirst("script:containsData(window.opener):containsData(location.replace)")
|
||||
|
||||
val redirectHeaders = Headers.Builder().set("Referer", document.baseUri()).build()
|
||||
|
||||
@@ -226,15 +231,52 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
|
||||
if (script2 != null) {
|
||||
val data = script2.data()
|
||||
val regexRedirect = """window\.location\.replace\('(.+)'\)""".toRegex()
|
||||
val url = regexRedirect.find(data)!!.groupValues[1]
|
||||
val regexRedirect = """window\.location\.replace\(['"](.+)['"]\)""".toRegex()
|
||||
val url = regexRedirect.find(data)?.groupValues?.get(1)?.unescapeUrl()
|
||||
|
||||
if (url != null) {
|
||||
return redirectToReadingPage(webClient.httpGet(url, redirectHeaders).parseHtml())
|
||||
}
|
||||
}
|
||||
|
||||
if (script3 != null) {
|
||||
val data = script3.data()
|
||||
val regexRedirect = """redirectUrl\s*=\s*'(.+)'""".toRegex()
|
||||
val url = regexRedirect.find(data)?.groupValues?.get(1)?.unescapeUrl()
|
||||
|
||||
if (url != null) {
|
||||
return redirectToReadingPage(webClient.httpGet(url, redirectHeaders).parseHtml())
|
||||
}
|
||||
}
|
||||
|
||||
if (script4 != null) {
|
||||
val url = script4.attr("value").unescapeUrl()
|
||||
|
||||
return redirectToReadingPage(webClient.httpGet(url, redirectHeaders).parseHtml())
|
||||
}
|
||||
|
||||
if (script5 != null) {
|
||||
val data = script5.data()
|
||||
val regexRedirect = """;[^.]location\.replace\(['"](.+)['"]\)""".toRegex()
|
||||
val url = regexRedirect.find(data)?.groupValues?.get(1)?.unescapeUrl()
|
||||
|
||||
if (url != null) {
|
||||
return redirectToReadingPage(webClient.httpGet(url, redirectHeaders).parseHtml())
|
||||
}
|
||||
}
|
||||
|
||||
return document
|
||||
}
|
||||
|
||||
private fun String.unescapeUrl(): String {
|
||||
return if (this.startsWith("http:\\/\\/") || this.startsWith("https:\\/\\/")) {
|
||||
this.replace("\\/", "/")
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/library", headers).parseHtml()
|
||||
val elements = doc.body().select("div#books-genders > div > div")
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.util.*
|
||||
|
||||
internal abstract class FmreaderParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaSource,
|
||||
source: MangaParserSource,
|
||||
domain: String,
|
||||
pageSize: Int = 20,
|
||||
) : PagedMangaParser(context, source, pageSize) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.text.SimpleDateFormat
|
||||
|
||||
@MangaSourceParser("MANHWA18COM", "Manhwa18.com", "en", ContentType.HENTAI)
|
||||
internal class Manhwa18Com(context: MangaLoaderContext) :
|
||||
FmreaderParser(context, MangaSource.MANHWA18COM, "manhwa18.com") {
|
||||
FmreaderParser(context, MangaParserSource.MANHWA18COM, "manhwa18.com") {
|
||||
|
||||
override val listUrl = "/tim-kiem"
|
||||
override val selectState = "div.info-item:contains(Status) span.info-value "
|
||||
|
||||
@@ -8,7 +8,7 @@ import org.koitharu.kotatsu.parsers.util.*
|
||||
|
||||
@MangaSourceParser("OLIMPOSCANS", "OlimpoScans", "es")
|
||||
internal class OlimpoScans(context: MangaLoaderContext) :
|
||||
FmreaderParser(context, MangaSource.OLIMPOSCANS, "olimposcans.com") {
|
||||
FmreaderParser(context, MangaParserSource.OLIMPOSCANS, "leerolimpo.com") {
|
||||
|
||||
override val selectState = "ul.manga-info li:contains(Estado) a"
|
||||
override val selectAlt = "ul.manga-info li:contains(Otros nombres)"
|
||||
|
||||
@@ -11,7 +11,7 @@ import java.text.SimpleDateFormat
|
||||
|
||||
@MangaSourceParser("KLZ9", "Klz9", "ja")
|
||||
internal class Klz9(context: MangaLoaderContext) :
|
||||
FmreaderParser(context, MangaSource.KLZ9, "klz9.com") {
|
||||
FmreaderParser(context, MangaParserSource.KLZ9, "klz9.com") {
|
||||
|
||||
override val selectDesc = "div.row:contains(Description)"
|
||||
override val selectState = "ul.manga-info li:contains(Status) a"
|
||||
|
||||
@@ -5,14 +5,14 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.fmreader.FmreaderParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
@MangaSourceParser("WELOVEMANGA", "WeLoveManga", "ja")
|
||||
internal class WeLoveManga(context: MangaLoaderContext) :
|
||||
FmreaderParser(context, MangaSource.WELOVEMANGA, "welovemanga.one") {
|
||||
FmreaderParser(context, MangaParserSource.WELOVEMANGA, "welovemanga.one") {
|
||||
|
||||
override suspend fun getChapters(doc: Document): List<MangaChapter> {
|
||||
val mid = doc.selectFirstOrThrow("div.cmt input").attr("value")
|
||||
|
||||
@@ -2,9 +2,9 @@ package org.koitharu.kotatsu.parsers.site.fmreader.ja
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.fmreader.FmreaderParser
|
||||
|
||||
@MangaSourceParser("WELOMA", "Weloma", "ja")
|
||||
internal class Weloma(context: MangaLoaderContext) :
|
||||
FmreaderParser(context, MangaSource.WELOMA, "weloma.art")
|
||||
FmreaderParser(context, MangaParserSource.WELOMA, "weloma.art")
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.*
|
||||
|
||||
internal abstract class FoolSlideParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaSource,
|
||||
source: MangaParserSource,
|
||||
domain: String,
|
||||
pageSize: Int = 25,
|
||||
) : PagedMangaParser(context, source, pageSize) {
|
||||
|
||||
@@ -2,9 +2,9 @@ package org.koitharu.kotatsu.parsers.site.foolslide.en
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||
|
||||
@MangaSourceParser("DEATHTOLLSCANS", "DeathTollScans", "en")
|
||||
internal class Deathtollscans(context: MangaLoaderContext) :
|
||||
FoolSlideParser(context, MangaSource.DEATHTOLLSCANS, "reader.deathtollscans.net", 26)
|
||||
FoolSlideParser(context, MangaParserSource.DEATHTOLLSCANS, "reader.deathtollscans.net", 26)
|
||||
|
||||
@@ -2,11 +2,11 @@ package org.koitharu.kotatsu.parsers.site.foolslide.en
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||
|
||||
@MangaSourceParser("MANGATELLERS", "Mangatellers", "en")
|
||||
internal class Mangatellers(context: MangaLoaderContext) :
|
||||
FoolSlideParser(context, MangaSource.MANGATELLERS, "reader.mangatellers.gr") {
|
||||
FoolSlideParser(context, MangaParserSource.MANGATELLERS, "reader.mangatellers.gr") {
|
||||
override val pagination = false
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package org.koitharu.kotatsu.parsers.site.foolslide.en
|
||||
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("READER_EVILFLOWERS", "Evil Flowers", "en")
|
||||
internal class ReaderEvilflowers(context: MangaLoaderContext) :
|
||||
FoolSlideParser(context, MangaSource.READER_EVILFLOWERS, "reader.evilflowers.com")
|
||||
FoolSlideParser(context, MangaParserSource.READER_EVILFLOWERS, "reader.evilflowers.com")
|
||||
|
||||
@@ -3,13 +3,14 @@ package org.koitharu.kotatsu.parsers.site.foolslide.en
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
|
||||
@MangaSourceParser("SEINAGI", "Seinagi", "en")
|
||||
internal class Seinagi(context: MangaLoaderContext) :
|
||||
FoolSlideParser(context, MangaSource.SEINAGI, "reader.seinagi.org.es") {
|
||||
FoolSlideParser(context, MangaParserSource.SEINAGI, "reader.seinagi.org.es") {
|
||||
|
||||
override val pagination = false
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@ package org.koitharu.kotatsu.parsers.site.foolslide.es
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||
|
||||
@MangaSourceParser("MENUDO_FANSUB", "Menudo Fansub", "es")
|
||||
internal class MenudoFansub(context: MangaLoaderContext) :
|
||||
FoolSlideParser(context, MangaSource.MENUDO_FANSUB, "www.menudo-fansub.com", 25) {
|
||||
FoolSlideParser(context, MangaParserSource.MENUDO_FANSUB, "www.menudo-fansub.com", 25) {
|
||||
override val searchUrl = "slide/search/"
|
||||
override val listUrl = "slide/directory/"
|
||||
}
|
||||
|
||||
@@ -3,13 +3,15 @@ package org.koitharu.kotatsu.parsers.site.foolslide.es
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
|
||||
@MangaSourceParser("PZYKOSIS666HFANSUB", "Pzykosis666h Fansub", "es", ContentType.HENTAI)
|
||||
internal class Pzykosis666hFansub(context: MangaLoaderContext) :
|
||||
FoolSlideParser(context, MangaSource.PZYKOSIS666HFANSUB, "lector.pzykosis666hfansub.com") {
|
||||
FoolSlideParser(context, MangaParserSource.PZYKOSIS666HFANSUB, "lector.pzykosis666hfansub.com") {
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
||||
|
||||
@@ -5,17 +5,13 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||
import org.koitharu.kotatsu.parsers.util.domain
|
||||
import org.koitharu.kotatsu.parsers.util.parseHtml
|
||||
import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
|
||||
import org.koitharu.kotatsu.parsers.util.src
|
||||
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
|
||||
@MangaSourceParser("SEINAGIADULTO", "Seinagi Adulto", "es", ContentType.HENTAI)
|
||||
internal class SeinagiAdulto(context: MangaLoaderContext) :
|
||||
FoolSlideParser(context, MangaSource.SEINAGIADULTO, "adulto.seinagi.org.es") {
|
||||
FoolSlideParser(context, MangaParserSource.SEINAGIADULTO, "adulto.seinagi.org.es") {
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
||||
|
||||
@@ -2,13 +2,14 @@ package org.koitharu.kotatsu.parsers.site.foolslide.fr
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||
|
||||
// The source has changed template so for the moment it is dead.
|
||||
@MangaSourceParser("HNISCANTRAD", "HniScantrad", "fr")
|
||||
internal class HniScantrad(context: MangaLoaderContext) :
|
||||
FoolSlideParser(context, MangaSource.HNISCANTRAD, "hni-scantrad.net") {
|
||||
FoolSlideParser(context, MangaParserSource.HNISCANTRAD, "hni-scantrad.net") {
|
||||
|
||||
override val pagination = false
|
||||
override val searchUrl = "lel/search/"
|
||||
override val listUrl = "lel/directory/"
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package org.koitharu.kotatsu.parsers.site.foolslide.it
|
||||
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("POWERMANGA", "PowerManga", "it")
|
||||
internal class PowerManga(context: MangaLoaderContext) :
|
||||
FoolSlideParser(context, MangaSource.POWERMANGA, "reader.powermanga.org") {
|
||||
FoolSlideParser(context, MangaParserSource.POWERMANGA, "reader.powermanga.org") {
|
||||
override val pagination = false
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package org.koitharu.kotatsu.parsers.site.foolslide.it
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||
|
||||
@MangaSourceParser("RAMAREADER", "RamaReader", "it")
|
||||
internal class Ramareader(context: MangaLoaderContext) :
|
||||
FoolSlideParser(context, MangaSource.RAMAREADER, "www.ramareader.it") {
|
||||
FoolSlideParser(context, MangaParserSource.RAMAREADER, "www.ramareader.it") {
|
||||
override val searchUrl = "read/search/"
|
||||
override val listUrl = "read/directory/"
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package org.koitharu.kotatsu.parsers.site.foolslide.it
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||
|
||||
@MangaSourceParser("READNIFTEAM", "ReadNifTeam", "it")
|
||||
internal class ReadNifteam(context: MangaLoaderContext) :
|
||||
FoolSlideParser(context, MangaSource.READNIFTEAM, "read-nifteam.info") {
|
||||
FoolSlideParser(context, MangaParserSource.READNIFTEAM, "read-nifteam.info") {
|
||||
override val searchUrl = "slide/search/"
|
||||
override val listUrl = "slide/directory/"
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ import java.util.*
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("BENTOMANGA", "BentoManga", "fr")
|
||||
internal class BentomangaParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.BENTOMANGA, 10) {
|
||||
internal class BentomangaParser(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.BENTOMANGA, 10) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.UPDATED,
|
||||
@@ -228,7 +229,8 @@ internal class BentomangaParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
MangaChapter(
|
||||
id = generateUid(href),
|
||||
name = if (name != null && name != title) "$title: $name" else title,
|
||||
number = href.substringAfterLast('/').toIntOrNull() ?: 0,
|
||||
number = href.substringAfterLast('/').toFloatOrNull() ?: 0f,
|
||||
volume = 0,
|
||||
url = href,
|
||||
scanlator = div.selectFirst(".team_link-name")?.textOrNull(),
|
||||
uploadDate = div.selectFirst(".component-chapter-date")
|
||||
|
||||
@@ -17,7 +17,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("FURYOSOCIETY", "FuryoSociety", "fr")
|
||||
internal class FuryoSociety(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.FURYOSOCIETY, 0) {
|
||||
PagedMangaParser(context, MangaParserSource.FURYOSOCIETY, 0) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL, SortOrder.UPDATED)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("LEGACY_SCANS", "LegacyScans", "fr")
|
||||
internal class LegacyScansParser(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.LEGACY_SCANS, 18) {
|
||||
PagedMangaParser(context, MangaParserSource.LEGACY_SCANS, 18) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY)
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.koitharu.kotatsu.parsers.site.fr
|
||||
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
@@ -11,7 +10,7 @@ import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("LIRESCAN", "LireScan", "fr")
|
||||
internal class LireScan(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.LIRESCAN, 20) {
|
||||
internal class LireScan(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.LIRESCAN, 20) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("LUGNICASCANS", "LugnicaScans", "fr")
|
||||
internal class LugnicaScans(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.LUGNICASCANS, 10) {
|
||||
internal class LugnicaScans(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.LUGNICASCANS, 10) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.ALPHABETICAL,
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
package org.koitharu.kotatsu.parsers.site.fr
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import org.json.JSONArray
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
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.getIntOrDefault
|
||||
import org.koitharu.kotatsu.parsers.util.json.mapJSON
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("MANGAMANA", "MangaMana", "fr")
|
||||
internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAMANA, 25) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.RATING,
|
||||
SortOrder.ALPHABETICAL,
|
||||
SortOrder.ALPHABETICAL_DESC,
|
||||
SortOrder.NEWEST,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> =
|
||||
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("www.manga-mana.com")
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val postData = buildString {
|
||||
append("page=")
|
||||
append(page)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
val domainCdn = "cdn" + domain.removePrefix("www")
|
||||
val json = webClient.httpGet("https://$domain/search-live?q=${filter.query}").parseJsonArray()
|
||||
return json.mapJSON { jo ->
|
||||
val slug = jo.getString("slug") ?: throw Exception("Missing Slug")
|
||||
val url = "https://$domain/m/$slug"
|
||||
val img = "https://$domainCdn/uploads/manga/$slug/cover/cover_thumb.jpg"
|
||||
Manga(
|
||||
id = generateUid(url),
|
||||
title = jo.getString("name").orEmpty(),
|
||||
coverUrl = img,
|
||||
altTitle = jo.getString("otherNames").orEmpty(),
|
||||
author = null,
|
||||
isNsfw = when (jo.getIntOrDefault("caution", 0)) {
|
||||
0 -> false
|
||||
2 -> true
|
||||
else -> false
|
||||
},
|
||||
rating = RATING_UNKNOWN,
|
||||
url = url,
|
||||
description = jo.getString("summary_old").orEmpty(),
|
||||
publicUrl = url,
|
||||
tags = emptySet(),
|
||||
state = when (jo.getIntOrDefault("status_id_fr", 4)) {
|
||||
1 -> MangaState.ONGOING
|
||||
2 -> MangaState.FINISHED
|
||||
3 -> MangaState.ABANDONED
|
||||
else -> null
|
||||
},
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
if (filter.sortOrder == SortOrder.UPDATED) {
|
||||
|
||||
if (filter.tags.isNotEmpty() or filter.states.isNotEmpty()) {
|
||||
throw IllegalArgumentException("Le filtrage par « tri par : mis à jour » avec les genres ou les statuts n'est pas pris en charge par cette source.")
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet("https://$domain/?page=$page").parseHtml()
|
||||
return doc.select("div.row div.col_home").map { div ->
|
||||
val href = div.selectFirstOrThrow("h4 a").attrAsRelativeUrl("href")
|
||||
val isNsfw = div.selectFirst("img[data-adult]")?.attr("data-adult")?.isNotEmpty() ?: false
|
||||
val img = if (isNsfw) {
|
||||
div.selectFirst("img")?.attr("data-adult")
|
||||
} else {
|
||||
div.selectFirst("img")?.attr("data-src")?.replace(" ", "")
|
||||
}
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
title = div.select("h4").text(),
|
||||
altTitle = null,
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = isNsfw,
|
||||
coverUrl = img.orEmpty(),
|
||||
description = null,
|
||||
tags = emptySet(),
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("&category=")
|
||||
append(it.key)
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "1"
|
||||
MangaState.FINISHED -> "2"
|
||||
MangaState.ABANDONED -> "3"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
append("&sort_by=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.RATING -> append("score&sort_dir=desc")
|
||||
SortOrder.NEWEST -> append("updated_at&sort_dir=desc")
|
||||
SortOrder.ALPHABETICAL -> append("name&sort_dir=asc")
|
||||
SortOrder.ALPHABETICAL_DESC -> append("name&sort_dir=desc")
|
||||
else -> append("updated_at&sort_dir=desc")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("&sort_by=updated_at&sort_dir=desc")
|
||||
}
|
||||
}
|
||||
|
||||
val url = "https://$domain/liste-mangas"
|
||||
val token = webClient.httpGet(url).parseHtml().selectFirstOrThrow("meta[name=csrf-token]").attr("content")
|
||||
val headers = Headers.Builder().add("X-CSRF-TOKEN", token).add("X-Requested-With", "XMLHttpRequest")
|
||||
.add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8").build()
|
||||
val doc = makeRequest(url, postData.toRequestBody(), headers)
|
||||
|
||||
return doc.select("div.p-2 div.col").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
val isNsfw = div.selectFirst("img[data-adult]")?.attr("data-adult")?.isNotEmpty() ?: false
|
||||
val img = if (isNsfw) {
|
||||
div.selectFirst("img")?.attr("data-adult")
|
||||
} else {
|
||||
div.selectFirst("img")?.attr("data-src")?.replace(" ", "")
|
||||
}
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
title = div.select("h2.fs-6").text(),
|
||||
altTitle = doc.selectFirst(".mangalist_item_othernames")?.text().orEmpty(),
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
rating = div.getElementById("avgrating")?.ownText()?.toFloat()?.div(5f) ?: RATING_UNKNOWN,
|
||||
isNsfw = isNsfw,
|
||||
coverUrl = img.orEmpty(),
|
||||
description = div.selectFirst(".mangalist_item_description")?.text().orEmpty(),
|
||||
tags = div.select("div.mb-1 a").mapNotNullToSet {
|
||||
val key = it.attr("href").substringAfterLast('=')
|
||||
MangaTag(
|
||||
key = key,
|
||||
title = it.text(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private suspend fun makeRequest(url: String, payload: RequestBody, headers: Headers): Document {
|
||||
var retryCount = 0
|
||||
val backoffDelay = 2000L // Initial delay (milliseconds)
|
||||
val request = Request.Builder().url(url).post(payload).headers(headers).build()
|
||||
while (true) {
|
||||
try {
|
||||
return Jsoup.parse(context.httpClient.newCall(request).execute().parseJson().getString("html"))
|
||||
|
||||
} catch (e: Exception) {
|
||||
// Log or handle the exception as needed
|
||||
if (++retryCount <= 5) {
|
||||
withContext(Dispatchers.Default) {
|
||||
delay(backoffDelay)
|
||||
}
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val mangaUrl = manga.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(mangaUrl).parseHtml()
|
||||
val maxPageChapterSelect = doc.select("ul.pagination a.page-link")
|
||||
var maxPageChapter = 1
|
||||
if (!maxPageChapterSelect.isNullOrEmpty()) {
|
||||
maxPageChapterSelect.map {
|
||||
val i = it.attr("href").substringAfterLast("=").toInt()
|
||||
if (i > maxPageChapter) {
|
||||
maxPageChapter = i
|
||||
}
|
||||
}
|
||||
}
|
||||
manga.copy(
|
||||
state = when (doc.select("div.show_details div.d-flex:contains(Statut) span").text()) {
|
||||
"En Cours" -> MangaState.ONGOING
|
||||
"Terminé" -> MangaState.FINISHED
|
||||
"Abandonné" -> MangaState.ABANDONED
|
||||
else -> null
|
||||
},
|
||||
author = doc.selectFirst("div.show_details span[itemprop=author]")?.text().orEmpty(),
|
||||
description = doc.selectFirst("dd[itemprop=description]")?.text(),
|
||||
rating = doc.getElementById("avgrating")?.ownText()?.toFloat()?.div(5f) ?: RATING_UNKNOWN,
|
||||
tags = doc.select("ul.list-unstyled li a.category").mapNotNullToSet {
|
||||
val key = it.attr("href").substringAfterLast('=')
|
||||
MangaTag(
|
||||
key = key,
|
||||
title = it.text(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
chapters = run {
|
||||
if (maxPageChapter == 1) {
|
||||
parseChapters(doc)
|
||||
} else {
|
||||
coroutineScope {
|
||||
val result = ArrayList(parseChapters(doc))
|
||||
result.ensureCapacity(result.size * maxPageChapter)
|
||||
(2..maxPageChapter).map { i ->
|
||||
async {
|
||||
loadChapters(mangaUrl, i)
|
||||
}
|
||||
}.awaitAll()
|
||||
.flattenTo(result)
|
||||
result
|
||||
}
|
||||
}
|
||||
}.reversed(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private suspend fun loadChapters(baseUrl: String, page: Int): List<MangaChapter> {
|
||||
return parseChapters(webClient.httpGet("$baseUrl?page=$page").parseHtml().body())
|
||||
}
|
||||
|
||||
private val dateFormat = SimpleDateFormat("d MMM yyyy", sourceLocale)
|
||||
|
||||
private fun parseChapters(doc: Element): List<MangaChapter> {
|
||||
return doc.select("ul.list-unstyled li a.chapter_link")
|
||||
.mapChapters { i, a ->
|
||||
val href = a.attrAsRelativeUrl("href")
|
||||
val name = a.selectFirst(".chapter div")?.html()?.substringBefore("<") ?: "Chapitre $i"
|
||||
val dateText = a.selectFirst(".small")?.text()
|
||||
val chapterN = href.substringAfterLast('/').replace("-", ".").replace("[^0-9.]".toRegex(), "").toFloat()
|
||||
MangaChapter(
|
||||
id = generateUid(href),
|
||||
name = name,
|
||||
number = chapterN,
|
||||
volume = 0,
|
||||
url = href,
|
||||
scanlator = null,
|
||||
uploadDate = dateFormat.tryParse(dateText),
|
||||
branch = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val chapterUrl = chapter.url.toAbsoluteUrl(domain)
|
||||
val docs = webClient.httpGet(chapterUrl).parseHtml()
|
||||
|
||||
val cdn = docs.selectFirstOrThrow("script:containsData(var cdn = )").data().substringAfterLast("var cdn = \"")
|
||||
.substringBefore('"')
|
||||
val domainCdn = cdn + domain.removePrefix("www")
|
||||
val slugManga = chapterUrl.substringAfter("/m/").substringBeforeLast('/')
|
||||
val slugChapter = chapterUrl.substringAfterLast('/')
|
||||
|
||||
val script = docs.selectFirstOrThrow("script:containsData(var pages =)")
|
||||
val json = JSONArray(script.data().substringAfter("pages = ").substringBefore("; var next_chapter"))
|
||||
val pages = ArrayList<MangaPage>(json.length())
|
||||
for (i in 0 until json.length()) {
|
||||
val img = json.getJSONObject(i).getString("image")
|
||||
val v = json.getJSONObject(i).getInt("version")
|
||||
val url = "https://$domainCdn/uploads/manga/$slugManga/chapters_fr/$slugChapter/$img?$v"
|
||||
pages.add(
|
||||
MangaPage(
|
||||
id = generateUid(url),
|
||||
url = url,
|
||||
preview = null,
|
||||
source = source,
|
||||
),
|
||||
)
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/liste-mangas").parseHtml()
|
||||
return doc.select("select.selectpicker option").drop(1).mapNotNullToSet {
|
||||
MangaTag(
|
||||
key = it.attr("value"),
|
||||
title = it.text(),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("SCANS_MANGAS_ME", "ScansMangas.me", "fr")
|
||||
internal class ScansMangasMe(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.SCANS_MANGAS_ME, 0) {
|
||||
PagedMangaParser(context, MangaParserSource.SCANS_MANGAS_ME, 0) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.ALPHABETICAL,
|
||||
@@ -132,7 +132,7 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
private fun getChapters(doc: Document): List<MangaChapter> {
|
||||
return doc.body().requireElementById("chapter_list").select("li").mapChapters(reversed = true) { i, li ->
|
||||
return doc.body().requireElementById("chapter_list").select("li").mapChapters(reversed = true) { _, li ->
|
||||
val a = li.selectFirstOrThrow("a")
|
||||
val href = a.attrAsRelativeUrl("href")
|
||||
MangaChapter(
|
||||
|
||||
@@ -13,7 +13,8 @@ import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("SCANTRADUNION", "ScantradUnion", "fr")
|
||||
internal class ScantradUnion(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.SCANTRADUNION, 10) {
|
||||
internal class ScantradUnion(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.SCANTRADUNION, 10) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.ALPHABETICAL,
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.*
|
||||
|
||||
internal abstract class GalleryAdultsParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaSource,
|
||||
source: MangaParserSource,
|
||||
domain: String,
|
||||
pageSize: Int = 20,
|
||||
) : PagedMangaParser(context, source, pageSize) {
|
||||
@@ -158,7 +158,8 @@ internal abstract class GalleryAdultsParser(
|
||||
MangaChapter(
|
||||
id = manga.id,
|
||||
name = manga.title,
|
||||
number = 1,
|
||||
number = 1f,
|
||||
volume = 0,
|
||||
url = urlChapters,
|
||||
scanlator = null,
|
||||
uploadDate = 0,
|
||||
|
||||
@@ -4,7 +4,7 @@ import org.jsoup.nodes.Element
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
@@ -13,7 +13,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("ASMHENTAI", "AsmHentai", type = ContentType.HENTAI)
|
||||
internal class AsmHentai(context: MangaLoaderContext) :
|
||||
GalleryAdultsParser(context, MangaSource.ASMHENTAI, "asmhentai.com") {
|
||||
GalleryAdultsParser(context, MangaParserSource.ASMHENTAI, "asmhentai.com") {
|
||||
override val selectGallery = ".preview_item"
|
||||
override val selectGalleryLink = ".image a"
|
||||
override val selectGalleryImg = ".image img"
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("DOUJINDESUUK", "DoujinDesu.uk", type = ContentType.HENTAI)
|
||||
internal class DoujinDesuUk(context: MangaLoaderContext) :
|
||||
GalleryAdultsParser(context, MangaSource.DOUJINDESUUK, "doujindesu.uk", 25) {
|
||||
GalleryAdultsParser(context, MangaParserSource.DOUJINDESUUK, "doujindesu.uk", 25) {
|
||||
override val selectGallery = ".gallery"
|
||||
override val selectGalleryLink = "a"
|
||||
override val selectGalleryTitle = ".caption"
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("HENTAI3", "3Hentai", type = ContentType.HENTAI)
|
||||
internal class Hentai3(context: MangaLoaderContext) :
|
||||
GalleryAdultsParser(context, MangaSource.HENTAI3, "3hentai.net", pageSize = 30) {
|
||||
GalleryAdultsParser(context, MangaParserSource.HENTAI3, "3hentai.net", pageSize = 30) {
|
||||
|
||||
override val selectGallery = ".doujin "
|
||||
override val selectGalleryLink = "a"
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("HENTAIENVY", "HentaiEnvy", type = ContentType.HENTAI)
|
||||
internal class HentaiEnvy(context: MangaLoaderContext) :
|
||||
GalleryAdultsParser(context, MangaSource.HENTAIENVY, "hentaienvy.com", pageSize = 24) {
|
||||
GalleryAdultsParser(context, MangaParserSource.HENTAIENVY, "hentaienvy.com", pageSize = 24) {
|
||||
override val selectGalleryLink = "a"
|
||||
override val selectGalleryTitle = "div.title"
|
||||
override val selectTags = ".tags_items"
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("HENTAIERA", "HentaiEra", type = ContentType.HENTAI)
|
||||
internal class HentaiEra(context: MangaLoaderContext) :
|
||||
GalleryAdultsParser(context, MangaSource.HENTAIERA, "hentaiera.com", 25) {
|
||||
GalleryAdultsParser(context, MangaParserSource.HENTAIERA, "hentaiera.com", 25) {
|
||||
override val selectTags = ".tags_section"
|
||||
override val selectTag = ".galleries_info li:contains(Tags) div.info_tags"
|
||||
override val selectAuthor = ".galleries_info li:contains(Artists) span.item_name"
|
||||
@@ -131,7 +131,8 @@ internal class HentaiEra(context: MangaLoaderContext) :
|
||||
MangaChapter(
|
||||
id = manga.id,
|
||||
name = manga.title,
|
||||
number = 1,
|
||||
number = 1f,
|
||||
volume = 0,
|
||||
url = urlChapters,
|
||||
scanlator = null,
|
||||
uploadDate = 0,
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("HENTAIFORCE", "HentaiForce", type = ContentType.HENTAI)
|
||||
internal class HentaiForce(context: MangaLoaderContext) :
|
||||
GalleryAdultsParser(context, MangaSource.HENTAIFORCE, "hentaiforce.net", pageSize = 30) {
|
||||
GalleryAdultsParser(context, MangaParserSource.HENTAIFORCE, "hentaiforce.net", pageSize = 30) {
|
||||
override val selectGallery = ".gallery"
|
||||
override val selectGalleryLink = "a.gallery-thumb"
|
||||
override val pathTagUrl = "/tags/popular/"
|
||||
|
||||
@@ -11,7 +11,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("HENTAIFOX", "HentaiFox", type = ContentType.HENTAI)
|
||||
internal class HentaiFox(context: MangaLoaderContext) :
|
||||
GalleryAdultsParser(context, MangaSource.HENTAIFOX, "hentaifox.com") {
|
||||
GalleryAdultsParser(context, MangaParserSource.HENTAIFOX, "hentaifox.com") {
|
||||
override val selectGallery = ".lc_galleries .thumb, .related_galleries .thumb"
|
||||
override val pathTagUrl = "/tags/popular/pag/"
|
||||
override val selectTags = ".list_tags"
|
||||
|
||||
@@ -4,7 +4,7 @@ import org.jsoup.nodes.Element
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
@@ -13,7 +13,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("HENTAIROX", "HentaiRox", type = ContentType.HENTAI)
|
||||
internal class HentaiRox(context: MangaLoaderContext) :
|
||||
GalleryAdultsParser(context, MangaSource.HENTAIROX, "hentairox.com") {
|
||||
GalleryAdultsParser(context, MangaParserSource.HENTAIROX, "hentairox.com") {
|
||||
override val selectGalleryImg = ".inner_thumb img"
|
||||
override val selectTags = ".gtags"
|
||||
override val selectTag = "li:contains(Tags:)"
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("NHENTAI", "NHentai.net", type = ContentType.HENTAI)
|
||||
internal class NHentaiParser(context: MangaLoaderContext) :
|
||||
GalleryAdultsParser(context, MangaSource.NHENTAI, "nhentai.net", 25) {
|
||||
GalleryAdultsParser(context, MangaParserSource.NHENTAI, "nhentai.net", 25) {
|
||||
override val selectGallery = "div.index-container:not(.index-popular) .gallery, #related-container .gallery"
|
||||
override val selectGalleryLink = "a"
|
||||
override val selectGalleryTitle = ".caption"
|
||||
|
||||
@@ -11,7 +11,7 @@ import java.util.*
|
||||
|
||||
internal abstract class GattsuParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaSource,
|
||||
source: MangaParserSource,
|
||||
domain: String,
|
||||
pageSize: Int = 20,
|
||||
) : PagedMangaParser(context, source, pageSize) {
|
||||
@@ -115,7 +115,8 @@ internal abstract class GattsuParser(
|
||||
MangaChapter(
|
||||
id = manga.id,
|
||||
name = manga.title,
|
||||
number = 1,
|
||||
number = 1f,
|
||||
volume = 0,
|
||||
url = urlChapter,
|
||||
scanlator = null,
|
||||
uploadDate = 0,
|
||||
|
||||
@@ -3,9 +3,9 @@ package org.koitharu.kotatsu.parsers.site.gattsu.pt
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.gattsu.GattsuParser
|
||||
|
||||
@MangaSourceParser("HENTAISEASON", "HentaiSeason", "pt", ContentType.HENTAI)
|
||||
internal class HentaiSeason(context: MangaLoaderContext) :
|
||||
GattsuParser(context, MangaSource.HENTAISEASON, "hentaiseason.com")
|
||||
GattsuParser(context, MangaParserSource.HENTAISEASON, "hentaiseason.com")
|
||||
|
||||
@@ -3,11 +3,11 @@ package org.koitharu.kotatsu.parsers.site.gattsu.pt
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.gattsu.GattsuParser
|
||||
|
||||
@MangaSourceParser("HENTAITOKYO", "HentaiTokyo", "pt", ContentType.HENTAI)
|
||||
internal class HentaiTokyo(context: MangaLoaderContext) :
|
||||
GattsuParser(context, MangaSource.HENTAITOKYO, "hentaitokyo.net") {
|
||||
GattsuParser(context, MangaParserSource.HENTAITOKYO, "hentaitokyo.net") {
|
||||
override val tagUrl = "tags"
|
||||
}
|
||||
|
||||
@@ -5,18 +5,14 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
|
||||
import org.koitharu.kotatsu.parsers.site.gattsu.GattsuParser
|
||||
import org.koitharu.kotatsu.parsers.util.attrAsAbsoluteUrl
|
||||
import org.koitharu.kotatsu.parsers.util.domain
|
||||
import org.koitharu.kotatsu.parsers.util.generateUid
|
||||
import org.koitharu.kotatsu.parsers.util.selectLastOrThrow
|
||||
import org.koitharu.kotatsu.parsers.util.src
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
|
||||
@MangaSourceParser("MUNDOHENTAIOFICIAL", "MundoHentaiOficial", "pt", ContentType.HENTAI)
|
||||
internal class MundoHentaiOficial(context: MangaLoaderContext) :
|
||||
GattsuParser(context, MangaSource.MUNDOHENTAIOFICIAL, "mundohentaioficial.com") {
|
||||
GattsuParser(context, MangaParserSource.MUNDOHENTAIOFICIAL, "mundohentaioficial.com") {
|
||||
|
||||
override val tagUrl = "tags"
|
||||
|
||||
|
||||
@@ -3,17 +3,13 @@ package org.koitharu.kotatsu.parsers.site.gattsu.pt
|
||||
import org.jsoup.nodes.Element
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.site.gattsu.GattsuParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
|
||||
@MangaSourceParser("UNIVERSOHENTAI", "UniversoHentai", "pt", ContentType.HENTAI)
|
||||
internal class UniversoHentai(context: MangaLoaderContext) :
|
||||
GattsuParser(context, MangaSource.UNIVERSOHENTAI, "universohentai.com") {
|
||||
GattsuParser(context, MangaParserSource.UNIVERSOHENTAI, "universohentai.com") {
|
||||
|
||||
override val tagPrefix = "category"
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.*
|
||||
|
||||
internal abstract class GuyaParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaSource,
|
||||
source: MangaParserSource,
|
||||
domain: String,
|
||||
pageSize: Int = 0,
|
||||
) : PagedMangaParser(context, source, pageSize) {
|
||||
@@ -91,7 +91,8 @@ internal abstract class GuyaParser(
|
||||
MangaChapter(
|
||||
id = generateUid(url),
|
||||
name = chapter.getString("title"),
|
||||
number = i,
|
||||
number = i.toFloat(),
|
||||
volume = 0,
|
||||
url = url,
|
||||
scanlator = null,
|
||||
uploadDate = 0,
|
||||
|
||||
@@ -2,9 +2,9 @@ package org.koitharu.kotatsu.parsers.site.guya.all
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.guya.GuyaParser
|
||||
|
||||
@MangaSourceParser("MAHOUSHOUJOBU", "MahouShoujobu")
|
||||
internal class MahouShoujobu(context: MangaLoaderContext) :
|
||||
GuyaParser(context, MangaSource.MAHOUSHOUJOBU, "mahoushoujobu.com")
|
||||
GuyaParser(context, MangaParserSource.MAHOUSHOUJOBU, "mahoushoujobu.com")
|
||||
|
||||
@@ -2,9 +2,9 @@ package org.koitharu.kotatsu.parsers.site.guya.en
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.guya.GuyaParser
|
||||
|
||||
@MangaSourceParser("DANKE", "DankeFursLesen", "en")
|
||||
internal class Danke(context: MangaLoaderContext) :
|
||||
GuyaParser(context, MangaSource.DANKE, "danke.moe")
|
||||
GuyaParser(context, MangaParserSource.DANKE, "danke.moe")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user