feat(lib/megacloud): Save key to preferences (#2758)

This commit is contained in:
Secozzi 2024-01-15 21:25:08 +00:00 committed by GitHub
parent f87cc5ce9d
commit 8dc70c0d0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 64 additions and 65 deletions

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.lib.megacloudextractor package eu.kanade.tachiyomi.lib.megacloudextractor
import android.content.SharedPreferences
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
@ -10,6 +11,7 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
@ -19,7 +21,11 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class MegaCloudExtractor(private val client: OkHttpClient, private val headers: Headers) { class MegaCloudExtractor(
private val client: OkHttpClient,
private val headers: Headers,
private val preferences: SharedPreferences
) {
private val json: Json by injectLazy() private val json: Json by injectLazy()
private val playlistUtils by lazy { PlaylistUtils(client, headers) } private val playlistUtils by lazy { PlaylistUtils(client, headers) }
@ -36,56 +42,65 @@ class MegaCloudExtractor(private val client: OkHttpClient, private val headers:
private val SOURCES_KEY = arrayOf("1", "6") 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 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 E6_SCRIPT_URL = "https://rapid-cloud.co/js/player/prod/e6-player-v2.min.js"
private const val E4_SCRIPT_URL = "https://rabbitstream.net/js/player/prod/e4-player.min.js"
private val INDEX_PAIRS_MAP = mutableMapOf("1" to emptyList<List<Int>>(), "6" to emptyList<List<Int>>())
private val MUTEX = Mutex() 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 <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() }
} }
} }
private fun getIndexPairs(type: String) = runLocked { // Stolen from TurkAnime
INDEX_PAIRS_MAP[type].orEmpty().ifEmpty { private fun getKey(type: String): List<List<Int>> = runLocked {
val scriptUrl = when (type) { if (shouldUpdateKey) {
"1" -> E1_SCRIPT_URL updateKey(type)
"6" -> E6_SCRIPT_URL shouldUpdateKey = false
"4" -> E4_SCRIPT_URL }
else -> throw Exception("Unknown key type") json.decodeFromString<List<List<Int>>>(
} preferences.getString(PREF_KEY_KEY + type, PREF_KEY_DEFAULT)!!
val script = noCacheClient.newCall(GET(scriptUrl, cache = cacheControl)) )
.execute() }
.use { it.body.string() }
val regex =
Regex("case\\s*0x[0-9a-f]+:(?![^;]*=partKey)\\s*\\w+\\s*=\\s*(\\w+)\\s*,\\s*\\w+\\s*=\\s*(\\w+);")
val matches = regex.findAll(script).toList()
val indexPairs = matches.map { match ->
val var1 = match.groupValues[1]
val var2 = match.groupValues[2]
val regexVar1 = Regex(",${var1}=((?:0x)?([0-9a-fA-F]+))") private fun updateKey(type: String) {
val regexVar2 = Regex(",${var2}=((?:0x)?([0-9a-fA-F]+))") val scriptUrl = when (type) {
"1" -> E1_SCRIPT_URL
"6" -> E6_SCRIPT_URL
else -> throw Exception("Unknown key type")
}
val script = noCacheClient.newCall(GET(scriptUrl, cache = cacheControl))
.execute()
.use { it.body.string() }
val regex =
Regex("case\\s*0x[0-9a-f]+:(?![^;]*=partKey)\\s*\\w+\\s*=\\s*(\\w+)\\s*,\\s*\\w+\\s*=\\s*(\\w+);")
val matches = regex.findAll(script).toList()
val indexPairs = matches.map { match ->
val var1 = match.groupValues[1]
val var2 = match.groupValues[2]
val matchVar1 = regexVar1.find(script)?.groupValues?.get(1)?.removePrefix("0x") val regexVar1 = Regex(",${var1}=((?:0x)?([0-9a-fA-F]+))")
val matchVar2 = regexVar2.find(script)?.groupValues?.get(1)?.removePrefix("0x") val regexVar2 = Regex(",${var2}=((?:0x)?([0-9a-fA-F]+))")
if (matchVar1 != null && matchVar2 != null) { val matchVar1 = regexVar1.find(script)?.groupValues?.get(1)?.removePrefix("0x")
try { val matchVar2 = regexVar2.find(script)?.groupValues?.get(1)?.removePrefix("0x")
listOf(matchVar1.toInt(16), matchVar2.toInt(16))
} catch (e: NumberFormatException) { if (matchVar1 != null && matchVar2 != null) {
emptyList() try {
} listOf(matchVar1.toInt(16), matchVar2.toInt(16))
} else { } catch (e: NumberFormatException) {
emptyList() emptyList()
} }
}.filter { it.isNotEmpty() } } else {
INDEX_PAIRS_MAP[type] = indexPairs emptyList()
indexPairs }
} }.filter { it.isNotEmpty() }
val encoded = json.encodeToString(indexPairs)
preferences.edit().putString(PREF_KEY_KEY + type, encoded).apply()
} }
private fun cipherTextCleaner(data: String, type: String): Pair<String, String> { private fun cipherTextCleaner(data: String, type: String): Pair<String, String> {
val indexPairs = getIndexPairs(type) val indexPairs = getKey(type)
val (password, ciphertext, _) = indexPairs.fold(Triple("", data, 0)) { previous, item -> val (password, ciphertext, _) = indexPairs.fold(Triple("", data, 0)) { previous, item ->
val start = item.first() + previous.third val start = item.first() + previous.third
val end = start + item.last() val end = start + item.last()
@ -103,7 +118,7 @@ class MegaCloudExtractor(private val client: OkHttpClient, private val headers:
val (ciphertext, password) = cipherTextCleaner(ciphered, type) val (ciphertext, password) = cipherTextCleaner(ciphered, type)
return CryptoAES.decrypt(ciphertext, password).ifEmpty { return CryptoAES.decrypt(ciphertext, password).ifEmpty {
// Update index pairs // Update index pairs
runLocked { INDEX_PAIRS_MAP[type] = emptyList<List<Int>>() } shouldUpdateKey = true
tryDecrypting(ciphered, type, attempts + 1) tryDecrypting(ciphered, type, attempts + 1)
} }
} }
@ -138,7 +153,7 @@ class MegaCloudExtractor(private val client: OkHttpClient, private val headers:
if (!data.encrypted) return json.decodeFromString<VideoDto>(srcRes) if (!data.encrypted) return json.decodeFromString<VideoDto>(srcRes)
val ciphered = data.sources.jsonPrimitive.content.toString() val ciphered = data.sources.jsonPrimitive.content
val decrypted = json.decodeFromString<List<VideoLink>>(tryDecrypting(ciphered, keyType)) val decrypted = json.decodeFromString<List<VideoLink>>(tryDecrypting(ciphered, keyType))
return VideoDto(decrypted, data.tracks) return VideoDto(decrypted, data.tracks)

View File

@ -17,7 +17,7 @@ class Kaido : ZoroTheme(
) )
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) } private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
private val megaCloudExtractor by lazy { MegaCloudExtractor(client, headers) } private val megaCloudExtractor by lazy { MegaCloudExtractor(client, headers, preferences) }
override fun extractVideo(server: VideoData): List<Video> { override fun extractVideo(server: VideoData): List<Video> {
return when (server.name) { return when (server.name) {

View File

@ -1,4 +0,0 @@
dependencies {
implementation(project(":lib-megacloud-extractor"))
implementation(project(':lib-streamtape-extractor'))
}

View File

@ -21,7 +21,7 @@ class AniWatch : ZoroTheme(
) )
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) } private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
private val megaCloudExtractor by lazy { MegaCloudExtractor(client, headers) } private val megaCloudExtractor by lazy { MegaCloudExtractor(client, headers, preferences) }
override fun extractVideo(server: VideoData): List<Video> { override fun extractVideo(server: VideoData): List<Video> {
return when (server.name) { return when (server.name) {

View File

@ -16,10 +16,8 @@ import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.multisrc.zorotheme.dto.HtmlResponse import eu.kanade.tachiyomi.multisrc.zorotheme.dto.HtmlResponse
import eu.kanade.tachiyomi.multisrc.zorotheme.dto.SourcesResponse import eu.kanade.tachiyomi.multisrc.zorotheme.dto.SourcesResponse
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import kotlinx.coroutines.Dispatchers import eu.kanade.tachiyomi.util.parallelCatchingFlatMap
import kotlinx.coroutines.async import eu.kanade.tachiyomi.util.parallelMapNotNull
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl import okhttp3.HttpUrl
@ -41,11 +39,11 @@ abstract class ZoroTheme(
override val supportsLatest = true override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.client
private val json: Json by injectLazy() private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy { val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
@ -115,7 +113,7 @@ abstract class ZoroTheme(
addIfNotBlank("em", params.end_month) addIfNotBlank("em", params.end_month)
addIfNotBlank("ed", params.end_day) addIfNotBlank("ed", params.end_day)
addIfNotBlank("genres", params.genres) addIfNotBlank("genres", params.genres)
}.build().toString() }.build()
return GET(url, docHeaders) return GET(url, docHeaders)
} }
@ -211,7 +209,9 @@ abstract class ZoroTheme(
val name: String, val name: String,
) )
override fun videoListParse(response: Response): List<Video> { override suspend fun getVideoList(episode: SEpisode): List<Video> {
val response = client.newCall(videoListRequest(episode)).execute()
val episodeReferer = response.request.header("referer")!! val episodeReferer = response.request.header("referer")!!
val typeSelection = preferences.typeToggle val typeSelection = preferences.typeToggle
val hosterSelection = preferences.hostToggle val hosterSelection = preferences.hostToggle
@ -236,9 +236,7 @@ abstract class ZoroTheme(
} }
}.flatten() }.flatten()
return embedLinks.parallelCatchingFlatMap(::extractVideo).ifEmpty { return embedLinks.parallelCatchingFlatMap(::extractVideo)
throw Exception("Failed to extract videos.")
}
} }
protected open fun extractVideo(server: VideoData): List<Video> { protected open fun extractVideo(server: VideoData): List<Video> {
@ -253,16 +251,6 @@ abstract class ZoroTheme(
// ============================= Utilities ============================== // ============================= Utilities ==============================
private inline fun <A, B> Iterable<A>.parallelMapNotNull(crossinline f: suspend (A) -> B?): List<B> =
runBlocking {
map { async(Dispatchers.Default) { f(it) } }.awaitAll().filterNotNull()
}
private inline fun <A, B> Iterable<A>.parallelCatchingFlatMap(crossinline f: suspend (A) -> Iterable<B>): List<B> =
runBlocking {
map { async(Dispatchers.Default) { runCatching { f(it) }.getOrElse { emptyList() } } }.awaitAll().flatten()
}
private inline fun <reified T> Response.parseAs(): T { private inline fun <reified T> Response.parseAs(): T {
return use { it.body.string() }.let(json::decodeFromString) return use { it.body.string() }.let(json::decodeFromString)
} }