package com.hexated import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.LoadResponse.Companion.addActors import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.getQualityFromName import org.jsoup.Jsoup import org.jsoup.nodes.Element import java.util.* class HDrezkaProvider : MainAPI() { override var mainUrl = "https://rezka.ag" override var name = "HDrezka" override val hasMainPage = true override var lang = "ru" override val hasDownloadSupport = true override val supportedTypes = setOf( TvType.Movie, TvType.TvSeries, TvType.Anime, TvType.AsianDrama ) override val mainPage = mainPageOf( "$mainUrl/films/?filter=watching" to "фильмы", "$mainUrl/series/?filter=watching" to "сериалы", "$mainUrl/cartoons/?filter=watching" to "мультфильмы", "$mainUrl/animation/?filter=watching" to "аниме", ) override suspend fun getMainPage( page: Int, request: MainPageRequest ): HomePageResponse { val url = request.data.split("?") val home = app.get("${url.first()}page/$page/?${url.last()}").document.select( "div.b-content__inline_items div.b-content__inline_item" ).map { it.toSearchResult() } return newHomePageResponse(request.name, home) } private fun Element.toSearchResult(): SearchResponse { val title = this.selectFirst("div.b-content__inline_item-link > a")?.text()?.trim().toString() val href = this.selectFirst("a")?.attr("href").toString() val posterUrl = this.select("img").attr("src") val type = if (this.select("span.info").isNotEmpty()) TvType.TvSeries else TvType.Movie return if (type == TvType.Movie) { newMovieSearchResponse(title, href, TvType.Movie) { this.posterUrl = posterUrl } } else { val episode = this.select("span.info").text().substringAfter(",").replace(Regex("[^0-9]"), "") .toIntOrNull() newAnimeSearchResponse(title, href, TvType.TvSeries) { this.posterUrl = posterUrl addDubStatus( dubExist = true, dubEpisodes = episode, subExist = true, subEpisodes = episode ) } } } override suspend fun search(query: String): List { val link = "$mainUrl/search/?do=search&subaction=search&q=$query" val document = app.get(link).document return document.select("div.b-content__inline_items div.b-content__inline_item").map { it.toSearchResult() } } override suspend fun load(url: String): LoadResponse { val document = app.get(url).document val id = url.split("/").last().split("-").first() val title = (document.selectFirst("div.b-post__origtitle")?.text()?.trim() ?: document.selectFirst("div.b-post__title h1")?.text()?.trim()).toString() val poster = fixUrlNull(document.selectFirst("div.b-sidecover img")?.attr("src")) val tags = document.select("table.b-post__info > tbody > tr:nth-child(5) span[itemprop=genre]") .map { it.text() } val year = document.select("div.film-info > div:nth-child(2) a").text().toIntOrNull() val tvType = if (document.select("div#simple-episodes-tabs") .isNullOrEmpty() ) TvType.Movie else TvType.TvSeries val description = document.selectFirst("div.b-post__description_text")?.text()?.trim() val trailer = app.post( "$mainUrl/engine/ajax/gettrailervideo.php", data = mapOf("id" to id), referer = url ).parsedSafe()?.code.let { Jsoup.parse(it.toString()).select("iframe").attr("src") } val rating = document.selectFirst("table.b-post__info > tbody > tr:nth-child(1) span.bold")?.text() .toRatingInt() val actors = document.select("table.b-post__info > tbody > tr:last-child span.item").map { Actor( it.selectFirst("span[itemprop=name]")?.text().toString(), it.selectFirst("span[itemprop=actor]")?.attr("data-photo") ) } val recommendations = document.select("div.b-sidelist div.b-content__inline_item").map { it.toSearchResult() } val data = HashMap() val server = ArrayList>() data["id"] = id data["favs"] = document.selectFirst("input#ctrl_favs")?.attr("value").toString() data["ref"] = url return if (tvType == TvType.TvSeries) { document.select("ul#translators-list li").map { res -> server.add( mapOf( "translator_name" to res.text(), "translator_id" to res.attr("data-translator_id"), ) ) } val episodes = document.select("div#simple-episodes-tabs ul li").map { val season = it.attr("data-season_id").toIntOrNull() val episode = it.attr("data-episode_id").toIntOrNull() val name = "Episode $episode" data["season"] = "$season" data["episode"] = "$episode" data["server"] = server data["action"] = "get_stream" Episode( data.toJson(), name, season, episode, ) } newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) { this.posterUrl = poster this.year = year this.plot = description this.tags = tags this.rating = rating addActors(actors) this.recommendations = recommendations addTrailer(trailer) } } else { document.select("ul#translators-list li").map { res -> server.add( mapOf( "translator_name" to res.text(), "translator_id" to res.attr("data-translator_id"), "camrip" to res.attr("data-camrip"), "ads" to res.attr("data-ads"), "director" to res.attr("data-director") ) ) } data["server"] = server data["action"] = "get_movie" newMovieLoadResponse(title, url, TvType.Movie, data.toJson()) { this.posterUrl = poster this.year = year this.plot = description this.tags = tags this.rating = rating addActors(actors) this.recommendations = recommendations addTrailer(trailer) } } } private fun decryptStreamUrl(data: String): String { fun getTrash(arr: List, item: Int): List { val trash = ArrayList>() for (i in 1..item) { trash.add(arr) } return trash.reduce { acc, list -> val temp = ArrayList() acc.forEach { ac -> list.forEach { li -> temp.add(ac.plus(li)) } } return@reduce temp } } val trashList = listOf("@", "#", "!", "^", "$") val trashSet = getTrash(trashList, 2) + getTrash(trashList, 3) var trashString = data.replace("#h", "").split("//_//").joinToString("") trashSet.forEach { val temp = base64Encode(it.toByteArray()) trashString = trashString.replace(temp, "") } return base64Decode(trashString) } private fun cleanCallback( source: String, url: String, quality: String, isM3u8: Boolean, sourceCallback: (ExtractorLink) -> Unit ) { sourceCallback.invoke( ExtractorLink( source, source, url, "$mainUrl/", getQuality(quality), isM3u8, headers = mapOf( "Origin" to mainUrl ) ) ) } private fun getLanguage(str: String): String { return when (str) { "Русский" -> "Russian" "Українська" -> "Ukrainian" else -> str } } private fun getQuality(str: String): Int { return when (str) { "360p" -> Qualities.P240.value "480p" -> Qualities.P360.value "720p" -> Qualities.P480.value "1080p" -> Qualities.P720.value "1080p Ultra" -> Qualities.P1080.value else -> getQualityFromName(str) } } private fun invokeSources( source: String, url: String, subtitle: String, subCallback: (SubtitleFile) -> Unit, sourceCallback: (ExtractorLink) -> Unit ) { decryptStreamUrl(url).split(",").map { links -> val quality = Regex("\\[([0-9]*p.*?)]").find(links)?.groupValues?.getOrNull(1) .toString().trim() links.replace("[$quality]", "").split("or").map { it.trim() } .map { link -> if (link.endsWith(".m3u8")) { cleanCallback( "$source (Main)", link, quality, true, sourceCallback, ) } else { cleanCallback( "$source (Backup)", link, quality, false, sourceCallback, ) } } } subtitle.split(",").map { sub -> val language = Regex("\\[(.*)]").find(sub)?.groupValues?.getOrNull(1) .toString() val link = sub.replace("[$language]", "").trim() subCallback.invoke( SubtitleFile( getLanguage(language), link ) ) } } override suspend fun loadLinks( data: String, isCasting: Boolean, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ): Boolean { tryParseJson(data)?.let { res -> if (res.server?.isEmpty() == true) { val document = app.get(res.ref ?: return@let).document document.select("script").map { script -> if (script.data().contains("sof.tv.initCDNMoviesEvents(")) { val dataJson = script.data().substringAfter("false, {").substringBefore("});") tryParseJson("{$dataJson}")?.let { source -> invokeSources( this.name, source.streams, source.subtitle.toString(), subtitleCallback, callback ) } } } } else { res.server?.apmap { server -> suspendSafeApiCall { app.post( url = "$mainUrl/ajax/get_cdn_series/?t=${Date().time}", data = mapOf( "id" to res.id, "translator_id" to server.translator_id, "favs" to res.favs, "is_camrip" to server.camrip, "is_ads" to server.ads, "is_director" to server.director, "season" to res.season, "episode" to res.episode, "action" to res.action, ).filterValues { it != null }.mapValues { it.value as String }, referer = res.ref ).parsedSafe()?.let { source -> invokeSources( server.translator_name.toString(), source.url, source.subtitle.toString(), subtitleCallback, callback ) } } } } } return true } data class LocalSources( @JsonProperty("streams") val streams: String, @JsonProperty("subtitle") val subtitle: Any?, ) data class Sources( @JsonProperty("url") val url: String, @JsonProperty("subtitle") val subtitle: Any?, ) data class Server( @JsonProperty("translator_name") val translator_name: String?, @JsonProperty("translator_id") val translator_id: String?, @JsonProperty("camrip") val camrip: String?, @JsonProperty("ads") val ads: String?, @JsonProperty("director") val director: String?, ) data class Data( @JsonProperty("id") val id: String?, @JsonProperty("favs") val favs: String?, @JsonProperty("server") val server: List?, @JsonProperty("season") val season: String?, @JsonProperty("episode") val episode: String?, @JsonProperty("action") val action: String?, @JsonProperty("ref") val ref: String?, ) data class Trailer( @JsonProperty("success") val success: Boolean?, @JsonProperty("code") val code: String?, ) }