feat(lib/megacloud): Save key to preferences (#2758)
This commit is contained in:
parent
f87cc5ce9d
commit
8dc70c0d0c
@ -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)
|
||||||
|
@ -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) {
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
dependencies {
|
|
||||||
implementation(project(":lib-megacloud-extractor"))
|
|
||||||
implementation(project(':lib-streamtape-extractor'))
|
|
||||||
}
|
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user