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(
|
||||
"${
|
||||
val getSourcesUrl = "${
|
||||
mainIframeUrl.replace(
|
||||
"/embed",
|
||||
"/ajax/embed"
|
||||
)
|
||||
}/getSources?id=$mainIframeId&_token=$iframeToken&_number=$number${sid?.let { "$&sId=$it" } ?: ""}",
|
||||
}/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…
Reference in a new issue