From a7c19b49596625e0e837c6a2886f8ecf945bb3f1 Mon Sep 17 00:00:00 2001 From: ThePromidius Date: Tue, 20 Sep 2022 21:44:46 +0200 Subject: [PATCH] Kavita: Filter and search update (#13526) * Refactored code and removed hack to make search work - Added queryfield to MetadataDto.kt - Changed how we encode volumes in chapter_number in chapterFromVolume (tracking) * Implemented query in filter Removed PDFs, Epubs from format filter Added Year Release filter Added time to read Sort Refactored some variables Bumped version * Changelog and bump version --- src/all/kavita/CHANGELOG.md | 11 ++ src/all/kavita/build.gradle | 2 +- .../tachiyomi/extension/all/kavita/Kavita.kt | 166 ++++++++---------- .../extension/all/kavita/KavitaConstants.kt | 11 +- .../extension/all/kavita/dto/MangaDto.kt | 17 -- .../extension/all/kavita/dto/MetadataDto.kt | 4 +- 6 files changed, 93 insertions(+), 118 deletions(-) diff --git a/src/all/kavita/CHANGELOG.md b/src/all/kavita/CHANGELOG.md index 351690885..4a89901b9 100644 --- a/src/all/kavita/CHANGELOG.md +++ b/src/all/kavita/CHANGELOG.md @@ -1,3 +1,14 @@ +## 1.3.7 + +### Features + +* New Sort filter: Time to read +* New Filter: Year release filter + +### Fix +* Filters can now be used together with search +* Epub and pdfs no longer show in format filter (currently not supported) + ## 1.3.6 ### Fix diff --git a/src/all/kavita/build.gradle b/src/all/kavita/build.gradle index eadb5d163..86079c5da 100644 --- a/src/all/kavita/build.gradle +++ b/src/all/kavita/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Kavita' pkgNameSuffix = 'all.kavita' extClass = '.KavitaFactory' - extVersionCode = 6 + extVersionCode = 7 } dependencies { diff --git a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/Kavita.kt b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/Kavita.kt index 0fcee7cb6..474ef0773 100644 --- a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/Kavita.kt +++ b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/Kavita.kt @@ -21,10 +21,8 @@ import eu.kanade.tachiyomi.extension.all.kavita.dto.MetadataPeople import eu.kanade.tachiyomi.extension.all.kavita.dto.MetadataPubStatus import eu.kanade.tachiyomi.extension.all.kavita.dto.MetadataTag import eu.kanade.tachiyomi.extension.all.kavita.dto.PersonRole -import eu.kanade.tachiyomi.extension.all.kavita.dto.SearchResultsDto import eu.kanade.tachiyomi.extension.all.kavita.dto.SeriesDto import eu.kanade.tachiyomi.extension.all.kavita.dto.SeriesMetadataDto -import eu.kanade.tachiyomi.extension.all.kavita.dto.SeriesSearchDto import eu.kanade.tachiyomi.extension.all.kavita.dto.ServerInfoDto import eu.kanade.tachiyomi.extension.all.kavita.dto.VolumeDto import eu.kanade.tachiyomi.network.GET @@ -48,7 +46,6 @@ import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put import okhttp3.Dns import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient @@ -142,7 +139,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou return POST( "$apiUrl/series/all?pageNumber=$page&libraryId=0&pageSize=20", headersBuilder().build(), - buildFilterBody() + buildFilterBody(currentFilter) ) } @@ -176,71 +173,71 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou /** * SEARCH MANGA * **/ - private var isFilterOn = false // If any filter option is enabled this is true - private var toFilter = MetadataPayload() + + private var currentFilter: MetadataPayload = MetadataPayload() override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - toFilter = MetadataPayload() // need to reset it or will double - isFilterOn = false + val newFilter = MetadataPayload() // need to reset it or will double filters.forEach { filter -> when (filter) { is SortFilter -> { if (filter.state != null) { - toFilter.sorting = filter.state!!.index + 1 - toFilter.sorting_asc = filter.state!!.ascending - // Disabled until search is stable -// isFilterOn = false + newFilter.sorting = filter.state!!.index + 1 + newFilter.sorting_asc = filter.state!!.ascending } } is StatusFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.readStatus.add(content.name) - isFilterOn = true + newFilter.readStatus.add(content.name) + } + } + } + is ReleaseYearRangeGroup -> { + filter.state.forEach { content -> + if (content.state.isNotEmpty()) { + if (content.name == "Min") + newFilter.releaseYearRangeMin = content.state.toInt() + if (content.name == "Max") + newFilter.releaseYearRangeMax = content.state.toInt() } } } is GenreFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.genres.add(genresListMeta.find { it.title == content.name }!!.id) - isFilterOn = true + newFilter.genres.add(genresListMeta.find { it.title == content.name }!!.id) } } } is UserRating -> { - toFilter.userRating = filter.state - isFilterOn = true + newFilter.userRating = filter.state } is TagFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.tags.add(tagsListMeta.find { it.title == content.name }!!.id) - isFilterOn = true + newFilter.tags.add(tagsListMeta.find { it.title == content.name }!!.id) } } } is AgeRatingFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.ageRating.add(ageRatingsListMeta.find { it.title == content.name }!!.value) - isFilterOn = true + newFilter.ageRating.add(ageRatingsListMeta.find { it.title == content.name }!!.value) } } } is FormatsFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.formats.add(MangaFormat.valueOf(content.name).ordinal) - isFilterOn = true + newFilter.formats.add(MangaFormat.valueOf(content.name).ordinal) } } } is CollectionFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.collections.add(collectionsListMeta.find { it.title == content.name }!!.id) - isFilterOn = true + newFilter.collections.add(collectionsListMeta.find { it.title == content.name }!!.id) } } } @@ -248,16 +245,14 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou is LanguageFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.language.add(languagesListMeta.find { it.title == content.name }!!.isoCode) - isFilterOn = true + newFilter.language.add(languagesListMeta.find { it.title == content.name }!!.isoCode) } } } is LibrariesFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.libraries.add(libraryListMeta.find { it.name == content.name }!!.id) - isFilterOn = true + newFilter.libraries.add(libraryListMeta.find { it.name == content.name }!!.id) } } } @@ -265,8 +260,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou is PubStatusFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.pubStatus.add(pubStatusListMeta.find { it.title == content.name }!!.value) - isFilterOn = true + newFilter.pubStatus.add(pubStatusListMeta.find { it.title == content.name }!!.value) } } } @@ -274,116 +268,84 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou is WriterPeopleFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.peopleWriters.add(peopleListMeta.find { it.name == content.name }!!.id) - isFilterOn = true + newFilter.peopleWriters.add(peopleListMeta.find { it.name == content.name }!!.id) } } } is PencillerPeopleFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.peoplePenciller.add(peopleListMeta.find { it.name == content.name }!!.id) - isFilterOn = true + newFilter.peoplePenciller.add(peopleListMeta.find { it.name == content.name }!!.id) } } } is InkerPeopleFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.peopleInker.add(peopleListMeta.find { it.name == content.name }!!.id) - isFilterOn = true + newFilter.peopleInker.add(peopleListMeta.find { it.name == content.name }!!.id) } } } is ColoristPeopleFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.peoplePeoplecolorist.add(peopleListMeta.find { it.name == content.name }!!.id) - isFilterOn = true + newFilter.peoplePeoplecolorist.add(peopleListMeta.find { it.name == content.name }!!.id) } } } is LettererPeopleFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.peopleLetterer.add(peopleListMeta.find { it.name == content.name }!!.id) - isFilterOn = true + newFilter.peopleLetterer.add(peopleListMeta.find { it.name == content.name }!!.id) } } } is CoverArtistPeopleFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.peopleCoverArtist.add(peopleListMeta.find { it.name == content.name }!!.id) - isFilterOn = true + newFilter.peopleCoverArtist.add(peopleListMeta.find { it.name == content.name }!!.id) } } } is EditorPeopleFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.peopleEditor.add(peopleListMeta.find { it.name == content.name }!!.id) - isFilterOn = true + newFilter.peopleEditor.add(peopleListMeta.find { it.name == content.name }!!.id) } } } is PublisherPeopleFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.peoplePublisher.add(peopleListMeta.find { it.name == content.name }!!.id) - isFilterOn = true + newFilter.peoplePublisher.add(peopleListMeta.find { it.name == content.name }!!.id) } } } is CharacterPeopleFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.peopleCharacter.add(peopleListMeta.find { it.name == content.name }!!.id) - isFilterOn = true + newFilter.peopleCharacter.add(peopleListMeta.find { it.name == content.name }!!.id) } } } is TranslatorPeopleFilterGroup -> { filter.state.forEach { content -> if (content.state) { - toFilter.peopleTranslator.add(peopleListMeta.find { it.name == content.name }!!.id) - isFilterOn = true + newFilter.peopleTranslator.add(peopleListMeta.find { it.name == content.name }!!.id) } } } - else -> isFilterOn = false + else -> {} } } - if (query.isEmpty()) { - isFilterOn = true - return popularMangaRequest(page) - } else { - isFilterOn = false - val url = "$apiUrl/Library/search".toHttpUrl().newBuilder() - .addQueryParameter("queryString", query) - return GET(url.toString(), headers) - } + newFilter.seriesNameQuery = query + currentFilter = newFilter + return popularMangaRequest(page) } override fun searchMangaParse(response: Response): MangasPage { - if (isFilterOn) { - return popularMangaParse(response) - } else { - if (response.request.url.toString().contains("api/series/all")) - return popularMangaParse(response) - - val result = response.parseAs().series - val mangaList = result.map(::searchMangaFromObject) - return MangasPage(mangaList, false) - } - } - - private fun searchMangaFromObject(obj: SeriesSearchDto): SManga = SManga.create().apply { - title = obj.name - thumbnail_url = "$apiUrl/Image/series-cover?seriesId=${obj.seriesId}" - description = "None" - url = "$apiUrl/Series/${obj.seriesId}" + return popularMangaParse(response) } /** @@ -448,12 +410,12 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou private fun chapterFromObject(obj: ChapterDto): SChapter = SChapter.create().apply { url = obj.id.toString() - if (obj.number == "0" && obj.isSpecial) { + name = if (obj.number == "0" && obj.isSpecial) { // This is a special. Chapter name is special name - name = obj.range + obj.range } else { val cleanedName = obj.title.replaceFirst("^0+(?!$)".toRegex(), "") - name = "Chapter $cleanedName" + "Chapter $cleanedName" } date_upload = helper.parseDate(obj.created) chapter_number = obj.number.toFloat() @@ -487,7 +449,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou // Is a single-file volume // We encode the chapter number to support tracking name = "Volume ${volume.number}" - chapter_number = volume.number.toFloat() / 100 + chapter_number = volume.number.toFloat() / 10000 } } else { name = "Unhandled Else Volume ${volume.number}" @@ -579,7 +541,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou "Translator" ) - private class UserRating() : + private class UserRating : Filter.Select( "Minimum Rating", arrayOf( @@ -594,16 +556,21 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou private class SortFilter(sortables: Array) : Filter.Sort("Sort by", sortables, Selection(0, true)) - val sortableList = listOf( + private val sortableList = listOf( Pair("Sort name", 1), Pair("Created", 2), Pair("Last modified", 3), Pair("Item added", 4), + Pair("Time to Read", 5) ) + private class StatusFilter(name: String) : Filter.CheckBox(name, false) private class StatusFilterGroup(filters: List) : Filter.Group("Status", filters) + private class ReleaseYearRange(name: String) : Filter.Text(name) + private class ReleaseYearRangeGroup(filters: List) : + Filter.Group("Release Year", filters) private class GenreFilter(name: String) : Filter.CheckBox(name, false) private class GenreFilterGroup(genres: List) : Filter.Group("Genres", genres) @@ -637,7 +604,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou private class PeopleHeaderFilter(name: String) : Filter.Header(name) - private class PeopleSeparatorFilter() : + private class PeopleSeparatorFilter : Filter.Separator() private class WriterPeopleFilter(name: String) : Filter.CheckBox(name, false) @@ -713,6 +680,13 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou ) ) } + if (toggledFilters.contains("ReleaseYearRange")) { + filtersLoaded.add( + ReleaseYearRangeGroup( + listOf("Min", "Max").map { ReleaseYearRange(it) } + ) + ) + } if (genresListMeta.isNotEmpty() and toggledFilters.contains("Genres")) { filtersLoaded.add( @@ -736,8 +710,6 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou "Image", "Archive", "Unknown", - "Epub", - "Pdf" ).map { FormatFilter(it) } ) ) @@ -890,11 +862,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou .add("Content-Type", "application/json") .add("Authorization", "Bearer $jwtToken") } - private fun buildFilterBody(filter: MetadataPayload = toFilter): RequestBody { - var filter = filter - if (!isFilterOn and !filter.forceUseMetadataPayload) { - filter = MetadataPayload() - } + private fun buildFilterBody(filter: MetadataPayload): RequestBody { val formats = if (filter.formats.isEmpty()) { buildJsonArray { @@ -946,6 +914,14 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou put("isAscending", JsonPrimitive(filter.sorting_asc)) } ) + put("seriesNameQuery", filter.seriesNameQuery) + put( + "releaseYearRange", + buildJsonObject { + put("min", filter.releaseYearRangeMin) + put("max", filter.releaseYearRangeMax) + } + ) } return payload.toString().toRequestBody(JSON_MEDIA_TYPE) } @@ -1052,7 +1028,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou private fun getPrefBaseUrl(): String = preferences.getString("BASEURL", "")!! private fun getPrefApiUrl(): String = preferences.getString("APIURL", "")!! - private fun getPrefKey(key: String): String = preferences.getString(key, "")!! + private fun getPrefKey(): String = preferences.getString("APIKEY", "")!! private fun getToggledFilters() = preferences.getStringSet(KavitaConstants.toggledFiltersPref, KavitaConstants.defaultFilterPrefEntries)!! // We strip the last slash since we will append it above @@ -1129,7 +1105,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou if (jwtToken.isEmpty()) setupLogin() Log.v(LOG_TAG, "[Login] Starting login") val request = POST( - "$apiUrl/Plugin/authenticate?apiKey=${getPrefKey("APIKEY")}&pluginName=Tachiyomi-Kavita", + "$apiUrl/Plugin/authenticate?apiKey=${getPrefKey()}&pluginName=Tachiyomi-Kavita", setupLoginHeaders().build(), "{}".toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) ) client.newCall(request).execute().use { diff --git a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaConstants.kt b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaConstants.kt index 1fa7ddeb8..f385510c0 100644 --- a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaConstants.kt +++ b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaConstants.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.extension.all.kavita object KavitaConstants { - // togle filters + // toggle filters const val toggledFiltersPref = "toggledFilters" val filterPrefEntries = arrayOf( "Sort Options", @@ -24,7 +24,8 @@ object KavitaConstants { "Editor", "Publisher", "Character", - "Translators" + "Translators", + "ReleaseYearRange" ) val filterPrefEntriesValue = arrayOf( "Sort Options", @@ -47,7 +48,8 @@ object KavitaConstants { "Editor", "Publisher", "Character", - "Translators" + "Translators", + "ReleaseYearRange" ) val defaultFilterPrefEntries = setOf( "Sort Options", @@ -70,7 +72,8 @@ object KavitaConstants { "Editor", "Publisher", "Character", - "Translators" + "Translators", + "ReleaseYearRange" ) const val customSourceNamePref = "customSourceName" diff --git a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/MangaDto.kt b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/MangaDto.kt index 1e64ca053..c948b4be2 100644 --- a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/MangaDto.kt +++ b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/MangaDto.kt @@ -96,20 +96,3 @@ data class ChapterDto( val volumeId: Int, val created: String ) - -@Serializable -data class SearchResultsDto( - val series: List -) - -@Serializable -data class SeriesSearchDto( - val seriesId: Int, - val name: String, - val originalName: String, - val sortName: String, - val localizedName: String, - val format: Int, - val libraryName: String, - val libraryId: Int -) diff --git a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/MetadataDto.kt b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/MetadataDto.kt index 44d43c4b0..b4939f238 100644 --- a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/MetadataDto.kt +++ b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/dto/MetadataDto.kt @@ -63,6 +63,9 @@ data class MetadataPayload( var language: ArrayList = arrayListOf(), var libraries: ArrayList = arrayListOf(), var pubStatus: ArrayList = arrayListOf(), + var seriesNameQuery: String = "", + var releaseYearRangeMin: Int = 0, + var releaseYearRangeMax: Int = 0, var peopleWriters: ArrayList = arrayListOf(), var peoplePenciller: ArrayList = arrayListOf(), @@ -74,5 +77,4 @@ data class MetadataPayload( var peoplePublisher: ArrayList = arrayListOf(), var peopleCharacter: ArrayList = arrayListOf(), var peopleTranslator: ArrayList = arrayListOf(), - )