From 5103ad09dc998d1cbfed197e83b5de594f8375ec Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Fri, 4 Aug 2023 17:20:23 +0200 Subject: [PATCH 1/3] reverted gradle bump --- app/build.gradle.kts | 6 +++--- build.gradle.kts | 3 ++- gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5c864117..9300775c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -115,11 +115,11 @@ android { compileOptions { isCoreLibraryDesugaringEnabled = true - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = "17" + jvmTarget = "1.8" freeCompilerArgs = listOf("-Xjvm-default=compatibility") } lint { diff --git a/build.gradle.kts b/build.gradle.kts index 972a4caf..762e4588 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,8 @@ buildscript { mavenCentral() } 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.dokka:dokka-gradle-plugin:1.5.0") diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d4745142..baa28c97 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Apr 30 17:11:15 CEST 2021 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 zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME From ca6700e28de32e99080d9b975fac83303bb5a0cc Mon Sep 17 00:00:00 2001 From: self-similarity <137652432+self-similarity@users.noreply.github.com> Date: Fri, 4 Aug 2023 15:21:20 +0000 Subject: [PATCH 2/3] More meaningful errors when adding repositories (#537) * More meaningful errors when adding repositories --- .../settings/extensions/ExtensionsFragment.kt | 21 +++++++++++++++++-- .../settings/extensions/PluginsViewModel.kt | 12 +++++++---- app/src/main/res/values/strings.xml | 2 ++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt index 7b72fc3b..8bc947c5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt @@ -224,14 +224,31 @@ class ExtensionsFragment : Fragment() { showToast(R.string.error_invalid_data, Toast.LENGTH_SHORT) } } else { + val repository = RepositoryManager.parseRepository(url) + + // Exit if wrong repository + if (repository == null) { + showToast(R.string.no_repository_found_error, Toast.LENGTH_LONG) + return@ioSafe + } + val fixedName = if (!name.isNullOrBlank()) name - else RepositoryManager.parseRepository(url)?.name ?: "No name" + else repository.name val newRepo = RepositoryData(fixedName, url) RepositoryManager.addRepository(newRepo) extensionViewModel.loadStats() extensionViewModel.loadRepositories() - this@ExtensionsFragment.activity?.downloadAllPluginsDialog(url, fixedName) + + val plugins = RepositoryManager.getRepoPlugins(url) + if (plugins.isNullOrEmpty()) { + showToast(R.string.no_plugins_found_error, Toast.LENGTH_LONG) + } else { + this@ExtensionsFragment.activity?.downloadAllPluginsDialog( + url, + fixedName + ) + } } } dialog.dismissSafe(activity) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt index 6c68ac17..471105be 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt @@ -86,13 +86,17 @@ class PluginsViewModel : ViewModel() { }.also { list -> main { showToast( - if (list.isEmpty()) { - txt( + when { + // No plugins at all + plugins.isEmpty() -> txt( + R.string.no_plugins_found_error, + ) + // All plugins downloaded + list.isEmpty() -> txt( R.string.batch_download_nothing_to_download_format, txt(R.string.plugin) ) - } else { - txt( + else -> txt( R.string.batch_download_start_format, list.size, txt(if (list.size == 1) R.string.plugin_singular else R.string.plugin) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4dd6eadc..3df4b8c1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -572,6 +572,8 @@ Started downloading %d %s… Downloaded %d %s All %s already downloaded + No plugins found in repository + Repository not found, check the URL and try VPN Batch download plugin plugins From bbbb7c4982d6f83f236883e2a9ed40d7a2b8eb61 Mon Sep 17 00:00:00 2001 From: Sofie <117321707+Sofie99@users.noreply.github.com> Date: Sat, 5 Aug 2023 08:11:46 +0700 Subject: [PATCH 3/3] Extractor: added Rabbitstream (#536) * Extractor: added Rabbitstream * fix all request --------- Co-authored-by: Sofie99 --- .../cloudstream3/extractors/Rabbitstream.kt | 170 ++++++++++++++++++ .../cloudstream3/utils/ExtractorApi.kt | 5 +- 2 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt new file mode 100644 index 00000000..b686f7d8 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt @@ -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() + val sources = encryptedMap?.sources + val decryptedSources = if (sources == null || encryptedMap.encrypted == false) { + response.parsedSafe() + } else { + val (key, encData) = extractRealKey(sources, getRawKey()) + val decrypted = decryptMapped>(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 { + val table = parseJson>>(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 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? = emptyList(), + @JsonProperty("tracks") val tracks: List? = emptyList(), + ) + + data class SourcesEncrypted( + @JsonProperty("sources") val sources: String? = null, + @JsonProperty("encrypted") val encrypted: Boolean? = null, + @JsonProperty("tracks") val tracks: List? = emptyList(), + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt index ed190bcc..83c61542 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -428,7 +428,10 @@ val extractorApis: MutableList = arrayListOf( Cda(), Dailymotion(), ByteShare(), - Ztreamhub() + Ztreamhub(), + Rabbitstream(), + Dokicloud(), + Megacloud(), )