Partial migration to MangaListFilterV2
This commit is contained in:
@@ -169,7 +169,7 @@ abstract class MangaParser @InternalParsersApi constructor(
|
||||
@Deprecated(
|
||||
"Use getList with filter instead",
|
||||
ReplaceWith(
|
||||
"getList(offset, MangaListFilter.Search(query))",
|
||||
"getList(offset, SortOrder.RELEVANCE, MangaListFilterV2(query = query))",
|
||||
"org.koitharu.kotatsu.parsers.model.MangaListFilter",
|
||||
),
|
||||
)
|
||||
@@ -188,7 +188,7 @@ abstract class MangaParser @InternalParsersApi constructor(
|
||||
@Deprecated(
|
||||
"Use getList with filter instead",
|
||||
ReplaceWith(
|
||||
"getList(offset, MangaListFilter.Advanced(sortOrder, tags, null, emptySet()))",
|
||||
"getList(offset, sortOrder, MangaListFilterV2(tags = tags))",
|
||||
"org.koitharu.kotatsu.parsers.model.MangaListFilter",
|
||||
),
|
||||
)
|
||||
@@ -218,7 +218,13 @@ abstract class MangaParser @InternalParsersApi constructor(
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated(
|
||||
"Use getList with filter instead",
|
||||
ReplaceWith(
|
||||
"getList(offset, filter.sortOrder, filter)",
|
||||
"org.koitharu.kotatsu.parsers.model.MangaListFilter",
|
||||
),
|
||||
)
|
||||
open suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
return when (filter) {
|
||||
is MangaListFilter.Advanced -> getList(
|
||||
@@ -247,6 +253,31 @@ abstract class MangaParser @InternalParsersApi constructor(
|
||||
}
|
||||
}
|
||||
|
||||
open suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2) = getList(
|
||||
offset = offset,
|
||||
filter = when {
|
||||
filter.query.isNullOrEmpty() -> MangaListFilter.Advanced(
|
||||
sortOrder = order,
|
||||
tags = filter.tags,
|
||||
tagsExclude = filter.tagsExclude,
|
||||
locale = filter.locale,
|
||||
localeMangas = filter.sourceLocale,
|
||||
states = filter.states,
|
||||
contentRating = filter.contentRating,
|
||||
query = filter.query,
|
||||
year = filter.year,
|
||||
yearFrom = filter.yearFrom,
|
||||
yearTo = filter.yearTo,
|
||||
types = filter.types,
|
||||
demographics = filter.demographics,
|
||||
)
|
||||
|
||||
else -> MangaListFilter.Search(
|
||||
query = filter.query,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* Parse details for [Manga]: chapters list, description, large cover, etc.
|
||||
* Must return the same manga, may change any fields excepts id, url and source
|
||||
|
||||
@@ -3,6 +3,7 @@ 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
|
||||
|
||||
@@ -2,121 +2,37 @@ package org.koitharu.kotatsu.parsers.model
|
||||
|
||||
import java.util.*
|
||||
|
||||
@Suppress("DataClassPrivateConstructor")
|
||||
data class MangaListFilterV2 private constructor(
|
||||
@JvmField 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,
|
||||
data class MangaListFilterV2(
|
||||
@JvmField val query: String? = null,
|
||||
@JvmField val tags: Set<MangaTag> = emptySet(),
|
||||
@JvmField val tagsExclude: Set<MangaTag> = emptySet(),
|
||||
@JvmField val locale: Locale? = null,
|
||||
@JvmField val sourceLocale: Locale? = null,
|
||||
@JvmField val states: Set<MangaState> = emptySet(),
|
||||
@JvmField val contentRating: Set<ContentRating> = emptySet(),
|
||||
@JvmField val types: Set<ContentType> = emptySet(),
|
||||
@JvmField val demographics: Set<Demographic> = emptySet(),
|
||||
@JvmField val year: Int = 0,
|
||||
@JvmField val yearFrom: Int = 0,
|
||||
@JvmField val yearTo: Int = 0,
|
||||
) {
|
||||
|
||||
fun isEmpty(): Boolean = tags.isEmpty() &&
|
||||
tagsExclude.isEmpty() &&
|
||||
locale == null &&
|
||||
localeMangas == null &&
|
||||
sourceLocale == null &&
|
||||
states.isEmpty() &&
|
||||
contentRating.isEmpty() &&
|
||||
query == null &&
|
||||
year == 0 &&
|
||||
yearFrom == 0 &&
|
||||
yearTo == 0
|
||||
|
||||
fun newBuilder() = Builder()
|
||||
.sortOrder(sortOrder)
|
||||
.tags(tags)
|
||||
.tagsExclude(tagsExclude)
|
||||
.locale(locale)
|
||||
.localeMangas(localeMangas)
|
||||
.states(states)
|
||||
.contentRatings(contentRating)
|
||||
.searchQuery(query)
|
||||
.year(year)
|
||||
.yearFrom(yearFrom)
|
||||
.yearTo(yearTo)
|
||||
|
||||
class Builder {
|
||||
|
||||
private var _sortOrder: SortOrder? = null
|
||||
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 = 0
|
||||
private var _yearFrom: Int = 0
|
||||
private var _yearTo: Int = 0
|
||||
|
||||
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 searchQuery(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 build() = MangaListFilterV2(
|
||||
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,
|
||||
)
|
||||
}
|
||||
yearTo == 0 &&
|
||||
types.isEmpty() &&
|
||||
demographics.isEmpty()
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
val EMPTY = Builder().build()
|
||||
val EMPTY = MangaListFilterV2()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
|
||||
override val isSearchYearSupported: Boolean = true
|
||||
override val isSearchOriginalLanguages: Boolean = true
|
||||
|
||||
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilterV2): List<Manga> {
|
||||
val domain = domain
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
@@ -57,105 +57,91 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
|
||||
append("&offset=")
|
||||
append(offset)
|
||||
append("&includes[]=cover_art&includes[]=author&includes[]=artist")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("&title=")
|
||||
append(filter.query)
|
||||
append("&contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic&order[relevance]=desc")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
filter.query?.let {
|
||||
append("&title=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
filter.query.let {
|
||||
append("&title=")
|
||||
append(filter.query)
|
||||
}
|
||||
filter.tags.forEach {
|
||||
append("&includedTags[]=")
|
||||
append(it.key)
|
||||
}
|
||||
|
||||
filter.tags.forEach {
|
||||
append("&includedTags[]=")
|
||||
append(it.key)
|
||||
}
|
||||
filter.tagsExclude.forEach {
|
||||
append("&excludedTags[]=")
|
||||
append(it.key)
|
||||
}
|
||||
|
||||
filter.tagsExclude.forEach {
|
||||
append("&excludedTags[]=")
|
||||
append(it.key)
|
||||
}
|
||||
if (filter.contentRating.isNotEmpty()) {
|
||||
filter.contentRating.forEach {
|
||||
when (it) {
|
||||
ContentRating.SAFE -> append("&contentRating[]=safe")
|
||||
ContentRating.SUGGESTIVE -> append("&contentRating[]=suggestive&contentRating[]=erotica")
|
||||
ContentRating.ADULT -> append("&contentRating[]=pornographic")
|
||||
|
||||
if (filter.contentRating.isNotEmpty()) {
|
||||
filter.contentRating.forEach {
|
||||
when (it) {
|
||||
ContentRating.SAFE -> append("&contentRating[]=safe")
|
||||
ContentRating.SUGGESTIVE -> append("&contentRating[]=suggestive&contentRating[]=erotica")
|
||||
ContentRating.ADULT -> append("&contentRating[]=pornographic")
|
||||
|
||||
}
|
||||
}
|
||||
} else append("&contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic")
|
||||
|
||||
append("&order")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.UPDATED -> "[latestUploadedChapter]=desc"
|
||||
SortOrder.UPDATED_ASC -> "[latestUploadedChapter]=asc"
|
||||
SortOrder.RATING -> "[rating]=desc"
|
||||
SortOrder.RATING_ASC -> "[rating]=asc"
|
||||
SortOrder.ALPHABETICAL -> "[title]=asc"
|
||||
SortOrder.ALPHABETICAL_DESC -> "[title]=desc"
|
||||
SortOrder.NEWEST -> "[year]=desc"
|
||||
SortOrder.NEWEST_ASC -> "[year]=asc"
|
||||
SortOrder.POPULARITY -> "[followedCount]=desc"
|
||||
SortOrder.POPULARITY_ASC -> "[followedCount]=asc"
|
||||
SortOrder.ADDED -> "[createdAt]=desc"
|
||||
SortOrder.ADDED_ASC -> "[createdAt]=asc"
|
||||
SortOrder.RELEVANCE -> "&order[relevance]=desc"
|
||||
else -> "[latestUploadedChapter]=desc"
|
||||
},
|
||||
)
|
||||
|
||||
filter.states.forEach {
|
||||
append("&status[]=")
|
||||
when (it) {
|
||||
MangaState.ONGOING -> append("ongoing")
|
||||
MangaState.FINISHED -> append("completed")
|
||||
MangaState.ABANDONED -> append("cancelled")
|
||||
MangaState.PAUSED -> append("hiatus")
|
||||
else -> append("")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
filter.localeMangas?.let {
|
||||
append("&originalLanguage[]=")
|
||||
append(it.language)
|
||||
}
|
||||
|
||||
filter.year?.let {
|
||||
append("&year=")
|
||||
append(filter.year)
|
||||
}
|
||||
}
|
||||
} else append("&contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic")
|
||||
|
||||
null -> {
|
||||
append("&order[latestUploadedChapter]=desc")
|
||||
append("&order")
|
||||
append(
|
||||
when (order) {
|
||||
SortOrder.UPDATED -> "[latestUploadedChapter]=desc"
|
||||
SortOrder.UPDATED_ASC -> "[latestUploadedChapter]=asc"
|
||||
SortOrder.RATING -> "[rating]=desc"
|
||||
SortOrder.RATING_ASC -> "[rating]=asc"
|
||||
SortOrder.ALPHABETICAL -> "[title]=asc"
|
||||
SortOrder.ALPHABETICAL_DESC -> "[title]=desc"
|
||||
SortOrder.NEWEST -> "[year]=desc"
|
||||
SortOrder.NEWEST_ASC -> "[year]=asc"
|
||||
SortOrder.POPULARITY -> "[followedCount]=desc"
|
||||
SortOrder.POPULARITY_ASC -> "[followedCount]=asc"
|
||||
SortOrder.ADDED -> "[createdAt]=desc"
|
||||
SortOrder.ADDED_ASC -> "[createdAt]=asc"
|
||||
SortOrder.RELEVANCE -> "&order[relevance]=desc"
|
||||
else -> "[latestUploadedChapter]=desc"
|
||||
},
|
||||
)
|
||||
|
||||
filter.states.forEach {
|
||||
append("&status[]=")
|
||||
when (it) {
|
||||
MangaState.ONGOING -> append("ongoing")
|
||||
MangaState.FINISHED -> append("completed")
|
||||
MangaState.ABANDONED -> append("cancelled")
|
||||
MangaState.PAUSED -> append("hiatus")
|
||||
else -> append("")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
filter.sourceLocale?.let {
|
||||
append("&originalLanguage[]=")
|
||||
append(it.language)
|
||||
}
|
||||
|
||||
if (filter.year != 0) {
|
||||
append("&year=")
|
||||
append(filter.year)
|
||||
}
|
||||
}
|
||||
val json = webClient.httpGet(url).parseJson().getJSONArray("data")
|
||||
return json.mapJSON { jo ->
|
||||
|
||||
@@ -52,27 +52,14 @@ internal class MangaParserTest {
|
||||
val parser = context.newParserInstance(source)
|
||||
val subject = parser.getList(
|
||||
offset = 0,
|
||||
filter = MangaListFilter.Advanced(
|
||||
sortOrder = SortOrder.POPULARITY,
|
||||
tags = emptySet(),
|
||||
locale = null,
|
||||
localeMangas = null,
|
||||
states = emptySet(),
|
||||
tagsExclude = emptySet(),
|
||||
contentRating = emptySet(),
|
||||
query = null,
|
||||
year = null,
|
||||
yearFrom = null,
|
||||
yearTo = null,
|
||||
types = emptySet(),
|
||||
demographics = emptySet(),
|
||||
),
|
||||
order = SortOrder.POPULARITY,
|
||||
filter = MangaListFilterV2.EMPTY,
|
||||
).minByOrNull {
|
||||
it.title.length
|
||||
} ?: error("No manga found")
|
||||
val query = subject.title
|
||||
check(query.isNotBlank()) { "Manga title '$query' is blank" }
|
||||
val list = parser.getList(0, MangaListFilter.Search(query))
|
||||
val list = parser.getList(0, SortOrder.RELEVANCE, MangaListFilterV2(query = query))
|
||||
assert(list.isNotEmpty()) { "Empty search results by \"$query\"" }
|
||||
assert(list.singleOrNull { it.url == subject.url && it.id == subject.id } != null) {
|
||||
"Single subject '${subject.title} (${subject.publicUrl})' not found in search results"
|
||||
@@ -102,9 +89,8 @@ internal class MangaParserTest {
|
||||
val tag = tags.last()
|
||||
val list = parser.getList(
|
||||
offset = 0,
|
||||
MangaListFilter.Advanced.Builder(parser.defaultSortOrder)
|
||||
.tags(setOf(tag))
|
||||
.build(),
|
||||
order = parser.defaultSortOrder,
|
||||
filter = MangaListFilterV2(tags = setOf(tag)),
|
||||
)
|
||||
checkMangaList(list, "${tag.title} (${tag.key})")
|
||||
assert(list.all { it.source == source })
|
||||
@@ -117,10 +103,8 @@ internal class MangaParserTest {
|
||||
if (!parser.isMultipleTagsSupported) return@runTest
|
||||
val tags = parser.getAvailableTags().shuffled().take(2).toSet()
|
||||
|
||||
val filter = MangaListFilter.Advanced.Builder(parser.availableSortOrders.first())
|
||||
.tags(tags)
|
||||
.build()
|
||||
val list = parser.getList(0, filter)
|
||||
val filter = MangaListFilterV2(tags = tags)
|
||||
val list = parser.getList(0, parser.defaultSortOrder, filter)
|
||||
checkMangaList(list, "${tags.joinToString { it.title }} (${tags.joinToString { it.key }})")
|
||||
assert(list.all { it.source == source })
|
||||
}
|
||||
@@ -133,22 +117,11 @@ internal class MangaParserTest {
|
||||
if (locales.isEmpty()) {
|
||||
return@runTest
|
||||
}
|
||||
val filter = MangaListFilter.Advanced(
|
||||
sortOrder = parser.availableSortOrders.first(),
|
||||
tags = setOf(),
|
||||
tagsExclude = setOf(),
|
||||
val filter = MangaListFilterV2(
|
||||
locale = locales.random(),
|
||||
localeMangas = locales.random(),
|
||||
states = setOf(),
|
||||
contentRating = setOf(),
|
||||
query = null,
|
||||
year = null,
|
||||
yearFrom = null,
|
||||
yearTo = null,
|
||||
types = emptySet(),
|
||||
demographics = emptySet(),
|
||||
sourceLocale = locales.random(),
|
||||
)
|
||||
val list = parser.getList(offset = 0, filter)
|
||||
val list = parser.getList(offset = 0, order = parser.defaultSortOrder, filter)
|
||||
checkMangaList(list, filter.locale.toString())
|
||||
assert(list.all { it.source == source })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user