Partial migration to MangaListFilterV2

master
Koitharu 2 years ago
parent 9042074c50
commit 481ad02e01
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -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,104 +57,90 @@ 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)
}
filter.query?.let {
append("&title=")
append(filter.query.urlEncoded())
}
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")
}
}
} 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"
},
)
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")
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("")
}
}
}
} else append("&contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic")
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.demographics.forEach {
append("&publicationDemographic[]=")
append(
when (it) {
Demographic.SHOUNEN -> "shounen"
Demographic.SHOUJO -> "shoujo"
Demographic.SEINEN -> "seinen"
Demographic.JOSEI -> "josei"
Demographic.NONE -> "none"
},
)
}
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.locale?.let {
append("&availableTranslatedLanguage[]=")
append(it.language)
}
filter.demographics.forEach {
append("&publicationDemographic[]=")
append(
when (it) {
Demographic.SHOUNEN -> "shounen"
Demographic.SHOUJO -> "shoujo"
Demographic.SEINEN -> "seinen"
Demographic.JOSEI -> "josei"
Demographic.NONE -> "none"
},
)
}
filter.localeMangas?.let {
append("&originalLanguage[]=")
append(it.language)
}
filter.locale?.let {
append("&availableTranslatedLanguage[]=")
append(it.language)
}
filter.year?.let {
append("&year=")
append(filter.year)
}
}
filter.sourceLocale?.let {
append("&originalLanguage[]=")
append(it.language)
}
null -> {
append("&order[latestUploadedChapter]=desc")
}
if (filter.year != 0) {
append("&year=")
append(filter.year)
}
}
val json = webClient.httpGet(url).parseJson().getJSONArray("data")

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

Loading…
Cancel
Save