From 2c9649c0dd58e71ac6e99434097a591e704a8397 Mon Sep 17 00:00:00 2001 From: hexated Date: Mon, 15 May 2023 01:49:25 +0700 Subject: [PATCH] fixed Movierulzhd --- .../main/kotlin/com/hexated/GogoExtractor.kt | 166 ---------- .../src/main/kotlin/com/hexated/Kissasian.kt | 6 +- Movierulzhd/build.gradle.kts | 2 +- .../src/main/kotlin/com/hexated/Extractors.kt | 143 +++++++++ .../main/kotlin/com/hexated/Movierulzhd.kt | 8 +- .../main/kotlin/com/hexated/RabbitStream.kt | 302 ++++++++++++++++++ .../src/main/kotlin/com/hexated/Sbflix.kt | 89 ------ .../src/main/kotlin/com/hexated/Extractors.kt | 190 ----------- .../main/kotlin/com/hexated/GogoExtractor.kt | 166 ---------- .../main/kotlin/com/hexated/SoraExtractor.kt | 4 +- .../kotlin/com/hexated/SoraStreamPlugin.kt | 13 - 11 files changed, 457 insertions(+), 632 deletions(-) delete mode 100644 Kissasian/src/main/kotlin/com/hexated/GogoExtractor.kt create mode 100644 Movierulzhd/src/main/kotlin/com/hexated/Extractors.kt create mode 100644 Movierulzhd/src/main/kotlin/com/hexated/RabbitStream.kt delete mode 100644 Movierulzhd/src/main/kotlin/com/hexated/Sbflix.kt delete mode 100644 SoraStream/src/main/kotlin/com/hexated/Extractors.kt delete mode 100644 SoraStream/src/main/kotlin/com/hexated/GogoExtractor.kt diff --git a/Kissasian/src/main/kotlin/com/hexated/GogoExtractor.kt b/Kissasian/src/main/kotlin/com/hexated/GogoExtractor.kt deleted file mode 100644 index ed19fd25..00000000 --- a/Kissasian/src/main/kotlin/com/hexated/GogoExtractor.kt +++ /dev/null @@ -1,166 +0,0 @@ -package com.hexated - -import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.base64Decode -import com.lagradost.cloudstream3.base64DecodeArray -import com.lagradost.cloudstream3.base64Encode -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall -import com.lagradost.cloudstream3.mvvm.safeApiCall -import com.lagradost.cloudstream3.utils.AppUtils -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.M3u8Helper -import com.lagradost.cloudstream3.utils.getQualityFromName -import org.jsoup.nodes.Document -import java.net.URI -import javax.crypto.Cipher -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec - -object GogoExtractor { - - /** - * @param id base64Decode(show_id) + IV - * @return the encryption key - * */ - private fun getKey(id: String): String? { - return normalSafeApiCall { - id.map { - it.code.toString(16) - }.joinToString("").substring(0, 32) - } - } - - // https://github.com/saikou-app/saikou/blob/3e756bd8e876ad7a9318b17110526880525a5cd3/app/src/main/java/ani/saikou/anime/source/extractors/GogoCDN.kt#L60 - // No Licence on the function - private fun cryptoHandler( - string: String, - iv: String, - secretKeyString: String, - encrypt: Boolean = true - ): String { - //println("IV: $iv, Key: $secretKeyString, encrypt: $encrypt, Message: $string") - val ivParameterSpec = IvParameterSpec(iv.toByteArray()) - val secretKey = SecretKeySpec(secretKeyString.toByteArray(), "AES") - val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") - return if (!encrypt) { - cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec) - String(cipher.doFinal(base64DecodeArray(string))) - } else { - cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec) - base64Encode(cipher.doFinal(string.toByteArray())) - } - } - - /** - * @param iframeUrl something like https://gogoplay4.com/streaming.php?id=XXXXXX - * @param mainApiName used for ExtractorLink names and source - * @param iv secret iv from site, required non-null if isUsingAdaptiveKeys is off - * @param secretKey secret key for decryption from site, required non-null if isUsingAdaptiveKeys is off - * @param secretDecryptKey secret key to decrypt the response json, required non-null if isUsingAdaptiveKeys is off - * @param isUsingAdaptiveKeys generates keys from IV and ID, see getKey() - * @param isUsingAdaptiveData generate encrypt-ajax data based on $("script[data-name='episode']")[0].dataset.value - * */ - suspend fun extractVidstream( - iframeUrl: String, - mainApiName: String, - callback: (ExtractorLink) -> Unit, - iv: String?, - secretKey: String?, - secretDecryptKey: String?, - // This could be removed, but i prefer it verbose - isUsingAdaptiveKeys: Boolean, - isUsingAdaptiveData: Boolean, - // If you don't want to re-fetch the document - iframeDocument: Document? = null - ) = safeApiCall { - // https://github.com/saikou-app/saikou/blob/3e756bd8e876ad7a9318b17110526880525a5cd3/app/src/main/java/ani/saikou/anime/source/extractors/GogoCDN.kt - // No Licence on the following code - // Also modified of https://github.com/jmir1/aniyomi-extensions/blob/master/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/extractors/GogoCdnExtractor.kt - // License on the code above https://github.com/jmir1/aniyomi-extensions/blob/master/LICENSE - - if ((iv == null || secretKey == null || secretDecryptKey == null) && !isUsingAdaptiveKeys) - return@safeApiCall - - val id = Regex("id=([^&]+)").find(iframeUrl)!!.value.removePrefix("id=") - - var document: Document? = iframeDocument - val foundIv = - iv ?: (document ?: app.get(iframeUrl).document.also { document = it }) - .select("""div.wrapper[class*=container]""") - .attr("class").split("-").lastOrNull() ?: return@safeApiCall - val foundKey = secretKey ?: getKey(base64Decode(id) + foundIv) ?: return@safeApiCall - val foundDecryptKey = secretDecryptKey ?: foundKey - - val uri = URI(iframeUrl) - val mainUrl = "https://" + uri.host - - val encryptedId = cryptoHandler(id, foundIv, foundKey) - val encryptRequestData = if (isUsingAdaptiveData) { - // Only fetch the document if necessary - val realDocument = document ?: app.get(iframeUrl).document - val dataEncrypted = - realDocument.select("script[data-name='episode']").attr("data-value") - val headers = cryptoHandler(dataEncrypted, foundIv, foundKey, false) - "id=$encryptedId&alias=$id&" + headers.substringAfter("&") - } else { - "id=$encryptedId&alias=$id" - } - - val jsonResponse = - app.get( - "$mainUrl/encrypt-ajax.php?$encryptRequestData", - headers = mapOf("X-Requested-With" to "XMLHttpRequest") - ) - val dataencrypted = - jsonResponse.text.substringAfter("{\"data\":\"").substringBefore("\"}") - val datadecrypted = cryptoHandler(dataencrypted, foundIv, foundDecryptKey, false) - val sources = AppUtils.parseJson(datadecrypted) - - suspend fun invokeGogoSource( - source: GogoSource, - sourceCallback: (ExtractorLink) -> Unit - ) { - if (source.file.contains(".m3u8")) { - M3u8Helper.generateM3u8( - mainApiName, - source.file, - mainUrl, - headers = mapOf("Origin" to "https://plyr.link") - ).forEach(sourceCallback) - } else { - sourceCallback.invoke( - ExtractorLink( - mainApiName, - mainApiName, - source.file, - mainUrl, - getQualityFromName(source.label), - ) - ) - } - } - - sources.source?.forEach { - invokeGogoSource(it, callback) - } - sources.sourceBk?.forEach { - invokeGogoSource(it, callback) - } - } - - data class GogoSources( - @JsonProperty("source") val source: List?, - @JsonProperty("sourceBk") val sourceBk: List?, - //val track: List, - //val advertising: List, - //val linkiframe: String - ) - - data class GogoSource( - @JsonProperty("file") val file: String, - @JsonProperty("label") val label: String?, - @JsonProperty("type") val type: String?, - @JsonProperty("default") val default: String? = null - ) -} diff --git a/Kissasian/src/main/kotlin/com/hexated/Kissasian.kt b/Kissasian/src/main/kotlin/com/hexated/Kissasian.kt index 34685061..eab56061 100644 --- a/Kissasian/src/main/kotlin/com/hexated/Kissasian.kt +++ b/Kissasian/src/main/kotlin/com/hexated/Kissasian.kt @@ -1,7 +1,7 @@ package com.hexated import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.mvvm.safeApiCall +import com.lagradost.cloudstream3.extractors.helper.GogoHelper import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.httpsify import com.lagradost.cloudstream3.utils.loadExtractor @@ -72,7 +72,7 @@ class Kissasian : MainAPI() { val episodes = document.select("ul.listing li").map { val name = it.selectFirst("a")?.attr("title") val link = fixUrlNull(it.selectFirst("a")?.attr("href")) - val epNum = Regex("Episode\\s([0-9]+)").find("$name")?.groupValues?.getOrNull(1)?.toIntOrNull() + val epNum = Regex("Episode\\s(\\d+)").find("$name")?.groupValues?.getOrNull(1)?.toIntOrNull() newEpisode(link) { this.name = name this.episode = epNum @@ -121,7 +121,7 @@ class Kissasian : MainAPI() { val iv = "9262859232435825" val secretKey = "93422192433952489752342908585752" val secretDecryptKey = secretKey - GogoExtractor.extractVidstream( + GogoHelper.extractVidstream( iframe.url, this.name, callback, diff --git a/Movierulzhd/build.gradle.kts b/Movierulzhd/build.gradle.kts index 882f54e2..14a4d754 100644 --- a/Movierulzhd/build.gradle.kts +++ b/Movierulzhd/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 33 +version = 34 cloudstream { diff --git a/Movierulzhd/src/main/kotlin/com/hexated/Extractors.kt b/Movierulzhd/src/main/kotlin/com/hexated/Extractors.kt new file mode 100644 index 00000000..82cceac1 --- /dev/null +++ b/Movierulzhd/src/main/kotlin/com/hexated/Extractors.kt @@ -0,0 +1,143 @@ +package com.hexated + +import com.fasterxml.jackson.annotation.JsonProperty +import com.hexated.RabbitStream.extractRabbitStream +import com.lagradost.cloudstream3.APIHolder +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.apmap +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.* +import kotlin.random.Random + +const val twoEmbedAPI = "https://www.2embed.to" + +class Sbrulz : Sbflix() { + override val name = "Sbrulz" + override var mainUrl = "https://sbrulz.xyz" +} + +open class Sbflix : ExtractorApi() { + override val mainUrl = "https://sbflix.xyz" + override val name = "Sbflix" + override val requiresReferer = false + private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val regexID = + Regex("(embed-[a-zA-Z\\d]{0,8}[a-zA-Z\\d_-]+|/e/[a-zA-Z\\d]{0,8}[a-zA-Z\\d_-]+)") + val id = regexID.findAll(url).map { + it.value.replace(Regex("(embed-|/e/)"), "") + }.first() + val master = "$mainUrl/375664356a494546326c4b797c7c6e756577776778623171737/${encodeId(id)}" + val headers = mapOf( + "watchsb" to "sbstream", + ) + val mapped = app.get( + master.lowercase(), + headers = headers, + referer = url, + ).parsedSafe
() + callback.invoke( + ExtractorLink( + name, + name, + mapped?.streamData?.file ?: return, + url, + Qualities.P720.value, + isM3u8 = true, + headers = headers + ) + ) + + mapped.streamData.subs?.map {sub -> + subtitleCallback.invoke( + SubtitleFile( + sub.label.toString(), + sub.file ?: return@map null, + ) + ) + } + } + + private fun encodeId(id: String): String { + val code = "${createHashTable()}||$id||${createHashTable()}||streamsb" + return code.toCharArray().joinToString("") { char -> + char.code.toString(16) + } + } + + private fun createHashTable(): String { + return buildString { + repeat(12) { + append(alphabet[Random.nextInt(alphabet.length)]) + } + } + } + + data class Subs ( + @JsonProperty("file") val file: String? = null, + @JsonProperty("label") val label: String? = null, + ) + + data class StreamData ( + @JsonProperty("file") val file: String, + @JsonProperty("cdn_img") val cdnImg: String, + @JsonProperty("hash") val hash: String, + @JsonProperty("subs") val subs: ArrayList? = arrayListOf(), + @JsonProperty("length") val length: String, + @JsonProperty("id") val id: String, + @JsonProperty("title") val title: String, + @JsonProperty("backup") val backup: String, + ) + + data class Main ( + @JsonProperty("stream_data") val streamData: StreamData, + @JsonProperty("status_code") val statusCode: Int, + ) + +} + +suspend fun invokeTwoEmbed( + url: String? = null, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit +) { + val document = app.get(url ?: return).document + val captchaKey = + document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]") + .attr("src").substringAfter("render=") + + document.select(".dropdown-menu a[data-id]").map { it.attr("data-id") }.apmap { serverID -> + val token = APIHolder.getCaptchaToken(url, captchaKey) + app.get( + "${twoEmbedAPI}/ajax/embed/play?id=$serverID&_token=$token", referer = url + ).parsedSafe()?.let { source -> + val link = source.link ?: return@let + if (link.contains("rabbitstream")) { + extractRabbitStream( + link, + subtitleCallback, + callback, + false, + decryptKey = RabbitStream.getKey() + ) { it } + } else { + loadExtractor( + link, twoEmbedAPI, subtitleCallback, callback + ) + } + } + } +} + +data class EmbedJson( + @JsonProperty("type") val type: String? = null, + @JsonProperty("link") val link: String? = null, + @JsonProperty("sources") val sources: List = arrayListOf(), + @JsonProperty("tracks") val tracks: List? = null, +) diff --git a/Movierulzhd/src/main/kotlin/com/hexated/Movierulzhd.kt b/Movierulzhd/src/main/kotlin/com/hexated/Movierulzhd.kt index 0d3c6041..77e68039 100644 --- a/Movierulzhd/src/main/kotlin/com/hexated/Movierulzhd.kt +++ b/Movierulzhd/src/main/kotlin/com/hexated/Movierulzhd.kt @@ -12,7 +12,7 @@ import org.jsoup.nodes.Element import java.net.URI class Movierulzhd : MainAPI() { - override var mainUrl = "https://movierulzhd.bid" + override var mainUrl = "https://movierulzhd.press" private var directUrl = mainUrl override var name = "Movierulzhd" override val hasMainPage = true @@ -248,7 +248,11 @@ class Movierulzhd : MainAPI() { headers = mapOf("X-Requested-With" to "XMLHttpRequest") ).parsed().embed_url - if(!source.contains("youtube")) loadExtractor(source, data, subtitleCallback, callback) + when { + source.contains("2embed") -> invokeTwoEmbed(source,subtitleCallback, callback) + !source.contains("youtube") -> loadExtractor(source, data, subtitleCallback, callback) + else -> return@safeApiCall + } } } } diff --git a/Movierulzhd/src/main/kotlin/com/hexated/RabbitStream.kt b/Movierulzhd/src/main/kotlin/com/hexated/RabbitStream.kt new file mode 100644 index 00000000..41b73992 --- /dev/null +++ b/Movierulzhd/src/main/kotlin/com/hexated/RabbitStream.kt @@ -0,0 +1,302 @@ +package com.hexated + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall +import com.lagradost.cloudstream3.utils.AppUtils +import com.lagradost.cloudstream3.utils.Coroutines.ioSafe +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.M3u8Helper +import com.lagradost.cloudstream3.utils.getQualityFromName +import kotlinx.coroutines.delay +import okhttp3.RequestBody.Companion.toRequestBody +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.collections.ArrayList + +object RabbitStream { + + suspend fun extractRabbitStream( + url: String, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + 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 + val mainIframeUrl = + url.substringBeforeLast("/") + val mainIframeId = url.substringAfterLast("/") + .substringBefore("?") // https://rapid-cloud.ru/embed-6/dcPOVRE57YOT?z= -> dcPOVRE57YOT + var sid: String? = null + if (useSidAuthentication && extractorData != null) { + negotiateNewSid(extractorData)?.also { pollingData -> + app.post( + "$extractorData&t=${generateTimeStamp()}&sid=${pollingData.sid}", + requestBody = "40".toRequestBody(), + timeout = 60 + ) + val text = app.get( + "$extractorData&t=${generateTimeStamp()}&sid=${pollingData.sid}", + timeout = 60 + ).text.replaceBefore("{", "") + + sid = AppUtils.parseJson(text).sid + ioSafe { app.get("$extractorData&t=${generateTimeStamp()}&sid=${pollingData.sid}") } + } + } + val getSourcesUrl = "${ + mainIframeUrl.replace( + "/embed", + "/ajax/embed" + ) + }/getSources?id=$mainIframeId${sid?.let { "$&sId=$it" } ?: ""}" + val response = app.get( + getSourcesUrl, + referer = "${Movierulzhd().mainUrl}/", + headers = mapOf( + "X-Requested-With" to "XMLHttpRequest", + "Accept" to "*/*", + "Accept-Language" to "en-US,en;q=0.5", + "Connection" to "keep-alive", + "TE" to "trailers" + ) + ) + + val sourceObject = if (decryptKey != null) { + val encryptedMap = response.parsedSafe() + val sources = encryptedMap?.sources + if (sources == null || encryptedMap.encrypted == false) { + response.parsedSafe() + } else { + 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( + 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( + "Vidcloud", + "$twoEmbedAPI/", + extractorData, + ) + ?.forEach { + // Sets Zoro SID used for video loading +// (this as? ZoroProvider)?.sid?.set(it.url.hashCode(), sid) + callback(it) + } + } + } + } + + private suspend fun Sources.toExtractorLink( + name: String, + referer: String, + extractorData: String? = null, + ): List? { + return this.file?.let { file -> + //println("FILE::: $file") + val isM3u8 = URI(this.file).path.endsWith(".m3u8") || this.type.equals( + "hls", + ignoreCase = true + ) + return if (isM3u8) { + suspendSafeApiCall { + M3u8Helper().m3u8Generation( + M3u8Helper.M3u8Stream( + this.file, + null, + mapOf("Referer" to "https://mzzcloud.life/") + ), false + ) + .map { stream -> + ExtractorLink( + name, + name, + stream.streamUrl, + referer, + getQualityFromName(stream.quality?.toString()), + true, + extractorData = extractorData + ) + } + }.takeIf { !it.isNullOrEmpty() } ?: listOf( + // Fallback if m3u8 extractor fails + ExtractorLink( + name, + name, + this.file, + referer, + getQualityFromName(this.label), + isM3u8, + extractorData = extractorData + ) + ) + } else { + listOf( + ExtractorLink( + name, + name, + file, + referer, + getQualityFromName(this.label), + false, + extractorData = extractorData + ) + ) + } + } + } + + private fun Tracks.toSubtitleFile(): SubtitleFile? { + return this.file?.let { + SubtitleFile( + this.label ?: "Unknown", + it + ) + } + } + + /** + * Generates a session + * 1 Get request. + * */ + private suspend fun negotiateNewSid(baseUrl: String): PollingData? { + // Tries multiple times + for (i in 1..5) { + val jsonText = + app.get("$baseUrl&t=${generateTimeStamp()}").text.replaceBefore( + "{", + "" + ) +// println("Negotiated sid $jsonText") + AppUtils.parseJson(jsonText)?.let { return it } + delay(1000L * i) + } + return null + } + + private fun generateTimeStamp(): String { + val chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_" + var code = "" + var time = APIHolder.unixTimeMS + while (time > 0) { + code += chars[(time % (chars.length)).toInt()] + time /= chars.length + } + return code.reversed() + } + + suspend fun getKey(): String { + return app.get("https://raw.githubusercontent.com/enimax-anime/key/e4/key.txt") + .text + } + + private inline fun decryptMapped(input: String, key: String): T? { + return AppUtils.tryParseJson(decrypt(input, key)) + } + + 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 PollingData( + @JsonProperty("sid") val sid: String? = null, + @JsonProperty("upgrades") val upgrades: ArrayList = arrayListOf(), + @JsonProperty("pingInterval") val pingInterval: Int? = null, + @JsonProperty("pingTimeout") val pingTimeout: Int? = null + ) + + data class Tracks( + @JsonProperty("file") val file: String?, + @JsonProperty("label") val label: String?, + @JsonProperty("kind") val kind: String? + ) + + data class Sources( + @JsonProperty("file") val file: String?, + @JsonProperty("type") val type: String?, + @JsonProperty("label") val label: String? + ) + + data class SourceObject( + @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("encrypted") val encrypted: Boolean?, + @JsonProperty("sources_1") val sources1: String?, + @JsonProperty("sources_2") val sources2: String?, + @JsonProperty("sourcesBackup") val sourcesBackup: String?, + @JsonProperty("tracks") val tracks: List? + ) + +} \ No newline at end of file diff --git a/Movierulzhd/src/main/kotlin/com/hexated/Sbflix.kt b/Movierulzhd/src/main/kotlin/com/hexated/Sbflix.kt deleted file mode 100644 index 166d604e..00000000 --- a/Movierulzhd/src/main/kotlin/com/hexated/Sbflix.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.hexated - -import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.SubtitleFile -import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.utils.AppUtils -import com.lagradost.cloudstream3.utils.ExtractorApi -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.Qualities - -class Sbrulz : Sbflix() { - override val name = "Sbrulz" - override var mainUrl = "https://sbrulz.xyz" -} - -open class Sbflix : ExtractorApi() { - override val mainUrl = "https://sbflix.xyz" - override val name = "Sbflix" - override val requiresReferer = false - - override suspend fun getUrl( - url: String, - referer: String?, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit - ) { - val regexID = - Regex("(embed-[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+|/e/[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)") - val id = regexID.findAll(url).map { - it.value.replace(Regex("(embed-|/e/)"), "") - }.first() - val master = "$mainUrl/sources16/" + bytesToHex("||$id||||streamsb".toByteArray()) + "/" - val headers = mapOf( - "watchsb" to "sbstream", - ) - val urltext = app.get( - master.lowercase(), - headers = headers, - referer = url, - ).text - val mapped = urltext.let { AppUtils.parseJson
(it) } - callback.invoke( - ExtractorLink( - name, - name, - mapped.streamData.file, - url, - Qualities.Unknown.value, - isM3u8 = true, - headers = headers - ) - ) - } - - private val hexArray = "0123456789ABCDEF".toCharArray() - - private fun bytesToHex(bytes: ByteArray): String { - val hexChars = CharArray(bytes.size * 2) - for (j in bytes.indices) { - val v = bytes[j].toInt() and 0xFF - - hexChars[j * 2] = hexArray[v ushr 4] - hexChars[j * 2 + 1] = hexArray[v and 0x0F] - } - return String(hexChars) - } - - data class Subs( - @JsonProperty("file") val file: String, - @JsonProperty("label") val label: String, - ) - - data class StreamData( - @JsonProperty("file") val file: String, - @JsonProperty("cdn_img") val cdnImg: String, - @JsonProperty("hash") val hash: String, - @JsonProperty("subs") val subs: List?, - @JsonProperty("length") val length: String, - @JsonProperty("id") val id: String, - @JsonProperty("title") val title: String, - @JsonProperty("backup") val backup: String, - ) - - data class Main( - @JsonProperty("stream_data") val streamData: StreamData, - @JsonProperty("status_code") val statusCode: Int, - ) - -} diff --git a/SoraStream/src/main/kotlin/com/hexated/Extractors.kt b/SoraStream/src/main/kotlin/com/hexated/Extractors.kt deleted file mode 100644 index 4546eeab..00000000 --- a/SoraStream/src/main/kotlin/com/hexated/Extractors.kt +++ /dev/null @@ -1,190 +0,0 @@ -package com.hexated - -import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.extractors.Filesim -import com.lagradost.cloudstream3.extractors.StreamSB -import com.lagradost.cloudstream3.extractors.XStreamCdn -import com.lagradost.cloudstream3.extractors.helper.AsianEmbedHelper -import com.lagradost.cloudstream3.utils.AppUtils -import com.lagradost.cloudstream3.utils.ExtractorApi -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.Qualities -import javax.crypto.Cipher -import javax.crypto.SecretKeyFactory -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.PBEKeySpec -import javax.crypto.spec.SecretKeySpec - -class StreamM4u : XStreamCdn() { - override val name: String = "StreamM4u" - override val mainUrl: String = "https://streamm4u.club" -} - -class Fembed9hd : XStreamCdn() { - override var mainUrl = "https://fembed9hd.com" - override var name = "Fembed9hd" -} - -class Sbasian : StreamSB() { - override var mainUrl = "https://sbasian.pro" - override var name = "Sbasian" -} - -class Sbnet : StreamSB() { - override var name = "Sbnet" - override var mainUrl = "https://sbnet.one" -} - -class Sblongvu : StreamSB() { - override var name = "Sblongvu" - override var mainUrl = "https://sblongvu.com" -} - -class Keephealth : StreamSB() { - override var name = "Keephealth" - override var mainUrl = "https://keephealth.info" -} - -class Moviesm4u : Filesim() { - override val mainUrl = "https://moviesm4u.com" - override val name = "Moviesm4u" -} - -class FileMoonIn : Filesim() { - override val mainUrl = "https://filemoon.in" - override val name = "FileMoon" -} - -class StreamhideCom : Filesim() { - override var name: String = "Streamhide" - override var mainUrl: String = "https://streamhide.com" -} - -class Movhide : Filesim() { - override var name: String = "Movhide" - override var mainUrl: String = "https://movhide.pro" -} - -class Bestx : Chillx() { - override val name = "Bestx" - override val mainUrl = "https://bestx.stream" -} - -class Watchx : Chillx() { - override val name = "Watchx" - override val mainUrl = "https://watchx.top" -} - -open class Chillx : ExtractorApi() { - override val name = "Chillx" - override val mainUrl = "https://chillx.top" - override val requiresReferer = true - - companion object { - private const val KEY = "4VqE3#N7zt&HEP^a" - } - - override suspend fun getUrl( - url: String, - referer: String?, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit - ) { - val master = Regex("MasterJS\\s*=\\s*'([^']+)").find( - app.get( - url, - referer = referer - ).text - )?.groupValues?.get(1) - val encData = AppUtils.tryParseJson(base64Decode(master ?: return)) - val decrypt = cryptoAESHandler(encData ?: return, KEY, false) - - val source = Regex("""sources:\s*\[\{"file":"([^"]+)""").find(decrypt)?.groupValues?.get(1) - val tracks = Regex("""tracks:\s*\[(.+)]""").find(decrypt)?.groupValues?.get(1) - - // required - val headers = mapOf( - "Accept" to "*/*", - "Connection" to "keep-alive", - "Sec-Fetch-Dest" to "empty", - "Sec-Fetch-Mode" to "cors", - "Sec-Fetch-Site" to "cross-site", - "Origin" to mainUrl, - ) - - callback.invoke( - ExtractorLink( - name, - name, - source ?: return, - "$mainUrl/", - Qualities.P1080.value, - headers = headers, - isM3u8 = true - ) - ) - - AppUtils.tryParseJson>("[$tracks]") - ?.filter { it.kind == "captions" }?.map { track -> - subtitleCallback.invoke( - SubtitleFile( - track.label ?: "", - track.file ?: return@map null - ) - ) - } - } - - private fun cryptoAESHandler( - data: AESData, - pass: String, - encrypt: Boolean = true - ): String { - val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512") - val spec = PBEKeySpec( - pass.toCharArray(), - data.salt?.hexToByteArray(), - data.iterations?.toIntOrNull() ?: 1, - 256 - ) - val key = factory.generateSecret(spec) - val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") - return if (!encrypt) { - cipher.init( - Cipher.DECRYPT_MODE, - SecretKeySpec(key.encoded, "AES"), - IvParameterSpec(data.iv?.hexToByteArray()) - ) - String(cipher.doFinal(base64DecodeArray(data.ciphertext.toString()))) - } else { - cipher.init( - Cipher.ENCRYPT_MODE, - SecretKeySpec(key.encoded, "AES"), - IvParameterSpec(data.iv?.hexToByteArray()) - ) - base64Encode(cipher.doFinal(data.ciphertext?.toByteArray())) - } - } - - private fun String.hexToByteArray(): ByteArray { - check(length % 2 == 0) { "Must have an even length" } - return chunked(2) - .map { it.toInt(16).toByte() } - - .toByteArray() - } - - data class AESData( - @JsonProperty("ciphertext") val ciphertext: String? = null, - @JsonProperty("iv") val iv: String? = null, - @JsonProperty("salt") val salt: String? = null, - @JsonProperty("iterations") val iterations: String? = null, - ) - - data class Tracks( - @JsonProperty("file") val file: String? = null, - @JsonProperty("label") val label: String? = null, - @JsonProperty("kind") val kind: String? = null, - ) -} \ No newline at end of file diff --git a/SoraStream/src/main/kotlin/com/hexated/GogoExtractor.kt b/SoraStream/src/main/kotlin/com/hexated/GogoExtractor.kt deleted file mode 100644 index ed19fd25..00000000 --- a/SoraStream/src/main/kotlin/com/hexated/GogoExtractor.kt +++ /dev/null @@ -1,166 +0,0 @@ -package com.hexated - -import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.base64Decode -import com.lagradost.cloudstream3.base64DecodeArray -import com.lagradost.cloudstream3.base64Encode -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall -import com.lagradost.cloudstream3.mvvm.safeApiCall -import com.lagradost.cloudstream3.utils.AppUtils -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.M3u8Helper -import com.lagradost.cloudstream3.utils.getQualityFromName -import org.jsoup.nodes.Document -import java.net.URI -import javax.crypto.Cipher -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec - -object GogoExtractor { - - /** - * @param id base64Decode(show_id) + IV - * @return the encryption key - * */ - private fun getKey(id: String): String? { - return normalSafeApiCall { - id.map { - it.code.toString(16) - }.joinToString("").substring(0, 32) - } - } - - // https://github.com/saikou-app/saikou/blob/3e756bd8e876ad7a9318b17110526880525a5cd3/app/src/main/java/ani/saikou/anime/source/extractors/GogoCDN.kt#L60 - // No Licence on the function - private fun cryptoHandler( - string: String, - iv: String, - secretKeyString: String, - encrypt: Boolean = true - ): String { - //println("IV: $iv, Key: $secretKeyString, encrypt: $encrypt, Message: $string") - val ivParameterSpec = IvParameterSpec(iv.toByteArray()) - val secretKey = SecretKeySpec(secretKeyString.toByteArray(), "AES") - val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") - return if (!encrypt) { - cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec) - String(cipher.doFinal(base64DecodeArray(string))) - } else { - cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec) - base64Encode(cipher.doFinal(string.toByteArray())) - } - } - - /** - * @param iframeUrl something like https://gogoplay4.com/streaming.php?id=XXXXXX - * @param mainApiName used for ExtractorLink names and source - * @param iv secret iv from site, required non-null if isUsingAdaptiveKeys is off - * @param secretKey secret key for decryption from site, required non-null if isUsingAdaptiveKeys is off - * @param secretDecryptKey secret key to decrypt the response json, required non-null if isUsingAdaptiveKeys is off - * @param isUsingAdaptiveKeys generates keys from IV and ID, see getKey() - * @param isUsingAdaptiveData generate encrypt-ajax data based on $("script[data-name='episode']")[0].dataset.value - * */ - suspend fun extractVidstream( - iframeUrl: String, - mainApiName: String, - callback: (ExtractorLink) -> Unit, - iv: String?, - secretKey: String?, - secretDecryptKey: String?, - // This could be removed, but i prefer it verbose - isUsingAdaptiveKeys: Boolean, - isUsingAdaptiveData: Boolean, - // If you don't want to re-fetch the document - iframeDocument: Document? = null - ) = safeApiCall { - // https://github.com/saikou-app/saikou/blob/3e756bd8e876ad7a9318b17110526880525a5cd3/app/src/main/java/ani/saikou/anime/source/extractors/GogoCDN.kt - // No Licence on the following code - // Also modified of https://github.com/jmir1/aniyomi-extensions/blob/master/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/extractors/GogoCdnExtractor.kt - // License on the code above https://github.com/jmir1/aniyomi-extensions/blob/master/LICENSE - - if ((iv == null || secretKey == null || secretDecryptKey == null) && !isUsingAdaptiveKeys) - return@safeApiCall - - val id = Regex("id=([^&]+)").find(iframeUrl)!!.value.removePrefix("id=") - - var document: Document? = iframeDocument - val foundIv = - iv ?: (document ?: app.get(iframeUrl).document.also { document = it }) - .select("""div.wrapper[class*=container]""") - .attr("class").split("-").lastOrNull() ?: return@safeApiCall - val foundKey = secretKey ?: getKey(base64Decode(id) + foundIv) ?: return@safeApiCall - val foundDecryptKey = secretDecryptKey ?: foundKey - - val uri = URI(iframeUrl) - val mainUrl = "https://" + uri.host - - val encryptedId = cryptoHandler(id, foundIv, foundKey) - val encryptRequestData = if (isUsingAdaptiveData) { - // Only fetch the document if necessary - val realDocument = document ?: app.get(iframeUrl).document - val dataEncrypted = - realDocument.select("script[data-name='episode']").attr("data-value") - val headers = cryptoHandler(dataEncrypted, foundIv, foundKey, false) - "id=$encryptedId&alias=$id&" + headers.substringAfter("&") - } else { - "id=$encryptedId&alias=$id" - } - - val jsonResponse = - app.get( - "$mainUrl/encrypt-ajax.php?$encryptRequestData", - headers = mapOf("X-Requested-With" to "XMLHttpRequest") - ) - val dataencrypted = - jsonResponse.text.substringAfter("{\"data\":\"").substringBefore("\"}") - val datadecrypted = cryptoHandler(dataencrypted, foundIv, foundDecryptKey, false) - val sources = AppUtils.parseJson(datadecrypted) - - suspend fun invokeGogoSource( - source: GogoSource, - sourceCallback: (ExtractorLink) -> Unit - ) { - if (source.file.contains(".m3u8")) { - M3u8Helper.generateM3u8( - mainApiName, - source.file, - mainUrl, - headers = mapOf("Origin" to "https://plyr.link") - ).forEach(sourceCallback) - } else { - sourceCallback.invoke( - ExtractorLink( - mainApiName, - mainApiName, - source.file, - mainUrl, - getQualityFromName(source.label), - ) - ) - } - } - - sources.source?.forEach { - invokeGogoSource(it, callback) - } - sources.sourceBk?.forEach { - invokeGogoSource(it, callback) - } - } - - data class GogoSources( - @JsonProperty("source") val source: List?, - @JsonProperty("sourceBk") val sourceBk: List?, - //val track: List, - //val advertising: List, - //val linkiframe: String - ) - - data class GogoSource( - @JsonProperty("file") val file: String, - @JsonProperty("label") val label: String?, - @JsonProperty("type") val type: String?, - @JsonProperty("default") val default: String? = null - ) -} diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt index 17e2a8d1..80dfeafa 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt @@ -1,13 +1,13 @@ package com.hexated import com.fasterxml.jackson.annotation.JsonProperty -import com.hexated.GogoExtractor.extractVidstream import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.nicehttp.Requests import com.lagradost.nicehttp.Session import com.hexated.RabbitStream.extractRabbitStream +import com.lagradost.cloudstream3.extractors.helper.GogoHelper import com.lagradost.cloudstream3.network.CloudflareKiller import com.lagradost.nicehttp.RequestBodyTypes import kotlinx.coroutines.delay @@ -328,7 +328,7 @@ object SoraExtractor : SoraStream() { val iv = "9225679083961858" val secretKey = "25742532592138496744665879883281" val secretDecryptKey = secretKey - extractVidstream( + GogoHelper.extractVidstream( iframe.url, "Vidstream", callback, diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStreamPlugin.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStreamPlugin.kt index 5575dfba..27f4b152 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStreamPlugin.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStreamPlugin.kt @@ -11,18 +11,5 @@ class SoraStreamPlugin: Plugin() { // All providers should be added in this manner. Please don't edit the providers list directly. registerMainAPI(SoraStream()) registerMainAPI(SoraStreamLite()) - registerExtractorAPI(StreamM4u()) - registerExtractorAPI(Sblongvu()) - registerExtractorAPI(Keephealth()) - registerExtractorAPI(FileMoonIn()) - registerExtractorAPI(Sbnet()) - registerExtractorAPI(Chillx()) - registerExtractorAPI(Watchx()) - registerExtractorAPI(StreamhideCom()) - registerExtractorAPI(Movhide()) - registerExtractorAPI(Moviesm4u()) - registerExtractorAPI(Fembed9hd()) - registerExtractorAPI(Sbasian()) - registerExtractorAPI(Bestx()) } } \ No newline at end of file