Merge branch 'feature/search_query'

Koitharu 1 year ago
commit cdbb004ca1
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -70,6 +70,7 @@ class ParserProcessor(
package org.koitharu.kotatsu.parsers package org.koitharu.kotatsu.parsers
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.core.MangaParserWrapper
internal fun MangaParserSource.newParser(context: MangaLoaderContext): MangaParser = when (this) { internal fun MangaParserSource.newParser(context: MangaLoaderContext): MangaParser = when (this) {
@ -98,10 +99,11 @@ class ParserProcessor(
factoryWriter?.write( factoryWriter?.write(
""" """
MangaParserSource.DUMMY -> throw NotImplementedError("Manga parser ${'$'}name cannot be instantiated") MangaParserSource.DUMMY -> throw NotImplementedError("Manga parser ${'$'}name cannot be instantiated")
}.also { }.let {
require(it.source == this) { require(it.source == this) {
"Cannot instantiate manga parser: ${'$'}name mapped to ${'$'}{it.source}" "Cannot instantiate manga parser: ${'$'}name mapped to ${'$'}{it.source}"
} }
MangaParserWrapper(it)
} }
""".trimIndent(), """.trimIndent(),
) )

@ -1,116 +1,81 @@
package org.koitharu.kotatsu.parsers package org.koitharu.kotatsu.parsers
import androidx.annotation.CallSuper
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.Interceptor
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.OkHttpWebClient import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
import org.koitharu.kotatsu.parsers.network.WebClient import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
import org.koitharu.kotatsu.parsers.util.FaviconParser
import org.koitharu.kotatsu.parsers.util.LinkResolver import org.koitharu.kotatsu.parsers.util.LinkResolver
import org.koitharu.kotatsu.parsers.util.RelatedMangaFinder import org.koitharu.kotatsu.parsers.util.convertToMangaSearchQuery
import org.koitharu.kotatsu.parsers.util.domain import org.koitharu.kotatsu.parsers.util.toMangaListFilterCapabilities
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
import java.util.* import java.util.*
public abstract class MangaParser @InternalParsersApi constructor( public interface MangaParser : Interceptor {
@property:InternalParsersApi public val context: MangaLoaderContext,
public val source: MangaParserSource, public val source: MangaParserSource
) {
/** /**
* Supported [SortOrder] variants. Must not be empty. * Supported [SortOrder] variants. Must not be empty.
* *
* For better performance use [EnumSet] for more than one item. * For better performance use [EnumSet] for more than one item.
*/ */
public abstract val availableSortOrders: Set<SortOrder> public val availableSortOrders: Set<SortOrder>
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 open fun getRequestHeaders(): Headers = Headers.Builder() public val searchQueryCapabilities: MangaSearchQueryCapabilities
.add("User-Agent", config[userAgentKey])
.build()
/** public val config: MangaSourceConfig
* 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 }
}
@JvmField public val domain: String
protected val webClient: WebClient = OkHttpWebClient(context.httpClient, source)
/** public suspend fun getList(query: MangaSearchQuery): List<Manga>
* 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<Manga>
/** /**
* Parse details for [Manga]: chapters list, description, large cover, etc. * Parse details for [Manga]: chapters list, description, large cover, etc.
* Must return the same manga, may change any fields excepts id, url and source * Must return the same manga, may change any fields excepts id, url and source
* @see Manga.copy * @see Manga.copy
*/ */
public abstract suspend fun getDetails(manga: Manga): Manga public suspend fun getDetails(manga: Manga): Manga
/** /**
* Parse pages list for specified chapter. * Parse pages list for specified chapter.
* @see MangaPage for details * @see MangaPage for details
*/ */
public abstract suspend fun getPages(chapter: MangaChapter): List<MangaPage> public suspend fun getPages(chapter: MangaChapter): List<MangaPage>
/** /**
* Fetch direct link to the page image. * 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 * Parse favicons from the main page of the source`s website
*/ */
public open suspend fun getFavicons(): Favicons { public suspend fun getFavicons(): Favicons
return FaviconParser(webClient, domain).parseFavicons()
}
@CallSuper public fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>)
public open fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
keys.add(configKeyDomain)
}
public open suspend fun getRelatedManga(seed: Manga): List<Manga> { public suspend fun getRelatedManga(seed: Manga): List<Manga>
return RelatedMangaFinder(listOf(this)).invoke(seed)
} public fun getRequestHeaders(): Headers
/** /**
* Return [Manga] object by web link to it * Return [Manga] object by web link to it
* @see [Manga.publicUrl] * @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<Manga> {
return getList(convertToMangaSearchQuery(offset, order, filter))
}
@Deprecated("Please check searchQueryCapabilities")
public val filterCapabilities: MangaListFilterCapabilities
get() = searchQueryCapabilities.toMangaListFilterCapabilities()
} }

@ -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<ConfigKey<*>>) {
keys.add(configKeyDomain)
}
public override suspend fun getRelatedManga(seed: Manga): List<Manga> {
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())
}

@ -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<Manga> = getList(
offset = query.offset,
order = query.order ?: defaultSortOrder,
filter = convertToMangaListFilter(query),
)
abstract override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List<Manga>
/**
* 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<ConfigKey<*>>) {
keys.add(configKeyDomain)
}
public override suspend fun getRelatedManga(seed: Manga): List<Manga> {
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())
}

@ -1,6 +1,8 @@
package org.koitharu.kotatsu.parsers package org.koitharu.kotatsu.parsers.core
import androidx.annotation.VisibleForTesting 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.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaParserSource 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 import org.koitharu.kotatsu.parsers.util.Paginator
@InternalParsersApi @InternalParsersApi
public abstract class PagedMangaParser( public abstract class LegacyPagedMangaParser(
context: MangaLoaderContext, context: MangaLoaderContext,
source: MangaParserSource, source: MangaParserSource,
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) @JvmField public val pageSize: Int, @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) @JvmField public val pageSize: Int,
searchPageSize: Int = pageSize, searchPageSize: Int = pageSize,
) : MangaParser(context, source) { ) : LegacyMangaParser(context, source) {
@JvmField @JvmField
protected val paginator: Paginator = Paginator(pageSize) protected val paginator: Paginator = Paginator(pageSize)

@ -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.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
@InternalParsersApi @InternalParsersApi
public abstract class SinglePageMangaParser( public abstract class LegacySinglePageMangaParser(
context: MangaLoaderContext, context: MangaLoaderContext,
source: MangaParserSource, source: MangaParserSource,
) : MangaParser(context, source) { ) : LegacyMangaParser(context, source) {
final override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { final override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
if (offset > 0) { if (offset > 0) {

@ -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<Manga> = 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<MangaPage> = 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<Manga> = 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
}
}

@ -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<Manga> {
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<Manga>
private suspend fun searchManga(
paginator: Paginator,
query: MangaSearchQuery,
): List<Manga> {
val offset: Int = query.offset
val page = paginator.getPage(offset)
val list = getListPage(query, page)
paginator.onListReceived(offset, page, list.size)
return list
}
}

@ -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<Manga> {
if (query.offset > 0) {
return emptyList()
}
return getSinglePageList(query)
}
public abstract suspend fun getSinglePageList(searchQuery: MangaSearchQuery): List<Manga>
}

@ -2,10 +2,9 @@ package org.koitharu.kotatsu.parsers.model
import androidx.collection.ArrayMap import androidx.collection.ArrayMap
import org.koitharu.kotatsu.parsers.util.findById import org.koitharu.kotatsu.parsers.util.findById
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.nullIfEmpty import org.koitharu.kotatsu.parsers.util.nullIfEmpty
public data class Manga constructor( public data class Manga(
/** /**
* Unique identifier for manga * Unique identifier for manga
*/ */
@ -71,6 +70,91 @@ public data class Manga constructor(
*/ */
@JvmField public val source: MangaSource, @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<MangaTag>,
/**
* 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<MangaChapter>? = 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 * Author of the manga, may be null
*/ */

@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.model
import java.util.* import java.util.*
@Deprecated("Please check new searchManga method and MangaSearchQuery class")
public data class MangaListFilter( public data class MangaListFilter(
@JvmField val query: String? = null, @JvmField val query: String? = null,
@JvmField val tags: Set<MangaTag> = emptySet(), @JvmField val tags: Set<MangaTag> = emptySet(),
@ -42,4 +43,47 @@ public data class MangaListFilter(
@JvmStatic @JvmStatic
public val EMPTY: MangaListFilter = MangaListFilter() public val EMPTY: MangaListFilter = MangaListFilter()
} }
internal class Builder {
private var query: String? = null
private val tags: MutableSet<MangaTag> = mutableSetOf()
private val tagsExclude: MutableSet<MangaTag> = mutableSetOf()
private var locale: Locale? = null
private var originalLocale: Locale? = null
private val states: MutableSet<MangaState> = mutableSetOf()
private val contentRating: MutableSet<ContentRating> = mutableSetOf()
private val types: MutableSet<ContentType> = mutableSetOf()
private val demographics: MutableSet<Demographic> = 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<MangaTag>): Builder = apply { this.tags.addAll(tags) }
fun excludeTag(tag: MangaTag): Builder = apply { tagsExclude.add(tag) }
fun excludeTags(tags: Collection<MangaTag>): 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<MangaState>): Builder = apply { this.states.addAll(states) }
fun addContentRating(rating: ContentRating): Builder = apply { contentRating.add(rating) }
fun addContentRatings(ratings: Collection<ContentRating>): Builder =
apply { this.contentRating.addAll(ratings) }
fun addType(type: ContentType): Builder = apply { types.add(type) }
fun addTypes(types: Collection<ContentType>): Builder = apply { this.types.addAll(types) }
fun addDemographic(demographic: Demographic): Builder = apply { demographics.add(demographic) }
fun addDemographics(demographics: Collection<Demographic>): 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,
)
}
} }

