Global refactoring: partial migrate to ListFilterOptions/ListFilterCapabilities

master
Koitharu 2 years ago
parent c5d3a7b0c1
commit 5030548500
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -64,6 +64,8 @@ All functions in `MangaParser` class are documented. Pay attention to some pecul
find issues during unit testing. find issues during unit testing.
- If your source website (or it's api) uses pages for pagination instead of offset you should extend `PagedMangaParser` - If your source website (or it's api) uses pages for pagination instead of offset you should extend `PagedMangaParser`
instead of `MangaParser`. instead of `MangaParser`.
- If your source website (or it's api) do not provide pagination (has only one page of content) you should extend
`SinglePageMangaParser` instead of `MangaParser` nor `PagedMangaParser.
- Your parser may also implement the `Interceptor` interface for additional manipulation of all network requests and/or - Your parser may also implement the `Interceptor` interface for additional manipulation of all network requests and/or
responses, including image loading. responses, including image loading.

@ -32,103 +32,35 @@ abstract class MangaParser @InternalParsersApi constructor(
* For better performance use [EnumSet] for more than one item. * For better performance use [EnumSet] for more than one item.
*/ */
@Deprecated("") @Deprecated("")
open val availableStates: Set<MangaState> internal open val availableStates: Set<MangaState>
get() = emptySet() get() = emptySet()
open val filterCapabilities: MangaListFilterCapabilities open val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities( get() = MangaListFilterCapabilities(
isMultipleTagsSupported = isMultipleTagsSupported, isMultipleTagsSupported = isMultipleTagsSupported,
isTagsExclusionSupported = isTagsExclusionSupported, isTagsExclusionSupported = isTagsExclusionSupported,
isSearchSupported = isSearchSupported, isSearchSupported = true,
isSearchWithFiltersSupported = searchSupportedWithMultipleFilters, isSearchWithFiltersSupported = false,
isYearSupported = isSearchYearSupported,
isYearRangeSupported = isSearchYearRangeSupported,
isSourceLocaleSupported = isSearchOriginalLanguages,
) )
/**
* Supported [ContentRating] variants for filtering. May be empty.
*
* For better performance use [EnumSet] for more than one item.
*/
@Deprecated("Use getListFilterCapabilities instead")
open val availableContentRating: Set<ContentRating>
get() = emptySet()
/**
* Supported [ContentType] variants for filtering. May be empty.
*
* For better performance use [EnumSet] for more than one item.
*/
@Deprecated("Use getListFilterCapabilities instead")
open val availableContentTypes: Set<ContentType>
get() = emptySet()
/**
* Supported [Demographic] variants for filtering. May be empty.
*
* For better performance use [EnumSet] for more than one item.
*/
@Deprecated("Use getListFilterCapabilities instead")
open val availableDemographics: Set<Demographic>
get() = emptySet()
/** /**
* Whether parser supports filtering by more than one tag * Whether parser supports filtering by more than one tag
*/ */
@Deprecated("Use getListFilterCapabilities instead") @Deprecated("Use getListFilterCapabilities instead")
open val isMultipleTagsSupported: Boolean = true internal open val isMultipleTagsSupported: Boolean = true
/** /**
* Whether parser supports tagsExclude field in filter * Whether parser supports tagsExclude field in filter
*/ */
@Deprecated("Use getListFilterCapabilities instead") @Deprecated("Use getListFilterCapabilities instead")
open val isTagsExclusionSupported: Boolean = false internal open val isTagsExclusionSupported: Boolean = false
/**
* Whether parser supports searching by string query using [MangaListFilter.Search]
*/
@Deprecated("Use getListFilterCapabilities instead")
open val isSearchSupported: Boolean = true
/**
* Whether parser supports searching by string query using [MangaListFilter.Advanced]
*/
@Deprecated("Use getListFilterCapabilities instead")
open val searchSupportedWithMultipleFilters: Boolean = false
/**
* Whether parser supports searching by year
*/
@Deprecated("Use getListFilterCapabilities instead")
open val isSearchYearSupported: Boolean = false
/**
* Whether parser supports searching by year range
*/
@Deprecated("Use getListFilterCapabilities instead")
open val isSearchYearRangeSupported: Boolean = false
/**
* Whether parser supports searching Original Languages
*/
@Deprecated("Use getListFilterCapabilities instead")
open val isSearchOriginalLanguages: Boolean = false
@Deprecated(
message = "Use availableSortOrders instead",
replaceWith = ReplaceWith("availableSortOrders"),
)
open val sortOrders: Set<SortOrder>
get() = availableSortOrders
val config by lazy { context.getConfig(source) } val config by lazy { context.getConfig(source) }
open val sourceLocale: Locale open val sourceLocale: Locale
get() = if (source.locale.isEmpty()) Locale.ROOT else Locale(source.locale) get() = if (source.locale.isEmpty()) Locale.ROOT else Locale(source.locale)
val isNsfwSource = source.contentType == ContentType.HENTAI protected val isNsfwSource = source.contentType == ContentType.HENTAI
/** /**
* Provide default domain and available alternatives, if any. * Provide default domain and available alternatives, if any.
@ -145,7 +77,7 @@ abstract class MangaParser @InternalParsersApi constructor(
.build() .build()
/** /**
* Used as fallback if value of `sortOrder` passed to [getList] is null * Used as fallback if value of `order` passed to [getList] is null
*/ */
open val defaultSortOrder: SortOrder open val defaultSortOrder: SortOrder
get() { get() {
@ -161,7 +93,7 @@ abstract class MangaParser @InternalParsersApi constructor(
* *
* @param offset starting from 0 and used for pagination. * @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. * Note than passed value may not be divisible by internal page size, so you should adjust it manually.
* @param order one of [availableSortOrders] or null for default value * @param order one of [availableSortOrders] or [defaultSortOrder] for default value
* @param filter is a set of filter rules * @param filter is a set of filter rules
*/ */
abstract suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> abstract suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga>
@ -182,39 +114,24 @@ abstract class MangaParser @InternalParsersApi constructor(
/** /**
* Fetch direct link to the page image. * Fetch direct link to the page image.
*/ */
open suspend fun getPageUrl(page: MangaPage): String = page.url.toAbsoluteUrl(domain) internal open suspend fun getPageUrl(page: MangaPage): String = page.url.toAbsoluteUrl(domain)
/** /**
* Fetch available tags (genres) for source * Fetch available tags (genres) for source
*/ */
@Deprecated("Use getListFilterDatasets instead") @Deprecated("Use getListFilterDatasets instead")
open suspend fun getAvailableTags(): Set<MangaTag> = emptySet() internal open suspend fun getAvailableTags(): Set<MangaTag> = emptySet()
open suspend fun getFilterOptions(): MangaListFilterOptions = coroutineScope { open suspend fun getFilterOptions(): MangaListFilterOptions = coroutineScope {
val tagsDeferred = async { getAvailableTags() } val tagsDeferred = async { getAvailableTags() }
val localesDeferred = async { getAvailableLocales() }
MangaListFilterOptions( MangaListFilterOptions(
availableTags = tagsDeferred.await(), availableTags = tagsDeferred.await(),
availableStates = availableStates, availableStates = availableStates,
availableContentRating = availableContentRating, availableContentRating = emptySet(),
availableContentTypes = availableContentTypes, availableLocales = emptySet(),
availableDemographics = availableDemographics,
availableLocales = localesDeferred.await(),
) )
} }
/**
* Fetch available locales for multilingual sources
*/
@Deprecated("Use getListFilterDatasets instead")
open suspend fun getAvailableLocales(): Set<Locale> = emptySet()
@Deprecated(
message = "Use getAvailableTags instead",
replaceWith = ReplaceWith("getAvailableTags()"),
)
suspend fun getTags(): Set<MangaTag> = getAvailableTags()
/** /**
* Parse favicons from the main page of the source`s website * Parse favicons from the main page of the source`s website
*/ */
@ -230,10 +147,4 @@ abstract class MangaParser @InternalParsersApi constructor(
open suspend fun getRelatedManga(seed: Manga): List<Manga> { open suspend fun getRelatedManga(seed: Manga): List<Manga> {
return RelatedMangaFinder(listOf(this)).invoke(seed) return RelatedMangaFinder(listOf(this)).invoke(seed)
} }
protected fun getParser(source: MangaParserSource) = if (this.source == source) {
this
} else {
context.newParserInstance(source)
}
} }

@ -1,154 +0,0 @@
package org.koitharu.kotatsu.parsers.model
import org.koitharu.kotatsu.parsers.MangaParser
import java.util.*
@Deprecated("Use MangaListFilterV2 instead")
sealed interface MangaListFilter {
fun isEmpty(): Boolean
val sortOrder: SortOrder?
fun isValid(parser: MangaParser): Boolean = when (this) {
is Advanced -> (sortOrder in parser.availableSortOrders) &&
(tags.size <= 1 || parser.isMultipleTagsSupported) &&
(tagsExclude.isEmpty() || parser.isTagsExclusionSupported) &&
(contentRating.isEmpty() || parser.availableContentRating.containsAll(contentRating)) &&
(states.isEmpty() || parser.availableStates.containsAll(states) &&
(parser.searchSupportedWithMultipleFilters) && (parser.isSearchOriginalLanguages) &&
(parser.isSearchYearSupported) && (parser.isSearchYearRangeSupported)) &&
(types.isEmpty() || parser.availableContentTypes.containsAll(types)) &&
(demographics.isEmpty() || parser.availableDemographics.containsAll(demographics))
is Search -> parser.isSearchSupported
}
data class Search(
@JvmField val query: String,
) : MangaListFilter {
override val sortOrder: SortOrder? = null
override fun isEmpty() = query.isBlank()
}
data class Advanced(
override val sortOrder: SortOrder,
@JvmField val tags: Set<MangaTag>,
@JvmField val tagsExclude: Set<MangaTag>,
@JvmField val locale: Locale?,
@JvmField val localeMangas: Locale?,
@JvmField val states: Set<MangaState>,
@JvmField val contentRating: Set<ContentRating>,
@JvmField val query: String?,
@JvmField val year: Int?,
@JvmField val yearFrom: Int?,
@JvmField val yearTo: Int?,
@JvmField val types: Set<ContentType>,
@JvmField val demographics: Set<Demographic>,
) : MangaListFilter {
override fun isEmpty(): Boolean =
tags.isEmpty() && tagsExclude.isEmpty() && locale == null && localeMangas == null && states.isEmpty() && contentRating.isEmpty() && query == null && year == null && yearFrom == null && yearTo == null && types.isEmpty() && demographics.isEmpty()
fun newBuilder() = Builder(sortOrder)
.tags(tags)
.tagsExclude(tagsExclude)
.locale(locale)
.localeMangas(localeMangas)
.states(states)
.contentRatings(contentRating)
.query(query)
.year(year)
.yearFrom(yearFrom)
.yearTo(yearTo)
.type(types)
.demographic(demographics)
class Builder(sortOrder: SortOrder) {
private var _sortOrder: SortOrder = sortOrder
private var _tags: Set<MangaTag>? = null
private var _tagsExclude: Set<MangaTag>? = null
private var _locale: Locale? = null
private var _localeMangas: Locale? = null
private var _states: Set<MangaState>? = null
private var _contentRating: Set<ContentRating>? = null
private var _query: String? = null
private var _year: Int? = null
private var _yearFrom: Int? = null
private var _yearTo: Int? = null
private var _types: Set<ContentType>? = null
private var _demographic: Set<Demographic>? = null
fun sortOrder(order: SortOrder) = apply {
_sortOrder = order
}
fun tags(tags: Set<MangaTag>?) = apply {
_tags = tags
}
fun tagsExclude(tags: Set<MangaTag>?) = apply {
_tagsExclude = tags
}
fun locale(locale: Locale?) = apply {
_locale = locale
}
fun localeMangas(localeMangas: Locale?) = apply {
_localeMangas = localeMangas
}
fun states(states: Set<MangaState>?) = apply {
_states = states
}
fun contentRatings(rating: Set<ContentRating>?) = apply {
_contentRating = rating
}
fun query(query: String?) = apply {
_query = query
}
fun year(year: Int?) = apply {
_year = year
}
fun yearFrom(yearFrom: Int?) = apply {
_yearFrom = yearFrom
}
fun yearTo(yearTo: Int?) = apply {
_yearTo = yearTo
}
fun type(type: Set<ContentType>?) = apply {
_types = type
}
fun demographic(demographic: Set<Demographic>?) = apply {
_demographic = demographic
}
fun build() = Advanced(
sortOrder = _sortOrder,
tags = _tags.orEmpty(),
tagsExclude = _tagsExclude.orEmpty(),
locale = _locale,
localeMangas = _localeMangas,
states = _states.orEmpty(),
contentRating = _contentRating.orEmpty(),
query = _query,
year = _year,
yearFrom = _yearFrom,
yearTo = _yearTo,
types = _types.orEmpty(),
demographics = _demographic.orEmpty(),
)
}
}
}

@ -3,11 +3,48 @@ package org.koitharu.kotatsu.parsers.model
import org.koitharu.kotatsu.parsers.InternalParsersApi import org.koitharu.kotatsu.parsers.InternalParsersApi
data class MangaListFilterCapabilities @InternalParsersApi constructor( data class MangaListFilterCapabilities @InternalParsersApi constructor(
/**
* Whether parser supports filtering by more than one tag
* @see [MangaListFilterV2.tags]
* @see [MangaListFilterOptions.availableTags]
*/
val isMultipleTagsSupported: Boolean, val isMultipleTagsSupported: Boolean,
/**
* Whether parser supports tagsExclude field in filter
* @see [MangaListFilterV2.tagsExclude]
* @see [MangaListFilterOptions.availableTags]
*/
val isTagsExclusionSupported: Boolean, val isTagsExclusionSupported: Boolean,
/**
* Whether parser supports searching by string query
* @see [MangaListFilterV2.query]
*/
val isSearchSupported: Boolean, val isSearchSupported: Boolean,
/**
* Whether parser supports searching by string query combined within other filters
*/
val isSearchWithFiltersSupported: Boolean, val isSearchWithFiltersSupported: Boolean,
/**
* Whether parser supports searching/filtering by year
* @see [MangaListFilterV2.year]
*/
val isYearSupported: Boolean = false, val isYearSupported: Boolean = false,
/**
* Whether parser supports searching by year range
* @see [MangaListFilterV2.yearFrom] and [MangaListFilterV2.yearTo]
*/
val isYearRangeSupported: Boolean = false, val isYearRangeSupported: Boolean = false,
val isSourceLocaleSupported: Boolean = false,
/**
* Whether parser supports searching Original Languages
* @see [MangaListFilterV2.originalLocale]
* @see [MangaListFilterOptions.availableLocales]
*/
val isOriginalLocaleSupported: Boolean = false,
) )

@ -4,10 +4,42 @@ import org.koitharu.kotatsu.parsers.InternalParsersApi
import java.util.* import java.util.*
data class MangaListFilterOptions @InternalParsersApi constructor( data class MangaListFilterOptions @InternalParsersApi constructor(
/**
* Available tags (genres)
*/
val availableTags: Set<MangaTag>, val availableTags: Set<MangaTag>,
/**
* Supported [MangaState] variants for filtering. May be empty.
*
* For better performance use [EnumSet] for more than one item.
*/
val availableStates: Set<MangaState> = emptySet(), val availableStates: Set<MangaState> = emptySet(),
/**
* Supported [ContentRating] variants for filtering. May be empty.
*
* For better performance use [EnumSet] for more than one item.
*/
val availableContentRating: Set<ContentRating> = emptySet(), val availableContentRating: Set<ContentRating> = emptySet(),
/**
* Supported [ContentType] variants for filtering. May be empty.
*
* For better performance use [EnumSet] for more than one item.
*/
val availableContentTypes: Set<ContentType> = emptySet(), val availableContentTypes: Set<ContentType> = emptySet(),
/**
* Supported [Demographic] variants for filtering. May be empty.
*
* For better performance use [EnumSet] for more than one item.
*/
val availableDemographics: Set<Demographic> = emptySet(), val availableDemographics: Set<Demographic> = emptySet(),
/**
* Supported content locales for multilingual sources
*/
val availableLocales: Set<Locale> = emptySet(), val availableLocales: Set<Locale> = emptySet(),
) )

@ -7,7 +7,7 @@ data class MangaListFilterV2(
@JvmField val tags: Set<MangaTag> = emptySet(), @JvmField val tags: Set<MangaTag> = emptySet(),
@JvmField val tagsExclude: Set<MangaTag> = emptySet(), @JvmField val tagsExclude: Set<MangaTag> = emptySet(),
@JvmField val locale: Locale? = null, @JvmField val locale: Locale? = null,
@JvmField val sourceLocale: Locale? = null, @JvmField val originalLocale: Locale? = null,
@JvmField val states: Set<MangaState> = emptySet(), @JvmField val states: Set<MangaState> = emptySet(),
@JvmField val contentRating: Set<ContentRating> = emptySet(), @JvmField val contentRating: Set<ContentRating> = emptySet(),
@JvmField val types: Set<ContentType> = emptySet(), @JvmField val types: Set<ContentType> = emptySet(),
@ -20,7 +20,7 @@ data class MangaListFilterV2(
fun isEmpty(): Boolean = tags.isEmpty() && fun isEmpty(): Boolean = tags.isEmpty() &&
tagsExclude.isEmpty() && tagsExclude.isEmpty() &&
locale == null && locale == null &&
sourceLocale == null && originalLocale == null &&
states.isEmpty() && states.isEmpty() &&
contentRating.isEmpty() && contentRating.isEmpty() &&
query == null && query == null &&

@ -68,7 +68,7 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -44,7 +44,7 @@ internal class ComickFunParser(context: MangaLoaderContext) :
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = true, isYearRangeSupported = true,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -58,7 +58,7 @@ internal class ExHentaiParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override val isAuthorized: Boolean override val isAuthorized: Boolean

@ -70,14 +70,27 @@ class HitomiLaParser(context: MangaLoaderContext) : MangaParser(context, MangaPa
Locale.JAPANESE to "japanese", Locale.JAPANESE to "japanese",
) )
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true,
isTagsExclusionSupported = false,
isSearchSupported = true,
isSearchWithFiltersSupported = false,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(),
availableStates = emptySet(),
availableContentRating = emptySet(),
availableLocales = localeMap.keys,
)
private fun Locale?.getSiteLang(): String = when (this) { private fun Locale?.getSiteLang(): String = when (this) {
null -> "all" null -> "all"
else -> localeMap[this] ?: "all" else -> localeMap[this] ?: "all"
} }
override suspend fun getAvailableLocales(): Set<Locale> = localeMap.keys private suspend fun fetchAvailableTags(): Set<MangaTag> = coroutineScope {
override suspend fun getAvailableTags(): Set<MangaTag> = coroutineScope {
('a'..'z').map { alphabet -> ('a'..'z').map { alphabet ->
async { async {
val doc = webClient.httpGet("https://$domain/alltags-$alphabet.html").parseHtml() val doc = webClient.httpGet("https://$domain/alltags-$alphabet.html").parseHtml()

@ -22,6 +22,21 @@ internal class ImHentai(context: MangaLoaderContext) :
override val configKeyDomain = ConfigKey.Domain("imhentai.xxx") override val configKeyDomain = ConfigKey.Domain("imhentai.xxx")
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true,
isTagsExclusionSupported = false,
isSearchSupported = true,
isSearchWithFiltersSupported = false,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(),
availableLocales = setOf(
Locale.ENGLISH, Locale.JAPANESE, Locale("es"), Locale.FRENCH, Locale("kr"), Locale.GERMAN, Locale("ru"),
),
)
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys) super.onCreateConfig(keys)
keys.add(userAgentKey) keys.add(userAgentKey)
@ -90,7 +105,7 @@ internal class ImHentai(context: MangaLoaderContext) :
//Tags are deliberately reduced because there are too many and this slows down the application. //Tags are deliberately reduced because there are too many and this slows down the application.
//only the most popular ones are taken. //only the most popular ones are taken.
override suspend fun getAvailableTags(): Set<MangaTag> { private suspend fun fetchAvailableTags(): Set<MangaTag> {
return coroutineScope { return coroutineScope {
(1..3).map { page -> (1..3).map { page ->
async { getTags(page) } async { getTags(page) }
@ -113,10 +128,6 @@ internal class ImHentai(context: MangaLoaderContext) :
) )
} }
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
Locale.ENGLISH, Locale.JAPANESE, Locale("es"), Locale.FRENCH, Locale("kr"), Locale.GERMAN, Locale("ru"),
)
override suspend fun getDetails(manga: Manga): Manga = coroutineScope { override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
val fullUrl = manga.url.toAbsoluteUrl(domain) val fullUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml() val doc = webClient.httpGet(fullUrl).parseHtml()

@ -41,7 +41,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
isSearchWithFiltersSupported = true, isSearchWithFiltersSupported = true,
isYearSupported = true, isYearSupported = true,
isYearRangeSupported = true, isYearRangeSupported = true,
isSourceLocaleSupported = true, isOriginalLocaleSupported = true,
) )
override val availableSortOrders: EnumSet<SortOrder> = EnumSet.allOf(SortOrder::class.java) override val availableSortOrders: EnumSet<SortOrder> = EnumSet.allOf(SortOrder::class.java)
@ -150,7 +150,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
append(it.language) append(it.language)
} }
filter.sourceLocale?.let { filter.originalLocale?.let {
append("&originalLanguage[]=") append("&originalLanguage[]=")
append(it.language) append(it.language)
} }

@ -79,7 +79,7 @@ internal abstract class MangaFireParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -29,7 +29,7 @@ internal class MangaPark(context: MangaLoaderContext) :
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -75,7 +75,7 @@ class MangaReaderToParser(context: MangaLoaderContext) : PagedMangaParser(contex
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -48,7 +48,7 @@ internal abstract class NineMangaParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -42,7 +42,7 @@ internal class NineNineNineHentaiParser(context: MangaLoaderContext) :
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -48,7 +48,7 @@ internal abstract class AnimeBootstrapParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -29,7 +29,7 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -33,7 +33,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -35,7 +35,7 @@ internal abstract class CupFoxParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -35,7 +35,7 @@ internal class AsuraScansParser(context: MangaLoaderContext) :
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -30,7 +30,7 @@ internal class BeeToon(context: MangaLoaderContext) :
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -20,17 +20,27 @@ internal class FlixScansOrg(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.FLIXSCANSORG, 18) { PagedMangaParser(context, MangaParserSource.FLIXSCANSORG, 18) {
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
override val availableContentRating: Set<ContentRating> = EnumSet.of(ContentRating.ADULT)
override val configKeyDomain = ConfigKey.Domain("flixscans.org") override val configKeyDomain = ConfigKey.Domain("flixscans.org")
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true,
isTagsExclusionSupported = false,
isSearchSupported = false,
isSearchWithFiltersSupported = false,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(),
availableStates = EnumSet.allOf(MangaState::class.java),
availableContentRating = EnumSet.of(ContentRating.ADULT),
)
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys) super.onCreateConfig(keys)
keys.add(userAgentKey) keys.add(userAgentKey)
} }
override val isSearchSupported = false
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
val json = when { val json = when {
!filter.query.isNullOrEmpty() -> { !filter.query.isNullOrEmpty() -> {
@ -101,7 +111,7 @@ internal class FlixScansOrg(context: MangaLoaderContext) :
} }
} }
override suspend fun getAvailableTags(): Set<MangaTag> { private suspend fun fetchAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/search/advance").parseHtml() val doc = webClient.httpGet("https://$domain/search/advance").parseHtml()
val json = JSONArray(doc.requireElementById("__NUXT_DATA__").data()) val json = JSONArray(doc.requireElementById("__NUXT_DATA__").data())
val tagsList = json.getJSONArray(3).toString().replace("[", "").replace("]", "").split(",") val tagsList = json.getJSONArray(3).toString().replace("[", "").replace("]", "").split(",")

@ -38,7 +38,7 @@ internal class Mangaowl(context: MangaLoaderContext) :
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -34,7 +34,7 @@ internal class TempleScanEsp(context: MangaLoaderContext) :
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -29,8 +29,6 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
private val chapterDateFormat = SimpleDateFormat("yyyy-MM-dd", sourceLocale) private val chapterDateFormat = SimpleDateFormat("yyyy-MM-dd", sourceLocale)
override val availableContentRating: Set<ContentRating> = EnumSet.of(ContentRating.SAFE, ContentRating.ADULT)
override val availableSortOrders: Set<SortOrder> = EnumSet.of( override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL,
SortOrder.ALPHABETICAL_DESC, SortOrder.ALPHABETICAL_DESC,
@ -40,6 +38,20 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
SortOrder.RATING, SortOrder.RATING,
) )
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true,
isTagsExclusionSupported = false,
isSearchSupported = true,
isSearchWithFiltersSupported = false,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(),
availableStates = emptySet(),
availableContentRating = EnumSet.of(ContentRating.SAFE, ContentRating.ADULT),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")
@ -283,7 +295,7 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
} }
override suspend fun getAvailableTags(): Set<MangaTag> { private suspend fun fetchAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/library", getRequestHeaders()).parseHtml() val doc = webClient.httpGet("https://$domain/library", getRequestHeaders()).parseHtml()
val elements = doc.body().select("div#books-genders > div > div") val elements = doc.body().select("div#books-genders > div > div")
return elements.mapNotNullToSet { element -> return elements.mapNotNullToSet { element ->

@ -47,7 +47,7 @@ internal abstract class FmreaderParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
init { init {

@ -40,7 +40,7 @@ internal abstract class FoolSlideParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
init { init {

@ -5,7 +5,6 @@ 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.PagedMangaParser
import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.SinglePageMangaParser
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.*
@ -25,13 +24,25 @@ internal class FuryoSociety(context: MangaLoaderContext) :
override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP)
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = false,
isTagsExclusionSupported = false,
isSearchSupported = false,
isSearchWithFiltersSupported = false,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = emptySet(),
availableStates = emptySet(),
availableContentRating = emptySet(),
)
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys) super.onCreateConfig(keys)
keys.add(userAgentKey) keys.add(userAgentKey)
} }
override val isSearchSupported = false
override suspend fun getFavicons(): Favicons { override suspend fun getFavicons(): Favicons {
return Favicons( return Favicons(
listOf( listOf(
@ -80,10 +91,6 @@ internal class FuryoSociety(context: MangaLoaderContext) :
} }
} }
override suspend fun getAvailableTags(): Set<MangaTag> = emptySet()
override suspend fun getDetails(manga: Manga): Manga = coroutineScope { override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
val fullUrl = manga.url.toAbsoluteUrl(domain) val fullUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml() val doc = webClient.httpGet(fullUrl).parseHtml()
@ -95,7 +102,6 @@ internal class FuryoSociety(context: MangaLoaderContext) :
) )
} }
private fun getChapters(doc: Document): List<MangaChapter> { private fun getChapters(doc: Document): List<MangaChapter> {
return doc.body().select("div.list.fs-chapter-list div.element").mapChapters(reversed = true) { i, div -> return doc.body().select("div.list.fs-chapter-list div.element").mapChapters(reversed = true) { i, div ->
val a = div.selectFirstOrThrow("div.title a") val a = div.selectFirstOrThrow("div.title a")

@ -35,7 +35,7 @@ internal class LugnicaScans(context: MangaLoaderContext) :
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -35,7 +35,7 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -32,7 +32,7 @@ internal class ScantradUnion(context: MangaLoaderContext) :
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -38,7 +38,7 @@ internal abstract class GalleryAdultsParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -14,6 +14,7 @@ import java.util.*
@MangaSourceParser("ASMHENTAI", "AsmHentai", type = ContentType.HENTAI) @MangaSourceParser("ASMHENTAI", "AsmHentai", type = ContentType.HENTAI)
internal class AsmHentai(context: MangaLoaderContext) : internal class AsmHentai(context: MangaLoaderContext) :
GalleryAdultsParser(context, MangaParserSource.ASMHENTAI, "asmhentai.com") { GalleryAdultsParser(context, MangaParserSource.ASMHENTAI, "asmhentai.com") {
override val selectGallery = ".preview_item" override val selectGallery = ".preview_item"
override val selectGalleryLink = ".image a" override val selectGalleryLink = ".image a"
override val selectGalleryImg = ".image img" override val selectGalleryImg = ".image img"
@ -21,11 +22,13 @@ internal class AsmHentai(context: MangaLoaderContext) :
override val selectAuthor = "div.tags:contains(Artists:) .tag_list a span.tag" override val selectAuthor = "div.tags:contains(Artists:) .tag_list a span.tag"
override val idImg = "fimg" override val idImg = "fimg"
override suspend fun getAvailableLocales(): Set<Locale> = setOf( override suspend fun getFilterOptions() = super.getFilterOptions().copy(
Locale.ENGLISH, availableLocales = setOf(
Locale.JAPANESE, Locale.ENGLISH,
Locale.CHINESE, Locale.JAPANESE,
Locale("tr"), Locale.CHINESE,
Locale("tr"),
),
) )
override fun Element.parseTags() = select("a").mapToSet { override fun Element.parseTags() = select("a").mapToSet {

@ -21,10 +21,12 @@ internal class DoujinDesuUk(context: MangaLoaderContext) :
override val selectLanguageChapter = "div.tag-container:contains(Languages) a" override val selectLanguageChapter = "div.tag-container:contains(Languages) a"
override val idImg = "image-container" override val idImg = "image-container"
override suspend fun getAvailableLocales(): Set<Locale> = setOf( override suspend fun getFilterOptions() = super.getFilterOptions().copy(
Locale.ENGLISH, availableLocales = setOf(
Locale.JAPANESE, Locale.ENGLISH,
Locale.CHINESE, Locale.JAPANESE,
Locale.CHINESE,
),
) )
override fun parseMangaList(doc: Document): List<Manga> { override fun parseMangaList(doc: Document): List<Manga> {

@ -23,18 +23,23 @@ internal class Hentai3(context: MangaLoaderContext) :
override val selectUrlChapter = "#main-cover a" override val selectUrlChapter = "#main-cover a"
override val idImg = ".js-main-img" override val idImg = ".js-main-img"
override val isMultipleTagsSupported = true override val filterCapabilities: MangaListFilterCapabilities
get() = super.filterCapabilities.copy(
isMultipleTagsSupported = true,
)
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
override suspend fun getAvailableLocales(): Set<Locale> = setOf( override suspend fun getFilterOptions() = super.getFilterOptions().copy(
Locale.ENGLISH, availableLocales = setOf(
Locale.FRENCH, Locale.ENGLISH,
Locale.JAPANESE, Locale.FRENCH,
Locale("es"), Locale.JAPANESE,
Locale("ru"), Locale("es"),
Locale.ITALIAN, Locale("ru"),
Locale("pt"), Locale.ITALIAN,
Locale("pt"),
),
) )
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {

@ -24,6 +24,20 @@ internal class HentaiEnvy(context: MangaLoaderContext) :
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
override suspend fun getFilterOptions() = super.getFilterOptions().copy(
availableLocales = setOf(
Locale.ENGLISH,
Locale.FRENCH,
Locale.JAPANESE,
Locale.CHINESE,
Locale("es"),
Locale("ru"),
Locale("ko"),
Locale.GERMAN,
Locale("pt"),
),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")
@ -63,16 +77,4 @@ internal class HentaiEnvy(context: MangaLoaderContext) :
} }
return parseMangaList(webClient.httpGet(url).parseHtml()) return parseMangaList(webClient.httpGet(url).parseHtml())
} }
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
Locale.ENGLISH,
Locale.FRENCH,
Locale.JAPANESE,
Locale.CHINESE,
Locale("es"),
Locale("ru"),
Locale("ko"),
Locale.GERMAN,
Locale("pt"),
)
} }

@ -21,25 +21,32 @@ internal class HentaiForce(context: MangaLoaderContext) :
override val selectLanguageChapter = "div.tag-container:contains(Languages:) a" override val selectLanguageChapter = "div.tag-container:contains(Languages:) a"
override val idImg = ".gallery-reader-img-wrapper img" override val idImg = ".gallery-reader-img-wrapper img"
override val isMultipleTagsSupported = true override val filterCapabilities: MangaListFilterCapabilities
get() = super.filterCapabilities.copy(
isMultipleTagsSupported = true,
)
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
override suspend fun getAvailableLocales(): Set<Locale> = setOf( override suspend fun getFilterOptions(): MangaListFilterOptions {
Locale.ENGLISH, return super.getFilterOptions().copy(
Locale.FRENCH, availableLocales = setOf(
Locale.JAPANESE, Locale.ENGLISH,
Locale.CHINESE, Locale.FRENCH,
Locale("es"), Locale.JAPANESE,
Locale("ru"), Locale.CHINESE,
Locale("ko"), Locale("es"),
Locale.GERMAN, Locale("ru"),
Locale("id"), Locale("ko"),
Locale.ITALIAN, Locale.GERMAN,
Locale("pt"), Locale("id"),
Locale("th"), Locale.ITALIAN,
Locale("vi"), Locale("pt"),
) Locale("th"),
Locale("vi"),
),
)
}
override suspend fun getPageUrl(page: MangaPage): String { override suspend fun getPageUrl(page: MangaPage): String {
val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml() val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml()

@ -4,6 +4,7 @@ import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaListFilterOptions
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
@ -20,15 +21,19 @@ internal class HentaiRox(context: MangaLoaderContext) :
override val selectAuthor = "li:contains(Artists:) span.item_name" override val selectAuthor = "li:contains(Artists:) span.item_name"
override val selectLanguageChapter = "li:contains(Languages:) .item_name" override val selectLanguageChapter = "li:contains(Languages:) .item_name"
override suspend fun getAvailableLocales(): Set<Locale> = setOf( override suspend fun getFilterOptions(): MangaListFilterOptions {
Locale.ENGLISH, return super.getFilterOptions().copy(
Locale.FRENCH, availableLocales = setOf(
Locale.JAPANESE, Locale.ENGLISH,
Locale("es"), Locale.FRENCH,
Locale("ru"), Locale.JAPANESE,
Locale("ko"), Locale("es"),
Locale.GERMAN, Locale("ru"),
) Locale("ko"),
Locale.GERMAN,
),
)
}
override fun Element.parseTags() = select("a.tag, .gallery_title a").mapToSet { override fun Element.parseTags() = select("a.tag, .gallery_title a").mapToSet {
val key = it.attr("href").removeSuffix('/').substringAfterLast('/') val key = it.attr("href").removeSuffix('/').substringAfterLast('/')

@ -24,7 +24,7 @@ internal abstract class GuyaParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -53,7 +53,7 @@ internal abstract class HeanCms(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -28,11 +28,17 @@ internal abstract class HeanCmsAlt(
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
override val isSearchSupported = false
protected open val listUrl = "/comics" protected open val listUrl = "/comics"
protected open val datePattern = "MMMM d, yyyy" protected open val datePattern = "MMMM d, yyyy"
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = false,
isTagsExclusionSupported = false,
isSearchSupported = false,
isSearchWithFiltersSupported = false,
)
init { init {
paginator.firstPage = 1 paginator.firstPage = 1
searchPaginator.firstPage = 1 searchPaginator.firstPage = 1
@ -41,6 +47,12 @@ internal abstract class HeanCmsAlt(
protected open val selectManga = "div.grid.grid-cols-2 div:not([class]):contains(M)" protected open val selectManga = "div.grid.grid-cols-2 div:not([class]):contains(M)"
protected open val selectMangaTitle = "h5" protected open val selectMangaTitle = "h5"
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = emptySet(),
availableStates = emptySet(),
availableContentRating = emptySet(),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")
@ -75,8 +87,6 @@ internal abstract class HeanCmsAlt(
} }
} }
override suspend fun getAvailableTags(): Set<MangaTag> = emptySet()
protected open val selectDesc = "div.description-container" protected open val selectDesc = "div.description-container"
protected open val selectAlt = "div.series-alternative-names" protected open val selectAlt = "div.series-alternative-names"
protected open val selectChapter = "ul.MuiList-root a" protected open val selectChapter = "ul.MuiList-root a"

@ -35,15 +35,17 @@ internal abstract class HotComicsParser(
protected open val onePage = false protected open val onePage = false
protected open val isSearchSupported: Boolean = true
override val filterCapabilities: MangaListFilterCapabilities override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities( get() = MangaListFilterCapabilities(
isMultipleTagsSupported = false, isMultipleTagsSupported = false,
isTagsExclusionSupported = false, isTagsExclusionSupported = false,
isSearchSupported = true, isSearchSupported = isSearchSupported,
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -36,7 +36,7 @@ internal abstract class IkenParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -35,7 +35,7 @@ internal abstract class MadaraParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -25,9 +25,10 @@ internal class Manga18Fx(context: MangaLoaderContext) :
searchPaginator.firstPage = 1 searchPaginator.firstPage = 1
} }
override val availableContentRating: Set<ContentRating> = emptySet() override suspend fun getFilterOptions() = super.getFilterOptions().copy(
availableStates = emptySet(),
override val availableStates: Set<MangaState> get() = emptySet() availableContentRating = emptySet(),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
val url = buildString { val url = buildString {

@ -28,9 +28,10 @@ internal class Manhwa18Cc(context: MangaLoaderContext) :
searchPaginator.firstPage = 1 searchPaginator.firstPage = 1
} }
override val availableContentRating: Set<ContentRating> = emptySet() override suspend fun getFilterOptions() = super.getFilterOptions().copy(
availableStates = emptySet(),
override val availableStates: Set<MangaState> get() = emptySet() availableContentRating = emptySet(),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
val url = buildString { val url = buildString {

@ -1,10 +1,6 @@
package org.koitharu.kotatsu.parsers.site.madara.en package org.koitharu.kotatsu.parsers.site.madara.en
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.*
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import okhttp3.Headers import okhttp3.Headers
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
@ -16,18 +12,25 @@ import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.EnumSet import java.util.*
@MangaSourceParser("HENTAIMANGA", "HentaiManga", "en", ContentType.HENTAI) @MangaSourceParser("HENTAIMANGA", "HentaiManga", "en", ContentType.HENTAI)
internal class HentaiManga(context: MangaLoaderContext) : internal class HentaiManga(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.HENTAIMANGA, "hentaimanga.me", 36) { MadaraParser(context, MangaParserSource.HENTAIMANGA, "hentaimanga.me", 36) {
override val postReq = true override val postReq = true
override val withoutAjax = true override val withoutAjax = true
override val isTagsExclusionSupported = false
override val availableSortOrders: Set<SortOrder> = override val availableSortOrders: Set<SortOrder> =
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.RATING) EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.RATING)
override val availableStates: Set<MangaState> = emptySet()
override val availableContentRating: Set<ContentRating> = emptySet() override val filterCapabilities: MangaListFilterCapabilities
get() = super.filterCapabilities.copy(
isMultipleTagsSupported = false,
)
override suspend fun getFilterOptions() = super.getFilterOptions().copy(
availableStates = emptySet(),
availableContentRating = emptySet(),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
val pages = page + 1 val pages = page + 1

@ -20,14 +20,17 @@ internal class IsekaiScan(context: MangaLoaderContext) :
SortOrder.POPULARITY, SortOrder.POPULARITY,
SortOrder.UPDATED, SortOrder.UPDATED,
) )
override val availableContentRating: Set<ContentRating> = emptySet()
override val availableStates: Set<MangaState> = emptySet()
init { init {
paginator.firstPage = 1 paginator.firstPage = 1
searchPaginator.firstPage = 1 searchPaginator.firstPage = 1
} }
override suspend fun getFilterOptions() = super.getFilterOptions().copy(
availableStates = emptySet(),
availableContentRating = emptySet(),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")

@ -23,15 +23,16 @@ internal class MangaDass(context: MangaLoaderContext) :
override val selectChapter = "li.a-h" override val selectChapter = "li.a-h"
override val selectDesc = "div.ss-manga" override val selectDesc = "div.ss-manga"
override val availableStates: Set<MangaState> get() = emptySet()
override val availableContentRating: Set<ContentRating> = emptySet()
init { init {
paginator.firstPage = 1 paginator.firstPage = 1
searchPaginator.firstPage = 1 searchPaginator.firstPage = 1
} }
override suspend fun getFilterOptions() = super.getFilterOptions().copy(
availableStates = emptySet(),
availableContentRating = emptySet(),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")

@ -17,13 +17,20 @@ internal class MangaDna(context: MangaLoaderContext) :
override val datePattern = "dd MMM yyyy" override val datePattern = "dd MMM yyyy"
override val withoutAjax = true override val withoutAjax = true
override val isTagsExclusionSupported = false
override val availableSortOrders: Set<SortOrder> = override val availableSortOrders: Set<SortOrder> =
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.RATING) EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.RATING)
override val selectDesc = "div.dsct" override val selectDesc = "div.dsct"
override val selectChapter = "li.a-h" override val selectChapter = "li.a-h"
override val availableStates: Set<MangaState> = emptySet()
override val availableContentRating: Set<ContentRating> = emptySet() override val filterCapabilities: MangaListFilterCapabilities
get() = super.filterCapabilities.copy(
isTagsExclusionSupported = false,
)
override suspend fun getFilterOptions() = super.getFilterOptions().copy(
availableStates = emptySet(),
availableContentRating = emptySet(),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
val url = buildString { val url = buildString {

@ -23,15 +23,18 @@ internal class MangaPure(context: MangaLoaderContext) :
SortOrder.UPDATED, SortOrder.UPDATED,
) )
override val availableStates: Set<MangaState> = emptySet()
override val availableContentRating: Set<ContentRating> = emptySet()
init { init {
paginator.firstPage = 1 paginator.firstPage = 1
searchPaginator.firstPage = 1 searchPaginator.firstPage = 1
} }
override suspend fun getFilterOptions(): MangaListFilterOptions {
return super.getFilterOptions().copy(
availableStates = emptySet(),
availableContentRating = emptySet(),
)
}
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")

@ -14,12 +14,12 @@ internal class Manhwaz(context: MangaLoaderContext) :
override val listUrl = "genre/manhwa" override val listUrl = "genre/manhwa"
override val tagPrefix = "genre/" override val tagPrefix = "genre/"
override val withoutAjax = true override val withoutAjax = true
override val isTagsExclusionSupported = false
override val selectTestAsync = "div.list-chapter" override val selectTestAsync = "div.list-chapter"
override val availableStates: Set<MangaState> = emptySet() override val filterCapabilities: MangaListFilterCapabilities
get() = super.filterCapabilities.copy(
override val availableContentRating: Set<ContentRating> = emptySet() isTagsExclusionSupported = false,
)
override val availableSortOrders: Set<SortOrder> = EnumSet.of( override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED, SortOrder.UPDATED,
@ -33,6 +33,11 @@ internal class Manhwaz(context: MangaLoaderContext) :
searchPaginator.firstPage = 1 searchPaginator.firstPage = 1
} }
override suspend fun getFilterOptions() = super.getFilterOptions().copy(
availableStates = emptySet(),
availableContentRating = emptySet(),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")

@ -24,13 +24,21 @@ internal class ManyToon(context: MangaLoaderContext) :
override val listUrl = "comic/" override val listUrl = "comic/"
override val postReq = true override val postReq = true
override val withoutAjax = true override val withoutAjax = true
override val isTagsExclusionSupported = false
override val availableSortOrders: Set<SortOrder> = override val availableSortOrders: Set<SortOrder> =
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.RATING) EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.RATING)
override val availableStates: Set<MangaState> = emptySet() override val filterCapabilities: MangaListFilterCapabilities
get() = super.filterCapabilities.copy(
isTagsExclusionSupported = false,
)
override val availableContentRating: Set<ContentRating> = emptySet() override suspend fun getFilterOptions(): MangaListFilterOptions {
return super.getFilterOptions().copy(
availableStates = emptySet(),
availableContentRating = emptySet(),
)
}
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
val pages = page + 1 val pages = page + 1

@ -10,16 +10,20 @@ import java.util.*
@MangaSourceParser("DRAGONTRANSLATION", "Dragon Translation", "es") @MangaSourceParser("DRAGONTRANSLATION", "Dragon Translation", "es")
internal class DragonTranslationParser(context: MangaLoaderContext) : internal class DragonTranslationParser(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.DRAGONTRANSLATION, "dragontranslation.net", 30) { MadaraParser(context, MangaParserSource.DRAGONTRANSLATION, "dragontranslation.net", 30) {
override val selectPage = "div#chapter_imgs img" override val selectPage = "div#chapter_imgs img"
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
override val availableStates: Set<MangaState> = emptySet()
override val availableContentRating: Set<ContentRating> = emptySet()
init { init {
paginator.firstPage = 1 paginator.firstPage = 1
searchPaginator.firstPage = 1 searchPaginator.firstPage = 1
} }
override suspend fun getFilterOptions() = super.getFilterOptions().copy(
availableStates = emptySet(),
availableContentRating = emptySet(),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")

@ -16,16 +16,23 @@ internal class TmoManga(context: MangaLoaderContext) :
override val listUrl = "biblioteca/" override val listUrl = "biblioteca/"
override val selectGenre = "div.summary-content a.tags_manga" override val selectGenre = "div.summary-content a.tags_manga"
override val withoutAjax = true override val withoutAjax = true
override val isTagsExclusionSupported = false
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY)
override val availableStates: Set<MangaState> = emptySet()
override val availableContentRating: Set<ContentRating> = emptySet() override val filterCapabilities: MangaListFilterCapabilities
get() = super.filterCapabilities.copy(
isTagsExclusionSupported = false,
)
init { init {
paginator.firstPage = 1 paginator.firstPage = 1
searchPaginator.firstPage = 1 searchPaginator.firstPage = 1
} }
override suspend fun getFilterOptions() = super.getFilterOptions().copy(
availableStates = emptySet(),
availableContentRating = emptySet(),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")

@ -65,7 +65,7 @@ internal abstract class MadthemeParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -44,7 +44,7 @@ internal abstract class MangAdventureParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -51,7 +51,7 @@ internal abstract class MangaReaderParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -1,5 +1,7 @@
package org.koitharu.kotatsu.parsers.site.mangareader.en package org.koitharu.kotatsu.parsers.site.mangareader.en
import androidx.collection.ArrayMap
import kotlinx.coroutines.sync.withLock
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Request import okhttp3.Request
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
@ -25,11 +27,13 @@ internal class RizzComic(context: MangaLoaderContext) :
SortOrder.POPULARITY, SortOrder.POPULARITY,
SortOrder.ALPHABETICAL_DESC, SortOrder.ALPHABETICAL_DESC,
) )
override val availableStates: Set<MangaState> =
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED) override val filterCapabilities: MangaListFilterCapabilities
override val isMultipleTagsSupported = true get() = super.filterCapabilities.copy(
override val isSearchSupported = true isMultipleTagsSupported = true,
override val isTagsExclusionSupported = false isSearchSupported = true,
isTagsExclusionSupported = false,
)
private val filterUrl = "/Index/filter_series" private val filterUrl = "/Index/filter_series"
private val searchUrl = "/Index/live_search" private val searchUrl = "/Index/live_search"
@ -55,6 +59,10 @@ internal class RizzComic(context: MangaLoaderContext) :
return randomPartRegex.find(slug)?.groupValues?.get(1) ?: "" return randomPartRegex.find(slug)?.groupValues?.get(1) ?: ""
} }
override suspend fun getFilterOptions() = super.getFilterOptions().copy(
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
if (page > 1) { if (page > 1) {
return emptyList() return emptyList()
@ -149,13 +157,14 @@ internal class RizzComic(context: MangaLoaderContext) :
else -> "all" else -> "all"
} }
override suspend fun getAvailableTags(): Set<MangaTag> { override suspend fun getOrCreateTagMap(): Map<String, MangaTag> = mutex.withLock {
tagCache?.let { return@withLock it }
val url = "https://$domain/series" val url = "https://$domain/series"
val doc = webClient.httpGet(url).parseHtml() val doc = webClient.httpGet(url).parseHtml()
val genreElements = doc.select("input.genre-item") val genreElements = doc.select("input.genre-item")
return genreElements.mapNotNullToSet { element -> val genres = genreElements.mapNotNull { element ->
val id = element.attr("value") val id = element.attr("value")
val name = element.nextElementSibling()?.text() val name = element.nextElementSibling()?.text()
@ -169,5 +178,6 @@ internal class RizzComic(context: MangaLoaderContext) :
null null
} }
} }
genres.associateByTo(ArrayMap(genres.size)) { it.title }.also { tagCache = it }
} }
} }

@ -6,7 +6,6 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaListFilterV2 import org.koitharu.kotatsu.parsers.model.MangaListFilterV2
import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource

@ -5,7 +5,6 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaListFilterV2 import org.koitharu.kotatsu.parsers.model.MangaListFilterV2
import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource

@ -5,7 +5,6 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaListFilterV2 import org.koitharu.kotatsu.parsers.model.MangaListFilterV2
import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource

@ -20,8 +20,12 @@ internal class RevolutionScantrad(context: MangaLoaderContext) :
) { ) {
override val listUrl = "/series.html" override val listUrl = "/series.html"
override val datePattern = "yyyy" override val datePattern = "yyyy"
override val isTagsExclusionSupported = false
override val isSearchSupported = false override val filterCapabilities: MangaListFilterCapabilities
get() = super.filterCapabilities.copy(
isTagsExclusionSupported = false,
isSearchSupported = false,
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
if (page > 1) { if (page > 1) {

@ -72,7 +72,7 @@ internal abstract class MmrcmsParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -30,7 +30,7 @@ internal abstract class OneMangaParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -4,7 +4,6 @@ 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.PagedMangaParser
import org.koitharu.kotatsu.parsers.SinglePageMangaParser import org.koitharu.kotatsu.parsers.SinglePageMangaParser
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.*
@ -29,10 +28,20 @@ internal abstract class PizzaReaderParser(
} }
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL) override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
override val availableStates: Set<MangaState> =
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED) override val filterCapabilities: MangaListFilterCapabilities
override val availableContentRating: Set<ContentRating> = EnumSet.of(ContentRating.SAFE, ContentRating.ADULT) get() = MangaListFilterCapabilities(
override val isTagsExclusionSupported = true isMultipleTagsSupported = true,
isTagsExclusionSupported = true,
isSearchSupported = true,
isSearchWithFiltersSupported = false,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = emptySet(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED),
availableContentRating = EnumSet.of(ContentRating.SAFE, ContentRating.ADULT),
)
@JvmField @JvmField
protected val ongoing: Set<String> = hashSetOf( protected val ongoing: Set<String> = hashSetOf(
@ -198,9 +207,6 @@ internal abstract class PizzaReaderParser(
) )
} }
override suspend fun getAvailableTags(): Set<MangaTag> = emptySet()
override suspend fun getDetails(manga: Manga): Manga = coroutineScope { override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
val fullUrl = manga.url.toAbsoluteUrl(domain) val fullUrl = manga.url.toAbsoluteUrl(domain)
val json = webClient.httpGet(fullUrl).parseJson().getJSONObject("comic") val json = webClient.httpGet(fullUrl).parseJson().getJSONObject("comic")

@ -25,14 +25,25 @@ class LerManga(context: MangaLoaderContext) : PagedMangaParser(context, MangaPar
override val configKeyDomain = ConfigKey.Domain("lermanga.org") override val configKeyDomain = ConfigKey.Domain("lermanga.org")
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = false,
isTagsExclusionSupported = false,
isSearchSupported = false,
isSearchWithFiltersSupported = false,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(),
availableStates = emptySet(),
availableContentRating = emptySet(),
)
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys) super.onCreateConfig(keys)
keys.add(userAgentKey) keys.add(userAgentKey)
} }
override val isMultipleTagsSupported = false
override val isSearchSupported = false
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")
@ -97,7 +108,7 @@ class LerManga(context: MangaLoaderContext) : PagedMangaParser(context, MangaPar
} }
} }
override suspend fun getAvailableTags(): Set<MangaTag> { private suspend fun fetchAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain").parseHtml().requireElementById("menu-header") val doc = webClient.httpGet("https://$domain").parseHtml().requireElementById("menu-header")
return doc.select("#menu-item:contains(GÊNERO) ul li a").mapNotNullToSet { a -> return doc.select("#menu-item:contains(GÊNERO) ul li a").mapNotNullToSet { a ->
MangaTag( MangaTag(

@ -25,9 +25,19 @@ internal class LuratoonScansParser(context: MangaLoaderContext) :
override fun getRequestHeaders(): Headers = Headers.Builder().add("User-Agent", config[userAgentKey]).build() override fun getRequestHeaders(): Headers = Headers.Builder().add("User-Agent", config[userAgentKey]).build()
override val isSearchSupported = false override val filterCapabilities: MangaListFilterCapabilities
override val isTagsExclusionSupported = false get() = MangaListFilterCapabilities(
override val isMultipleTagsSupported = false isMultipleTagsSupported = false,
isTagsExclusionSupported = false,
isSearchSupported = false,
isSearchWithFiltersSupported = false,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = emptySet(),
availableStates = emptySet(),
availableContentRating = emptySet(),
)
override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List<Manga> { override suspend fun getList(order: SortOrder, filter: MangaListFilterV2): List<Manga> {
require(filter.query.isNullOrEmpty()) { ErrorMessages.SEARCH_NOT_SUPPORTED } require(filter.query.isNullOrEmpty()) { ErrorMessages.SEARCH_NOT_SUPPORTED }
@ -124,8 +134,6 @@ internal class LuratoonScansParser(context: MangaLoaderContext) :
} }
} }
override suspend fun getAvailableTags(): Set<MangaTag> = emptySet()
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request()) val response = chain.proceed(chain.request())
if (response.mimeType == "application/octet-stream") { if (response.mimeType == "application/octet-stream") {

@ -25,7 +25,7 @@ class OnePieceEx(context: MangaLoaderContext) : SinglePageMangaParser(context, M
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -26,7 +26,7 @@ class YugenMangas(context: MangaLoaderContext) : SinglePageMangaParser(context,
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -32,7 +32,7 @@ internal class AComics(context: MangaLoaderContext) :
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
init { init {

@ -19,6 +19,7 @@ import java.util.*
class MangaWtfParser( class MangaWtfParser(
context: MangaLoaderContext, context: MangaLoaderContext,
) : PagedMangaParser(context, MangaParserSource.MANGA_WTF, pageSize = 20) { ) : PagedMangaParser(context, MangaParserSource.MANGA_WTF, pageSize = 20) {
override val availableSortOrders: Set<SortOrder> = override val availableSortOrders: Set<SortOrder> =
EnumSet.of( EnumSet.of(
SortOrder.POPULARITY, SortOrder.POPULARITY,
@ -30,17 +31,24 @@ class MangaWtfParser(
@InternalParsersApi @InternalParsersApi
override val configKeyDomain = ConfigKey.Domain("manga.wtf") override val configKeyDomain = ConfigKey.Domain("manga.wtf")
override val isTagsExclusionSupported = true override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true,
isTagsExclusionSupported = true,
isSearchSupported = true,
isSearchWithFiltersSupported = false,
)
override val availableStates: Set<MangaState> = override suspend fun getFilterOptions() = MangaListFilterOptions(
EnumSet.of( availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(
MangaState.UPCOMING, MangaState.UPCOMING,
MangaState.PAUSED, MangaState.PAUSED,
MangaState.ONGOING, MangaState.ONGOING,
MangaState.FINISHED, MangaState.FINISHED,
) ),
availableContentRating = EnumSet.allOf(ContentRating::class.java),
override val availableContentRating: Set<ContentRating> = EnumSet.allOf(ContentRating::class.java) )
init { init {
paginator.firstPage = 0 paginator.firstPage = 0
@ -161,10 +169,8 @@ class MangaWtfParser(
} }
} }
override suspend fun getAvailableTags(): Set<MangaTag> { private suspend fun fetchAvailableTags(): Set<MangaTag> {
val url = val url = urlBuilder("api").addPathSegment("label")
urlBuilder("api")
.addPathSegment("label")
val json = webClient.httpGet(url.build()).parseJson() val json = webClient.httpGet(url.build()).parseJson()
return json.getJSONArray("content").mapJSONToSet { jo -> return json.getJSONArray("content").mapJSONToSet { jo ->
MangaTag( MangaTag(

@ -74,7 +74,7 @@ internal abstract class GroupleParser(
isSearchWithFiltersSupported = true, isSearchWithFiltersSupported = true,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = true, isYearRangeSupported = true,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -40,7 +40,7 @@ internal abstract class LibSocialParser(
isSearchWithFiltersSupported = true, isSearchWithFiltersSupported = true,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -30,7 +30,7 @@ internal abstract class ScanParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -29,7 +29,7 @@ class YaoiFlix(context: MangaLoaderContext) : PagedMangaParser(context, MangaPar
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -35,7 +35,7 @@ class YurinekoParser(context: MangaLoaderContext) : PagedMangaParser(context, Ma
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -72,7 +72,7 @@ internal abstract class WpComicsParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -6,7 +6,6 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.exception.NotFoundException import org.koitharu.kotatsu.parsers.exception.NotFoundException
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.MangaListFilterV2 import org.koitharu.kotatsu.parsers.model.MangaListFilterV2
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.MangaState import org.koitharu.kotatsu.parsers.model.MangaState

@ -6,7 +6,6 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.exception.NotFoundException import org.koitharu.kotatsu.parsers.exception.NotFoundException
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.MangaListFilterV2 import org.koitharu.kotatsu.parsers.model.MangaListFilterV2
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.MangaState import org.koitharu.kotatsu.parsers.model.MangaState

@ -42,7 +42,7 @@ internal abstract class ZeistMangaParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
@JvmField @JvmField

@ -46,7 +46,7 @@ internal abstract class ZMangaParser(
isSearchWithFiltersSupported = false, isSearchWithFiltersSupported = false,
isYearSupported = false, isYearSupported = false,
isYearRangeSupported = false, isYearRangeSupported = false,
isSourceLocaleSupported = false, isOriginalLocaleSupported = false,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(

@ -122,7 +122,7 @@ internal class MangaParserTest {
} }
val filter = MangaListFilterV2( val filter = MangaListFilterV2(
locale = locales.random(), locale = locales.random(),
sourceLocale = locales.random(), originalLocale = locales.random(),
) )
val list = parser.getList(offset = 0, order = parser.defaultSortOrder, filter) val list = parser.getList(offset = 0, order = parser.defaultSortOrder, filter)
checkMangaList(list, filter.locale.toString()) checkMangaList(list, filter.locale.toString())

Loading…
Cancel
Save