mirror of
				https://github.com/recloudstream/cloudstream.git
				synced 2024-08-15 01:53:11 +00:00 
			
		
		
		
	fix zoro
This commit is contained in:
		
							parent
							
								
									3be125f12a
								
							
						
					
					
						commit
						ed573f4f22
					
				
					 5 changed files with 293 additions and 203 deletions
				
			
		|  | @ -15,6 +15,7 @@ import com.lagradost.cloudstream3.metaproviders.CrossTmdbProvider | |||
| import com.lagradost.cloudstream3.movieproviders.* | ||||
| import com.lagradost.cloudstream3.ui.player.SubtitleData | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import okhttp3.Interceptor | ||||
| import java.util.* | ||||
| import kotlin.math.absoluteValue | ||||
| 
 | ||||
|  | @ -394,6 +395,11 @@ abstract class MainAPI { | |||
|     ): Boolean { | ||||
|         throw NotImplementedError() | ||||
|     } | ||||
| 
 | ||||
|     /** An okhttp interceptor for used in OkHttpDataSource */ | ||||
|     open fun getVideoInterceptor(extractorLink: ExtractorLink) : Interceptor? { | ||||
|         return null | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** Might need a different implementation for desktop*/ | ||||
|  |  | |||
|  | @ -1,22 +1,23 @@ | |||
| package com.lagradost.cloudstream3.animeproviders | ||||
| 
 | ||||
| import android.util.Log | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.fasterxml.jackson.databind.util.NameTransformer | ||||
| import com.fasterxml.jackson.module.kotlin.readValue | ||||
| import com.lagradost.cloudstream3.* | ||||
| import com.lagradost.cloudstream3.APIHolder.getCaptchaToken | ||||
| import com.lagradost.cloudstream3.movieproviders.SflixProvider | ||||
| import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.extractRabbitStream | ||||
| import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.toExtractorLink | ||||
| import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.toSubtitleFile | ||||
| import com.lagradost.cloudstream3.network.WebViewResolver | ||||
| import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.runSflixExtractorVerifierJob | ||||
| import com.lagradost.cloudstream3.network.Requests.Companion.await | ||||
| import com.lagradost.cloudstream3.utils.Coroutines.ioSafe | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.loadExtractor | ||||
| import okhttp3.Interceptor | ||||
| import org.jsoup.Jsoup | ||||
| import org.jsoup.nodes.Element | ||||
| import java.net.URI | ||||
| import java.util.* | ||||
| 
 | ||||
| private const val OPTIONS = "OPTIONS" | ||||
| 
 | ||||
| class ZoroProvider : MainAPI() { | ||||
|     override var mainUrl = "https://zoro.to" | ||||
|     override var name = "Zoro" | ||||
|  | @ -285,26 +286,48 @@ class ZoroProvider : MainAPI() { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private suspend fun getM3u8FromRapidCloud(url: String): String { | ||||
|         return /*Regex("""/(embed-\d+)/(.*?)\?z=""").find(url)?.groupValues?.let { | ||||
|             val jsonLink = "https://rapid-cloud.ru/ajax/${it[1]}/getSources?id=${it[2]}" | ||||
|             app.get(jsonLink).text | ||||
|         } ?:*/ app.get( | ||||
|             "$url&autoPlay=1&oa=0", | ||||
|             headers = mapOf( | ||||
|                 "Referer" to "https://zoro.to/", | ||||
|                 "User-Agent" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0" | ||||
|             ), | ||||
|             interceptor = WebViewResolver( | ||||
|                 Regex("""/getSources""") | ||||
|             ) | ||||
|         ).text | ||||
|     } | ||||
| 
 | ||||
|     private data class RapidCloudResponse( | ||||
|         @JsonProperty("link") val link: String | ||||
|     ) | ||||
| 
 | ||||
|     override suspend fun extractorVerifierJob(extractorData: String?) { | ||||
|         Log.d(this.name, "Starting ${this.name} job!") | ||||
|         runSflixExtractorVerifierJob(this, extractorData, "https://rapid-cloud.ru/") | ||||
|     } | ||||
| 
 | ||||
|     /** Url hashcode to sid */ | ||||
|     var sid: HashMap<Int, String?> = hashMapOf() | ||||
| 
 | ||||
|     /** | ||||
|      * Makes an identical Options request before .ts request | ||||
|      * Adds an SID header to the .ts request. | ||||
|      * */ | ||||
|     override fun getVideoInterceptor(extractorLink: ExtractorLink): Interceptor { | ||||
|         return Interceptor { chain -> | ||||
|             val request = chain.request() | ||||
|             if (request.url.toString().endsWith(".ts") | ||||
|                 && request.method != OPTIONS | ||||
|                 // No option requests on VidCloud | ||||
|                 && !request.url.toString().contains("betterstream") | ||||
|             ) { | ||||
|                 val newRequest = | ||||
|                     chain.request() | ||||
|                         .newBuilder().apply { | ||||
|                             sid[extractorLink.url.hashCode()]?.let { sid -> | ||||
|                                 addHeader("SID", sid) | ||||
|                             } | ||||
|                         } | ||||
|                         .build() | ||||
|                 val options = request.newBuilder().method(OPTIONS, request.body).build() | ||||
|                 ioSafe { app.baseClient.newCall(options).await() } | ||||
| 
 | ||||
|                 return@Interceptor chain.proceed(newRequest) | ||||
|             } else { | ||||
|                 return@Interceptor chain.proceed(chain.request()) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     override suspend fun loadLinks( | ||||
|         data: String, | ||||
|         isCasting: Boolean, | ||||
|  | @ -322,6 +345,8 @@ class ZoroProvider : MainAPI() { | |||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         val extractorData = | ||||
|             "https://ws1.rapid-cloud.ru/socket.io/?EIO=4&transport=polling" | ||||
| 
 | ||||
|         // Prevent duplicates | ||||
|         servers.distinctBy { it.second }.apmap { | ||||
|  | @ -330,11 +355,18 @@ class ZoroProvider : MainAPI() { | |||
|             val extractorLink = app.get( | ||||
|                 link, | ||||
|             ).mapped<RapidCloudResponse>().link | ||||
|             val hasLoadedExtractorLink = loadExtractor(extractorLink, mainUrl, callback) | ||||
|             val hasLoadedExtractorLink = | ||||
|                 loadExtractor(extractorLink, "https://rapid-cloud.ru/", callback) | ||||
| 
 | ||||
|             if (!hasLoadedExtractorLink) { | ||||
|                 extractRabbitStream(extractorLink, subtitleCallback, callback) { sourceName -> | ||||
|                      sourceName + " - ${it.first}" | ||||
|                 extractRabbitStream( | ||||
|                     extractorLink, | ||||
|                     subtitleCallback, | ||||
|                     // Blacklist VidCloud for now | ||||
|                     { videoLink -> if (!videoLink.url.contains("betterstream")) callback(videoLink) }, | ||||
|                     extractorData | ||||
|                 ) { sourceName -> | ||||
|                     sourceName + " - ${it.first}" | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -7,11 +7,13 @@ 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.animeproviders.ZoroProvider | ||||
| import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall | ||||
| import com.lagradost.cloudstream3.network.AppResponse | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.parseJson | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.toJson | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson | ||||
| import com.lagradost.cloudstream3.utils.Coroutines.ioSafe | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.M3u8Helper | ||||
| import com.lagradost.cloudstream3.utils.getQualityFromName | ||||
|  | @ -357,182 +359,24 @@ open class SflixProvider : MainAPI() { | |||
|         return !urls.isNullOrEmpty() | ||||
|     } | ||||
| 
 | ||||
|     data class PollingData( | ||||
|         @JsonProperty("sid") val sid: String? = null, | ||||
|         @JsonProperty("upgrades") val upgrades: ArrayList<String> = arrayListOf(), | ||||
|         @JsonProperty("pingInterval") val pingInterval: Int? = null, | ||||
|         @JsonProperty("pingTimeout") val pingTimeout: Int? = null | ||||
|     ) | ||||
| 
 | ||||
|     /* | ||||
|     # python code to figure out the time offset based on code if necessary | ||||
|     chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_" | ||||
|     code = "Nxa_-bM" | ||||
|     total = 0 | ||||
|     for i, char in enumerate(code[::-1]): | ||||
|         index = chars.index(char) | ||||
|         value = index * 64**i | ||||
|         total += value | ||||
|     print(f"total {total}") | ||||
|     */ | ||||
|     private fun generateTimeStamp(): String { | ||||
|         val chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_" | ||||
|         var code = "" | ||||
|         var time = unixTimeMS | ||||
|         while (time > 0) { | ||||
|             code += chars[(time % (chars.length)).toInt()] | ||||
|             time /= chars.length | ||||
|         } | ||||
|         return code.reversed() | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Generates a session | ||||
|      * */ | ||||
|     private suspend fun negotiateNewSid(baseUrl: String): PollingData? { | ||||
|         // Tries multiple times | ||||
|         for (i in 1..5) { | ||||
|             val jsonText = | ||||
|                 app.get("$baseUrl&t=${generateTimeStamp()}").text.replaceBefore("{", "") | ||||
| //            println("Negotiated sid $jsonText") | ||||
|             parseJson<PollingData?>(jsonText)?.let { return it } | ||||
|             delay(1000L * i) | ||||
|         } | ||||
|         return null | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Generates a new session if the request fails | ||||
|      * @return the data and if it is new. | ||||
|      * */ | ||||
|     private suspend fun getUpdatedData( | ||||
|         response: AppResponse, | ||||
|         data: PollingData, | ||||
|         baseUrl: String | ||||
|     ): Pair<PollingData, Boolean> { | ||||
|         if (!response.response.isSuccessful) { | ||||
|             return negotiateNewSid(baseUrl)?.let { | ||||
|                 it to true | ||||
|             } ?: data to false | ||||
|         } | ||||
|         return data to false | ||||
|     } | ||||
| 
 | ||||
|     override suspend fun extractorVerifierJob(extractorData: String?) { | ||||
|         if (extractorData == null) return | ||||
| 
 | ||||
|         val headers = mapOf( | ||||
|             "Referer" to "https://rabbitstream.net/" | ||||
|         ) | ||||
| 
 | ||||
|         var data = negotiateNewSid(extractorData) ?: return | ||||
|         // 40 is hardcoded, dunno how it's generated, but it seems to work everywhere. | ||||
|         // This request is obligatory | ||||
|         app.post( | ||||
|             "$extractorData&t=${generateTimeStamp()}&sid=${data.sid}", | ||||
|             data = 40, headers = headers | ||||
|         )//.also { println("First post ${it.text}") } | ||||
|         // This makes the second get request work, and re-connect work. | ||||
|         val reconnectSid = | ||||
|             parseJson<PollingData>( | ||||
|                 app.get( | ||||
|                     "$extractorData&t=${generateTimeStamp()}&sid=${data.sid}", | ||||
|                     headers = headers | ||||
|                 ) | ||||
| //                    .also { println("First get ${it.text}") } | ||||
|                     .text.replaceBefore("{", "") | ||||
|             ).sid | ||||
|         // This response is used in the post requests. Same contents in all it seems. | ||||
|         val authInt = | ||||
|             app.get( | ||||
|                 "$extractorData&t=${generateTimeStamp()}&sid=${data.sid}", | ||||
|                 timeout = 60, | ||||
|                 headers = headers | ||||
|             ).text | ||||
|                 //.also { println("Second get ${it}") } | ||||
|                 // Dunno if it's actually generated like this, just guessing. | ||||
|                 .toIntOrNull()?.plus(1) ?: 3 | ||||
| 
 | ||||
|         // Prevents them from fucking us over with doing a while(true){} loop | ||||
|         val interval = maxOf(data.pingInterval?.toLong()?.plus(2000) ?: return, 10000L) | ||||
|         var reconnect = false | ||||
|         var newAuth = false | ||||
|         while (true) { | ||||
|             val authData = | ||||
|                 when { | ||||
|                     newAuth -> "40" | ||||
|                     reconnect -> """42["_reconnect", "$reconnectSid"]""" | ||||
|                     else -> authInt | ||||
|                 } | ||||
| 
 | ||||
|             val url = "${extractorData}&t=${generateTimeStamp()}&sid=${data.sid}" | ||||
| 
 | ||||
|             getUpdatedData( | ||||
|                 app.post(url, data = authData, headers = headers), | ||||
|                 data, | ||||
|                 extractorData | ||||
|             ).also { | ||||
|                 newAuth = it.second | ||||
|                 data = it.first | ||||
|             } | ||||
| 
 | ||||
|             //.also { println("Sflix post job ${it.text}") } | ||||
|             Log.d(this.name, "Running ${this.name} job $url") | ||||
| 
 | ||||
|             val time = measureTimeMillis { | ||||
|                 // This acts as a timeout | ||||
|                 val getResponse = app.get( | ||||
|                     "${extractorData}&t=${generateTimeStamp()}&sid=${data.sid}", | ||||
|                     timeout = 60, | ||||
|                     headers = headers | ||||
|                 ) | ||||
| //                    .also { println("Sflix get job ${it.text}") } | ||||
|                 if (getResponse.text.contains("sid")) { | ||||
|                     reconnect = true | ||||
| //                    println("Reconnecting") | ||||
|                 } | ||||
|             } | ||||
|             // Always waits even if the get response is instant, to prevent a while true loop. | ||||
|             if (time < interval - 4000) | ||||
|                 delay(4000) | ||||
|         } | ||||
|         runSflixExtractorVerifierJob(this, extractorData, "https://rabbitstream.net/") | ||||
|     } | ||||
| 
 | ||||
|     private fun Element.toSearchResult(): SearchResponse { | ||||
|         val inner = this.selectFirst("div.film-poster") | ||||
|         val img = inner.select("img") | ||||
|         val img = this.select("img") | ||||
|         val title = img.attr("title") | ||||
|         val posterUrl = img.attr("data-src") ?: img.attr("src") | ||||
|         val href = fixUrl(inner.select("a").attr("href")) | ||||
|         val posterUrl = img.attr("data-src") | ||||
|         val href = fixUrl(this.select("a").attr("href")) | ||||
|         val isMovie = href.contains("/movie/") | ||||
|         val otherInfo = this.selectFirst("div.film-detail > div.fd-infor")?.select("span")?.toList() ?: listOf() | ||||
|         var rating: Int? = null | ||||
|         var year: Int? = null | ||||
|         var quality: SearchQuality? = null | ||||
|         when (otherInfo.size) { | ||||
|             1 -> { | ||||
|                 year = otherInfo[0]?.text()?.trim()?.toIntOrNull() | ||||
|             } | ||||
|             2 -> { | ||||
|                 year = otherInfo[0]?.text()?.trim()?.toIntOrNull() | ||||
|             } | ||||
|             3 -> { | ||||
|                 rating = otherInfo[0]?.text()?.toRatingInt() | ||||
|                 quality = getQualityFromString(otherInfo[1]?.text()) | ||||
|                 year = otherInfo[2]?.text()?.trim()?.toIntOrNull() | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return if (isMovie) { | ||||
|             MovieSearchResponse( | ||||
|                 title, | ||||
|                 href, | ||||
|                 this@SflixProvider.name, | ||||
|                 TvType.Movie, | ||||
|                 posterUrl = posterUrl, | ||||
|                 year = year, | ||||
|                 quality = quality | ||||
|                 posterUrl, | ||||
|                 null | ||||
|             ) | ||||
|         } else { | ||||
|             TvSeriesSearchResponse( | ||||
|  | @ -541,14 +385,179 @@ open class SflixProvider : MainAPI() { | |||
|                 this@SflixProvider.name, | ||||
|                 TvType.Movie, | ||||
|                 posterUrl, | ||||
|                 year = year, | ||||
|                 episodes = null, | ||||
|                 quality = quality | ||||
|                 null, | ||||
|                 null | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|         data class PollingData( | ||||
|             @JsonProperty("sid") val sid: String? = null, | ||||
|             @JsonProperty("upgrades") val upgrades: ArrayList<String> = arrayListOf(), | ||||
|             @JsonProperty("pingInterval") val pingInterval: Int? = null, | ||||
|             @JsonProperty("pingTimeout") val pingTimeout: Int? = null | ||||
|         ) | ||||
| 
 | ||||
|         /* | ||||
|         # python code to figure out the time offset based on code if necessary | ||||
|         chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_" | ||||
|         code = "Nxa_-bM" | ||||
|         total = 0 | ||||
|         for i, char in enumerate(code[::-1]): | ||||
|             index = chars.index(char) | ||||
|             value = index * 64**i | ||||
|             total += value | ||||
|         print(f"total {total}") | ||||
|         */ | ||||
|         private fun generateTimeStamp(): String { | ||||
|             val chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_" | ||||
|             var code = "" | ||||
|             var time = unixTimeMS | ||||
|             while (time > 0) { | ||||
|                 code += chars[(time % (chars.length)).toInt()] | ||||
|                 time /= chars.length | ||||
|             } | ||||
|             return code.reversed() | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         /** | ||||
|          * Generates a session | ||||
|          * 1 Get request. | ||||
|          * */ | ||||
|         private suspend fun negotiateNewSid(baseUrl: String): PollingData? { | ||||
|             // Tries multiple times | ||||
|             for (i in 1..5) { | ||||
|                 val jsonText = | ||||
|                     app.get("$baseUrl&t=${generateTimeStamp()}").text.replaceBefore("{", "") | ||||
| //            println("Negotiated sid $jsonText") | ||||
|                 parseJson<PollingData?>(jsonText)?.let { return it } | ||||
|                 delay(1000L * i) | ||||
|             } | ||||
|             return null | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * Generates a new session if the request fails | ||||
|          * @return the data and if it is new. | ||||
|          * */ | ||||
|         private suspend fun getUpdatedData( | ||||
|             response: AppResponse, | ||||
|             data: PollingData, | ||||
|             baseUrl: String | ||||
|         ): Pair<PollingData, Boolean> { | ||||
|             if (!response.response.isSuccessful) { | ||||
|                 return negotiateNewSid(baseUrl)?.let { | ||||
|                     it to true | ||||
|                 } ?: data to false | ||||
|             } | ||||
|             return data to false | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         private suspend fun initPolling( | ||||
|             extractorData: String, | ||||
|             referer: String | ||||
|         ): Pair<PollingData?, String?> { | ||||
|             val headers = mapOf( | ||||
|                 "Referer" to referer // "https://rabbitstream.net/" | ||||
|             ) | ||||
| 
 | ||||
|             val data = negotiateNewSid(extractorData) ?: return null to null | ||||
|             app.post( | ||||
|                 "$extractorData&t=${generateTimeStamp()}&sid=${data.sid}", | ||||
|                 data = 40, headers = headers | ||||
|             ) | ||||
| 
 | ||||
|             // This makes the second get request work, and re-connect work. | ||||
|             val reconnectSid = | ||||
|                 parseJson<PollingData>( | ||||
|                     app.get( | ||||
|                         "$extractorData&t=${generateTimeStamp()}&sid=${data.sid}", | ||||
|                         headers = headers | ||||
|                     ) | ||||
| //                    .also { println("First get ${it.text}") } | ||||
|                         .text.replaceBefore("{", "") | ||||
|                 ).sid | ||||
| 
 | ||||
|             // This response is used in the post requests. Same contents in all it seems. | ||||
|             val authInt = | ||||
|                 app.get( | ||||
|                     "$extractorData&t=${generateTimeStamp()}&sid=${data.sid}", | ||||
|                     timeout = 60, | ||||
|                     headers = headers | ||||
|                 ).text | ||||
|                     //.also { println("Second get ${it}") } | ||||
|                     // Dunno if it's actually generated like this, just guessing. | ||||
|                     .toIntOrNull()?.plus(1) ?: 3 | ||||
| 
 | ||||
|             return data to reconnectSid | ||||
|         } | ||||
| 
 | ||||
|         suspend fun runSflixExtractorVerifierJob( | ||||
|             api: MainAPI, | ||||
|             extractorData: String?, | ||||
|             referer: String | ||||
|         ) { | ||||
|             if (extractorData == null) return | ||||
|             val headers = mapOf( | ||||
|                 "Referer" to referer // "https://rabbitstream.net/" | ||||
|             ) | ||||
| 
 | ||||
|             lateinit var data: PollingData | ||||
|             var reconnectSid = "" | ||||
| 
 | ||||
|             initPolling(extractorData, referer) | ||||
|                 .also { | ||||
|                     data = it.first ?: throw RuntimeException("Data Null") | ||||
|                     reconnectSid = it.second ?: throw RuntimeException("ReconnectSid Null") | ||||
|                 } | ||||
| 
 | ||||
|             // Prevents them from fucking us over with doing a while(true){} loop | ||||
|             val interval = maxOf(data.pingInterval?.toLong()?.plus(2000) ?: return, 10000L) | ||||
|             var reconnect = false | ||||
|             var newAuth = false | ||||
| 
 | ||||
| 
 | ||||
|             while (true) { | ||||
|                 val authData = | ||||
|                     when { | ||||
|                         newAuth -> "40" | ||||
|                         reconnect -> """42["_reconnect", "$reconnectSid"]""" | ||||
|                         else -> "3" | ||||
|                     } | ||||
| 
 | ||||
|                 val url = "${extractorData}&t=${generateTimeStamp()}&sid=${data.sid}" | ||||
| 
 | ||||
|                 getUpdatedData( | ||||
|                     app.post(url, data = authData, headers = headers), | ||||
|                     data, | ||||
|                     extractorData | ||||
|                 ).also { | ||||
|                     newAuth = it.second | ||||
|                     data = it.first | ||||
|                 } | ||||
| 
 | ||||
|                 //.also { println("Sflix post job ${it.text}") } | ||||
|                 Log.d(api.name, "Running ${api.name} job $url") | ||||
| 
 | ||||
|                 val time = measureTimeMillis { | ||||
|                     // This acts as a timeout | ||||
|                     val getResponse = app.get( | ||||
|                         url, | ||||
|                         timeout = interval / 1000, | ||||
|                         headers = headers | ||||
|                     ) | ||||
| //                    .also { println("Sflix get job ${it.text}") } | ||||
|                     reconnect = getResponse.text.contains("sid") | ||||
|                 } | ||||
|                 // Always waits even if the get response is instant, to prevent a while true loop. | ||||
|                 if (time < interval - 4000) | ||||
|                     delay(4000) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         fun String?.isValidServer(): Boolean { | ||||
|             if (this.isNullOrEmpty()) return false | ||||
|             if (this.equals("UpCloud", ignoreCase = true) || this.equals( | ||||
|  | @ -563,7 +572,7 @@ open class SflixProvider : MainAPI() { | |||
|         fun Sources.toExtractorLink( | ||||
|             caller: MainAPI, | ||||
|             name: String, | ||||
|             extractorData: String? = null | ||||
|             extractorData: String? = null, | ||||
|         ): List<ExtractorLink>? { | ||||
|             return this.file?.let { file -> | ||||
|                 //println("FILE::: $file") | ||||
|  | @ -632,13 +641,30 @@ open class SflixProvider : MainAPI() { | |||
|             val number = | ||||
|                 Regex("""recaptchaNumber = '(.*?)'""").find(iframe.text)?.groupValues?.get(1) | ||||
| 
 | ||||
|             var sid: String? = null | ||||
| 
 | ||||
|             extractorData?.let { negotiateNewSid(it) }?.also { | ||||
|                 app.post( | ||||
|                     "$extractorData&t=${generateTimeStamp()}&sid=${it.sid}", | ||||
|                     data = "40", | ||||
|                     timeout = 60 | ||||
|                 ) | ||||
|                 val text = app.get( | ||||
|                     "$extractorData&t=${generateTimeStamp()}&sid=${it.sid}", | ||||
|                     timeout = 60 | ||||
|                 ).text.replaceBefore("{", "") | ||||
| 
 | ||||
|                 sid = parseJson<PollingData>(text).sid | ||||
|                 ioSafe { app.get("$extractorData&t=${generateTimeStamp()}&sid=${it.sid}") } | ||||
|             } | ||||
| 
 | ||||
|             val mapped = app.get( | ||||
|                 "${ | ||||
|                     mainIframeUrl.replace( | ||||
|                         "/embed", | ||||
|                         "/ajax/embed" | ||||
|                     ) | ||||
|                 }/getSources?id=$mainIframeId&_token=$iframeToken&_number=$number", | ||||
|                 }/getSources?id=$mainIframeId&_token=$iframeToken&_number=$number${sid?.let { "&sid=$it" } ?: ""}", | ||||
|                 referer = mainUrl, | ||||
|                 headers = mapOf( | ||||
|                     "X-Requested-With" to "XMLHttpRequest", | ||||
|  | @ -667,11 +693,18 @@ open class SflixProvider : MainAPI() { | |||
|                 mapped.sources2 to "source 3", | ||||
|                 mapped.sourcesBackup to "source backup" | ||||
|             ) | ||||
| 
 | ||||
|             list.forEach { subList -> | ||||
|                 subList.first?.forEach { source -> | ||||
|                     source?.toExtractorLink(this, nameTransformer(subList.second), extractorData) | ||||
|                         ?.forEach(callback) | ||||
|                     source?.toExtractorLink( | ||||
|                         this, | ||||
|                         nameTransformer(subList.second), | ||||
|                         extractorData, | ||||
|                     ) | ||||
|                         ?.forEach { | ||||
|                             // Sets Zoro SID used for video loading | ||||
|                             (this as? ZoroProvider)?.sid?.set(it.url.hashCode(), sid) | ||||
|                             callback(it) | ||||
|                         } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ class APIRepository(val api: MainAPI) { | |||
|             override val supportedTypes = emptySet<TvType>() | ||||
|         } | ||||
| 
 | ||||
|         fun isInvalidData(data : String): Boolean { | ||||
|         fun isInvalidData(data: String): Boolean { | ||||
|             return data.isEmpty() || data == "[]" || data == "about:blank" | ||||
|         } | ||||
|     } | ||||
|  | @ -30,7 +30,7 @@ class APIRepository(val api: MainAPI) { | |||
|     val hasQuickSearch = api.hasQuickSearch | ||||
| 
 | ||||
|     suspend fun load(url: String): Resource<LoadResponse> { | ||||
|         if(isInvalidData(url)) throw ErrorLoadingException() | ||||
|         if (isInvalidData(url)) throw ErrorLoadingException() | ||||
| 
 | ||||
|         return safeApiCall { | ||||
|             api.load(api.fixUrl(url)) ?: throw ErrorLoadingException() | ||||
|  | @ -64,6 +64,12 @@ class APIRepository(val api: MainAPI) { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     suspend fun extractorVerifierJob(extractorData: String?) { | ||||
|         safeApiCall { | ||||
|             api.extractorVerifierJob(extractorData) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     suspend fun loadLinks( | ||||
|         data: String, | ||||
|         isCasting: Boolean, | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ import android.util.Log | |||
| import android.widget.FrameLayout | ||||
| import com.google.android.exoplayer2.* | ||||
| import com.google.android.exoplayer2.database.StandaloneDatabaseProvider | ||||
| import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource | ||||
| import com.google.android.exoplayer2.source.DefaultMediaSourceFactory | ||||
| import com.google.android.exoplayer2.source.MergingMediaSource | ||||
| import com.google.android.exoplayer2.source.SingleSampleMediaSource | ||||
|  | @ -17,12 +18,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelector | |||
| import com.google.android.exoplayer2.ui.SubtitleView | ||||
| import com.google.android.exoplayer2.upstream.DataSource | ||||
| import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory | ||||
| import com.google.android.exoplayer2.upstream.DefaultHttpDataSource | ||||
| import com.google.android.exoplayer2.upstream.cache.CacheDataSource | ||||
| import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor | ||||
| import com.google.android.exoplayer2.upstream.cache.SimpleCache | ||||
| import com.google.android.exoplayer2.util.MimeTypes | ||||
| import com.lagradost.cloudstream3.APIHolder.getApiFromName | ||||
| import com.lagradost.cloudstream3.USER_AGENT | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.mvvm.logError | ||||
| import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
|  | @ -307,8 +309,19 @@ class CS3IPlayer : IPlayer { | |||
|         var requestSubtitleUpdate: (() -> Unit)? = null | ||||
| 
 | ||||
|         private fun createOnlineSource(link: ExtractorLink): DataSource.Factory { | ||||
|             // Because Trailers.to seems to fail with http/1.1 the normal one uses. | ||||
|             return DefaultHttpDataSource.Factory().apply { | ||||
|             val provider = getApiFromName(link.source) | ||||
|             val interceptor = provider.getVideoInterceptor(link) | ||||
| 
 | ||||
|             val client = app.baseClient | ||||
|                 .let { | ||||
|                     if (interceptor != null) | ||||
|                         it.newBuilder() | ||||
|                             .addInterceptor(interceptor) | ||||
|                             .build() | ||||
|                     else it | ||||
|                 } | ||||
| 
 | ||||
|             return OkHttpDataSource.Factory(client).apply { | ||||
|                 setUserAgent(USER_AGENT) | ||||
|                 val headers = mapOf( | ||||
|                     "referer" to link.referer, | ||||
|  | @ -322,7 +335,7 @@ class CS3IPlayer : IPlayer { | |||
|                 setDefaultRequestProperties(headers) | ||||
| 
 | ||||
|                 //https://stackoverflow.com/questions/69040127/error-code-io-bad-http-status-exoplayer-android | ||||
|                 setAllowCrossProtocolRedirects(true) | ||||
| //                setAllowCrossProtocolRedirects(true) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue