package com.hexated import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.SubtitleHelper import com.lagradost.cloudstream3.utils.getQualityFromName class Loklok : MainAPI() { override var name = "Loklok" override val hasMainPage = true override val hasChromecastSupport = true override val instantLinkLoading = true override val supportedTypes = setOf( TvType.Movie, TvType.TvSeries, TvType.Anime, TvType.AsianDrama, ) private val headers = mapOf( "lang" to "en", "versioncode" to "11", "clienttype" to "ios_jike_default" ) // no license found // thanks to https://github.com/napthedev/filmhot for providing API private val api = base64Decode("aHR0cHM6Ly9nYS1tb2JpbGUtYXBpLmxva2xvay50dg==") private val apiUrl = "$api/${base64Decode("Y21zL2FwcA==")}" private val mainImageUrl = "https://images.weserv.nl" private fun encode(input: String): String? = java.net.URLEncoder.encode(input, "utf-8") override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { val home = ArrayList() for (i in 0..10) { app.get("$apiUrl/homePage/getHome?page=$i", headers = headers) .parsedSafe()?.data?.recommendItems ?.filterNot { it.homeSectionType == "BLOCK_GROUP" } ?.filterNot { it.homeSectionType == "BANNER" } ?.mapNotNull { res -> val header = res.homeSectionName ?: return@mapNotNull null val media = res.media?.mapNotNull { media -> media.toSearchResponse() } ?: throw ErrorLoadingException("Invalid Json Reponse") home.add(HomePageList(header, media)) } } return HomePageResponse(home) } private fun Media.toSearchResponse(): SearchResponse? { return newMovieSearchResponse( title ?: name ?: return null, UrlData(id, category).toJson(), TvType.Movie, ) { this.posterUrl = (imageUrl ?: coverVerticalUrl)?.let { "$mainImageUrl/?url=${encode(it)}&w=175&h=246&fit=cover&output=webp" } } } override suspend fun search(query: String): List { // val res = app.post( // "$apiUrl/search/v1/searchWithKeyWord", data = mapOf( // "searchKeyWord" to query, // "size" to "50", // "sort" to "", // "searchType" to "" // ), headers = headers // ) val searchApi = base64Decode("aHR0cHM6Ly9maWxtaG90LmxpdmUvX25leHQvZGF0YS9NeXQzRm4tVHRXaHJ2a1RBaG45SGw=") val res = app.get( "$searchApi/search.json?q=$query", headers = mapOf("x-nextjs-data" to "1") ) return res.parsedSafe()?.pageProps?.result?.mapNotNull { media -> newMovieSearchResponse( media.name ?: return@mapNotNull null, UrlData(media.id?.toIntOrNull(), media.domainType).toJson(), TvType.Movie, ) { this.posterUrl = media.coverVerticalUrl } } ?: throw ErrorLoadingException("Invalid Json Reponse") } override suspend fun load(url: String): LoadResponse? { val data = parseJson(url) val res = app.get( "$apiUrl/movieDrama/get?id=${data.id}&category=${data.category}", headers = headers ).parsedSafe()?.data ?: throw ErrorLoadingException("Invalid Json Reponse") val episodes = res.episodeVo?.map { eps -> val definition = eps.definitionList?.map { Definition( it.code, it.description, ) } val subtitling = eps.subtitlingList?.map { Subtitling( it.languageAbbr, it.language, it.subtitlingUrl ) } Episode( data = UrlEpisode( data.id.toString(), data.category, eps.id, definition, subtitling ).toJson(), episode = eps.seriesNo ) } ?: throw ErrorLoadingException("No Episode Found") val recommendations = res.likeList?.mapNotNull { rec -> rec.toSearchResponse() } return newTvSeriesLoadResponse( res.name ?: return null, url, if (data.category == 0) TvType.Movie else TvType.TvSeries, episodes ) { this.posterUrl = res.coverVerticalUrl this.year = res.year this.plot = res.introduction this.tags = res.tagNameList this.rating = res.score.toRatingInt() this.recommendations = recommendations } } private fun getLanguage(str: String): String { return when (str) { "in_ID" -> "Indonesian" else -> str.split("_").first().let { SubtitleHelper.fromTwoLettersToLanguage(it).toString() } } } override suspend fun loadLinks( data: String, isCasting: Boolean, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ): Boolean { val res = parseJson(data) res.definitionList?.apmap { video -> safeApiCall { app.get( "$apiUrl/media/previewInfo?category=${res.category}&contentId=${res.id}&episodeId=${res.epId}&definition=${video.code}", headers = headers ).parsedSafe