diff --git a/SflixProvider/build.gradle.kts b/SflixProvider/build.gradle.kts index 869a21a..2f48a34 100644 --- a/SflixProvider/build.gradle.kts +++ b/SflixProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 2 +version = 3 cloudstream { diff --git a/SflixProvider/src/main/kotlin/com/lagradost/SflixProvider.kt b/SflixProvider/src/main/kotlin/com/lagradost/SflixProvider.kt index 3b9bcc7..37ec921 100644 --- a/SflixProvider/src/main/kotlin/com/lagradost/SflixProvider.kt +++ b/SflixProvider/src/main/kotlin/com/lagradost/SflixProvider.kt @@ -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?, - @JsonProperty("sources_1") val sources1: List?, - @JsonProperty("sources_2") val sources2: List?, - @JsonProperty("sourcesBackup") val sourcesBackup: List?, + @JsonProperty("sources") val sources: List? = null, + @JsonProperty("sources_1") val sources1: List? = null, + @JsonProperty("sources_2") val sources2: List? = null, + @JsonProperty("sourcesBackup") val sourcesBackup: List? = null, + @JsonProperty("tracks") val tracks: List? = 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? ) @@ -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().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 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() + ) - mapped.tracks?.forEach { track -> + val sourceObject = if (response.text.contains("encrypted") && decryptKey != null) { + val encryptedMap = response.parsedSafe() + val sources = + encryptedMap?.sources ?: throw RuntimeException("NO SOURCES $encryptedMap") + + val decrypted = decryptMapped>(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( diff --git a/SflixProvider/src/main/kotlin/com/lagradost/ZoroProvider.kt b/SflixProvider/src/main/kotlin/com/lagradost/ZoroProvider.kt index 7ff14a6..eff74e2 100644 --- a/SflixProvider/src/main/kotlin/com/lagradost/ZoroProvider.kt +++ b/SflixProvider/src/main/kotlin/com/lagradost/ZoroProvider.kt @@ -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().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