forked from recloudstream/cloudstream
		
	optimized sflix
This commit is contained in:
		
							parent
							
								
									6cdf18ae8f
								
							
						
					
					
						commit
						8f9ac96de5
					
				
					 2 changed files with 123 additions and 14 deletions
				
			
		|  | @ -2,6 +2,8 @@ package com.lagradost.cloudstream3 | ||||||
| 
 | 
 | ||||||
| import android.annotation.SuppressLint | import android.annotation.SuppressLint | ||||||
| import android.content.Context | import android.content.Context | ||||||
|  | import android.net.Uri | ||||||
|  | import android.util.Base64.encodeToString | ||||||
| import androidx.annotation.WorkerThread | import androidx.annotation.WorkerThread | ||||||
| import androidx.preference.PreferenceManager | import androidx.preference.PreferenceManager | ||||||
| import com.fasterxml.jackson.databind.DeserializationFeature | import com.fasterxml.jackson.databind.DeserializationFeature | ||||||
|  | @ -121,6 +123,58 @@ object APIHolder { | ||||||
|         return url.replace(getApiFromName(apiName).mainUrl, "").replace("/", "").hashCode() |         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<String> { |     fun Context.getApiSettings(): HashSet<String> { | ||||||
|         //val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) |         //val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) | ||||||
| 
 | 
 | ||||||
|  | @ -250,14 +304,17 @@ abstract class MainAPI { | ||||||
|     suspend open fun getMainPage(): HomePageResponse? { |     suspend open fun getMainPage(): HomePageResponse? { | ||||||
|         throw NotImplementedError() |         throw NotImplementedError() | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     @WorkerThread |     @WorkerThread | ||||||
|     suspend open fun search(query: String): List<SearchResponse>? { |     suspend open fun search(query: String): List<SearchResponse>? { | ||||||
|         throw NotImplementedError() |         throw NotImplementedError() | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     @WorkerThread |     @WorkerThread | ||||||
|     suspend open fun quickSearch(query: String): List<SearchResponse>? { |     suspend open fun quickSearch(query: String): List<SearchResponse>? { | ||||||
|         throw NotImplementedError() |         throw NotImplementedError() | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     @WorkerThread |     @WorkerThread | ||||||
|     /** |     /** | ||||||
|      * Based on data from search() or getMainPage() it generates a LoadResponse, |      * Based on data from search() or getMainPage() it generates a LoadResponse, | ||||||
|  |  | ||||||
|  | @ -3,14 +3,12 @@ package com.lagradost.cloudstream3.movieproviders | ||||||
| import android.util.Log | import android.util.Log | ||||||
| import com.fasterxml.jackson.annotation.JsonProperty | import com.fasterxml.jackson.annotation.JsonProperty | ||||||
| import com.lagradost.cloudstream3.* | import com.lagradost.cloudstream3.* | ||||||
|  | import com.lagradost.cloudstream3.APIHolder.getCaptchaToken | ||||||
| import com.lagradost.cloudstream3.APIHolder.unixTimeMS | import com.lagradost.cloudstream3.APIHolder.unixTimeMS | ||||||
| import com.lagradost.cloudstream3.LoadResponse.Companion.addActors | import com.lagradost.cloudstream3.LoadResponse.Companion.addActors | ||||||
| import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration | import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration | ||||||
| import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall | import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall | ||||||
| import com.lagradost.cloudstream3.network.AppResponse | 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.parseJson | ||||||
| import com.lagradost.cloudstream3.utils.AppUtils.toJson | import com.lagradost.cloudstream3.utils.AppUtils.toJson | ||||||
| import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson | import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson | ||||||
|  | @ -277,6 +275,14 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() { | ||||||
|         @JsonProperty("tracks") val tracks: List<Tracks?>? |         @JsonProperty("tracks") val tracks: List<Tracks?>? | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|  |     data class IframeJson( | ||||||
|  | //        @JsonProperty("type") val type: String? = null, | ||||||
|  |         @JsonProperty("link") val link: String? = null, | ||||||
|  | //        @JsonProperty("sources") val sources: ArrayList<String> = arrayListOf(), | ||||||
|  | //        @JsonProperty("tracks") val tracks: ArrayList<String> = arrayListOf(), | ||||||
|  | //        @JsonProperty("title") val title: String? = null | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|     override suspend fun loadLinks( |     override suspend fun loadLinks( | ||||||
|         data: String, |         data: String, | ||||||
|         isCasting: Boolean, |         isCasting: Boolean, | ||||||
|  | @ -299,21 +305,67 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() { | ||||||
| 
 | 
 | ||||||
|         urls?.apmap { url -> |         urls?.apmap { url -> | ||||||
|             suspendSafeApiCall { |             suspendSafeApiCall { | ||||||
|                 val resolved = WebViewResolver( | //                val resolved = WebViewResolver( | ||||||
|                     Regex("""/getSources"""), | //                    Regex("""/getSources"""), | ||||||
|                     // This is unreliable, generating my own link instead | //                    // This is unreliable, generating my own link instead | ||||||
| //                  additionalUrls = listOf(Regex("""^.*transport=polling(?!.*sid=).*$""")) | ////                  additionalUrls = listOf(Regex("""^.*transport=polling(?!.*sid=).*$""")) | ||||||
|                 ).resolveUsingWebView(getRequestCreator(url)) | //                ).resolveUsingWebView(getRequestCreator(url)) | ||||||
| //              val extractorData = resolved.second.getOrNull(0)?.url?.toString() | ////              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<IframeJson>().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<SourceObject>() | ||||||
| 
 | 
 | ||||||
|                 // Some smarter ws11 or w10 selection might be required in the future. |                 // Some smarter ws11 or w10 selection might be required in the future. | ||||||
|                 val extractorData = |                 val extractorData = | ||||||
|                     "https://ws11.rabbitstream.net/socket.io/?EIO=4&transport=polling" |                     "https://ws11.rabbitstream.net/socket.io/?EIO=4&transport=polling" | ||||||
| 
 | 
 | ||||||
|                 val sources = resolved.first?.let { app.baseClient.newCall(it).execute().text } | //                val sources = resolved.first?.let { app.baseClient.newCall(it).execute().text } | ||||||
|                     ?: return@suspendSafeApiCall | //                    ?: return@suspendSafeApiCall | ||||||
| 
 | 
 | ||||||
|                 val mapped = parseJson<SourceObject>(sources) | //                val mapped = parseJson<SourceObject>(sources) | ||||||
| 
 | 
 | ||||||
|                 mapped.tracks?.forEach { |                 mapped.tracks?.forEach { | ||||||
|                     it?.toSubtitleFile()?.let { subtitleFile -> |                     it?.toSubtitleFile()?.let { subtitleFile -> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue