mirror of
				https://github.com/recloudstream/cloudstream-extensions.git
				synced 2024-08-15 03:03:54 +00:00 
			
		
		
		
	Fix SflixProvider.kt
This commit is contained in:
		
							parent
							
								
									7ecd74b98f
								
							
						
					
					
						commit
						8910da0fd0
					
				
					 3 changed files with 129 additions and 41 deletions
				
			
		|  | @ -1,5 +1,5 @@ | |||
| // use an integer for version numbers | ||||
| version = 2 | ||||
| version = 3 | ||||
| 
 | ||||
| 
 | ||||
| cloudstream { | ||||
|  |  | |||
|  | @ -23,7 +23,12 @@ import okhttp3.RequestBody.Companion.toRequestBody | |||
| import org.jsoup.Jsoup | ||||
| import org.jsoup.nodes.Element | ||||
| import java.net.URI | ||||
| import java.nio.charset.StandardCharsets | ||||
| import java.security.MessageDigest | ||||
| import java.util.* | ||||
| import javax.crypto.Cipher | ||||
| import javax.crypto.spec.IvParameterSpec | ||||
| import javax.crypto.spec.SecretKeySpec | ||||
| import kotlin.system.measureTimeMillis | ||||
| 
 | ||||
| open class SflixProvider : MainAPI() { | ||||
|  | @ -41,7 +46,7 @@ open class SflixProvider : MainAPI() { | |||
|     ) | ||||
|     override val vpnStatus = VPNStatus.None | ||||
| 
 | ||||
|     override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse { | ||||
|     override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { | ||||
|         val html = app.get("$mainUrl/home").text | ||||
|         val document = Jsoup.parse(html) | ||||
| 
 | ||||
|  | @ -290,10 +295,18 @@ open class SflixProvider : MainAPI() { | |||
|     ) | ||||
| 
 | ||||
|     data class SourceObject( | ||||
|         @JsonProperty("sources") val sources: List<Sources?>?, | ||||
|         @JsonProperty("sources_1") val sources1: List<Sources?>?, | ||||
|         @JsonProperty("sources_2") val sources2: List<Sources?>?, | ||||
|         @JsonProperty("sourcesBackup") val sourcesBackup: List<Sources?>?, | ||||
|         @JsonProperty("sources") val sources: List<Sources?>? = null, | ||||
|         @JsonProperty("sources_1") val sources1: List<Sources?>? = null, | ||||
|         @JsonProperty("sources_2") val sources2: List<Sources?>? = null, | ||||
|         @JsonProperty("sourcesBackup") val sourcesBackup: List<Sources?>? = null, | ||||
|         @JsonProperty("tracks") val tracks: List<Tracks?>? = null | ||||
|     ) | ||||
| 
 | ||||
|     data class SourceObjectEncrypted( | ||||
|         @JsonProperty("sources") val sources: String?, | ||||
|         @JsonProperty("sources_1") val sources1: String?, | ||||
|         @JsonProperty("sources_2") val sources2: String?, | ||||
|         @JsonProperty("sourcesBackup") val sourcesBackup: String?, | ||||
|         @JsonProperty("tracks") val tracks: List<Tracks?>? | ||||
|     ) | ||||
| 
 | ||||
|  | @ -305,6 +318,15 @@ open class SflixProvider : MainAPI() { | |||
| //        @JsonProperty("title") val title: String? = null | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
|     open suspend fun getKey(): String? { | ||||
|         data class KeyObject( | ||||
|             @JsonProperty("key") val key: String? = null | ||||
|         ) | ||||
|         return app.get("https://raw.githubusercontent.com/chenkaslowankiya/BruhGlow/main/keys.json") | ||||
|             .parsed<KeyObject>().key | ||||
|     } | ||||
| 
 | ||||
|     override suspend fun loadLinks( | ||||
|         data: String, | ||||
|         isCasting: Boolean, | ||||
|  | @ -347,7 +369,13 @@ open class SflixProvider : MainAPI() { | |||
|                 if (iframeLink.contains("streamlare", ignoreCase = true)) { | ||||
|                     loadExtractor(iframeLink, null, subtitleCallback, callback) | ||||
|                 } else { | ||||
|                     extractRabbitStream(iframeLink, subtitleCallback, callback, false) { it } | ||||
|                     extractRabbitStream( | ||||
|                         iframeLink, | ||||
|                         subtitleCallback, | ||||
|                         callback, | ||||
|                         false, | ||||
|                         decryptKey = getKey() | ||||
|                     ) { it } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | @ -468,7 +496,7 @@ open class SflixProvider : MainAPI() { | |||
|             if (!response.okhttpResponse.isSuccessful) { | ||||
|                 return negotiateNewSid(baseUrl)?.let { | ||||
|                     it to true | ||||
|                 } ?: data to false | ||||
|                 } ?: (data to false) | ||||
|             } | ||||
|             return data to false | ||||
|         } | ||||
|  | @ -652,6 +680,49 @@ open class SflixProvider : MainAPI() { | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private fun md5(input: ByteArray): ByteArray { | ||||
|             return MessageDigest.getInstance("MD5").digest(input) | ||||
|         } | ||||
| 
 | ||||
|         private fun generateKey(salt: ByteArray, secret: ByteArray): ByteArray { | ||||
|             var key = md5(secret + salt) | ||||
|             var currentKey = key | ||||
|             while (currentKey.size < 48) { | ||||
|                 key = md5(key + secret + salt) | ||||
|                 currentKey += key | ||||
|             } | ||||
|             return currentKey | ||||
|         } | ||||
| 
 | ||||
|         private fun decryptSourceUrl(decryptionKey: ByteArray, sourceUrl: String): String { | ||||
|             val cipherData = base64DecodeArray(sourceUrl) | ||||
|             val encrypted = cipherData.copyOfRange(16, cipherData.size) | ||||
|             val aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding") | ||||
| 
 | ||||
|             Objects.requireNonNull(aesCBC).init( | ||||
|                 Cipher.DECRYPT_MODE, SecretKeySpec( | ||||
|                     decryptionKey.copyOfRange(0, 32), | ||||
|                     "AES" | ||||
|                 ), | ||||
|                 IvParameterSpec(decryptionKey.copyOfRange(32, decryptionKey.size)) | ||||
|             ) | ||||
|             val decryptedData = aesCBC!!.doFinal(encrypted) | ||||
|             return String(decryptedData, StandardCharsets.UTF_8) | ||||
|         } | ||||
| 
 | ||||
|         private inline fun <reified T> decryptMapped(input: String, key: String): T? { | ||||
|             return tryParseJson(decrypt(input, key)) | ||||
|         } | ||||
| 
 | ||||
|         private fun decrypt(input: String, key: String): String { | ||||
|             return decryptSourceUrl( | ||||
|                 generateKey( | ||||
|                     base64DecodeArray(input).copyOfRange(8, 16), | ||||
|                     key.toByteArray() | ||||
|                 ), input | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         suspend fun MainAPI.extractRabbitStream( | ||||
|             url: String, | ||||
|             subtitleCallback: (SubtitleFile) -> Unit, | ||||
|  | @ -659,6 +730,7 @@ open class SflixProvider : MainAPI() { | |||
|             useSidAuthentication: Boolean, | ||||
|             /** Used for extractorLink name, input: Source name */ | ||||
|             extractorData: String? = null, | ||||
|             decryptKey: String? = null, | ||||
|             nameTransformer: (String) -> String, | ||||
|         ) = suspendSafeApiCall { | ||||
|             // https://rapid-cloud.ru/embed-6/dcPOVRE57YOT?z= -> https://rapid-cloud.ru/embed-6 | ||||
|  | @ -666,13 +738,13 @@ open class SflixProvider : MainAPI() { | |||
|                 url.substringBeforeLast("/") | ||||
|             val mainIframeId = url.substringAfterLast("/") | ||||
|                 .substringBefore("?") // https://rapid-cloud.ru/embed-6/dcPOVRE57YOT?z= -> dcPOVRE57YOT | ||||
|             val iframe = app.get(url, referer = mainUrl) | ||||
|             val iframeKey = | ||||
|                 iframe.document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]") | ||||
|                     .attr("src").substringAfter("render=") | ||||
|             val iframeToken = getCaptchaToken(url, iframeKey) | ||||
|             val number = | ||||
|                 Regex("""recaptchaNumber = '(.*?)'""").find(iframe.text)?.groupValues?.get(1) | ||||
| //            val iframe = app.get(url, referer = mainUrl) | ||||
| //            val iframeKey = | ||||
| //                iframe.document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]") | ||||
| //                    .attr("src").substringAfter("render=") | ||||
| //            val iframeToken = getCaptchaToken(url, iframeKey) | ||||
| //            val number = | ||||
| //                Regex("""recaptchaNumber = '(.*?)'""").find(iframe.text)?.groupValues?.get(1) | ||||
| 
 | ||||
|             var sid: String? = null | ||||
|             if (useSidAuthentication && extractorData != null) { | ||||
|  | @ -691,42 +763,52 @@ open class SflixProvider : MainAPI() { | |||
|                     ioSafe { app.get("$extractorData&t=${generateTimeStamp()}&sid=${pollingData.sid}") } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             val mapped = app.get( | ||||
|                 "${ | ||||
|                     mainIframeUrl.replace( | ||||
|                         "/embed", | ||||
|                         "/ajax/embed" | ||||
|                     ) | ||||
|                 }/getSources?id=$mainIframeId&_token=$iframeToken&_number=$number${sid?.let { "$&sId=$it" } ?: ""}", | ||||
|             val getSourcesUrl = "${ | ||||
|                 mainIframeUrl.replace( | ||||
|                     "/embed", | ||||
|                     "/ajax/embed" | ||||
|                 ) | ||||
|             }/getSources?id=$mainIframeId${sid?.let { "$&sId=$it" } ?: ""}" | ||||
|             val response = app.get( | ||||
|                 getSourcesUrl, | ||||
|                 referer = mainUrl, | ||||
|                 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" | ||||
|                 ) | ||||
|             ).parsed<SourceObject>() | ||||
|             ) | ||||
| 
 | ||||
|             mapped.tracks?.forEach { track -> | ||||
|             val sourceObject = if (response.text.contains("encrypted") && decryptKey != null) { | ||||
|                 val encryptedMap = response.parsedSafe<SourceObjectEncrypted>() | ||||
|                 val sources = | ||||
|                     encryptedMap?.sources ?: throw RuntimeException("NO SOURCES $encryptedMap") | ||||
| 
 | ||||
|                 val decrypted = decryptMapped<List<Sources>>(sources, decryptKey) | ||||
|                 SourceObject( | ||||
|                     sources = decrypted, | ||||
|                     tracks = encryptedMap.tracks | ||||
|                 ) | ||||
|             } else { | ||||
|                 response.parsedSafe() | ||||
|             } ?: return@suspendSafeApiCall | ||||
| 
 | ||||
| 
 | ||||
|             sourceObject.tracks?.forEach { track -> | ||||
|                 track?.toSubtitleFile()?.let { subtitleFile -> | ||||
|                     subtitleCallback.invoke(subtitleFile) | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             val list = listOf( | ||||
|                 mapped.sources to "source 1", | ||||
|                 mapped.sources1 to "source 2", | ||||
|                 mapped.sources2 to "source 3", | ||||
|                 mapped.sourcesBackup to "source backup" | ||||
|                 sourceObject.sources to "source 1", | ||||
|                 sourceObject.sources1 to "source 2", | ||||
|                 sourceObject.sources2 to "source 3", | ||||
|                 sourceObject.sourcesBackup to "source backup" | ||||
|             ) | ||||
| 
 | ||||
|             list.forEach { subList -> | ||||
|                 subList.first?.forEach { source -> | ||||
|                     source?.toExtractorLink( | ||||
|  |  | |||
|  | @ -322,6 +322,10 @@ class ZoroProvider : MainAPI() { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private suspend fun getKey(): String { | ||||
|         return app.get("https://raw.githubusercontent.com/consumet/rapidclown/main/key.txt").text | ||||
|     } | ||||
| 
 | ||||
|     override suspend fun loadLinks( | ||||
|         data: String, | ||||
|         isCasting: Boolean, | ||||
|  | @ -349,21 +353,23 @@ class ZoroProvider : MainAPI() { | |||
|             val extractorLink = app.get( | ||||
|                 link, | ||||
|             ).parsed<RapidCloudResponse>().link | ||||
|             val hasLoadedExtractorLink = | ||||
|                 loadExtractor(extractorLink, "https://rapid-cloud.ru/", subtitleCallback, callback) | ||||
| //            val hasLoadedExtractorLink = | ||||
| //                loadExtractor(extractorLink, "https://rapid-cloud.ru/", subtitleCallback, callback) | ||||
| 
 | ||||
|             if (!hasLoadedExtractorLink) { | ||||
| //            if (!hasLoadedExtractorLink) { | ||||
|                 extractRabbitStream( | ||||
|                     extractorLink, | ||||
|                     subtitleCallback, | ||||
|                     // Blacklist VidCloud for now | ||||
|                     { videoLink -> if (!videoLink.url.contains("betterstream")) callback(videoLink) }, | ||||
|                     true, | ||||
|                     extractorData | ||||
|                     false, | ||||
| //                    extractorData, | ||||
|                     decryptKey = getKey() | ||||
| 
 | ||||
|                 ) { sourceName -> | ||||
|                     sourceName + " - ${it.first}" | ||||
|                 } | ||||
|             } | ||||
| //            } | ||||
|         } | ||||
| 
 | ||||
|         return true | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue