Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extractor: added Rabbitstream #536

Merged
merged 3 commits into from
Aug 5, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you are using something related to https://github.com/enimax-anime/enimax please add some sort of link to that in the top of the file along with the licence of enimax

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be cached with smth like

private var rawKey : String? = null 
suspend fun getRawKey() : String = rawKey ?: app.get(key).text.also { rawKey = it }

val response = app.get(
"$mainUrl/$embed/getSources?id=$id",
referer = mainUrl,
headers = mapOf("X-Requested-With" to "XMLHttpRequest")
)
val encryptedMap = response.parsedSafe<SourcesEncrypted>()
val sources = encryptedMap?.sources
val decryptedSources = if (sources == null || encryptedMap.encrypted == false) {
response.parsedSafe()
} else {
val (key, encData) = extractRealKey(sources, rawKey)
val decrypted = decryptMapped<List<Sources>>(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<String, String> {
val table = parseJson<List<List<Int>>>(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 <reified T> 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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add ?: throw ErrorLoadingExeption("Cipher not found")

so you dont need to do Objects.requireNonNull and !! later


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<Sources?>? = emptyList(),
@JsonProperty("tracks") val tracks: List<Tracks?>? = emptyList(),
)

data class SourcesEncrypted(
@JsonProperty("sources") val sources: String? = null,
@JsonProperty("encrypted") val encrypted: Boolean? = null,
@JsonProperty("tracks") val tracks: List<Tracks?>? = emptyList(),
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,10 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
Cda(),
Dailymotion(),
ByteShare(),
Ztreamhub()
Ztreamhub(),
Rabbitstream(),
Dokicloud(),
Megacloud(),
)


Expand Down
Loading