@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.model
import org.koitharu.kotatsu.parsers.InternalParsersApi import org.koitharu.kotatsu.parsers.InternalParsersApi
@Deprecated("Please check new MangaSearchQueryCapabilities class")
public data class MangaListFilterCapabilities @InternalParsersApi constructor( public data class MangaListFilterCapabilities @InternalParsersApi constructor(
/** /**

@ -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<QueryCriteria<*>>,
@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<QueryCriteria<*>>()
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<QueryCriteria<*>>): Set<QueryCriteria<*>> {
val uniqueCriteria =
ArrayMap<Pair<SearchableField, Class<out QueryCriteria<*>>>, 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)
}
}

@ -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<SearchCapability>,
) {
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
}
}
}
}
}

@ -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<T> {
public val field: SearchableField
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
public data class Include<T : Any>(
public override val field: SearchableField,
@JvmField public val values: Set<T>,
) : QueryCriteria<T> {
init {
check(values.all { x -> field.type.isInstance(x) })
}
}
public data class Exclude<T : Any>(
public override val field: SearchableField,
@JvmField public val values: Set<T>,
) : QueryCriteria<T> {
init {
check(values.all { x -> field.type.isInstance(x) })
}
}
public data class Range<T : Comparable<T>>(
public override val field: SearchableField,
@JvmField public val from: T,
@JvmField public val to: T,
) : QueryCriteria<T> {
init {
check(field.type.isInstance(from))
check(field.type.isInstance(to))
}
}
public data class Match<T : Any>(
public override val field: SearchableField,
@JvmField public val value: T,
) : QueryCriteria<T> {
init {
check(field.type.isInstance(value))
}
}
}

@ -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<KClass<out QueryCriteria<*>>>,
@JvmField public val multiValue: Boolean,
@JvmField public val otherCriteria: Boolean,
)

@ -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);
}

@ -7,7 +7,7 @@ import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
@ -20,7 +20,7 @@ import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
@MangaSourceParser("BATOTO", "Bato.To") @MangaSourceParser("BATOTO", "Bato.To")
internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser( internal class BatoToParser(context: MangaLoaderContext) : LegacyPagedMangaParser(
context = context, context = context,
source = MangaParserSource.BATOTO, source = MangaParserSource.BATOTO,
pageSize = 60, pageSize = 60,

@ -7,7 +7,7 @@ import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -20,7 +20,7 @@ private const val CHAPTERS_LIMIT = 99999
@MangaSourceParser("COMICK_FUN", "ComicK") @MangaSourceParser("COMICK_FUN", "ComicK")
internal class ComickFunParser(context: MangaLoaderContext) : internal class ComickFunParser(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.COMICK_FUN, 20) { LegacyPagedMangaParser(context, MangaParserSource.COMICK_FUN, 20) {
override val configKeyDomain = ConfigKey.Domain("comick.io") override val configKeyDomain = ConfigKey.Domain("comick.io")

@ -13,7 +13,7 @@ import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.exception.TooManyRequestExceptions 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) @MangaSourceParser("EXHENTAI", "ExHentai", type = ContentType.HENTAI)
internal class ExHentaiParser( internal class ExHentaiParser(
context: MangaLoaderContext, context: MangaLoaderContext,
) : PagedMangaParser(context, MangaParserSource.EXHENTAI, pageSize = 25), MangaParserAuthProvider, Interceptor { ) : LegacyPagedMangaParser(context, MangaParserSource.EXHENTAI, pageSize = 25), MangaParserAuthProvider, Interceptor {
override val availableSortOrders: Set<SortOrder> = setOf(SortOrder.NEWEST) override val availableSortOrders: Set<SortOrder> = setOf(SortOrder.NEWEST)

@ -11,7 +11,7 @@ import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
@ -28,7 +28,7 @@ import kotlin.math.min
@OptIn(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
@MangaSourceParser("HITOMILA", "Hitomi.La", type = ContentType.HENTAI) @MangaSourceParser("HITOMILA", "Hitomi.La", type = ContentType.HENTAI)
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 val configKeyDomain = ConfigKey.Domain("hitomi.la")
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
@ -516,14 +516,14 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
title = doc.selectFirstOrThrow("h1").text(), title = doc.selectFirstOrThrow("h1").text(),
url = id.toString(), url = id.toString(),
coverUrl = coverUrl =
"https:" + "https:" +
doc.selectFirstOrThrow("picture > source") doc.selectFirstOrThrow("picture > source")
.attr("data-srcset") .attr("data-srcset")
.substringBefore(" "), .substringBefore(" "),
publicUrl = publicUrl =
doc.selectFirstOrThrow("h1 > a") doc.selectFirstOrThrow("h1 > a")
.attrAsRelativeUrl("href") .attrAsRelativeUrl("href")
.toAbsoluteUrl(domain), .toAbsoluteUrl(domain),
authors = emptySet(), authors = emptySet(),
tags = emptySet(), tags = emptySet(),
contentRating = ContentRating.ADULT, contentRating = ContentRating.ADULT,
@ -550,34 +550,35 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
return manga.copy( return manga.copy(
title = json.getString("title"), title = json.getString("title"),
largeCoverUrl = largeCoverUrl =
json.getJSONArray("files").getJSONObject(0).let { json.getJSONArray("files").getJSONObject(0).let {
val hash = it.getString("hash") val hash = it.getString("hash")
val commonId = commonImageId() val commonId = commonImageId()
val imageId = imageIdFromHash(hash) val imageId = imageIdFromHash(hash)
val subDomain = 'a' + subdomainOffset(imageId) val subDomain = 'a' + subdomainOffset(imageId)
"https://${getDomain("${subDomain}a")}/webp/$commonId$imageId/$hash.webp" "https://${getDomain("${subDomain}a")}/webp/$commonId$imageId/$hash.webp"
}, },
authors = author?.let { setOf(it) } ?: emptySet(), authors = author?.let { setOf(it) } ?: emptySet(),
publicUrl = json.getString("galleryurl").toAbsoluteUrl(domain), publicUrl = json.getString("galleryurl").toAbsoluteUrl(domain),
tags = tags =
buildSet { buildSet
json.optJSONArray("characters") {
?.mapToTags("character") json.optJSONArray("characters")
?.let(::addAll) ?.mapToTags("character")
json.optJSONArray("tags") ?.let(::addAll)
?.mapToTags("tag") json.optJSONArray("tags")
?.let(::addAll) ?.mapToTags("tag")
json.optJSONArray("artists") ?.let(::addAll)
?.mapToTags("artist") json.optJSONArray("artists")
?.let(::addAll) ?.mapToTags("artist")
json.optJSONArray("parodys") ?.let(::addAll)
?.mapToTags("parody") json.optJSONArray("parodys")
?.let(::addAll) ?.mapToTags("parody")
json.optJSONArray("groups") ?.let(::addAll)
?.mapToTags("group") json.optJSONArray("groups")
?.let(::addAll) ?.mapToTags("group")
}, ?.let(::addAll)
},
chapters = listOf( chapters = listOf(
MangaChapter( MangaChapter(
id = generateUid(manga.url), id = generateUid(manga.url),
@ -601,15 +602,15 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
mapJSON { mapJSON {
MangaTag( MangaTag(
title = title =
it.getString(key).toCamelCase().let { title -> it.getString(key).toCamelCase().let { title ->
if (it.getStringOrNull("female")?.toIntOrNull() == 1) { if (it.getStringOrNull("female")?.toIntOrNull() == 1) {
"$title" "$title"
} else if (it.getStringOrNull("male")?.toIntOrNull() == 1) { } else if (it.getStringOrNull("male")?.toIntOrNull() == 1) {
"$title" "$title"
} else { } else {
title title
} }
}, },
key = it.getString("url").tagUrlToTag(), key = it.getString("url").tagUrlToTag(),
source = source, source = source,
).let(tags::add) ).let(tags::add)
@ -666,7 +667,7 @@ internal class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context
} }
} }
// / ---> // / --->
private var scriptLastRetrieval: Long = -1L private var scriptLastRetrieval: Long = -1L
private val mutex = Mutex() private val mutex = Mutex()

@ -7,7 +7,7 @@ import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -15,7 +15,7 @@ import java.util.*
@MangaSourceParser("IMHENTAI", "ImHentai", type = ContentType.HENTAI) @MangaSourceParser("IMHENTAI", "ImHentai", type = ContentType.HENTAI)
internal class ImHentai(context: MangaLoaderContext) : internal class ImHentai(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.IMHENTAI, pageSize = 20) { LegacyPagedMangaParser(context, MangaParserSource.IMHENTAI, pageSize = 20) {
override val availableSortOrders: Set<SortOrder> = override val availableSortOrders: Set<SortOrder> =
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.RATING) EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.RATING)

@ -6,7 +6,7 @@ import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.NotFoundException import org.koitharu.kotatsu.parsers.exception.NotFoundException
@ -21,7 +21,7 @@ import javax.crypto.spec.SecretKeySpec
internal abstract class LineWebtoonsParser( internal abstract class LineWebtoonsParser(
context: MangaLoaderContext, context: MangaLoaderContext,
source: MangaParserSource, source: MangaParserSource,
) : MangaParser(context, source) { ) : LegacyMangaParser(context, source) {
override val filterCapabilities: MangaListFilterCapabilities override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities( get() = MangaListFilterCapabilities(

@ -7,13 +7,18 @@ import kotlinx.coroutines.coroutineScope
import okhttp3.HttpUrl import okhttp3.HttpUrl
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.core.AbstractMangaParser
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.* 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.*
import org.koitharu.kotatsu.parsers.util.json.* import org.koitharu.kotatsu.parsers.util.json.*
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -29,7 +34,7 @@ private const val SERVER_DATA = "data"
private const val SERVER_DATA_SAVER = "data-saver" private const val SERVER_DATA_SAVER = "data-saver"
@MangaSourceParser("MANGADEX", "MangaDex") @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") override val configKeyDomain = ConfigKey.Domain("mangadex.org")
@ -63,15 +68,68 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
SortOrder.RELEVANCE, SortOrder.RELEVANCE,
) )
override val filterCapabilities: MangaListFilterCapabilities override val searchQueryCapabilities: MangaSearchQueryCapabilities
get() = MangaListFilterCapabilities( get() = MangaSearchQueryCapabilities(
isMultipleTagsSupported = true, SearchCapability(
isTagsExclusionSupported = true, field = TAG,
isSearchSupported = true, criteriaTypes = setOf(Include::class, Exclude::class),
isSearchWithFiltersSupported = true, multiValue = true,
isYearSupported = true, otherCriteria = true,
isOriginalLocaleSupported = true, ),
isAuthorSearchSupported = 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 { 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<Manga> { private fun SearchableField.toParamName(): String = when (this) {
val domain = domain TITLE_NAME -> "title"
val url = buildString { TAG -> "includedTags[]"
append("https://api.") AUTHOR -> "authors[]"
append(domain) STATE -> "status[]"
append("/manga?limit=") CONTENT_TYPE -> "contentType[]"
append(PAGE_SIZE) CONTENT_RATING -> "contentRating[]"
append("&offset=") DEMOGRAPHIC -> "publicationDemographic[]"
append(offset) ORIGINAL_LANGUAGE -> "originalLanguage[]"
append("&includes[]=cover_art&includes[]=author&includes[]=artist") LANGUAGE -> "availableTranslatedLanguage[]"
PUBLICATION_YEAR -> "year"
}
filter.query?.let { private fun Any?.toQueryParam(): String = when (this) {
append("&title=") is String -> urlEncoded()
append(filter.query.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 { is ContentRating -> when (this) {
append("&includedTags[]=") ContentRating.SAFE -> "safe"
append(it.key) // quick fix for double value
} ContentRating.SUGGESTIVE -> "suggestive&contentRating[]=erotica"
ContentRating.ADULT -> "pornographic"
}
filter.tagsExclude.forEach { is Demographic -> when (this) {
append("&excludedTags[]=") Demographic.SHOUNEN -> "shounen"
append(it.key) Demographic.SHOUJO -> "shoujo"
} Demographic.SEINEN -> "seinen"
Demographic.JOSEI -> "josei"
Demographic.NONE -> "none"
else -> ""
}
if (filter.contentRating.isNotEmpty()) { is SortOrder -> when (this) {
filter.contentRating.forEach { SortOrder.UPDATED -> "[latestUploadedChapter]=desc"
when (it) { SortOrder.UPDATED_ASC -> "[latestUploadedChapter]=asc"
ContentRating.SAFE -> append("&contentRating[]=safe") SortOrder.RATING -> "[rating]=desc"
ContentRating.SUGGESTIVE -> append("&contentRating[]=suggestive&contentRating[]=erotica") SortOrder.RATING_ASC -> "[rating]=asc"
ContentRating.ADULT -> append("&contentRating[]=pornographic") 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 -> this.toString().urlEncoded()
} }
} else {
append("&contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic")
}
if (!filter.author.isNullOrEmpty()) { private fun StringBuilder.appendCriterion(field: SearchableField, value: Any?, paramName: String? = null) {
append("&authorOrArtist=").append(getAuthorId(filter.author)) val param = paramName ?: field.toParamName()
} if (param.isNotBlank()) {
append("&$param=")
append(value.toQueryParam())
}
}
append("&order") override suspend fun getList(query: MangaSearchQuery): List<Manga> {
append( val url = buildString {
when (order) { append("https://api.$domain/manga?limit=$PAGE_SIZE&offset=${query.offset}")
SortOrder.UPDATED -> "[latestUploadedChapter]=desc" .append("&includes[]=cover_art&includes[]=author&includes[]=artist&includedTagsMode=AND&excludedTagsMode=OR")
SortOrder.UPDATED_ASC -> "[latestUploadedChapter]=asc"
SortOrder.RATING -> "[rating]=desc" var hasContentRating = false
SortOrder.RATING_ASC -> "[rating]=asc"
SortOrder.ALPHABETICAL -> "[title]=asc" query.criteria.forEach { criterion ->
SortOrder.ALPHABETICAL_DESC -> "[title]=desc" when (criterion) {
SortOrder.NEWEST -> "[year]=desc" is Include<*> -> {
SortOrder.NEWEST_ASC -> "[year]=asc" if (criterion.field == CONTENT_RATING) {
SortOrder.POPULARITY -> "[followedCount]=desc" hasContentRating = true
SortOrder.POPULARITY_ASC -> "[followedCount]=asc" }
SortOrder.ADDED -> "[createdAt]=desc" criterion.values.forEach { appendCriterion(criterion.field, it) }
SortOrder.ADDED_ASC -> "[createdAt]=asc" }
SortOrder.RELEVANCE -> "&order[relevance]=desc"
else -> "[latestUploadedChapter]=desc"
},
)
filter.states.forEach { is Exclude<*> -> {
append("&status[]=") criterion.values.forEach { appendCriterion(criterion.field, it, "excludedTags[]") }
when (it) { }
MangaState.ONGOING -> append("ongoing")
MangaState.FINISHED -> append("completed")
MangaState.ABANDONED -> append("cancelled")
MangaState.PAUSED -> append("hiatus")
else -> append("")
}
}
filter.demographics.forEach { is Match<*> -> {
append("&publicationDemographic[]=") appendCriterion(criterion.field, criterion.value)
append( }
when (it) {
Demographic.SHOUNEN -> "shounen"
Demographic.SHOUJO -> "shoujo"
Demographic.SEINEN -> "seinen"
Demographic.JOSEI -> "josei"
Demographic.NONE -> "none"
else -> ""
},
)
}
filter.locale?.let { else -> {
append("&availableTranslatedLanguage[]=") // Not supported
if (it.language == "in") { }
append("id")
} else {
append(it.language)
} }
} }
filter.originalLocale?.let { // If contentRating is not provided, add default values
append("&originalLanguage[]=") if (!hasContentRating) {
if (it.language == "in") { append("&contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic")
append("id")
} else {
append(it.language)
}
} }
if (filter.year != 0) { append("&order")
append("&year=") append((query.order ?: defaultSortOrder).toQueryParam())
append(filter.year)
}
} }
val json = webClient.httpGet(url).parseJson().getJSONArray("data") val json = webClient.httpGet(url).parseJson().getJSONArray("data")
return json.mapJSON { jo -> jo.fetchManga(null) } return json.mapJSON { jo -> jo.fetchManga(null) }
} }
@ -282,14 +337,17 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
val attrs = getJSONObject("attributes") val attrs = getJSONObject("attributes")
val relations = getJSONArray("relationships").associateByKey("type") val relations = getJSONArray("relationships").associateByKey("type")
val cover = relations["cover_art"] val cover = relations["cover_art"]
?.firstOrNull()
?.getJSONObject("attributes") ?.getJSONObject("attributes")
?.getString("fileName") ?.getString("fileName")
?.let { ?.let {
"https://uploads.$domain/covers/$id/$it" "https://uploads.$domain/covers/$id/$it"
} }
val author = (relations["author"] ?: relations["artist"]) val authors: Set<String> = (relations["author"] ?: relations["artist"])
?.getJSONObject("attributes") ?.mapNotNullToSet {
?.getStringOrNull("name") it.getJSONObject("attributes")?.getStringOrNull("name")
}.orEmpty()
return Manga( return Manga(
id = generateUid(id), id = generateUid(id),
title = requireNotNull(attrs.getJSONObject("title").selectByLocale()) { title = requireNotNull(attrs.getJSONObject("title").selectByLocale()) {
@ -325,7 +383,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
"cancelled" -> MangaState.ABANDONED "cancelled" -> MangaState.ABANDONED
else -> null else -> null
}, },
authors = author?.let { setOf(it) } ?: emptySet(), authors = authors,
chapters = chapters, chapters = chapters,
source = source, 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<JSONObject>): List<MangaChapter> { private fun mapChapters(list: List<JSONObject>): List<MangaChapter> {
// 2022-01-02T00:27:11+00:00 // 2022-01-02T00:27:11+00:00
val dateFormat = SimpleDateFormat( val dateFormat = SimpleDateFormat(
@ -444,7 +486,8 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
val locale = attrs.getStringOrNull("translatedLanguage")?.let { Locale.forLanguageTag(it) } val locale = attrs.getStringOrNull("translatedLanguage")?.let { Locale.forLanguageTag(it) }
val lc = locale?.getDisplayName(locale)?.toTitleCase(locale) val lc = locale?.getDisplayName(locale)?.toTitleCase(locale)
val relations = jo.getJSONArray("relationships").associateByKey("type") 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 branch = (list.indices).firstNotNullOf { i ->
val b = if (i == 0) lc else "$lc ($i)" val b = if (i == 0) lc else "$lc ($i)"
if (branchedChapters[b]?.get(volume to number) == null) b else null 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() return chaptersBuilder.toList()
} }
private fun JSONArray.associateByKey(key: String): Map<String, JSONObject> { private fun JSONArray.associateByKey(key: String): Map<String, List<JSONObject>> {
val destination = LinkedHashMap<String, JSONObject>(length()) val destination = LinkedHashMap<String, MutableList<JSONObject>>(length())
repeat(length()) { i -> repeat(length()) { i ->
val item = getJSONObject(i) val item = getJSONObject(i)
val keyValue = item.getString(key) val keyValue = item.getString(key)
destination[keyValue] = item destination.computeIfAbsent(keyValue) { mutableListOf() }.add(item)
} }
return destination return destination
} }

@ -11,7 +11,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser 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.bitmap.Rect
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
@ -28,7 +28,7 @@ internal abstract class MangaFireParser(
context: MangaLoaderContext, context: MangaLoaderContext,
source: MangaParserSource, source: MangaParserSource,
private val siteLang: String, 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") override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("mangafire.to")

@ -4,7 +4,7 @@ import androidx.collection.ArrayMap
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -15,7 +15,7 @@ import java.util.*
@MangaSourceParser("MANGAPARK", "MangaPark") @MangaSourceParser("MANGAPARK", "MangaPark")
internal class MangaPark(context: MangaLoaderContext) : internal class MangaPark(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.MANGAPARK, pageSize = 36) { LegacyPagedMangaParser(context, MangaParserSource.MANGAPARK, pageSize = 36) {
override val configKeyDomain = ConfigKey.Domain( override val configKeyDomain = ConfigKey.Domain(
"mangapark.net", "mangapark.net",

@ -10,7 +10,7 @@ import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -25,7 +25,7 @@ internal abstract class MangaPlusParser(
context: MangaLoaderContext, context: MangaLoaderContext,
source: MangaParserSource, source: MangaParserSource,
private val sourceLang: String, private val sourceLang: String,
) : SinglePageMangaParser(context, source), Interceptor { ) : LegacySinglePageMangaParser(context, source), Interceptor {
private val apiUrl = "https://jumpg-webapi.tokyo-cdn.com/api" private val apiUrl = "https://jumpg-webapi.tokyo-cdn.com/api"
override val configKeyDomain = ConfigKey.Domain("mangaplus.shueisha.co.jp") override val configKeyDomain = ConfigKey.Domain("mangaplus.shueisha.co.jp")

@ -9,7 +9,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser 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.Bitmap
import org.koitharu.kotatsu.parsers.bitmap.Rect import org.koitharu.kotatsu.parsers.bitmap.Rect
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
@ -23,7 +23,7 @@ import kotlin.math.min
@MangaSourceParser("MANGAREADERTO", "MangaReader.To") @MangaSourceParser("MANGAREADERTO", "MangaReader.To")
internal class MangaReaderToParser(context: MangaLoaderContext) : internal class MangaReaderToParser(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.MANGAREADERTO, 16), LegacyPagedMangaParser(context, MangaParserSource.MANGAREADERTO, 16),
Interceptor, MangaParserAuthProvider { Interceptor, MangaParserAuthProvider {
override val configKeyDomain = ConfigKey.Domain("mangareader.to") override val configKeyDomain = ConfigKey.Domain("mangareader.to")

@ -8,7 +8,7 @@ import okhttp3.Interceptor
import okhttp3.Response import okhttp3.Response
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -19,7 +19,7 @@ internal abstract class NineMangaParser(
context: MangaLoaderContext, context: MangaLoaderContext,
source: MangaParserSource, source: MangaParserSource,
defaultDomain: String, defaultDomain: String,
) : PagedMangaParser(context, source, pageSize = 26), Interceptor { ) : LegacyPagedMangaParser(context, source, pageSize = 26), Interceptor {
override val configKeyDomain = ConfigKey.Domain(defaultDomain) override val configKeyDomain = ConfigKey.Domain(defaultDomain)

@ -8,7 +8,7 @@ import org.json.JSONObject
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -23,7 +23,7 @@ import java.util.*
@Broken @Broken
@MangaSourceParser("NINENINENINEHENTAI", "AnimeH", type = ContentType.HENTAI) @MangaSourceParser("NINENINENINEHENTAI", "AnimeH", type = ContentType.HENTAI)
internal class NineNineNineHentaiParser(context: MangaLoaderContext) : 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") override val configKeyDomain = ConfigKey.Domain("animeh.to")

@ -8,7 +8,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.NotFoundException import org.koitharu.kotatsu.parsers.exception.NotFoundException
@ -24,7 +24,7 @@ import javax.crypto.spec.SecretKeySpec
internal abstract class WebtoonsParser( internal abstract class WebtoonsParser(
context: MangaLoaderContext, context: MangaLoaderContext,
source: MangaParserSource, source: MangaParserSource,
) : MangaParser(context, source) { ) : LegacyMangaParser(context, source) {
private val signer by lazy { private val signer by lazy {
WebtoonsUrlSigner("gUtPzJFZch4ZyAGviiyH94P99lQ3pFdRTwpJWDlSGFfwgpr6ses5ALOxWHOIT7R1") WebtoonsUrlSigner("gUtPzJFZch4ZyAGviiyH94P99lQ3pFdRTwpJWDlSGFfwgpr6ses5ALOxWHOIT7R1")

@ -5,7 +5,7 @@ import kotlinx.coroutines.coroutineScope
import org.json.JSONArray import org.json.JSONArray
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -16,7 +16,7 @@ internal abstract class AnimeBootstrapParser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 24, pageSize: Int = 24,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -7,7 +7,7 @@ import org.json.JSONObject
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -18,7 +18,8 @@ import java.util.*
@Broken @Broken
@MangaSourceParser("FLIXSCANS", "FlixScans.net", "ar") @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<SortOrder> = EnumSet.of(SortOrder.UPDATED) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
override val configKeyDomain = ConfigKey.Domain("flixscans.net") override val configKeyDomain = ConfigKey.Domain("flixscans.net")

@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.ar
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
@ -12,7 +12,8 @@ import java.util.*
@Broken @Broken
@MangaSourceParser("MANGASTORM", "MangaStorm", "ar") @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<SortOrder> = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED)
override val configKeyDomain = ConfigKey.Domain("mangastorm.org") override val configKeyDomain = ConfigKey.Domain("mangastorm.org")

@ -6,7 +6,7 @@ import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -14,7 +14,8 @@ import java.text.SimpleDateFormat
import java.util.* import java.util.*
@MangaSourceParser("TEAMXNOVEL", "TeamXNovel", "ar") @MangaSourceParser("TEAMXNOVEL", "TeamXNovel", "ar")
internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.TEAMXNOVEL, 10) { internal class TeamXNovel(context: MangaLoaderContext) :
LegacyPagedMangaParser(context, MangaParserSource.TEAMXNOVEL, 10) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)

@ -4,21 +4,24 @@ import androidx.collection.ArraySet
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* 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.asTypedList
import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed
import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull 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.* import java.util.*
@Broken @Broken
@MangaSourceParser("ANIBEL", "Anibel", "be") @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") override val configKeyDomain = ConfigKey.Domain("anibel.net")

@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.cupfox
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -13,7 +13,7 @@ internal abstract class CupFoxParser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 24, pageSize: Int = 24,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -6,7 +6,7 @@ import kotlinx.coroutines.sync.withLock
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -18,7 +18,7 @@ import java.util.*
@MangaSourceParser("ASURASCANS", "AsuraComic", "en") @MangaSourceParser("ASURASCANS", "AsuraComic", "en")
internal class AsuraScansParser(context: MangaLoaderContext) : internal class AsuraScansParser(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.ASURASCANS, pageSize = 30) { LegacyPagedMangaParser(context, MangaParserSource.ASURASCANS, pageSize = 30) {
override val configKeyDomain = ConfigKey.Domain("asuracomic.net") override val configKeyDomain = ConfigKey.Domain("asuracomic.net")

@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -11,7 +11,7 @@ import java.util.*
@MangaSourceParser("BEETOON", "BeeToon.net", "en") @MangaSourceParser("BEETOON", "BeeToon.net", "en")
internal class BeeToon(context: MangaLoaderContext) : internal class BeeToon(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.BEETOON, pageSize = 30) { LegacyPagedMangaParser(context, MangaParserSource.BEETOON, pageSize = 30) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)

@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -10,7 +10,7 @@ import java.util.*
@MangaSourceParser("CLONEMANGA", "CloneManga", "en") @MangaSourceParser("CLONEMANGA", "CloneManga", "en")
internal class CloneMangaParser(context: MangaLoaderContext) : internal class CloneMangaParser(context: MangaLoaderContext) :
SinglePageMangaParser(context, MangaParserSource.CLONEMANGA) { LegacySinglePageMangaParser(context, MangaParserSource.CLONEMANGA) {
override val availableSortOrders: Set<SortOrder> = Collections.singleton( override val availableSortOrders: Set<SortOrder> = Collections.singleton(
SortOrder.POPULARITY, SortOrder.POPULARITY,

@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
@ -11,7 +11,8 @@ import java.text.SimpleDateFormat
import java.util.* import java.util.*
@MangaSourceParser("COMICEXTRA", "ComicExtra", "en", ContentType.COMICS) @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") override val configKeyDomain = ConfigKey.Domain("azcomix.me")

@ -9,7 +9,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
@ -19,7 +19,7 @@ import java.util.*
@MangaSourceParser("DYNASTYSCANS", "DynastyScans", "en") @MangaSourceParser("DYNASTYSCANS", "DynastyScans", "en")
internal class DynastyScans(context: MangaLoaderContext) : internal class DynastyScans(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.DYNASTYSCANS, 117) { LegacyPagedMangaParser(context, MangaParserSource.DYNASTYSCANS, 117) {
override val configKeyDomain = ConfigKey.Domain("dynasty-scans.com") override val configKeyDomain = ConfigKey.Domain("dynasty-scans.com")

@ -6,7 +6,7 @@ import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -17,7 +17,7 @@ import java.util.concurrent.TimeUnit
@MangaSourceParser("FLAMECOMICS", "FlameComics", "en") @MangaSourceParser("FLAMECOMICS", "FlameComics", "en")
internal class FlameComics(context: MangaLoaderContext) : internal class FlameComics(context: MangaLoaderContext) :
SinglePageMangaParser(context, MangaParserSource.FLAMECOMICS) { LegacySinglePageMangaParser(context, MangaParserSource.FLAMECOMICS) {
private val commonPrefix = suspendLazy(initializer = ::fetchCommonPrefix) private val commonPrefix = suspendLazy(initializer = ::fetchCommonPrefix)

@ -6,6 +6,7 @@ import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.* import org.koitharu.kotatsu.parsers.*
import org.koitharu.kotatsu.parsers.config.ConfigKey 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.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.asTypedList import org.koitharu.kotatsu.parsers.util.json.asTypedList
@ -16,7 +17,7 @@ import java.util.*
@Broken @Broken
@MangaSourceParser("FLIXSCANSORG", "FlixScans.org", "en") @MangaSourceParser("FLIXSCANSORG", "FlixScans.org", "en")
internal class FlixScansOrg(context: MangaLoaderContext) : internal class FlixScansOrg(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.FLIXSCANSORG, 18) { LegacyPagedMangaParser(context, MangaParserSource.FLIXSCANSORG, 18) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
override val configKeyDomain = ConfigKey.Domain("flixscans.org") override val configKeyDomain = ConfigKey.Domain("flixscans.org")

@ -4,7 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -12,7 +12,8 @@ import java.text.SimpleDateFormat
import java.util.* import java.util.*
@MangaSourceParser("MANGAGEKO", "MangaGeko", "en") @MangaSourceParser("MANGAGEKO", "MangaGeko", "en")
internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAGEKO, 30) { internal class MangaGeko(context: MangaLoaderContext) :
LegacyPagedMangaParser(context, MangaParserSource.MANGAGEKO, 30) {
override val availableSortOrders: Set<SortOrder> = override val availableSortOrders: Set<SortOrder> =
EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST) EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST)

@ -5,7 +5,7 @@ import kotlinx.coroutines.coroutineScope
import okhttp3.Headers import okhttp3.Headers
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -13,7 +13,7 @@ import java.util.*
@MangaSourceParser("MANGAKAWAII_EN", "MangaKawaii En", "en") @MangaSourceParser("MANGAKAWAII_EN", "MangaKawaii En", "en")
internal class MangaKawaiiEn(context: MangaLoaderContext) : 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") override val configKeyDomain = ConfigKey.Domain("www.mangakawaii.io")

@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("MANGATOWN", "MangaTown", "en") @MangaSourceParser("MANGATOWN", "MangaTown", "en")
internal class MangaTownParser(context: MangaLoaderContext) : internal class MangaTownParser(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.MANGATOWN, 30) { LegacyPagedMangaParser(context, MangaParserSource.MANGATOWN, 30) {
override val configKeyDomain = ConfigKey.Domain("www.mangatown.com") override val configKeyDomain = ConfigKey.Domain("www.mangatown.com")

@ -5,7 +5,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
@ -17,7 +17,7 @@ import java.util.*
@Broken @Broken
@MangaSourceParser("MANGAOWL", "MangaOwl.to", "en") @MangaSourceParser("MANGAOWL", "MangaOwl.to", "en")
internal class Mangaowl(context: MangaLoaderContext) : internal class Mangaowl(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.MANGAOWL, pageSize = 24) { LegacyPagedMangaParser(context, MangaParserSource.MANGAOWL, pageSize = 24) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of( override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.POPULARITY, SortOrder.POPULARITY,

@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.en
import androidx.collection.ArrayMap import androidx.collection.ArrayMap
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("MANHWA18COM", "Manhwa18.com", "en", type = ContentType.HENTAI) @MangaSourceParser("MANHWA18COM", "Manhwa18.com", "en", type = ContentType.HENTAI)
internal class Manhwa18Com(context: MangaLoaderContext) : 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") override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwa18.com")

@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.en
import androidx.collection.ArrayMap import androidx.collection.ArrayMap
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("MANHWA18", "Manhwa18.net", "en", type = ContentType.HENTAI) @MangaSourceParser("MANHWA18", "Manhwa18.net", "en", type = ContentType.HENTAI)
internal class Manhwa18Parser(context: MangaLoaderContext) : 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") override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwa18.net")

@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("MANHWASMEN", "ManhwasMen", "en", type = ContentType.HENTAI) @MangaSourceParser("MANHWASMEN", "ManhwasMen", "en", type = ContentType.HENTAI)
internal class ManhwasMen(context: MangaLoaderContext) : 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") override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwas.men")

@ -2,14 +2,15 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.* import java.util.*
@MangaSourceParser("MYCOMICLIST", "MyComicList", "en", ContentType.COMICS) @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") override val configKeyDomain = ConfigKey.Domain("mycomiclist.org")

@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -12,7 +12,8 @@ import java.util.*
@Broken @Broken
@MangaSourceParser("PO2SCANS", "Po2Scans", "en") @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<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
override val configKeyDomain = ConfigKey.Domain("po2scans.com") override val configKeyDomain = ConfigKey.Domain("po2scans.com")

@ -8,7 +8,7 @@ import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -17,7 +17,7 @@ import java.util.*
@Broken @Broken
@MangaSourceParser("PURURIN", "Pururin", "en", ContentType.HENTAI) @MangaSourceParser("PURURIN", "Pururin", "en", ContentType.HENTAI)
internal class Pururin(context: MangaLoaderContext) : internal class Pururin(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.PURURIN, pageSize = 20) { LegacyPagedMangaParser(context, MangaParserSource.PURURIN, pageSize = 20) {
override val configKeyDomain = ConfigKey.Domain("pururin.to") override val configKeyDomain = ConfigKey.Domain("pururin.to")

@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("VYMANGA", "VyManga", "en") @MangaSourceParser("VYMANGA", "VyManga", "en")
internal class VyManga(context: MangaLoaderContext) : 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") override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("vymanga.net")

@ -11,6 +11,7 @@ import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.core.LegacyMangaParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.model.ContentRating.SAFE import org.koitharu.kotatsu.parsers.model.ContentRating.SAFE
import org.koitharu.kotatsu.parsers.model.ContentRating.SUGGESTIVE import org.koitharu.kotatsu.parsers.model.ContentRating.SUGGESTIVE
@ -22,7 +23,7 @@ import java.text.SimpleDateFormat
import java.util.* import java.util.*
@MangaSourceParser("WEEBCENTRAL", "Weeb Central", "en") @MangaSourceParser("WEEBCENTRAL", "Weeb Central", "en")
internal class WeebCentral(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.WEEBCENTRAL), internal class WeebCentral(context: MangaLoaderContext) : LegacyMangaParser(context, MangaParserSource.WEEBCENTRAL),
MangaParserAuthProvider { MangaParserAuthProvider {
override val configKeyDomain = ConfigKey.Domain("weebcentral.com") override val configKeyDomain = ConfigKey.Domain("weebcentral.com")

@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.es
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
@ -16,7 +16,7 @@ import java.util.*
@MangaSourceParser("TEMPLESCANESP", "TempleScanEsp", "es", ContentType.HENTAI) @MangaSourceParser("TEMPLESCANESP", "TempleScanEsp", "es", ContentType.HENTAI)
internal class TempleScanEsp(context: MangaLoaderContext) : internal class TempleScanEsp(context: MangaLoaderContext) :
SinglePageMangaParser(context, MangaParserSource.TEMPLESCANESP) { LegacySinglePageMangaParser(context, MangaParserSource.TEMPLESCANESP) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.NEWEST_ASC) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.NEWEST_ASC)

@ -6,7 +6,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -14,7 +14,7 @@ import java.text.SimpleDateFormat
import java.util.* import java.util.*
@MangaSourceParser("TUMANGAONLINE", "TuMangaOnline", "es") @MangaSourceParser("TUMANGAONLINE", "TuMangaOnline", "es")
internal class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser( internal class TuMangaOnlineParser(context: MangaLoaderContext) : LegacyPagedMangaParser(
context, context,
source = MangaParserSource.TUMANGAONLINE, source = MangaParserSource.TUMANGAONLINE,
pageSize = 24, pageSize = 24,

@ -4,7 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -17,7 +17,7 @@ internal abstract class FmreaderParser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 20, pageSize: Int = 20,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -4,7 +4,7 @@ import kotlinx.coroutines.coroutineScope
import org.json.JSONArray import org.json.JSONArray
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -16,7 +16,7 @@ internal abstract class FoolSlideParser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 25, pageSize: Int = 25,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -8,7 +8,7 @@ import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
@ -19,7 +19,7 @@ import java.util.*
@Broken @Broken
@MangaSourceParser("BENTOMANGA", "BentoManga", "fr") @MangaSourceParser("BENTOMANGA", "BentoManga", "fr")
internal class BentomangaParser(context: MangaLoaderContext) : internal class BentomangaParser(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.BENTOMANGA, 10) { LegacyPagedMangaParser(context, MangaParserSource.BENTOMANGA, 10) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of( override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED, SortOrder.UPDATED,

@ -5,7 +5,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.ErrorMessages import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
@ -16,7 +16,7 @@ import java.util.*
@MangaSourceParser("FURYOSOCIETY", "FuryoSociety", "fr") @MangaSourceParser("FURYOSOCIETY", "FuryoSociety", "fr")
internal class FuryoSociety(context: MangaLoaderContext) : internal class FuryoSociety(context: MangaLoaderContext) :
SinglePageMangaParser(context, MangaParserSource.FURYOSOCIETY) { LegacySinglePageMangaParser(context, MangaParserSource.FURYOSOCIETY) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL, SortOrder.UPDATED) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL, SortOrder.UPDATED)

@ -4,7 +4,7 @@ import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -14,7 +14,7 @@ import java.util.*
@MangaSourceParser("LEGACY_SCANS", "LegacyScans", "fr") @MangaSourceParser("LEGACY_SCANS", "LegacyScans", "fr")
internal class LegacyScansParser(context: MangaLoaderContext) : internal class LegacyScansParser(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.LEGACY_SCANS, 18) { LegacyPagedMangaParser(context, MangaParserSource.LEGACY_SCANS, 18) {
override val configKeyDomain = ConfigKey.Domain("legacy-scans.com") override val configKeyDomain = ConfigKey.Domain("legacy-scans.com")

@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.fr
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -12,7 +12,7 @@ import java.util.*
@Broken @Broken
@MangaSourceParser("LIRESCAN", "LireScan", "fr") @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<SortOrder> = EnumSet.of(SortOrder.UPDATED) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)

@ -4,7 +4,7 @@ import org.json.JSONArray
import org.koitharu.kotatsu.parsers.ErrorMessages import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
@ -17,7 +17,7 @@ import java.util.*
@MangaSourceParser("LUGNICASCANS", "LugnicaScans", "fr") @MangaSourceParser("LUGNICASCANS", "LugnicaScans", "fr")
internal class LugnicaScans(context: MangaLoaderContext) : internal class LugnicaScans(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.LUGNICASCANS, 10) { LegacyPagedMangaParser(context, MangaParserSource.LUGNICASCANS, 10) {
override val configKeyDomain = ConfigKey.Domain("lugnica-scans.com") override val configKeyDomain = ConfigKey.Domain("lugnica-scans.com")

@ -5,14 +5,15 @@ import kotlinx.coroutines.coroutineScope
import okhttp3.Headers import okhttp3.Headers
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.* import java.util.*
@MangaSourceParser("MANGAKAWAII", "MangaKawaii Fr", "fr") @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") override val configKeyDomain = ConfigKey.Domain("www.mangakawaii.io")

@ -11,7 +11,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
@ -22,7 +22,8 @@ import java.text.SimpleDateFormat
import java.util.* import java.util.*
@MangaSourceParser("MANGAMANA", "MangaMana", "fr") @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") override val configKeyDomain = ConfigKey.Domain("www.manga-mana.com")

@ -6,7 +6,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
@ -16,7 +16,7 @@ import java.util.*
@Broken @Broken
@MangaSourceParser("SCANS_MANGAS_ME", "ScansMangas.me", "fr") @MangaSourceParser("SCANS_MANGAS_ME", "ScansMangas.me", "fr")
internal class ScansMangasMe(context: MangaLoaderContext) : internal class ScansMangasMe(context: MangaLoaderContext) :
SinglePageMangaParser(context, MangaParserSource.SCANS_MANGAS_ME) { LegacySinglePageMangaParser(context, MangaParserSource.SCANS_MANGAS_ME) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of( override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL,

@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.fr
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
@ -13,7 +13,7 @@ import java.util.*
@MangaSourceParser("SCANTRADUNION", "ScantradUnion", "fr") @MangaSourceParser("SCANTRADUNION", "ScantradUnion", "fr")
internal class ScantradUnion(context: MangaLoaderContext) : internal class ScantradUnion(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.SCANTRADUNION, 10) { LegacyPagedMangaParser(context, MangaParserSource.SCANTRADUNION, 10) {
override val configKeyDomain = ConfigKey.Domain("scantrad-union.com") override val configKeyDomain = ConfigKey.Domain("scantrad-union.com")

@ -7,7 +7,7 @@ import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -20,7 +20,7 @@ internal abstract class FuzzyDoodleParser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 24, pageSize: Int = 24,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -8,7 +8,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.ErrorMessages import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -19,7 +19,7 @@ internal abstract class GalleryAdultsParser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 20, pageSize: Int = 20,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.gattsu
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -14,7 +14,7 @@ internal abstract class GattsuParser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 20, pageSize: Int = 20,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.guya
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -12,7 +12,7 @@ internal abstract class GuyaParser(
context: MangaLoaderContext, context: MangaLoaderContext,
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
) : SinglePageMangaParser(context, source) { ) : LegacySinglePageMangaParser(context, source) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.heancms
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -19,7 +19,7 @@ internal abstract class HeanCms(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 20, pageSize: Int = 20,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.heancmsalt
import org.koitharu.kotatsu.parsers.ErrorMessages import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -17,7 +17,7 @@ internal abstract class HeanCmsAlt(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 18, pageSize: Int = 18,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -7,7 +7,7 @@ import kotlinx.coroutines.sync.withLock
import okhttp3.Headers import okhttp3.Headers
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
@ -20,7 +20,7 @@ internal abstract class HotComicsParser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 24, pageSize: Int = 24,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.id
import okhttp3.Headers import okhttp3.Headers
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -12,7 +12,7 @@ import java.util.*
@MangaSourceParser("DOUJINDESU", "DoujinDesu.tv", "id") @MangaSourceParser("DOUJINDESU", "DoujinDesu.tv", "id")
internal class DoujinDesuParser(context: MangaLoaderContext) : internal class DoujinDesuParser(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.DOUJINDESU, pageSize = 18) { LegacyPagedMangaParser(context, MangaParserSource.DOUJINDESU, pageSize = 18) {
override val configKeyDomain: ConfigKey.Domain override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("doujindesu.tv") get() = ConfigKey.Domain("doujindesu.tv")

@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.id
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -10,7 +10,7 @@ import java.util.*
@MangaSourceParser("HENTAICROT", "HentaiCrot", "id", ContentType.HENTAI) @MangaSourceParser("HENTAICROT", "HentaiCrot", "id", ContentType.HENTAI)
internal class HentaiCrot(context: MangaLoaderContext) : internal class HentaiCrot(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.HENTAICROT, 8) { LegacyPagedMangaParser(context, MangaParserSource.HENTAICROT, 8) {
override val configKeyDomain = ConfigKey.Domain("hentaicrot.com") override val configKeyDomain = ConfigKey.Domain("hentaicrot.com")

@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.id
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -10,7 +10,7 @@ import java.util.*
@MangaSourceParser("PIXHENTAI", "PixHentai", "id", ContentType.HENTAI) @MangaSourceParser("PIXHENTAI", "PixHentai", "id", ContentType.HENTAI)
internal class PixHentai(context: MangaLoaderContext) : internal class PixHentai(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.PIXHENTAI, 8) { LegacyPagedMangaParser(context, MangaParserSource.PIXHENTAI, 8) {
override val configKeyDomain = ConfigKey.Domain("pixhentai.com") override val configKeyDomain = ConfigKey.Domain("pixhentai.com")

@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.iken
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -18,7 +18,7 @@ internal abstract class IkenParser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 18, pageSize: Int = 18,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.parsers.site.ja package org.koitharu.kotatsu.parsers.site.ja
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
@ -16,7 +16,7 @@ private const val STATUS_FINISHED = "完結"
@MangaSourceParser("NICOVIDEO_SEIGA", "NicoVideo Seiga", "ja") @MangaSourceParser("NICOVIDEO_SEIGA", "NicoVideo Seiga", "ja")
internal class NicovideoSeigaParser(context: MangaLoaderContext) : internal class NicovideoSeigaParser(context: MangaLoaderContext) :
MangaParser(context, MangaParserSource.NICOVIDEO_SEIGA), LegacyMangaParser(context, MangaParserSource.NICOVIDEO_SEIGA),
MangaParserAuthProvider { MangaParserAuthProvider {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("nicovideo.jp") override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("nicovideo.jp")

@ -5,7 +5,7 @@ import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.core.LegacySinglePageMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -17,7 +17,7 @@ internal abstract class KeyoappParser(
context: MangaLoaderContext, context: MangaLoaderContext,
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
) : SinglePageMangaParser(context, source) { ) : LegacySinglePageMangaParser(context, source) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -7,7 +7,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
import org.json.JSONObject import org.json.JSONObject
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -20,7 +20,7 @@ internal abstract class LikeMangaParser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 36, pageSize: Int = 36,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -5,7 +5,7 @@ import kotlinx.coroutines.coroutineScope
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -17,7 +17,7 @@ internal abstract class LilianaParser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 24, pageSize: Int = 24,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -8,7 +8,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.exception.ParseException
@ -23,7 +23,7 @@ internal abstract class MadaraParser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 12, pageSize: Int = 12,
) : PagedMangaParser(context, source, pageSize), MangaParserAuthProvider { ) : LegacyPagedMangaParser(context, source, pageSize), MangaParserAuthProvider {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -4,7 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -17,7 +17,7 @@ internal abstract class MadthemeParser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 48, pageSize: Int = 48,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -4,7 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -16,7 +16,7 @@ internal abstract class Manga18Parser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 20, pageSize: Int = 20,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -4,9 +4,15 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.PagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.core.PagedMangaParser
import org.koitharu.kotatsu.parsers.model.* 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.*
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -30,12 +36,32 @@ internal abstract class MangaboxParser(
SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL,
) )
override val filterCapabilities: MangaListFilterCapabilities override val searchQueryCapabilities: MangaSearchQueryCapabilities
get() = MangaListFilterCapabilities( get() = MangaSearchQueryCapabilities(
isMultipleTagsSupported = true, SearchCapability(
isTagsExclusionSupported = true, field = TAG,
isSearchSupported = true, criteriaTypes = setOf(Include::class, Exclude::class),
isSearchWithFiltersSupported = true, 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( override suspend fun getFilterOptions() = MangaListFilterOptions(
@ -59,64 +85,84 @@ internal abstract class MangaboxParser(
) )
protected open val listUrl = "/advanced_search" protected open val listUrl = "/advanced_search"
protected open val authorUrl = "/search/author"
protected open val searchUrl = "/search/story/" protected open val searchUrl = "/search/story/"
protected open val datePattern = "MMM dd,yy" protected open val datePattern = "MMM dd,yy"
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { private fun SearchableField.toParamName(): String = when (this) {
val url = buildString { TITLE_NAME, AUTHOR -> "keyw"
append("https://") TAG -> "g_i"
append(domain) STATE -> "sts"
append(listUrl) else -> ""
append("/?s=all") }
filter.query?.let {
append("&keyw=")
append(filter.query.replace(" ", "_").urlEncoded())
}
if (filter.tags.isNotEmpty()) { private fun Any?.toQueryParam(): String = when (this) {
append("&g_i=") is String -> replace(" ", "_").urlEncoded()
filter.tags.forEach { is MangaTag -> key
append("_") is MangaState -> when (this) {
append(it.key) MangaState.ONGOING -> "ongoing"
append("_") MangaState.FINISHED -> "completed"
} else -> ""
} }
if (filter.tagsExclude.isNotEmpty()) { is SortOrder -> when (this) {
append("&g_e=") SortOrder.ALPHABETICAL -> "az"
filter.tagsExclude.forEach { SortOrder.NEWEST -> "newest"
append("_") SortOrder.POPULARITY -> "topview"
append(it.key) else -> ""
append("_") }
}
}
filter.states.oneOrThrowIfMany()?.let { else -> this.toString().replace(" ", "_").urlEncoded()
append("&sts=") }
append(
when (it) {
MangaState.ONGOING -> "ongoing"
MangaState.FINISHED -> "completed"
else -> ""
},
)
}
append("&orby=") private fun StringBuilder.appendCriterion(field: SearchableField, value: Any?, paramName: String? = null) {
when (order) { val param = paramName ?: field.toParamName()
SortOrder.POPULARITY -> append("topview") if (param.isNotBlank()) {
SortOrder.UPDATED -> append("") append("&$param=")
SortOrder.NEWEST -> append("newest") append(value.toQueryParam())
SortOrder.ALPHABETICAL -> append("az") }
else -> append("") }
override suspend fun getListPage(query: MangaSearchQuery, page: Int): List<Manga> {
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("&${pageQueryParameter}")
append(page.toString()) 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 { return doc.select("div.content-genres-item, div.list-story-item").ifEmpty {
doc.select("div.search-story-item") doc.select("div.search-story-item")
@ -134,7 +180,7 @@ internal abstract class MangaboxParser(
authors = emptySet(), authors = emptySet(),
state = null, state = null,
source = source, 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 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( manga.copy(
tags = doc.body().select(selectTag).mapToSet { a -> tags = doc.body().select(selectTag).mapToSet { a ->
MangaTag( MangaTag(
@ -186,7 +233,7 @@ internal abstract class MangaboxParser(
}, },
description = desc, description = desc,
altTitle = alt, altTitle = alt,
authors = author?.let { setOf(it) } ?: emptySet(), authors = authors,
state = state, state = state,
chapters = chaptersDeferred.await(), chapters = chaptersDeferred.await(),
) )

@ -6,6 +6,12 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* 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.site.mangabox.MangaboxParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.* import java.util.*
@ -31,65 +37,88 @@ internal class Mangairo(context: MangaLoaderContext) :
SortOrder.POPULARITY, SortOrder.POPULARITY,
SortOrder.NEWEST, SortOrder.NEWEST,
) )
override val filterCapabilities: MangaListFilterCapabilities
get() = super.filterCapabilities.copy( override val searchQueryCapabilities: MangaSearchQueryCapabilities
isTagsExclusionSupported = false, get() = MangaSearchQueryCapabilities(
isMultipleTagsSupported = false, SearchCapability(
isSearchWithFiltersSupported = false, 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<Manga> { private fun Any?.toQueryParam(): String = when (this) {
val url = buildString { is String -> replace(" ", "_").urlEncoded()
append("https://") is MangaTag -> key
append(domain) is MangaState -> when (this) {
when { MangaState.ONGOING -> "ongoing"
MangaState.FINISHED -> "completed"
!filter.query.isNullOrEmpty() -> { else -> "all"
append(searchUrl) }
append(filter.query.urlEncoded())
append("?page=")
}
else -> { is SortOrder -> when (this) {
append(listUrl) SortOrder.POPULARITY -> "topview"
append("/type-") SortOrder.UPDATED -> "latest"
when (order) { SortOrder.NEWEST -> "newest"
SortOrder.POPULARITY -> append("topview") else -> "latest"
SortOrder.UPDATED -> append("latest") }
SortOrder.NEWEST -> append("newest")
else -> append("latest") else -> this.toString().urlEncoded()
} }
override suspend fun getListPage(query: MangaSearchQuery, page: Int): List<Manga> {
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-") query.criteria.forEach { criterion ->
if (filter.tags.isNotEmpty()) { when (criterion) {
filter.tags.oneOrThrowIfMany()?.let { is Include<*> -> {
append(it.key) when (criterion.field) {
TAG -> category = criterion.values.first().toQueryParam()
STATE -> state = criterion.values.first().toQueryParam()
else -> Unit
} }
} else {
append("all")
} }
append("/state-") is Match<*> -> {
if (filter.states.isNotEmpty()) { if (criterion.field == TITLE_NAME) {
filter.states.oneOrThrowIfMany()?.let { criterion.value.toQueryParam().takeIf { it.isNotBlank() }?.let { titleName ->
append( titleSearchUrl = "https://${domain}${searchUrl}${titleName}/" +
when (it) { "?page=${query.offset}"
MangaState.ONGOING -> "ongoing" }
MangaState.FINISHED -> "completed"
else -> "all"
},
)
} }
} 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 -> return doc.select("div.story-item").map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
Manga( Manga(
@ -104,7 +133,7 @@ internal class Mangairo(context: MangaLoaderContext) :
authors = emptySet(), authors = emptySet(),
state = null, state = null,
source = source, source = source,
contentRating = if (isNsfwSource) ContentRating.ADULT else null, contentRating = if (source.contentType == ContentType.HENTAI) ContentRating.ADULT else ContentRating.SAFE,
) )
} }
} }

@ -5,6 +5,9 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* 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.site.mangabox.MangaboxParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -19,63 +22,109 @@ internal class Mangakakalot(context: MangaLoaderContext) :
SortOrder.POPULARITY, SortOrder.POPULARITY,
SortOrder.NEWEST, SortOrder.NEWEST,
) )
override val filterCapabilities: MangaListFilterCapabilities
get() = super.filterCapabilities.copy( override val searchQueryCapabilities: MangaSearchQueryCapabilities
isTagsExclusionSupported = false, get() = MangaSearchQueryCapabilities(
isMultipleTagsSupported = false, SearchCapability(
isSearchWithFiltersSupported = false, 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 otherDomain = "chapmanganato.com"
override val listUrl = "/manga_list" override val listUrl = "/manga_list"
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { 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<Manga> {
var titleSearchUrl: String? = null
val url = buildString { val url = buildString {
append("https://") val pageQueryParameter = "page=$page"
append(domain) append("https://$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=")
}
else -> { query.criteria.forEach { criterion ->
append(listUrl) when (criterion) {
append("?type=") is Include<*> -> {
when (order) { criterion.field.toParamName().takeIf { it.isNotBlank() }?.let { param ->
SortOrder.POPULARITY -> append("topview") append("&$param=${criterion.values.first().toQueryParam()}")
SortOrder.UPDATED -> append("latest")
SortOrder.NEWEST -> append("newest")
else -> append("latest")
}
if (filter.tags.isNotEmpty()) {
append("&category=")
filter.tags.oneOrThrowIfMany()?.let {
append(it.key)
} }
} }
filter.states.oneOrThrowIfMany()?.let { is Match<*> -> {
append("&state=") if (criterion.field == TITLE_NAME) {
append( criterion.value.toQueryParam().takeIf { it.isNotBlank() }?.let { titleName ->
when (it) { titleSearchUrl = "https://${domain}${searchUrl}${titleName}/" +
MangaState.ONGOING -> "ongoing" "?$pageQueryParameter"
MangaState.FINISHED -> "completed" }
else -> "all" }
}, 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 { return doc.select("div.list-truyen-item-wrap").ifEmpty {
doc.select("div.story_item") doc.select("div.story_item")
@ -93,7 +142,7 @@ internal class Mangakakalot(context: MangaLoaderContext) :
authors = emptySet(), authors = emptySet(),
state = null, state = null,
source = source, source = source,
contentRating = if (isNsfwSource) ContentRating.ADULT else null, contentRating = null,
) )
} }
} }

@ -6,6 +6,9 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* 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.site.mangabox.MangaboxParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.* import java.util.*
@ -22,58 +25,98 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
SortOrder.POPULARITY, SortOrder.POPULARITY,
SortOrder.NEWEST, SortOrder.NEWEST,
) )
override val filterCapabilities: MangaListFilterCapabilities
get() = super.filterCapabilities.copy( override val searchQueryCapabilities: MangaSearchQueryCapabilities
isTagsExclusionSupported = false, get() = MangaSearchQueryCapabilities(
isMultipleTagsSupported = false, SearchCapability(
isSearchWithFiltersSupported = false, 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<Manga> { 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<Manga> {
var titleSearchUrl: String? = null
val url = buildString { val url = buildString {
append("https://") val pageQueryParameter = "page=$page"
append(domain) append("https://$domain/?")
when {
!filter.query.isNullOrEmpty() -> {
append(searchUrl)
append(filter.query.urlEncoded())
append("?page=")
}
else -> { query.criteria.forEach { criterion ->
append(listUrl) when (criterion) {
append("?type=") is Include<*> -> {
when (order) { criterion.field.toParamName().takeIf { it.isNotBlank() }?.let { param ->
SortOrder.POPULARITY -> append("topview") append("&$param=${criterion.values.first().toQueryParam()}")
SortOrder.UPDATED -> append("latest")
SortOrder.NEWEST -> append("newest")
else -> append("latest")
}
if (filter.tags.isNotEmpty()) {
append("&category=")
filter.tags.oneOrThrowIfMany()?.let {
append(it.key)
} }
} }
filter.states.oneOrThrowIfMany()?.let { is Match<*> -> {
append("&state=") if (criterion.field == TITLE_NAME) {
append( criterion.value.toQueryParam().takeIf { it.isNotBlank() }?.let { titleName ->
when (it) { titleSearchUrl = "https://${domain}${searchUrl}${titleName}/" +
MangaState.ONGOING -> "Ongoing" "?$pageQueryParameter"
MangaState.FINISHED -> "Completed" }
else -> "all" }
}, 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 { return doc.select("div.list-truyen-item-wrap").ifEmpty {
doc.select("div.story_item") doc.select("div.story_item")
}.map { div -> }.map { div ->
@ -90,7 +133,7 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
authors = emptySet(), authors = emptySet(),
state = null, state = null,
source = source, source = source,
contentRating = if (isNsfwSource) ContentRating.ADULT else null, contentRating = null,
) )
} }
} }

@ -16,5 +16,6 @@ internal class Manganato(context: MangaLoaderContext) :
) )
override val otherDomain = "chapmanganato.to" override val otherDomain = "chapmanganato.to"
override val authorUrl = "/author/story"
override val selectPage = ".container-chapter-reader > img" override val selectPage = ".container-chapter-reader > img"
} }

@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.mangadventure
import okhttp3.HttpUrl import okhttp3.HttpUrl
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.NotFoundException import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
@ -17,7 +17,7 @@ internal abstract class MangAdventureParser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 25, pageSize: Int = 25,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -12,7 +12,7 @@ import okhttp3.internal.closeQuietly
import org.json.JSONObject import org.json.JSONObject
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -25,7 +25,7 @@ internal abstract class MangaReaderParser(
domain: String, domain: String,
pageSize: Int, pageSize: Int,
searchPageSize: Int, searchPageSize: Int,
) : PagedMangaParser(context, source, pageSize, searchPageSize), Interceptor { ) : LegacyPagedMangaParser(context, source, pageSize, searchPageSize), Interceptor {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -2,7 +2,7 @@ package org.koitharu.kotatsu.parsers.site.mangaworld
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -14,7 +14,7 @@ internal abstract class MangaWorldParser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 16, pageSize: Int = 16,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of( override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.POPULARITY, SortOrder.POPULARITY,

@ -4,7 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -16,7 +16,7 @@ internal abstract class MmrcmsParser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 20, pageSize: Int = 20,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.nepnep
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.network.UserAgents
@ -20,7 +20,7 @@ internal abstract class NepnepParser(
context: MangaLoaderContext, context: MangaLoaderContext,
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
) : MangaParser(context, source) { ) : LegacyMangaParser(context, source) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.parsers.site.onemanga package org.koitharu.kotatsu.parsers.site.onemanga
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -11,7 +11,7 @@ internal abstract class OneMangaParser(
context: MangaLoaderContext, context: MangaLoaderContext,
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
) : SinglePageMangaParser(context, source) { ) : LegacySinglePageMangaParser(context, source) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -3,7 +3,7 @@ package org.koitharu.kotatsu.parsers.site.otakusanctuary
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -16,7 +16,7 @@ internal abstract class OtakuSanctuaryParser(
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
pageSize: Int = 32, pageSize: Int = 32,
) : PagedMangaParser(context, source, pageSize) { ) : LegacyPagedMangaParser(context, source, pageSize) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

@ -4,7 +4,7 @@ import kotlinx.coroutines.coroutineScope
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@ -19,7 +19,7 @@ internal abstract class PizzaReaderParser(
context: MangaLoaderContext, context: MangaLoaderContext,
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
) : SinglePageMangaParser(context, source) { ) : LegacySinglePageMangaParser(context, source) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save