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
|
// use an integer for version numbers
|
||||||
version = 2
|
version = 3
|
||||||
|
|
||||||
|
|
||||||
cloudstream {
|
cloudstream {
|
||||||
|
|
|
@ -23,7 +23,12 @@ import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.security.MessageDigest
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.spec.IvParameterSpec
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
open class SflixProvider : MainAPI() {
|
open class SflixProvider : MainAPI() {
|
||||||
|
@ -41,7 +46,7 @@ open class SflixProvider : MainAPI() {
|
||||||
)
|
)
|
||||||
override val vpnStatus = VPNStatus.None
|
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 html = app.get("$mainUrl/home").text
|
||||||
val document = Jsoup.parse(html)
|
val document = Jsoup.parse(html)
|
||||||
|
|
||||||
|
@ -290,10 +295,18 @@ open class SflixProvider : MainAPI() {
|
||||||
)
|
)
|
||||||
|
|
||||||
data class SourceObject(
|
data class SourceObject(
|
||||||
@JsonProperty("sources") val sources: List<Sources?>?,
|
@JsonProperty("sources") val sources: List<Sources?>? = null,
|
||||||
@JsonProperty("sources_1") val sources1: List<Sources?>?,
|
@JsonProperty("sources_1") val sources1: List<Sources?>? = null,
|
||||||
@JsonProperty("sources_2") val sources2: List<Sources?>?,
|
@JsonProperty("sources_2") val sources2: List<Sources?>? = null,
|
||||||
@JsonProperty("sourcesBackup") val sourcesBackup: List<Sources?>?,
|
@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?>?
|
@JsonProperty("tracks") val tracks: List<Tracks?>?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -305,6 +318,15 @@ open class SflixProvider : MainAPI() {
|
||||||
// @JsonProperty("title") val title: String? = null
|
// @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(
|
override suspend fun loadLinks(
|
||||||
data: String,
|
data: String,
|
||||||
isCasting: Boolean,
|
isCasting: Boolean,
|
||||||
|
@ -347,7 +369,13 @@ open class SflixProvider : MainAPI() {
|
||||||
if (iframeLink.contains("streamlare", ignoreCase = true)) {
|
if (iframeLink.contains("streamlare", ignoreCase = true)) {
|
||||||
loadExtractor(iframeLink, null, subtitleCallback, callback)
|
loadExtractor(iframeLink, null, subtitleCallback, callback)
|
||||||
} else {
|
} 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) {
|
if (!response.okhttpResponse.isSuccessful) {
|
||||||
return negotiateNewSid(baseUrl)?.let {
|
return negotiateNewSid(baseUrl)?.let {
|
||||||
it to true
|
it to true
|
||||||
} ?: data to false
|
} ?: (data to false)
|
||||||
}
|
}
|
||||||
return 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(
|
suspend fun MainAPI.extractRabbitStream(
|
||||||
url: String,
|
url: String,
|
||||||
subtitleCallback: (SubtitleFile) -> Unit,
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
@ -659,6 +730,7 @@ open class SflixProvider : MainAPI() {
|
||||||
useSidAuthentication: Boolean,
|
useSidAuthentication: Boolean,
|
||||||
/** Used for extractorLink name, input: Source name */
|
/** Used for extractorLink name, input: Source name */
|
||||||
extractorData: String? = null,
|
extractorData: String? = null,
|
||||||
|
decryptKey: String? = null,
|
||||||
nameTransformer: (String) -> String,
|
nameTransformer: (String) -> String,
|
||||||
) = suspendSafeApiCall {
|
) = suspendSafeApiCall {
|
||||||
// https://rapid-cloud.ru/embed-6/dcPOVRE57YOT?z= -> https://rapid-cloud.ru/embed-6
|
// https://rapid-cloud.ru/embed-6/dcPOVRE57YOT?z= -> https://rapid-cloud.ru/embed-6
|
||||||
|
@ -666,13 +738,13 @@ open class SflixProvider : MainAPI() {
|
||||||
url.substringBeforeLast("/")
|
url.substringBeforeLast("/")
|
||||||
val mainIframeId = url.substringAfterLast("/")
|
val mainIframeId = url.substringAfterLast("/")
|
||||||
.substringBefore("?") // https://rapid-cloud.ru/embed-6/dcPOVRE57YOT?z= -> dcPOVRE57YOT
|
.substringBefore("?") // https://rapid-cloud.ru/embed-6/dcPOVRE57YOT?z= -> dcPOVRE57YOT
|
||||||
val iframe = app.get(url, referer = mainUrl)
|
// val iframe = app.get(url, referer = mainUrl)
|
||||||
val iframeKey =
|
// val iframeKey =
|
||||||
iframe.document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]")
|
// iframe.document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]")
|
||||||
.attr("src").substringAfter("render=")
|
// .attr("src").substringAfter("render=")
|
||||||
val iframeToken = getCaptchaToken(url, iframeKey)
|
// val iframeToken = getCaptchaToken(url, iframeKey)
|
||||||
val number =
|
// val number =
|
||||||
Regex("""recaptchaNumber = '(.*?)'""").find(iframe.text)?.groupValues?.get(1)
|
// Regex("""recaptchaNumber = '(.*?)'""").find(iframe.text)?.groupValues?.get(1)
|
||||||
|
|
||||||
var sid: String? = null
|
var sid: String? = null
|
||||||
if (useSidAuthentication && extractorData != null) {
|
if (useSidAuthentication && extractorData != null) {
|
||||||
|
@ -691,42 +763,52 @@ open class SflixProvider : MainAPI() {
|
||||||
ioSafe { app.get("$extractorData&t=${generateTimeStamp()}&sid=${pollingData.sid}") }
|
ioSafe { app.get("$extractorData&t=${generateTimeStamp()}&sid=${pollingData.sid}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val getSourcesUrl = "${
|
||||||
val mapped = app.get(
|
mainIframeUrl.replace(
|
||||||
"${
|
"/embed",
|
||||||
mainIframeUrl.replace(
|
"/ajax/embed"
|
||||||
"/embed",
|
)
|
||||||
"/ajax/embed"
|
}/getSources?id=$mainIframeId${sid?.let { "$&sId=$it" } ?: ""}"
|
||||||
)
|
val response = app.get(
|
||||||
}/getSources?id=$mainIframeId&_token=$iframeToken&_number=$number${sid?.let { "$&sId=$it" } ?: ""}",
|
getSourcesUrl,
|
||||||
referer = mainUrl,
|
referer = mainUrl,
|
||||||
headers = mapOf(
|
headers = mapOf(
|
||||||
"X-Requested-With" to "XMLHttpRequest",
|
"X-Requested-With" to "XMLHttpRequest",
|
||||||
"Accept" to "*/*",
|
"Accept" to "*/*",
|
||||||
"Accept-Language" to "en-US,en;q=0.5",
|
"Accept-Language" to "en-US,en;q=0.5",
|
||||||
// "Cache-Control" to "no-cache",
|
|
||||||
"Connection" to "keep-alive",
|
"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"
|
"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 ->
|
track?.toSubtitleFile()?.let { subtitleFile ->
|
||||||
subtitleCallback.invoke(subtitleFile)
|
subtitleCallback.invoke(subtitleFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val list = listOf(
|
val list = listOf(
|
||||||
mapped.sources to "source 1",
|
sourceObject.sources to "source 1",
|
||||||
mapped.sources1 to "source 2",
|
sourceObject.sources1 to "source 2",
|
||||||
mapped.sources2 to "source 3",
|
sourceObject.sources2 to "source 3",
|
||||||
mapped.sourcesBackup to "source backup"
|
sourceObject.sourcesBackup to "source backup"
|
||||||
)
|
)
|
||||||
|
|
||||||
list.forEach { subList ->
|
list.forEach { subList ->
|
||||||
subList.first?.forEach { source ->
|
subList.first?.forEach { source ->
|
||||||
source?.toExtractorLink(
|
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(
|
override suspend fun loadLinks(
|
||||||
data: String,
|
data: String,
|
||||||
isCasting: Boolean,
|
isCasting: Boolean,
|
||||||
|
@ -349,21 +353,23 @@ class ZoroProvider : MainAPI() {
|
||||||
val extractorLink = app.get(
|
val extractorLink = app.get(
|
||||||
link,
|
link,
|
||||||
).parsed<RapidCloudResponse>().link
|
).parsed<RapidCloudResponse>().link
|
||||||
val hasLoadedExtractorLink =
|
// val hasLoadedExtractorLink =
|
||||||
loadExtractor(extractorLink, "https://rapid-cloud.ru/", subtitleCallback, callback)
|
// loadExtractor(extractorLink, "https://rapid-cloud.ru/", subtitleCallback, callback)
|
||||||
|
|
||||||
if (!hasLoadedExtractorLink) {
|
// if (!hasLoadedExtractorLink) {
|
||||||
extractRabbitStream(
|
extractRabbitStream(
|
||||||
extractorLink,
|
extractorLink,
|
||||||
subtitleCallback,
|
subtitleCallback,
|
||||||
// Blacklist VidCloud for now
|
// Blacklist VidCloud for now
|
||||||
{ videoLink -> if (!videoLink.url.contains("betterstream")) callback(videoLink) },
|
{ videoLink -> if (!videoLink.url.contains("betterstream")) callback(videoLink) },
|
||||||
true,
|
false,
|
||||||
extractorData
|
// extractorData,
|
||||||
|
decryptKey = getKey()
|
||||||
|
|
||||||
) { sourceName ->
|
) { sourceName ->
|
||||||
sourceName + " - ${it.first}"
|
sourceName + " - ${it.first}"
|
||||||
}
|
}
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
Loading…
Reference in a new issue