diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index edf31bba..251290d9 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -13,7 +13,7 @@ concurrency: cancel-in-progress: true env: - CI_CHUNK_SIZE: 288 + CI_CHUNK_SIZE: 65 jobs: prepare: @@ -86,7 +86,7 @@ jobs: build-cache-${{ github.event.pull_request.base.sha }}- build-cache- - - name: Build extensions + - name: Build extensions (chunk ${{ matrix.chunk }}) env: CI_CHUNK_NUM: ${{ matrix.chunk }} run: chmod +x ./gradlew && ./gradlew -p src assembleDebug diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 419bc51c..68b8173e 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -37,14 +37,15 @@ jobs: projects=(src/*/*) echo "NUM_INDIVIDUAL_MODULES=${#projects[@]}" >> "$GITHUB_ENV" - - name: Find lib changes - id: modified-libs - uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46.0.5 - with: - files: lib/ - files_ignore: lib/**.md - files_separator: " " - safe_output: false + # Temporary pause because of leak of tj-actions/changed-files + # - name: Find lib changes + # id: modified-libs + # uses: tj-actions/changed-files@90a06d6ba9543371ab4df8eeca0be07ca6054959 #v42 + # with: + # files: lib/ + # files_ignore: lib/**.md + # files_separator: " " + # safe_output: false - name: Import GPG key uses: https://github.com/crazy-max/ghaction-import-gpg@v6 # v6.1.0 @@ -54,11 +55,12 @@ jobs: git_user_signingkey: true git_commit_gpgsign: true - - name: Bump extensions that uses a modified lib - if: steps.modified-libs.outputs.any_changed == 'true' - run: | - chmod +x ./.github/scripts/bump-versions.py - ./.github/scripts/bump-versions.py ${{ steps.modified-libs.outputs.all_changed_files }} + # # This step is going to commit, but this will not trigger another workflow. + # - name: Bump extensions that uses a modified lib + # if: steps.modified-libs.outputs.any_changed == 'true' + # run: | + # chmod +x ./.github/scripts/bump-versions.py + # ./.github/scripts/bump-versions.py ${{ steps.modified-libs.outputs.all_changed_files }} - id: generate-matrices name: Create output matrices @@ -104,17 +106,6 @@ jobs: - name: Set up Gradle uses: https://github.com/gradle/actions/setup-gradle@245c8a24de79c0dbeabaf19ebcbbd3b2c36f278d # v4 - - name: Restore build cache - uses: https://github.com/actions/cache/restore@v3 - with: - path: | - src/**/build - !src/**/build/outputs - key: build-cache-${{ github.sha }}-${{ matrix.chunk }} - restore-keys: | - build-cache-${{ github.sha }}- - build-cache- - - name: Build extensions env: CI_CHUNK_NUM: ${{ matrix.chunk }} @@ -123,7 +114,7 @@ jobs: KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} run: chmod +x ./gradlew && ./gradlew -p src assembleRelease - - name: Store build cache + - name: Cache build directories uses: https://github.com/actions/cache/save@v3 with: path: | @@ -195,10 +186,6 @@ jobs: - name: Sync repo run: | rsync -a --delete --exclude .git --exclude .gitignore main/repo/ repo --exclude README.md --exclude repo.json - - - name: Increase buffer size - run: | - git config --global http.postBuffer 157286400 - name: Deploy repo uses: https://github.com/EndBug/add-and-commit@v9 diff --git a/lib/megacloud-extractor/src/main/assets/megacloud.getsrcs.js b/lib/megacloud-extractor/src/main/assets/megacloud.getsrcs.js index a8b05bf3..68b29a79 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/v2/e-1/'; +const embed_url = 'https://megacloud.tv/embed-2/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/v2/e-1/1hnXq7VzX0Ex?k=1', + baseUrl: 'https://megacloud.tv/embed-2/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/v2/e-1/1hnXq7VzX0Ex?k=1', + href: 'https://megacloud.tv/embed-2/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/v2/e-1/getSources?id=' + + let getSourcesUrl = 'https://megacloud.tv/embed-2/ajax/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&autoPlay=1&oa=0&asi=1', + Referer: embed_url + xrax + '?k=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 +} 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 b6d82ea4..6b4d2fcf 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,8 +1,6 @@ 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 @@ -22,12 +20,7 @@ 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 -// MegaCloudExtractor class MegaCloudExtractor( private val client: OkHttpClient, private val headers: Headers, @@ -45,16 +38,16 @@ class MegaCloudExtractor( companion object { 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_KEY = arrayOf("1", "6") - 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 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 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() } } @@ -73,8 +66,8 @@ class MegaCloudExtractor( private fun updateKey(type: String) { val scriptUrl = when (type) { - "1" -> "${SERVER_URL[0]}$E1_SCRIPT_URL" - "6" -> "${SERVER_URL[1]}$E6_SCRIPT_URL" + "1" -> E1_SCRIPT_URL + "6" -> E6_SCRIPT_URL else -> throw Exception("Unknown key type") } val script = noCacheClient.newCall(GET(scriptUrl, cache = cacheControl)) @@ -149,21 +142,16 @@ class MegaCloudExtractor( } private fun getVideoDto(url: String): VideoDto { - val type = if ( - url.startsWith("https://megacloud.tv") || - url.startsWith("https://megacloud.blog") - ) 0 else 1 + val type = if (url.startsWith("https://megacloud.tv") or url.startsWith("https://megacloud.blog")) 0 else 1 val keyType = SOURCES_KEY[type] val id = url.substringAfter(SOURCES_SPLITTER[type], "") - .substringBefore("?", "") - .ifEmpty { throw Exception("Failed to extract ID from URL") } + .substringBefore("?", "").ifEmpty { throw Exception("I HATE THE ANTICHRIST") } - // Previous method using WebViewResolver to get key - // if (type == 0) { - // return webViewResolver.getSources(id)!! - // } + if (type == 0) { + return webViewResolver.getSources(id)!! + } val srcRes = client.newCall(GET(SERVER_URL[type] + SOURCES_URL[type] + id)) .execute() @@ -174,82 +162,11 @@ class MegaCloudExtractor( if (!data.encrypted) return json.decodeFromString(srcRes) val ciphered = data.sources.jsonPrimitive.content - val decrypted = json.decodeFromString>( - // tryDecrypting(ciphered, keyType), - tryDecrypting(ciphered), - ) + val decrypted = json.decodeFromString>(tryDecrypting(ciphered, keyType)) 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, @@ -268,4 +185,4 @@ class MegaCloudExtractor( @Serializable data class TrackDto(val file: String, val kind: String, val label: String = "") -} \ No newline at end of file +} diff --git a/lib/voe-extractor/src/main/java/eu/kanade/tachiyomi/lib/voeextractor/VoeExtractor.kt b/lib/voe-extractor/src/main/java/eu/kanade/tachiyomi/lib/voeextractor/VoeExtractor.kt index fe017e16..1e30fb57 100644 --- a/lib/voe-extractor/src/main/java/eu/kanade/tachiyomi/lib/voeextractor/VoeExtractor.kt +++ b/lib/voe-extractor/src/main/java/eu/kanade/tachiyomi/lib/voeextractor/VoeExtractor.kt @@ -18,33 +18,14 @@ class VoeExtractor(private val client: OkHttpClient) { private val playlistUtils by lazy { PlaylistUtils(clientDdos) } + private val linkRegex = "(http|https)://([\\w_-]+(?:\\.[\\w_-]+)+)([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])".toRegex() + + private val base64Regex = Regex("'.*'") + + private val scriptBase64Regex = "(let|var)\\s+\\w+\\s*=\\s*'(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)';".toRegex() + @Serializable - data class VideoLinkDTO(val source: String) - - private fun decodeVoeData(data: String): String { - val shifted = data.map { char -> - when (char) { - in 'A'..'Z' -> 'A' + (char - 'A' + 13).mod(26) - in 'a'..'z' -> 'a' + (char - 'a' + 13).mod(26) - else -> char - } - }.joinToString() - - val junk = listOf("@$", "^^", "~@", "%?", "*~", "!!", "#&") - var result = shifted - for (part in junk) { - result = result.replace(part, "_") - } - val clean = result.replace("_", "") - - val transformed = String(Base64.decode(clean, Base64.DEFAULT)).map { - (it.code - 3).toChar() - }.joinToString().reversed() - - val decoded = String(Base64.decode(transformed, Base64.DEFAULT)) - - return json.decodeFromString(decoded).source - } + data class VideoLinkDTO(val file: String) fun videosFromUrl(url: String, prefix: String = ""): List