This commit is contained in:
jack 2023-11-21 23:51:29 +07:00
parent 379525d83b
commit 40163c8a4f
4 changed files with 18 additions and 437 deletions

View File

@ -1,5 +1,5 @@
// use an integer for version numbers
version = 52
version = 53
cloudstream {

View File

@ -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<Main>()
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<Subs>? = 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<EmbedJson>()?.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<String?> = arrayListOf(),
@JsonProperty("tracks") val tracks: List<String>? = null,
)

View File

@ -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())
}

View File

@ -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<PollingData>(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<SourceObjectEncrypted>()
val sources = encryptedMap?.sources
if (sources == null || encryptedMap.encrypted == false) {
response.parsedSafe()
} else {
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(
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<ExtractorLink>? {
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<PollingData?>(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 <reified T> 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<String> = 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<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("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<Tracks?>?
)
}