Merge branch 'filter-type-and-demographic' into feature/advanced_filter

master
Koitharu 2 years ago
commit 821e51ff7d
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -5,7 +5,11 @@ object ErrorMessages {
const val FILTER_MULTIPLE_STATES_NOT_SUPPORTED = "Multiple states are not supported by this source"
const val FILTER_MULTIPLE_GENRES_NOT_SUPPORTED = "Multiple genres are not supported by this source"
const val FILTER_MULTIPLE_CONTENT_RATING_NOT_SUPPORTED =
"Multiple Content Rating are not supported by this source"
"Multiple Content ratings are not supported by this source"
const val FILTER_MULTIPLE_CONTENT_TYPES_NOT_SUPPORTED =
"Multiple Content types are not supported by this source"
const val FILTER_MULTIPLE_DEMOGRAPHICS_NOT_SUPPORTED =
"Multiple Demographics are not supported by this source"
const val FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED =
"Filtering by both genres and locale is not supported by this source"
const val FILTER_BOTH_STATES_GENRES_NOT_SUPPORTED =

@ -34,9 +34,30 @@ abstract class MangaParser @InternalParsersApi constructor(
get() = emptySet()
/**
* Supported [ContentRating] variants for filtering. May be empty.
*
* For better performance use [EnumSet] for more than one item.
*/
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.
*/
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.
*/
open val availableDemographics: Set<Demographic>
get() = emptySet()
/**
* Whether parser supports filtering by more than one tag
*/
@ -191,6 +212,8 @@ abstract class MangaParser @InternalParsersApi constructor(
year = null,
yearFrom = null,
yearTo = null,
types = emptySet(),
demographics = emptySet(),
),
)
}

@ -7,6 +7,10 @@ enum class ContentType {
*/
MANGA,
MANHWA,
MANHUA,
/**
* Use this if the source provides mostly nsfw content.
*/
@ -17,6 +21,8 @@ enum class ContentType {
*/
COMICS,
NOVEL,
/**
* Use this type if no other suits your needs. For example, for an indie manga
*/

@ -0,0 +1,9 @@
package org.koitharu.kotatsu.parsers.model
enum class Demographic {
SHOUNEN,
SHOUJO,
SEINEN,
JOSEI,
NONE,
}

@ -15,8 +15,10 @@ sealed interface MangaListFilter {
(tagsExclude.isEmpty() || parser.isTagsExclusionSupported) &&
(contentRating.isEmpty() || parser.availableContentRating.containsAll(contentRating)) &&
(states.isEmpty() || parser.availableStates.containsAll(states) &&
(parser.searchSupportedWithMultipleFilters) &&(parser.isSearchOriginalLanguages) &&
(parser.isSearchYearSupported) && (parser.isSearchYearRangeSupported))
(parser.searchSupportedWithMultipleFilters) && (parser.isSearchOriginalLanguages) &&
(parser.isSearchYearSupported) && (parser.isSearchYearRangeSupported)) &&
(types.isEmpty() || parser.availableContentTypes.containsAll(types)) &&
(demographics.isEmpty() || parser.availableDemographics.containsAll(demographics))
is Search -> parser.isSearchSupported
}
@ -42,10 +44,12 @@ sealed interface MangaListFilter {
@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
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)
@ -58,6 +62,8 @@ sealed interface MangaListFilter {
.year(year)
.yearFrom(yearFrom)
.yearTo(yearTo)
.type(types)
.demographic(demographics)
class Builder(sortOrder: SortOrder) {
@ -72,6 +78,8 @@ sealed interface MangaListFilter {
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
@ -117,7 +125,13 @@ sealed interface MangaListFilter {
_yearTo = yearTo
}
fun type(type: Set<ContentType>?) = apply {
_types = type
}
fun demographic(demographic: Set<Demographic>?) = apply {
_demographic = demographic
}
fun build() = Advanced(
sortOrder = _sortOrder,
@ -131,6 +145,8 @@ sealed interface MangaListFilter {
year = _year,
yearFrom = _yearFrom,
yearTo = _yearTo,
types = _types.orEmpty(),
demographics = _demographic.orEmpty(),
)
}
}

@ -38,6 +38,15 @@ internal class ComickFunParser(context: MangaLoaderContext) :
SortOrder.NEWEST,
)
override val availableContentTypes: Set<ContentType> = EnumSet.of(
ContentType.MANGA,
ContentType.MANHWA,
ContentType.MANHUA,
ContentType.OTHER,
)
override val availableDemographics: Set<Demographic> = EnumSet.allOf(Demographic::class.java)
override val availableStates: Set<MangaState> =
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED)
@ -58,10 +67,6 @@ internal class ComickFunParser(context: MangaLoaderContext) :
url.addQueryParameter("q", filter.query)
}
null -> {
url.addQueryParameter("sort", "view")
}
is MangaListFilter.Advanced -> {
filter.tags.forEach {
@ -97,12 +102,42 @@ internal class ComickFunParser(context: MangaLoaderContext) :
}
filter.yearFrom?.let {
url.addQueryParameter("from", filter.yearFrom.toString() )
url.addQueryParameter("from", filter.yearFrom.toString())
}
filter.yearTo?.let {
url.addQueryParameter("to", filter.yearTo.toString() )
url.addQueryParameter("to", filter.yearTo.toString())
}
filter.types.forEach {
url.addQueryParameter(
"country",
when (it) {
ContentType.MANGA -> "jp"
ContentType.MANHWA -> "kr"
ContentType.MANHUA -> "cn"
ContentType.OTHER -> "others"
else -> ""
},
)
}
filter.demographics.forEach {
url.addQueryParameter(
"demographic",
when (it) {
Demographic.SHOUNEN -> "1"
Demographic.SHOUJO -> "2"
Demographic.SEINEN -> "3"
Demographic.JOSEI -> "4"
Demographic.NONE -> "5"
},
)
}
}
null -> {
url.addQueryParameter("sort", "uploaded")
}
}
val ja = webClient.httpGet(url.build()).parseJsonArray()

