Merge pull request #31 from KotatsuApp/master

[pull] master from KotatsuApp:master
master
Naga 2 years ago committed by GitHub
commit 32eab42c26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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

@ -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,9 +104,14 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
filter.locale?.let { filter.locale?.let {
append("&langs=") append("&langs=")
if (it.language == "in") {
append("id")
} else {
append(it.language) append(it.language)
} }
}
append("&genres=") append("&genres=")
if (filter.tags.isNotEmpty()) { if (filter.tags.isNotEmpty()) {
appendAll(filter.tags, ",") { it.key } appendAll(filter.tags, ",") { it.key }

@ -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

Loading…
Cancel
Save