From cc12318692c97d06f2d8f0d3fd7a8ab238b03913 Mon Sep 17 00:00:00 2001 From: AlmightyHak Date: Thu, 19 Jun 2025 12:39:19 -0500 Subject: [PATCH 1/2] yoinked from yuzono --- .../src/main/assets/megacloud.getsrcs.js | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/megacloud-extractor/src/main/assets/megacloud.getsrcs.js b/lib/megacloud-extractor/src/main/assets/megacloud.getsrcs.js index 68b29a79..a8b05bf3 100644 --- a/lib/megacloud-extractor/src/main/assets/megacloud.getsrcs.js +++ b/lib/megacloud-extractor/src/main/assets/megacloud.getsrcs.js @@ -2,7 +2,7 @@ // solution inspired from https://github.com/drblgn/rabbit_wasm/blob/main/rabbit.ts // solution inspired from https://github.com/shimizudev/consumet.ts/blob/master/dist/extractors/megacloud/megacloud.getsrcs.js -const embed_url = 'https://megacloud.tv/embed-2/e-1/'; +const embed_url = 'https://megacloud.tv/embed-2/v2/e-1/'; const referrer = 'https://hianime.to'; const user_agent = navigator.userAgent; let wasm; @@ -31,7 +31,7 @@ const image_data = { data: window.decoded_png, }; const canvas = { - baseUrl: 'https://megacloud.tv/embed-2/e-1/1hnXq7VzX0Ex?k=1', + baseUrl: 'https://megacloud.tv/embed-2/v2/e-1/1hnXq7VzX0Ex?k=1', width: 0, height: 0, style: { @@ -58,7 +58,7 @@ const fake_window = { }, origin: 'https://megacloud.tv', location: { - href: 'https://megacloud.tv/embed-2/e-1/1hnXq7VzX0Ex?k=1', + href: 'https://megacloud.tv/embed-2/v2/e-1/1hnXq7VzX0Ex?k=1', origin: 'https://megacloud.tv', }, performance: { @@ -327,9 +327,9 @@ function initWasm() { __wbg_createElement_03cf347ddad1c8c0: function () { return applyToWindow(function ( // @ts-ignore - index, + index, // @ts-ignore - decodeIndex, + decodeIndex, // @ts-ignore decodeIndexOffset) { return addToStack(canvas); @@ -338,9 +338,9 @@ function initWasm() { __wbg_querySelector_118a0639aa1f51cd: function () { return applyToWindow(function ( // @ts-ignore - index, + index, // @ts-ignore - decodeIndex, + decodeIndex, // @ts-ignore decodeOffset) { //let item = get(index).querySelector(decodeSub(decodeIndex, decodeOffset)); @@ -353,11 +353,11 @@ function initWasm() { return addToStack(nodeList); }, arguments); }, - __wbg_getAttribute_706ae88bd37410fa: function (offset, + __wbg_getAttribute_706ae88bd37410fa: function (offset, // @ts-ignore - index, + index, // @ts-ignore - decodeIndex, + decodeIndex, // @ts-ignore decodeOffset) { //let attr = get(index).getAttribute(decodeSub(decodeIndex, decodeOffset)); @@ -676,7 +676,7 @@ async function getSources(xrax) { let res = {}; try { await V(); - let getSourcesUrl = 'https://megacloud.tv/embed-2/ajax/e-1/getSources?id=' + + let getSourcesUrl = 'https://megacloud.tv/embed-2/v2/e-1/getSources?id=' + fake_window.pid + '&v=' + fake_window.localStorage.kversion + @@ -688,7 +688,7 @@ async function getSources(xrax) { headers: { 'User-Agent': user_agent, //"Referrer": fake_window.origin + "/v2/embed-4/" + xrax + "?z=", - Referer: embed_url + xrax + '?k=1', + Referer: embed_url + xrax + '?k=1&autoPlay=1&oa=0&asi=1', 'X-Requested-With': 'XMLHttpRequest', }, method: 'GET', @@ -711,4 +711,4 @@ async function getSources(xrax) { catch (err) { console.error(err); } -} +} \ No newline at end of file -- 2.47.2 From d2579b9f06cc915580ba69e942c44580fcfe2ff6 Mon Sep 17 00:00:00 2001 From: AlmightyHak Date: Thu, 19 Jun 2025 12:40:25 -0500 Subject: [PATCH 2/2] Update lib/megacloud-extractor/src/main/java/eu/kanade/tachiyomi/lib/megacloudextractor/MegaCloudExtractor.kt --- .../megacloudextractor/MegaCloudExtractor.kt | 108 +++++++++++++++--- 1 file changed, 95 insertions(+), 13 deletions(-) diff --git a/lib/megacloud-extractor/src/main/java/eu/kanade/tachiyomi/lib/megacloudextractor/MegaCloudExtractor.kt b/lib/megacloud-extractor/src/main/java/eu/kanade/tachiyomi/lib/megacloudextractor/MegaCloudExtractor.kt index 6b4d2fcf..2494ccef 100644 --- a/lib/megacloud-extractor/src/main/java/eu/kanade/tachiyomi/lib/megacloudextractor/MegaCloudExtractor.kt +++ b/lib/megacloud-extractor/src/main/java/eu/kanade/tachiyomi/lib/megacloudextractor/MegaCloudExtractor.kt @@ -1,6 +1,8 @@ package eu.kanade.tachiyomi.lib.megacloudextractor import android.content.SharedPreferences +import android.util.Base64 +import android.util.Log import eu.kanade.tachiyomi.animesource.model.Track import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES @@ -20,6 +22,10 @@ import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient import uy.kohesive.injekt.injectLazy +import java.security.MessageDigest +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec class MegaCloudExtractor( private val client: OkHttpClient, @@ -38,16 +44,16 @@ class MegaCloudExtractor( companion object { private val SERVER_URL = arrayOf("https://megacloud.tv", "https://rapid-cloud.co") - private val SOURCES_URL = arrayOf("/embed-2/ajax/e-1/getSources?id=", "/ajax/embed-6-v2/getSources?id=") + private val SOURCES_URL = arrayOf("/embed-2/v2/e-1/getSources?id=", "/ajax/embed-6-v2/getSources?id=") private val SOURCES_SPLITTER = arrayOf("/e-1/", "/embed-6-v2/") private val SOURCES_KEY = arrayOf("1", "6") - private const val E1_SCRIPT_URL = "https://megacloud.tv/js/player/a/prod/e1-player.min.js" - private const val E6_SCRIPT_URL = "https://rapid-cloud.co/js/player/prod/e6-player-v2.min.js" + private const val E1_SCRIPT_URL = "/js/player/a/v2/pro/embed-1.min.js" + private const val E6_SCRIPT_URL = "/js/player/e6-player-v2.min.js" private val MUTEX = Mutex() private var shouldUpdateKey = false private const val PREF_KEY_KEY = "megacloud_key_" private const val PREF_KEY_DEFAULT = "[[0, 0]]" - + private inline fun runLocked(crossinline block: () -> R) = runBlocking(Dispatchers.IO) { MUTEX.withLock { block() } } @@ -66,8 +72,8 @@ class MegaCloudExtractor( private fun updateKey(type: String) { val scriptUrl = when (type) { - "1" -> E1_SCRIPT_URL - "6" -> E6_SCRIPT_URL + "1" -> "${SERVER_URL[0]}$E1_SCRIPT_URL" + "6" -> "${SERVER_URL[1]}$E6_SCRIPT_URL" else -> throw Exception("Unknown key type") } val script = noCacheClient.newCall(GET(scriptUrl, cache = cacheControl)) @@ -142,16 +148,21 @@ class MegaCloudExtractor( } private fun getVideoDto(url: String): VideoDto { - val type = if (url.startsWith("https://megacloud.tv") or url.startsWith("https://megacloud.blog")) 0 else 1 + val type = if ( + url.startsWith("https://megacloud.tv") || + url.startsWith("https://megacloud.blog") + ) 0 else 1 val keyType = SOURCES_KEY[type] val id = url.substringAfter(SOURCES_SPLITTER[type], "") - .substringBefore("?", "").ifEmpty { throw Exception("I HATE THE ANTICHRIST") } + .substringBefore("?", "") + .ifEmpty { throw Exception("Failed to extract ID from URL") } - if (type == 0) { - return webViewResolver.getSources(id)!! - } + // Previous method using WebViewResolver to get key + // if (type == 0) { + // return webViewResolver.getSources(id)!! + // } val srcRes = client.newCall(GET(SERVER_URL[type] + SOURCES_URL[type] + id)) .execute() @@ -162,11 +173,82 @@ class MegaCloudExtractor( if (!data.encrypted) return json.decodeFromString(srcRes) val ciphered = data.sources.jsonPrimitive.content - val decrypted = json.decodeFromString>(tryDecrypting(ciphered, keyType)) + val decrypted = json.decodeFromString>( + // tryDecrypting(ciphered, keyType), + tryDecrypting(ciphered), + ) return VideoDto(decrypted, data.tracks) } + var megaKey: String? = null + + private fun tryDecrypting(ciphered: String): String { + return megaKey?.let { key -> + try { + decryptOpenSSL(ciphered, key).also { + Log.i("MegaCloudExtractor", "Decrypted URL: $it") + } + } catch (e: RuntimeException) { + Log.e("MegaCloudExtractor", "Decryption failed with existing key: ${e.message}") + decryptWithNewKey(ciphered) + } + } ?: decryptWithNewKey(ciphered) + } + + private fun decryptWithNewKey(ciphered: String): String { + val newKey = requestNewKey() + megaKey = newKey + return decryptOpenSSL(ciphered, newKey).also { + Log.i("MegaCloudExtractor", "Decrypted URL with new key: $it") + } + } + + private fun requestNewKey(): String = + client.newCall(GET("https://raw.githubusercontent.com/yogesh-hacker/MegacloudKeys/refs/heads/main/keys.json")) + .execute() + .use { response -> + if (!response.isSuccessful) throw IllegalStateException("Failed to fetch keys.json") + val jsonStr = response.body.string() + if (jsonStr.isEmpty()) throw IllegalStateException("keys.json is empty") + val key = json.decodeFromString>(jsonStr)["mega"] + ?: throw IllegalStateException("Mega key not found in keys.json") + Log.i("MegaCloudExtractor", "Using Mega Key: $key") + megaKey = key + key + } + + private fun decryptOpenSSL(encBase64: String, password: String): String { + try { + val data = Base64.decode(encBase64, Base64.NO_WRAP) // Base64.DEFAULT or Base64.NO_WRAP + require(data.copyOfRange(0, 8).contentEquals("Salted__".toByteArray())) + val salt = data.copyOfRange(8, 16) + val (key, iv) = opensslKeyIv(password.toByteArray(), salt) + + val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") + val secretKey = SecretKeySpec(key, "AES") + val ivSpec = IvParameterSpec(iv) + cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec) + + val decrypted = cipher.doFinal(data.copyOfRange(16, data.size)) + return String(decrypted) + } catch (e: Exception) { + Log.e("DecryptOpenSSL", "Decryption failed: ${e.message}") + throw RuntimeException("Decryption failed: ${e.message}", e) + } + } + + private fun opensslKeyIv(password: ByteArray, salt: ByteArray, keyLen: Int = 32, ivLen: Int = 16): Pair { + var d = ByteArray(0) + var d_i = ByteArray(0) + while (d.size < keyLen + ivLen) { + val md = MessageDigest.getInstance("MD5") + d_i = md.digest(d_i + password + salt) + d += d_i + } + return Pair(d.copyOfRange(0, keyLen), d.copyOfRange(keyLen, keyLen + ivLen)) + } + @Serializable data class VideoDto( val sources: List, @@ -185,4 +267,4 @@ class MegaCloudExtractor( @Serializable data class TrackDto(val file: String, val kind: String, val label: String = "") -} +} \ No newline at end of file -- 2.47.2