@ -37,6 +37,8 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
override val availableContentRating: Set<ContentRating> = EnumSet.allOf(ContentRating::class.java)
override val availableDemographics: Set<Demographic> = EnumSet.allOf(Demographic::class.java)
override val availableStates: Set<MangaState> =
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED)
@ -108,6 +110,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
SortOrder.RELEVANCE -> "&order[relevance]=desc"
},
)
filter.states.forEach {
append("&status[]=")
when (it) {
@ -118,6 +121,20 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
else -> append("")
}
}
filter.demographics.forEach {
append("&publicationDemographic[]=")
append(
when (it) {
Demographic.SHOUNEN -> "shounen"
Demographic.SHOUJO -> "shoujo"
Demographic.SEINEN -> "seinen"
Demographic.JOSEI -> "josei"
Demographic.NONE -> "none"
},
)
}
filter.locale?.let {
append("&availableTranslatedLanguage[]=")
append(it.language)

@ -46,6 +46,16 @@ internal abstract class MangaReaderParser(
override val availableStates: Set<MangaState>
get() = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED)
override val availableContentTypes: Set<ContentType>
get() = EnumSet.of(
ContentType.MANGA,
ContentType.MANHWA,
ContentType.MANHUA,
ContentType.COMICS,
ContentType.NOVEL,
)
override val isTagsExclusionSupported = true
protected open val listUrl = "/manga"
@ -110,6 +120,20 @@ internal abstract class MangaReaderParser(
}
}
filter.types.oneOrThrowIfMany()?.let {
append("&type=")
append(
when (it) {
ContentType.MANGA -> "manga"
ContentType.MANHWA -> "manhwa"
ContentType.MANHUA -> "manhua"
ContentType.COMICS -> "comic"
ContentType.NOVEL -> "novel"
else -> ""
},
)
}
append("&page=")
append(page.toString())
}

@ -51,30 +51,34 @@ fun Element.parseFailed(message: String? = null): Nothing {
}
@InternalParsersApi
fun Set<MangaTag>?.oneOrThrowIfMany(): MangaTag? {
return when {
isNullOrEmpty() -> null
size == 1 -> first()
else -> throw IllegalArgumentException(ErrorMessages.FILTER_MULTIPLE_GENRES_NOT_SUPPORTED)
}
}
fun Set<MangaTag>?.oneOrThrowIfMany(): MangaTag? = oneOrThrowIfMany(
ErrorMessages.FILTER_MULTIPLE_GENRES_NOT_SUPPORTED,
)
@InternalParsersApi
fun Set<MangaState>?.oneOrThrowIfMany(): MangaState? {
return when {
isNullOrEmpty() -> null
size == 1 -> first()
else -> throw IllegalArgumentException(ErrorMessages.FILTER_MULTIPLE_STATES_NOT_SUPPORTED)
}
}
fun Set<MangaState>?.oneOrThrowIfMany(): MangaState? = oneOrThrowIfMany(
ErrorMessages.FILTER_MULTIPLE_STATES_NOT_SUPPORTED,
)
@InternalParsersApi
fun Set<ContentType>?.oneOrThrowIfMany(): ContentType? = oneOrThrowIfMany(
ErrorMessages.FILTER_MULTIPLE_CONTENT_TYPES_NOT_SUPPORTED,
)
@InternalParsersApi
fun Set<ContentRating>?.oneOrThrowIfMany(): ContentRating? {
return when {
fun Set<Demographic>?.oneOrThrowIfMany(): Demographic? = oneOrThrowIfMany(
ErrorMessages.FILTER_MULTIPLE_DEMOGRAPHICS_NOT_SUPPORTED,
)
@InternalParsersApi
fun Set<ContentRating>?.oneOrThrowIfMany(): ContentRating? = oneOrThrowIfMany(
ErrorMessages.FILTER_MULTIPLE_CONTENT_RATING_NOT_SUPPORTED,
)
private fun <T> Set<T>?.oneOrThrowIfMany(msg: String): T? = when {
isNullOrEmpty() -> null
size == 1 -> first()
else -> throw IllegalArgumentException(ErrorMessages.FILTER_MULTIPLE_CONTENT_RATING_NOT_SUPPORTED)
}
else -> throw IllegalArgumentException(msg)
}
val MangaParser.domain: String

@ -64,6 +64,8 @@ internal class MangaParserTest {
year = null,
yearFrom = null,
yearTo = null,
types = emptySet(),
demographics = emptySet(),
),
).minByOrNull {
it.title.length
@ -143,6 +145,8 @@ internal class MangaParserTest {
year = null,
yearFrom = null,
yearTo = null,
types = emptySet(),
demographics = emptySet(),
)
val list = parser.getList(offset = 0, filter)
checkMangaList(list, filter.locale.toString())

Loading…
Cancel
Save