From f8daecd5b40f6d306e4237fbe3446a1f83b1cabd Mon Sep 17 00:00:00 2001 From: imper1aldev <23511335+imper1aldev@users.noreply.github.com> Date: Mon, 4 Mar 2024 04:31:04 -0600 Subject: [PATCH] fix(src/es): Some fixes for Spanish extensions (#2980) --- src/es/doramasflix/build.gradle | 2 +- .../es/doramasflix/DataModel.kt | 300 +++++++++++++ .../es/doramasflix/Doramasflix.kt | 405 ++++++++++-------- src/es/locopelis/build.gradle | 3 +- .../animeextension/es/locopelis/LocoPelis.kt | 44 +- src/es/pelisplushd/build.gradle | 2 +- .../es/pelisplushd/Pelisplushd.kt | 210 ++++----- .../es/pelisplushd/Pelisplusph.kt | 152 ++----- .../es/pelisplushd/Pelisplusto.kt | 179 +++----- 9 files changed, 737 insertions(+), 560 deletions(-) create mode 100644 src/es/doramasflix/src/eu/kanade/tachiyomi/animeextension/es/doramasflix/DataModel.kt diff --git a/src/es/doramasflix/build.gradle b/src/es/doramasflix/build.gradle index 3870dd09bb..d5fa25b1b1 100644 --- a/src/es/doramasflix/build.gradle +++ b/src/es/doramasflix/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Doramasflix' extClass = '.Doramasflix' - extVersionCode = 16 + extVersionCode = 17 } apply from: "$rootDir/common.gradle" diff --git a/src/es/doramasflix/src/eu/kanade/tachiyomi/animeextension/es/doramasflix/DataModel.kt b/src/es/doramasflix/src/eu/kanade/tachiyomi/animeextension/es/doramasflix/DataModel.kt new file mode 100644 index 0000000000..2614f31875 --- /dev/null +++ b/src/es/doramasflix/src/eu/kanade/tachiyomi/animeextension/es/doramasflix/DataModel.kt @@ -0,0 +1,300 @@ +package eu.kanade.tachiyomi.animeextension.es.doramasflix + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject + +// -----------------------Season models------------------------// +@Serializable +data class SeasonModel( + val data: DataSeason = DataSeason(), +) + +@Serializable +data class DataSeason( + val listSeasons: List = emptyList(), +) + +@Serializable +data class ListSeason( + val slug: String, + @SerialName("season_number") + val seasonNumber: Long, + @SerialName("poster_path") + val posterPath: String?, + @SerialName("air_date") + val airDate: String?, + @SerialName("serie_name") + val serieName: String?, + val poster: String?, + val backdrop: String?, + @SerialName("__typename") + val typename: String, +) + +// -----------------------Episode Model------------------------// +@Serializable +data class EpisodeModel( + val data: DataEpisode = DataEpisode(), +) + +@Serializable +data class DataEpisode( + val listEpisodes: List = emptyList(), +) + +@Serializable +data class ListEpisode( + @SerialName("_id") + val id: String, + val name: String?, + val slug: String, + @SerialName("serie_name") + val serieName: String?, + @SerialName("serie_name_es") + val serieNameEs: String?, + @SerialName("serie_id") + val serieId: String?, + @SerialName("still_path") + val stillPath: String?, + @SerialName("air_date") + val airDate: String?, + @SerialName("season_number") + val seasonNumber: Long?, + @SerialName("episode_number") + val episodeNumber: Long?, + // val languages: List, + val poster: String?, + val backdrop: String?, + @SerialName("__typename") + val typename: String, +) + +// -----------------------Details Model------------------------// + +data class DetailsModel( + val props: Props, + val page: String, + val query: Query, + val buildId: String, + val isFallback: Boolean, + val gip: Boolean, +) + +data class Props( + val pageProps: PageProps, +) + +data class PageProps( + val deviceType: String, + val slug: String, + // val apolloClient: Any?, + val apolloState: HashMap>, + val ssrComplete: Boolean, +) + +data class Query( + val slug: String, +) + +// -----------------------Pagination Model------------------------// + +@Serializable +data class PaginationModel( + val data: DataPagination = DataPagination(), +) + +@Serializable +data class DataPagination( + val paginationDorama: PaginationDorama? = null, + val paginationMovie: PaginationDorama? = null, +) + +@Serializable +data class PaginationDorama( + val count: Long, + val pageInfo: PageInfo, + val items: List = emptyList(), + @SerialName("__typename") + val typename: String, +) + +@Serializable +data class PageInfo( + val currentPage: Long, + val hasNextPage: Boolean, + val hasPreviousPage: Boolean, + @SerialName("__typename") + val typename: String, +) + +@Serializable +data class Item( + @SerialName("_id") + val id: String, + val name: String, + @SerialName("name_es") + val nameEs: String?, + val slug: String, + val cast: List = emptyList(), + val names: String?, + val overview: String?, + val languages: List = emptyList(), + @SerialName("created_by") + val createdBy: List = emptyList(), + val popularity: Double?, + @SerialName("poster_path") + val posterPath: String?, + @SerialName("backdrop_path") + val backdropPath: String?, + @SerialName("first_air_date") + val firstAirDate: String?, + @SerialName("isTVShow") + val isTvshow: Boolean?, + val poster: String?, + val backdrop: String?, + val genres: List = emptyList(), + val networks: List = emptyList(), + @SerialName("__typename") + val typename: String, +) + +@Serializable +data class Cast( + val adult: Boolean?, + val gender: Long?, + val id: Long?, + @SerialName("known_for_department") + val knownForDepartment: String?, + val name: String?, + @SerialName("original_name") + val originalName: String?, + val popularity: Double?, + @SerialName("profile_path") + val profilePath: String?, + val character: String?, + @SerialName("credit_id") + val creditId: String?, + val order: Long?, +) + +@Serializable +data class CreatedBy( + val adult: Boolean?, + val gender: Long?, + val id: Long?, + @SerialName("known_for_department") + val knownForDepartment: String?, + val name: String?, + @SerialName("original_name") + val originalName: String?, + val popularity: Double?, + @SerialName("profile_path") + val profilePath: String?, + @SerialName("credit_id") + val creditId: String?, + val department: String?, + val job: String?, +) + +@Serializable +data class Genre( + val name: String?, + val slug: String?, + @SerialName("__typename") + val typename: String?, +) + +@Serializable +data class Network( + val name: String?, + val slug: String?, + @SerialName("__typename") + val typename: String?, +) + +// -----------------------Search Model------------------------// +@Serializable +data class SearchModel( + val data: Data = Data(), +) + +@Serializable +data class Data( + val searchDorama: List = emptyList(), + val searchMovie: List = emptyList(), +) + +@Serializable +data class SearchDorama( + @SerialName("_id") + val id: String, + val slug: String, + val name: String, + @SerialName("name_es") + val nameEs: String?, + @SerialName("poster_path") + val posterPath: String?, + val poster: String?, + @SerialName("__typename") + val typename: String, +) + +// ------------------------------------------------------- +@Serializable +data class VideoModel( + val json: JsonVideo = JsonVideo(), +) + +@Serializable +data class JsonVideo( + val lang: String? = "", + val page: String? = "", + val link: String? = "", + val server: String? = "", +) + +@Serializable +data class VideoToken( + val link: String?, + val server: String?, + val app: String?, + val iat: Long?, + val exp: Long?, +) + +// ------------------------------------------------ + +@Serializable +data class TokenModel( + val props: PropsToken = PropsToken(), + val page: String? = null, + val query: QueryToken = QueryToken(), + val buildId: String? = null, + val isFallback: Boolean? = false, + val isExperimentalCompile: Boolean? = false, + val gssp: Boolean? = false, + // val scriptLoader: List, +) + +@Serializable +data class PropsToken( + val pageProps: PagePropsToken = PagePropsToken(), + @SerialName("__N_SSP") + val nSsp: Boolean? = false, +) + +@Serializable +data class PagePropsToken( + val token: String? = null, + val name: String? = null, + val app: String? = null, + val server: String? = null, + val iosapp: String? = null, + val externalLink: String? = null, +) + +@Serializable +data class QueryToken( + val token: String? = null, +) diff --git a/src/es/doramasflix/src/eu/kanade/tachiyomi/animeextension/es/doramasflix/Doramasflix.kt b/src/es/doramasflix/src/eu/kanade/tachiyomi/animeextension/es/doramasflix/Doramasflix.kt index d8b185fec5..97ecaea991 100644 --- a/src/es/doramasflix/src/eu/kanade/tachiyomi/animeextension/es/doramasflix/Doramasflix.kt +++ b/src/es/doramasflix/src/eu/kanade/tachiyomi/animeextension/es/doramasflix/Doramasflix.kt @@ -2,9 +2,11 @@ package eu.kanade.tachiyomi.animeextension.es.doramasflix import android.app.Application import android.content.SharedPreferences +import android.util.Base64 import androidx.preference.ListPreference import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource +import eu.kanade.tachiyomi.animesource.model.AnimeFilter import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimesPage import eu.kanade.tachiyomi.animesource.model.SAnime @@ -24,11 +26,12 @@ import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor +import eu.kanade.tachiyomi.lib.vudeoextractor.VudeoExtractor import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.util.asJsoup -import kotlinx.serialization.decodeFromString +import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonArray @@ -43,6 +46,8 @@ import okhttp3.Response import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import java.text.SimpleDateFormat +import java.util.Date class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() { @@ -86,6 +91,10 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() { "Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare", "Uqload", ) + + private val DATE_FORMATTER by lazy { + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + } } private val preferences: SharedPreferences by lazy { @@ -121,7 +130,8 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() { document.select("script").map { el -> if (el.data().contains("{\"props\":{\"pageProps\":{")) { val apolloState = json.decodeFromString(el.data())!!.jsonObject["props"]!!.jsonObject["pageProps"]!!.jsonObject["apolloState"]!!.jsonObject - val dorama = apolloState!!.entries!!.firstOrNull()!!.value!!.jsonObject + val dorama = apolloState!!.entries!!.firstOrNull { (key, _) -> Regex("\\b(?:Movie|Dorama):[a-zA-Z0-9]+").matches(key) }!!.value!!.jsonObject + val genres = try { apolloState.entries.filter { x -> x.key.contains("genres") }.joinToString { it.value.jsonObject["name"]!!.jsonPrimitive.content } } catch (_: Exception) { "" } val network = try { apolloState.entries.firstOrNull { x -> x.key.contains("networks") }?.value?.jsonObject?.get("name")!!.jsonPrimitive.content } catch (_: Exception) { "" } val artist = try { dorama["cast"]?.jsonObject?.get("json")?.jsonArray?.firstOrNull()?.jsonObject?.get("name")?.jsonPrimitive?.content } catch (_: Exception) { "" } @@ -158,55 +168,60 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() { } override fun episodeListParse(response: Response): List { - val episodes = mutableListOf() - if (response.request.url.toString().contains("peliculas-online")) { - val episode = SEpisode.create() - episode.episode_number = 1F - episode.name = "Película" - episode.setUrlWithoutDomain(response.request.url.toString()) - episodes.add(episode) + return if (response.request.url.toString().contains("peliculas-online")) { + listOf( + SEpisode.create().apply { + episode_number = 1F + name = "Película" + setUrlWithoutDomain(response.request.url.toString()) + }, + ) } else { - val jsonEpisodes = mutableListOf() val id = response.request.url.toString().substringAfter("?id=") val responseString = response.body.string() - val data = json.decodeFromString(responseString)!!.jsonObject["data"]!!.jsonObject - data["listSeasons"]!!.jsonArray.forEach { - val season = it.jsonObject["season_number"]!!.jsonPrimitive.content + val data = json.decodeFromString(responseString).data + + data.listSeasons.parallelCatchingFlatMapBlocking { + val season = it.seasonNumber val body = ( "{\"operationName\":\"listEpisodes\",\"variables\":{\"serie_id\":\"$id\",\"season_number\":$season},\"query\":\"query " + "listEpisodes(\$season_number: Float!, \$serie_id: MongoID!) {\\n listEpisodes(sort: NUMBER_ASC, filter: {type_serie: \\\"dorama\\\", " + "serie_id: \$serie_id, season_number: \$season_number}) {\\n _id\\n name\\n slug\\n serie_name\\n serie_name_es\\n " + "serie_id\\n still_path\\n air_date\\n season_number\\n episode_number\\n languages\\n poster\\n backdrop\\n __typename\\n }\\n}\\n\"}" ).toRequestBody(mediaType) - client.newCall(POST(apiUrl, popularRequestHeaders, body)).execute().let { resp -> jsonEpisodes.add(resp.body.string()) } + + val episodes = client.newCall(POST(apiUrl, popularRequestHeaders, body)).execute().let { resp -> + json.decodeFromString(resp.body.string()) + } + parseEpisodeListJson(episodes) } - jsonEpisodes.forEach { json -> episodes.addAll(parseEpisodeListJson(json)) } - } - return episodes.reversed() + }.reversed() } - private fun parseEpisodeListJson(jsonLine: String?): List { - val jsonData = jsonLine ?: return emptyList() - val episodes = mutableListOf() - val data = json.decodeFromString(jsonData)!!.jsonObject["data"]!!.jsonObject - data["listEpisodes"]!!.jsonArray!!.map { - val noSeason = it.jsonObject["season_number"]!!.jsonPrimitive!!.content - val noEpisode = it.jsonObject["episode_number"]!!.jsonPrimitive!!.content - var nameEp = it.jsonObject["name"]!!.jsonPrimitive!!.content - nameEp = if (nameEp == "null") "- Capítulo $noEpisode" else "- $nameEp" - val slug = it.jsonObject["slug"]!!.jsonPrimitive!!.content - val episode = SEpisode.create() - episode.name = "T$noSeason - E$noEpisode $nameEp" - episode.episode_number = noEpisode.toFloat() - episode.setUrlWithoutDomain(urlSolverByType("episode", slug)) - episodes.add(episode) + private fun parseEpisodeListJson(episodes: EpisodeModel): List { + var isUpcoming = false + val currentDate = Date().time + return episodes.data.listEpisodes.mapIndexed { idx, episodeObject -> + val dateEp = episodeObject.airDate + val nameEp = if (episodeObject.name.isNullOrEmpty()) "- Capítulo ${episodeObject.episodeNumber}" else "- ${episodeObject.name}" + if (dateEp != null && dateEp.toDate() > currentDate && !isUpcoming) isUpcoming = true + + SEpisode.create().apply { + name = "T${episodeObject.seasonNumber} - E${episodeObject.episodeNumber} $nameEp" + episode_number = episodeObject.episodeNumber?.toFloat() ?: idx.toFloat() + date_upload = dateEp?.toDate() ?: 0L + scanlator = if (isUpcoming) "Próximamente..." else null + setUrlWithoutDomain(urlSolverByType("episode", episodeObject.slug)) + } } - return episodes } override fun latestUpdatesParse(response: Response): AnimesPage { val responseString = response.body.string() - return parsePopularAnimeJson(responseString) + return when { + responseString.contains("paginationMovie") -> parsePopularJson(responseString, "movie") + else -> parsePopularJson(responseString, "dorama") + } } override fun latestUpdatesRequest(page: Int): Request { @@ -226,7 +241,10 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() { override fun popularAnimeParse(response: Response): AnimesPage { val responseString = response.body.string() - return parsePopularAnimeJson(responseString) + return when { + responseString.contains("paginationMovie") -> parsePopularJson(responseString, "movie") + else -> parsePopularJson(responseString, "dorama") + } } private val languages = arrayOf( @@ -248,26 +266,33 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() { return languages.firstOrNull { it.first == this }?.second ?: "" } - private fun parsePopularAnimeJson(jsonLine: String?): AnimesPage { + private fun parsePopularJson(jsonLine: String?, type: String): AnimesPage { val jsonData = jsonLine ?: return AnimesPage(emptyList(), false) - val animeList = mutableListOf() - val paginationDorama = json.decodeFromString(jsonData)!!.jsonObject["data"]!!.jsonObject!!["paginationDorama"]!!.jsonObject - val hasNextPage = paginationDorama["pageInfo"]!!.jsonObject["hasNextPage"]!!.jsonPrimitive!!.content!!.toBoolean() - paginationDorama["items"]!!.jsonArray.map { - val anime = SAnime.create() - val genres = it.jsonObject!!["genres"]!!.jsonArray!!.joinToString { it.jsonObject["name"]!!.jsonPrimitive!!.content } - val id = it.jsonObject!!["_id"]!!.jsonPrimitive!!.content - val poster = it.jsonObject["poster_path"]!!.jsonPrimitive.content - val urlImg = poster.ifEmpty { it.jsonObject!!["poster"]!!.jsonPrimitive.content } - - anime.title = "${it.jsonObject!!["name"]!!.jsonPrimitive!!.content} (${it.jsonObject!!["name_es"]!!.jsonPrimitive!!.content})" - anime.description = it.jsonObject!!["overview"]!!.jsonPrimitive!!.content - anime.genre = genres - anime.thumbnail_url = externalOrInternalImg(urlImg, true) - anime.setUrlWithoutDomain(urlSolverByType(it.jsonObject!!["__typename"]!!.jsonPrimitive!!.content, it.jsonObject!!["slug"]!!.jsonPrimitive!!.content, id)) - animeList.add(anime) + val data = json.decodeFromString(jsonData).data + + val pagination = when (type) { + "dorama" -> data.paginationDorama + "movie" -> data.paginationMovie + else -> throw IllegalArgumentException("Tipo de dato no válido: $type") } - return AnimesPage(animeList, hasNextPage) + + val hasNextPage = pagination?.pageInfo?.hasNextPage ?: false + val animeList = pagination?.items?.map { animeObject -> + val urlImg = when { + !animeObject.posterPath.isNullOrEmpty() -> animeObject.posterPath.toString() + !animeObject.poster.isNullOrEmpty() -> animeObject.poster.toString() + else -> "" + } + + SAnime.create().apply { + title = "${animeObject.name} (${animeObject.nameEs})" + description = animeObject.overview + genre = animeObject.genres.joinToString { it.name ?: "" } + thumbnail_url = externalOrInternalImg(urlImg, true) + setUrlWithoutDomain(urlSolverByType(animeObject.typename, animeObject.slug, animeObject.id)) + } + } + return AnimesPage(animeList ?: emptyList(), hasNextPage) } private fun urlSolverByType(type: String, slug: String, id: String? = ""): String { @@ -296,45 +321,45 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() { override fun searchAnimeParse(response: Response): AnimesPage { val responseString = response.body.string() - return if (responseString.contains("searchDorama")) { - parseSearchAnimeJson(responseString) - } else { - parsePopularAnimeJson(responseString) + return when { + responseString.contains("searchDorama") -> parseSearchAnimeJson(responseString) + responseString.contains("paginationMovie") -> parsePopularJson(responseString, "movie") + else -> parsePopularJson(responseString, "dorama") } } private fun parseSearchAnimeJson(jsonLine: String?): AnimesPage { val jsonData = jsonLine ?: return AnimesPage(emptyList(), false) + val jsonObject = json.decodeFromString(jsonData).data + val animeList = mutableListOf() - val paginationDorama = json.decodeFromString(jsonData)!!.jsonObject["data"]!!.jsonObject - paginationDorama["searchDorama"]!!.jsonArray.map { - val anime = SAnime.create() - val id = it.jsonObject!!["_id"]!!.jsonPrimitive!!.content - val poster = it.jsonObject!!["poster_path"]!!.jsonPrimitive!!.content - val urlImg = poster.ifEmpty { it.jsonObject!!["poster"]!!.jsonPrimitive!!.content } - - anime.title = "${it.jsonObject!!["name"]!!.jsonPrimitive!!.content} (${it.jsonObject!!["name_es"]!!.jsonPrimitive!!.content})" - anime.thumbnail_url = externalOrInternalImg(urlImg, true) - anime.setUrlWithoutDomain(urlSolverByType(it.jsonObject!!["__typename"]!!.jsonPrimitive!!.content, it.jsonObject!!["slug"]!!.jsonPrimitive!!.content, id)) - animeList.add(anime) + jsonObject.searchDorama.map { castToSAnime(it) }.also(animeList::addAll) + jsonObject.searchMovie.map { castToSAnime(it) }.also(animeList::addAll) + + return AnimesPage(animeList, false) + } + + private fun castToSAnime(animeObject: SearchDorama): SAnime { + val urlImg = when { + !animeObject.posterPath.isNullOrEmpty() -> animeObject.posterPath.toString() + !animeObject.poster.isNullOrEmpty() -> animeObject.poster.toString() + else -> "" } - paginationDorama["searchMovie"]!!.jsonArray.map { - val anime = SAnime.create() - val id = it.jsonObject!!["_id"]!!.jsonPrimitive!!.content - val poster = it.jsonObject!!["poster_path"]!!.jsonPrimitive!!.content - val urlImg = poster.ifEmpty { it.jsonObject!!["poster"]!!.jsonPrimitive!!.content } - - anime.title = "${it.jsonObject!!["name"]!!.jsonPrimitive!!.content} (${it.jsonObject!!["name_es"]!!.jsonPrimitive!!.content})" - anime.thumbnail_url = externalOrInternalImg(urlImg, true) - anime.setUrlWithoutDomain(urlSolverByType(it.jsonObject!!["__typename"]!!.jsonPrimitive!!.content, it.jsonObject!!["slug"]!!.jsonPrimitive!!.content, id)) - animeList.add(anime) + return SAnime.create().apply { + title = "${animeObject.name} (${animeObject.nameEs})" + thumbnail_url = externalOrInternalImg(urlImg, true) + setUrlWithoutDomain(urlSolverByType(animeObject.typename, animeObject.slug, animeObject.id)) } - return AnimesPage(animeList, false) } override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { + val filterList = if (filters.isEmpty()) getFilterList() else filters + val genreFilter = filterList.find { it is GenreFilter } as GenreFilter + return when { query.isNotBlank() -> searchQueryRequest(query) + "peliculas" in genreFilter.toUriPart() -> popularMovieRequest(page) + "variedades" in genreFilter.toUriPart() -> popularVarietiesRequest(page) else -> popularAnimeRequest(page) } } @@ -350,123 +375,148 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() { return POST(apiUrl, popularRequestHeaders, body) } + private fun popularMovieRequest(page: Int): Request { + val mediaType = "application/json; charset=utf-8".toMediaType() + val body = ( + "{\"operationName\":\"listMovies\",\"variables\":{\"perPage\":32,\"sort\":\"CREATEDAT_DESC\",\"filter\":{},\"page\":$page},\"query\":\"query " + + "listMovies(\$page: Int, \$perPage: Int, \$sort: SortFindManyMovieInput, \$filter: FilterFindManyMovieInput) {\\n paginationMovie(page: \$page" + + ", perPage: \$perPage, sort: \$sort, filter: \$filter) {\\n count\\n pageInfo {\\n currentPage\\n hasNextPage\\n hasPreviousPage\\n" + + " __typename\\n }\\n items {\\n _id\\n name\\n name_es\\n slug\\n cast\\n names\\n overview\\n " + + "languages\\n popularity\\n poster_path\\n vote_average\\n backdrop_path\\n release_date\\n runtime\\n poster\\n " + + "backdrop\\n genres {\\n name\\n __typename\\n }\\n networks {\\n name\\n __typename\\n }\\n " + + "__typename\\n }\\n __typename\\n }\\n}\\n\"}" + ).toRequestBody(mediaType) + + return POST(apiUrl, popularRequestHeaders, body) + } + + private fun popularVarietiesRequest(page: Int): Request { + val mediaType = "application/json; charset=utf-8".toMediaType() + val body = ( + "{\"operationName\":\"listDoramas\",\"variables\":{\"page\":$page,\"sort\":\"CREATEDAT_DESC\",\"perPage\":32,\"filter\":{\"isTVShow\":true}},\"query\":\"query " + + "listDoramas(\$page: Int, \$perPage: Int, \$sort: SortFindManyDoramaInput, \$filter: FilterFindManyDoramaInput) {\\n paginationDorama(page: \$page, perPage: \$perPage, " + + "sort: \$sort, filter: \$filter) {\\n count\\n pageInfo {\\n currentPage\\n hasNextPage\\n hasPreviousPage\\n __typename\\n }\\n " + + "items {\\n _id\\n name\\n name_es\\n slug\\n cast\\n names\\n overview\\n languages\\n created_by\\n " + + "popularity\\n poster_path\\n vote_average\\n backdrop_path\\n first_air_date\\n episode_run_time\\n isTVShow\\n poster\\n " + + "backdrop\\n genres {\\n name\\n slug\\n __typename\\n }\\n networks {\\n name\\n slug\\n " + + "__typename\\n }\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}" + ).toRequestBody(mediaType) + + return POST(apiUrl, popularRequestHeaders, body) + } + + override fun getFilterList(): AnimeFilterList = AnimeFilterList( + AnimeFilter.Header("La busqueda por texto ignora el filtro"), + GenreFilter(), + ) + + private class GenreFilter : UriPartFilter( + "Géneros", + arrayOf( + Pair("Doramas", "doramas"), + Pair("Películas", "peliculas"), + Pair("Variedades", "variedades"), + ), + ) + + private open class UriPartFilter(displayName: String, val vals: Array>) : + AnimeFilter.Select(displayName, vals.map { it.first }.toTypedArray()) { + fun toUriPart() = vals[state].second + } + + private fun String.toDate(): Long { + return runCatching { DATE_FORMATTER.parse(trim())?.time }.getOrNull() ?: 0L + } + override fun videoListParse(response: Response): List