mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Merge branch 'recloudstream:master' into master
This commit is contained in:
commit
e19127be80
5 changed files with 180 additions and 6 deletions
|
@ -115,11 +115,11 @@ android {
|
||||||
compileOptions {
|
compileOptions {
|
||||||
isCoreLibraryDesugaringEnabled = true
|
isCoreLibraryDesugaringEnabled = true
|
||||||
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "17"
|
jvmTarget = "1.8"
|
||||||
freeCompilerArgs = listOf("-Xjvm-default=compatibility")
|
freeCompilerArgs = listOf("-Xjvm-default=compatibility")
|
||||||
}
|
}
|
||||||
lint {
|
lint {
|
||||||
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
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
|
||||||
|
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 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"
|
||||||
|
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"
|
||||||
|
private var rawKey: String? = null
|
||||||
|
|
||||||
|
override suspend fun getUrl(
|
||||||
|
url: String,
|
||||||
|
referer: String?,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
) {
|
||||||
|
val id = url.substringAfterLast("/").substringBefore("?")
|
||||||
|
|
||||||
|
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, getRawKey())
|
||||||
|
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 suspend fun getRawKey(): String = rawKey ?: app.get(key).text.also { rawKey = it }
|
||||||
|
|
||||||
|
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")
|
||||||
|
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")
|
||||||
|
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(),
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
|
@ -428,7 +428,10 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
||||||
Cda(),
|
Cda(),
|
||||||
Dailymotion(),
|
Dailymotion(),
|
||||||
ByteShare(),
|
ByteShare(),
|
||||||
Ztreamhub()
|
Ztreamhub(),
|
||||||
|
Rabbitstream(),
|
||||||
|
Dokicloud(),
|
||||||
|
Megacloud(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,8 @@ buildscript {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools.build:gradle:8.0.2")
|
// we stay on low ver because prerelease build gradle is fucked
|
||||||
|
classpath("com.android.tools.build:gradle:7.3.1")
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.20")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.20")
|
||||||
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.5.0")
|
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.5.0")
|
||||||
|
|
||||||
|
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
||||||
#Fri Apr 30 17:11:15 CEST 2021
|
#Fri Apr 30 17:11:15 CEST 2021
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue