diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 23fc9313f..cea339be1 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -53,7 +53,6 @@
-
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 80379704a..b7138af7b 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -14,7 +14,7 @@ val versionPatch = 0
val versionBuild = 0
val applicationName: String = libs.versions.applicationName.get()
val _applicationId: String = libs.versions.applicationId.get()
-val _versionName = "${versionMajor}.${versionMinor}.${versionPatch}"
+val _versionName = "${versionMajor}.${versionMinor}.${versionPatch}-beta" // TODO: Remove beta
android {
diff --git a/core/util/src/main/kotlin/com/flixclusive/core/util/network/CryptographyUtil.kt b/core/util/src/main/kotlin/com/flixclusive/core/util/network/CryptographyUtil.kt
index e5f0b10e7..0e892c30f 100644
--- a/core/util/src/main/kotlin/com/flixclusive/core/util/network/CryptographyUtil.kt
+++ b/core/util/src/main/kotlin/com/flixclusive/core/util/network/CryptographyUtil.kt
@@ -113,12 +113,12 @@ object CryptographyUtil {
* @param data The base64-encoded string to decode.
* @return The decoded string.
*/
- fun base64Decode(data: String): String = String(Base64.decode(data, Base64.DEFAULT))
+ fun base64Decode(data: String, flag: Int = Base64.DEFAULT): String = String(Base64.decode(data, flag))
/**
* Encodes the specified data into a base64-encoded string.
* @param data The data to encode.
* @return The base64-encoded string.
*/
- fun base64Encode(data: ByteArray): String = Base64.encodeToString(data, Base64.DEFAULT)
+ fun base64Encode(data: ByteArray, flag: Int = Base64.DEFAULT): String = String(Base64.encode(data, flag))
}
\ No newline at end of file
diff --git a/extractor/upcloud/src/main/kotlin/com/flixclusive/extractor/upcloud/VidCloud.kt b/extractor/upcloud/src/main/kotlin/com/flixclusive/extractor/upcloud/VidCloud.kt
index 152fe671a..8847c18dc 100644
--- a/extractor/upcloud/src/main/kotlin/com/flixclusive/extractor/upcloud/VidCloud.kt
+++ b/extractor/upcloud/src/main/kotlin/com/flixclusive/extractor/upcloud/VidCloud.kt
@@ -10,8 +10,7 @@ import com.flixclusive.core.util.network.request
import com.flixclusive.extractor.upcloud.dto.DecryptedSource
import com.flixclusive.extractor.upcloud.dto.UpCloudEmbedData
import com.flixclusive.extractor.upcloud.dto.UpCloudEmbedData.Companion.toSubtitle
-import com.flixclusive.extractor.upcloud.util.DecryptUtils.extractEmbedDecryptionDetails
-import com.flixclusive.extractor.upcloud.util.DecryptUtils.getKeyStops
+import com.flixclusive.extractor.upcloud.util.getKey
import com.flixclusive.model.provider.SourceLink
import com.flixclusive.model.provider.Subtitle
import com.flixclusive.provider.base.extractor.Extractor
@@ -33,7 +32,7 @@ class VidCloud(
override val host: String = "https://rabbitstream.net"
private val alternateHost: String = "https://dokicloud.one"
- private val e4ScriptEndpoint = "https://rabbitstream.net/js/player/prod/e4-player.min.js"
+ private val luckyAnimalImage = "https://rabbitstream.net/images/lucky_animal/icon.png"
private fun getHost(isAlternative: Boolean) =
(if (isAlternative) "DokiCloud" else "Rabbitstream")
@@ -79,18 +78,15 @@ class VidCloud(
var sources = mutableListOf()
if (upCloudEmbedData.encrypted) {
- val e4Script = client.request(
- url = e4ScriptEndpoint,
- headers = options
- ).execute().body?.string()
- ?: throw Exception("Cannot fetch key decoder")
-
- val stops = getKeyStops(e4Script)
- val (decryptedKey, newSource) = extractEmbedDecryptionDetails(upCloudEmbedData.sources, stops)
-
- sources = fromJson>(
- decryptAes(newSource, decryptedKey)
- )
+ client.request(luckyAnimalImage).execute()
+ .use { keyResponse ->
+ keyResponse.body?.run {
+ val key = getKey(byteStream())
+ sources = fromJson>(
+ decryptAes(upCloudEmbedData.sources, key)
+ )
+ }
+ }
}
diff --git a/extractor/upcloud/src/main/kotlin/com/flixclusive/extractor/upcloud/util/DecryptHelper.kt b/extractor/upcloud/src/main/kotlin/com/flixclusive/extractor/upcloud/util/DecryptHelper.kt
new file mode 100644
index 000000000..f377a2374
--- /dev/null
+++ b/extractor/upcloud/src/main/kotlin/com/flixclusive/extractor/upcloud/util/DecryptHelper.kt
@@ -0,0 +1,81 @@
+package com.flixclusive.extractor.upcloud.util
+
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.util.Base64
+import com.flixclusive.core.util.network.CryptographyUtil
+import java.io.InputStream
+
+fun getKey(responseBodyStream: InputStream): String {
+ val bitmap = BitmapFactory.decodeStream(responseBodyStream)
+
+ val rgba = bitmapToRgba(bitmap)
+
+ val binary = extractBitsFromImage(rgba)
+ val hexCompiledString = binaryToASCII(binary)
+ val keys = convertHexToIntegers(hexCompiledString)
+
+ return encodeIntegersToBase64(keys)
+}
+
+private fun bitmapToRgba(bitmap: Bitmap): ByteArray {
+ require(bitmap.config == Bitmap.Config.ARGB_8888) { "Bitmap must be in ARGB_8888 format" }
+ val pixels = IntArray(bitmap.width * bitmap.height)
+ val bytes = ByteArray(pixels.size * 4)
+ bitmap.getPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)
+ var i = 0
+ for (pixel in pixels) {
+ // Get components assuming is ARGB
+ val A = pixel shr 24 and 0xff
+ val R = pixel shr 16 and 0xff
+ val G = pixel shr 8 and 0xff
+ val B = pixel and 0xff
+ bytes[i++] = R.toByte()
+ bytes[i++] = G.toByte()
+ bytes[i++] = B.toByte()
+ bytes[i++] = A.toByte()
+ }
+ return bytes
+}
+
+private fun extractBitsFromImage(imageData: ByteArray): String {
+ val i = 8 * imageData[3]
+ val stringBuilder = StringBuilder()
+
+ for (j in 0 until i) {
+ stringBuilder.append(imageData[4 * (j + 1) + 3] % 2)
+ }
+
+ return stringBuilder.toString()
+}
+
+private fun binaryToASCII(string: String): String {
+ val chunks = string.chunked(8) // Splits the string into chunks of 8 characters
+ val asciiChars = chunks.map { chunk -> chunk.toInt(2).toChar() } // Converts each chunk from binary to decimal and then to its corresponding ASCII character
+ return asciiChars.joinToString("") // Joins the ASCII characters into a single string
+}
+
+private fun convertHexToIntegers(hexString: String): List {
+ val keys = mutableListOf()
+ var i = 0
+ while (i < hexString.length) {
+ // Convert each pair of hexadecimal characters into an integer
+ val hexPair = hexString.substring(i, i + 2)
+ keys.add(hexPair.toInt(16))
+ i += 2
+ }
+ return keys
+}
+
+private fun encodeIntegersToBase64(keys: List): String {
+ val keyBytes = keys.toByteArray()
+ return CryptographyUtil.base64Encode(keyBytes, Base64.NO_WRAP) // Encode the bytes to base64
+}
+
+private fun List.toByteArray(): ByteArray {
+ val result = ByteArray(size)
+ for (i in indices) {
+ result[i] = get(i).toByte()
+ }
+ return result
+}
\ No newline at end of file
diff --git a/extractor/upcloud/src/main/kotlin/com/flixclusive/extractor/upcloud/util/DecryptUtils.kt b/extractor/upcloud/src/main/kotlin/com/flixclusive/extractor/upcloud/util/DecryptUtils.kt
deleted file mode 100644
index b0e203272..000000000
--- a/extractor/upcloud/src/main/kotlin/com/flixclusive/extractor/upcloud/util/DecryptUtils.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-package com.flixclusive.extractor.upcloud.util
-
-typealias Stops = List>
-
-object DecryptUtils {
- fun getKeyStops(script: String): Stops {
- val startOfSwitch = script.lastIndexOf("switch")
- val endOfCases = script.indexOf("=partKey")
- val switchBody = script.substring(startOfSwitch, endOfCases)
-
- val stops = mutableListOf>()
- val regex = Regex(":[a-zA-Z0-9]+=([a-zA-Z0-9]+),[a-zA-Z0-9]+=([a-zA-Z0-9]+);")
- val matches = regex.findAll(switchBody)
-
- for (match in matches) {
- val innerNumbers = mutableListOf()
- for (varMatch in listOf(match.groupValues[1], match.groupValues[2])) {
- val regexStr = Regex("$varMatch=([a-zA-Z0-9]+)")
- val varMatches = regexStr.findAll(script)
- val lastMatch = varMatches.lastOrNull()?.groupValues?.lastOrNull() ?: return listOf()
- val number = if (lastMatch.startsWith("0x")) {
- lastMatch.substring(2).toInt(16)
- } else {
- lastMatch.toInt()
- }
- innerNumbers.add(number)
- }
- stops.add(innerNumbers)
- }
-
- return stops
- }
-
- fun extractEmbedDecryptionDetails(encryptedString: String, stops: Stops): Pair {
- var decryptedKey = ""
- var offset = 0
- var secondStopSum = 0
- var newEncryptedString = encryptedString
-
- for(stop in stops) {
- val start = stop[0] + secondStopSum
- val end = start + stop[1]
- secondStopSum += stop[1]
-
- decryptedKey += newEncryptedString.substring(
- start - offset,
- end - offset
- )
-
- newEncryptedString = newEncryptedString.replaceRange(
- start - offset,
- end - offset,
- ""
- )
-
- offset += end - start
- }
-
- return Pair(decryptedKey, newEncryptedString)
- }
-}
\ No newline at end of file