From d3ef880964b5eb3ee5bea4d5d94a44438e535e92 Mon Sep 17 00:00:00 2001 From: Sofie99 Date: Fri, 4 Aug 2023 20:42:49 +0700 Subject: [PATCH] Extractor: added Rabbitstream --- .../cloudstream3/extractors/Megacloud.kt | 167 ------------------ .../cloudstream3/extractors/Rabbitstream.kt | 23 ++- 2 files changed, 10 insertions(+), 180 deletions(-) delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/extractors/Megacloud.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Megacloud.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Megacloud.kt deleted file mode 100644 index d1a19f3c..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Megacloud.kt +++ /dev/null @@ -1,167 +0,0 @@ -package com.lagradost.cloudstream3.extractors - -import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.SubtitleFile -import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.base64DecodeArray -import com.lagradost.cloudstream3.utils.AppUtils -import com.lagradost.cloudstream3.utils.AppUtils.parseJson -import com.lagradost.cloudstream3.utils.ExtractorApi -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.M3u8Helper -import java.nio.charset.StandardCharsets -import java.security.MessageDigest -import java.util.Objects -import javax.crypto.Cipher -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec - -class Megacloud : Rabbitstream() { - override val name = "Megacloud" - override val mainUrl = "https://megacloud.tv" - override val embed = "embed-2/ajax/e-1" - override val key = "https://raw.githubusercontent.com/enimax-anime/key/e6/key.txt" -} - -class Dokicloud : Rabbitstream() { - override val name = "Dokicloud" - override val mainUrl = "https://dokicloud.one" -} - -open class Rabbitstream : ExtractorApi() { - override val name = "Rabbitstream" - override val mainUrl = "https://rabbitstream.net" - override val requiresReferer = false - open val embed = "ajax/embed-4" - open val key = "https://raw.githubusercontent.com/enimax-anime/key/e4/key.txt" - - override suspend fun getUrl( - url: String, - referer: String?, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit - ) { - val id = url.substringAfterLast("/").substringBefore("?") - val rawKey = app.get(key).text - val response = app.get( - "$mainUrl/$embed/getSources?id=$id", - referer = mainUrl, - headers = mapOf("X-Requested-With" to "XMLHttpRequest") - ) - val encryptedMap = response.parsedSafe() - val sources = encryptedMap?.sources - val decryptedSources = if (sources == null || encryptedMap.encrypted == false) { - response.parsedSafe() - } else { - val (key, encData) = extractRealKey(sources, rawKey) - val decrypted = decryptMapped>(encData, key) - SourcesResponses( - sources = decrypted, - tracks = encryptedMap.tracks - ) - } - - decryptedSources?.sources?.map { source -> - M3u8Helper.generateM3u8( - name, - source?.file ?: return@map, - "$mainUrl/", - ).forEach(callback) - } - - decryptedSources?.tracks?.map { track -> - subtitleCallback.invoke( - SubtitleFile( - track?.label ?: "", - track?.file ?: return@map - ) - ) - } - - } - - private fun extractRealKey(originalString: String?, stops: String): Pair { - val table = parseJson>>(stops) - val decryptedKey = StringBuilder() - var offset = 0 - var encryptedString = originalString - - table.forEach { (start, end) -> - decryptedKey.append(encryptedString?.substring(start - offset, end - offset)) - encryptedString = encryptedString?.substring( - 0, - start - offset - ) + encryptedString?.substring(end - offset) - offset += end - start - } - return decryptedKey.toString() to encryptedString.toString() - } - - private inline fun decryptMapped(input: String, key: String): T? { - val decrypt = decrypt(input, key) - return AppUtils.tryParseJson(decrypt) - } - - private fun decrypt(input: String, key: String): String { - return decryptSourceUrl( - generateKey( - base64DecodeArray(input).copyOfRange(8, 16), - key.toByteArray() - ), 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 md5(input: ByteArray): ByteArray { - return MessageDigest.getInstance("MD5").digest(input) - } - - 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) - } - - data class Tracks( - @JsonProperty("file") val file: String? = null, - @JsonProperty("label") val label: String? = null, - @JsonProperty("kind") val kind: String? = null, - ) - - data class Sources( - @JsonProperty("file") val file: String? = null, - @JsonProperty("type") val type: String? = null, - @JsonProperty("label") val label: String? = null, - ) - - data class SourcesResponses( - @JsonProperty("sources") val sources: List? = emptyList(), - @JsonProperty("tracks") val tracks: List? = emptyList(), - ) - - data class SourcesEncrypted( - @JsonProperty("sources") val sources: String? = null, - @JsonProperty("encrypted") val encrypted: Boolean? = null, - @JsonProperty("tracks") val tracks: List? = emptyList(), - ) - -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt index b686f7d8..d1a19f3c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt @@ -1,7 +1,6 @@ package com.lagradost.cloudstream3.extractors import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.base64DecodeArray @@ -12,12 +11,11 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper import java.nio.charset.StandardCharsets import java.security.MessageDigest +import java.util.Objects import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec -// No License found in https://github.com/enimax-anime/key -// special credits to @enimax for providing key class Megacloud : Rabbitstream() { override val name = "Megacloud" override val mainUrl = "https://megacloud.tv" @@ -36,7 +34,6 @@ open class Rabbitstream : ExtractorApi() { override val requiresReferer = false open val embed = "ajax/embed-4" open val key = "https://raw.githubusercontent.com/enimax-anime/key/e4/key.txt" - private var rawKey: String? = null override suspend fun getUrl( url: String, @@ -45,19 +42,18 @@ open class Rabbitstream : ExtractorApi() { callback: (ExtractorLink) -> Unit ) { val id = url.substringAfterLast("/").substringBefore("?") - + val rawKey = app.get(key).text val response = app.get( "$mainUrl/$embed/getSources?id=$id", referer = mainUrl, headers = mapOf("X-Requested-With" to "XMLHttpRequest") ) - val encryptedMap = response.parsedSafe() val sources = encryptedMap?.sources val decryptedSources = if (sources == null || encryptedMap.encrypted == false) { response.parsedSafe() } else { - val (key, encData) = extractRealKey(sources, getRawKey()) + val (key, encData) = extractRealKey(sources, rawKey) val decrypted = decryptMapped>(encData, key) SourcesResponses( sources = decrypted, @@ -84,8 +80,6 @@ open class Rabbitstream : ExtractorApi() { } - private suspend fun getRawKey(): String = rawKey ?: app.get(key).text.also { rawKey = it } - private fun extractRealKey(originalString: String?, stops: String): Pair { val table = parseJson>>(stops) val decryptedKey = StringBuilder() @@ -135,12 +129,15 @@ open class Rabbitstream : ExtractorApi() { val cipherData = base64DecodeArray(sourceUrl) val encrypted = cipherData.copyOfRange(16, cipherData.size) val aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding") - aesCBC.init( - Cipher.DECRYPT_MODE, - SecretKeySpec(decryptionKey.copyOfRange(0, 32), "AES"), + + Objects.requireNonNull(aesCBC).init( + Cipher.DECRYPT_MODE, SecretKeySpec( + decryptionKey.copyOfRange(0, 32), + "AES" + ), IvParameterSpec(decryptionKey.copyOfRange(32, decryptionKey.size)) ) - val decryptedData = aesCBC?.doFinal(encrypted) ?: throw ErrorLoadingException("Cipher not found") + val decryptedData = aesCBC!!.doFinal(encrypted) return String(decryptedData, StandardCharsets.UTF_8) }