From 8f9ac96de51e8f9d6529831c94b7f4714f3d8371 Mon Sep 17 00:00:00 2001 From: Blatzar <46196380+Blatzar@users.noreply.github.com> Date: Sat, 19 Feb 2022 16:15:48 +0100 Subject: [PATCH] optimized sflix --- .../com/lagradost/cloudstream3/MainAPI.kt | 61 ++++++++++++++- .../movieproviders/SflixProvider.kt | 76 ++++++++++++++++--- 2 files changed, 123 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index c19a1292..d4163c43 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -2,6 +2,8 @@ package com.lagradost.cloudstream3 import android.annotation.SuppressLint import android.content.Context +import android.net.Uri +import android.util.Base64.encodeToString import androidx.annotation.WorkerThread import androidx.preference.PreferenceManager import com.fasterxml.jackson.databind.DeserializationFeature @@ -121,6 +123,58 @@ object APIHolder { return url.replace(getApiFromName(apiName).mainUrl, "").replace("/", "").hashCode() } + + /** + * Gets the website captcha token + * discovered originally by https://github.com/ahmedgamal17 + * optimized by https://github.com/justfoolingaround + * + * @param url the main url, likely the same website you found the key from. + * @param key used to fill https://www.google.com/recaptcha/api.js?render=.... + * + * @param referer the referer for the google.com/recaptcha/api.js... request, optional. + * */ + + // Try document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]").attr("src").substringAfter("render=") + // To get the key + suspend fun getCaptchaToken(url: String, key: String, referer: String? = null): String? { + val uri = Uri.parse(url) + val domain = encodeToString( + (uri.scheme + "://" + uri.host + ":443").encodeToByteArray(), + 0 + ).replace("\n", "").replace("=",".") + + val vToken = + app.get( + "https://www.google.com/recaptcha/api.js?render=$key", + referer = referer, + cacheTime = 0 + ) + .text + .substringAfter("releases/") + .substringBefore("/") + val recapToken = + app.get("https://www.google.com/recaptcha/api2/anchor?ar=1&hl=en&size=invisible&cb=cs3&k=$key&co=$domain&v=$vToken") + .document + .selectFirst("#recaptcha-token")?.attr("value") + if (recapToken != null) { + return app.post( + "https://www.google.com/recaptcha/api2/reload?k=$key", + data = mapOf( + "v" to vToken, + "k" to key, + "c" to recapToken, + "co" to domain, + "sa" to "", + "reason" to "q" + ), cacheTime = 0 + ).text + .substringAfter("rresp\",\"") + .substringBefore("\"") + } + return null + } + fun Context.getApiSettings(): HashSet { //val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) @@ -250,14 +304,17 @@ abstract class MainAPI { suspend open fun getMainPage(): HomePageResponse? { throw NotImplementedError() } + @WorkerThread suspend open fun search(query: String): List? { throw NotImplementedError() } + @WorkerThread suspend open fun quickSearch(query: String): List? { throw NotImplementedError() } + @WorkerThread /** * Based on data from search() or getMainPage() it generates a LoadResponse, @@ -463,7 +520,7 @@ data class Actor( data class ActorData( val actor: Actor, val role: ActorRole? = null, - val roleString : String? = null, + val roleString: String? = null, val voiceActor: Actor? = null, ) @@ -553,7 +610,7 @@ interface LoadResponse { } fun LoadResponse.setDuration(input: String?) { - val cleanInput = input?.trim()?.replace(" ","") ?: return + val cleanInput = input?.trim()?.replace(" ", "") ?: return Regex("([0-9]*)h.*?([0-9]*)m").find(cleanInput)?.groupValues?.let { values -> if (values.size == 3) { val hours = values[1].toIntOrNull() diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt index 6b9c6592..78580905 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt @@ -3,14 +3,12 @@ package com.lagradost.cloudstream3.movieproviders import android.util.Log import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.APIHolder.getCaptchaToken import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.LoadResponse.Companion.addActors import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.network.AppResponse -import com.lagradost.cloudstream3.network.WebViewResolver -import com.lagradost.cloudstream3.network.getRequestCreator -import com.lagradost.cloudstream3.network.text import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson @@ -277,6 +275,14 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() { @JsonProperty("tracks") val tracks: List? ) + data class IframeJson( +// @JsonProperty("type") val type: String? = null, + @JsonProperty("link") val link: String? = null, +// @JsonProperty("sources") val sources: ArrayList = arrayListOf(), +// @JsonProperty("tracks") val tracks: ArrayList = arrayListOf(), +// @JsonProperty("title") val title: String? = null + ) + override suspend fun loadLinks( data: String, isCasting: Boolean, @@ -299,21 +305,67 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() { urls?.apmap { url -> suspendSafeApiCall { - val resolved = WebViewResolver( - Regex("""/getSources"""), - // This is unreliable, generating my own link instead -// additionalUrls = listOf(Regex("""^.*transport=polling(?!.*sid=).*$""")) - ).resolveUsingWebView(getRequestCreator(url)) -// val extractorData = resolved.second.getOrNull(0)?.url?.toString() +// val resolved = WebViewResolver( +// Regex("""/getSources"""), +// // This is unreliable, generating my own link instead +//// additionalUrls = listOf(Regex("""^.*transport=polling(?!.*sid=).*$""")) +// ).resolveUsingWebView(getRequestCreator(url)) +//// val extractorData = resolved.second.getOrNull(0)?.url?.toString() + + // ------- Main site ------- + + // Possible without token + +// val response = app.get(url) +// val key = +// response.document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]") +// .attr("src").substringAfter("render=") +// val token = getCaptchaToken(mainUrl, key) ?: return@suspendSafeApiCall + + val serverId = url.substringAfterLast(".") + val iframeLink = + app.get("https://sflix.to/ajax/get_link/$serverId").mapped().link + ?: return@suspendSafeApiCall + + // ------- Iframe ------- + val mainIframeUrl = + iframeLink.substringBeforeLast("/") // "https://rabbitstream.net/embed-4/6sBcv1i8vUF6?z=" -> "https://rabbitstream.net/embed-4" + val mainIframeId = iframeLink.substringAfterLast("/") + .substringBefore("?") // "https://rabbitstream.net/embed-4/6sBcv1i8vUF6?z=" -> "6sBcv1i8vUF6" + + val iframe = app.get(iframeLink, referer = mainUrl) + val iframeKey = + iframe.document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]") + .attr("src").substringAfter("render=") + val iframeToken = getCaptchaToken(iframeLink, iframeKey) + val number = Regex("""recaptchaNumber = '(.*?)'""").find(iframe.text)?.groupValues?.get(1) + + val mapped = app.get( + "${mainIframeUrl.replace("/embed", "/ajax/embed")}/getSources?id=$mainIframeId&_token=$iframeToken&_number=$number", + referer = "https://rabbitstream.net/", + headers = mapOf( + "X-Requested-With" to "XMLHttpRequest", + "Accept" to "*/*", + "Accept-Language" to "en-US,en;q=0.5", +// "Cache-Control" to "no-cache", + "Connection" to "keep-alive", +// "Sec-Fetch-Dest" to "empty", +// "Sec-Fetch-Mode" to "no-cors", +// "Sec-Fetch-Site" to "cross-site", +// "Pragma" to "no-cache", +// "Cache-Control" to "no-cache", + "TE" to "trailers" + ) + ).mapped() // Some smarter ws11 or w10 selection might be required in the future. val extractorData = "https://ws11.rabbitstream.net/socket.io/?EIO=4&transport=polling" - val sources = resolved.first?.let { app.baseClient.newCall(it).execute().text } - ?: return@suspendSafeApiCall +// val sources = resolved.first?.let { app.baseClient.newCall(it).execute().text } +// ?: return@suspendSafeApiCall - val mapped = parseJson(sources) +// val mapped = parseJson(sources) mapped.tracks?.forEach { it?.toSubtitleFile()?.let { subtitleFile ->