From cdc6c9c1a09e528807119e50795b56bc5954e96b Mon Sep 17 00:00:00 2001 From: Claudemirovsky <63046606+Claudemirovsky@users.noreply.github.com> Date: Sat, 18 Nov 2023 09:08:00 -0300 Subject: [PATCH] feat(tr/hdfilmcehennemi): Implement search filters (#2524) --- src/tr/hdfilmcehennemi/build.gradle | 2 +- .../tr/hdfilmcehennemi/HDFilmCehennemi.kt | 67 ++++++-- .../hdfilmcehennemi/HDFilmCehennemiFilters.kt | 154 ++++++++++++++++++ 3 files changed, 206 insertions(+), 17 deletions(-) create mode 100644 src/tr/hdfilmcehennemi/src/eu/kanade/tachiyomi/animeextension/tr/hdfilmcehennemi/HDFilmCehennemiFilters.kt diff --git a/src/tr/hdfilmcehennemi/build.gradle b/src/tr/hdfilmcehennemi/build.gradle index fbd8fe543..2a06e5f57 100644 --- a/src/tr/hdfilmcehennemi/build.gradle +++ b/src/tr/hdfilmcehennemi/build.gradle @@ -8,7 +8,7 @@ ext { extName = 'HDFilmCehennemi' pkgNameSuffix = 'tr.hdfilmcehennemi' extClass = '.HDFilmCehennemi' - extVersionCode = 5 + extVersionCode = 6 libVersion = '13' containsNsfw = true } diff --git a/src/tr/hdfilmcehennemi/src/eu/kanade/tachiyomi/animeextension/tr/hdfilmcehennemi/HDFilmCehennemi.kt b/src/tr/hdfilmcehennemi/src/eu/kanade/tachiyomi/animeextension/tr/hdfilmcehennemi/HDFilmCehennemi.kt index f3998eba1..6e238758f 100644 --- a/src/tr/hdfilmcehennemi/src/eu/kanade/tachiyomi/animeextension/tr/hdfilmcehennemi/HDFilmCehennemi.kt +++ b/src/tr/hdfilmcehennemi/src/eu/kanade/tachiyomi/animeextension/tr/hdfilmcehennemi/HDFilmCehennemi.kt @@ -25,6 +25,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import okhttp3.FormBody +import okhttp3.MultipartBody import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document @@ -81,6 +82,8 @@ class HDFilmCehennemi : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector() // =============================== Search =============================== + override fun getFilterList() = HDFilmCehennemiFilters.FILTER_LIST + override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable { return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler val id = query.removePrefix(PREFIX_SEARCH) @@ -102,9 +105,28 @@ class HDFilmCehennemi : ConfigurableAnimeSource, ParsedAnimeHttpSource() { .add("X-Requested-With", "XMLHttpRequest") .build() - val body = FormBody.Builder().add("query", query).build() + return when { + query.isNotBlank() -> { + val body = FormBody.Builder().add("query", query).build() - return POST("$baseUrl/search/", headers, body) + POST("$baseUrl/search/", headers, body) + } + else -> { + val params = HDFilmCehennemiFilters.getSearchParameters(filters) + + val form = MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("kesfet[type]", params.type) + .addFormDataPart("kesfet[genres]", params.genres) + .addFormDataPart("kesfet[years]", params.years) + .addFormDataPart("kesfet[imdb]", params.imdbScore) + .addFormDataPart("kesfet[orderBy]", params.order) + .addFormDataPart("page", page.toString()) + .build() + + POST("$baseUrl/movies/load/", headers, form) + } + } } @Serializable @@ -113,26 +135,39 @@ class HDFilmCehennemi : ConfigurableAnimeSource, ParsedAnimeHttpSource() { @Serializable data class ItemDto(val title: String, val poster: String, val slug: String, val slug_prefix: String) + @Serializable + data class FilterSearchResponse(val html: String, val showMore: Boolean, val status: Int) + override fun searchAnimeParse(response: Response): AnimesPage { - val data = response.parseAs() - val items = data.result.map { - SAnime.create().apply { - title = it.title - thumbnail_url = "$baseUrl/uploads/poster/" + it.poster - url = "/" + it.slug_prefix + it.slug + return when { + response.request.url.toString().contains("/search/") -> { // Text search + val data = response.parseAs() + val items = data.result.map { + SAnime.create().apply { + title = it.title + thumbnail_url = "$baseUrl/uploads/poster/" + it.poster + url = "/" + it.slug_prefix + it.slug + } + } + + AnimesPage(items, false) + } + + else -> { // Filter search + val data = response.parseAs() + if (data.status != 1) return AnimesPage(emptyList(), false) + + val doc = response.asJsoup(data.html) + val items = doc.select(searchAnimeSelector()).map(::searchAnimeFromElement) + + AnimesPage(items, data.showMore) } } - - return AnimesPage(items, false) } - override fun searchAnimeSelector(): String { - throw UnsupportedOperationException("Not used.") - } + override fun searchAnimeSelector() = "div.poster > a" - override fun searchAnimeFromElement(element: Element): SAnime { - throw UnsupportedOperationException("Not used.") - } + override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element) override fun searchAnimeNextPageSelector(): String? { throw UnsupportedOperationException("Not used.") diff --git a/src/tr/hdfilmcehennemi/src/eu/kanade/tachiyomi/animeextension/tr/hdfilmcehennemi/HDFilmCehennemiFilters.kt b/src/tr/hdfilmcehennemi/src/eu/kanade/tachiyomi/animeextension/tr/hdfilmcehennemi/HDFilmCehennemiFilters.kt new file mode 100644 index 000000000..7af49c3c2 --- /dev/null +++ b/src/tr/hdfilmcehennemi/src/eu/kanade/tachiyomi/animeextension/tr/hdfilmcehennemi/HDFilmCehennemiFilters.kt @@ -0,0 +1,154 @@ +package eu.kanade.tachiyomi.animeextension.tr.hdfilmcehennemi + +import eu.kanade.tachiyomi.animesource.model.AnimeFilter +import eu.kanade.tachiyomi.animesource.model.AnimeFilterList + +object HDFilmCehennemiFilters { + open class QueryPartFilter( + displayName: String, + val vals: Array>, + ) : AnimeFilter.Select( + displayName, + vals.map { it.first }.toTypedArray(), + ) { + fun toQueryPart() = vals[state].second + } + + open class CheckBoxFilterList(name: String, val pairs: Array>) : + AnimeFilter.Group(name, pairs.map { CheckBoxVal(it.first, false) }) + + private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state) + + private inline fun AnimeFilterList.asQueryPart(): String { + return (first { it is R } as QueryPartFilter).toQueryPart() + } + + private inline fun AnimeFilterList.parseCheckbox( + options: Array>, + ): String { + return (first { it is R } as CheckBoxFilterList).state + .asSequence() + .filter { it.state } + .map { checkbox -> options.find { it.first == checkbox.name }!!.second } + .joinToString(",") + } + + class TypeFilter : QueryPartFilter("Türü", HDFilmCehennemiFiltersData.TYPES) + + class GenresFilter : CheckBoxFilterList("Türler", HDFilmCehennemiFiltersData.GENRES) + class YearsFilter : CheckBoxFilterList("Yıllar", HDFilmCehennemiFiltersData.YEARS) + class IMDBScoreFilter : CheckBoxFilterList("IMDb Puanı", HDFilmCehennemiFiltersData.SCORES) + + class SortFilter : AnimeFilter.Sort( + "Tarafından sipariş", + HDFilmCehennemiFiltersData.ORDERS.map { it.first }.toTypedArray(), + Selection(0, false), + ) + + val FILTER_LIST get() = AnimeFilterList( + AnimeFilter.Header("NOTE: Ignored if using text search!"), + AnimeFilter.Separator(), + + TypeFilter(), + SortFilter(), + AnimeFilter.Separator(), + + IMDBScoreFilter(), + GenresFilter(), + YearsFilter(), + ) + + data class FilterSearchParams( + val type: String = "1", + val order: String = "posts.imdb desc", + val imdbScore: String = "", + val genres: String = "", + val years: String = "", + ) + + internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams { + if (filters.isEmpty()) return FilterSearchParams() + + val sortFilter = filters.firstOrNull { it is SortFilter } as? SortFilter + val orderBy = sortFilter?.state?.run { + val order = HDFilmCehennemiFiltersData.ORDERS[index].second + val orderWay = if (ascending) "asc" else "desc" + "$order $orderWay" + } ?: "posts.imdb desc" + + return FilterSearchParams( + filters.asQueryPart(), + orderBy, + filters.parseCheckbox(HDFilmCehennemiFiltersData.SCORES), + filters.parseCheckbox(HDFilmCehennemiFiltersData.GENRES), + filters.parseCheckbox(HDFilmCehennemiFiltersData.YEARS), + ) + } + + private object HDFilmCehennemiFiltersData { + val TYPES = arrayOf( + Pair("Filmler", "1"), + Pair("Diziler", "2"), + ) + + val GENRES = arrayOf( + Pair("Adult", "40"), + Pair("Aile", "8"), + Pair("Aksiyon", "1"), + Pair("Animasyon", "3"), + Pair("Belgesel", "6"), + Pair("Bilim Kurgu", "24"), + Pair("Biyografi", "26"), + Pair("Dram", "7"), + Pair("Fantastik", "9"), + Pair("Film-Noir", "39"), + Pair("Game-Show", "34"), + Pair("Gerilim", "16"), + Pair("Gizem", "13"), + Pair("Komedi", "4"), + Pair("Korku", "11"), + Pair("Macera", "2"), + Pair("Müzik", "12"), + Pair("Müzik", "27"), + Pair("Polisiye", "32"), + Pair("Reality", "37"), + Pair("Reality-TV", "33"), + Pair("Romantik", "14"), + Pair("Savaş", "17"), + Pair("Short", "35"), + Pair("Spor", "28"), + Pair("Suç", "5"), + Pair("Tarih", "10"), + Pair("Western", "18"), + ) + + val YEARS = arrayOf( + Pair("2023", "2023"), + Pair("2022", "2022"), + Pair("2021", "2021"), + Pair("2020", "2020"), + Pair("2019", "2019"), + Pair("2018", "2018"), + Pair("2017", "2017"), + Pair("2016", "2016"), + Pair("2015-2010 arası", "2010-2015"), + Pair("2010-2000 arası", "2000-2010"), + Pair("2000 öncesi", "1901-2000"), + ) + + val SCORES = arrayOf( + Pair("9", "9-10"), + Pair("8", "8-9"), + Pair("7", "7-8"), + Pair("6", "6-7"), + Pair("5 ve altı", "0-6"), + ) + + val ORDERS = arrayOf( + Pair("IMDb Puanına", "posts.imdb"), + Pair("Site Puanı", "avg"), + Pair("Yıla", "posts.year"), + Pair("İzlenme", "views"), + ) + } +}