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
|
3. Usage in code
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
val parser = mangaLoaderContext.newParserInstance(MangaSource.MANGADEX)
|
val parser = mangaLoaderContext.newParserInstance(MangaParserSourceMANGADEX)
|
||||||
```
|
```
|
||||||
|
|
||||||
`mangaLoaderContext` is an implementation of the `MangaLoaderContext` class.
|
`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)
|
and [Non-Android](https://github.com/KotatsuApp/kotatsu-dl/blob/master/src/jvmMain/kotlin/org/koitharu/kotatsu_dl/logic/MangaLoaderContextImpl.kt)
|
||||||
implementation.
|
implementation.
|
||||||
|
|
||||||
Note that the `MangaSource.LOCAL` and `MangaSource.DUMMY` parsers cannot be instantiated.
|
Note that the `MangaParserSourceLOCAL` and `MangaParserSourceDUMMY` parsers cannot be instantiated.
|
||||||
|
|
||||||
## Contribution
|
## Contribution
|
||||||
|
|
||||||
|
|||||||
10
build.gradle
10
build.gradle
@@ -2,8 +2,8 @@ import tasks.ReportGenerateTask
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'java-library'
|
id 'java-library'
|
||||||
id 'org.jetbrains.kotlin.jvm' version '1.9.23'
|
id 'org.jetbrains.kotlin.jvm' version '2.0.10-RC'
|
||||||
id 'com.google.devtools.ksp' version '1.9.23-1.0.20'
|
id 'com.google.devtools.ksp' version '2.0.10-RC-1.0.23'
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,12 +53,12 @@ afterEvaluate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
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.okhttp3:okhttp:4.12.0'
|
||||||
implementation 'com.squareup.okio:okio:3.9.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 'org.json:json:20240303'
|
||||||
implementation 'androidx.collection:collection:1.4.0'
|
implementation 'androidx.collection:collection:1.4.1'
|
||||||
|
|
||||||
ksp project(':kotatsu-parsers-ksp')
|
ksp project(':kotatsu-parsers-ksp')
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'org.jetbrains.kotlin.jvm' version '1.9.23'
|
id 'org.jetbrains.kotlin.jvm' version '2.0.10-RC'
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@@ -14,5 +14,5 @@ dependencies {
|
|||||||
implementation gradleApi()
|
implementation gradleApi()
|
||||||
implementation 'org.simpleframework:simple-xml:2.7.1'
|
implementation 'org.simpleframework:simple-xml:2.7.1'
|
||||||
implementation 'com.soywiz.korlibs.korte:korte-jvm:4.0.10'
|
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 {
|
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
|
package org.koitharu.kotatsu.parsers
|
||||||
|
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
internal fun MangaParserSource.newParser(context: MangaLoaderContext): MangaParser = when (this) {
|
||||||
@InternalParsersApi
|
|
||||||
@Deprecated("", replaceWith = ReplaceWith("context.newParserInstance(this)"))
|
|
||||||
fun MangaSource.newParser(context: MangaLoaderContext): MangaParser = when (this) {
|
|
||||||
|
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
@@ -83,14 +80,12 @@ class ParserProcessor(
|
|||||||
"""
|
"""
|
||||||
package org.koitharu.kotatsu.parsers.model
|
package org.koitharu.kotatsu.parsers.model
|
||||||
|
|
||||||
enum class MangaSource(
|
enum class MangaParserSource(
|
||||||
val title: String,
|
val title: String,
|
||||||
val locale: String,
|
val locale: String,
|
||||||
val contentType: ContentType,
|
val contentType: ContentType,
|
||||||
val isBroken: Boolean,
|
val isBroken: Boolean,
|
||||||
) {
|
): MangaSource {
|
||||||
LOCAL("Local", "", ContentType.OTHER, false),
|
|
||||||
UNKNOWN("Unknown", "", ContentType.OTHER, true),
|
|
||||||
|
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
@@ -102,9 +97,7 @@ class ParserProcessor(
|
|||||||
|
|
||||||
factoryWriter?.write(
|
factoryWriter?.write(
|
||||||
"""
|
"""
|
||||||
MangaSource.LOCAL,
|
MangaParserSource.DUMMY -> throw NotImplementedError("Manga parser ${'$'}name cannot be instantiated")
|
||||||
MangaSource.UNKNOWN,
|
|
||||||
MangaSource.DUMMY -> throw NotImplementedError("Manga parser ${'$'}name cannot be instantiated")
|
|
||||||
}.also {
|
}.also {
|
||||||
require(it.source == this) {
|
require(it.source == this) {
|
||||||
"Cannot instantiate manga parser: ${'$'}name mapped to ${'$'}{it.source}"
|
"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")
|
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 =
|
val deprecationString =
|
||||||
if (deprecation != null) {
|
if (deprecation != null) {
|
||||||
val reason =
|
val reason =
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import okhttp3.OkHttpClient
|
|||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.koitharu.kotatsu.parsers.bitmap.Bitmap
|
import org.koitharu.kotatsu.parsers.bitmap.Bitmap
|
||||||
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
|
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@@ -14,8 +15,7 @@ abstract class MangaLoaderContext {
|
|||||||
|
|
||||||
abstract val cookieJar: CookieJar
|
abstract val cookieJar: CookieJar
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
fun newParserInstance(source: MangaParserSource): MangaParser = source.newParser(this)
|
||||||
fun newParserInstance(source: MangaSource): MangaParser = source.newParser(this)
|
|
||||||
|
|
||||||
open fun encodeBase64(data: ByteArray): String = Base64.getEncoder().encodeToString(data)
|
open fun encodeBase64(data: ByteArray): String = Base64.getEncoder().encodeToString(data)
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ abstract class MangaLoaderContext {
|
|||||||
*/
|
*/
|
||||||
abstract fun redrawImageResponse(
|
abstract fun redrawImageResponse(
|
||||||
response: Response,
|
response: Response,
|
||||||
redraw: (image: Bitmap) -> Bitmap
|
redraw: (image: Bitmap) -> Bitmap,
|
||||||
): Response
|
): Response
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,6 +50,6 @@ abstract class MangaLoaderContext {
|
|||||||
*/
|
*/
|
||||||
abstract fun createBitmap(
|
abstract fun createBitmap(
|
||||||
width: Int,
|
width: Int,
|
||||||
height: Int
|
height: Int,
|
||||||
): Bitmap
|
): Bitmap
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import java.util.*
|
|||||||
|
|
||||||
abstract class MangaParser @InternalParsersApi constructor(
|
abstract class MangaParser @InternalParsersApi constructor(
|
||||||
@property:InternalParsersApi val context: MangaLoaderContext,
|
@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) }
|
val config by lazy { context.getConfig(source) }
|
||||||
|
|
||||||
open val sourceLocale: Locale
|
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
|
val isNsfwSource = source.contentType == ContentType.HENTAI
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ abstract class MangaParser @InternalParsersApi constructor(
|
|||||||
return RelatedMangaFinder(listOf(this)).invoke(seed)
|
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
|
this
|
||||||
} else {
|
} else {
|
||||||
context.newParserInstance(source)
|
context.newParserInstance(source)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.util.Paginator
|
|||||||
@InternalParsersApi
|
@InternalParsersApi
|
||||||
abstract class PagedMangaParser(
|
abstract class PagedMangaParser(
|
||||||
context: MangaLoaderContext,
|
context: MangaLoaderContext,
|
||||||
source: MangaSource,
|
source: MangaParserSource,
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) @JvmField internal val pageSize: Int,
|
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) @JvmField internal val pageSize: Int,
|
||||||
searchPageSize: Int = pageSize,
|
searchPageSize: Int = pageSize,
|
||||||
) : MangaParser(context, source) {
|
) : MangaParser(context, source) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.model
|
|||||||
|
|
||||||
class Favicons internal constructor(
|
class Favicons internal constructor(
|
||||||
favicons: Collection<Favicon>,
|
favicons: Collection<Favicon>,
|
||||||
@JvmField val referer: String,
|
@JvmField val referer: String?,
|
||||||
) : Collection<Favicon> {
|
) : Collection<Favicon> {
|
||||||
|
|
||||||
private val icons = favicons.sortedDescending()
|
private val icons = favicons.sortedDescending()
|
||||||
@@ -47,4 +47,12 @@ class Favicons internal constructor(
|
|||||||
}
|
}
|
||||||
return result
|
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")
|
@MangaSourceParser("BATOTO", "Bato.To")
|
||||||
internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
|
internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||||
context = context,
|
context = context,
|
||||||
source = MangaSource.BATOTO,
|
source = MangaParserSource.BATOTO,
|
||||||
pageSize = 60,
|
pageSize = 60,
|
||||||
searchPageSize = 20,
|
searchPageSize = 20,
|
||||||
) {
|
) {
|
||||||
@@ -104,7 +104,12 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
|
|||||||
|
|
||||||
filter.locale?.let {
|
filter.locale?.let {
|
||||||
append("&langs=")
|
append("&langs=")
|
||||||
append(it.language)
|
if (it.language == "in") {
|
||||||
|
append("id")
|
||||||
|
} else {
|
||||||
|
append(it.language)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
append("&genres=")
|
append("&genres=")
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ import java.util.*
|
|||||||
private const val CHAPTERS_LIMIT = 99999
|
private const val CHAPTERS_LIMIT = 99999
|
||||||
|
|
||||||
@MangaSourceParser("COMICK_FUN", "ComicK")
|
@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")
|
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)
|
@MangaSourceParser("EXHENTAI", "ExHentai", type = ContentType.HENTAI)
|
||||||
internal class ExHentaiParser(
|
internal class ExHentaiParser(
|
||||||
context: MangaLoaderContext,
|
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 availableSortOrders: Set<SortOrder> = setOf(SortOrder.NEWEST)
|
||||||
override val isTagsExclusionSupported: Boolean = true
|
override val isTagsExclusionSupported: Boolean = true
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import kotlin.math.min
|
|||||||
|
|
||||||
@OptIn(ExperimentalUnsignedTypes::class)
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
@MangaSourceParser("HITOMILA", "Hitomi.La", type = ContentType.HENTAI)
|
@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")
|
override val configKeyDomain = ConfigKey.Domain("hitomi.la")
|
||||||
|
|
||||||
private val ltnBaseUrl get() = "https://${getDomain("ltn")}"
|
private val ltnBaseUrl get() = "https://${getDomain("ltn")}"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import java.util.*
|
|||||||
|
|
||||||
@MangaSourceParser("IMHENTAI", "ImHentai", type = ContentType.HENTAI)
|
@MangaSourceParser("IMHENTAI", "ImHentai", type = ContentType.HENTAI)
|
||||||
internal class ImHentai(context: MangaLoaderContext) :
|
internal class ImHentai(context: MangaLoaderContext) :
|
||||||
PagedMangaParser(context, MangaSource.IMHENTAI, pageSize = 20) {
|
PagedMangaParser(context, MangaParserSource.IMHENTAI, pageSize = 20) {
|
||||||
|
|
||||||
override val availableSortOrders: Set<SortOrder> =
|
override val availableSortOrders: Set<SortOrder> =
|
||||||
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.RATING)
|
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.RATING)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import javax.crypto.spec.SecretKeySpec
|
|||||||
|
|
||||||
internal abstract class LineWebtoonsParser(
|
internal abstract class LineWebtoonsParser(
|
||||||
context: MangaLoaderContext,
|
context: MangaLoaderContext,
|
||||||
source: MangaSource,
|
source: MangaParserSource,
|
||||||
) : MangaParser(context, source) {
|
) : MangaParser(context, source) {
|
||||||
|
|
||||||
override val isMultipleTagsSupported = false
|
override val isMultipleTagsSupported = false
|
||||||
@@ -308,25 +308,25 @@ internal abstract class LineWebtoonsParser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@MangaSourceParser("LINEWEBTOONS_EN", "LineWebtoons English", "en", type = ContentType.MANGA)
|
@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)
|
@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)
|
@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)
|
@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)
|
@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)
|
@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)
|
@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) {
|
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"
|
private const val LOCALE_FALLBACK = "en"
|
||||||
|
|
||||||
@MangaSourceParser("MANGADEX", "MangaDex")
|
@MangaSourceParser("MANGADEX", "MangaDex")
|
||||||
internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.MANGADEX) {
|
internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.MANGADEX) {
|
||||||
|
|
||||||
override val configKeyDomain = ConfigKey.Domain("mangadex.org")
|
override val configKeyDomain = ConfigKey.Domain("mangadex.org")
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package org.koitharu.kotatsu.parsers.site.all
|
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.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
@@ -22,7 +24,7 @@ private const val MIN_SPLIT_COUNT = 5
|
|||||||
|
|
||||||
internal abstract class MangaFireParser(
|
internal abstract class MangaFireParser(
|
||||||
context: MangaLoaderContext,
|
context: MangaLoaderContext,
|
||||||
source: MangaSource,
|
source: MangaParserSource,
|
||||||
private val siteLang: String,
|
private val siteLang: String,
|
||||||
) : PagedMangaParser(context, source, 30), Interceptor {
|
) : PagedMangaParser(context, source, 30), Interceptor {
|
||||||
|
|
||||||
@@ -425,23 +427,25 @@ internal abstract class MangaFireParser(
|
|||||||
private fun Int.ceilDiv(other: Int) = (this + (other - 1)) / other
|
private fun Int.ceilDiv(other: Int) = (this + (other - 1)) / other
|
||||||
|
|
||||||
@MangaSourceParser("MANGAFIRE_EN", "MangaFire English", "en")
|
@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")
|
@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")
|
@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")
|
@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")
|
@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")
|
@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")
|
@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")
|
@MangaSourceParser("MANGAPARK", "MangaPark")
|
||||||
internal class MangaPark(context: MangaLoaderContext) :
|
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)
|
override val availableSortOrders: Set<SortOrder> = EnumSet.allOf(SortOrder::class.java)
|
||||||
|
|
||||||
@@ -257,7 +257,7 @@ internal class MangaPark(context: MangaLoaderContext) :
|
|||||||
.findAll(script)
|
.findAll(script)
|
||||||
.mapNotNullTo(ArrayList()) {
|
.mapNotNullTo(ArrayList()) {
|
||||||
val url = it.groupValues.getOrNull(1) ?: return@mapNotNullTo null
|
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(
|
MangaPage(
|
||||||
id = generateUid(url),
|
id = generateUid(url),
|
||||||
url = url,
|
url = url,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import java.util.*
|
|||||||
|
|
||||||
internal abstract class MangaPlusParser(
|
internal abstract class MangaPlusParser(
|
||||||
context: MangaLoaderContext,
|
context: MangaLoaderContext,
|
||||||
source: MangaSource,
|
source: MangaParserSource,
|
||||||
private val sourceLang: String,
|
private val sourceLang: String,
|
||||||
) : MangaParser(context, source), Interceptor {
|
) : MangaParser(context, source), Interceptor {
|
||||||
|
|
||||||
@@ -267,63 +267,63 @@ internal abstract class MangaPlusParser(
|
|||||||
@MangaSourceParser("MANGAPLUSPARSER_EN", "MANGA Plus English", "en")
|
@MangaSourceParser("MANGAPLUSPARSER_EN", "MANGA Plus English", "en")
|
||||||
class English(context: MangaLoaderContext) : MangaPlusParser(
|
class English(context: MangaLoaderContext) : MangaPlusParser(
|
||||||
context,
|
context,
|
||||||
MangaSource.MANGAPLUSPARSER_EN,
|
MangaParserSource.MANGAPLUSPARSER_EN,
|
||||||
"ENGLISH",
|
"ENGLISH",
|
||||||
)
|
)
|
||||||
|
|
||||||
@MangaSourceParser("MANGAPLUSPARSER_ES", "MANGA Plus Spanish", "es")
|
@MangaSourceParser("MANGAPLUSPARSER_ES", "MANGA Plus Spanish", "es")
|
||||||
class Spanish(context: MangaLoaderContext) : MangaPlusParser(
|
class Spanish(context: MangaLoaderContext) : MangaPlusParser(
|
||||||
context,
|
context,
|
||||||
MangaSource.MANGAPLUSPARSER_ES,
|
MangaParserSource.MANGAPLUSPARSER_ES,
|
||||||
"SPANISH",
|
"SPANISH",
|
||||||
)
|
)
|
||||||
|
|
||||||
@MangaSourceParser("MANGAPLUSPARSER_FR", "MANGA Plus French", "fr")
|
@MangaSourceParser("MANGAPLUSPARSER_FR", "MANGA Plus French", "fr")
|
||||||
class French(context: MangaLoaderContext) : MangaPlusParser(
|
class French(context: MangaLoaderContext) : MangaPlusParser(
|
||||||
context,
|
context,
|
||||||
MangaSource.MANGAPLUSPARSER_FR,
|
MangaParserSource.MANGAPLUSPARSER_FR,
|
||||||
"FRENCH",
|
"FRENCH",
|
||||||
)
|
)
|
||||||
|
|
||||||
@MangaSourceParser("MANGAPLUSPARSER_ID", "MANGA Plus Indonesian", "id")
|
@MangaSourceParser("MANGAPLUSPARSER_ID", "MANGA Plus Indonesian", "id")
|
||||||
class Indonesian(context: MangaLoaderContext) : MangaPlusParser(
|
class Indonesian(context: MangaLoaderContext) : MangaPlusParser(
|
||||||
context,
|
context,
|
||||||
MangaSource.MANGAPLUSPARSER_ID,
|
MangaParserSource.MANGAPLUSPARSER_ID,
|
||||||
"INDONESIAN",
|
"INDONESIAN",
|
||||||
)
|
)
|
||||||
|
|
||||||
@MangaSourceParser("MANGAPLUSPARSER_PTBR", "MANGA Plus Portuguese (Brazil)", "pt")
|
@MangaSourceParser("MANGAPLUSPARSER_PTBR", "MANGA Plus Portuguese (Brazil)", "pt")
|
||||||
class Portuguese(context: MangaLoaderContext) : MangaPlusParser(
|
class Portuguese(context: MangaLoaderContext) : MangaPlusParser(
|
||||||
context,
|
context,
|
||||||
MangaSource.MANGAPLUSPARSER_PTBR,
|
MangaParserSource.MANGAPLUSPARSER_PTBR,
|
||||||
"PORTUGUESE_BR",
|
"PORTUGUESE_BR",
|
||||||
)
|
)
|
||||||
|
|
||||||
@MangaSourceParser("MANGAPLUSPARSER_RU", "MANGA Plus Russian", "ru")
|
@MangaSourceParser("MANGAPLUSPARSER_RU", "MANGA Plus Russian", "ru")
|
||||||
class Russian(context: MangaLoaderContext) : MangaPlusParser(
|
class Russian(context: MangaLoaderContext) : MangaPlusParser(
|
||||||
context,
|
context,
|
||||||
MangaSource.MANGAPLUSPARSER_RU,
|
MangaParserSource.MANGAPLUSPARSER_RU,
|
||||||
"RUSSIAN",
|
"RUSSIAN",
|
||||||
)
|
)
|
||||||
|
|
||||||
@MangaSourceParser("MANGAPLUSPARSER_TH", "MANGA Plus Thai", "th")
|
@MangaSourceParser("MANGAPLUSPARSER_TH", "MANGA Plus Thai", "th")
|
||||||
class Thai(context: MangaLoaderContext) : MangaPlusParser(
|
class Thai(context: MangaLoaderContext) : MangaPlusParser(
|
||||||
context,
|
context,
|
||||||
MangaSource.MANGAPLUSPARSER_TH,
|
MangaParserSource.MANGAPLUSPARSER_TH,
|
||||||
"THAI",
|
"THAI",
|
||||||
)
|
)
|
||||||
|
|
||||||
@MangaSourceParser("MANGAPLUSPARSER_VI", "MANGA Plus Vietnamese", "vi")
|
@MangaSourceParser("MANGAPLUSPARSER_VI", "MANGA Plus Vietnamese", "vi")
|
||||||
class Vietnamese(context: MangaLoaderContext) : MangaPlusParser(
|
class Vietnamese(context: MangaLoaderContext) : MangaPlusParser(
|
||||||
context,
|
context,
|
||||||
MangaSource.MANGAPLUSPARSER_VI,
|
MangaParserSource.MANGAPLUSPARSER_VI,
|
||||||
"VIETNAMESE",
|
"VIETNAMESE",
|
||||||
)
|
)
|
||||||
|
|
||||||
@MangaSourceParser("MANGAPLUSPARSER_DE", "MANGA Plus German", "de")
|
@MangaSourceParser("MANGAPLUSPARSER_DE", "MANGA Plus German", "de")
|
||||||
class German(context: MangaLoaderContext) : MangaPlusParser(
|
class German(context: MangaLoaderContext) : MangaPlusParser(
|
||||||
context,
|
context,
|
||||||
MangaSource.MANGAPLUSPARSER_DE,
|
MangaParserSource.MANGAPLUSPARSER_DE,
|
||||||
"GERMAN",
|
"GERMAN",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import javax.crypto.spec.SecretKeySpec
|
|||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
@MangaSourceParser("MANGAREADERTO", "MangaReader.To")
|
@MangaSourceParser("MANGAREADERTO", "MangaReader.To")
|
||||||
class MangaReaderToParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.MANGAREADERTO, 16),
|
class MangaReaderToParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAREADERTO, 16),
|
||||||
Interceptor {
|
Interceptor {
|
||||||
|
|
||||||
override val configKeyDomain = ConfigKey.Domain("mangareader.to")
|
override val configKeyDomain = ConfigKey.Domain("mangareader.to")
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import java.util.*
|
|||||||
|
|
||||||
internal abstract class NineMangaParser(
|
internal abstract class NineMangaParser(
|
||||||
context: MangaLoaderContext,
|
context: MangaLoaderContext,
|
||||||
source: MangaSource,
|
source: MangaParserSource,
|
||||||
defaultDomain: String,
|
defaultDomain: String,
|
||||||
) : PagedMangaParser(context, source, pageSize = 26), Interceptor {
|
) : PagedMangaParser(context, source, pageSize = 26), Interceptor {
|
||||||
|
|
||||||
@@ -267,49 +267,49 @@ internal abstract class NineMangaParser(
|
|||||||
@MangaSourceParser("NINEMANGA_EN", "NineManga English", "en")
|
@MangaSourceParser("NINEMANGA_EN", "NineManga English", "en")
|
||||||
class English(context: MangaLoaderContext) : NineMangaParser(
|
class English(context: MangaLoaderContext) : NineMangaParser(
|
||||||
context,
|
context,
|
||||||
MangaSource.NINEMANGA_EN,
|
MangaParserSource.NINEMANGA_EN,
|
||||||
"www.ninemanga.com",
|
"www.ninemanga.com",
|
||||||
)
|
)
|
||||||
|
|
||||||
@MangaSourceParser("NINEMANGA_ES", "NineManga Español", "es")
|
@MangaSourceParser("NINEMANGA_ES", "NineManga Español", "es")
|
||||||
class Spanish(context: MangaLoaderContext) : NineMangaParser(
|
class Spanish(context: MangaLoaderContext) : NineMangaParser(
|
||||||
context,
|
context,
|
||||||
MangaSource.NINEMANGA_ES,
|
MangaParserSource.NINEMANGA_ES,
|
||||||
"es.ninemanga.com",
|
"es.ninemanga.com",
|
||||||
)
|
)
|
||||||
|
|
||||||
@MangaSourceParser("NINEMANGA_RU", "NineManga Русский", "ru")
|
@MangaSourceParser("NINEMANGA_RU", "NineManga Русский", "ru")
|
||||||
class Russian(context: MangaLoaderContext) : NineMangaParser(
|
class Russian(context: MangaLoaderContext) : NineMangaParser(
|
||||||
context,
|
context,
|
||||||
MangaSource.NINEMANGA_RU,
|
MangaParserSource.NINEMANGA_RU,
|
||||||
"ru.ninemanga.com",
|
"ru.ninemanga.com",
|
||||||
)
|
)
|
||||||
|
|
||||||
@MangaSourceParser("NINEMANGA_DE", "NineManga Deutsch", "de")
|
@MangaSourceParser("NINEMANGA_DE", "NineManga Deutsch", "de")
|
||||||
class Deutsch(context: MangaLoaderContext) : NineMangaParser(
|
class Deutsch(context: MangaLoaderContext) : NineMangaParser(
|
||||||
context,
|
context,
|
||||||
MangaSource.NINEMANGA_DE,
|
MangaParserSource.NINEMANGA_DE,
|
||||||
"de.ninemanga.com",
|
"de.ninemanga.com",
|
||||||
)
|
)
|
||||||
|
|
||||||
@MangaSourceParser("NINEMANGA_BR", "NineManga Brasil", "pt")
|
@MangaSourceParser("NINEMANGA_BR", "NineManga Brasil", "pt")
|
||||||
class Brazil(context: MangaLoaderContext) : NineMangaParser(
|
class Brazil(context: MangaLoaderContext) : NineMangaParser(
|
||||||
context,
|
context,
|
||||||
MangaSource.NINEMANGA_BR,
|
MangaParserSource.NINEMANGA_BR,
|
||||||
"br.ninemanga.com",
|
"br.ninemanga.com",
|
||||||
)
|
)
|
||||||
|
|
||||||
@MangaSourceParser("NINEMANGA_IT", "NineManga Italiano", "it")
|
@MangaSourceParser("NINEMANGA_IT", "NineManga Italiano", "it")
|
||||||
class Italiano(context: MangaLoaderContext) : NineMangaParser(
|
class Italiano(context: MangaLoaderContext) : NineMangaParser(
|
||||||
context,
|
context,
|
||||||
MangaSource.NINEMANGA_IT,
|
MangaParserSource.NINEMANGA_IT,
|
||||||
"it.ninemanga.com",
|
"it.ninemanga.com",
|
||||||
)
|
)
|
||||||
|
|
||||||
@MangaSourceParser("NINEMANGA_FR", "NineManga Français", "fr")
|
@MangaSourceParser("NINEMANGA_FR", "NineManga Français", "fr")
|
||||||
class Francais(context: MangaLoaderContext) : NineMangaParser(
|
class Francais(context: MangaLoaderContext) : NineMangaParser(
|
||||||
context,
|
context,
|
||||||
MangaSource.NINEMANGA_FR,
|
MangaParserSource.NINEMANGA_FR,
|
||||||
"fr.ninemanga.com",
|
"fr.ninemanga.com",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ import org.koitharu.kotatsu.parsers.util.json.toJSONList
|
|||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@MangaSourceParser("NINENINENINEHENTAI", "999Hentai", type = ContentType.HENTAI)
|
@MangaSourceParser("NINENINENINEHENTAI", "AnimeH", type = ContentType.HENTAI)
|
||||||
internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
|
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(
|
override val availableSortOrders: EnumSet<SortOrder> = EnumSet.of(
|
||||||
SortOrder.POPULARITY,
|
SortOrder.POPULARITY,
|
||||||
@@ -377,7 +377,7 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun apiCall(query: String): JSONObject {
|
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 {
|
companion object {
|
||||||
|
|||||||
@@ -14,39 +14,16 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
|||||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||||
import org.koitharu.kotatsu.parsers.exception.NotFoundException
|
import org.koitharu.kotatsu.parsers.exception.NotFoundException
|
||||||
import org.koitharu.kotatsu.parsers.exception.ParseException
|
import org.koitharu.kotatsu.parsers.exception.ParseException
|
||||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
import org.koitharu.kotatsu.parsers.model.*
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.util.*
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
import org.koitharu.kotatsu.parsers.util.json.*
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
import java.util.*
|
||||||
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 javax.crypto.Mac
|
import javax.crypto.Mac
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
internal abstract class WebtoonsParser(
|
internal abstract class WebtoonsParser(
|
||||||
context: MangaLoaderContext,
|
context: MangaLoaderContext,
|
||||||
source: MangaSource,
|
source: MangaParserSource,
|
||||||
) : MangaParser(context, source) {
|
) : MangaParser(context, source) {
|
||||||
|
|
||||||
override val isMultipleTagsSupported = false
|
override val isMultipleTagsSupported = false
|
||||||
@@ -307,25 +284,25 @@ internal abstract class WebtoonsParser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@MangaSourceParser("WEBTOONS_EN", "Webtoons English", "en", type = ContentType.MANGA)
|
@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)
|
@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)
|
@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)
|
@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)
|
@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)
|
@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)
|
@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) {
|
private inner class WebtoonsUrlSigner(private val secret: String) {
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import java.util.*
|
|||||||
|
|
||||||
internal abstract class AnimeBootstrapParser(
|
internal abstract class AnimeBootstrapParser(
|
||||||
context: MangaLoaderContext,
|
context: MangaLoaderContext,
|
||||||
source: MangaSource,
|
source: MangaParserSource,
|
||||||
domain: String,
|
domain: String,
|
||||||
pageSize: Int = 24,
|
pageSize: Int = 24,
|
||||||
) : PagedMangaParser(context, source, pageSize) {
|
) : 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.site.animebootstrap.AnimeBootstrapParser
|
||||||
import org.koitharu.kotatsu.parsers.util.*
|
import org.koitharu.kotatsu.parsers.util.*
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.EnumSet
|
import java.util.*
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
@Broken
|
@Broken
|
||||||
@MangaSourceParser("PAPSCAN", "PapScan", "fr")
|
@MangaSourceParser("PAPSCAN", "PapScan", "fr")
|
||||||
internal class PapScan(context: MangaLoaderContext) :
|
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 sourceLocale: Locale = Locale.ENGLISH
|
||||||
override val isMultipleTagsSupported = false
|
override val isMultipleTagsSupported = false
|
||||||
override val listUrl = "/liste-manga"
|
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.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
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
|
import org.koitharu.kotatsu.parsers.site.animebootstrap.AnimeBootstrapParser
|
||||||
|
|
||||||
@MangaSourceParser("KOMIKZOID", "KomikzoId", "id")
|
@MangaSourceParser("KOMIKZOID", "KomikzoId", "id")
|
||||||
internal class KomikzoId(context: MangaLoaderContext) :
|
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.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
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
|
import org.koitharu.kotatsu.parsers.site.animebootstrap.AnimeBootstrapParser
|
||||||
|
|
||||||
@MangaSourceParser("NEUMANGA", "NeuManga.xyz", "id")
|
@MangaSourceParser("NEUMANGA", "NeuManga.xyz", "id")
|
||||||
internal class NeuManga(context: MangaLoaderContext) :
|
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.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
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
|
import org.koitharu.kotatsu.parsers.site.animebootstrap.AnimeBootstrapParser
|
||||||
|
|
||||||
@MangaSourceParser("SEKTEKOMIK", "SekteKomik", "id")
|
@MangaSourceParser("SEKTEKOMIK", "SekteKomik", "id")
|
||||||
internal class SekteKomik(context: MangaLoaderContext) :
|
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.*
|
import java.util.*
|
||||||
|
|
||||||
@MangaSourceParser("FLIXSCANS", "FlixScans.net", "ar")
|
@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 availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
|
||||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import org.koitharu.kotatsu.parsers.util.*
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@MangaSourceParser("MANGASTORM", "MangaStorm", "ar")
|
@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 availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED)
|
||||||
override val configKeyDomain = ConfigKey.Domain("mangastorm.org")
|
override val configKeyDomain = ConfigKey.Domain("mangastorm.org")
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@MangaSourceParser("TEAMXNOVEL", "TeamXNovel", "ar")
|
@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 availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||||
override val availableStates: Set<MangaState> =
|
override val availableStates: Set<MangaState> =
|
||||||
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED)
|
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 val isMultipleTagsSupported = false
|
||||||
|
|
||||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||||
@@ -31,7 +31,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
|
|||||||
when (filter) {
|
when (filter) {
|
||||||
|
|
||||||
is MangaListFilter.Search -> {
|
is MangaListFilter.Search -> {
|
||||||
append("/series?search=")
|
append("/?search=")
|
||||||
append(filter.query.urlEncoded())
|
append(filter.query.urlEncoded())
|
||||||
if (page > 1) {
|
if (page > 1) {
|
||||||
append("&page=")
|
append("&page=")
|
||||||
@@ -95,7 +95,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
|
|||||||
publicUrl = href.toAbsoluteUrl(domain),
|
publicUrl = href.toAbsoluteUrl(domain),
|
||||||
rating = RATING_UNKNOWN,
|
rating = RATING_UNKNOWN,
|
||||||
isNsfw = false,
|
isNsfw = false,
|
||||||
coverUrl = div.selectFirstOrThrow("img").src().orEmpty(),
|
coverUrl = div.selectFirstOrThrow("img").src()?.replace("thumbnail_", "").orEmpty(),
|
||||||
tags = emptySet(),
|
tags = emptySet(),
|
||||||
state = when (div.selectFirst(".status")?.text()) {
|
state = when (div.selectFirst(".status")?.text()) {
|
||||||
"مستمرة" -> MangaState.ONGOING
|
"مستمرة" -> MangaState.ONGOING
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import java.util.*
|
|||||||
|
|
||||||
@Broken
|
@Broken
|
||||||
@MangaSourceParser("ANIBEL", "Anibel", "be")
|
@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")
|
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")
|
@MangaSourceParser("BEETOON", "BeeToon.net", "en")
|
||||||
internal class BeeToon(context: MangaLoaderContext) :
|
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)
|
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import org.koitharu.kotatsu.parsers.util.*
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@MangaSourceParser("CLONEMANGA", "CloneManga", "en")
|
@MangaSourceParser("CLONEMANGA", "CloneManga", "en")
|
||||||
internal class CloneMangaParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.CLONEMANGA) {
|
internal class CloneMangaParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.CLONEMANGA) {
|
||||||
|
|
||||||
override val availableSortOrders: Set<SortOrder> = Collections.singleton(
|
override val availableSortOrders: Set<SortOrder> = Collections.singleton(
|
||||||
SortOrder.POPULARITY,
|
SortOrder.POPULARITY,
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@MangaSourceParser("COMICEXTRA", "ComicExtra", "en")
|
@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> =
|
override val availableSortOrders: Set<SortOrder> =
|
||||||
EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST)
|
EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST)
|
||||||
|
|
||||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
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
|
override val isMultipleTagsSupported = false
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@MangaSourceParser("DYNASTYSCANS", "DynastyScans", "en")
|
@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 availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
|
||||||
override val configKeyDomain = ConfigKey.Domain("dynasty-scans.com")
|
override val configKeyDomain = ConfigKey.Domain("dynasty-scans.com")
|
||||||
|
|
||||||
@@ -38,8 +39,7 @@ internal class DynastyScans(context: MangaLoaderContext) : PagedMangaParser(cont
|
|||||||
append("/search?q=")
|
append("/search?q=")
|
||||||
append(filter.query.urlEncoded())
|
append(filter.query.urlEncoded())
|
||||||
append("&")
|
append("&")
|
||||||
append("classes[]".urlEncoded())
|
append("classes[]=Series&page=")
|
||||||
append("=Serie&page=")
|
|
||||||
append(page.toString())
|
append(page.toString())
|
||||||
}
|
}
|
||||||
return parseMangaListQuery(webClient.httpGet(url).parseHtml())
|
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 doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||||
val chapters = getChapters(doc)
|
val chapters = getChapters(doc)
|
||||||
val root = doc.requireElementById("main")
|
val root = doc.requireElementById("main")
|
||||||
|
val licensedText = root.select("h4")
|
||||||
|
.find { it.ownText() == "This manga has been licensed" }
|
||||||
|
?.nextElementSibling()?.html()
|
||||||
return manga.copy(
|
return manga.copy(
|
||||||
altTitle = null,
|
altTitle = null,
|
||||||
state = when (root.select("h2.tag-title small").last()?.text()) {
|
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.
|
.orEmpty(), // It is needed if the manga was found via the search.
|
||||||
tags = root.selectFirstOrThrow("div.tag-tags").parseTags(),
|
tags = root.selectFirstOrThrow("div.tag-tags").parseTags(),
|
||||||
author = null,
|
author = null,
|
||||||
description = null,
|
description = licensedText,
|
||||||
chapters = chapters,
|
chapters = chapters,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@MangaSourceParser("FLIXSCANSORG", "FlixScans.org", "en")
|
@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 availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
|
||||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
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.async
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import okhttp3.Headers
|
|
||||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||||
import org.koitharu.kotatsu.parsers.model.*
|
import org.koitharu.kotatsu.parsers.model.*
|
||||||
import org.koitharu.kotatsu.parsers.network.UserAgents
|
|
||||||
import org.koitharu.kotatsu.parsers.util.*
|
import org.koitharu.kotatsu.parsers.util.*
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@MangaSourceParser("MANGAGEKO", "MangaGeko", "en")
|
@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> =
|
override val availableSortOrders: Set<SortOrder> =
|
||||||
EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST)
|
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 isMultipleTagsSupported = false
|
||||||
|
|
||||||
override val headers: Headers = Headers.Builder()
|
private val userAgentKey = ConfigKey.UserAgent(context.getDefaultUserAgent())
|
||||||
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
|
||||||
.build()
|
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||||
|
super.onCreateConfig(keys)
|
||||||
|
keys.add(userAgentKey)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package org.koitharu.kotatsu.parsers.site.en
|
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.config.ConfigKey
|
||||||
import org.koitharu.kotatsu.parsers.model.*
|
import org.koitharu.kotatsu.parsers.model.*
|
||||||
import org.koitharu.kotatsu.parsers.util.*
|
import org.koitharu.kotatsu.parsers.util.*
|
||||||
@@ -9,7 +11,8 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@MangaSourceParser("MANGATOWN", "MangaTown", "en")
|
@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")
|
override val configKeyDomain = ConfigKey.Domain("www.mangatown.com")
|
||||||
|
|
||||||
@@ -102,7 +105,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c
|
|||||||
id = generateUid(href),
|
id = generateUid(href),
|
||||||
title = a.attr("title"),
|
title = a.attr("title"),
|
||||||
coverUrl = a.selectFirst("img")?.absUrl("src").orEmpty(),
|
coverUrl = a.selectFirst("img")?.absUrl("src").orEmpty(),
|
||||||
source = MangaSource.MANGATOWN,
|
source = MangaParserSource.MANGATOWN,
|
||||||
altTitle = null,
|
altTitle = null,
|
||||||
rating = li.selectFirst("p.score")?.selectFirst("b")
|
rating = li.selectFirst("p.score")?.selectFirst("b")
|
||||||
?.ownText()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
|
?.ownText()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
|
||||||
@@ -118,7 +121,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c
|
|||||||
MangaTag(
|
MangaTag(
|
||||||
title = x.attr("title").toTitleCase(),
|
title = x.attr("title").toTitleCase(),
|
||||||
key = x.attr("href").substringAfter("/directory/0-").substringBefore("-0-"),
|
key = x.attr("href").substringAfter("/directory/0-").substringBefore("-0-"),
|
||||||
source = MangaSource.MANGATOWN,
|
source = MangaParserSource.MANGATOWN,
|
||||||
)
|
)
|
||||||
}.orEmpty(),
|
}.orEmpty(),
|
||||||
url = href,
|
url = href,
|
||||||
@@ -143,7 +146,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c
|
|||||||
MangaTag(
|
MangaTag(
|
||||||
title = a.attr("title").toTitleCase(),
|
title = a.attr("title").toTitleCase(),
|
||||||
key = a.attr("href").substringAfter("/directory/0-").substringBefore("-0-"),
|
key = a.attr("href").substringAfter("/directory/0-").substringBefore("-0-"),
|
||||||
source = MangaSource.MANGATOWN,
|
source = MangaParserSource.MANGATOWN,
|
||||||
)
|
)
|
||||||
}.orEmpty(),
|
}.orEmpty(),
|
||||||
description = info?.getElementById("show")?.ownText(),
|
description = info?.getElementById("show")?.ownText(),
|
||||||
@@ -156,7 +159,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c
|
|||||||
MangaChapter(
|
MangaChapter(
|
||||||
id = generateUid(href),
|
id = generateUid(href),
|
||||||
url = href,
|
url = href,
|
||||||
source = MangaSource.MANGATOWN,
|
source = MangaParserSource.MANGATOWN,
|
||||||
number = i + 1f,
|
number = i + 1f,
|
||||||
volume = 0,
|
volume = 0,
|
||||||
uploadDate = parseChapterDate(
|
uploadDate = parseChapterDate(
|
||||||
@@ -174,10 +177,10 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c
|
|||||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||||
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
||||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||||
val root = doc.body().selectFirstOrThrow("div.page_select")
|
val root = doc.body().selectFirst("div.page_select")
|
||||||
val isManga = root.select("select")
|
val isManga = root?.select("select")
|
||||||
|
|
||||||
if (isManga.isEmpty()) {//Webtoon
|
if (isManga.isNullOrEmpty()) {//Webtoon
|
||||||
val imgElements = doc.select("div#viewer.read_img img.image")
|
val imgElements = doc.select("div#viewer.read_img img.image")
|
||||||
return imgElements.map {
|
return imgElements.map {
|
||||||
val href = it.attr("src")
|
val href = it.attr("src")
|
||||||
@@ -185,7 +188,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c
|
|||||||
id = generateUid(href),
|
id = generateUid(href),
|
||||||
url = href,
|
url = href,
|
||||||
preview = null,
|
preview = null,
|
||||||
source = MangaSource.MANGATOWN,
|
source = MangaParserSource.MANGATOWN,
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -199,7 +202,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c
|
|||||||
id = generateUid(href),
|
id = generateUid(href),
|
||||||
url = href,
|
url = href,
|
||||||
preview = null,
|
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 a = li.selectFirst("a") ?: return@mapNotNullToSet null
|
||||||
val key = a.attr("href").substringAfter("/directory/0-").substringBefore("-0-")
|
val key = a.attr("href").substringAfter("/directory/0-").substringBefore("-0-")
|
||||||
MangaTag(
|
MangaTag(
|
||||||
source = MangaSource.MANGATOWN,
|
source = MangaParserSource.MANGATOWN,
|
||||||
key = key,
|
key = key,
|
||||||
title = a.text().toTitleCase(),
|
title = a.text().toTitleCase(),
|
||||||
)
|
)
|
||||||
@@ -253,7 +256,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : PagedMangaParser(c
|
|||||||
MangaChapter(
|
MangaChapter(
|
||||||
id = generateUid(href),
|
id = generateUid(href),
|
||||||
url = href,
|
url = href,
|
||||||
source = MangaSource.MANGATOWN,
|
source = MangaParserSource.MANGATOWN,
|
||||||
number = i + 1f,
|
number = i + 1f,
|
||||||
volume = 0,
|
volume = 0,
|
||||||
uploadDate = parseChapterDate(
|
uploadDate = parseChapterDate(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.parsers.site.en
|
|||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
|
import org.koitharu.kotatsu.parsers.Broken
|
||||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||||
@@ -14,9 +15,10 @@ import org.koitharu.kotatsu.parsers.util.json.mapJSON
|
|||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@Broken
|
||||||
@MangaSourceParser("MANGAOWL", "MangaOwl.to", "en")
|
@MangaSourceParser("MANGAOWL", "MangaOwl.to", "en")
|
||||||
internal class Mangaowl(context: MangaLoaderContext) :
|
internal class Mangaowl(context: MangaLoaderContext) :
|
||||||
PagedMangaParser(context, MangaSource.MANGAOWL, pageSize = 24) {
|
PagedMangaParser(context, MangaParserSource.MANGAOWL, pageSize = 24) {
|
||||||
|
|
||||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||||
SortOrder.POPULARITY,
|
SortOrder.POPULARITY,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import java.util.*
|
|||||||
|
|
||||||
@MangaSourceParser("MANHWA18", "Manhwa18.net", "en", type = ContentType.HENTAI)
|
@MangaSourceParser("MANHWA18", "Manhwa18.net", "en", type = ContentType.HENTAI)
|
||||||
class Manhwa18Parser(context: MangaLoaderContext) :
|
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")
|
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwa18.net")
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ class Manhwa18Parser(context: MangaLoaderContext) :
|
|||||||
author = null,
|
author = null,
|
||||||
largeCoverUrl = null,
|
largeCoverUrl = null,
|
||||||
description = null,
|
description = null,
|
||||||
source = MangaSource.MANHWA18,
|
source = MangaParserSource.MANHWA18,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,7 +167,7 @@ class Manhwa18Parser(context: MangaLoaderContext) :
|
|||||||
scanlator = null,
|
scanlator = null,
|
||||||
uploadDate = uploadDate,
|
uploadDate = uploadDate,
|
||||||
branch = null,
|
branch = null,
|
||||||
source = MangaSource.MANHWA18,
|
source = MangaParserSource.MANHWA18,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -210,7 +210,7 @@ class Manhwa18Parser(context: MangaLoaderContext) :
|
|||||||
id = generateUid(url),
|
id = generateUid(url),
|
||||||
url = url,
|
url = url,
|
||||||
preview = null,
|
preview = null,
|
||||||
source = MangaSource.MANHWA18,
|
source = MangaParserSource.MANHWA18,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import java.util.*
|
|||||||
|
|
||||||
@MangaSourceParser("MANHWASMEN", "ManhwasMen", "en", type = ContentType.HENTAI)
|
@MangaSourceParser("MANHWASMEN", "ManhwasMen", "en", type = ContentType.HENTAI)
|
||||||
class ManhwasMen(context: MangaLoaderContext) :
|
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")
|
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwas.men")
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@MangaSourceParser("PO2SCANS", "Po2Scans", "en")
|
@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 availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
|
||||||
override val configKeyDomain = ConfigKey.Domain("po2scans.com")
|
override val configKeyDomain = ConfigKey.Domain("po2scans.com")
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import java.util.*
|
|||||||
|
|
||||||
@MangaSourceParser("PURURIN", "Pururin", "en", ContentType.HENTAI)
|
@MangaSourceParser("PURURIN", "Pururin", "en", ContentType.HENTAI)
|
||||||
internal class Pururin(context: MangaLoaderContext) :
|
internal class Pururin(context: MangaLoaderContext) :
|
||||||
PagedMangaParser(context, MangaSource.PURURIN, pageSize = 20) {
|
PagedMangaParser(context, MangaParserSource.PURURIN, pageSize = 20) {
|
||||||
|
|
||||||
override val availableSortOrders: Set<SortOrder> =
|
override val availableSortOrders: Set<SortOrder> =
|
||||||
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.RATING, SortOrder.ALPHABETICAL)
|
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.RATING, SortOrder.ALPHABETICAL)
|
||||||
|
|||||||
@@ -23,21 +23,18 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
private const val TOO_MANY_REQUESTS = 429
|
|
||||||
private const val MAX_RETRY_COUNT = 5
|
private const val MAX_RETRY_COUNT = 5
|
||||||
private val JSON_MEDIA_TYPE get() = "application/json; charset=utf-8".toMediaType()
|
private val JSON_MEDIA_TYPE get() = "application/json; charset=utf-8".toMediaType()
|
||||||
|
|
||||||
@MangaSourceParser("REAPERCOMICS", "ReaperComics", "en")
|
@MangaSourceParser("REAPERCOMICS", "ReaperComics", "en")
|
||||||
internal class ReaperComics(context: MangaLoaderContext) :
|
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 availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.ALPHABETICAL)
|
||||||
|
|
||||||
override val configKeyDomain = ConfigKey.Domain("reaperscans.com")
|
override val configKeyDomain = ConfigKey.Domain("reaperscans.com")
|
||||||
|
|
||||||
private val userAgentKey = ConfigKey.UserAgent(
|
private val userAgentKey = ConfigKey.UserAgent(context.getDefaultUserAgent())
|
||||||
"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 baseHeaders: Headers
|
private val baseHeaders: Headers
|
||||||
get() = Headers.Builder().add("User-Agent", config[userAgentKey]).build()
|
get() = Headers.Builder().add("User-Agent", config[userAgentKey]).build()
|
||||||
@@ -159,9 +156,9 @@ internal class ReaperComics(context: MangaLoaderContext) :
|
|||||||
keys.add(userAgentKey)
|
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 {
|
override suspend fun getDetails(manga: Manga): Manga {
|
||||||
val cachedChapters = chapterCache[manga.url]
|
val cachedChapters = chapterCache[manga.url]
|
||||||
@@ -171,17 +168,18 @@ internal class ReaperComics(context: MangaLoaderContext) :
|
|||||||
|
|
||||||
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||||
val simpleDateFormat = SimpleDateFormat("dd/MM/yyyy", sourceLocale)
|
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>()
|
val chapters = mutableSetOf<MangaChapter>()
|
||||||
var hasNextPage = doc.selectFirst(chapterListNextPageSelector()) != null
|
var hasNextPage = doc.selectFirst(chapterListNextPageSelector) != null
|
||||||
chapters.addAll(
|
chapters.addAll(
|
||||||
doc.select(chapterListSelector()).mapChapters { _, li ->
|
doc.select(chapterListSelector).mapChapters { _, li ->
|
||||||
val a = li.selectFirstOrThrow("a")
|
val a = li.selectFirstOrThrow("a")
|
||||||
val chapterUrl = a.attr("href").toRelativeUrl(domain)
|
val chapterUrl = a.attr("href").toRelativeUrl(domain)
|
||||||
MangaChapter(
|
MangaChapter(
|
||||||
id = generateUid(chapterUrl),
|
id = generateUid(chapterUrl),
|
||||||
name = li.selectFirst("div.truncate p.truncate")?.text().orEmpty(),
|
name = li.selectFirst("div.truncate p.truncate")?.text().orEmpty(),
|
||||||
number = totalChapters--,
|
number = totalChapters--,
|
||||||
|
volume = 0,
|
||||||
url = chapterUrl,
|
url = chapterUrl,
|
||||||
scanlator = null,
|
scanlator = null,
|
||||||
uploadDate = parseChapterDate(
|
uploadDate = parseChapterDate(
|
||||||
@@ -219,7 +217,7 @@ internal class ReaperComics(context: MangaLoaderContext) :
|
|||||||
var pageToQuery = 2
|
var pageToQuery = 2
|
||||||
|
|
||||||
// Javascript: (Math.random() + 1).toString(36).substring(8)
|
// Javascript: (Math.random() + 1).toString(36).substring(8)
|
||||||
val generateId = { ->
|
val generateId = {
|
||||||
"1.${
|
"1.${
|
||||||
Random.nextLong().toString(36)
|
Random.nextLong().toString(36)
|
||||||
}".substring(10)
|
}".substring(10)
|
||||||
@@ -249,13 +247,14 @@ internal class ReaperComics(context: MangaLoaderContext) :
|
|||||||
serverMemo = mergeLeft(serverMemo, responseData.serverMemo)
|
serverMemo = mergeLeft(serverMemo, responseData.serverMemo)
|
||||||
val chaptersHtml = Jsoup.parse(responseData.effects.html, "https://$domain")
|
val chaptersHtml = Jsoup.parse(responseData.effects.html, "https://$domain")
|
||||||
chapters.addAll(
|
chapters.addAll(
|
||||||
chaptersHtml.select(chapterListSelector()).mapChapters { _, li ->
|
chaptersHtml.select(chapterListSelector).mapChapters { _, li ->
|
||||||
val a = li.selectFirstOrThrow("a")
|
val a = li.selectFirstOrThrow("a")
|
||||||
val chapterUrl = a.attr("href").toRelativeUrl(domain)
|
val chapterUrl = a.attr("href").toRelativeUrl(domain)
|
||||||
MangaChapter(
|
MangaChapter(
|
||||||
id = generateUid(chapterUrl),
|
id = generateUid(chapterUrl),
|
||||||
name = li.selectFirst("div.truncate p.truncate")?.text().orEmpty(),
|
name = li.selectFirst("div.truncate p.truncate")?.text().orEmpty(),
|
||||||
number = totalChapters--,
|
number = totalChapters--,
|
||||||
|
volume = 0,
|
||||||
url = chapterUrl,
|
url = chapterUrl,
|
||||||
scanlator = null,
|
scanlator = null,
|
||||||
uploadDate = parseChapterDate(
|
uploadDate = parseChapterDate(
|
||||||
@@ -267,7 +266,7 @@ internal class ReaperComics(context: MangaLoaderContext) :
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
hasNextPage = chaptersHtml.selectFirst(chapterListNextPageSelector()) != null
|
hasNextPage = chaptersHtml.selectFirst(chapterListNextPageSelector) != null
|
||||||
pageToQuery++
|
pageToQuery++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import java.util.*
|
|||||||
|
|
||||||
@MangaSourceParser("TEMPLESCANESP", "TempleScanEsp", "es", ContentType.HENTAI)
|
@MangaSourceParser("TEMPLESCANESP", "TempleScanEsp", "es", ContentType.HENTAI)
|
||||||
internal class TempleScanEsp(context: MangaLoaderContext) :
|
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)
|
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.NEWEST, SortOrder.UPDATED)
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import java.util.*
|
|||||||
@MangaSourceParser("TUMANGAONLINE", "TuMangaOnline", "es")
|
@MangaSourceParser("TUMANGAONLINE", "TuMangaOnline", "es")
|
||||||
class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
|
class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||||
context,
|
context,
|
||||||
source = MangaSource.TUMANGAONLINE,
|
source = MangaParserSource.TUMANGAONLINE,
|
||||||
pageSize = 24,
|
pageSize = 24,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -147,7 +147,8 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
|
|||||||
return MangaChapter(
|
return MangaChapter(
|
||||||
id = generateUid(href),
|
id = generateUid(href),
|
||||||
name = "One Shot",
|
name = "One Shot",
|
||||||
number = 1,
|
number = 1f,
|
||||||
|
volume = 0,
|
||||||
url = href,
|
url = href,
|
||||||
scanlator = element.select("div.col-md-6.text-truncate").text(),
|
scanlator = element.select("div.col-md-6.text-truncate").text(),
|
||||||
branch = null,
|
branch = null,
|
||||||
@@ -163,7 +164,8 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
|
|||||||
return MangaChapter(
|
return MangaChapter(
|
||||||
id = generateUid(href),
|
id = generateUid(href),
|
||||||
name = chName,
|
name = chName,
|
||||||
number = number + 1,
|
number = number + 1f,
|
||||||
|
volume = 0,
|
||||||
url = href,
|
url = href,
|
||||||
scanlator = element.select("div.col-md-6.text-truncate").text(),
|
scanlator = element.select("div.col-md-6.text-truncate").text(),
|
||||||
branch = null,
|
branch = null,
|
||||||
@@ -205,6 +207,9 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
|
|||||||
private suspend fun redirectToReadingPage(document: Document): Document {
|
private suspend fun redirectToReadingPage(document: Document): Document {
|
||||||
val script1 = document.selectFirst("script:containsData(uniqid)")
|
val script1 = document.selectFirst("script:containsData(uniqid)")
|
||||||
val script2 = document.selectFirst("script:containsData(window.location.replace)")
|
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()
|
val redirectHeaders = Headers.Builder().set("Referer", document.baseUri()).build()
|
||||||
|
|
||||||
@@ -226,15 +231,52 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
|
|||||||
|
|
||||||
if (script2 != null) {
|
if (script2 != null) {
|
||||||
val data = script2.data()
|
val data = script2.data()
|
||||||
val regexRedirect = """window\.location\.replace\('(.+)'\)""".toRegex()
|
val regexRedirect = """window\.location\.replace\(['"](.+)['"]\)""".toRegex()
|
||||||
val url = regexRedirect.find(data)!!.groupValues[1]
|
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())
|
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
|
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> {
|
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||||
val doc = webClient.httpGet("https://$domain/library", headers).parseHtml()
|
val doc = webClient.httpGet("https://$domain/library", headers).parseHtml()
|
||||||
val elements = doc.body().select("div#books-genders > div > div")
|
val elements = doc.body().select("div#books-genders > div > div")
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import java.util.*
|
|||||||
|
|
||||||
internal abstract class FmreaderParser(
|
internal abstract class FmreaderParser(
|
||||||
context: MangaLoaderContext,
|
context: MangaLoaderContext,
|
||||||
source: MangaSource,
|
source: MangaParserSource,
|
||||||
domain: String,
|
domain: String,
|
||||||
pageSize: Int = 20,
|
pageSize: Int = 20,
|
||||||
) : PagedMangaParser(context, source, pageSize) {
|
) : PagedMangaParser(context, source, pageSize) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import java.text.SimpleDateFormat
|
|||||||
|
|
||||||
@MangaSourceParser("MANHWA18COM", "Manhwa18.com", "en", ContentType.HENTAI)
|
@MangaSourceParser("MANHWA18COM", "Manhwa18.com", "en", ContentType.HENTAI)
|
||||||
internal class Manhwa18Com(context: MangaLoaderContext) :
|
internal class Manhwa18Com(context: MangaLoaderContext) :
|
||||||
FmreaderParser(context, MangaSource.MANHWA18COM, "manhwa18.com") {
|
FmreaderParser(context, MangaParserSource.MANHWA18COM, "manhwa18.com") {
|
||||||
|
|
||||||
override val listUrl = "/tim-kiem"
|
override val listUrl = "/tim-kiem"
|
||||||
override val selectState = "div.info-item:contains(Status) span.info-value "
|
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")
|
@MangaSourceParser("OLIMPOSCANS", "OlimpoScans", "es")
|
||||||
internal class OlimpoScans(context: MangaLoaderContext) :
|
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 selectState = "ul.manga-info li:contains(Estado) a"
|
||||||
override val selectAlt = "ul.manga-info li:contains(Otros nombres)"
|
override val selectAlt = "ul.manga-info li:contains(Otros nombres)"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import java.text.SimpleDateFormat
|
|||||||
|
|
||||||
@MangaSourceParser("KLZ9", "Klz9", "ja")
|
@MangaSourceParser("KLZ9", "Klz9", "ja")
|
||||||
internal class Klz9(context: MangaLoaderContext) :
|
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 selectDesc = "div.row:contains(Description)"
|
||||||
override val selectState = "ul.manga-info li:contains(Status) a"
|
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.MangaSourceParser
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
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.site.fmreader.FmreaderParser
|
||||||
import org.koitharu.kotatsu.parsers.util.*
|
import org.koitharu.kotatsu.parsers.util.*
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
@MangaSourceParser("WELOVEMANGA", "WeLoveManga", "ja")
|
@MangaSourceParser("WELOVEMANGA", "WeLoveManga", "ja")
|
||||||
internal class WeLoveManga(context: MangaLoaderContext) :
|
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> {
|
override suspend fun getChapters(doc: Document): List<MangaChapter> {
|
||||||
val mid = doc.selectFirstOrThrow("div.cmt input").attr("value")
|
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.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
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
|
import org.koitharu.kotatsu.parsers.site.fmreader.FmreaderParser
|
||||||
|
|
||||||
@MangaSourceParser("WELOMA", "Weloma", "ja")
|
@MangaSourceParser("WELOMA", "Weloma", "ja")
|
||||||
internal class Weloma(context: MangaLoaderContext) :
|
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(
|
internal abstract class FoolSlideParser(
|
||||||
context: MangaLoaderContext,
|
context: MangaLoaderContext,
|
||||||
source: MangaSource,
|
source: MangaParserSource,
|
||||||
domain: String,
|
domain: String,
|
||||||
pageSize: Int = 25,
|
pageSize: Int = 25,
|
||||||
) : PagedMangaParser(context, source, pageSize) {
|
) : 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.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
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
|
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||||
|
|
||||||
@MangaSourceParser("DEATHTOLLSCANS", "DeathTollScans", "en")
|
@MangaSourceParser("DEATHTOLLSCANS", "DeathTollScans", "en")
|
||||||
internal class Deathtollscans(context: MangaLoaderContext) :
|
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.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
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
|
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||||
|
|
||||||
@MangaSourceParser("MANGATELLERS", "Mangatellers", "en")
|
@MangaSourceParser("MANGATELLERS", "Mangatellers", "en")
|
||||||
internal class Mangatellers(context: MangaLoaderContext) :
|
internal class Mangatellers(context: MangaLoaderContext) :
|
||||||
FoolSlideParser(context, MangaSource.MANGATELLERS, "reader.mangatellers.gr") {
|
FoolSlideParser(context, MangaParserSource.MANGATELLERS, "reader.mangatellers.gr") {
|
||||||
override val pagination = false
|
override val pagination = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package org.koitharu.kotatsu.parsers.site.foolslide.en
|
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.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
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
|
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||||
|
|
||||||
|
@Broken
|
||||||
@MangaSourceParser("READER_EVILFLOWERS", "Evil Flowers", "en")
|
@MangaSourceParser("READER_EVILFLOWERS", "Evil Flowers", "en")
|
||||||
internal class ReaderEvilflowers(context: MangaLoaderContext) :
|
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 kotlinx.coroutines.coroutineScope
|
||||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
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.site.foolslide.FoolSlideParser
|
||||||
import org.koitharu.kotatsu.parsers.util.*
|
import org.koitharu.kotatsu.parsers.util.*
|
||||||
|
|
||||||
@MangaSourceParser("SEINAGI", "Seinagi", "en")
|
@MangaSourceParser("SEINAGI", "Seinagi", "en")
|
||||||
internal class Seinagi(context: MangaLoaderContext) :
|
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
|
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.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
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
|
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||||
|
|
||||||
@MangaSourceParser("MENUDO_FANSUB", "Menudo Fansub", "es")
|
@MangaSourceParser("MENUDO_FANSUB", "Menudo Fansub", "es")
|
||||||
internal class MenudoFansub(context: MangaLoaderContext) :
|
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 searchUrl = "slide/search/"
|
||||||
override val listUrl = "slide/directory/"
|
override val listUrl = "slide/directory/"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ package org.koitharu.kotatsu.parsers.site.foolslide.es
|
|||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
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.site.foolslide.FoolSlideParser
|
||||||
import org.koitharu.kotatsu.parsers.util.*
|
import org.koitharu.kotatsu.parsers.util.*
|
||||||
|
|
||||||
@MangaSourceParser("PZYKOSIS666HFANSUB", "Pzykosis666h Fansub", "es", ContentType.HENTAI)
|
@MangaSourceParser("PZYKOSIS666HFANSUB", "Pzykosis666h Fansub", "es", ContentType.HENTAI)
|
||||||
internal class Pzykosis666hFansub(context: MangaLoaderContext) :
|
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 {
|
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
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.MangaSourceParser
|
||||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||||
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||||
import org.koitharu.kotatsu.parsers.util.domain
|
import org.koitharu.kotatsu.parsers.util.*
|
||||||
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
|
|
||||||
|
|
||||||
@MangaSourceParser("SEINAGIADULTO", "Seinagi Adulto", "es", ContentType.HENTAI)
|
@MangaSourceParser("SEINAGIADULTO", "Seinagi Adulto", "es", ContentType.HENTAI)
|
||||||
internal class SeinagiAdulto(context: MangaLoaderContext) :
|
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 {
|
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
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.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
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
|
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||||
|
|
||||||
// The source has changed template so for the moment it is dead.
|
// The source has changed template so for the moment it is dead.
|
||||||
@MangaSourceParser("HNISCANTRAD", "HniScantrad", "fr")
|
@MangaSourceParser("HNISCANTRAD", "HniScantrad", "fr")
|
||||||
internal class HniScantrad(context: MangaLoaderContext) :
|
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 pagination = false
|
||||||
override val searchUrl = "lel/search/"
|
override val searchUrl = "lel/search/"
|
||||||
override val listUrl = "lel/directory/"
|
override val listUrl = "lel/directory/"
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package org.koitharu.kotatsu.parsers.site.foolslide.it
|
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.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
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
|
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||||
|
|
||||||
|
@Broken
|
||||||
@MangaSourceParser("POWERMANGA", "PowerManga", "it")
|
@MangaSourceParser("POWERMANGA", "PowerManga", "it")
|
||||||
internal class PowerManga(context: MangaLoaderContext) :
|
internal class PowerManga(context: MangaLoaderContext) :
|
||||||
FoolSlideParser(context, MangaSource.POWERMANGA, "reader.powermanga.org") {
|
FoolSlideParser(context, MangaParserSource.POWERMANGA, "reader.powermanga.org") {
|
||||||
override val pagination = false
|
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.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
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
|
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||||
|
|
||||||
@MangaSourceParser("RAMAREADER", "RamaReader", "it")
|
@MangaSourceParser("RAMAREADER", "RamaReader", "it")
|
||||||
internal class Ramareader(context: MangaLoaderContext) :
|
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 searchUrl = "read/search/"
|
||||||
override val listUrl = "read/directory/"
|
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.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
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
|
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||||
|
|
||||||
@MangaSourceParser("READNIFTEAM", "ReadNifTeam", "it")
|
@MangaSourceParser("READNIFTEAM", "ReadNifTeam", "it")
|
||||||
internal class ReadNifteam(context: MangaLoaderContext) :
|
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 searchUrl = "slide/search/"
|
||||||
override val listUrl = "slide/directory/"
|
override val listUrl = "slide/directory/"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ import java.util.*
|
|||||||
|
|
||||||
@Broken
|
@Broken
|
||||||
@MangaSourceParser("BENTOMANGA", "BentoManga", "fr")
|
@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(
|
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||||
SortOrder.UPDATED,
|
SortOrder.UPDATED,
|
||||||
@@ -228,7 +229,8 @@ internal class BentomangaParser(context: MangaLoaderContext) : PagedMangaParser(
|
|||||||
MangaChapter(
|
MangaChapter(
|
||||||
id = generateUid(href),
|
id = generateUid(href),
|
||||||
name = if (name != null && name != title) "$title: $name" else title,
|
name = if (name != null && name != title) "$title: $name" else title,
|
||||||
number = href.substringAfterLast('/').toIntOrNull() ?: 0,
|
number = href.substringAfterLast('/').toFloatOrNull() ?: 0f,
|
||||||
|
volume = 0,
|
||||||
url = href,
|
url = href,
|
||||||
scanlator = div.selectFirst(".team_link-name")?.textOrNull(),
|
scanlator = div.selectFirst(".team_link-name")?.textOrNull(),
|
||||||
uploadDate = div.selectFirst(".component-chapter-date")
|
uploadDate = div.selectFirst(".component-chapter-date")
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import java.util.*
|
|||||||
|
|
||||||
@MangaSourceParser("FURYOSOCIETY", "FuryoSociety", "fr")
|
@MangaSourceParser("FURYOSOCIETY", "FuryoSociety", "fr")
|
||||||
internal class FuryoSociety(context: MangaLoaderContext) :
|
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)
|
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL, SortOrder.UPDATED)
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import java.util.*
|
|||||||
|
|
||||||
@MangaSourceParser("LEGACY_SCANS", "LegacyScans", "fr")
|
@MangaSourceParser("LEGACY_SCANS", "LegacyScans", "fr")
|
||||||
internal class LegacyScansParser(context: MangaLoaderContext) :
|
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)
|
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.koitharu.kotatsu.parsers.site.fr
|
package org.koitharu.kotatsu.parsers.site.fr
|
||||||
|
|
||||||
|
|
||||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||||
@@ -11,7 +10,7 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@MangaSourceParser("LIRESCAN", "LireScan", "fr")
|
@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)
|
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@MangaSourceParser("LUGNICASCANS", "LugnicaScans", "fr")
|
@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(
|
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||||
SortOrder.ALPHABETICAL,
|
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")
|
@MangaSourceParser("SCANS_MANGAS_ME", "ScansMangas.me", "fr")
|
||||||
internal class ScansMangasMe(context: MangaLoaderContext) :
|
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(
|
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||||
SortOrder.ALPHABETICAL,
|
SortOrder.ALPHABETICAL,
|
||||||
@@ -132,7 +132,7 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getChapters(doc: Document): List<MangaChapter> {
|
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 a = li.selectFirstOrThrow("a")
|
||||||
val href = a.attrAsRelativeUrl("href")
|
val href = a.attrAsRelativeUrl("href")
|
||||||
MangaChapter(
|
MangaChapter(
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@MangaSourceParser("SCANTRADUNION", "ScantradUnion", "fr")
|
@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(
|
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||||
SortOrder.ALPHABETICAL,
|
SortOrder.ALPHABETICAL,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import java.util.*
|
|||||||
|
|
||||||
internal abstract class GalleryAdultsParser(
|
internal abstract class GalleryAdultsParser(
|
||||||
context: MangaLoaderContext,
|
context: MangaLoaderContext,
|
||||||
source: MangaSource,
|
source: MangaParserSource,
|
||||||
domain: String,
|
domain: String,
|
||||||
pageSize: Int = 20,
|
pageSize: Int = 20,
|
||||||
) : PagedMangaParser(context, source, pageSize) {
|
) : PagedMangaParser(context, source, pageSize) {
|
||||||
@@ -158,7 +158,8 @@ internal abstract class GalleryAdultsParser(
|
|||||||
MangaChapter(
|
MangaChapter(
|
||||||
id = manga.id,
|
id = manga.id,
|
||||||
name = manga.title,
|
name = manga.title,
|
||||||
number = 1,
|
number = 1f,
|
||||||
|
volume = 0,
|
||||||
url = urlChapters,
|
url = urlChapters,
|
||||||
scanlator = null,
|
scanlator = null,
|
||||||
uploadDate = 0,
|
uploadDate = 0,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import org.jsoup.nodes.Element
|
|||||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
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.model.MangaTag
|
||||||
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
|
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
|
||||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||||
@@ -13,7 +13,7 @@ import java.util.*
|
|||||||
|
|
||||||
@MangaSourceParser("ASMHENTAI", "AsmHentai", type = ContentType.HENTAI)
|
@MangaSourceParser("ASMHENTAI", "AsmHentai", type = ContentType.HENTAI)
|
||||||
internal class AsmHentai(context: MangaLoaderContext) :
|
internal class AsmHentai(context: MangaLoaderContext) :
|
||||||
GalleryAdultsParser(context, MangaSource.ASMHENTAI, "asmhentai.com") {
|
GalleryAdultsParser(context, MangaParserSource.ASMHENTAI, "asmhentai.com") {
|
||||||
override val selectGallery = ".preview_item"
|
override val selectGallery = ".preview_item"
|
||||||
override val selectGalleryLink = ".image a"
|
override val selectGalleryLink = ".image a"
|
||||||
override val selectGalleryImg = ".image img"
|
override val selectGalleryImg = ".image img"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import java.util.*
|
|||||||
|
|
||||||
@MangaSourceParser("DOUJINDESUUK", "DoujinDesu.uk", type = ContentType.HENTAI)
|
@MangaSourceParser("DOUJINDESUUK", "DoujinDesu.uk", type = ContentType.HENTAI)
|
||||||
internal class DoujinDesuUk(context: MangaLoaderContext) :
|
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 selectGallery = ".gallery"
|
||||||
override val selectGalleryLink = "a"
|
override val selectGalleryLink = "a"
|
||||||
override val selectGalleryTitle = ".caption"
|
override val selectGalleryTitle = ".caption"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import java.util.*
|
|||||||
|
|
||||||
@MangaSourceParser("HENTAI3", "3Hentai", type = ContentType.HENTAI)
|
@MangaSourceParser("HENTAI3", "3Hentai", type = ContentType.HENTAI)
|
||||||
internal class Hentai3(context: MangaLoaderContext) :
|
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 selectGallery = ".doujin "
|
||||||
override val selectGalleryLink = "a"
|
override val selectGalleryLink = "a"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import java.util.*
|
|||||||
|
|
||||||
@MangaSourceParser("HENTAIENVY", "HentaiEnvy", type = ContentType.HENTAI)
|
@MangaSourceParser("HENTAIENVY", "HentaiEnvy", type = ContentType.HENTAI)
|
||||||
internal class HentaiEnvy(context: MangaLoaderContext) :
|
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 selectGalleryLink = "a"
|
||||||
override val selectGalleryTitle = "div.title"
|
override val selectGalleryTitle = "div.title"
|
||||||
override val selectTags = ".tags_items"
|
override val selectTags = ".tags_items"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import java.util.*
|
|||||||
|
|
||||||
@MangaSourceParser("HENTAIERA", "HentaiEra", type = ContentType.HENTAI)
|
@MangaSourceParser("HENTAIERA", "HentaiEra", type = ContentType.HENTAI)
|
||||||
internal class HentaiEra(context: MangaLoaderContext) :
|
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 selectTags = ".tags_section"
|
||||||
override val selectTag = ".galleries_info li:contains(Tags) div.info_tags"
|
override val selectTag = ".galleries_info li:contains(Tags) div.info_tags"
|
||||||
override val selectAuthor = ".galleries_info li:contains(Artists) span.item_name"
|
override val selectAuthor = ".galleries_info li:contains(Artists) span.item_name"
|
||||||
@@ -131,7 +131,8 @@ internal class HentaiEra(context: MangaLoaderContext) :
|
|||||||
MangaChapter(
|
MangaChapter(
|
||||||
id = manga.id,
|
id = manga.id,
|
||||||
name = manga.title,
|
name = manga.title,
|
||||||
number = 1,
|
number = 1f,
|
||||||
|
volume = 0,
|
||||||
url = urlChapters,
|
url = urlChapters,
|
||||||
scanlator = null,
|
scanlator = null,
|
||||||
uploadDate = 0,
|
uploadDate = 0,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import java.util.*
|
|||||||
|
|
||||||
@MangaSourceParser("HENTAIFORCE", "HentaiForce", type = ContentType.HENTAI)
|
@MangaSourceParser("HENTAIFORCE", "HentaiForce", type = ContentType.HENTAI)
|
||||||
internal class HentaiForce(context: MangaLoaderContext) :
|
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 selectGallery = ".gallery"
|
||||||
override val selectGalleryLink = "a.gallery-thumb"
|
override val selectGalleryLink = "a.gallery-thumb"
|
||||||
override val pathTagUrl = "/tags/popular/"
|
override val pathTagUrl = "/tags/popular/"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import java.util.*
|
|||||||
|
|
||||||
@MangaSourceParser("HENTAIFOX", "HentaiFox", type = ContentType.HENTAI)
|
@MangaSourceParser("HENTAIFOX", "HentaiFox", type = ContentType.HENTAI)
|
||||||
internal class HentaiFox(context: MangaLoaderContext) :
|
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 selectGallery = ".lc_galleries .thumb, .related_galleries .thumb"
|
||||||
override val pathTagUrl = "/tags/popular/pag/"
|
override val pathTagUrl = "/tags/popular/pag/"
|
||||||
override val selectTags = ".list_tags"
|
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.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
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.model.MangaTag
|
||||||
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
|
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
|
||||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||||
@@ -13,7 +13,7 @@ import java.util.*
|
|||||||
|
|
||||||
@MangaSourceParser("HENTAIROX", "HentaiRox", type = ContentType.HENTAI)
|
@MangaSourceParser("HENTAIROX", "HentaiRox", type = ContentType.HENTAI)
|
||||||
internal class HentaiRox(context: MangaLoaderContext) :
|
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 selectGalleryImg = ".inner_thumb img"
|
||||||
override val selectTags = ".gtags"
|
override val selectTags = ".gtags"
|
||||||
override val selectTag = "li:contains(Tags:)"
|
override val selectTag = "li:contains(Tags:)"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import java.util.*
|
|||||||
|
|
||||||
@MangaSourceParser("NHENTAI", "NHentai.net", type = ContentType.HENTAI)
|
@MangaSourceParser("NHENTAI", "NHentai.net", type = ContentType.HENTAI)
|
||||||
internal class NHentaiParser(context: MangaLoaderContext) :
|
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 selectGallery = "div.index-container:not(.index-popular) .gallery, #related-container .gallery"
|
||||||
override val selectGalleryLink = "a"
|
override val selectGalleryLink = "a"
|
||||||
override val selectGalleryTitle = ".caption"
|
override val selectGalleryTitle = ".caption"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import java.util.*
|
|||||||
|
|
||||||
internal abstract class GattsuParser(
|
internal abstract class GattsuParser(
|
||||||
context: MangaLoaderContext,
|
context: MangaLoaderContext,
|
||||||
source: MangaSource,
|
source: MangaParserSource,
|
||||||
domain: String,
|
domain: String,
|
||||||
pageSize: Int = 20,
|
pageSize: Int = 20,
|
||||||
) : PagedMangaParser(context, source, pageSize) {
|
) : PagedMangaParser(context, source, pageSize) {
|
||||||
@@ -115,7 +115,8 @@ internal abstract class GattsuParser(
|
|||||||
MangaChapter(
|
MangaChapter(
|
||||||
id = manga.id,
|
id = manga.id,
|
||||||
name = manga.title,
|
name = manga.title,
|
||||||
number = 1,
|
number = 1f,
|
||||||
|
volume = 0,
|
||||||
url = urlChapter,
|
url = urlChapter,
|
||||||
scanlator = null,
|
scanlator = null,
|
||||||
uploadDate = 0,
|
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.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
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
|
import org.koitharu.kotatsu.parsers.site.gattsu.GattsuParser
|
||||||
|
|
||||||
@MangaSourceParser("HENTAISEASON", "HentaiSeason", "pt", ContentType.HENTAI)
|
@MangaSourceParser("HENTAISEASON", "HentaiSeason", "pt", ContentType.HENTAI)
|
||||||
internal class HentaiSeason(context: MangaLoaderContext) :
|
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.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
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
|
import org.koitharu.kotatsu.parsers.site.gattsu.GattsuParser
|
||||||
|
|
||||||
@MangaSourceParser("HENTAITOKYO", "HentaiTokyo", "pt", ContentType.HENTAI)
|
@MangaSourceParser("HENTAITOKYO", "HentaiTokyo", "pt", ContentType.HENTAI)
|
||||||
internal class HentaiTokyo(context: MangaLoaderContext) :
|
internal class HentaiTokyo(context: MangaLoaderContext) :
|
||||||
GattsuParser(context, MangaSource.HENTAITOKYO, "hentaitokyo.net") {
|
GattsuParser(context, MangaParserSource.HENTAITOKYO, "hentaitokyo.net") {
|
||||||
override val tagUrl = "tags"
|
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.MangaSourceParser
|
||||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||||
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
|
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
|
||||||
import org.koitharu.kotatsu.parsers.site.gattsu.GattsuParser
|
import org.koitharu.kotatsu.parsers.site.gattsu.GattsuParser
|
||||||
import org.koitharu.kotatsu.parsers.util.attrAsAbsoluteUrl
|
import org.koitharu.kotatsu.parsers.util.*
|
||||||
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
|
|
||||||
|
|
||||||
@MangaSourceParser("MUNDOHENTAIOFICIAL", "MundoHentaiOficial", "pt", ContentType.HENTAI)
|
@MangaSourceParser("MUNDOHENTAIOFICIAL", "MundoHentaiOficial", "pt", ContentType.HENTAI)
|
||||||
internal class MundoHentaiOficial(context: MangaLoaderContext) :
|
internal class MundoHentaiOficial(context: MangaLoaderContext) :
|
||||||
GattsuParser(context, MangaSource.MUNDOHENTAIOFICIAL, "mundohentaioficial.com") {
|
GattsuParser(context, MangaParserSource.MUNDOHENTAIOFICIAL, "mundohentaioficial.com") {
|
||||||
|
|
||||||
override val tagUrl = "tags"
|
override val tagUrl = "tags"
|
||||||
|
|
||||||
|
|||||||
@@ -3,17 +3,13 @@ package org.koitharu.kotatsu.parsers.site.gattsu.pt
|
|||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
import org.koitharu.kotatsu.parsers.model.*
|
||||||
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.site.gattsu.GattsuParser
|
import org.koitharu.kotatsu.parsers.site.gattsu.GattsuParser
|
||||||
import org.koitharu.kotatsu.parsers.util.*
|
import org.koitharu.kotatsu.parsers.util.*
|
||||||
|
|
||||||
@MangaSourceParser("UNIVERSOHENTAI", "UniversoHentai", "pt", ContentType.HENTAI)
|
@MangaSourceParser("UNIVERSOHENTAI", "UniversoHentai", "pt", ContentType.HENTAI)
|
||||||
internal class UniversoHentai(context: MangaLoaderContext) :
|
internal class UniversoHentai(context: MangaLoaderContext) :
|
||||||
GattsuParser(context, MangaSource.UNIVERSOHENTAI, "universohentai.com") {
|
GattsuParser(context, MangaParserSource.UNIVERSOHENTAI, "universohentai.com") {
|
||||||
|
|
||||||
override val tagPrefix = "category"
|
override val tagPrefix = "category"
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import java.util.*
|
|||||||
|
|
||||||
internal abstract class GuyaParser(
|
internal abstract class GuyaParser(
|
||||||
context: MangaLoaderContext,
|
context: MangaLoaderContext,
|
||||||
source: MangaSource,
|
source: MangaParserSource,
|
||||||
domain: String,
|
domain: String,
|
||||||
pageSize: Int = 0,
|
pageSize: Int = 0,
|
||||||
) : PagedMangaParser(context, source, pageSize) {
|
) : PagedMangaParser(context, source, pageSize) {
|
||||||
@@ -91,7 +91,8 @@ internal abstract class GuyaParser(
|
|||||||
MangaChapter(
|
MangaChapter(
|
||||||
id = generateUid(url),
|
id = generateUid(url),
|
||||||
name = chapter.getString("title"),
|
name = chapter.getString("title"),
|
||||||
number = i,
|
number = i.toFloat(),
|
||||||
|
volume = 0,
|
||||||
url = url,
|
url = url,
|
||||||
scanlator = null,
|
scanlator = null,
|
||||||
uploadDate = 0,
|
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.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
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
|
import org.koitharu.kotatsu.parsers.site.guya.GuyaParser
|
||||||
|
|
||||||
@MangaSourceParser("MAHOUSHOUJOBU", "MahouShoujobu")
|
@MangaSourceParser("MAHOUSHOUJOBU", "MahouShoujobu")
|
||||||
internal class MahouShoujobu(context: MangaLoaderContext) :
|
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.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
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
|
import org.koitharu.kotatsu.parsers.site.guya.GuyaParser
|
||||||
|
|
||||||
@MangaSourceParser("DANKE", "DankeFursLesen", "en")
|
@MangaSourceParser("DANKE", "DankeFursLesen", "en")
|
||||||
internal class Danke(context: MangaLoaderContext) :
|
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