diff --git a/Movierulzhd/build.gradle.kts b/Movierulzhd/build.gradle.kts index e995b96a..5d09bb3d 100644 --- a/Movierulzhd/build.gradle.kts +++ b/Movierulzhd/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 52 +version = 53 cloudstream { diff --git a/Movierulzhd/src/main/kotlin/com/hexated/Extractors.kt b/Movierulzhd/src/main/kotlin/com/hexated/Extractors.kt index c6ed476a..37772c96 100644 --- a/Movierulzhd/src/main/kotlin/com/hexated/Extractors.kt +++ b/Movierulzhd/src/main/kotlin/com/hexated/Extractors.kt @@ -1,37 +1,15 @@ 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.extractors.Filesim import com.lagradost.cloudstream3.utils.* -import kotlin.random.Random const val twoEmbedAPI = "https://www.2embed.to" -class Sbnmp : Filesim() { +class Sbnmp : ExtractorApi() { override val name = "Sbnmp" override var mainUrl = "https://sbnmp.bar" -} - -class Sbrulz : Sbflix() { - override val name = "Sbrulz" - override var mainUrl = "https://sbrulz.xyz" -} - -class Sbmiz : Sbflix() { - override val name = "Sbmiz" - override var mainUrl = "https://sbmiz.site" - } - -open class Sbflix : ExtractorApi() { - override val mainUrl = "https://sbflix.xyz" - override val name = "Sbflix" - override val requiresReferer = false - private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + override val requiresReferer = true override suspend fun getUrl( url: String, @@ -39,78 +17,26 @@ open class Sbflix : ExtractorApi() { 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
() + val response = app.get(url, referer = referer) + val script = if (!getPacked(response.text).isNullOrEmpty()) { + getAndUnpack(response.text) + } else { + response.document.selectFirst("script:containsData(sources:)")?.data() + } + val m3u8 = + Regex("file:\\s*\"(.*?m3u8.*?)\"").find(script ?: return)?.groupValues?.getOrNull(1) + callback.invoke( ExtractorLink( - name, - name, - mapped?.streamData?.file ?: return, - url, - Qualities.P720.value, - isM3u8 = true, - headers = headers + this.name, + this.name, + m3u8 ?: return, + mainUrl, + Qualities.Unknown.value, + INFER_TYPE, ) ) - - 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, - ) - } open class Akamaicdn : ExtractorApi() { @@ -140,43 +66,3 @@ open class Akamaicdn : ExtractorApi() { ) } } - -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/MovierulzhdPlugin.kt b/Movierulzhd/src/main/kotlin/com/hexated/MovierulzhdPlugin.kt index a97f4cc9..9ef951a8 100644 --- a/Movierulzhd/src/main/kotlin/com/hexated/MovierulzhdPlugin.kt +++ b/Movierulzhd/src/main/kotlin/com/hexated/MovierulzhdPlugin.kt @@ -11,9 +11,6 @@ class MovierulzhdPlugin: Plugin() { // All providers should be added in this manner. Please don't edit the providers list directly. registerMainAPI(Movierulzhd()) registerMainAPI(Hdmovie2()) - registerExtractorAPI(Sbflix()) - registerExtractorAPI(Sbrulz()) - registerExtractorAPI(Sbmiz()) registerExtractorAPI(Sbnmp()) registerExtractorAPI(Akamaicdn()) } diff --git a/Movierulzhd/src/main/kotlin/com/hexated/RabbitStream.kt b/Movierulzhd/src/main/kotlin/com/hexated/RabbitStream.kt deleted file mode 100644 index 41b73992..00000000 --- a/Movierulzhd/src/main/kotlin/com/hexated/RabbitStream.kt +++ /dev/null @@ -1,302 +0,0 @@ -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