revert e2f32478fc
Some checks failed
CI / Build individual modules (push) Has been cancelled
CI / Publish repo (push) Has been cancelled
CI / Prepare job (push) Has been cancelled

revert megacloud fix (#1020)

yoinked from yuzono
This commit is contained in:
AlmightyHak 2025-06-19 13:32:58 -05:00
parent a4be7ee304
commit 38d2bdbb59
2 changed files with 26 additions and 108 deletions

View file

@ -2,7 +2,7 @@
// solution inspired from https://github.com/drblgn/rabbit_wasm/blob/main/rabbit.ts // 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 // 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/v2/e-1/'; const embed_url = 'https://megacloud.tv/embed-2/e-1/';
const referrer = 'https://hianime.to'; const referrer = 'https://hianime.to';
const user_agent = navigator.userAgent; const user_agent = navigator.userAgent;
let wasm; let wasm;
@ -31,7 +31,7 @@ const image_data = {
data: window.decoded_png, data: window.decoded_png,
}; };
const canvas = { const canvas = {
baseUrl: 'https://megacloud.tv/embed-2/v2/e-1/1hnXq7VzX0Ex?k=1', baseUrl: 'https://megacloud.tv/embed-2/e-1/1hnXq7VzX0Ex?k=1',
width: 0, width: 0,
height: 0, height: 0,
style: { style: {
@ -58,7 +58,7 @@ const fake_window = {
}, },
origin: 'https://megacloud.tv', origin: 'https://megacloud.tv',
location: { location: {
href: 'https://megacloud.tv/embed-2/v2/e-1/1hnXq7VzX0Ex?k=1', href: 'https://megacloud.tv/embed-2/e-1/1hnXq7VzX0Ex?k=1',
origin: 'https://megacloud.tv', origin: 'https://megacloud.tv',
}, },
performance: { performance: {
@ -327,9 +327,9 @@ function initWasm() {
__wbg_createElement_03cf347ddad1c8c0: function () { __wbg_createElement_03cf347ddad1c8c0: function () {
return applyToWindow(function ( return applyToWindow(function (
// @ts-ignore // @ts-ignore
index, index,
// @ts-ignore // @ts-ignore
decodeIndex, decodeIndex,
// @ts-ignore // @ts-ignore
decodeIndexOffset) { decodeIndexOffset) {
return addToStack(canvas); return addToStack(canvas);
@ -338,9 +338,9 @@ function initWasm() {
__wbg_querySelector_118a0639aa1f51cd: function () { __wbg_querySelector_118a0639aa1f51cd: function () {
return applyToWindow(function ( return applyToWindow(function (
// @ts-ignore // @ts-ignore
index, index,
// @ts-ignore // @ts-ignore
decodeIndex, decodeIndex,
// @ts-ignore // @ts-ignore
decodeOffset) { decodeOffset) {
//let item = get(index).querySelector(decodeSub(decodeIndex, decodeOffset)); //let item = get(index).querySelector(decodeSub(decodeIndex, decodeOffset));
@ -353,11 +353,11 @@ function initWasm() {
return addToStack(nodeList); return addToStack(nodeList);
}, arguments); }, arguments);
}, },
__wbg_getAttribute_706ae88bd37410fa: function (offset, __wbg_getAttribute_706ae88bd37410fa: function (offset,
// @ts-ignore // @ts-ignore
index, index,
// @ts-ignore // @ts-ignore
decodeIndex, decodeIndex,
// @ts-ignore // @ts-ignore
decodeOffset) { decodeOffset) {
//let attr = get(index).getAttribute(decodeSub(decodeIndex, decodeOffset)); //let attr = get(index).getAttribute(decodeSub(decodeIndex, decodeOffset));
@ -676,7 +676,7 @@ async function getSources(xrax) {
let res = {}; let res = {};
try { try {
await V(); await V();
let getSourcesUrl = 'https://megacloud.tv/embed-2/v2/e-1/getSources?id=' + let getSourcesUrl = 'https://megacloud.tv/embed-2/ajax/e-1/getSources?id=' +
fake_window.pid + fake_window.pid +
'&v=' + '&v=' +
fake_window.localStorage.kversion + fake_window.localStorage.kversion +
@ -688,7 +688,7 @@ async function getSources(xrax) {
headers: { headers: {
'User-Agent': user_agent, 'User-Agent': user_agent,
//"Referrer": fake_window.origin + "/v2/embed-4/" + xrax + "?z=", //"Referrer": fake_window.origin + "/v2/embed-4/" + xrax + "?z=",
Referer: embed_url + xrax + '?k=1&autoPlay=1&oa=0&asi=1', Referer: embed_url + xrax + '?k=1',
'X-Requested-With': 'XMLHttpRequest', 'X-Requested-With': 'XMLHttpRequest',
}, },
method: 'GET', method: 'GET',
@ -711,4 +711,4 @@ async function getSources(xrax) {
catch (err) { catch (err) {
console.error(err); console.error(err);
} }
} }

View file

@ -1,8 +1,6 @@
package eu.kanade.tachiyomi.lib.megacloudextractor package eu.kanade.tachiyomi.lib.megacloudextractor
import android.content.SharedPreferences 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.Track
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
@ -22,10 +20,6 @@ import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import uy.kohesive.injekt.injectLazy 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( class MegaCloudExtractor(
private val client: OkHttpClient, private val client: OkHttpClient,
@ -44,16 +38,16 @@ class MegaCloudExtractor(
companion object { companion object {
private val SERVER_URL = arrayOf("https://megacloud.tv", "https://rapid-cloud.co") private val SERVER_URL = arrayOf("https://megacloud.tv", "https://rapid-cloud.co")
private val SOURCES_URL = arrayOf("/embed-2/v2/e-1/getSources?id=", "/ajax/embed-6-v2/getSources?id=") private val SOURCES_URL = arrayOf("/embed-2/ajax/e-1/getSources?id=", "/ajax/embed-6-v2/getSources?id=")
private val SOURCES_SPLITTER = arrayOf("/e-1/", "/embed-6-v2/") private val SOURCES_SPLITTER = arrayOf("/e-1/", "/embed-6-v2/")
private val SOURCES_KEY = arrayOf("1", "6") private val SOURCES_KEY = arrayOf("1", "6")
private const val E1_SCRIPT_URL = "/js/player/a/v2/pro/embed-1.min.js" private const val E1_SCRIPT_URL = "https://megacloud.tv/js/player/a/prod/e1-player.min.js"
private const val E6_SCRIPT_URL = "/js/player/e6-player-v2.min.js" private const val E6_SCRIPT_URL = "https://rapid-cloud.co/js/player/prod/e6-player-v2.min.js"
private val MUTEX = Mutex() private val MUTEX = Mutex()
private var shouldUpdateKey = false private var shouldUpdateKey = false
private const val PREF_KEY_KEY = "megacloud_key_" private const val PREF_KEY_KEY = "megacloud_key_"
private const val PREF_KEY_DEFAULT = "[[0, 0]]" private const val PREF_KEY_DEFAULT = "[[0, 0]]"
private inline fun <reified R> runLocked(crossinline block: () -> R) = runBlocking(Dispatchers.IO) { private inline fun <reified R> runLocked(crossinline block: () -> R) = runBlocking(Dispatchers.IO) {
MUTEX.withLock { block() } MUTEX.withLock { block() }
} }
@ -72,8 +66,8 @@ class MegaCloudExtractor(
private fun updateKey(type: String) { private fun updateKey(type: String) {
val scriptUrl = when (type) { val scriptUrl = when (type) {
"1" -> "${SERVER_URL[0]}$E1_SCRIPT_URL" "1" -> E1_SCRIPT_URL
"6" -> "${SERVER_URL[1]}$E6_SCRIPT_URL" "6" -> E6_SCRIPT_URL
else -> throw Exception("Unknown key type") else -> throw Exception("Unknown key type")
} }
val script = noCacheClient.newCall(GET(scriptUrl, cache = cacheControl)) val script = noCacheClient.newCall(GET(scriptUrl, cache = cacheControl))
@ -148,21 +142,16 @@ class MegaCloudExtractor(
} }
private fun getVideoDto(url: String): VideoDto { private fun getVideoDto(url: String): VideoDto {
val type = if ( val type = if (url.startsWith("https://megacloud.tv") or url.startsWith("https://megacloud.blog")) 0 else 1
url.startsWith("https://megacloud.tv") ||
url.startsWith("https://megacloud.blog")
) 0 else 1
val keyType = SOURCES_KEY[type] val keyType = SOURCES_KEY[type]
val id = url.substringAfter(SOURCES_SPLITTER[type], "") val id = url.substringAfter(SOURCES_SPLITTER[type], "")
.substringBefore("?", "") .substringBefore("?", "").ifEmpty { throw Exception("I HATE THE ANTICHRIST") }
.ifEmpty { throw Exception("Failed to extract ID from URL") }
// Previous method using WebViewResolver to get key if (type == 0) {
// if (type == 0) { return webViewResolver.getSources(id)!!
// return webViewResolver.getSources(id)!! }
// }
val srcRes = client.newCall(GET(SERVER_URL[type] + SOURCES_URL[type] + id)) val srcRes = client.newCall(GET(SERVER_URL[type] + SOURCES_URL[type] + id))
.execute() .execute()
@ -173,82 +162,11 @@ class MegaCloudExtractor(
if (!data.encrypted) return json.decodeFromString<VideoDto>(srcRes) if (!data.encrypted) return json.decodeFromString<VideoDto>(srcRes)
val ciphered = data.sources.jsonPrimitive.content val ciphered = data.sources.jsonPrimitive.content
val decrypted = json.decodeFromString<List<VideoLink>>( val decrypted = json.decodeFromString<List<VideoLink>>(tryDecrypting(ciphered, keyType))
// tryDecrypting(ciphered, keyType),
tryDecrypting(ciphered),
)
return VideoDto(decrypted, data.tracks) 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<Map<String, String>>(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<ByteArray, ByteArray> {
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 @Serializable
data class VideoDto( data class VideoDto(
val sources: List<VideoLink>, val sources: List<VideoLink>,
@ -267,4 +185,4 @@ class MegaCloudExtractor(
@Serializable @Serializable
data class TrackDto(val file: String, val kind: String, val label: String = "") data class TrackDto(val file: String, val kind: String, val label: String = "")
} }