diff --git a/kotatsu-parsers-ksp/src/main/kotlin/org/koitharu/kotatsu/parsers/ksp/ParserProcessor.kt b/kotatsu-parsers-ksp/src/main/kotlin/org/koitharu/kotatsu/parsers/ksp/ParserProcessor.kt
index d95f99b85..46f1293c1 100644
--- a/kotatsu-parsers-ksp/src/main/kotlin/org/koitharu/kotatsu/parsers/ksp/ParserProcessor.kt
+++ b/kotatsu-parsers-ksp/src/main/kotlin/org/koitharu/kotatsu/parsers/ksp/ParserProcessor.kt
@@ -70,6 +70,7 @@ class ParserProcessor(
package org.koitharu.kotatsu.parsers
import org.koitharu.kotatsu.parsers.model.MangaParserSource
+ import org.koitharu.kotatsu.parsers.core.MangaParserWrapper
internal fun MangaParserSource.newParser(context: MangaLoaderContext): MangaParser = when (this) {
@@ -98,10 +99,11 @@ class ParserProcessor(
factoryWriter?.write(
"""
MangaParserSource.DUMMY -> throw NotImplementedError("Manga parser ${'$'}name cannot be instantiated")
- }.also {
+ }.let {
require(it.source == this) {
"Cannot instantiate manga parser: ${'$'}name mapped to ${'$'}{it.source}"
}
+ MangaParserWrapper(it)
}
""".trimIndent(),
)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt
index cc3a920c1..d2fa0fa65 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt
@@ -1,116 +1,81 @@
package org.koitharu.kotatsu.parsers
-import androidx.annotation.CallSuper
import okhttp3.Headers
import okhttp3.HttpUrl
+import okhttp3.Interceptor
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
import org.koitharu.kotatsu.parsers.model.*
-import org.koitharu.kotatsu.parsers.network.OkHttpWebClient
-import org.koitharu.kotatsu.parsers.network.WebClient
-import org.koitharu.kotatsu.parsers.util.FaviconParser
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
import org.koitharu.kotatsu.parsers.util.LinkResolver
-import org.koitharu.kotatsu.parsers.util.RelatedMangaFinder
-import org.koitharu.kotatsu.parsers.util.domain
-import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
+import org.koitharu.kotatsu.parsers.util.convertToMangaSearchQuery
+import org.koitharu.kotatsu.parsers.util.toMangaListFilterCapabilities
import java.util.*
-public abstract class MangaParser @InternalParsersApi constructor(
- @property:InternalParsersApi public val context: MangaLoaderContext,
- public val source: MangaParserSource,
-) {
+public interface MangaParser : Interceptor {
+
+ public val source: MangaParserSource
/**
* Supported [SortOrder] variants. Must not be empty.
*
* For better performance use [EnumSet] for more than one item.
*/
- public abstract val availableSortOrders: Set
-
- public abstract val filterCapabilities: MangaListFilterCapabilities
-
- public val config: MangaSourceConfig by lazy { context.getConfig(source) }
-
- public open val sourceLocale: Locale
- get() = if (source.locale.isEmpty()) Locale.ROOT else Locale(source.locale)
-
- protected val isNsfwSource: Boolean = source.contentType == ContentType.HENTAI
-
- /**
- * Provide default domain and available alternatives, if any.
- *
- * Never hardcode domain in requests, use [domain] instead.
- */
- @InternalParsersApi
- public abstract val configKeyDomain: ConfigKey.Domain
-
- protected open val userAgentKey: ConfigKey.UserAgent = ConfigKey.UserAgent(context.getDefaultUserAgent())
+ public val availableSortOrders: Set
- public open fun getRequestHeaders(): Headers = Headers.Builder()
- .add("User-Agent", config[userAgentKey])
- .build()
+ public val searchQueryCapabilities: MangaSearchQueryCapabilities
- /**
- * Used as fallback if value of `order` passed to [getList] is null
- */
- public open val defaultSortOrder: SortOrder
- get() {
- val supported = availableSortOrders
- return SortOrder.entries.first { it in supported }
- }
+ public val config: MangaSourceConfig
- @JvmField
- protected val webClient: WebClient = OkHttpWebClient(context.httpClient, source)
+ public val domain: String
- /**
- * Parse list of manga by specified criteria
- *
- * @param offset starting from 0 and used for pagination.
- * Note than passed value may not be divisible by internal page size, so you should adjust it manually.
- * @param order one of [availableSortOrders] or [defaultSortOrder] for default value
- * @param filter is a set of filter rules
- */
- public abstract suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List
+ public suspend fun getList(query: MangaSearchQuery): List
/**
* Parse details for [Manga]: chapters list, description, large cover, etc.
* Must return the same manga, may change any fields excepts id, url and source
* @see Manga.copy
*/
- public abstract suspend fun getDetails(manga: Manga): Manga
+ public suspend fun getDetails(manga: Manga): Manga
/**
* Parse pages list for specified chapter.
* @see MangaPage for details
*/
- public abstract suspend fun getPages(chapter: MangaChapter): List
+ public suspend fun getPages(chapter: MangaChapter): List
/**
* Fetch direct link to the page image.
*/
- public open suspend fun getPageUrl(page: MangaPage): String = page.url.toAbsoluteUrl(domain)
+ public suspend fun getPageUrl(page: MangaPage): String
- public abstract suspend fun getFilterOptions(): MangaListFilterOptions
+ public suspend fun getFilterOptions(): MangaListFilterOptions
/**
* Parse favicons from the main page of the source`s website
*/
- public open suspend fun getFavicons(): Favicons {
- return FaviconParser(webClient, domain).parseFavicons()
- }
+ public suspend fun getFavicons(): Favicons
- @CallSuper
- public open fun onCreateConfig(keys: MutableCollection>) {
- keys.add(configKeyDomain)
- }
+ public fun onCreateConfig(keys: MutableCollection>)
- public open suspend fun getRelatedManga(seed: Manga): List {
- return RelatedMangaFinder(listOf(this)).invoke(seed)
- }
+ public suspend fun getRelatedManga(seed: Manga): List
+
+ public fun getRequestHeaders(): Headers
/**
* Return [Manga] object by web link to it
* @see [Manga.publicUrl]
*/
- internal open suspend fun resolveLink(resolver: LinkResolver, link: HttpUrl): Manga? = null
+ @InternalParsersApi
+ public suspend fun resolveLink(resolver: LinkResolver, link: HttpUrl): Manga?
+
+ @Deprecated("Use getList(query: MangaSearchQuery) instead")
+ public suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List {
+ return getList(convertToMangaSearchQuery(offset, order, filter))
+ }
+
+ @Deprecated("Please check searchQueryCapabilities")
+ public val filterCapabilities: MangaListFilterCapabilities
+ get() = searchQueryCapabilities.toMangaListFilterCapabilities()
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/core/AbstractMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/AbstractMangaParser.kt
new file mode 100644
index 000000000..28e58ba7a
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/AbstractMangaParser.kt
@@ -0,0 +1,91 @@
+package org.koitharu.kotatsu.parsers.core
+
+import androidx.annotation.CallSuper
+import okhttp3.Headers
+import okhttp3.HttpUrl
+import okhttp3.Interceptor
+import okhttp3.Response
+import org.koitharu.kotatsu.parsers.InternalParsersApi
+import org.koitharu.kotatsu.parsers.MangaLoaderContext
+import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.config.ConfigKey
+import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
+import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.network.OkHttpWebClient
+import org.koitharu.kotatsu.parsers.network.WebClient
+import org.koitharu.kotatsu.parsers.util.FaviconParser
+import org.koitharu.kotatsu.parsers.util.LinkResolver
+import org.koitharu.kotatsu.parsers.util.RelatedMangaFinder
+import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
+import java.util.*
+
+@InternalParsersApi
+public abstract class AbstractMangaParser @InternalParsersApi constructor(
+ @property:InternalParsersApi public val context: MangaLoaderContext,
+ public override val source: MangaParserSource,
+) : MangaParser {
+
+ public override val config: MangaSourceConfig by lazy { context.getConfig(source) }
+
+ public open val sourceLocale: Locale
+ get() = if (source.locale.isEmpty()) Locale.ROOT else Locale(source.locale)
+
+ /**
+ * Provide default domain and available alternatives, if any.
+ *
+ * Never hardcode domain in requests, use [domain] instead.
+ */
+ @InternalParsersApi
+ public abstract val configKeyDomain: ConfigKey.Domain
+
+ protected open val userAgentKey: ConfigKey.UserAgent = ConfigKey.UserAgent(context.getDefaultUserAgent())
+
+ @Deprecated("Override intercept() instead")
+ override fun getRequestHeaders(): Headers = Headers.Builder()
+ .add("User-Agent", config[userAgentKey])
+ .build()
+
+ /**
+ * Used as fallback if value of `order` passed to [getList] is null
+ */
+ public open val defaultSortOrder: SortOrder
+ get() {
+ val supported = availableSortOrders
+ return SortOrder.entries.first { it in supported }
+ }
+
+ override val domain: String
+ get() = config[configKeyDomain]
+
+ @JvmField
+ protected val webClient: WebClient = OkHttpWebClient(context.httpClient, source)
+
+ /**
+ * Fetch direct link to the page image.
+ */
+ public override suspend fun getPageUrl(page: MangaPage): String = page.url.toAbsoluteUrl(domain)
+
+ /**
+ * Parse favicons from the main page of the source`s website
+ */
+ public override suspend fun getFavicons(): Favicons {
+ return FaviconParser(webClient, domain).parseFavicons()
+ }
+
+ @CallSuper
+ public override fun onCreateConfig(keys: MutableCollection>) {
+ keys.add(configKeyDomain)
+ }
+
+ public override suspend fun getRelatedManga(seed: Manga): List {
+ return RelatedMangaFinder(listOf(this)).invoke(seed)
+ }
+
+ /**
+ * Return [Manga] object by web link to it
+ * @see [Manga.publicUrl]
+ */
+ override suspend fun resolveLink(resolver: LinkResolver, link: HttpUrl): Manga? = null
+
+ override fun intercept(chain: Interceptor.Chain): Response = chain.proceed(chain.request())
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacyMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacyMangaParser.kt
new file mode 100644
index 000000000..18895d2f7
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacyMangaParser.kt
@@ -0,0 +1,110 @@
+package org.koitharu.kotatsu.parsers.core
+
+import androidx.annotation.CallSuper
+import okhttp3.Headers
+import okhttp3.HttpUrl
+import okhttp3.Interceptor
+import okhttp3.Response
+import org.koitharu.kotatsu.parsers.InternalParsersApi
+import org.koitharu.kotatsu.parsers.MangaLoaderContext
+import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.config.ConfigKey
+import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
+import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
+import org.koitharu.kotatsu.parsers.network.OkHttpWebClient
+import org.koitharu.kotatsu.parsers.network.WebClient
+import org.koitharu.kotatsu.parsers.util.*
+import java.util.*
+
+@Suppress("OVERRIDE_DEPRECATION")
+@InternalParsersApi
+public abstract class LegacyMangaParser @InternalParsersApi constructor(
+ @property:InternalParsersApi public val context: MangaLoaderContext,
+ public override val source: MangaParserSource,
+) : MangaParser {
+
+ public final override val searchQueryCapabilities: MangaSearchQueryCapabilities
+ get() = filterCapabilities.toMangaSearchQueryCapabilities()
+
+ abstract override val filterCapabilities: MangaListFilterCapabilities
+
+ public override val config: MangaSourceConfig by lazy { context.getConfig(source) }
+
+ public open val sourceLocale: Locale
+ get() = if (source.locale.isEmpty()) Locale.ROOT else Locale(source.locale)
+
+ protected val isNsfwSource: Boolean = source.contentType == ContentType.HENTAI
+
+ /**
+ * Provide default domain and available alternatives, if any.
+ *
+ * Never hardcode domain in requests, use [domain] instead.
+ */
+ @InternalParsersApi
+ public abstract val configKeyDomain: ConfigKey.Domain
+
+ protected open val userAgentKey: ConfigKey.UserAgent = ConfigKey.UserAgent(context.getDefaultUserAgent())
+
+ override fun getRequestHeaders(): Headers = Headers.Builder()
+ .add("User-Agent", config[userAgentKey])
+ .build()
+
+ /**
+ * Used as fallback if value of `order` passed to [getList] is null
+ */
+ public open val defaultSortOrder: SortOrder
+ get() {
+ val supported = availableSortOrders
+ return SortOrder.entries.first { it in supported }
+ }
+
+ override val domain: String
+ get() = config[configKeyDomain]
+
+ @JvmField
+ protected val webClient: WebClient = OkHttpWebClient(context.httpClient, source)
+
+ /**
+ * Search list of manga by specified searchQuery
+ *
+ * @param query searchQuery
+ */
+ public final override suspend fun getList(query: MangaSearchQuery): List = getList(
+ offset = query.offset,
+ order = query.order ?: defaultSortOrder,
+ filter = convertToMangaListFilter(query),
+ )
+
+ abstract override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List
+
+ /**
+ * Fetch direct link to the page image.
+ */
+ public override suspend fun getPageUrl(page: MangaPage): String = page.url.toAbsoluteUrl(domain)
+
+ /**
+ * Parse favicons from the main page of the source`s website
+ */
+ public override suspend fun getFavicons(): Favicons {
+ return FaviconParser(webClient, domain).parseFavicons()
+ }
+
+ @CallSuper
+ public override fun onCreateConfig(keys: MutableCollection>) {
+ keys.add(configKeyDomain)
+ }
+
+ public override suspend fun getRelatedManga(seed: Manga): List {
+ return RelatedMangaFinder(listOf(this)).invoke(seed)
+ }
+
+ /**
+ * Return [Manga] object by web link to it
+ * @see [Manga.publicUrl]
+ */
+ override suspend fun resolveLink(resolver: LinkResolver, link: HttpUrl): Manga? = null
+
+ override fun intercept(chain: Interceptor.Chain): Response = chain.proceed(chain.request())
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacyPagedMangaParser.kt
similarity index 85%
rename from src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt
rename to src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacyPagedMangaParser.kt
index 02492f43c..57c51d39f 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/PagedMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacyPagedMangaParser.kt
@@ -1,6 +1,8 @@
-package org.koitharu.kotatsu.parsers
+package org.koitharu.kotatsu.parsers.core
import androidx.annotation.VisibleForTesting
+import org.koitharu.kotatsu.parsers.InternalParsersApi
+import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaParserSource
@@ -8,12 +10,12 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.Paginator
@InternalParsersApi
-public abstract class PagedMangaParser(
+public abstract class LegacyPagedMangaParser(
context: MangaLoaderContext,
source: MangaParserSource,
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) @JvmField public val pageSize: Int,
searchPageSize: Int = pageSize,
-) : MangaParser(context, source) {
+) : LegacyMangaParser(context, source) {
@JvmField
protected val paginator: Paginator = Paginator(pageSize)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacySinglePageMangaParser.kt
similarity index 70%
rename from src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt
rename to src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacySinglePageMangaParser.kt
index 6e2425af5..898280ce9 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/SinglePageMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/LegacySinglePageMangaParser.kt
@@ -1,15 +1,17 @@
-package org.koitharu.kotatsu.parsers
+package org.koitharu.kotatsu.parsers.core
+import org.koitharu.kotatsu.parsers.InternalParsersApi
+import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.SortOrder
@InternalParsersApi
-public abstract class SinglePageMangaParser(
+public abstract class LegacySinglePageMangaParser(
context: MangaLoaderContext,
source: MangaParserSource,
-) : MangaParser(context, source) {
+) : LegacyMangaParser(context, source) {
final override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List {
if (offset > 0) {
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/core/MangaParserWrapper.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/MangaParserWrapper.kt
new file mode 100644
index 000000000..4c13da50d
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/MangaParserWrapper.kt
@@ -0,0 +1,64 @@
+package org.koitharu.kotatsu.parsers.core
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import okhttp3.Interceptor
+import okhttp3.Request
+import okhttp3.Response
+import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.util.mergeWith
+
+internal class MangaParserWrapper(
+ private val delegate: MangaParser,
+) : MangaParser by delegate {
+
+ override suspend fun getList(searchQuery: MangaSearchQuery): List = withContext(Dispatchers.Default) {
+ if (!searchQuery.skipValidation) {
+ searchQueryCapabilities.validate(searchQuery)
+ }
+ delegate.getList(searchQuery)
+ }
+
+ override suspend fun getDetails(manga: Manga): Manga = withContext(Dispatchers.Default) {
+ delegate.getDetails(manga)
+ }
+
+ override suspend fun getPages(chapter: MangaChapter): List = withContext(Dispatchers.Default) {
+ delegate.getPages(chapter)
+ }
+
+ override suspend fun getPageUrl(page: MangaPage): String = withContext(Dispatchers.Default) {
+ delegate.getPageUrl(page)
+ }
+
+ override suspend fun getFilterOptions(): MangaListFilterOptions = withContext(Dispatchers.Default) {
+ delegate.getFilterOptions()
+ }
+
+ override suspend fun getFavicons(): Favicons = withContext(Dispatchers.Default) {
+ delegate.getFavicons()
+ }
+
+ override suspend fun getRelatedManga(seed: Manga): List = withContext(Dispatchers.Default) {
+ delegate.getRelatedManga(seed)
+ }
+
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val request = chain.request()
+ val headers = request.headers.newBuilder()
+ .mergeWith(delegate.getRequestHeaders(), replaceExisting = false)
+ .build()
+ val newRequest = request.newBuilder().headers(headers).build()
+ return delegate.intercept(ProxyChain(chain, newRequest))
+ }
+
+ private class ProxyChain(
+ private val delegate: Interceptor.Chain,
+ private val request: Request,
+ ) : Interceptor.Chain by delegate {
+
+ override fun request(): Request = request
+ }
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/core/PagedMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/PagedMangaParser.kt
new file mode 100644
index 000000000..87c994b09
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/PagedMangaParser.kt
@@ -0,0 +1,56 @@
+package org.koitharu.kotatsu.parsers.core
+
+import androidx.annotation.VisibleForTesting
+import org.koitharu.kotatsu.parsers.InternalParsersApi
+import org.koitharu.kotatsu.parsers.MangaLoaderContext
+import org.koitharu.kotatsu.parsers.model.Manga
+import org.koitharu.kotatsu.parsers.model.MangaParserSource
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.SearchableField
+import org.koitharu.kotatsu.parsers.util.Paginator
+
+@InternalParsersApi
+public abstract class PagedMangaParser(
+ context: MangaLoaderContext,
+ source: MangaParserSource,
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) @JvmField public val pageSize: Int,
+ searchPageSize: Int = pageSize,
+) : AbstractMangaParser(context, source) {
+
+ @JvmField
+ protected val paginator: Paginator = Paginator(pageSize)
+
+ @JvmField
+ protected val searchPaginator: Paginator = Paginator(searchPageSize)
+
+ final override suspend fun getList(query: MangaSearchQuery): List {
+ var containTitleNameCriteria = false
+ query.criteria.forEach {
+ if (it.field == SearchableField.TITLE_NAME) {
+ containTitleNameCriteria = true
+ }
+ }
+
+ return searchManga(
+ paginator = if (containTitleNameCriteria) {
+ paginator
+ } else {
+ searchPaginator
+ },
+ query = query,
+ )
+ }
+
+ public abstract suspend fun getListPage(query: MangaSearchQuery, page: Int): List
+
+ private suspend fun searchManga(
+ paginator: Paginator,
+ query: MangaSearchQuery,
+ ): List {
+ val offset: Int = query.offset
+ val page = paginator.getPage(offset)
+ val list = getListPage(query, page)
+ paginator.onListReceived(offset, page, list.size)
+ return list
+ }
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/core/SinglePageMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/SinglePageMangaParser.kt
new file mode 100644
index 000000000..1934cd5d5
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/core/SinglePageMangaParser.kt
@@ -0,0 +1,23 @@
+package org.koitharu.kotatsu.parsers.core
+
+import org.koitharu.kotatsu.parsers.InternalParsersApi
+import org.koitharu.kotatsu.parsers.MangaLoaderContext
+import org.koitharu.kotatsu.parsers.model.Manga
+import org.koitharu.kotatsu.parsers.model.MangaParserSource
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+
+@InternalParsersApi
+public abstract class SinglePageMangaParser(
+ context: MangaLoaderContext,
+ source: MangaParserSource,
+) : AbstractMangaParser(context, source) {
+
+ final override suspend fun getList(query: MangaSearchQuery): List {
+ if (query.offset > 0) {
+ return emptyList()
+ }
+ return getSinglePageList(query)
+ }
+
+ public abstract suspend fun getSinglePageList(searchQuery: MangaSearchQuery): List
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Manga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Manga.kt
index 9863b7bea..17c9c80c9 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Manga.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/Manga.kt
@@ -2,10 +2,9 @@ package org.koitharu.kotatsu.parsers.model
import androidx.collection.ArrayMap
import org.koitharu.kotatsu.parsers.util.findById
-import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
-public data class Manga constructor(
+public data class Manga(
/**
* Unique identifier for manga
*/
@@ -71,6 +70,91 @@ public data class Manga constructor(
*/
@JvmField public val source: MangaSource,
) {
+
+ @Deprecated("Use other constructor")
+ public constructor(
+ /**
+ * Unique identifier for manga
+ */
+ id: Long,
+ /**
+ * Manga title, human-readable
+ */
+ title: String,
+ /**
+ * Alternative title (for example on other language), may be null
+ */
+ altTitle: String?,
+ /**
+ * Relative url to manga (**without** a domain) or any other uri.
+ * Used principally in parsers
+ */
+ url: String,
+ /**
+ * Absolute url to manga, must be ready to open in browser
+ */
+ publicUrl: String,
+ /**
+ * Normalized manga rating, must be in range of 0..1 or [RATING_UNKNOWN] if rating s unknown
+ * @see hasRating
+ */
+ rating: Float,
+ /**
+ * Indicates that manga may contain sensitive information (18+, NSFW)
+ */
+ isNsfw: Boolean,
+ /**
+ * Absolute link to the cover
+ * @see largeCoverUrl
+ */
+ coverUrl: String?,
+ /**
+ * Tags (genres) of the manga
+ */
+ tags: Set,
+ /**
+ * Manga status (ongoing, finished) or null if unknown
+ */
+ state: MangaState?,
+ /**
+ * Authors of the manga
+ */
+ author: String?,
+ /**
+ * Large cover url (absolute), null if is no large cover
+ * @see coverUrl
+ */
+ largeCoverUrl: String? = null,
+ /**
+ * Manga description, may be html or null
+ */
+ description: String? = null,
+ /**
+ * List of chapters
+ */
+ chapters: List? = null,
+ /**
+ * Manga source
+ */
+ source: MangaSource,
+ ) : this(
+ id = id,
+ title = title,
+ altTitle = altTitle?.nullIfEmpty(),
+ url = url,
+ publicUrl = publicUrl,
+ rating = rating,
+ contentRating = if (isNsfw) ContentRating.ADULT else null,
+ coverUrl = coverUrl?.nullIfEmpty(),
+ tags = tags,
+ state = state,
+ authors = setOfNotNull(author),
+ largeCoverUrl = largeCoverUrl?.nullIfEmpty(),
+ description = description?.nullIfEmpty(),
+ chapters = chapters,
+ source = source,
+ )
+
/**
* Author of the manga, may be null
*/
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilter.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilter.kt
index e996a5884..771f22874 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilter.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilter.kt
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.model
import java.util.*
+@Deprecated("Please check new searchManga method and MangaSearchQuery class")
public data class MangaListFilter(
@JvmField val query: String? = null,
@JvmField val tags: Set = emptySet(),
@@ -42,4 +43,47 @@ public data class MangaListFilter(
@JvmStatic
public val EMPTY: MangaListFilter = MangaListFilter()
}
+
+ internal class Builder {
+ private var query: String? = null
+ private val tags: MutableSet = mutableSetOf()
+ private val tagsExclude: MutableSet = mutableSetOf()
+ private var locale: Locale? = null
+ private var originalLocale: Locale? = null
+ private val states: MutableSet = mutableSetOf()
+ private val contentRating: MutableSet = mutableSetOf()
+ private val types: MutableSet = mutableSetOf()
+ private val demographics: MutableSet = mutableSetOf()
+ private var year: Int = YEAR_UNKNOWN
+ private var yearFrom: Int = YEAR_UNKNOWN
+ private var yearTo: Int = YEAR_UNKNOWN
+
+ fun query(query: String?): Builder = apply { this.query = query }
+ fun addTag(tag: MangaTag): Builder = apply { tags.add(tag) }
+ fun addTags(tags: Collection): Builder = apply { this.tags.addAll(tags) }
+ fun excludeTag(tag: MangaTag): Builder = apply { tagsExclude.add(tag) }
+ fun excludeTags(tags: Collection): Builder = apply { this.tagsExclude.addAll(tags) }
+ fun locale(locale: Locale?): Builder = apply { this.locale = locale }
+ fun originalLocale(locale: Locale?): Builder = apply { this.originalLocale = locale }
+ fun addState(state: MangaState): Builder = apply { states.add(state) }
+ fun addStates(states: Collection): Builder = apply { this.states.addAll(states) }
+ fun addContentRating(rating: ContentRating): Builder = apply { contentRating.add(rating) }
+ fun addContentRatings(ratings: Collection): Builder =
+ apply { this.contentRating.addAll(ratings) }
+
+ fun addType(type: ContentType): Builder = apply { types.add(type) }
+ fun addTypes(types: Collection): Builder = apply { this.types.addAll(types) }
+ fun addDemographic(demographic: Demographic): Builder = apply { demographics.add(demographic) }
+ fun addDemographics(demographics: Collection): Builder =
+ apply { this.demographics.addAll(demographics) }
+
+ fun year(year: Int): Builder = apply { this.year = year }
+ fun yearFrom(year: Int): Builder = apply { this.yearFrom = year }
+ fun yearTo(year: Int): Builder = apply { this.yearTo = year }
+
+ fun build(): MangaListFilter = MangaListFilter(
+ query, tags, tagsExclude, locale, originalLocale, states,
+ contentRating, types, demographics, year, yearFrom, yearTo,
+ )
+ }
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilterCapabilities.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilterCapabilities.kt
index d0daa06d3..00a479cdb 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilterCapabilities.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/MangaListFilterCapabilities.kt
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.model
import org.koitharu.kotatsu.parsers.InternalParsersApi
+@Deprecated("Please check new MangaSearchQueryCapabilities class")
public data class MangaListFilterCapabilities @InternalParsersApi constructor(
/**
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQuery.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQuery.kt
new file mode 100644
index 000000000..ae03fdee6
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQuery.kt
@@ -0,0 +1,89 @@
+package org.koitharu.kotatsu.parsers.model.search
+
+import androidx.collection.ArrayMap
+import androidx.collection.ArraySet
+import org.koitharu.kotatsu.parsers.model.SortOrder
+
+/**
+ * Represents a search query for filtering and sorting manga search results.
+ * This class is immutable and must be constructed using the [Builder].
+ *
+ * @property criteria The set of search criteria applied to the query.
+ * @property order The sorting order for the results (optional).
+ * @property offset The offset number for paginated search results (optional).
+ */
+
+@ConsistentCopyVisibility
+public data class MangaSearchQuery private constructor(
+ @JvmField public val criteria: Set>,
+ @JvmField public val order: SortOrder?,
+ @JvmField public val offset: Int,
+ @JvmField public val skipValidation: Boolean,
+) {
+
+ public fun newBuilder(): Builder = Builder(this)
+
+ public class Builder {
+
+ private val criteria = ArraySet>()
+ private var order: SortOrder? = null
+ private var offset: Int = 0
+ private var skipValidation: Boolean = false
+
+ public constructor()
+
+ public constructor(query: MangaSearchQuery) : this() {
+ criteria.addAll(query.criteria)
+ order = query.order
+ offset = query.offset
+ }
+
+ public fun criterion(criterion: QueryCriteria<*>): Builder = apply { criteria.add(criterion) }
+
+ public fun order(order: SortOrder?): Builder = apply { this.order = order }
+
+ public fun offset(offset: Int): Builder = apply { this.offset = offset }
+
+ public fun skipValidation(skip: Boolean): Builder = apply { this.skipValidation = skip }
+
+ @Throws(IllegalArgumentException::class)
+ public fun build(): MangaSearchQuery {
+ return MangaSearchQuery(deduplicateCriteria(criteria), order, offset, skipValidation)
+ }
+
+ private fun deduplicateCriteria(criteria: Set>): Set> {
+ val uniqueCriteria =
+ ArrayMap>>, QueryCriteria<*>>(criteria.size)
+
+ for (criterion in criteria) {
+ val key = criterion.field to criterion::class.java
+ val existing = uniqueCriteria[key]
+
+ when {
+ existing == null -> uniqueCriteria[key] = criterion
+
+ existing is QueryCriteria.Include<*> && criterion is QueryCriteria.Include<*> -> {
+ uniqueCriteria[key] =
+ QueryCriteria.Include(criterion.field, existing.values union criterion.values)
+ }
+
+ existing is QueryCriteria.Exclude<*> && criterion is QueryCriteria.Exclude<*> -> {
+ uniqueCriteria[key] =
+ QueryCriteria.Exclude(criterion.field, existing.values union criterion.values)
+ }
+
+ else -> throw IllegalArgumentException(
+ "Match and Range have only one criterion per type, but found duplicates for: ${criterion.field} in ${criterion::class.simpleName}",
+ )
+ }
+ }
+
+ return uniqueCriteria.values.toSet()
+ }
+ }
+
+ public companion object {
+
+ public val EMPTY: MangaSearchQuery = MangaSearchQuery(emptySet(), null, 0, false)
+ }
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt
new file mode 100644
index 000000000..818cc16cb
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt
@@ -0,0 +1,51 @@
+package org.koitharu.kotatsu.parsers.model.search
+
+import androidx.collection.ArraySet
+import org.koitharu.kotatsu.parsers.InternalParsersApi
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.util.mapToSet
+
+@ExposedCopyVisibility
+public data class MangaSearchQueryCapabilities internal constructor(
+ public val capabilities: Set,
+) {
+
+ public constructor(vararg capabilities: SearchCapability) : this(ArraySet(capabilities))
+
+ @InternalParsersApi
+ public fun validate(query: MangaSearchQuery) {
+ val strictFields = capabilities.filter { !it.otherCriteria }.mapToSet { it.field }
+ val usedStrictFields = query.criteria.mapToSet { it.field }.intersect(strictFields)
+
+ if (usedStrictFields.isNotEmpty() && query.criteria.size > 1) {
+ throw IllegalArgumentException(
+ "Query contains multiple criteria, but at least one field (${usedStrictFields.joinToString()}) does not support multiple criteria.",
+ )
+ }
+
+ for (criterion in query.criteria) {
+ val capability = capabilities.find { it.field == criterion.field }
+ ?: throw IllegalArgumentException("Unsupported search field: ${criterion.field}")
+
+ if (criterion::class !in capability.criteriaTypes) {
+ throw IllegalArgumentException(
+ "Unsupported search criterion: ${criterion::class.simpleName} for field ${criterion.field}",
+ )
+ }
+
+ // Ensure single value per criterion if supportMultiValue is false
+ if (!capability.multiValue) {
+ when (criterion) {
+ is Include<*> -> if (criterion.values.size > 1)
+ throw IllegalArgumentException("Multiple values are not allowed for field ${criterion.field}")
+
+ is Exclude<*> -> if (criterion.values.size > 1)
+ throw IllegalArgumentException("Multiple values are not allowed for field ${criterion.field}")
+
+ is Range<*> -> {} // Range is always valid (from, to)
+ is Match<*> -> {} // Match always has a single value
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/QueryCriteria.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/QueryCriteria.kt
new file mode 100644
index 000000000..ffaf57595
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/QueryCriteria.kt
@@ -0,0 +1,59 @@
+package org.koitharu.kotatsu.parsers.model.search
+
+/**
+ * Represents a generic search criterion used for filtering manga search results.
+ * Each criterion applies a specific condition to a [SearchableField] and operates on values of type [T].
+ *
+ * @param T The type of value associated with the search criterion.
+ * @property field The field to which this search criterion applies.
+ */
+public sealed interface QueryCriteria {
+
+ public val field: SearchableField
+
+ override fun equals(other: Any?): Boolean
+
+ override fun hashCode(): Int
+
+ public data class Include(
+ public override val field: SearchableField,
+ @JvmField public val values: Set,
+ ) : QueryCriteria {
+
+ init {
+ check(values.all { x -> field.type.isInstance(x) })
+ }
+ }
+
+ public data class Exclude(
+ public override val field: SearchableField,
+ @JvmField public val values: Set,
+ ) : QueryCriteria {
+
+ init {
+ check(values.all { x -> field.type.isInstance(x) })
+ }
+ }
+
+ public data class Range>(
+ public override val field: SearchableField,
+ @JvmField public val from: T,
+ @JvmField public val to: T,
+ ) : QueryCriteria {
+
+ init {
+ check(field.type.isInstance(from))
+ check(field.type.isInstance(to))
+ }
+ }
+
+ public data class Match(
+ public override val field: SearchableField,
+ @JvmField public val value: T,
+ ) : QueryCriteria {
+
+ init {
+ check(field.type.isInstance(value))
+ }
+ }
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchCapability.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchCapability.kt
new file mode 100644
index 000000000..097986fab
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchCapability.kt
@@ -0,0 +1,10 @@
+package org.koitharu.kotatsu.parsers.model.search
+
+import kotlin.reflect.KClass
+
+public data class SearchCapability (
+ @JvmField public val field: SearchableField,
+ @JvmField public val criteriaTypes: Set>>,
+ @JvmField public val multiValue: Boolean,
+ @JvmField public val otherCriteria: Boolean,
+)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchableField.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchableField.kt
new file mode 100644
index 000000000..d14e5c165
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchableField.kt
@@ -0,0 +1,23 @@
+package org.koitharu.kotatsu.parsers.model.search
+
+import org.koitharu.kotatsu.parsers.model.*
+import java.util.*
+
+/**
+ * Represents the various fields that can be used for searching manga.
+ * Each field is associated with a specific data type that defines its expected values.
+ *
+ * @property type The Java class representing the expected type of values for this field.
+ */
+public enum class SearchableField(public val type: Class<*>) {
+ TITLE_NAME(String::class.java),
+ TAG(MangaTag::class.java),
+ AUTHOR(MangaTag::class.java),
+ LANGUAGE(Locale::class.java),
+ ORIGINAL_LANGUAGE(Locale::class.java),
+ STATE(MangaState::class.java),
+ CONTENT_TYPE(ContentType::class.java),
+ CONTENT_RATING(ContentRating::class.java),
+ DEMOGRAPHIC(Demographic::class.java),
+ PUBLICATION_YEAR(Int::class.java);
+}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt
index 4b50a3733..d06cbd8cf 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt
@@ -7,7 +7,7 @@ import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
@@ -20,7 +20,7 @@ import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
@MangaSourceParser("BATOTO", "Bato.To")
-internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
+internal class BatoToParser(context: MangaLoaderContext) : LegacyPagedMangaParser(
context = context,
source = MangaParserSource.BATOTO,
pageSize = 60,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt
index 2a454e06b..2da65279a 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt
@@ -7,7 +7,7 @@ import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -20,7 +20,7 @@ private const val CHAPTERS_LIMIT = 99999
@MangaSourceParser("COMICK_FUN", "ComicK")
internal class ComickFunParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.COMICK_FUN, 20) {
+ LegacyPagedMangaParser(context, MangaParserSource.COMICK_FUN, 20) {
override val configKeyDomain = ConfigKey.Domain("comick.io")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ExHentaiParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ExHentaiParser.kt
index 3f76ebc4a..d37a9ac64 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ExHentaiParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ExHentaiParser.kt
@@ -13,7 +13,7 @@ import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.exception.TooManyRequestExceptions
@@ -31,7 +31,7 @@ private val TAG_PREFIXES = arrayOf("male:", "female:", "other:")
@MangaSourceParser("EXHENTAI", "ExHentai", type = ContentType.HENTAI)
internal class ExHentaiParser(
context: MangaLoaderContext,
-) : PagedMangaParser(context, MangaParserSource.EXHENTAI, pageSize = 25), MangaParserAuthProvider, Interceptor {
+) : LegacyPagedMangaParser(context, MangaParserSource.EXHENTAI, pageSize = 25), MangaParserAuthProvider, Interceptor {
override val availableSortOrders: Set = setOf(SortOrder.NEWEST)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/HitomiLaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/HitomiLaParser.kt
index 597813887..b8c7c1086 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/HitomiLaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/HitomiLaParser.kt
@@ -11,7 +11,7 @@ import org.json.JSONArray
import org.json.JSONObject
import org.jsoup.Jsoup
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
@@ -28,7 +28,7 @@ import kotlin.math.min
@OptIn(ExperimentalUnsignedTypes::class)
@MangaSourceParser("HITOMILA", "Hitomi.La", type = ContentType.HENTAI)
-internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.HITOMILA) {
+internal class HitomiLaParser(context: MangaLoaderContext) : LegacyMangaParser(context, MangaParserSource.HITOMILA) {
override val configKeyDomain = ConfigKey.Domain("hitomi.la")
override fun onCreateConfig(keys: MutableCollection>) {
@@ -516,14 +516,14 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
title = doc.selectFirstOrThrow("h1").text(),
url = id.toString(),
coverUrl =
- "https:" +
- doc.selectFirstOrThrow("picture > source")
- .attr("data-srcset")
- .substringBefore(" "),
+ "https:" +
+ doc.selectFirstOrThrow("picture > source")
+ .attr("data-srcset")
+ .substringBefore(" "),
publicUrl =
- doc.selectFirstOrThrow("h1 > a")
- .attrAsRelativeUrl("href")
- .toAbsoluteUrl(domain),
+ doc.selectFirstOrThrow("h1 > a")
+ .attrAsRelativeUrl("href")
+ .toAbsoluteUrl(domain),
authors = emptySet(),
tags = emptySet(),
contentRating = ContentRating.ADULT,
@@ -550,34 +550,35 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
return manga.copy(
title = json.getString("title"),
largeCoverUrl =
- json.getJSONArray("files").getJSONObject(0).let {
- val hash = it.getString("hash")
- val commonId = commonImageId()
- val imageId = imageIdFromHash(hash)
- val subDomain = 'a' + subdomainOffset(imageId)
-
- "https://${getDomain("${subDomain}a")}/webp/$commonId$imageId/$hash.webp"
- },
+ json.getJSONArray("files").getJSONObject(0).let {
+ val hash = it.getString("hash")
+ val commonId = commonImageId()
+ val imageId = imageIdFromHash(hash)
+ val subDomain = 'a' + subdomainOffset(imageId)
+
+ "https://${getDomain("${subDomain}a")}/webp/$commonId$imageId/$hash.webp"
+ },
authors = author?.let { setOf(it) } ?: emptySet(),
publicUrl = json.getString("galleryurl").toAbsoluteUrl(domain),
tags =
- buildSet {
- json.optJSONArray("characters")
- ?.mapToTags("character")
- ?.let(::addAll)
- json.optJSONArray("tags")
- ?.mapToTags("tag")
- ?.let(::addAll)
- json.optJSONArray("artists")
- ?.mapToTags("artist")
- ?.let(::addAll)
- json.optJSONArray("parodys")
- ?.mapToTags("parody")
- ?.let(::addAll)
- json.optJSONArray("groups")
- ?.mapToTags("group")
- ?.let(::addAll)
- },
+ buildSet
+ {
+ json.optJSONArray("characters")
+ ?.mapToTags("character")
+ ?.let(::addAll)
+ json.optJSONArray("tags")
+ ?.mapToTags("tag")
+ ?.let(::addAll)
+ json.optJSONArray("artists")
+ ?.mapToTags("artist")
+ ?.let(::addAll)
+ json.optJSONArray("parodys")
+ ?.mapToTags("parody")
+ ?.let(::addAll)
+ json.optJSONArray("groups")
+ ?.mapToTags("group")
+ ?.let(::addAll)
+ },
chapters = listOf(
MangaChapter(
id = generateUid(manga.url),
@@ -601,15 +602,15 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
mapJSON {
MangaTag(
title =
- it.getString(key).toCamelCase().let { title ->
- if (it.getStringOrNull("female")?.toIntOrNull() == 1) {
- "$title ♀"
- } else if (it.getStringOrNull("male")?.toIntOrNull() == 1) {
- "$title ♂"
- } else {
- title
- }
- },
+ it.getString(key).toCamelCase().let { title ->
+ if (it.getStringOrNull("female")?.toIntOrNull() == 1) {
+ "$title ♀"
+ } else if (it.getStringOrNull("male")?.toIntOrNull() == 1) {
+ "$title ♂"
+ } else {
+ title
+ }
+ },
key = it.getString("url").tagUrlToTag(),
source = source,
).let(tags::add)
@@ -666,7 +667,7 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
}
}
- // / --->
+// / --->
private var scriptLastRetrieval: Long = -1L
private val mutex = Mutex()
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ImHentai.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ImHentai.kt
index 4f7fc69d5..9a4c8149f 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ImHentai.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ImHentai.kt
@@ -7,7 +7,7 @@ import kotlinx.coroutines.coroutineScope
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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -15,7 +15,7 @@ import java.util.*
@MangaSourceParser("IMHENTAI", "ImHentai", type = ContentType.HENTAI)
internal class ImHentai(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.IMHENTAI, pageSize = 20) {
+ LegacyPagedMangaParser(context, MangaParserSource.IMHENTAI, pageSize = 20) {
override val availableSortOrders: Set =
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.RATING)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/LineWebtoonsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/LineWebtoonsParser.kt
index 6448e8fa1..ac577ebe5 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/LineWebtoonsParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/LineWebtoonsParser.kt
@@ -6,7 +6,7 @@ import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.NotFoundException
@@ -21,7 +21,7 @@ import javax.crypto.spec.SecretKeySpec
internal abstract class LineWebtoonsParser(
context: MangaLoaderContext,
source: MangaParserSource,
-) : MangaParser(context, source) {
+) : LegacyMangaParser(context, source) {
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt
index 5b5f84329..89d42b509 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt
@@ -7,13 +7,18 @@ import kotlinx.coroutines.coroutineScope
import okhttp3.HttpUrl
import org.json.JSONArray
import org.json.JSONObject
+import org.koitharu.kotatsu.parsers.core.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
-import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchCapability
+import org.koitharu.kotatsu.parsers.model.search.SearchableField
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.*
import java.text.SimpleDateFormat
@@ -29,7 +34,7 @@ private const val SERVER_DATA = "data"
private const val SERVER_DATA_SAVER = "data-saver"
@MangaSourceParser("MANGADEX", "MangaDex")
-internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.MANGADEX) {
+internal class MangaDexParser(context: MangaLoaderContext) : AbstractMangaParser(context, MangaParserSource.MANGADEX) {
override val configKeyDomain = ConfigKey.Domain("mangadex.org")
@@ -63,15 +68,68 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
SortOrder.RELEVANCE,
)
- override val filterCapabilities: MangaListFilterCapabilities
- get() = MangaListFilterCapabilities(
- isMultipleTagsSupported = true,
- isTagsExclusionSupported = true,
- isSearchSupported = true,
- isSearchWithFiltersSupported = true,
- isYearSupported = true,
- isOriginalLocaleSupported = true,
- isAuthorSearchSupported = true,
+ override val searchQueryCapabilities: MangaSearchQueryCapabilities
+ get() = MangaSearchQueryCapabilities(
+ SearchCapability(
+ field = TAG,
+ criteriaTypes = setOf(Include::class, Exclude::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = TITLE_NAME,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = STATE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = AUTHOR,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = CONTENT_TYPE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = CONTENT_RATING,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = DEMOGRAPHIC,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = ORIGINAL_LANGUAGE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = LANGUAGE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = PUBLICATION_YEAR,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = true,
+ ),
)
override suspend fun getFilterOptions(): MangaListFilterOptions = coroutineScope {
@@ -97,117 +155,114 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
)
}
- override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List {
- val domain = domain
- val url = buildString {
- append("https://api.")
- append(domain)
- append("/manga?limit=")
- append(PAGE_SIZE)
- append("&offset=")
- append(offset)
- append("&includes[]=cover_art&includes[]=author&includes[]=artist")
+ private fun SearchableField.toParamName(): String = when (this) {
+ TITLE_NAME -> "title"
+ TAG -> "includedTags[]"
+ AUTHOR -> "authors[]"
+ STATE -> "status[]"
+ CONTENT_TYPE -> "contentType[]"
+ CONTENT_RATING -> "contentRating[]"
+ DEMOGRAPHIC -> "publicationDemographic[]"
+ ORIGINAL_LANGUAGE -> "originalLanguage[]"
+ LANGUAGE -> "availableTranslatedLanguage[]"
+ PUBLICATION_YEAR -> "year"
+ }
- filter.query?.let {
- append("&title=")
- append(filter.query.urlEncoded())
- }
+ private fun Any?.toQueryParam(): String = when (this) {
+ is String -> urlEncoded()
+ is Locale -> if (language == "in") "id" else language
+ is MangaTag -> key
+ is MangaState -> when (this) {
+ MangaState.ONGOING -> "ongoing"
+ MangaState.FINISHED -> "completed"
+ MangaState.ABANDONED -> "cancelled"
+ MangaState.PAUSED -> "hiatus"
+ else -> ""
+ }
- filter.tags.forEach {
- append("&includedTags[]=")
- append(it.key)
- }
+ is ContentRating -> when (this) {
+ ContentRating.SAFE -> "safe"
+ // quick fix for double value
+ ContentRating.SUGGESTIVE -> "suggestive&contentRating[]=erotica"
+ ContentRating.ADULT -> "pornographic"
+ }
- filter.tagsExclude.forEach {
- append("&excludedTags[]=")
- append(it.key)
- }
+ is Demographic -> when (this) {
+ Demographic.SHOUNEN -> "shounen"
+ Demographic.SHOUJO -> "shoujo"
+ Demographic.SEINEN -> "seinen"
+ Demographic.JOSEI -> "josei"
+ Demographic.NONE -> "none"
+ else -> ""
+ }
- if (filter.contentRating.isNotEmpty()) {
- filter.contentRating.forEach {
- when (it) {
- ContentRating.SAFE -> append("&contentRating[]=safe")
- ContentRating.SUGGESTIVE -> append("&contentRating[]=suggestive&contentRating[]=erotica")
- ContentRating.ADULT -> append("&contentRating[]=pornographic")
+ is SortOrder -> when (this) {
+ SortOrder.UPDATED -> "[latestUploadedChapter]=desc"
+ SortOrder.UPDATED_ASC -> "[latestUploadedChapter]=asc"
+ SortOrder.RATING -> "[rating]=desc"
+ SortOrder.RATING_ASC -> "[rating]=asc"
+ SortOrder.ALPHABETICAL -> "[title]=asc"
+ SortOrder.ALPHABETICAL_DESC -> "[title]=desc"
+ SortOrder.NEWEST -> "[year]=desc"
+ SortOrder.NEWEST_ASC -> "[year]=asc"
+ SortOrder.POPULARITY -> "[followedCount]=desc"
+ SortOrder.POPULARITY_ASC -> "[followedCount]=asc"
+ SortOrder.ADDED -> "[createdAt]=desc"
+ SortOrder.ADDED_ASC -> "[createdAt]=asc"
+ SortOrder.RELEVANCE -> "&order[relevance]=desc"
+ else -> "[latestUploadedChapter]=desc"
+ }
- }
- }
- } else {
- append("&contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic")
- }
+ else -> this.toString().urlEncoded()
+ }
- if (!filter.author.isNullOrEmpty()) {
- append("&authorOrArtist=").append(getAuthorId(filter.author))
- }
+ private fun StringBuilder.appendCriterion(field: SearchableField, value: Any?, paramName: String? = null) {
+ val param = paramName ?: field.toParamName()
+ if (param.isNotBlank()) {
+ append("&$param=")
+ append(value.toQueryParam())
+ }
+ }
- append("&order")
- append(
- when (order) {
- SortOrder.UPDATED -> "[latestUploadedChapter]=desc"
- SortOrder.UPDATED_ASC -> "[latestUploadedChapter]=asc"
- SortOrder.RATING -> "[rating]=desc"
- SortOrder.RATING_ASC -> "[rating]=asc"
- SortOrder.ALPHABETICAL -> "[title]=asc"
- SortOrder.ALPHABETICAL_DESC -> "[title]=desc"
- SortOrder.NEWEST -> "[year]=desc"
- SortOrder.NEWEST_ASC -> "[year]=asc"
- SortOrder.POPULARITY -> "[followedCount]=desc"
- SortOrder.POPULARITY_ASC -> "[followedCount]=asc"
- SortOrder.ADDED -> "[createdAt]=desc"
- SortOrder.ADDED_ASC -> "[createdAt]=asc"
- SortOrder.RELEVANCE -> "&order[relevance]=desc"
- else -> "[latestUploadedChapter]=desc"
- },
- )
+ override suspend fun getList(query: MangaSearchQuery): List {
+ val url = buildString {
+ append("https://api.$domain/manga?limit=$PAGE_SIZE&offset=${query.offset}")
+ .append("&includes[]=cover_art&includes[]=author&includes[]=artist&includedTagsMode=AND&excludedTagsMode=OR")
+
+ var hasContentRating = false
+
+ query.criteria.forEach { criterion ->
+ when (criterion) {
+ is Include<*> -> {
+ if (criterion.field == CONTENT_RATING) {
+ hasContentRating = true
+ }
+ criterion.values.forEach { appendCriterion(criterion.field, it) }
+ }
- filter.states.forEach {
- append("&status[]=")
- when (it) {
- MangaState.ONGOING -> append("ongoing")
- MangaState.FINISHED -> append("completed")
- MangaState.ABANDONED -> append("cancelled")
- MangaState.PAUSED -> append("hiatus")
- else -> append("")
- }
- }
+ is Exclude<*> -> {
+ criterion.values.forEach { appendCriterion(criterion.field, it, "excludedTags[]") }
+ }
- filter.demographics.forEach {
- append("&publicationDemographic[]=")
- append(
- when (it) {
- Demographic.SHOUNEN -> "shounen"
- Demographic.SHOUJO -> "shoujo"
- Demographic.SEINEN -> "seinen"
- Demographic.JOSEI -> "josei"
- Demographic.NONE -> "none"
- else -> ""
- },
- )
- }
+ is Match<*> -> {
+ appendCriterion(criterion.field, criterion.value)
+ }
- filter.locale?.let {
- append("&availableTranslatedLanguage[]=")
- if (it.language == "in") {
- append("id")
- } else {
- append(it.language)
+ else -> {
+ // Not supported
+ }
}
}
- filter.originalLocale?.let {
- append("&originalLanguage[]=")
- if (it.language == "in") {
- append("id")
- } else {
- append(it.language)
- }
+ // If contentRating is not provided, add default values
+ if (!hasContentRating) {
+ append("&contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic")
}
- if (filter.year != 0) {
- append("&year=")
- append(filter.year)
- }
+ append("&order")
+ append((query.order ?: defaultSortOrder).toQueryParam())
}
+
val json = webClient.httpGet(url).parseJson().getJSONArray("data")
return json.mapJSON { jo -> jo.fetchManga(null) }
}
@@ -282,14 +337,17 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
val attrs = getJSONObject("attributes")
val relations = getJSONArray("relationships").associateByKey("type")
val cover = relations["cover_art"]
+ ?.firstOrNull()
?.getJSONObject("attributes")
?.getString("fileName")
?.let {
"https://uploads.$domain/covers/$id/$it"
}
- val author = (relations["author"] ?: relations["artist"])
- ?.getJSONObject("attributes")
- ?.getStringOrNull("name")
+ val authors: Set = (relations["author"] ?: relations["artist"])
+ ?.mapNotNullToSet {
+ it.getJSONObject("attributes")?.getStringOrNull("name")
+ }.orEmpty()
+
return Manga(
id = generateUid(id),
title = requireNotNull(attrs.getJSONObject("title").selectByLocale()) {
@@ -325,7 +383,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
"cancelled" -> MangaState.ABANDONED
else -> null
},
- authors = author?.let { setOf(it) } ?: emptySet(),
+ authors = authors,
chapters = chapters,
source = source,
)
@@ -409,22 +467,6 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
}
}
- private suspend fun getAuthorId(name: String): String {
- val url = urlBuilder("api")
- .addPathSegment("author")
- .addQueryParameter("name", name)
- .addQueryParameter("limit", "1")
- .build()
- val json = webClient.httpGet(url).parseJson()
- .getJSONArray("data")
- .getJSONObject(0)
- if (json.getJSONObject("attributes").getString("name").equals(name, ignoreCase = true)) {
- return json.getString("id")
- } else {
- throw NotFoundException("Author $name not found", url.toString())
- }
- }
-
private fun mapChapters(list: List): List {
// 2022-01-02T00:27:11+00:00
val dateFormat = SimpleDateFormat(
@@ -444,7 +486,8 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
val locale = attrs.getStringOrNull("translatedLanguage")?.let { Locale.forLanguageTag(it) }
val lc = locale?.getDisplayName(locale)?.toTitleCase(locale)
val relations = jo.getJSONArray("relationships").associateByKey("type")
- val team = relations["scanlation_group"]?.optJSONObject("attributes")?.getStringOrNull("name")
+ val team =
+ relations["scanlation_group"]?.firstOrNull()?.optJSONObject("attributes")?.getStringOrNull("name")
val branch = (list.indices).firstNotNullOf { i ->
val b = if (i == 0) lc else "$lc ($i)"
if (branchedChapters[b]?.get(volume to number) == null) b else null
@@ -470,12 +513,12 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
return chaptersBuilder.toList()
}
- private fun JSONArray.associateByKey(key: String): Map {
- val destination = LinkedHashMap(length())
+ private fun JSONArray.associateByKey(key: String): Map> {
+ val destination = LinkedHashMap>(length())
repeat(length()) { i ->
val item = getJSONObject(i)
val keyValue = item.getString(key)
- destination[keyValue] = item
+ destination.computeIfAbsent(keyValue) { mutableListOf() }.add(item)
}
return destination
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaFireParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaFireParser.kt
index 92444c16b..402b36ed4 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaFireParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaFireParser.kt
@@ -11,7 +11,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.bitmap.Rect
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
@@ -28,7 +28,7 @@ internal abstract class MangaFireParser(
context: MangaLoaderContext,
source: MangaParserSource,
private val siteLang: String,
-) : PagedMangaParser(context, source, 30), Interceptor, MangaParserAuthProvider {
+) : LegacyPagedMangaParser(context, source, 30), Interceptor, MangaParserAuthProvider {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("mangafire.to")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPark.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPark.kt
index 552f70044..3c67d6544 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPark.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPark.kt
@@ -4,7 +4,7 @@ import androidx.collection.ArrayMap
import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -15,7 +15,7 @@ import java.util.*
@MangaSourceParser("MANGAPARK", "MangaPark")
internal class MangaPark(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.MANGAPARK, pageSize = 36) {
+ LegacyPagedMangaParser(context, MangaParserSource.MANGAPARK, pageSize = 36) {
override val configKeyDomain = ConfigKey.Domain(
"mangapark.net",
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPlusParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPlusParser.kt
index d21c1842d..958568ffc 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPlusParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaPlusParser.kt
@@ -10,7 +10,7 @@ import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -25,7 +25,7 @@ internal abstract class MangaPlusParser(
context: MangaLoaderContext,
source: MangaParserSource,
private val sourceLang: String,
-) : SinglePageMangaParser(context, source), Interceptor {
+) : LegacySinglePageMangaParser(context, source), Interceptor {
private val apiUrl = "https://jumpg-webapi.tokyo-cdn.com/api"
override val configKeyDomain = ConfigKey.Domain("mangaplus.shueisha.co.jp")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaReaderToParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaReaderToParser.kt
index feb9d46de..a2ef623db 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaReaderToParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaReaderToParser.kt
@@ -9,7 +9,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.bitmap.Bitmap
import org.koitharu.kotatsu.parsers.bitmap.Rect
import org.koitharu.kotatsu.parsers.config.ConfigKey
@@ -23,7 +23,7 @@ import kotlin.math.min
@MangaSourceParser("MANGAREADERTO", "MangaReader.To")
internal class MangaReaderToParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.MANGAREADERTO, 16),
+ LegacyPagedMangaParser(context, MangaParserSource.MANGAREADERTO, 16),
Interceptor, MangaParserAuthProvider {
override val configKeyDomain = ConfigKey.Domain("mangareader.to")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineMangaParser.kt
index 89cb284fe..200404c08 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineMangaParser.kt
@@ -8,7 +8,7 @@ import okhttp3.Interceptor
import okhttp3.Response
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -19,7 +19,7 @@ internal abstract class NineMangaParser(
context: MangaLoaderContext,
source: MangaParserSource,
defaultDomain: String,
-) : PagedMangaParser(context, source, pageSize = 26), Interceptor {
+) : LegacyPagedMangaParser(context, source, pageSize = 26), Interceptor {
override val configKeyDomain = ConfigKey.Domain(defaultDomain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineNineNineHentaiParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineNineNineHentaiParser.kt
index 379e6a25d..c69d1757c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineNineNineHentaiParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineNineNineHentaiParser.kt
@@ -8,7 +8,7 @@ import org.json.JSONObject
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -23,7 +23,7 @@ import java.util.*
@Broken
@MangaSourceParser("NINENINENINEHENTAI", "AnimeH", type = ContentType.HENTAI)
internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.NINENINENINEHENTAI, PAGE_SIZE), Interceptor {
+ LegacyPagedMangaParser(context, MangaParserSource.NINENINENINEHENTAI, PAGE_SIZE), Interceptor {
override val configKeyDomain = ConfigKey.Domain("animeh.to")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/WebtoonsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/WebtoonsParser.kt
index da0313b28..559c1a7ab 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/WebtoonsParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/WebtoonsParser.kt
@@ -8,7 +8,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.NotFoundException
@@ -24,7 +24,7 @@ import javax.crypto.spec.SecretKeySpec
internal abstract class WebtoonsParser(
context: MangaLoaderContext,
source: MangaParserSource,
-) : MangaParser(context, source) {
+) : LegacyMangaParser(context, source) {
private val signer by lazy {
WebtoonsUrlSigner("gUtPzJFZch4ZyAGviiyH94P99lQ3pFdRTwpJWDlSGFfwgpr6ses5ALOxWHOIT7R1")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/AnimeBootstrapParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/AnimeBootstrapParser.kt
index 690e6ac22..796cef938 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/AnimeBootstrapParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/AnimeBootstrapParser.kt
@@ -5,7 +5,7 @@ import kotlinx.coroutines.coroutineScope
import org.json.JSONArray
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -16,7 +16,7 @@ internal abstract class AnimeBootstrapParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 24,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt
index 6123324e5..95e10c80d 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt
@@ -7,7 +7,7 @@ import org.json.JSONObject
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -18,7 +18,8 @@ import java.util.*
@Broken
@MangaSourceParser("FLIXSCANS", "FlixScans.net", "ar")
-internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.FLIXSCANS, 18) {
+internal class FlixScans(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.FLIXSCANS, 18) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED)
override val configKeyDomain = ConfigKey.Domain("flixscans.net")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/MangaStorm.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/MangaStorm.kt
index 2380610b9..68f1fd061 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/MangaStorm.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/MangaStorm.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.ar
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -12,7 +12,8 @@ import java.util.*
@Broken
@MangaSourceParser("MANGASTORM", "MangaStorm", "ar")
-internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGASTORM, 30) {
+internal class MangaStorm(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.MANGASTORM, 30) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED)
override val configKeyDomain = ConfigKey.Domain("mangastorm.org")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt
index 6fa4d04a5..3d20f19bc 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt
@@ -6,7 +6,7 @@ import kotlinx.coroutines.coroutineScope
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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("TEAMXNOVEL", "TeamXNovel", "ar")
-internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.TEAMXNOVEL, 10) {
+internal class TeamXNovel(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.TEAMXNOVEL, 10) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/be/AnibelParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/be/AnibelParser.kt
index 681e61757..d576190dc 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/be/AnibelParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/be/AnibelParser.kt
@@ -4,21 +4,24 @@ import androidx.collection.ArraySet
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.Broken
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
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.generateUid
+import org.koitharu.kotatsu.parsers.util.getDomain
import org.koitharu.kotatsu.parsers.util.json.asTypedList
import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed
import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull
+import org.koitharu.kotatsu.parsers.util.nullIfEmpty
+import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
import java.util.*
@Broken
@MangaSourceParser("ANIBEL", "Anibel", "be")
-internal class AnibelParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.ANIBEL) {
+internal class AnibelParser(context: MangaLoaderContext) : LegacyMangaParser(context, MangaParserSource.ANIBEL) {
override val configKeyDomain = ConfigKey.Domain("anibel.net")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/cupfox/CupFoxParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/cupfox/CupFoxParser.kt
index 5890f3347..144c630da 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/cupfox/CupFoxParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/cupfox/CupFoxParser.kt
@@ -2,7 +2,7 @@ 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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -13,7 +13,7 @@ internal abstract class CupFoxParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 24,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/AsuraScansParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/AsuraScansParser.kt
index 1af433538..7957c05fe 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/AsuraScansParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/AsuraScansParser.kt
@@ -6,7 +6,7 @@ import kotlinx.coroutines.sync.withLock
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -18,7 +18,7 @@ import java.util.*
@MangaSourceParser("ASURASCANS", "AsuraComic", "en")
internal class AsuraScansParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.ASURASCANS, pageSize = 30) {
+ LegacyPagedMangaParser(context, MangaParserSource.ASURASCANS, pageSize = 30) {
override val configKeyDomain = ConfigKey.Domain("asuracomic.net")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/BeeToon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/BeeToon.kt
index 8f27ab2a0..24e97249a 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/BeeToon.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/BeeToon.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -11,7 +11,7 @@ import java.util.*
@MangaSourceParser("BEETOON", "BeeToon.net", "en")
internal class BeeToon(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.BEETOON, pageSize = 30) {
+ LegacyPagedMangaParser(context, MangaParserSource.BEETOON, pageSize = 30) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt
index 0223c86fb..dc66235a6 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -10,7 +10,7 @@ import java.util.*
@MangaSourceParser("CLONEMANGA", "CloneManga", "en")
internal class CloneMangaParser(context: MangaLoaderContext) :
- SinglePageMangaParser(context, MangaParserSource.CLONEMANGA) {
+ LegacySinglePageMangaParser(context, MangaParserSource.CLONEMANGA) {
override val availableSortOrders: Set = Collections.singleton(
SortOrder.POPULARITY,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt
index ecc5f14bc..4cc39799e 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -11,7 +11,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("COMICEXTRA", "ComicExtra", "en", ContentType.COMICS)
-internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.COMICEXTRA, 36) {
+internal class ComicExtra(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.COMICEXTRA, 36) {
override val configKeyDomain = ConfigKey.Domain("azcomix.me")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt
index 97fa2bb91..60d52facc 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt
@@ -9,7 +9,7 @@ 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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -19,7 +19,7 @@ import java.util.*
@MangaSourceParser("DYNASTYSCANS", "DynastyScans", "en")
internal class DynastyScans(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.DYNASTYSCANS, 117) {
+ LegacyPagedMangaParser(context, MangaParserSource.DYNASTYSCANS, 117) {
override val configKeyDomain = ConfigKey.Domain("dynasty-scans.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlameComics.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlameComics.kt
index 67a15764d..916a40548 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlameComics.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlameComics.kt
@@ -6,7 +6,7 @@ import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -17,7 +17,7 @@ import java.util.concurrent.TimeUnit
@MangaSourceParser("FLAMECOMICS", "FlameComics", "en")
internal class FlameComics(context: MangaLoaderContext) :
- SinglePageMangaParser(context, MangaParserSource.FLAMECOMICS) {
+ LegacySinglePageMangaParser(context, MangaParserSource.FLAMECOMICS) {
private val commonPrefix = suspendLazy(initializer = ::fetchCommonPrefix)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlixScansOrg.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlixScansOrg.kt
index d7257f45c..61272beca 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlixScansOrg.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/FlixScansOrg.kt
@@ -6,6 +6,7 @@ import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.*
import org.koitharu.kotatsu.parsers.config.ConfigKey
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.asTypedList
@@ -16,7 +17,7 @@ import java.util.*
@Broken
@MangaSourceParser("FLIXSCANSORG", "FlixScans.org", "en")
internal class FlixScansOrg(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.FLIXSCANSORG, 18) {
+ LegacyPagedMangaParser(context, MangaParserSource.FLIXSCANSORG, 18) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED)
override val configKeyDomain = ConfigKey.Domain("flixscans.org")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt
index a44db7e23..294213141 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt
@@ -4,7 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("MANGAGEKO", "MangaGeko", "en")
-internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAGEKO, 30) {
+internal class MangaGeko(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.MANGAGEKO, 30) {
override val availableSortOrders: Set =
EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaKawaiiEn.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaKawaiiEn.kt
index 49ea5853e..94b6625d1 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaKawaiiEn.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaKawaiiEn.kt
@@ -5,7 +5,7 @@ import kotlinx.coroutines.coroutineScope
import okhttp3.Headers
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -13,7 +13,7 @@ import java.util.*
@MangaSourceParser("MANGAKAWAII_EN", "MangaKawaii En", "en")
internal class MangaKawaiiEn(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.MANGAKAWAII_EN, 50) {
+ LegacyPagedMangaParser(context, MangaParserSource.MANGAKAWAII_EN, 50) {
override val configKeyDomain = ConfigKey.Domain("www.mangakawaii.io")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt
index b3b65f0a5..dd3da9d8b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("MANGATOWN", "MangaTown", "en")
internal class MangaTownParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.MANGATOWN, 30) {
+ LegacyPagedMangaParser(context, MangaParserSource.MANGATOWN, 30) {
override val configKeyDomain = ConfigKey.Domain("www.mangatown.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Mangaowl.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Mangaowl.kt
index 42d179ac4..3063f7cbf 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Mangaowl.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Mangaowl.kt
@@ -5,7 +5,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -17,7 +17,7 @@ import java.util.*
@Broken
@MangaSourceParser("MANGAOWL", "MangaOwl.to", "en")
internal class Mangaowl(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.MANGAOWL, pageSize = 24) {
+ LegacyPagedMangaParser(context, MangaParserSource.MANGAOWL, pageSize = 24) {
override val availableSortOrders: Set = EnumSet.of(
SortOrder.POPULARITY,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Com.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Com.kt
index 069f8cb35..0f847ab9f 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Com.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Com.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.en
import androidx.collection.ArrayMap
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("MANHWA18COM", "Manhwa18.com", "en", type = ContentType.HENTAI)
internal class Manhwa18Com(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.MANHWA18COM, pageSize = 18, searchPageSize = 18) {
+ LegacyPagedMangaParser(context, MangaParserSource.MANHWA18COM, pageSize = 18, searchPageSize = 18) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwa18.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt
index 89362bee0..ef38c20a8 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.en
import androidx.collection.ArrayMap
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("MANHWA18", "Manhwa18.net", "en", type = ContentType.HENTAI)
internal class Manhwa18Parser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.MANHWA18, pageSize = 18, searchPageSize = 18) {
+ LegacyPagedMangaParser(context, MangaParserSource.MANHWA18, pageSize = 18, searchPageSize = 18) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwa18.net")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt
index 5f6efe8f8..bca53ed67 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("MANHWASMEN", "ManhwasMen", "en", type = ContentType.HENTAI)
internal class ManhwasMen(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.MANHWASMEN, pageSize = 30, searchPageSize = 30) {
+ LegacyPagedMangaParser(context, MangaParserSource.MANHWASMEN, pageSize = 30, searchPageSize = 30) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwas.men")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MyComicList.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MyComicList.kt
index bdaeba5d9..aa7d4b8bc 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MyComicList.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MyComicList.kt
@@ -2,14 +2,15 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("MYCOMICLIST", "MyComicList", "en", ContentType.COMICS)
-internal class MyComicList(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MYCOMICLIST, 24) {
+internal class MyComicList(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.MYCOMICLIST, 24) {
override val configKeyDomain = ConfigKey.Domain("mycomiclist.org")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt
index 5673dcd25..30d598137 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,8 @@ import java.util.*
@Broken
@MangaSourceParser("PO2SCANS", "Po2Scans", "en")
-internal class Po2Scans(context: MangaLoaderContext) : SinglePageMangaParser(context, MangaParserSource.PO2SCANS) {
+internal class Po2Scans(context: MangaLoaderContext) :
+ LegacySinglePageMangaParser(context, MangaParserSource.PO2SCANS) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL)
override val configKeyDomain = ConfigKey.Domain("po2scans.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Pururin.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Pururin.kt
index 3c5f6f421..33442e5d0 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Pururin.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Pururin.kt
@@ -8,7 +8,7 @@ import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -17,7 +17,7 @@ import java.util.*
@Broken
@MangaSourceParser("PURURIN", "Pururin", "en", ContentType.HENTAI)
internal class Pururin(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.PURURIN, pageSize = 20) {
+ LegacyPagedMangaParser(context, MangaParserSource.PURURIN, pageSize = 20) {
override val configKeyDomain = ConfigKey.Domain("pururin.to")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/VyManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/VyManga.kt
index 577a2a1bc..f00bb9a87 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/VyManga.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/VyManga.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("VYMANGA", "VyManga", "en")
internal class VyManga(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.VYMANGA, pageSize = 36) {
+ LegacyPagedMangaParser(context, MangaParserSource.VYMANGA, pageSize = 36) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("vymanga.net")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/WeebCentral.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/WeebCentral.kt
index 6698df462..eab7d1f7c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/WeebCentral.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/WeebCentral.kt
@@ -11,6 +11,7 @@ import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.model.ContentRating.SAFE
import org.koitharu.kotatsu.parsers.model.ContentRating.SUGGESTIVE
@@ -22,7 +23,7 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("WEEBCENTRAL", "Weeb Central", "en")
-internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.WEEBCENTRAL),
+internal class WeebCentral(context: MangaLoaderContext) : LegacyMangaParser(context, MangaParserSource.WEEBCENTRAL),
MangaParserAuthProvider {
override val configKeyDomain = ConfigKey.Domain("weebcentral.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TempleScanEsp.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TempleScanEsp.kt
index 670a05844..59696bc04 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TempleScanEsp.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TempleScanEsp.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.es
import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -16,7 +16,7 @@ import java.util.*
@MangaSourceParser("TEMPLESCANESP", "TempleScanEsp", "es", ContentType.HENTAI)
internal class TempleScanEsp(context: MangaLoaderContext) :
- SinglePageMangaParser(context, MangaParserSource.TEMPLESCANESP) {
+ LegacySinglePageMangaParser(context, MangaParserSource.TEMPLESCANESP) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.NEWEST_ASC)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TuMangaOnlineParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TuMangaOnlineParser.kt
index 98ea850ba..6a6191317 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TuMangaOnlineParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TuMangaOnlineParser.kt
@@ -6,7 +6,7 @@ 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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("TUMANGAONLINE", "TuMangaOnline", "es")
-internal class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
+internal class TuMangaOnlineParser(context: MangaLoaderContext) : LegacyPagedMangaParser(
context,
source = MangaParserSource.TUMANGAONLINE,
pageSize = 24,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/FmreaderParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/FmreaderParser.kt
index 7bf384de0..035acf6b2 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/FmreaderParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/FmreaderParser.kt
@@ -4,7 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -17,7 +17,7 @@ internal abstract class FmreaderParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 20,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/FoolSlideParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/FoolSlideParser.kt
index eea61201a..392f4d157 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/FoolSlideParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/FoolSlideParser.kt
@@ -4,7 +4,7 @@ import kotlinx.coroutines.coroutineScope
import org.json.JSONArray
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -16,7 +16,7 @@ internal abstract class FoolSlideParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 25,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/BentomangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/BentomangaParser.kt
index c2f7ae446..b619f8961 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/BentomangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/BentomangaParser.kt
@@ -8,7 +8,7 @@ import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
@@ -19,7 +19,7 @@ import java.util.*
@Broken
@MangaSourceParser("BENTOMANGA", "BentoManga", "fr")
internal class BentomangaParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.BENTOMANGA, 10) {
+ LegacyPagedMangaParser(context, MangaParserSource.BENTOMANGA, 10) {
override val availableSortOrders: Set = EnumSet.of(
SortOrder.UPDATED,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FuryoSociety.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FuryoSociety.kt
index daffcf2aa..5760d36dd 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FuryoSociety.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FuryoSociety.kt
@@ -5,7 +5,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -16,7 +16,7 @@ import java.util.*
@MangaSourceParser("FURYOSOCIETY", "FuryoSociety", "fr")
internal class FuryoSociety(context: MangaLoaderContext) :
- SinglePageMangaParser(context, MangaParserSource.FURYOSOCIETY) {
+ LegacySinglePageMangaParser(context, MangaParserSource.FURYOSOCIETY) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL, SortOrder.UPDATED)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LegacyScansParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LegacyScansParser.kt
index f0b07ec8d..8e6be1a87 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LegacyScansParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LegacyScansParser.kt
@@ -4,7 +4,7 @@ import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ import java.util.*
@MangaSourceParser("LEGACY_SCANS", "LegacyScans", "fr")
internal class LegacyScansParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.LEGACY_SCANS, 18) {
+ LegacyPagedMangaParser(context, MangaParserSource.LEGACY_SCANS, 18) {
override val configKeyDomain = ConfigKey.Domain("legacy-scans.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LireScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LireScan.kt
index da06d6092..a896db7c1 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LireScan.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LireScan.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.fr
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ import java.util.*
@Broken
@MangaSourceParser("LIRESCAN", "LireScan", "fr")
-internal class LireScan(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.LIRESCAN, 20) {
+internal class LireScan(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.LIRESCAN, 20) {
override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LugnicaScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LugnicaScans.kt
index c92024e15..576429dc9 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LugnicaScans.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LugnicaScans.kt
@@ -4,7 +4,7 @@ import org.json.JSONArray
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -17,7 +17,7 @@ import java.util.*
@MangaSourceParser("LUGNICASCANS", "LugnicaScans", "fr")
internal class LugnicaScans(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.LUGNICASCANS, 10) {
+ LegacyPagedMangaParser(context, MangaParserSource.LUGNICASCANS, 10) {
override val configKeyDomain = ConfigKey.Domain("lugnica-scans.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaKawaii.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaKawaii.kt
index 7227ceef5..412d7e1f4 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaKawaii.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaKawaii.kt
@@ -5,14 +5,15 @@ import kotlinx.coroutines.coroutineScope
import okhttp3.Headers
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("MANGAKAWAII", "MangaKawaii Fr", "fr")
-internal class MangaKawaii(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAKAWAII, 50) {
+internal class MangaKawaii(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.MANGAKAWAII, 50) {
override val configKeyDomain = ConfigKey.Domain("www.mangakawaii.io")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaMana.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaMana.kt
index 05aaa5de7..a13520fbf 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaMana.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaMana.kt
@@ -11,7 +11,7 @@ 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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
@@ -22,7 +22,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("MANGAMANA", "MangaMana", "fr")
-internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAMANA, 25) {
+internal class MangaMana(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.MANGAMANA, 25) {
override val configKeyDomain = ConfigKey.Domain("www.manga-mana.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScansMangasMe.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScansMangasMe.kt
index cf8295ce7..accedc8b9 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScansMangasMe.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScansMangasMe.kt
@@ -6,7 +6,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -16,7 +16,7 @@ import java.util.*
@Broken
@MangaSourceParser("SCANS_MANGAS_ME", "ScansMangas.me", "fr")
internal class ScansMangasMe(context: MangaLoaderContext) :
- SinglePageMangaParser(context, MangaParserSource.SCANS_MANGAS_ME) {
+ LegacySinglePageMangaParser(context, MangaParserSource.SCANS_MANGAS_ME) {
override val availableSortOrders: Set = EnumSet.of(
SortOrder.ALPHABETICAL,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScantradUnion.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScantradUnion.kt
index 26a377a98..d6bd75014 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScantradUnion.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScantradUnion.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.fr
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
@@ -13,7 +13,7 @@ import java.util.*
@MangaSourceParser("SCANTRADUNION", "ScantradUnion", "fr")
internal class ScantradUnion(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.SCANTRADUNION, 10) {
+ LegacyPagedMangaParser(context, MangaParserSource.SCANTRADUNION, 10) {
override val configKeyDomain = ConfigKey.Domain("scantrad-union.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fuzzydoodle/FuzzyDoodleParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fuzzydoodle/FuzzyDoodleParser.kt
index 42f7a7dca..a57cb2961 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fuzzydoodle/FuzzyDoodleParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fuzzydoodle/FuzzyDoodleParser.kt
@@ -7,7 +7,7 @@ import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -20,7 +20,7 @@ internal abstract class FuzzyDoodleParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 24,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/GalleryAdultsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/GalleryAdultsParser.kt
index 89a90e775..c4c943453 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/GalleryAdultsParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/GalleryAdultsParser.kt
@@ -8,7 +8,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -19,7 +19,7 @@ internal abstract class GalleryAdultsParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 20,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/gattsu/GattsuParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/gattsu/GattsuParser.kt
index ecc0ddb9c..644e5ab94 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/gattsu/GattsuParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/gattsu/GattsuParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.gattsu
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ internal abstract class GattsuParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 20,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/guya/GuyaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/guya/GuyaParser.kt
index 33064e963..b19fb2885 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/guya/GuyaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/guya/GuyaParser.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.guya
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ internal abstract class GuyaParser(
context: MangaLoaderContext,
source: MangaParserSource,
domain: String,
-) : SinglePageMangaParser(context, source) {
+) : LegacySinglePageMangaParser(context, source) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt
index ed0d3f614..6a3a7e4a4 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.heancms
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -19,7 +19,7 @@ internal abstract class HeanCms(
source: MangaParserSource,
domain: String,
pageSize: Int = 20,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/HeanCmsAlt.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/HeanCmsAlt.kt
index 3787f58f5..35f1f62ba 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/HeanCmsAlt.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/HeanCmsAlt.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.heancmsalt
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -17,7 +17,7 @@ internal abstract class HeanCmsAlt(
source: MangaParserSource,
domain: String,
pageSize: Int = 18,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/hotcomics/HotComicsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/hotcomics/HotComicsParser.kt
index 12359e36a..88b85cd97 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/hotcomics/HotComicsParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/hotcomics/HotComicsParser.kt
@@ -7,7 +7,7 @@ import kotlinx.coroutines.sync.withLock
import okhttp3.Headers
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -20,7 +20,7 @@ internal abstract class HotComicsParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 24,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/DoujinDesuParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/DoujinDesuParser.kt
index 8ded46b5b..7911cc366 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/DoujinDesuParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/DoujinDesuParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.id
import okhttp3.Headers
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("DOUJINDESU", "DoujinDesu.tv", "id")
internal class DoujinDesuParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.DOUJINDESU, pageSize = 18) {
+ LegacyPagedMangaParser(context, MangaParserSource.DOUJINDESU, pageSize = 18) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("doujindesu.tv")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/HentaiCrot.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/HentaiCrot.kt
index ca49d753a..771555ba4 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/HentaiCrot.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/HentaiCrot.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.id
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -10,7 +10,7 @@ import java.util.*
@MangaSourceParser("HENTAICROT", "HentaiCrot", "id", ContentType.HENTAI)
internal class HentaiCrot(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.HENTAICROT, 8) {
+ LegacyPagedMangaParser(context, MangaParserSource.HENTAICROT, 8) {
override val configKeyDomain = ConfigKey.Domain("hentaicrot.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/PixHentai.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/PixHentai.kt
index aefb5353c..266fc66b9 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/PixHentai.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/PixHentai.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.id
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -10,7 +10,7 @@ import java.util.*
@MangaSourceParser("PIXHENTAI", "PixHentai", "id", ContentType.HENTAI)
internal class PixHentai(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.PIXHENTAI, 8) {
+ LegacyPagedMangaParser(context, MangaParserSource.PIXHENTAI, 8) {
override val configKeyDomain = ConfigKey.Domain("pixhentai.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/iken/IkenParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/iken/IkenParser.kt
index 25518665c..5c79be35c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/iken/IkenParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/iken/IkenParser.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.iken
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -18,7 +18,7 @@ internal abstract class IkenParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 18,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt
index df0803f4b..88f5d0f11 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt
@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.parsers.site.ja
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
@@ -16,7 +16,7 @@ private const val STATUS_FINISHED = "完結"
@MangaSourceParser("NICOVIDEO_SEIGA", "NicoVideo Seiga", "ja")
internal class NicovideoSeigaParser(context: MangaLoaderContext) :
- MangaParser(context, MangaParserSource.NICOVIDEO_SEIGA),
+ LegacyMangaParser(context, MangaParserSource.NICOVIDEO_SEIGA),
MangaParserAuthProvider {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("nicovideo.jp")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/KeyoappParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/KeyoappParser.kt
index 21244d910..bd2329ffd 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/KeyoappParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/keyoapp/KeyoappParser.kt
@@ -5,7 +5,7 @@ import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -17,7 +17,7 @@ internal abstract class KeyoappParser(
context: MangaLoaderContext,
source: MangaParserSource,
domain: String,
-) : SinglePageMangaParser(context, source) {
+) : LegacySinglePageMangaParser(context, source) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/likemanga/LikeMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/likemanga/LikeMangaParser.kt
index fa458e922..e6225af7c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/likemanga/LikeMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/likemanga/LikeMangaParser.kt
@@ -7,7 +7,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
import org.json.JSONObject
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -20,7 +20,7 @@ internal abstract class LikeMangaParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 36,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/LilianaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/LilianaParser.kt
index 498e5f42e..8e5cc1c10 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/LilianaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/liliana/LilianaParser.kt
@@ -5,7 +5,7 @@ import kotlinx.coroutines.coroutineScope
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -17,7 +17,7 @@ internal abstract class LilianaParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 24,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt
index 2ab8ecb2a..1991d1c60 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt
@@ -8,7 +8,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.exception.ParseException
@@ -23,7 +23,7 @@ internal abstract class MadaraParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 12,
-) : PagedMangaParser(context, source, pageSize), MangaParserAuthProvider {
+) : LegacyPagedMangaParser(context, source, pageSize), MangaParserAuthProvider {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt
index f2a7c6ece..2a6d75b2f 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt
@@ -4,7 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -17,7 +17,7 @@ internal abstract class MadthemeParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 48,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt
index 36b9c05e2..98f8b52f5 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt
@@ -4,7 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -16,7 +16,7 @@ internal abstract class Manga18Parser(
source: MangaParserSource,
domain: String,
pageSize: Int = 20,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt
index 9e3b9db7f..77ca0b15e 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt
@@ -4,9 +4,15 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
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.core.PagedMangaParser
import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchCapability
+import org.koitharu.kotatsu.parsers.model.search.SearchableField
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
import org.koitharu.kotatsu.parsers.util.*
import java.text.DateFormat
import java.text.SimpleDateFormat
@@ -30,12 +36,32 @@ internal abstract class MangaboxParser(
SortOrder.ALPHABETICAL,
)
- override val filterCapabilities: MangaListFilterCapabilities
- get() = MangaListFilterCapabilities(
- isMultipleTagsSupported = true,
- isTagsExclusionSupported = true,
- isSearchSupported = true,
- isSearchWithFiltersSupported = true,
+ override val searchQueryCapabilities: MangaSearchQueryCapabilities
+ get() = MangaSearchQueryCapabilities(
+ SearchCapability(
+ field = TAG,
+ criteriaTypes = setOf(Include::class, Exclude::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = TITLE_NAME,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = STATE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = AUTHOR,
+ criteriaTypes = setOf(Include::class),
+ multiValue = false,
+ otherCriteria = false,
+ ),
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
@@ -59,64 +85,84 @@ internal abstract class MangaboxParser(
)
protected open val listUrl = "/advanced_search"
+ protected open val authorUrl = "/search/author"
protected open val searchUrl = "/search/story/"
protected open val datePattern = "MMM dd,yy"
- override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
- val url = buildString {
- append("https://")
- append(domain)
- append(listUrl)
- append("/?s=all")
-
- filter.query?.let {
- append("&keyw=")
- append(filter.query.replace(" ", "_").urlEncoded())
- }
+ private fun SearchableField.toParamName(): String = when (this) {
+ TITLE_NAME, AUTHOR -> "keyw"
+ TAG -> "g_i"
+ STATE -> "sts"
+ else -> ""
+ }
- if (filter.tags.isNotEmpty()) {
- append("&g_i=")
- filter.tags.forEach {
- append("_")
- append(it.key)
- append("_")
- }
- }
+ private fun Any?.toQueryParam(): String = when (this) {
+ is String -> replace(" ", "_").urlEncoded()
+ is MangaTag -> key
+ is MangaState -> when (this) {
+ MangaState.ONGOING -> "ongoing"
+ MangaState.FINISHED -> "completed"
+ else -> ""
+ }
- if (filter.tagsExclude.isNotEmpty()) {
- append("&g_e=")
- filter.tagsExclude.forEach {
- append("_")
- append(it.key)
- append("_")
- }
- }
+ is SortOrder -> when (this) {
+ SortOrder.ALPHABETICAL -> "az"
+ SortOrder.NEWEST -> "newest"
+ SortOrder.POPULARITY -> "topview"
+ else -> ""
+ }
- filter.states.oneOrThrowIfMany()?.let {
- append("&sts=")
- append(
- when (it) {
- MangaState.ONGOING -> "ongoing"
- MangaState.FINISHED -> "completed"
- else -> ""
- },
- )
- }
+ else -> this.toString().replace(" ", "_").urlEncoded()
+ }
- append("&orby=")
- when (order) {
- SortOrder.POPULARITY -> append("topview")
- SortOrder.UPDATED -> append("")
- SortOrder.NEWEST -> append("newest")
- SortOrder.ALPHABETICAL -> append("az")
- else -> append("")
+ private fun StringBuilder.appendCriterion(field: SearchableField, value: Any?, paramName: String? = null) {
+ val param = paramName ?: field.toParamName()
+ if (param.isNotBlank()) {
+ append("&$param=")
+ append(value.toQueryParam())
+ }
+ }
+
+ override suspend fun getListPage(query: MangaSearchQuery, page: Int): List {
+ var authorSearchUrl: String? = null
+ val url = buildString {
+ val pageQueryParameter = "page=$page"
+ append("https://${domain}${listUrl}/?s=all")
+
+ query.criteria.forEach { criterion ->
+ when (criterion) {
+ is Include<*> -> {
+ if (criterion.field == AUTHOR) {
+ criterion.values.firstOrNull()?.toQueryParam()?.takeIf { it.isNotBlank() }
+ ?.let { authorKey ->
+ authorSearchUrl = "https://${domain}${authorUrl}/${authorKey}/?$pageQueryParameter"
+ }
+ }
+
+ criterion.field.toParamName().takeIf { it.isNotBlank() }?.let { param ->
+ append("&$param=${criterion.values.joinToString("_") { it.toQueryParam() }}")
+ }
+ }
+
+ is Exclude<*> -> {
+ append("&g_e=${criterion.values.joinToString("_") { it.toQueryParam() }}")
+ }
+
+ is Match<*> -> {
+ appendCriterion(criterion.field, criterion.value)
+ }
+
+ else -> {
+ // Not supported
+ }
+ }
}
- append("&page=")
- append(page.toString())
+ append("&${pageQueryParameter}")
+ append("&orby=${(query.order ?: defaultSortOrder).toQueryParam()}")
}
- val doc = webClient.httpGet(url).parseHtml()
+ val doc = webClient.httpGet(authorSearchUrl ?: url).parseHtml()
return doc.select("div.content-genres-item, div.list-story-item").ifEmpty {
doc.select("div.search-story-item")
@@ -134,7 +180,7 @@ internal abstract class MangaboxParser(
authors = emptySet(),
state = null,
source = source,
- contentRating = if (isNsfwSource) ContentRating.ADULT else null,
+ contentRating = if (source.contentType == ContentType.HENTAI) ContentRating.ADULT else ContentRating.SAFE,
)
}
}
@@ -175,7 +221,8 @@ internal abstract class MangaboxParser(
}
}
val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "").nullIfEmpty()
- val author = doc.body().select(selectAut).eachText().joinToString().nullIfEmpty()
+ val authors = doc.body().select(selectAut).mapToSet { it.text() }
+
manga.copy(
tags = doc.body().select(selectTag).mapToSet { a ->
MangaTag(
@@ -186,7 +233,7 @@ internal abstract class MangaboxParser(
},
description = desc,
altTitle = alt,
- authors = author?.let { setOf(it) } ?: emptySet(),
+ authors = authors,
state = state,
chapters = chaptersDeferred.await(),
)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt
index 109858632..94d3105bc 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt
@@ -6,6 +6,12 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.Include
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.Match
+import org.koitharu.kotatsu.parsers.model.search.SearchCapability
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@@ -31,65 +37,88 @@ internal class Mangairo(context: MangaLoaderContext) :
SortOrder.POPULARITY,
SortOrder.NEWEST,
)
- override val filterCapabilities: MangaListFilterCapabilities
- get() = super.filterCapabilities.copy(
- isTagsExclusionSupported = false,
- isMultipleTagsSupported = false,
- isSearchWithFiltersSupported = false,
+
+ override val searchQueryCapabilities: MangaSearchQueryCapabilities
+ get() = MangaSearchQueryCapabilities(
+ SearchCapability(
+ field = TAG,
+ criteriaTypes = setOf(Include::class),
+ multiValue = false,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = TITLE_NAME,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = false,
+ ),
+ SearchCapability(
+ field = STATE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = false,
+ otherCriteria = true,
+ ),
)
- override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
- val url = buildString {
- append("https://")
- append(domain)
- when {
-
- !filter.query.isNullOrEmpty() -> {
- append(searchUrl)
- append(filter.query.urlEncoded())
- append("?page=")
- }
+ private fun Any?.toQueryParam(): String = when (this) {
+ is String -> replace(" ", "_").urlEncoded()
+ is MangaTag -> key
+ is MangaState -> when (this) {
+ MangaState.ONGOING -> "ongoing"
+ MangaState.FINISHED -> "completed"
+ else -> "all"
+ }
- else -> {
- append(listUrl)
- append("/type-")
- when (order) {
- SortOrder.POPULARITY -> append("topview")
- SortOrder.UPDATED -> append("latest")
- SortOrder.NEWEST -> append("newest")
- else -> append("latest")
- }
+ is SortOrder -> when (this) {
+ SortOrder.POPULARITY -> "topview"
+ SortOrder.UPDATED -> "latest"
+ SortOrder.NEWEST -> "newest"
+ else -> "latest"
+ }
+
+ else -> this.toString().urlEncoded()
+ }
+
+ override suspend fun getListPage(query: MangaSearchQuery, page: Int): List {
+ var titleSearchUrl: String? = null
+ var category = "all"
+ var state = "all"
+
+ val url = buildString {
+ append("https://${domain}${listUrl}")
+ append("/type-${(query.order ?: defaultSortOrder).toQueryParam()}")
- append("/ctg-")
- if (filter.tags.isNotEmpty()) {
- filter.tags.oneOrThrowIfMany()?.let {
- append(it.key)
+ query.criteria.forEach { criterion ->
+ when (criterion) {
+ is Include<*> -> {
+ when (criterion.field) {
+ TAG -> category = criterion.values.first().toQueryParam()
+ STATE -> state = criterion.values.first().toQueryParam()
+ else -> Unit
}
- } else {
- append("all")
}
- append("/state-")
- if (filter.states.isNotEmpty()) {
- filter.states.oneOrThrowIfMany()?.let {
- append(
- when (it) {
- MangaState.ONGOING -> "ongoing"
- MangaState.FINISHED -> "completed"
- else -> "all"
- },
- )
+ is Match<*> -> {
+ if (criterion.field == TITLE_NAME) {
+ criterion.value.toQueryParam().takeIf { it.isNotBlank() }?.let { titleName ->
+ titleSearchUrl = "https://${domain}${searchUrl}${titleName}/" +
+ "?page=${query.offset}"
+ }
}
- } else {
- append("all")
}
- append("/page-")
+ else -> {
+ // Not supported
+ }
}
}
- append(page.toString())
+ append("/ctg-$category")
+ append("/state-$state")
+ append("/page-$page")
}
- val doc = webClient.httpGet(url).parseHtml()
+
+ val doc = webClient.httpGet(titleSearchUrl ?: url).parseHtml()
+
return doc.select("div.story-item").map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
Manga(
@@ -104,7 +133,7 @@ internal class Mangairo(context: MangaLoaderContext) :
authors = emptySet(),
state = null,
source = source,
- contentRating = if (isNsfwSource) ContentRating.ADULT else null,
+ contentRating = if (source.contentType == ContentType.HENTAI) ContentRating.ADULT else ContentRating.SAFE,
)
}
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt
index 856e7747e..216cef027 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt
@@ -5,6 +5,9 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.search.*
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
@@ -19,63 +22,109 @@ internal class Mangakakalot(context: MangaLoaderContext) :
SortOrder.POPULARITY,
SortOrder.NEWEST,
)
- override val filterCapabilities: MangaListFilterCapabilities
- get() = super.filterCapabilities.copy(
- isTagsExclusionSupported = false,
- isMultipleTagsSupported = false,
- isSearchWithFiltersSupported = false,
+
+ override val searchQueryCapabilities: MangaSearchQueryCapabilities
+ get() = MangaSearchQueryCapabilities(
+ SearchCapability(
+ field = TAG,
+ criteriaTypes = setOf(Include::class),
+ multiValue = false,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = TITLE_NAME,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = false,
+ ),
+ SearchCapability(
+ field = STATE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = false,
+ otherCriteria = true,
+ ),
)
+
override val otherDomain = "chapmanganato.com"
override val listUrl = "/manga_list"
- override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
+ private fun SearchableField.toParamName(): String = when (this) {
+ TAG -> "category"
+ STATE -> "state"
+ else -> ""
+ }
+
+ private fun Any?.toQueryParam(): String = when (this) {
+ is String -> {
+ sanitizeTitleNameRegex.replace(this, "")
+ .replace(" ", "_")
+ .urlEncoded()
+ }
+
+ is MangaTag -> key
+ is MangaState -> when (this) {
+ MangaState.ONGOING -> "ongoing"
+ MangaState.FINISHED -> "completed"
+ else -> "all"
+ }
+
+ is SortOrder -> when (this) {
+ SortOrder.POPULARITY -> "topview"
+ SortOrder.UPDATED -> "latest"
+ SortOrder.NEWEST -> "newest"
+ else -> ""
+ }
+
+ else -> this.toString().replace(" ", "_").urlEncoded()
+ }
+
+ private fun StringBuilder.appendCriterion(field: SearchableField, value: Any?, paramName: String? = null) {
+ val param = paramName ?: field.toParamName()
+ if (param.isNotBlank()) {
+ append("&$param=")
+ append(value.toQueryParam())
+ }
+ }
+
+ private val sanitizeTitleNameRegex by lazy {
+ Regex("[^A-Za-z0-9 ]")
+ }
+
+ override suspend fun getListPage(query: MangaSearchQuery, page: Int): List {
+ var titleSearchUrl: String? = null
val url = buildString {
- append("https://")
- append(domain)
- when {
-
- !filter.query.isNullOrEmpty() -> {
- append(searchUrl)
- val regex = Regex("[^A-Za-z0-9 ]")
- val q = regex.replace(filter.query, "")
- append(q.replace(" ", "_"))
- append("?page=")
- }
+ val pageQueryParameter = "page=$page"
+ append("https://$domain/?")
- else -> {
- append(listUrl)
- append("?type=")
- when (order) {
- SortOrder.POPULARITY -> append("topview")
- SortOrder.UPDATED -> append("latest")
- SortOrder.NEWEST -> append("newest")
- else -> append("latest")
- }
- if (filter.tags.isNotEmpty()) {
- append("&category=")
- filter.tags.oneOrThrowIfMany()?.let {
- append(it.key)
+ query.criteria.forEach { criterion ->
+ when (criterion) {
+ is Include<*> -> {
+ criterion.field.toParamName().takeIf { it.isNotBlank() }?.let { param ->
+ append("&$param=${criterion.values.first().toQueryParam()}")
}
}
- filter.states.oneOrThrowIfMany()?.let {
- append("&state=")
- append(
- when (it) {
- MangaState.ONGOING -> "ongoing"
- MangaState.FINISHED -> "completed"
- else -> "all"
- },
- )
+ is Match<*> -> {
+ if (criterion.field == TITLE_NAME) {
+ criterion.value.toQueryParam().takeIf { it.isNotBlank() }?.let { titleName ->
+ titleSearchUrl = "https://${domain}${searchUrl}${titleName}/" +
+ "?$pageQueryParameter"
+ }
+ }
+ appendCriterion(criterion.field, criterion.value)
}
- append("&page=")
+ else -> {
+ // Not supported
+ }
}
}
- append(page.toString())
+
+ append("&$pageQueryParameter")
+ append("&type=${(query.order ?: defaultSortOrder).toQueryParam()}")
}
- val doc = webClient.httpGet(url).parseHtml()
+ val doc = webClient.httpGet(titleSearchUrl ?: url).parseHtml()
return doc.select("div.list-truyen-item-wrap").ifEmpty {
doc.select("div.story_item")
@@ -93,7 +142,7 @@ internal class Mangakakalot(context: MangaLoaderContext) :
authors = emptySet(),
state = null,
source = source,
- contentRating = if (isNsfwSource) ContentRating.ADULT else null,
+ contentRating = null,
)
}
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt
index 9d8a83b98..156994589 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt
@@ -6,6 +6,9 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.search.*
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@@ -22,58 +25,98 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
SortOrder.POPULARITY,
SortOrder.NEWEST,
)
- override val filterCapabilities: MangaListFilterCapabilities
- get() = super.filterCapabilities.copy(
- isTagsExclusionSupported = false,
- isMultipleTagsSupported = false,
- isSearchWithFiltersSupported = false,
+
+ override val searchQueryCapabilities: MangaSearchQueryCapabilities
+ get() = MangaSearchQueryCapabilities(
+ SearchCapability(
+ field = TAG,
+ criteriaTypes = setOf(Include::class),
+ multiValue = false,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = TITLE_NAME,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = false,
+ ),
+ SearchCapability(
+ field = STATE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = false,
+ otherCriteria = true,
+ ),
)
- override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
+ private fun SearchableField.toParamName(): String = when (this) {
+ TAG -> "category"
+ STATE -> "state"
+ else -> ""
+ }
+
+ private fun Any?.toQueryParam(): String = when (this) {
+ is String -> urlEncoded()
+ is MangaTag -> key
+ is MangaState -> when (this) {
+ MangaState.ONGOING -> "ongoing"
+ MangaState.FINISHED -> "completed"
+ else -> "all"
+ }
+
+ is SortOrder -> when (this) {
+ SortOrder.POPULARITY -> "topview"
+ SortOrder.UPDATED -> "latest"
+ SortOrder.NEWEST -> "newest"
+ else -> ""
+ }
+
+ else -> this.toString().urlEncoded()
+ }
+
+ private fun StringBuilder.appendCriterion(field: SearchableField, value: Any?, paramName: String? = null) {
+ val param = paramName ?: field.toParamName()
+ if (param.isNotBlank()) {
+ append("&$param=")
+ append(value.toQueryParam())
+ }
+ }
+
+ override suspend fun getListPage(query: MangaSearchQuery, page: Int): List {
+ var titleSearchUrl: String? = null
val url = buildString {
- append("https://")
- append(domain)
- when {
-
- !filter.query.isNullOrEmpty() -> {
- append(searchUrl)
- append(filter.query.urlEncoded())
- append("?page=")
- }
+ val pageQueryParameter = "page=$page"
+ append("https://$domain/?")
- else -> {
- append(listUrl)
- append("?type=")
- when (order) {
- SortOrder.POPULARITY -> append("topview")
- SortOrder.UPDATED -> append("latest")
- SortOrder.NEWEST -> append("newest")
- else -> append("latest")
- }
- if (filter.tags.isNotEmpty()) {
- append("&category=")
- filter.tags.oneOrThrowIfMany()?.let {
- append(it.key)
+ query.criteria.forEach { criterion ->
+ when (criterion) {
+ is Include<*> -> {
+ criterion.field.toParamName().takeIf { it.isNotBlank() }?.let { param ->
+ append("&$param=${criterion.values.first().toQueryParam()}")
}
}
- filter.states.oneOrThrowIfMany()?.let {
- append("&state=")
- append(
- when (it) {
- MangaState.ONGOING -> "Ongoing"
- MangaState.FINISHED -> "Completed"
- else -> "all"
- },
- )
+ is Match<*> -> {
+ if (criterion.field == TITLE_NAME) {
+ criterion.value.toQueryParam().takeIf { it.isNotBlank() }?.let { titleName ->
+ titleSearchUrl = "https://${domain}${searchUrl}${titleName}/" +
+ "?$pageQueryParameter"
+ }
+ }
+ appendCriterion(criterion.field, criterion.value)
}
- append("&page=")
+ else -> {
+ // Not supported
+ }
}
}
- append(page.toString())
+
+ append("&$pageQueryParameter")
+ append("&type=${(query.order ?: defaultSortOrder).toQueryParam()}")
}
- val doc = webClient.httpGet(url).parseHtml()
+
+ val doc = webClient.httpGet(titleSearchUrl ?: url).parseHtml()
+
return doc.select("div.list-truyen-item-wrap").ifEmpty {
doc.select("div.story_item")
}.map { div ->
@@ -90,7 +133,7 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
authors = emptySet(),
state = null,
source = source,
- contentRating = if (isNsfwSource) ContentRating.ADULT else null,
+ contentRating = null,
)
}
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Manganato.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Manganato.kt
index a41005c31..e885d9c3d 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Manganato.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Manganato.kt
@@ -16,5 +16,6 @@ internal class Manganato(context: MangaLoaderContext) :
)
override val otherDomain = "chapmanganato.to"
+ override val authorUrl = "/author/story"
override val selectPage = ".container-chapter-reader > img"
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangadventure/MangAdventureParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangadventure/MangAdventureParser.kt
index e24c6f7c5..cc9e0cd0d 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangadventure/MangAdventureParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangadventure/MangAdventureParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.mangadventure
import okhttp3.HttpUrl
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.model.*
@@ -17,7 +17,7 @@ internal abstract class MangAdventureParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 25,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt
index dc420130e..6852be480 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt
@@ -12,7 +12,7 @@ import okhttp3.internal.closeQuietly
import org.json.JSONObject
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -25,7 +25,7 @@ internal abstract class MangaReaderParser(
domain: String,
pageSize: Int,
searchPageSize: Int,
-) : PagedMangaParser(context, source, pageSize, searchPageSize), Interceptor {
+) : LegacyPagedMangaParser(context, source, pageSize, searchPageSize), Interceptor {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangaworld/MangaWorldParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangaworld/MangaWorldParser.kt
index a62c2f144..616189b20 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangaworld/MangaWorldParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangaworld/MangaWorldParser.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.mangaworld
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ internal abstract class MangaWorldParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 16,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val availableSortOrders: Set = EnumSet.of(
SortOrder.POPULARITY,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt
index 555c03aa9..19e845cd0 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt
@@ -4,7 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -16,7 +16,7 @@ internal abstract class MmrcmsParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 20,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt
index f01e743c7..046335dd7 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.nepnep
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -20,7 +20,7 @@ internal abstract class NepnepParser(
context: MangaLoaderContext,
source: MangaParserSource,
domain: String,
-) : MangaParser(context, source) {
+) : LegacyMangaParser(context, source) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/onemanga/OneMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/onemanga/OneMangaParser.kt
index 0e993d8a8..2ce07b75d 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/onemanga/OneMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/onemanga/OneMangaParser.kt
@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.parsers.site.onemanga
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -11,7 +11,7 @@ internal abstract class OneMangaParser(
context: MangaLoaderContext,
source: MangaParserSource,
domain: String,
-) : SinglePageMangaParser(context, source) {
+) : LegacySinglePageMangaParser(context, source) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt
index 2f31dc429..9de0b85af 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.otakusanctuary
import kotlinx.coroutines.coroutineScope
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -16,7 +16,7 @@ internal abstract class OtakuSanctuaryParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 32,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pizzareader/PizzaReaderParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pizzareader/PizzaReaderParser.kt
index 168b62004..766746fcf 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pizzareader/PizzaReaderParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pizzareader/PizzaReaderParser.kt
@@ -4,7 +4,7 @@ import kotlinx.coroutines.coroutineScope
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -19,7 +19,7 @@ internal abstract class PizzaReaderParser(
context: MangaLoaderContext,
source: MangaParserSource,
domain: String,
-) : SinglePageMangaParser(context, source) {
+) : LegacySinglePageMangaParser(context, source) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt
index b2aceddda..80bb87ba8 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.pt
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -12,7 +12,7 @@ import java.util.*
@Broken
@MangaSourceParser("BRMANGAS", "BrMangas", "pt")
-internal class BrMangas(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.BRMANGAS, 25) {
+internal class BrMangas(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.BRMANGAS, 25) {
override val configKeyDomain = ConfigKey.Domain("www.brmangas.net")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerManga.kt
index 8670f89fc..351c63647 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerManga.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerManga.kt
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.site.pt
import org.koitharu.kotatsu.parsers.*
import org.koitharu.kotatsu.parsers.config.ConfigKey
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
@@ -9,7 +10,7 @@ import java.util.*
@Broken
@MangaSourceParser("LERMANGA", "LerManga", "pt")
-internal class LerManga(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.LERMANGA, 24) {
+internal class LerManga(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.LERMANGA, 24) {
override val configKeyDomain = ConfigKey.Domain("lermanga.org")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerMangaOnline.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerMangaOnline.kt
index 8c7e10e24..48d40d7c9 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerMangaOnline.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerMangaOnline.kt
@@ -4,7 +4,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ import java.util.*
@Broken
@MangaSourceParser("LERMANGAONLINE", "LerMangaOnline", "pt")
internal class LerMangaOnline(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.LERMANGAONLINE, 20) {
+ LegacyPagedMangaParser(context, MangaParserSource.LERMANGAONLINE, 20) {
override val configKeyDomain = ConfigKey.Domain("lermangaonline.com.br")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LuratoonScansParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LuratoonScansParser.kt
index 6a367c57d..3e164c9ba 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LuratoonScansParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LuratoonScansParser.kt
@@ -8,6 +8,7 @@ import okhttp3.ResponseBody.Companion.toResponseBody
import org.json.JSONArray
import org.koitharu.kotatsu.parsers.*
import org.koitharu.kotatsu.parsers.config.ConfigKey
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
@@ -16,7 +17,7 @@ import java.util.zip.ZipInputStream
@Broken // Not dead but changed template
@MangaSourceParser("RANDOMSCANS", "LuratoonScan", "pt")
internal class LuratoonScansParser(context: MangaLoaderContext) :
- SinglePageMangaParser(context, MangaParserSource.RANDOMSCANS),
+ LegacySinglePageMangaParser(context, MangaParserSource.RANDOMSCANS),
Interceptor {
override val configKeyDomain = ConfigKey.Domain("luratoons.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MangaOnline.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MangaOnline.kt
index c7fea9299..06ed7bfa9 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MangaOnline.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MangaOnline.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.pt
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -10,7 +10,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("MANGAONLINE", "MangaOnline.biz", "pt")
-internal class MangaOnline(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAONLINE, 20) {
+internal class MangaOnline(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.MANGAONLINE, 20) {
override val configKeyDomain = ConfigKey.Domain("mangaonline.biz")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MuitoHentai.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MuitoHentai.kt
index e4fbe88ae..94714540c 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MuitoHentai.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MuitoHentai.kt
@@ -2,14 +2,15 @@ package org.koitharu.kotatsu.parsers.site.pt
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("MUITOHENTAI", "MuitoHentai", "pt", ContentType.HENTAI)
-internal class MuitoHentai(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MUITOHENTAI, 24) {
+internal class MuitoHentai(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.MUITOHENTAI, 24) {
override val configKeyDomain = ConfigKey.Domain("www.muitohentai.com")
@@ -125,7 +126,7 @@ internal class MuitoHentai(context: MangaLoaderContext) : PagedMangaParser(conte
.map { url ->
if (url.startsWith("https://$domain/")) {
url.substringAfter("$domain/")
- } else url
+ } else url
}
return src.map { url ->
@@ -133,7 +134,7 @@ internal class MuitoHentai(context: MangaLoaderContext) : PagedMangaParser(conte
id = generateUid(url),
url = url.takeUnless { it.startsWith("https://") }?.let { "https://$domain/$it" } ?: url,
preview = null,
- source = source
+ source = source,
)
}
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/OnePieceEx.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/OnePieceEx.kt
index 804aff944..a67f03951 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/OnePieceEx.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/OnePieceEx.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.pt
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -11,7 +11,8 @@ import java.util.*
@Broken
@MangaSourceParser("ONEPIECEEX", "OnePieceEx", "pt")
-internal class OnePieceEx(context: MangaLoaderContext) : SinglePageMangaParser(context, MangaParserSource.ONEPIECEEX) {
+internal class OnePieceEx(context: MangaLoaderContext) :
+ LegacySinglePageMangaParser(context, MangaParserSource.ONEPIECEEX) {
override val configKeyDomain = ConfigKey.Domain("onepieceex.net")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt
index 7bc31ba7e..d0c74d3de 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.pt
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -15,7 +15,7 @@ import java.util.*
@MangaSourceParser("YUGENMANGAS", "YugenApp", "pt")
internal class YugenMangas(context: MangaLoaderContext) :
- SinglePageMangaParser(context, MangaParserSource.YUGENMANGAS) {
+ LegacySinglePageMangaParser(context, MangaParserSource.YUGENMANGAS) {
override val configKeyDomain = ConfigKey.Domain("yugenmangasbr.voblog.xyz")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/AComics.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/AComics.kt
index 754b7b94e..2b42e6299 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/AComics.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/AComics.kt
@@ -6,7 +6,7 @@ import kotlinx.coroutines.sync.withLock
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ import java.util.*
@MangaSourceParser("ACOMICS", "AComics", "ru", ContentType.COMICS)
internal class AComics(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.ACOMICS, pageSize = 10) {
+ LegacyPagedMangaParser(context, MangaParserSource.ACOMICS, pageSize = 10) {
override val availableSortOrders: Set = EnumSet.of(
SortOrder.UPDATED,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/DesuMeParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/DesuMeParser.kt
index 237e31b5d..81f397e71 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/DesuMeParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/DesuMeParser.kt
@@ -5,7 +5,7 @@ import okhttp3.Headers
import okhttp3.HttpUrl
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
@@ -17,7 +17,8 @@ import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
import java.util.*
@MangaSourceParser("DESUME", "Desu", "ru")
-internal class DesuMeParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.DESUME, 20) {
+internal class DesuMeParser(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.DESUME, 20) {
override val configKeyDomain = ConfigKey.Domain("desu.me", "desu.win")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/MangaWtfParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/MangaWtfParser.kt
index cc0d6b9d5..9aa363db8 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/MangaWtfParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/MangaWtfParser.kt
@@ -7,7 +7,7 @@ import org.json.JSONObject
import org.koitharu.kotatsu.parsers.InternalParsersApi
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -18,7 +18,7 @@ import java.util.*
@MangaSourceParser("MANGA_WTF", "MangaWtf", "ru")
internal class MangaWtfParser(
context: MangaLoaderContext,
-) : PagedMangaParser(context, MangaParserSource.MANGA_WTF, pageSize = 20) {
+) : LegacyPagedMangaParser(context, MangaParserSource.MANGA_WTF, pageSize = 20) {
override val availableSortOrders: Set =
EnumSet.of(
@@ -126,14 +126,6 @@ internal class MangaWtfParser(
.addPathSegment(manga.url)
val jo = webClient.httpGet(url.build()).parseJson()
val isNsfwSource = jo.getStringOrNull("contentStatus").isNsfw()
- val author =
- jo.getJSONArray("relations").asTypedList().firstNotNullOfOrNull {
- if (it.getStringOrNull("type") == "AUTHOR") {
- it.getJSONObject("publisher").getStringOrNull("name")
- } else {
- null
- }
- }
Manga(
id = generateUid(jo.getString("id")),
title = jo.getJSONObject("name").getString("ru"),
@@ -145,7 +137,13 @@ internal class MangaWtfParser(
coverUrl = jo.getString("poster"),
tags = jo.getJSONArray("labels").mapJSONToSet { it.toMangaTag() },
state = jo.getStringOrNull("status")?.toMangaState(),
- authors = author?.let { setOf(it) } ?: emptySet(),
+ authors = jo.getJSONArray("relations").asTypedList().mapNotNullToSet {
+ if (it.getStringOrNull("type") == "AUTHOR") {
+ it.getJSONObject("publisher").getStringOrNull("name")
+ } else {
+ null
+ }
+ },
source = source,
largeCoverUrl = null,
description = jo.getString("description").nl2br(),
@@ -211,10 +209,10 @@ internal class MangaWtfParser(
MangaChapter(
id = generateUid(jo.getString("id")),
name =
- jo.getStringOrNull("name") ?: buildString {
- if (volume > 0) append("Том ").append(volume).append(' ')
- if (number > 0) append("Глава ").append(number) else append("Без имени")
- },
+ jo.getStringOrNull("name") ?: buildString {
+ if (volume > 0) append("Том ").append(volume).append(' ')
+ if (number > 0) append("Глава ").append(number) else append("Без имени")
+ },
number = number,
volume = volume,
url = jo.getString("id"),
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/NudeMoonParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/NudeMoonParser.kt
index 045b836d4..72b264526 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/NudeMoonParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/NudeMoonParser.kt
@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.parsers.site.ru
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
@@ -14,7 +14,7 @@ import java.util.*
@MangaSourceParser("NUDEMOON", "Nude-Moon", "ru", type = ContentType.HENTAI)
internal class NudeMoonParser(
context: MangaLoaderContext,
-) : MangaParser(context, MangaParserSource.NUDEMOON), MangaParserAuthProvider {
+) : LegacyMangaParser(context, MangaParserSource.NUDEMOON), MangaParserAuthProvider {
override val configKeyDomain = ConfigKey.Domain(
"b.nude-moon.fun",
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/RemangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/RemangaParser.kt
index 35165f083..5a52faab9 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/RemangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/RemangaParser.kt
@@ -10,7 +10,7 @@ import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ContentUnavailableException
import org.koitharu.kotatsu.parsers.exception.ParseException
@@ -29,7 +29,7 @@ private const val TOO_MANY_REQUESTS = 429
@MangaSourceParser("REMANGA", "Реманга", "ru")
internal class RemangaParser(
context: MangaLoaderContext,
-) : PagedMangaParser(context, MangaParserSource.REMANGA, PAGE_SIZE), MangaParserAuthProvider, Interceptor {
+) : LegacyPagedMangaParser(context, MangaParserSource.REMANGA, PAGE_SIZE), MangaParserAuthProvider, Interceptor {
private val baseHeaders: Headers
get() = Headers.Builder()
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/WaMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/WaMangaParser.kt
index 77e652341..b5468f22d 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/WaMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/WaMangaParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.ru
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ import java.util.*
@MangaSourceParser("WAMANGA", "WaManga", "ru", type = ContentType.MANGA)
internal class WaMangaParser(
context: MangaLoaderContext,
-) : SinglePageMangaParser(context, MangaParserSource.WAMANGA) {
+) : LegacySinglePageMangaParser(context, MangaParserSource.WAMANGA) {
override val configKeyDomain = ConfigKey.Domain("wamanga.me")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt
index 35dcc7db8..77a3560b0 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt
@@ -17,7 +17,7 @@ import okio.IOException
import org.json.JSONArray
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
@@ -43,7 +43,7 @@ internal abstract class GroupleParser(
context: MangaLoaderContext,
source: MangaParserSource,
private val siteId: Int,
-) : MangaParser(context, source), MangaParserAuthProvider, Interceptor {
+) : LegacyMangaParser(context, source), MangaParserAuthProvider, Interceptor {
@Volatile
private var cachedPagesServer: String? = null
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/ChanParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/ChanParser.kt
index 7c6ee5934..03cc4ddd8 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/ChanParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/multichan/ChanParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.ru.multichan
import okhttp3.HttpUrl
import org.jsoup.internal.StringUtil
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.model.*
@@ -14,7 +14,7 @@ import java.util.*
internal abstract class ChanParser(
context: MangaLoaderContext,
source: MangaParserSource,
-) : MangaParser(context, source), MangaParserAuthProvider {
+) : LegacyMangaParser(context, source), MangaParserAuthProvider {
override val availableSortOrders: Set = EnumSet.of(
SortOrder.NEWEST,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/rulib/LibSocialParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/rulib/LibSocialParser.kt
index 938f210c0..ac30dad1a 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/rulib/LibSocialParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/rulib/LibSocialParser.kt
@@ -7,7 +7,7 @@ import okhttp3.HttpUrl
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -21,7 +21,7 @@ internal abstract class LibSocialParser(
source: MangaParserSource,
protected val siteDomain: String,
protected val siteId: Int,
-) : PagedMangaParser(context, source, pageSize = 60) {
+) : LegacyPagedMangaParser(context, source, pageSize = 60) {
override val availableSortOrders: Set = EnumSet.of(
SortOrder.UPDATED,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt
index 1e626e44f..2b007ab4b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/scan/ScanParser.kt
@@ -5,7 +5,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.jsoup.Jsoup
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -18,7 +18,7 @@ internal abstract class ScanParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 0,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt
index 089ed9cf7..4e275b93b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.sinmh
import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ internal abstract class SinmhParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 36,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/MangaAy.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/MangaAy.kt
index 6bddf5572..d1493d77f 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/MangaAy.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/MangaAy.kt
@@ -6,7 +6,7 @@ import kotlinx.coroutines.sync.withLock
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -14,7 +14,7 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("MANGAAY", "MangaAy", "tr")
-internal class MangaAy(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAAY, 45) {
+internal class MangaAy(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.MANGAAY, 45) {
override val configKeyDomain = ConfigKey.Domain("manga-ay.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt
index 60f39fbee..9ddc2632b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.tr
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.SinglePageMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -10,7 +10,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("SADSCANS", "SadScans", "tr")
-internal class SadScans(context: MangaLoaderContext) : SinglePageMangaParser(context, MangaParserSource.SADSCANS) {
+internal class SadScans(context: MangaLoaderContext) :
+ LegacySinglePageMangaParser(context, MangaParserSource.SADSCANS) {
override val configKeyDomain = ConfigKey.Domain("sadscans.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt
index be12e2bd2..f9e1bec1f 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.tr
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -13,7 +13,7 @@ import java.util.*
@MangaSourceParser("TRWEBTOON", "TrWebtoon", "tr")
internal class TrWebtoon(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.TRWEBTOON, pageSize = 21) {
+ LegacyPagedMangaParser(context, MangaParserSource.TRWEBTOON, pageSize = 21) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("trwebtoon.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt
index 4b284921d..7e250aeb0 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HentaiUkrParser.kt
@@ -8,7 +8,7 @@ import okhttp3.Response
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
@@ -25,7 +25,7 @@ private const val PAGE_SIZE = 60
// NOTE High profile focus
@MangaSourceParser("HENTAIUKR", "HentaiUkr", "uk", ContentType.HENTAI)
-internal class HentaiUkrParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.HENTAIUKR),
+internal class HentaiUkrParser(context: MangaLoaderContext) : LegacyMangaParser(context, MangaParserSource.HENTAIUKR),
Interceptor {
private val date = SimpleDateFormat("yyyy-MM-dd", Locale.US)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HoneyMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HoneyMangaParser.kt
index 67f9d1de7..5d9ad1b31 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HoneyMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/HoneyMangaParser.kt
@@ -7,7 +7,7 @@ import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -27,7 +27,7 @@ private const val IMAGE_BASEURL_FALLBACK = "https://hmvolumestorage.b-cdn.net/pu
@MangaSourceParser("HONEYMANGA", "HoneyManga", "uk")
internal class HoneyMangaParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.HONEYMANGA, PAGE_SIZE),
+ LegacyPagedMangaParser(context, MangaParserSource.HONEYMANGA, PAGE_SIZE),
Interceptor {
private val urlApi get() = "https://data.api.$domain"
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/MangaInUaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/MangaInUaParser.kt
index 9fbf70bb4..7e968ca11 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/MangaInUaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/uk/MangaInUaParser.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.uk
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -12,7 +12,7 @@ import java.util.*
private const val DEF_BRANCH_NAME = "Основний переклад"
@MangaSourceParser("MANGAINUA", "MANGA/in/UA", "uk")
-internal class MangaInUaParser(context: MangaLoaderContext) : PagedMangaParser(
+internal class MangaInUaParser(context: MangaLoaderContext) : LegacyPagedMangaParser(
context = context,
source = MangaParserSource.MANGAINUA,
pageSize = 24,
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt
index eba7c8b08..de40c714e 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt
@@ -5,7 +5,7 @@ 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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -17,7 +17,7 @@ import org.koitharu.kotatsu.parsers.Broken
@Broken
@MangaSourceParser("BLOGTRUYEN", "Blog Truyện", "vi")
internal class BlogTruyenParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.BLOGTRUYEN, pageSize = 20) {
+ LegacyPagedMangaParser(context, MangaParserSource.BLOGTRUYEN, pageSize = 20) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("blogtruyenmoi.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVN.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVN.kt
index a4a18a72d..8f28c111e 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVN.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenVN.kt
@@ -7,7 +7,7 @@ import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -20,7 +20,7 @@ import java.util.*
@Broken
@MangaSourceParser("BLOGTRUYENVN", "BlogTruyen.vn (Unofficial)", "vi")
internal class BlogTruyenVN(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.BLOGTRUYENVN, pageSize = 20) {
+ LegacyPagedMangaParser(context, MangaParserSource.BLOGTRUYENVN, pageSize = 20) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("blogtruyenvn.org", "blogtruyenvn.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BuonDuaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BuonDuaParser.kt
index 990bf4df9..ccdffb17e 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BuonDuaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BuonDuaParser.kt
@@ -1,43 +1,20 @@
package org.koitharu.kotatsu.parsers.site.vi
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
-import org.koitharu.kotatsu.parsers.model.ContentRating
-import org.koitharu.kotatsu.parsers.model.ContentType
-import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.parsers.model.MangaChapter
-import org.koitharu.kotatsu.parsers.model.MangaListFilter
-import org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities
-import org.koitharu.kotatsu.parsers.model.MangaListFilterOptions
-import org.koitharu.kotatsu.parsers.model.MangaPage
-import org.koitharu.kotatsu.parsers.model.MangaParserSource
-import org.koitharu.kotatsu.parsers.model.MangaState
-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.attrAsRelativeUrl
-import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrlOrNull
-import org.koitharu.kotatsu.parsers.util.attrOrNull
-import org.koitharu.kotatsu.parsers.util.attrOrThrow
-import org.koitharu.kotatsu.parsers.util.domain
-import org.koitharu.kotatsu.parsers.util.generateUid
-import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
-import org.koitharu.kotatsu.parsers.util.parseHtml
-import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
-import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
-import org.koitharu.kotatsu.parsers.util.tryParse
-import org.koitharu.kotatsu.parsers.util.urlBuilder
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
+import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
-import java.util.EnumSet
+import java.util.*
@MangaSourceParser("BUONDUA", "Buon Dua", type = ContentType.OTHER)
-internal class BuonDuaParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.BUONDUA) {
+internal class BuonDuaParser(context: MangaLoaderContext) : LegacyMangaParser(context, MangaParserSource.BUONDUA) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain(
"buondua.com",
- "buondua.us"
+ "buondua.us",
)
override val availableSortOrders: Set
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CMangaParser.kt
index afd76828c..28f1ac4fc 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CMangaParser.kt
@@ -6,7 +6,7 @@ import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.model.Manga
@@ -20,7 +20,6 @@ import org.koitharu.kotatsu.parsers.model.MangaState
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.domain
import org.koitharu.kotatsu.parsers.util.generateUid
import org.koitharu.kotatsu.parsers.util.getCookies
import org.koitharu.kotatsu.parsers.util.json.asTypedList
@@ -46,7 +45,7 @@ private const val PAGE_SIZE = 20
@MangaSourceParser("CMANGA", "CManga", "vi")
internal class CMangaParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.CMANGA, PAGE_SIZE), MangaParserAuthProvider {
+ LegacyPagedMangaParser(context, MangaParserSource.CMANGA, PAGE_SIZE), MangaParserAuthProvider {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("cmangax.com")
@@ -131,7 +130,7 @@ internal class CMangaParser(context: MangaLoaderContext) :
order == SortOrder.POPULARITY_TODAY ||
order == SortOrder.POPULARITY_WEEK ||
order == SortOrder.POPULARITY_MONTH
- )
+ )
) {
addPathSegments("api/home_album_top")
} else {
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt
index 108b80cb6..fb83459f6 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt
@@ -10,7 +10,7 @@ import okio.IOException
import org.jsoup.HttpStatusException
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.bitmap.Bitmap
import org.koitharu.kotatsu.parsers.bitmap.Rect
import org.koitharu.kotatsu.parsers.config.ConfigKey
@@ -25,7 +25,7 @@ import java.util.TimeZone
@MangaSourceParser("CUUTRUYEN", "Cứu Truyện", "vi")
internal class CuuTruyenParser(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.CUUTRUYEN, 20), Interceptor {
+ LegacyPagedMangaParser(context, MangaParserSource.CUUTRUYEN, 20), Interceptor {
override val userAgentKey = ConfigKey.UserAgent(UserAgents.KOTATSU)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/DuaLeoTruyen.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/DuaLeoTruyen.kt
index 8ffb5ceb9..559e8693f 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/DuaLeoTruyen.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/DuaLeoTruyen.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.vi
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("DUALEOTRUYEN", "Dưa Leo Truyện", "vi", type = ContentType.HENTAI)
internal class DuaLeoTruyen(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.DUALEOTRUYEN, 60) {
+ LegacyPagedMangaParser(context, MangaParserSource.DUALEOTRUYEN, 60) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("dualeotruyenxxy.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/GocTruyenTranh.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/GocTruyenTranh.kt
index 39524a380..5f47c1f5d 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/GocTruyenTranh.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/GocTruyenTranh.kt
@@ -3,299 +3,303 @@ package org.koitharu.kotatsu.parsers.site.vi
import androidx.collection.arraySetOf
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.network.UserAgents
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
-import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("GOCTRUYENTRANH", "Góc Truyện Tranh", "vi")
internal class GocTruyenTranh(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.GOCTRUYENTRANH, 30) {
-
- override val configKeyDomain = ConfigKey.Domain("goctruyentranh.net")
+ LegacyPagedMangaParser(context, MangaParserSource.GOCTRUYENTRANH, 30) {
- override fun onCreateConfig(keys: MutableCollection>) {
+ override val configKeyDomain = ConfigKey.Domain("goctruyentranh.net")
+
+ override fun onCreateConfig(keys: MutableCollection>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP)
- override val availableSortOrders: Set = EnumSet.of(
- SortOrder.UPDATED,
- SortOrder.NEWEST,
- SortOrder.NEWEST_ASC,
- SortOrder.ALPHABETICAL,
- SortOrder.RATING,
- SortOrder.POPULARITY
- )
+ override val availableSortOrders: Set = EnumSet.of(
+ SortOrder.UPDATED,
+ SortOrder.NEWEST,
+ SortOrder.NEWEST_ASC,
+ SortOrder.ALPHABETICAL,
+ SortOrder.RATING,
+ SortOrder.POPULARITY,
+ )
+
+ override val filterCapabilities: MangaListFilterCapabilities
+ get() = MangaListFilterCapabilities(
+ isMultipleTagsSupported = true,
+ isTagsExclusionSupported = false,
+ isSearchSupported = true,
+ isSearchWithFiltersSupported = true,
+ )
+
+ override suspend fun getFilterOptions() = MangaListFilterOptions(
+ availableTags = availableTags(),
+ availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
+ availableContentTypes = EnumSet.of(
+ ContentType.MANGA,
+ ContentType.MANHWA,
+ ContentType.MANHUA,
+ ContentType.OTHER,
+ ),
+ )
+
+ override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
+ val url = buildString {
+ append("https://")
+ append(domain)
+ append("/baseapi/comics/filterComic")
+ append("?keyword=")
+ append(filter.query?.urlEncoded() ?: "")
+
+ if (filter.tags.isNotEmpty()) {
+ append("&categories=")
+ append(
+ filter.tags.joinToString(",") { tag ->
+ availableTags().find { it.title == tag.title }?.key ?: tag.key
+ },
+ )
+ }
- override val filterCapabilities: MangaListFilterCapabilities
- get() = MangaListFilterCapabilities(
- isMultipleTagsSupported = true,
- isTagsExclusionSupported = false,
- isSearchSupported = true,
- isSearchWithFiltersSupported = true,
- )
+ append("&status=")
+ when {
+ filter.states.isEmpty() -> append("")
+ filter.states.size > 1 -> append("")
+ else -> append(
+ when (filter.states.first()) {
+ MangaState.ONGOING -> "0"
+ MangaState.FINISHED -> "1"
+ else -> ""
+ },
+ )
+ }
- override suspend fun getFilterOptions() = MangaListFilterOptions(
- availableTags = availableTags(),
- availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
- availableContentTypes = EnumSet.of(
- ContentType.MANGA,
- ContentType.MANHWA,
- ContentType.MANHUA,
- ContentType.OTHER,
- ),
- )
+ append("&sort=")
+ append(
+ when (order) {
+ SortOrder.UPDATED -> "recently_updated"
+ SortOrder.NEWEST -> "latest"
+ SortOrder.NEWEST_ASC -> "oldest"
+ SortOrder.RATING -> "rating"
+ SortOrder.ALPHABETICAL -> "alphabet"
+ SortOrder.POPULARITY -> "mostView"
+ else -> "recently_updated"
+ },
+ )
- override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
- val url = buildString {
- append("https://")
- append(domain)
- append("/baseapi/comics/filterComic")
- append("?keyword=")
- append(filter.query?.urlEncoded() ?: "")
-
- if (filter.tags.isNotEmpty()) {
- append("&categories=")
- append(filter.tags.joinToString(",") { tag ->
- availableTags().find { it.title == tag.title }?.key ?: tag.key
- })
- }
-
- append("&status=")
- when {
- filter.states.isEmpty() -> append("")
- filter.states.size > 1 -> append("")
- else -> append(
- when (filter.states.first()) {
- MangaState.ONGOING -> "0"
- MangaState.FINISHED -> "1"
- else -> ""
- }
- )
- }
-
- append("&sort=")
- append(
- when (order) {
- SortOrder.UPDATED -> "recently_updated"
- SortOrder.NEWEST -> "latest"
- SortOrder.NEWEST_ASC -> "oldest"
- SortOrder.RATING -> "rating"
- SortOrder.ALPHABETICAL -> "alphabet"
- SortOrder.POPULARITY -> "mostView"
- else -> "recently_updated"
- }
- )
-
- if (filter.types.isNotEmpty()) {
- append("&country=")
- append(
- filter.types.joinToString(",") {
- when (it) {
- ContentType.MANGA -> "manga"
- ContentType.MANHWA -> "manhwa"
- ContentType.MANHUA -> "manhua"
- ContentType.OTHER -> "other"
- else -> "manga"
- }
- }
- )
- }
-
- append("&page=")
- append(page)
- }
-
- val json = webClient.httpGet(url).parseJson()
- val data = json.getJSONObject("comics").getJSONArray("data")
-
- return List(data.length()) { i ->
- val item = data.getJSONObject(i)
- val slug = item.getString("slug")
- val mangaUrl = buildString {
- append("https://")
- append(domain)
- append("/")
- append(slug)
- }
-
- val categories = item.optJSONArray("categories")
- val tags = if (categories != null) {
- List(categories.length()) { j ->
- val category = categories.getJSONObject(j)
- MangaTag(
- key = category.getString("id"),
- title = category.getString("name").toTitleCase(sourceLocale),
- source = source,
- )
- }.toSet()
- } else {
- emptySet()
- }
+ if (filter.types.isNotEmpty()) {
+ append("&country=")
+ append(
+ filter.types.joinToString(",") {
+ when (it) {
+ ContentType.MANGA -> "manga"
+ ContentType.MANHWA -> "manhwa"
+ ContentType.MANHUA -> "manhua"
+ ContentType.OTHER -> "other"
+ else -> "manga"
+ }
+ },
+ )
+ }
+
+ append("&page=")
+ append(page)
+ }
+
+ val json = webClient.httpGet(url).parseJson()
+ val data = json.getJSONObject("comics").getJSONArray("data")
+
+ return List(data.length()) { i ->
+ val item = data.getJSONObject(i)
+ val slug = item.getString("slug")
+ val mangaUrl = buildString {
+ append("https://")
+ append(domain)
+ append("/")
+ append(slug)
+ }
+
+ val categories = item.optJSONArray("categories")
+ val tags = if (categories != null) {
+ List(categories.length()) { j ->
+ val category = categories.getJSONObject(j)
+ MangaTag(
+ key = category.getString("id"),
+ title = category.getString("name").toTitleCase(sourceLocale),
+ source = source,
+ )
+ }.toSet()
+ } else {
+ emptySet()
+ }
// Check NSFW manga by tags, API / Site not have this information
- val checkNsfw = tags.any { tag ->
- tag.key in setOf("25", "39", "41", "43", "57", "63")
- }
-
- Manga(
- id = generateUid(mangaUrl),
- url = "/$slug",
- publicUrl = mangaUrl,
- title = item.getString("name"),
- altTitle = item.optString("origin_name")?.takeUnless { it == "null" || it.isEmpty() },
- description = item.optString("content"),
- rating = RATING_UNKNOWN,
+ val checkNsfw = tags.any { tag ->
+ tag.key in setOf("25", "39", "41", "43", "57", "63")
+ }
+
+ Manga(
+ id = generateUid(mangaUrl),
+ url = "/$slug",
+ publicUrl = mangaUrl,
+ title = item.getString("name"),
+ altTitle = item.optString("origin_name")?.takeUnless { it == "null" || it.isEmpty() },
+ description = item.optString("content"),
+ rating = RATING_UNKNOWN,
contentRating = if (checkNsfw || isNsfwSource) ContentRating.ADULT else null,
- coverUrl = item.optString("thumbnail"),
- tags = tags,
- state = when (item.optString("status")) {
- "0" -> MangaState.ONGOING
- "1" -> MangaState.FINISHED
- else -> null
- },
- source = source,
- authors = emptySet(),
- )
- }
- }
+ coverUrl = item.optString("thumbnail"),
+ tags = tags,
+ state = when (item.optString("status")) {
+ "0" -> MangaState.ONGOING
+ "1" -> MangaState.FINISHED
+ else -> null
+ },
+ source = source,
+ authors = emptySet(),
+ )
+ }
+ }
+
+ override suspend fun getDetails(manga: Manga): Manga {
+ val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
+ return manga.copy(
+ rating = doc.selectFirst("div > span.leading-none")?.text()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
+ authors = setOfNotNull(doc.selectFirst("aside p:contains(Tác giả:) a[href^='/tac-gia/']")?.textOrNull()),
+ chapters = doc.select("ul[itemtype='https://schema.org/ItemList'] li")
+ .mapChapters(reversed = true) { i, li ->
+ val a = li.selectFirstOrThrow("a")
+ val href = a.attrAsRelativeUrl("href")
+ val name = li.selectFirst("div.w-\\[50\\%\\].truncate.flex")?.text() ?: ""
+ val dateText = li.selectFirst("div.w-\\[50\\%\\].truncate.text-center")?.text()
+ MangaChapter(
+ id = generateUid(href),
+ name = name,
+ number = i + 1f,
+ volume = 0,
+ url = href,
+ scanlator = null,
+ uploadDate = parseChapterDate(dateText),
+ branch = null,
+ source = source,
+ )
+ },
+ )
+ }
+
+ override suspend fun getPages(chapter: MangaChapter): List {
+ val fullUrl = chapter.url.toAbsoluteUrl(domain)
+ val doc = webClient.httpGet(fullUrl).parseHtml()
+ return doc.select("img.lozad[data-src]").map { img ->
+ val url = img.attr("data-src")
+ MangaPage(
+ id = generateUid(url),
+ url = url,
+ preview = null,
+ source = source,
+ )
+ }
+ }
- override suspend fun getDetails(manga: Manga): Manga {
- val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
- val author = doc.selectFirst("aside p:contains(Tác giả:) a[href^='/tac-gia/']")?.text()
- return manga.copy(
- rating = doc.selectFirst("div > span.leading-none")?.text()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
- authors = author?.let { setOf(it) } ?: emptySet(),
- chapters = doc.select("ul[itemtype='https://schema.org/ItemList'] li").mapChapters(reversed = true) { i, li ->
- val a = li.selectFirstOrThrow("a")
- val href = a.attrAsRelativeUrl("href")
- val name = li.selectFirst("div.w-\\[50\\%\\].truncate.flex")?.text() ?: ""
- val dateText = li.selectFirst("div.w-\\[50\\%\\].truncate.text-center")?.text()
- MangaChapter(
- id = generateUid(href),
- name = name,
- number = i + 1f,
- volume = 0,
- url = href,
- scanlator = null,
- uploadDate = parseChapterDate(dateText),
- branch = null,
- source = source,
- )
- },
- )
- }
+ private fun parseChapterDate(dateText: String?): Long {
+ if (dateText == null) return 0
- override suspend fun getPages(chapter: MangaChapter): List {
- val fullUrl = chapter.url.toAbsoluteUrl(domain)
- val doc = webClient.httpGet(fullUrl).parseHtml()
- return doc.select("img.lozad[data-src]").map { img ->
- val url = img.attr("data-src")
- MangaPage(
- id = generateUid(url),
- url = url,
- preview = null,
- source = source,
- )
- }
- }
+ val number = dateText.filter { it.isDigit() }.toIntOrNull() ?: return 0
+ val now = System.currentTimeMillis()
- private fun parseChapterDate(dateText: String?): Long {
- if (dateText == null) return 0
-
- val number = dateText.filter { it.isDigit() }.toIntOrNull() ?: return 0
- val now = System.currentTimeMillis()
-
- return when {
- dateText.contains("phút trước") -> {
- now - (number * 60 * 1000L)
- }
- dateText.contains("giờ trước") -> {
- now - (number * 60 * 60 * 1000L)
- }
- dateText.contains("ngày trước") -> {
- now - (number * 24 * 60 * 60 * 1000L)
- }
- else -> 0L
- }
- }
+ return when {
+ dateText.contains("phút trước") -> {
+ now - (number * 60 * 1000L)
+ }
+
+ dateText.contains("giờ trước") -> {
+ now - (number * 60 * 60 * 1000L)
+ }
+
+ dateText.contains("ngày trước") -> {
+ now - (number * 24 * 60 * 60 * 1000L)
+ }
+
+ else -> 0L
+ }
+ }
- private fun availableTags() = arraySetOf(
- MangaTag("Action", "1", source),
- MangaTag("Adventure", "2", source),
- MangaTag("Fantasy", "3", source),
- MangaTag("Manhua", "4", source),
- MangaTag("Chuyển Sinh", "5", source),
- MangaTag("Truyện Màu", "6", source),
- MangaTag("Xuyên Không", "7", source),
- MangaTag("Manhwa", "8", source),
- MangaTag("Drama", "9", source),
- MangaTag("Historical", "10", source),
- MangaTag("Manga", "11", source),
- MangaTag("Seinen", "12", source),
- MangaTag("Comedy", "13", source),
- MangaTag("Martial Arts", "14", source),
- MangaTag("Mystery", "15", source),
- MangaTag("Romance", "16", source),
- MangaTag("Shounen", "17", source),
- MangaTag("Sports", "18", source),
- MangaTag("Supernatural", "19", source),
- MangaTag("Harem", "20", source),
- MangaTag("Webtoon", "21", source),
- MangaTag("School Life", "22", source),
- MangaTag("Psychological", "23", source),
- MangaTag("Cổ Đại", "24", source),
- MangaTag("Ecchi", "25", source),
- MangaTag("Gender Bender", "26", source),
- MangaTag("Shoujo", "27", source),
- MangaTag("Slice of Life", "28", source),
- MangaTag("Ngôn Tình", "29", source),
- MangaTag("Horror", "30", source),
- MangaTag("Sci-fi", "31", source),
- MangaTag("Tragedy", "32", source),
- MangaTag("Mecha", "33", source),
- MangaTag("Comic", "34", source),
- MangaTag("One shot", "35", source),
- MangaTag("Shoujo Ai", "36", source),
- MangaTag("Anime", "37", source),
- MangaTag("Josei", "38", source),
- MangaTag("Smut", "39", source),
- MangaTag("Shounen Ai", "40", source),
- MangaTag("Mature", "41", source),
- MangaTag("Soft Yuri", "42", source),
- MangaTag("Adult", "43", source),
- MangaTag("Doujinshi", "44", source),
- MangaTag("Live action", "45", source),
- MangaTag("Trinh Thám", "46", source),
- MangaTag("Việt Nam", "47", source),
- MangaTag("Truyện scan", "48", source),
- MangaTag("Cooking", "49", source),
- MangaTag("Tạp chí truyện tranh", "50", source),
- MangaTag("16+", "51", source),
- MangaTag("Thiếu Nhi", "52", source),
- MangaTag("Soft Yaoi", "53", source),
- MangaTag("Đam Mỹ", "54", source),
- MangaTag("BoyLove", "55", source),
- MangaTag("Yaoi", "56", source),
- MangaTag("18+", "57", source),
- MangaTag("Người Thú", "58", source),
- MangaTag("ABO", "59", source),
- MangaTag("Mafia", "60", source),
- MangaTag("Isekai", "61", source),
- MangaTag("Hệ Thống", "62", source),
- MangaTag("NTR", "63", source),
- MangaTag("Yuri", "64", source),
- MangaTag("Girl Love", "65", source),
- MangaTag("Demons", "66", source),
- MangaTag("Huyền Huyễn", "67", source),
- MangaTag("Detective", "68", source),
- MangaTag("Trọng Sinh", "69", source),
- MangaTag("Magic", "70", source),
- )
+ private fun availableTags() = arraySetOf(
+ MangaTag("Action", "1", source),
+ MangaTag("Adventure", "2", source),
+ MangaTag("Fantasy", "3", source),
+ MangaTag("Manhua", "4", source),
+ MangaTag("Chuyển Sinh", "5", source),
+ MangaTag("Truyện Màu", "6", source),
+ MangaTag("Xuyên Không", "7", source),
+ MangaTag("Manhwa", "8", source),
+ MangaTag("Drama", "9", source),
+ MangaTag("Historical", "10", source),
+ MangaTag("Manga", "11", source),
+ MangaTag("Seinen", "12", source),
+ MangaTag("Comedy", "13", source),
+ MangaTag("Martial Arts", "14", source),
+ MangaTag("Mystery", "15", source),
+ MangaTag("Romance", "16", source),
+ MangaTag("Shounen", "17", source),
+ MangaTag("Sports", "18", source),
+ MangaTag("Supernatural", "19", source),
+ MangaTag("Harem", "20", source),
+ MangaTag("Webtoon", "21", source),
+ MangaTag("School Life", "22", source),
+ MangaTag("Psychological", "23", source),
+ MangaTag("Cổ Đại", "24", source),
+ MangaTag("Ecchi", "25", source),
+ MangaTag("Gender Bender", "26", source),
+ MangaTag("Shoujo", "27", source),
+ MangaTag("Slice of Life", "28", source),
+ MangaTag("Ngôn Tình", "29", source),
+ MangaTag("Horror", "30", source),
+ MangaTag("Sci-fi", "31", source),
+ MangaTag("Tragedy", "32", source),
+ MangaTag("Mecha", "33", source),
+ MangaTag("Comic", "34", source),
+ MangaTag("One shot", "35", source),
+ MangaTag("Shoujo Ai", "36", source),
+ MangaTag("Anime", "37", source),
+ MangaTag("Josei", "38", source),
+ MangaTag("Smut", "39", source),
+ MangaTag("Shounen Ai", "40", source),
+ MangaTag("Mature", "41", source),
+ MangaTag("Soft Yuri", "42", source),
+ MangaTag("Adult", "43", source),
+ MangaTag("Doujinshi", "44", source),
+ MangaTag("Live action", "45", source),
+ MangaTag("Trinh Thám", "46", source),
+ MangaTag("Việt Nam", "47", source),
+ MangaTag("Truyện scan", "48", source),
+ MangaTag("Cooking", "49", source),
+ MangaTag("Tạp chí truyện tranh", "50", source),
+ MangaTag("16+", "51", source),
+ MangaTag("Thiếu Nhi", "52", source),
+ MangaTag("Soft Yaoi", "53", source),
+ MangaTag("Đam Mỹ", "54", source),
+ MangaTag("BoyLove", "55", source),
+ MangaTag("Yaoi", "56", source),
+ MangaTag("18+", "57", source),
+ MangaTag("Người Thú", "58", source),
+ MangaTag("ABO", "59", source),
+ MangaTag("Mafia", "60", source),
+ MangaTag("Isekai", "61", source),
+ MangaTag("Hệ Thống", "62", source),
+ MangaTag("NTR", "63", source),
+ MangaTag("Yuri", "64", source),
+ MangaTag("Girl Love", "65", source),
+ MangaTag("Demons", "66", source),
+ MangaTag("Huyền Huyễn", "67", source),
+ MangaTag("Detective", "68", source),
+ MangaTag("Trọng Sinh", "69", source),
+ MangaTag("Magic", "70", source),
+ )
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Hentai18VN.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Hentai18VN.kt
index 5f60b254e..792faad50 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Hentai18VN.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Hentai18VN.kt
@@ -1,13 +1,11 @@
package org.koitharu.kotatsu.parsers.site.vi
-import androidx.collection.arraySetOf
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
-import org.json.JSONObject
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -15,7 +13,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("HENTAI18VN", "Hentai18VN", "vi", type = ContentType.HENTAI)
-internal class Hentai18VN(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.HENTAI18VN, 30) {
+internal class Hentai18VN(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.HENTAI18VN, 30) {
override val configKeyDomain = ConfigKey.Domain("hentai18vn.art")
@@ -24,205 +23,199 @@ internal class Hentai18VN(context: MangaLoaderContext) : PagedMangaParser(contex
keys.add(userAgentKey)
}
- override val filterCapabilities: MangaListFilterCapabilities
- get() = MangaListFilterCapabilities(
- isSearchSupported = true,
- isSearchWithFiltersSupported = false
- )
-
- override suspend fun getFilterOptions() = MangaListFilterOptions(availableTags = fetchAvailableTags())
- override val availableSortOrders: Set = EnumSet.of(
- SortOrder.NEWEST,
- SortOrder.POPULARITY,
- SortOrder.UPDATED
- )
-
- override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
- return when {
- !filter.query.isNullOrEmpty() -> {
- if (page > 1) {
- return emptyList()
- }
-
- val keyword = filter.query
- val url = "http://$domain/search/html/1"
- val headers = Headers.Builder().add("X-Requested-With", "XMLHttpRequest").build()
- val response = webClient.httpPost(url.toHttpUrl(), payload = "keyword=$keyword", headers).parseHtml()
- parseMangaSearch(response)
- }
-
- !filter.tags.isNullOrEmpty() -> {
- val tag = filter.tags.first()
- val url = buildString {
- append("https://")
- append(domain)
- append("/the-loai/")
- append(tag.key)
- if (page > 1) {
- append("?page=")
- append(page)
- }
- }
- val response = webClient.httpGet(url).parseHtml()
- parseMangaList(response)
- }
-
- else -> {
- val url = buildString {
- append("https://")
- append(domain)
- append("/")
- append(
- when (order) {
- SortOrder.NEWEST -> "danh-sach/truyen-hentai-moi"
- SortOrder.POPULARITY -> "danh-sach/truyen-hentai-hot"
- SortOrder.UPDATED -> "danh-sach/truyen-hentai-hoan-thanh"
- else -> "danh-sach/truyen-hentai-hay"
- }
- )
- if (page > 1) {
- append("?page=")
- append(page)
- }
- }
-
- val response = webClient.httpGet(url).parseHtml()
- parseMangaList(response)
- }
- }
- }
-
- private fun parseMangaSearch(doc: Document): List {
- return doc.select("a.item").map { a ->
- val href = a.attr("href")
- val mangaInfo = a.selectFirst("img")
- Manga(
- id = generateUid(href),
- url = href,
- publicUrl = href.toAbsoluteUrl(domain),
- title = mangaInfo.attr("alt"),
- altTitle = null,
- authors = emptySet(),
- tags = emptySet(),
- rating = RATING_UNKNOWN,
- state = null,
- coverUrl = mangaInfo.requireSrc(),
- contentRating = if (isNsfwSource) ContentRating.ADULT else null,
- source = source
- )
- }
- }
-
- private fun parseMangaList(doc: Document): List {
- return doc.select("div.visual").map { div ->
- val a = div.selectFirst("div.main_text h3.title a")
- val img = div.selectFirst("div.hentai-cover img")
- val mangaUrl = a.attr("href")
- Manga(
- id = generateUid(mangaUrl),
- publicUrl = mangaUrl,
- url = mangaUrl.removePrefix("https://$domain"),
- title = a.text(),
- altTitle = null,
- authors = emptySet(),
- description = null,
- tags = emptySet(),
- rating = RATING_UNKNOWN,
- state = null,
- coverUrl = img.attr("data-original").takeIf { it.isNotEmpty() } ?: img.attr("src"),
- contentRating = if (isNsfwSource) ContentRating.ADULT else null,
- source = source,
- )
- }
- }
-
- override suspend fun getDetails(manga: Manga): Manga {
- val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
- val tags = doc.select("div.hentai-info .line-content a.item-tag")
- .mapNotNull { a ->
- MangaTag(
- title = a.text(),
- key = a.attr("href").substringAfterLast("/"),
- source = source
- )
- }.toSet()
-
- val chapters = doc.select("ul#chapter-list li.citem").mapChapters(reversed=true) { i, li ->
- val a = li.selectFirst("a")
- MangaChapter(
- id = generateUid(a.attr("href")),
- name = a.text(),
- number = i + 1f,
- url = a.attr("href").removePrefix("https://$domain"),
- uploadDate = parseChapterDate(li.selectFirst(".time")?.text()),
- source = source,
- scanlator = null,
- branch = null,
- volume = 0
- )
- }
-
- val altTitle = doc.selectFirst("h2.alternative")?.text()
- val author = doc.selectFirst("div.hentai-info .line:contains(Tác giả) .line-content")?.text()
- val state = when(doc.selectFirst("div.hentai-info .line:contains(Tình trạng) .line-content")?.text()) {
- "Đang cập nhật" -> MangaState.ONGOING
- "Hoàn thành" -> MangaState.FINISHED
- else -> null
- }
-
- return manga.copy(
- tags = tags,
+ override val filterCapabilities: MangaListFilterCapabilities
+ get() = MangaListFilterCapabilities(
+ isSearchSupported = true,
+ isSearchWithFiltersSupported = false,
+ )
+
+ override suspend fun getFilterOptions() = MangaListFilterOptions(availableTags = fetchAvailableTags())
+ override val availableSortOrders: Set = EnumSet.of(
+ SortOrder.NEWEST,
+ SortOrder.POPULARITY,
+ SortOrder.UPDATED,
+ )
+
+ override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
+ return when {
+ !filter.query.isNullOrEmpty() -> {
+ if (page > 1) {
+ return emptyList()
+ }
+
+ val keyword = filter.query
+ val url = "http://$domain/search/html/1"
+ val headers = Headers.Builder().add("X-Requested-With", "XMLHttpRequest").build()
+ val response = webClient.httpPost(url.toHttpUrl(), payload = "keyword=$keyword", headers).parseHtml()
+ parseMangaSearch(response)
+ }
+
+ !filter.tags.isNullOrEmpty() -> {
+ val tag = filter.tags.first()
+ val url = buildString {
+ append("https://")
+ append(domain)
+ append("/the-loai/")
+ append(tag.key)
+ if (page > 1) {
+ append("?page=")
+ append(page)
+ }
+ }
+ val response = webClient.httpGet(url).parseHtml()
+ parseMangaList(response)
+ }
+
+ else -> {
+ val url = buildString {
+ append("https://")
+ append(domain)
+ append("/")
+ append(
+ when (order) {
+ SortOrder.NEWEST -> "danh-sach/truyen-hentai-moi"
+ SortOrder.POPULARITY -> "danh-sach/truyen-hentai-hot"
+ SortOrder.UPDATED -> "danh-sach/truyen-hentai-hoan-thanh"
+ else -> "danh-sach/truyen-hentai-hay"
+ },
+ )
+ if (page > 1) {
+ append("?page=")
+ append(page)
+ }
+ }
+
+ val response = webClient.httpGet(url).parseHtml()
+ parseMangaList(response)
+ }
+ }
+ }
+
+ private fun parseMangaSearch(doc: Document): List {
+ return doc.select("a.item").map { a ->
+ val href = a.attr("href")
+ val mangaInfo = a.selectFirst("img")
+ Manga(
+ id = generateUid(href),
+ url = href,
+ publicUrl = href.toAbsoluteUrl(domain),
+ title = mangaInfo.attr("alt"),
+ altTitle = null,
+ authors = emptySet(),
+ tags = emptySet(),
+ rating = RATING_UNKNOWN,
+ state = null,
+ coverUrl = mangaInfo.requireSrc(),
+ contentRating = ContentRating.ADULT,
+ source = source,
+ )
+ }
+ }
+
+ private fun parseMangaList(doc: Document): List {
+ return doc.select("div.visual").map { div ->
+ val a = div.selectFirstOrThrow("div.main_text h3.title a")
+ val img = div.selectFirst("div.hentai-cover img")
+ val mangaUrl = a.attr("href")
+ Manga(
+ id = generateUid(mangaUrl),
+ publicUrl = mangaUrl,
+ url = mangaUrl.removePrefix("https://$domain"),
+ title = a.text(),
+ altTitle = null,
+ authors = emptySet(),
+ description = null,
+ tags = emptySet(),
+ rating = RATING_UNKNOWN,
+ state = null,
+ coverUrl = img?.src(arrayOf("data-original", "src")),
+ contentRating = ContentRating.ADULT,
+ source = source,
+ )
+ }
+ }
+
+ override suspend fun getDetails(manga: Manga): Manga {
+ val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
+ val tags = doc.select("div.hentai-info .line-content a.item-tag")
+ .mapNotNull { a ->
+ MangaTag(
+ title = a.text(),
+ key = a.attr("href").substringAfterLast("/"),
+ source = source,
+ )
+ }.toSet()
+
+ val chapters = doc.select("ul#chapter-list li.citem").mapChapters(reversed = true) { i, li ->
+ val a = li.selectFirst("a") ?: return@mapChapters null
+ MangaChapter(
+ id = generateUid(a.attr("href")),
+ name = a.text(),
+ number = i + 1f,
+ url = a.attr("href").removePrefix("https://$domain"),
+ uploadDate = parseChapterDate(li.selectFirst(".time")?.text()),
+ source = source,
+ scanlator = null,
+ branch = null,
+ volume = 0,
+ )
+ }
+
+ val altTitle = doc.selectFirst("h2.alternative")?.text()
+ val author = doc.selectFirst("div.hentai-info .line:contains(Tác giả) .line-content")?.text()
+ val state = when (doc.selectFirst("div.hentai-info .line:contains(Tình trạng) .line-content")?.text()) {
+ "Đang cập nhật" -> MangaState.ONGOING
+ "Hoàn thành" -> MangaState.FINISHED
+ else -> null
+ }
+
+ return manga.copy(
+ tags = tags,
authors = author?.let { setOf(it) } ?: emptySet(),
- altTitle = altTitle,
- state = state,
- chapters = chapters,
- description = doc.select("div.about").text()
- )
- }
-
- override suspend fun getPages(chapter: MangaChapter): List {
- val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
- return doc.select("div.chapter-content div.item-photo img").mapNotNull { img ->
- val url = img.requireSrc()
- MangaPage(
- id = generateUid(url),
- url = url,
- preview = null,
- source = source
- )
- }
- }
-
- private suspend fun fetchAvailableTags(): Set {
- val firstPage = webClient.httpGet("https://$domain/tim-the-loai").parseHtml()
- val lastPage = firstPage.selectFirst("a[aria-label=Last]")
- ?.attr("href")
- ?.substringAfter("page=")
- ?.toIntOrNull() ?: 1
-
- return (1..lastPage).flatMap { page ->
- val doc = if (page == 1) {
- firstPage
- } else {
- webClient.httpGet("https://$domain/tim-the-loai?page=$page").parseHtml()
- }
-
- doc.select("ul.list-tags li").mapNotNull { li ->
- val a = li.selectFirst("a") ?: return@mapNotNull null
- val title = a.selectFirst("h3.tag-name")?.text()?.trim() ?: return@mapNotNull null
- val key = a.attr("href").substringAfterLast("/")
- MangaTag( title = title, key = key, source = source )
- }
- }.toSet()
- }
-
- private fun parseChapterDate(date: String?): Long {
- if (date == null) return 0
- return try {
- val now = SimpleDateFormat("dd/MM/yyyy", Locale.US)
- now.parse(date)?.time ?: 0L
- } catch (e: Exception) {
- 0L
- }
- }
+ altTitle = altTitle,
+ state = state,
+ chapters = chapters,
+ description = doc.select("div.about").text(),
+ )
+ }
+
+ override suspend fun getPages(chapter: MangaChapter): List {
+ val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
+ return doc.select("div.chapter-content div.item-photo img").mapNotNull { img ->
+ val url = img.requireSrc()
+ MangaPage(
+ id = generateUid(url),
+ url = url,
+ preview = null,
+ source = source,
+ )
+ }
+ }
+
+ private suspend fun fetchAvailableTags(): Set {
+ val firstPage = webClient.httpGet("https://$domain/tim-the-loai").parseHtml()
+ val lastPage = firstPage.selectFirst("a[aria-label=Last]")
+ ?.attr("href")
+ ?.substringAfter("page=")
+ ?.toIntOrNull() ?: 1
+
+ return (1..lastPage).flatMap { page ->
+ val doc = if (page == 1) {
+ firstPage
+ } else {
+ webClient.httpGet("https://$domain/tim-the-loai?page=$page").parseHtml()
+ }
+
+ doc.select("ul.list-tags li").mapNotNull { li ->
+ val a = li.selectFirst("a") ?: return@mapNotNull null
+ val title = a.selectFirst("h3.tag-name")?.text()?.trim() ?: return@mapNotNull null
+ val key = a.attr("href").substringAfterLast("/")
+ MangaTag(title = title, key = key, source = source)
+ }
+ }.toSet()
+ }
+
+ private fun parseChapterDate(date: String?): Long {
+ return SimpleDateFormat("dd/MM/yyyy", Locale.US).tryParse(date)
+ }
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt
index d5e3ca6ea..8471d4994 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt
@@ -8,7 +8,7 @@ import kotlinx.coroutines.sync.withLock
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
@@ -21,7 +21,7 @@ private const val SEARCH_PAGE_SIZE = 10
@Broken
@MangaSourceParser("HENTAIVN", "HentaiVN", "vi", type = ContentType.HENTAI)
-internal class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.HENTAIVN) {
+internal class HentaiVNParser(context: MangaLoaderContext) : LegacyMangaParser(context, MangaParserSource.HENTAIVN) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("hentaihvn.tv")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVnBuzz.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVnBuzz.kt
index a141fb9a8..a230a9bbd 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVnBuzz.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVnBuzz.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.vi
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -11,7 +11,8 @@ import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("HENTAIVNBUZZ", "HentaiVn.buzz", "vi", type = ContentType.HENTAI)
-internal class HentaiVnBuzz(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.HENTAIVNBUZZ, 24) {
+internal class HentaiVnBuzz(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.HENTAIVNBUZZ, 24) {
override val configKeyDomain = ConfigKey.Domain("hentaivn.buzz")
@@ -177,25 +178,26 @@ internal class HentaiVnBuzz(context: MangaLoaderContext) : PagedMangaParser(cont
"Hoàn thành" -> MangaState.FINISHED
else -> null
},
- chapters = doc.select("div.story-detail__list-chapter--list ul.list-unstyled li a").mapIndexed { i, element ->
- val href = element.attrAsRelativeUrl("href")
- val name = element.text().removePrefix("- ")
- MangaChapter(
- id = generateUid(href),
- name = name,
- number = i + 1f,
- volume = 0,
- url = href,
- scanlator = null,
- uploadDate = 0,
- branch = null,
- source = source,
- )
- }
+ chapters = doc.select("div.story-detail__list-chapter--list ul.list-unstyled li a")
+ .mapIndexed { i, element ->
+ val href = element.attrAsRelativeUrl("href")
+ val name = element.text().removePrefix("- ")
+ MangaChapter(
+ id = generateUid(href),
+ name = name,
+ number = i + 1f,
+ volume = 0,
+ url = href,
+ scanlator = null,
+ uploadDate = 0,
+ branch = null,
+ source = source,
+ )
+ },
)
}
- override suspend fun getPages(chapter: MangaChapter): List {
+ override suspend fun getPages(chapter: MangaChapter): List {
val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
val imageUrls = doc.select("meta[property='og:image']").map { it.attr("content") }
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/KuroNeko.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/KuroNeko.kt
index bfa3899e3..ae66fc7d9 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/KuroNeko.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/KuroNeko.kt
@@ -1,16 +1,15 @@
package org.koitharu.kotatsu.parsers.site.vi
-import androidx.collection.arraySetOf
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("KURONEKO", "Việt Hentai", "vi", type = ContentType.HENTAI)
-internal class KuroNeko(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.KURONEKO, 60) {
+internal class KuroNeko(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.KURONEKO, 60) {
override val configKeyDomain = ConfigKey.Domain("vi-hentai.com")
@@ -218,14 +217,14 @@ internal class KuroNeko(context: MangaLoaderContext) : PagedMangaParser(context,
calendar.timeInMillis
}.getOrDefault(0L)
- private suspend fun availableTags(): Set {
- val doc = webClient.httpGet("https://$domain").parseHtml()
- return doc.select("ul.grid.grid-cols-2 a").mapToSet { a ->
- MangaTag(
- key = a.attr("href").removeSuffix('/').substringAfterLast('/'),
- title = a.text(),
- source = source,
- )
- }
- }
+ private suspend fun availableTags(): Set {
+ val doc = webClient.httpGet("https://$domain").parseHtml()
+ return doc.select("ul.grid.grid-cols-2 a").mapToSet { a ->
+ MangaTag(
+ key = a.attr("href").removeSuffix('/').substringAfterLast('/'),
+ title = a.text(),
+ source = source,
+ )
+ }
+ }
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt
index b4138b750..e8de25517 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt
@@ -1,16 +1,15 @@
package org.koitharu.kotatsu.parsers.site.vi
-import androidx.collection.arraySetOf
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("LXMANGA", "LXManga", "vi", type = ContentType.HENTAI)
-internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.LXMANGA, 60) {
+internal class LxManga(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.LXMANGA, 60) {
override val configKeyDomain = ConfigKey.Domain("lxmanga.cloud")
@@ -212,13 +211,13 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context,
private suspend fun availableTags(): Set {
val url = "https://$domain/the-loai"
val doc = webClient.httpGet(url).parseHtml()
-
+
return doc.select("nav.grid.grid-cols-3.md\\:grid-cols-8 button").map { button ->
val key = button.attr("wire:click").substringAfterLast(", '").substringBeforeLast("')")
MangaTag(
key = key,
title = button.select("span.text-ellipsis").text(),
- source = source
+ source = source,
)
}.toSet()
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/SayHentai.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/SayHentai.kt
index d59cd4147..04d64d5c0 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/SayHentai.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/SayHentai.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.vi
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -10,7 +10,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("SAYHENTAI", "SayHentai", "vi", ContentType.HENTAI)
-internal class SayHentai(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.SAYHENTAI, 20) {
+internal class SayHentai(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.SAYHENTAI, 20) {
override val configKeyDomain = ConfigKey.Domain("sayhentai.ink")
override fun onCreateConfig(keys: MutableCollection>) {
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenGG.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenGG.kt
index e54530855..08ae6705a 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenGG.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenGG.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.vi
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -10,7 +10,7 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("TRUYENGG", "TruyenGG", "vi")
-internal class TruyenGG(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.TRUYENGG, 42) {
+internal class TruyenGG(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.TRUYENGG, 42) {
override val configKeyDomain = ConfigKey.Domain("truyengg.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenHentaiVN.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenHentaiVN.kt
index a2b28fd71..73c114f7b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenHentaiVN.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenHentaiVN.kt
@@ -1,10 +1,9 @@
package org.koitharu.kotatsu.parsers.site.vi
-import androidx.collection.arraySetOf
import androidx.collection.ArrayMap
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -13,7 +12,8 @@ import java.util.*
import java.text.SimpleDateFormat
@MangaSourceParser("TRUYENHENTAIVN", "TruyenHentaiVN", "vi", type = ContentType.HENTAI)
-internal class TruyenHentaiVN(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.TRUYENHENTAIVN, 30) {
+internal class TruyenHentaiVN(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.TRUYENHENTAIVN, 30) {
private var cacheTags = suspendLazy(initializer = ::fetchTags)
override val configKeyDomain = ConfigKey.Domain("truyenhentaivn.live")
@@ -26,19 +26,19 @@ internal class TruyenHentaiVN(context: MangaLoaderContext) : PagedMangaParser(co
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isSearchSupported = true,
- isSearchWithFiltersSupported = false
+ isSearchWithFiltersSupported = false,
)
- override suspend fun getFilterOptions() = MangaListFilterOptions(availableTags = cacheTags.get().values.toSet())
+ override suspend fun getFilterOptions() = MangaListFilterOptions(availableTags = cacheTags.get().values.toSet())
- override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED)
+ override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List {
val url = buildString {
append("https://")
append(domain)
-
- when {
+
+ when {
!filter.tags.isNullOrEmpty() -> {
val tag = filter.tags.first()
append(tag.key)
@@ -47,7 +47,7 @@ internal class TruyenHentaiVN(context: MangaLoaderContext) : PagedMangaParser(co
append(page)
}
}
-
+
!filter.query.isNullOrEmpty() -> {
append("/tim-kiem-truyen/?q=")
append(filter.query.urlEncoded())
@@ -56,7 +56,7 @@ internal class TruyenHentaiVN(context: MangaLoaderContext) : PagedMangaParser(co
append(page)
}
}
-
+
else -> {
append("/chap-moi")
if (page > 1) {
@@ -87,61 +87,60 @@ internal class TruyenHentaiVN(context: MangaLoaderContext) : PagedMangaParser(co
state = null,
coverUrl = cover,
contentRating = ContentRating.ADULT,
- source = source
+ source = source,
)
}
}
override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
- val author = doc.selectFirst("div.author i")?.text()
- return manga.copy(
- authors = author?.let { setOf(it) } ?: emptySet(),
- tags = doc.select("div.genre.mb-3.mgen a").mapNotNull { a ->
- val key = a.attr("href").substringAfterLast("-")
- val title = a.text().trim()
- if (key.isNotEmpty() && title.isNotEmpty()) {
- MangaTag(
- key = key,
- title = title,
- source = source
- )
- } else null
- }.toSet(),
- description = doc.selectFirst("div.inner.mb-1.full")?.let { div ->
- div.select("p").joinToString("\n") { it.wholeText() }
- },
- coverUrl = doc.selectFirst("div.book img")?.src(),
- state = when(doc.selectFirst("div.tsinfo .imptdt i")?.text()?.trim()) {
- "Đã hoàn thành" -> MangaState.FINISHED
- "Đang tiến hành" -> MangaState.ONGOING
- else -> null
- },
- chapters = doc.select("div.chap-list .d-flex").mapChapters(reversed = true) { i, div ->
- val url = div.selectFirst("a")?.attrAsRelativeUrl("href") ?: ""
- val name = div.selectFirst("a .name")?.text() ?: ""
- val dateStr = div.selectFirst("a span:last-child")?.text()
-
- val uploadDate = dateStr?.let {
- try {
- SimpleDateFormat("dd-MM-yyyy", Locale.US).parse(it)?.time ?: 0L
- } catch (e: Exception) {
- 0L
- }
- } ?: 0L
-
- MangaChapter(
- id = generateUid(url),
- name = name,
- number = i + 1f,
- url = url,
- scanlator = null,
- uploadDate = uploadDate,
- branch = null,
- source = source,
- volume = 0
- )
- }
+ return manga.copy(
+ authors = setOfNotNull(doc.selectFirst("div.author i")?.textOrNull()),
+ tags = doc.select("div.genre.mb-3.mgen a").mapNotNullToSet { a ->
+ val key = a.attr("href").substringAfterLast("-")
+ val title = a.text().trim()
+ if (key.isNotEmpty() && title.isNotEmpty()) {
+ MangaTag(
+ key = key,
+ title = title,
+ source = source,
+ )
+ } else null
+ },
+ description = doc.selectFirst("div.inner.mb-1.full")?.let { div ->
+ div.select("p").joinToString("\n") { it.wholeText() }
+ },
+ coverUrl = doc.selectFirst("div.book img")?.src(),
+ state = when (doc.selectFirst("div.tsinfo .imptdt i")?.text()?.trim()) {
+ "Đã hoàn thành" -> MangaState.FINISHED
+ "Đang tiến hành" -> MangaState.ONGOING
+ else -> null
+ },
+ chapters = doc.select("div.chap-list .d-flex").mapChapters(reversed = true) { i, div ->
+ val url = div.selectFirst("a")?.attrAsRelativeUrl("href") ?: ""
+ val name = div.selectFirst("a .name")?.text() ?: ""
+ val dateStr = div.selectFirst("a span:last-child")?.text()
+
+ val uploadDate = dateStr?.let {
+ try {
+ SimpleDateFormat("dd-MM-yyyy", Locale.US).parse(it)?.time ?: 0L
+ } catch (e: Exception) {
+ 0L
+ }
+ } ?: 0L
+
+ MangaChapter(
+ id = generateUid(url),
+ name = name,
+ number = i + 1f,
+ url = url,
+ scanlator = null,
+ uploadDate = uploadDate,
+ branch = null,
+ source = source,
+ volume = 0,
+ )
+ },
)
}
@@ -154,7 +153,7 @@ internal class TruyenHentaiVN(context: MangaLoaderContext) : PagedMangaParser(co
id = generateUid(url),
url = url,
preview = null,
- source = source
+ source = source,
)
} else null
}
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenQQ.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenQQ.kt
index 1d9d3997f..081f82fd4 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenQQ.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenQQ.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.vi
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -10,7 +10,7 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("TRUYENQQ", "TruyenQQ", "vi")
-internal class TruyenQQ(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.TRUYENQQ, 42) {
+internal class TruyenQQ(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.TRUYENQQ, 42) {
override val configKeyDomain = ConfigKey.Domain("truyenqqto.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenTranh3Q.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenTranh3Q.kt
index 7b7bc4839..238040163 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenTranh3Q.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyenTranh3Q.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.vi
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -11,7 +11,7 @@ import java.util.*
@MangaSourceParser("TRUYENTRANH3Q", "TruyenTranh3Q", "vi")
internal class TruyenTranh3Q(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.TRUYENTRANH3Q, 42) {
+ LegacyPagedMangaParser(context, MangaParserSource.TRUYENTRANH3Q, 42) {
private val relativeTimePattern = Regex("(\\d+)\\s*(phút|giờ|ngày|tuần) trước")
private val absoluteTimePattern = Regex("(\\d{2}-\\d{2}-\\d{4})")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/VcomycsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/VcomycsParser.kt
index 6e9f97c16..828d5beb0 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/VcomycsParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/VcomycsParser.kt
@@ -6,7 +6,7 @@ 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.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.ContentType
@@ -24,7 +24,6 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrl
import org.koitharu.kotatsu.parsers.util.attrOrNull
import org.koitharu.kotatsu.parsers.util.attrOrThrow
-import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.generateUid
import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull
import org.koitharu.kotatsu.parsers.util.mapChapters
@@ -47,7 +46,8 @@ import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec
@MangaSourceParser("VCOMYCS", "Vcomycs", "vi", ContentType.MANGA)
-internal class VcomycsParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.VCOMYCS, 36) {
+internal class VcomycsParser(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.VCOMYCS, 36) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("vivicomi.org")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/YurinekoParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/YurinekoParser.kt
index 7044726b8..03cd9439a 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/YurinekoParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/YurinekoParser.kt
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.vi
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -15,7 +15,8 @@ import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("YURINEKO", "YuriNeko", "vi", ContentType.HENTAI)
-internal class YurinekoParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.YURINEKO, 20) {
+internal class YurinekoParser(context: MangaLoaderContext) :
+ LegacyPagedMangaParser(context, MangaParserSource.YURINEKO, 20) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("yurineko.site")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt
index f3c23294f..9d2e45cf0 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.vmp
import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -13,7 +13,7 @@ internal abstract class VmpParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 24,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/WpComicsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/WpComicsParser.kt
index a6ab955ff..c3f1f79ca 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/WpComicsParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/WpComicsParser.kt
@@ -10,7 +10,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.model.*
@@ -24,7 +24,7 @@ internal abstract class WpComicsParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 48,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ZeistMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ZeistMangaParser.kt
index b76584219..786ae437b 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ZeistMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zeistmanga/ZeistMangaParser.kt
@@ -8,7 +8,7 @@ import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -23,7 +23,7 @@ internal abstract class ZeistMangaParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 12,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zh/Baozimh.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zh/Baozimh.kt
index afb5c1c46..35a408f71 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zh/Baozimh.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zh/Baozimh.kt
@@ -5,7 +5,7 @@ import org.json.JSONArray
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.*
@@ -16,7 +16,7 @@ import java.util.*
@MangaSourceParser("BAOZIMH", "Baozimh", "zh")
internal class Baozimh(context: MangaLoaderContext) :
- PagedMangaParser(context, MangaParserSource.BAOZIMH, pageSize = 36) {
+ LegacyPagedMangaParser(context, MangaParserSource.BAOZIMH, pageSize = 36) {
override val configKeyDomain = ConfigKey.Domain("www.baozimh.com")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt
index 0f1e0b5ba..364959bf9 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt
@@ -4,7 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.PagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
@@ -17,7 +17,7 @@ internal abstract class ZMangaParser(
source: MangaParserSource,
domain: String,
pageSize: Int = 16,
-) : PagedMangaParser(context, source, pageSize) {
+) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain)
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/FaviconParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/FaviconParser.kt
index e41f9931f..242960483 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/FaviconParser.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/FaviconParser.kt
@@ -1,5 +1,7 @@
package org.koitharu.kotatsu.parsers.util
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.model.Favicon
import org.koitharu.kotatsu.parsers.model.Favicons
@@ -11,7 +13,7 @@ public class FaviconParser(
private val domain: String,
) {
- public suspend fun parseFavicons(): Favicons {
+ public suspend fun parseFavicons(): Favicons = withContext(Dispatchers.Default) {
val url = "https://$domain"
val doc = webClient.httpGet(url).parseHtml()
val result = HashSet()
@@ -31,7 +33,7 @@ public class FaviconParser(
if (result.isEmpty()) {
result.add(createFallback())
}
- return Favicons(result, url)
+ Favicons(result, url)
}
private fun parseLink(link: Element): Favicon? {
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/LinkResolver.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/LinkResolver.kt
index ba8c728c6..bd90acd02 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/LinkResolver.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/LinkResolver.kt
@@ -5,7 +5,7 @@ import kotlinx.coroutines.runInterruptible
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.koitharu.kotatsu.parsers.MangaLoaderContext
-import org.koitharu.kotatsu.parsers.MangaParser
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
@@ -14,19 +14,20 @@ public class LinkResolver internal constructor(
public val link: HttpUrl,
) {
- private val source = suspendLazy(initializer = ::resolveSource)
+ private val source = suspendLazy(Dispatchers.Default, ::resolveSource)
public suspend fun getSource(): MangaParserSource? = source.get()
public suspend fun getManga(): Manga? {
- val parser = context.newParserInstance(source.get() ?: return null)
+ val parser = context.newParserInstance(source.get() ?: return null) as? LegacyMangaParser
+ ?: return null
return parser.resolveLink(this, link) ?: resolveManga(parser)
}
private suspend fun resolveSource(): MangaParserSource? = runInterruptible(Dispatchers.Default) {
val domains = setOfNotNull(link.host, link.topPrivateDomain())
for (s in MangaParserSource.entries) {
- val parser = context.newParserInstance(s)
+ val parser = context.newParserInstance(s) as LegacyMangaParser
for (d in parser.configKeyDomain.presetValues) {
if (d in domains) {
return@runInterruptible s
@@ -37,7 +38,7 @@ public class LinkResolver internal constructor(
}
internal suspend fun resolveManga(
- parser: MangaParser,
+ parser: LegacyMangaParser,
url: String = link.toString().toRelativeUrl(link.host),
id: Long = parser.generateUid(url),
title: String = STUB_TITLE,
@@ -62,7 +63,7 @@ public class LinkResolver internal constructor(
),
)
- private suspend fun resolveBySeed(parser: MangaParser, s: Manga): Manga? {
+ private suspend fun resolveBySeed(parser: LegacyMangaParser, s: Manga): Manga? {
val seed = parser.getDetails(s)
if (!parser.filterCapabilities.isSearchSupported) {
return seed.takeUnless { it.chapters.isNullOrEmpty() }
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/MangaParserEnv.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/MangaParserEnv.kt
index 9a73a8370..8c22c7a0a 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/MangaParserEnv.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/MangaParserEnv.kt
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.util
import okhttp3.HttpUrl
import org.jsoup.nodes.Element
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.InternalParsersApi
import org.koitharu.kotatsu.parsers.MangaParser
@@ -81,17 +82,17 @@ private fun Set?.oneOrThrowIfMany(msg: String): T? = when {
else -> throw IllegalArgumentException(msg)
}
-public val MangaParser.domain: String
+public val LegacyMangaParser.domain: String
get() = config[configKeyDomain]
@InternalParsersApi
-public fun MangaParser.getDomain(subdomain: String): String {
+public fun LegacyMangaParser.getDomain(subdomain: String): String {
val domain = domain
return subdomain + "." + domain.removePrefix("www.")
}
@InternalParsersApi
-public fun MangaParser.urlBuilder(subdomain: String? = null): HttpUrl.Builder {
+public fun LegacyMangaParser.urlBuilder(subdomain: String? = null): HttpUrl.Builder {
return HttpUrl.Builder()
.scheme(SCHEME_HTTPS)
.host(if (subdomain == null) domain else "$subdomain.$domain")
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/RelatedMangaFinder.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/RelatedMangaFinder.kt
index 43bc9d3fb..03b76f9ca 100644
--- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/RelatedMangaFinder.kt
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/RelatedMangaFinder.kt
@@ -1,26 +1,27 @@
package org.koitharu.kotatsu.parsers.util
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.async
-import kotlinx.coroutines.awaitAll
-import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.*
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.model.Manga
-import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.SortOrder
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria
+import org.koitharu.kotatsu.parsers.model.search.SearchableField
public class RelatedMangaFinder(
private val parsers: Collection,
) {
- public suspend operator fun invoke(seed: Manga): List = coroutineScope {
- parsers.singleOrNull()?.let { parser ->
- findRelatedImpl(this, parser, seed)
- } ?: parsers.map { parser ->
- async {
+ public suspend operator fun invoke(seed: Manga): List = withContext(Dispatchers.Default) {
+ coroutineScope {
+ parsers.singleOrNull()?.let { parser ->
findRelatedImpl(this, parser, seed)
- }
- }.awaitAll().flatten()
+ } ?: parsers.map { parser ->
+ async {
+ findRelatedImpl(this, parser, seed)
+ }
+ }.awaitAll().flatten()
+ }
}
private suspend fun findRelatedImpl(scope: CoroutineScope, parser: MangaParser, seed: Manga): List {
@@ -34,7 +35,12 @@ public class RelatedMangaFinder(
}
val results = words.map { keyword ->
scope.async {
- val result = parser.getList(0, SortOrder.RELEVANCE, MangaListFilter(query = keyword))
+ val result = parser.getList(
+ MangaSearchQuery.Builder()
+ .order(SortOrder.RELEVANCE)
+ .criterion(QueryCriteria.Match(SearchableField.TITLE_NAME, keyword))
+ .build(),
+ )
result.filter { it.id != seed.id && it.containKeyword(keyword) }
}
}.awaitAll()
diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt
new file mode 100644
index 000000000..af7e79706
--- /dev/null
+++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt
@@ -0,0 +1,252 @@
+package org.koitharu.kotatsu.parsers.util
+
+import org.koitharu.kotatsu.parsers.model.MangaListFilter
+import org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities
+import org.koitharu.kotatsu.parsers.model.SortOrder
+import org.koitharu.kotatsu.parsers.model.YEAR_UNKNOWN
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchCapability
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
+
+/**
+ * Converts a [MangaListFilter] into a [MangaSearchQuery].
+ *
+ * This function iterates through the filter attributes in [MangaListFilter] and creates corresponding
+ * search criteria in a [MangaSearchQuery.Builder].
+ *
+ * @param filter The [MangaListFilter] to convert.
+ * @return A [MangaSearchQuery] constructed based on the given [filter].
+ */
+internal fun convertToMangaSearchQuery(offset: Int, sortOrder: SortOrder, filter: MangaListFilter): MangaSearchQuery {
+ return MangaSearchQuery.Builder().apply {
+ offset(offset)
+ order(sortOrder)
+ if (filter.tags.isNotEmpty()) criterion(Include(TAG, filter.tags))
+ if (filter.tagsExclude.isNotEmpty()) criterion(Exclude(TAG, filter.tagsExclude))
+ if (filter.states.isNotEmpty()) criterion(Include(STATE, filter.states))
+ if (filter.types.isNotEmpty()) criterion(Include(CONTENT_TYPE, filter.types))
+ if (filter.contentRating.isNotEmpty()) criterion(Include(CONTENT_RATING, filter.contentRating))
+ if (filter.demographics.isNotEmpty()) criterion(Include(DEMOGRAPHIC, filter.demographics))
+ if (validateYear(filter.yearFrom) || validateYear(filter.yearTo)) {
+ criterion(QueryCriteria.Range(PUBLICATION_YEAR, filter.yearFrom, filter.yearTo))
+ }
+ if (validateYear(filter.year)) {
+ criterion(Match(PUBLICATION_YEAR, filter.year))
+ }
+ filter.locale?.let {
+ criterion(Include(LANGUAGE, setOf(it)))
+ }
+ filter.originalLocale?.let {
+ criterion(Include(ORIGINAL_LANGUAGE, setOf(it)))
+ }
+ filter.query?.takeIf { it.isNotBlank() }?.let {
+ criterion(Match(TITLE_NAME, it))
+ }
+ }.build()
+}
+
+/**
+ * Converts a {@link MangaSearchQuery} into a {@link MangaListFilter}.
+ *
+ * This method iterates through the search criteria defined in the provided {@code searchQuery}
+ * and applies them to a {@link MangaListFilter.Builder}. The criteria are processed based on
+ * their types, such as inclusion, exclusion, equality checks, range filtering, and pattern matching.
+ *
+ *
+ * Supported criteria:
+ *
+ * - {@link QueryCriteria.Include} - Adds tags, states, content types, content ratings, demographics, and languages.
+ * - {@link QueryCriteria.Exclude} - Excludes tags.
+ * - {@link QueryCriteria.Equals} - Sets specific values like publication year.
+ * - {@link QueryCriteria.Between} - Sets a range of values like publication year range.
+ * - {@link QueryCriteria.Match} - Adds a search pattern for the title name.
+ *
+ *
+ *
+ * If an unsupported field is encountered, an {@link UnsupportedOperationException} is thrown.
+ *
+ *
+ * @param searchQuery The {@link MangaSearchQuery} to convert.
+ * @return A {@link MangaListFilter} constructed based on the given {@code searchQuery}.
+ * @throws UnsupportedOperationException If the search criteria contain unsupported fields.
+ */
+internal fun convertToMangaListFilter(searchQuery: MangaSearchQuery): MangaListFilter {
+ return MangaListFilter.Builder().apply {
+ for (criterion in searchQuery.criteria) {
+ when (criterion) {
+ is Include<*> -> handleInclude(this, criterion)
+ is Exclude<*> -> handleExclude(this, criterion)
+ is Range<*> -> handleBetween(this, criterion)
+ is Match<*> -> handleMatch(this, criterion)
+ }
+ }
+ }.build()
+}
+
+internal fun MangaSearchQueryCapabilities.toMangaListFilterCapabilities() = MangaListFilterCapabilities(
+ isMultipleTagsSupported = capabilities.any { x -> x.field == TAG && x.multiValue },
+ isTagsExclusionSupported = capabilities.any { x -> x.field == TAG && x.criteriaTypes.contains(Exclude::class) },
+ isSearchSupported = capabilities.any { x -> x.field == TITLE_NAME },
+ isSearchWithFiltersSupported = capabilities.any { x -> x.field == TITLE_NAME && x.otherCriteria },
+ isYearSupported = capabilities.any { x -> x.field == PUBLICATION_YEAR && x.criteriaTypes.contains(Match::class) },
+ isYearRangeSupported = capabilities.any { x -> x.field == PUBLICATION_YEAR && x.criteriaTypes.contains(Range::class) },
+ isOriginalLocaleSupported = capabilities.any { x -> x.field == ORIGINAL_LANGUAGE },
+ isAuthorSearchSupported = capabilities.any { x -> x.field == AUTHOR },
+)
+
+internal fun MangaListFilterCapabilities.toMangaSearchQueryCapabilities(): MangaSearchQueryCapabilities =
+ MangaSearchQueryCapabilities(
+ capabilities = setOfNotNull(
+ isMultipleTagsSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = TAG, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true,
+ )
+ },
+ isTagsExclusionSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = TAG, criteriaTypes = setOf(Exclude::class), multiValue = true, otherCriteria = true,
+ )
+ },
+ isSearchSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = TITLE_NAME,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = false,
+ )
+ },
+ isSearchWithFiltersSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = TITLE_NAME,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = true,
+ )
+ },
+ isYearSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = PUBLICATION_YEAR,
+ criteriaTypes = setOf(Match::class),
+ multiValue = false,
+ otherCriteria = true,
+ )
+ },
+ isYearRangeSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = PUBLICATION_YEAR,
+ criteriaTypes = setOf(Range::class),
+ multiValue = false,
+ otherCriteria = true,
+ )
+ },
+ isOriginalLocaleSupported.takeIf { it }?.let {
+ SearchCapability(
+ field = ORIGINAL_LANGUAGE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ )
+ },
+ SearchCapability(
+ field = LANGUAGE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = STATE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true,
+ ),
+ SearchCapability(
+ field = CONTENT_TYPE,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = CONTENT_RATING,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ SearchCapability(
+ field = DEMOGRAPHIC,
+ criteriaTypes = setOf(Include::class),
+ multiValue = true,
+ otherCriteria = true,
+ ),
+ ),
+ )
+
+private fun handleInclude(builder: MangaListFilter.Builder, criterion: Include<*>) {
+ val type = criterion.field.type
+
+ when (criterion.field) {
+ TAG -> builder.addTags(filterValues(criterion, type))
+ STATE -> builder.addStates(filterValues(criterion, type))
+ CONTENT_TYPE -> builder.addTypes(filterValues(criterion, type))
+ CONTENT_RATING -> builder.addContentRatings(filterValues(criterion, type))
+ DEMOGRAPHIC -> builder.addDemographics(filterValues(criterion, type))
+ LANGUAGE -> builder.locale(getFirstValue(criterion, type))
+ ORIGINAL_LANGUAGE -> builder.originalLocale(getFirstValue(criterion, type))
+ else -> throw IllegalArgumentException("Unsupported field for Include criterion: ${criterion.field}")
+ }
+}
+
+private fun handleExclude(builder: MangaListFilter.Builder, criterion: Exclude<*>) {
+ val type = criterion.field.type
+
+ when (criterion.field) {
+ TAG -> builder.excludeTags(filterValues(criterion, type))
+ else -> throw IllegalArgumentException("Unsupported field for Exclude criterion: ${criterion.field}")
+ }
+}
+
+private fun handleBetween(builder: MangaListFilter.Builder, criterion: Range<*>) {
+ val type = criterion.field.type
+
+ when (criterion.field) {
+ PUBLICATION_YEAR -> {
+ builder.yearFrom(getValue(criterion.from, type, YEAR_UNKNOWN))
+ builder.yearTo(getValue(criterion.to, type, YEAR_UNKNOWN))
+ }
+
+ else -> throw IllegalArgumentException("Unsupported field for Between criterion: ${criterion.field}")
+ }
+}
+
+private fun handleMatch(builder: MangaListFilter.Builder, criterion: Match<*>) {
+ val type = criterion.field.type
+
+ when (criterion.field) {
+ TITLE_NAME -> builder.query(getValue(criterion.value, type, ""))
+ PUBLICATION_YEAR -> builder.year(getValue(criterion.value, type, YEAR_UNKNOWN))
+ else -> throw IllegalArgumentException("Unsupported field for Match criterion: ${criterion.field}")
+ }
+}
+
+@Suppress("UNCHECKED_CAST")
+private fun filterValues(criterion: Include<*>, type: Class<*>): List {
+ return criterion.values.filter { type.isInstance(it) } as List
+}
+
+@Suppress("UNCHECKED_CAST")
+private fun filterValues(criterion: Exclude<*>, type: Class<*>): List {
+ return criterion.values.filter { type.isInstance(it) } as List
+}
+
+@Suppress("UNCHECKED_CAST")
+private fun getFirstValue(criterion: Include<*>, type: Class<*>): T? {
+ return criterion.values.firstOrNull { type.isInstance(it) } as? T
+}
+
+@Suppress("UNCHECKED_CAST")
+private fun getValue(value: Any?, type: Class<*>, default: T): T {
+ val isCompatibleIntType = (type == Int::class.java && Integer::class.isInstance(value))
+
+ return if (type.isInstance(value) || isCompatibleIntType) value as T else default
+}
+
+private fun validateYear(year: Int) = year != YEAR_UNKNOWN
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/CommonHeadersInterceptor.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/CommonHeadersInterceptor.kt
index f51e76cec..62a239f37 100644
--- a/src/test/kotlin/org/koitharu/kotatsu/parsers/CommonHeadersInterceptor.kt
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/CommonHeadersInterceptor.kt
@@ -5,8 +5,6 @@ import okhttp3.Request
import okhttp3.Response
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.MangaSource
-import org.koitharu.kotatsu.parsers.util.domain
-import org.koitharu.kotatsu.parsers.util.mergeWith
private const val HEADER_REFERER = "Referer"
@@ -20,11 +18,7 @@ internal class CommonHeadersInterceptor : Interceptor {
} else {
null
}
- val sourceHeaders = parser?.getRequestHeaders()
val headersBuilder = request.headers.newBuilder()
- if (sourceHeaders != null) {
- headersBuilder.mergeWith(sourceHeaders, replaceExisting = false)
- }
if (headersBuilder[HEADER_REFERER] == null && parser != null) {
headersBuilder[HEADER_REFERER] = "https://${parser.domain}/"
}
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
index 969500600..4e80719fb 100644
--- a/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/MangaParserTest.kt
@@ -5,8 +5,13 @@ import okhttp3.HttpUrl
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.params.ParameterizedTest
+import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
+import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.model.*
-import org.koitharu.kotatsu.parsers.util.domain
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.Include
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
import org.koitharu.kotatsu.parsers.util.medianOrNull
import org.koitharu.kotatsu.parsers.util.mimeType
import org.koitharu.kotatsu.test_util.*
@@ -22,7 +27,7 @@ internal class MangaParserTest {
@MangaSources
fun list(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val list = parser.getList(0, parser.defaultSortOrder, MangaListFilter.EMPTY)
+ val list = parser.getList(MangaSearchQuery.Builder().build())
checkMangaList(list, "list")
assert(list.all { it.source == source })
}
@@ -31,12 +36,13 @@ internal class MangaParserTest {
@MangaSources
fun pagination(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- if (parser is SinglePageMangaParser) {
+ if (parser is LegacySinglePageMangaParser) {
return@runTest
}
- val page1 = parser.getList(0, parser.defaultSortOrder, MangaListFilter.EMPTY)
- val page2 = parser.getList(page1.size, parser.defaultSortOrder, MangaListFilter.EMPTY)
- if (parser is PagedMangaParser) {
+ val page1 = parser.getList(MangaSearchQuery.EMPTY)
+ val page2 =
+ parser.getList(MangaSearchQuery.Builder().offset(page1.size).build())
+ if (parser is LegacyPagedMangaParser) {
assert(parser.pageSize >= page1.size) {
"Page size is ${page1.size} but ${parser.pageSize} expected"
}
@@ -52,18 +58,20 @@ internal class MangaParserTest {
@ParameterizedTest(name = "{index}|search|{0}")
@MangaSources
- fun search(source: MangaParserSource) = runTest(timeout = timeout) {
+ fun searchByTitleName(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val subject = parser.getList(
- offset = 0,
- order = SortOrder.POPULARITY,
- filter = MangaListFilter.EMPTY,
- ).minByOrNull {
+ val subject = parser.getList(MangaSearchQuery.EMPTY).minByOrNull {
it.title.length
} ?: error("No manga found")
+
val query = subject.title
check(query.isNotBlank()) { "Manga title '$query' is blank" }
- val list = parser.getList(0, SortOrder.RELEVANCE, MangaListFilter(query = query))
+ val list = parser.getList(
+ MangaSearchQuery.Builder()
+ .order(SortOrder.RELEVANCE)
+ .criterion(QueryCriteria.Match(TITLE_NAME, query))
+ .build(),
+ )
assert(list.isNotEmpty()) { "Empty search results by \"$query\"" }
assert(list.singleOrNull { it.url == subject.url && it.id == subject.id } != null) {
"Single subject '${subject.title} (${subject.publicUrl})' not found in search results"
@@ -92,9 +100,10 @@ internal class MangaParserTest {
val tag = tags.last()
val list = parser.getList(
- offset = 0,
- order = parser.defaultSortOrder,
- filter = MangaListFilter(tags = setOf(tag)),
+ MangaSearchQuery.Builder()
+ .offset(0)
+ .criterion(Include(TAG, setOf(tag)))
+ .build(),
)
checkMangaList(list, "${tag.title} (${tag.key})")
assert(list.all { it.source == source })
@@ -104,11 +113,16 @@ internal class MangaParserTest {
@MangaSources
fun tagsMultiple(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- if (!parser.filterCapabilities.isMultipleTagsSupported) return@runTest
+// if (!parser.filterCapabilities.isMultipleTagsSupported) return@runTest
val tags = parser.getFilterOptions().availableTags.shuffled().take(2).toSet()
- val filter = MangaListFilter(tags = tags)
- val list = parser.getList(0, parser.defaultSortOrder, filter)
+ val list = parser.getList(
+ MangaSearchQuery.Builder()
+ .offset(0)
+ .criterion(Include(TAG, tags))
+ .build(),
+ )
+
checkMangaList(list, "${tags.joinToString { it.title }} (${tags.joinToString { it.key }})")
assert(list.all { it.source == source })
}
@@ -121,12 +135,15 @@ internal class MangaParserTest {
if (locales.isEmpty()) {
return@runTest
}
- val filter = MangaListFilter(
- locale = locales.random(),
- originalLocale = locales.random(),
+ val locale = locales.random()
+ val list = parser.getList(
+ MangaSearchQuery.Builder()
+ .criterion(Include(LANGUAGE, setOf(locale)))
+ .criterion(Include(LANGUAGE, setOf(locale)))
+ .criterion(Include(ORIGINAL_LANGUAGE, setOf(locales.random())))
+ .build(),
)
- val list = parser.getList(offset = 0, order = parser.defaultSortOrder, filter)
- checkMangaList(list, filter.locale.toString())
+ checkMangaList(list, locale.toString())
assert(list.all { it.source == source })
}
@@ -135,8 +152,9 @@ internal class MangaParserTest {
@MangaSources
fun details(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val list = parser.getList(0, parser.defaultSortOrder, MangaListFilter.EMPTY)
- val manga = list[3]
+ val list = parser.getList(MangaSearchQuery.EMPTY)
+
+ val manga = list[0]
parser.getDetails(manga).apply {
assert(!chapters.isNullOrEmpty()) { "Chapters are null or empty" }
assert(publicUrl.isUrlAbsolute()) { "Manga public url is not absolute: '$publicUrl'" }
@@ -165,7 +183,7 @@ internal class MangaParserTest {
@MangaSources
fun pages(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val list = parser.getList(0, parser.defaultSortOrder, MangaListFilter.EMPTY)
+ val list = parser.getList(MangaSearchQuery.EMPTY)
val manga = list.first()
val chapter = parser.getDetails(manga).chapters?.firstOrNull() ?: error("Chapter is null at ${manga.publicUrl}")
val pages = parser.getPages(chapter)
@@ -220,7 +238,7 @@ internal class MangaParserTest {
@MangaSources
fun link(source: MangaParserSource) = runTest(timeout = timeout) {
val parser = context.newParserInstance(source)
- val manga = parser.getList(0, parser.defaultSortOrder, MangaListFilter.EMPTY).first()
+ val manga = parser.getList(MangaSearchQuery.Builder().build()).first()
val resolved = context.newLinkResolver(manga.publicUrl).getManga()
Assertions.assertNotNull(resolved)
resolved ?: return@runTest
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryTest.kt
new file mode 100644
index 000000000..b7be38327
--- /dev/null
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryTest.kt
@@ -0,0 +1,72 @@
+package org.koitharu.kotatsu.parsers.model.search
+
+import org.junit.jupiter.api.Assertions.assertDoesNotThrow
+import org.junit.jupiter.api.Assertions.assertThrows
+import org.junit.jupiter.api.Test
+import org.koitharu.kotatsu.parsers.model.MangaParserSource
+import org.koitharu.kotatsu.parsers.model.MangaState
+import org.koitharu.kotatsu.parsers.model.MangaTag
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
+import java.util.*
+
+class MangaSearchQueryCapabilitiesTest {
+
+ private val capabilities = MangaSearchQueryCapabilities(
+ capabilities = setOf(
+ SearchCapability(TITLE_NAME, setOf(Match::class), multiValue = false, otherCriteria = false),
+ SearchCapability(TAG, setOf(Include::class, Exclude::class), multiValue = true, otherCriteria = true),
+ SearchCapability(PUBLICATION_YEAR, setOf(Range::class), multiValue = false, otherCriteria = true),
+ SearchCapability(STATE, setOf(Include::class), multiValue = false, otherCriteria = true),
+ ),
+ )
+
+ @Test
+ fun validateValidSingleCriterionQuery() {
+ val query = MangaSearchQuery.Builder()
+ .criterion(Match(TITLE_NAME, "title"))
+ .build()
+
+ assertDoesNotThrow { capabilities.validate(query) }
+ }
+
+ @Test
+ fun validateUnsupportedFieldThrowsException() {
+ val query = MangaSearchQuery.Builder()
+ .criterion(Include(ORIGINAL_LANGUAGE, setOf(Locale.ENGLISH)))
+ .build()
+
+ assertThrows(IllegalArgumentException::class.java) { capabilities.validate(query) }
+ }
+
+ @Test
+ fun validateUnsupportedMultiValueThrowsException() {
+ val query = MangaSearchQuery.Builder()
+ .criterion(Include(STATE, setOf(MangaState.ONGOING, MangaState.FINISHED)))
+ .build()
+
+ assertThrows(IllegalArgumentException::class.java) { capabilities.validate(query) }
+ }
+
+ @Test
+ fun validateMultipleCriteriaWithOtherCriteriaAllowed() {
+ val query = MangaSearchQuery.Builder()
+ .criterion(Include(TAG, setOf(buildTag("tag1"), buildTag("tag2"))))
+ .criterion(Exclude(TAG, setOf(buildTag("tag3"))))
+ .build()
+
+ assertDoesNotThrow { capabilities.validate(query) }
+ }
+
+ @Test
+ fun validateMultipleCriteriaWithStrictCapabilityThrowsException() {
+ val query = MangaSearchQuery.Builder()
+ .criterion(Match(TITLE_NAME, "title"))
+ .criterion(Range(PUBLICATION_YEAR, 1990, 2000))
+ .build()
+
+ assertThrows(IllegalArgumentException::class.java) { capabilities.validate(query) }
+ }
+
+ private fun buildTag(name: String) = MangaTag(title = name, key = "${name}Key", source = MangaParserSource.DUMMY)
+}
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/util/IntentFilterGenerator.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/IntentFilterGenerator.kt
index 6b997776f..fd5b78df3 100644
--- a/src/test/kotlin/org/koitharu/kotatsu/parsers/util/IntentFilterGenerator.kt
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/IntentFilterGenerator.kt
@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.parsers.util
import org.junit.jupiter.api.Test
+import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaLoaderContextMock
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.newParser
@@ -25,7 +26,7 @@ class IntentFilterGenerator {
if (source == MangaParserSource.DUMMY) {
continue
}
- val parser = source.newParser(MangaLoaderContextMock)
+ val parser = source.newParser(MangaLoaderContextMock) as LegacyMangaParser
parser.configKeyDomain.presetValues.forEach { domain ->
writer.appendTab().append("")
}
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/util/ListFilterToSearchQueryConverterTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/ListFilterToSearchQueryConverterTest.kt
new file mode 100644
index 000000000..04530beca
--- /dev/null
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/ListFilterToSearchQueryConverterTest.kt
@@ -0,0 +1,77 @@
+package org.koitharu.kotatsu.parsers.util
+
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.koitharu.kotatsu.parsers.model.*
+import org.koitharu.kotatsu.parsers.model.ContentType.MANGA
+import org.koitharu.kotatsu.parsers.model.ContentType.MANHUA
+import org.koitharu.kotatsu.parsers.model.Demographic.SEINEN
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
+import java.util.*
+
+class ListFilterToSearchQueryConverterTest {
+
+ @Test
+ fun convertToMangaSearchQueryTest() {
+ val tags = setOf(buildMangaTag("tag1"), buildMangaTag("tag2"))
+ val excludedTags = setOf(buildMangaTag("exclude_tag"))
+ val states = setOf(MangaState.ONGOING)
+ val contentRatings = setOf(ContentRating.SAFE)
+ val contentTypes = setOf(MANGA, MANHUA)
+ val demographics = setOf(SEINEN)
+
+ val filter = MangaListFilter(
+ query = "title_name",
+ tags = tags,
+ tagsExclude = excludedTags,
+ locale = Locale.ENGLISH,
+ originalLocale = Locale.JAPANESE,
+ states = states,
+ contentRating = contentRatings,
+ types = contentTypes,
+ demographics = demographics,
+ year = 2020,
+ yearFrom = 1997,
+ yearTo = 2024,
+ )
+
+ val searchQuery = convertToMangaSearchQuery(0, SortOrder.NEWEST, filter)
+
+ val expectedQuery = MangaSearchQuery.Builder()
+ .offset(0)
+ .order(SortOrder.NEWEST)
+ .criterion(Match(TITLE_NAME, "title_name"))
+ .criterion(Include(TAG, tags))
+ .criterion(Exclude(TAG, excludedTags))
+ .criterion(Include(LANGUAGE, setOf(Locale.ENGLISH)))
+ .criterion(Include(ORIGINAL_LANGUAGE, setOf(Locale.JAPANESE)))
+ .criterion(Include(STATE, states))
+ .criterion(Include(CONTENT_RATING, contentRatings))
+ .criterion(Include(CONTENT_TYPE, contentTypes))
+ .criterion(Include(DEMOGRAPHIC, demographics))
+ .criterion(Range(PUBLICATION_YEAR, 1997, 2024))
+ .criterion(Match(PUBLICATION_YEAR, 2020))
+ .build()
+
+ assertEquals(expectedQuery, searchQuery)
+ }
+
+ @Test
+ fun convertToMangaSearchQueryWithEmptyFieldsTest() {
+ val filter = MangaListFilter()
+
+ val searchQuery = convertToMangaSearchQuery(0, SortOrder.NEWEST, filter)
+
+ assertEquals(MangaSearchQuery.Builder().offset(0).order(SortOrder.NEWEST).build(), searchQuery)
+ }
+
+ private fun buildMangaTag(name: String): MangaTag {
+ return MangaTag(
+ key = "${name}Key",
+ title = name,
+ source = MangaParserSource.DUMMY,
+ )
+ }
+}
diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryToListFilterConverterTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryToListFilterConverterTest.kt
new file mode 100644
index 000000000..ef2823783
--- /dev/null
+++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryToListFilterConverterTest.kt
@@ -0,0 +1,94 @@
+package org.koitharu.kotatsu.parsers.util
+
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.koitharu.kotatsu.parsers.model.ContentRating
+import org.koitharu.kotatsu.parsers.model.ContentType.MANGA
+import org.koitharu.kotatsu.parsers.model.ContentType.MANHUA
+import org.koitharu.kotatsu.parsers.model.Demographic.SEINEN
+import org.koitharu.kotatsu.parsers.model.MangaParserSource
+import org.koitharu.kotatsu.parsers.model.MangaState
+import org.koitharu.kotatsu.parsers.model.MangaTag
+import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
+import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
+import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
+import java.util.*
+
+class ConvertToMangaListFilterTest {
+
+ @Test
+ fun convertToMangaListFilterTest() {
+ val tags = setOf(buildMangaTag("tag1"), buildMangaTag("tag2"))
+ val excludedTags = setOf(buildMangaTag("exclude_tag"))
+ val states = setOf(MangaState.ONGOING)
+ val contentRatings = setOf(ContentRating.SAFE)
+ val contentTypes = setOf(MANGA, MANHUA)
+ val demographics = setOf(SEINEN)
+
+ val query = MangaSearchQuery.Builder()
+ .criterion(Match(TITLE_NAME, "title_name"))
+ .criterion(Include(TAG, tags))
+ .criterion(Exclude(TAG, excludedTags))
+ .criterion(Include(LANGUAGE, setOf(Locale.ENGLISH)))
+ .criterion(Include(ORIGINAL_LANGUAGE, setOf(Locale.JAPANESE)))
+ .criterion(Include(STATE, states))
+ .criterion(Include(CONTENT_RATING, contentRatings))
+ .criterion(Include(CONTENT_TYPE, contentTypes))
+ .criterion(Include(DEMOGRAPHIC, demographics))
+ .criterion(Range(PUBLICATION_YEAR, 1997, 2024))
+ .criterion(Match(PUBLICATION_YEAR, 2020))
+ .build()
+
+ val listFilter = convertToMangaListFilter(query)
+
+ assertEquals(listFilter.query, "title_name")
+ assertEquals(listFilter.tags, tags)
+ assertEquals(listFilter.tagsExclude, excludedTags)
+ assertEquals(listFilter.locale, Locale.ENGLISH)
+ assertEquals(listFilter.originalLocale, Locale.JAPANESE)
+ assertEquals(listFilter.states, states)
+ assertEquals(listFilter.contentRating, contentRatings)
+ assertEquals(listFilter.types, contentTypes)
+ assertEquals(listFilter.demographics, demographics)
+ assertEquals(listFilter.year, 2020)
+ assertEquals(listFilter.yearFrom, 1997)
+ assertEquals(listFilter.yearTo, 2024)
+ }
+
+ @Test
+ fun convertToMangaListFilterWithMultipleTagsIncludeTest() {
+ val tags1 = setOf(buildMangaTag("tag1"), buildMangaTag("tag2"))
+ val tags2 = setOf(buildMangaTag("tag3"), buildMangaTag("tag4"))
+
+ val query = MangaSearchQuery.Builder()
+ .criterion(Include(TAG, tags1))
+ .criterion(Include(TAG, tags2))
+ .build()
+
+ val listFilter = convertToMangaListFilter(query)
+
+ assertEquals(listFilter.tags, tags1 union tags2)
+ }
+
+ @Test
+ fun convertToMangaListFilterWithUnsupportedFieldTest() {
+ val query = MangaSearchQuery.Builder()
+ .criterion(Include(AUTHOR, setOf(buildMangaTag("author"))))
+ .build()
+
+ val exception = assertThrows {
+ convertToMangaListFilter(query)
+ }
+
+ assert(exception.message!!.contains("Unsupported field for Include criterion: AUTHOR"))
+ }
+
+ private fun buildMangaTag(name: String): MangaTag {
+ return MangaTag(
+ key = "${name}Key",
+ title = name,
+ source = MangaParserSource.DUMMY,
+ )
+ }
+}