diff --git a/src/en/nineanime/build.gradle b/src/en/nineanime/build.gradle index dbc9ffc887..7ec7021237 100644 --- a/src/en/nineanime/build.gradle +++ b/src/en/nineanime/build.gradle @@ -5,8 +5,12 @@ ext { extName = '9anime' pkgNameSuffix = 'en.nineanime' extClass = '.NineAnime' - extVersionCode = 20 + extVersionCode = 21 libVersion = '13' } +dependencies { + compileOnly libs.bundles.coroutines +} + apply from: "$rootDir/common.gradle" diff --git a/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/FindKeys.kt b/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/FindKeys.kt new file mode 100644 index 0000000000..0d0e62d3b0 --- /dev/null +++ b/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/FindKeys.kt @@ -0,0 +1,69 @@ +package eu.kanade.tachiyomi.animeextension.en.nineanime + +import app.cash.quickjs.QuickJs +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonPrimitive + +const val fallbackcipherKey = "xtbmecCv4faAOSLV" +const val fallbackdecipherKey = "hlPeNwkncH0fq9so" + +fun getKeys(allJsScript: String, json: Json): Pair { + val quickJs = QuickJs.create() + val keys = try { + val scriptResult = quickJs.evaluate(finderScript(allJsScript)).toString() + val returnObject = json.decodeFromString(scriptResult) + val cipherKey = returnObject["cipher"]!!.jsonPrimitive.content + val decipherKey = returnObject["decipher"]!!.jsonPrimitive.content + Pair(cipherKey, decipherKey) + } catch (t: Throwable) { + Pair(fallbackcipherKey, fallbackdecipherKey) + } + quickJs.close() + return keys +} + +private fun finderScript(script: String) = """ +let secret0 = ""; +let secret1 = ""; +const script = String.raw`$script`; +const prefix = `$prefix`; +var newscript = prefix + script; +const fn_regex = /or(?=:function\(.*?\) {var \w=.*?return .;})/gm +let fn_name = script.match(fn_regex); +const regex = RegExp(String.raw`(?<=this\["${'$'}{fn_name}"]\().+?(?=,)`, "gm"); +let res = [...script.matchAll(regex)]; +for (var index of [1,0]) { + let match = res[index][0]; + let varnames = match.split("+"); + for (var varnameindex = 0; varnameindex < varnames.length; varnameindex++) { + let varname = varnames[varnameindex]; + let search = `${'$'}{varname}=`; + // variables are declared on line 2 + let line2index = script.indexOf("\n") + prefix.length; + let line2 = newscript.substring(line2index + 1); + let i = line2index + line2.indexOf(search) + search.length; + let after = newscript.substring(i + 1); + let j = after.indexOf(";") + i + 1; + let before = newscript.substring(0, j + 1); + let after_semicolon = newscript.substring(j + 1); + newscript = before + `secret${'$'}{index}=${'$'}{res[index][0]};` + after_semicolon; + } +}; +try { eval(newscript); } catch(e) {} +let return_object = {cipher: secret0, decipher: secret1}; +JSON.stringify(return_object); +""" + +private const val prefix = """const document = { documentElement: {} }; +const jQuery = function () { return { off: function () { return { on: function(e) { return { on: function() { return { on: function() { return { on: function() { return { on: function() { return { }; } }; } }; } }; } }; } }; }, ready: function (e) {} } }; +jQuery.fn = { dropdown: {}, extend: {} }; +const window = { fn: { extend: {} } }; +const navigator = {}; +const setTimeout = {}; +const clearTimeout = {}; +const setInterval = {}; +const clearInterval = {}; + +""" diff --git a/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/NineAnime.kt b/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/NineAnime.kt index ad3acd5e1b..cd588bc108 100644 --- a/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/NineAnime.kt +++ b/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/NineAnime.kt @@ -12,13 +12,14 @@ import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.util.asJsoup -import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.coroutines.runBlocking import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive +import okhttp3.CacheControl import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient @@ -31,7 +32,6 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy -@ExperimentalSerializationApi class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override val name = "9anime" @@ -372,9 +372,23 @@ class NineAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() { private fun encode(input: String): String = java.net.URLEncoder.encode(input, "utf-8").replace("+", "%20") private fun decode(input: String): String = java.net.URLDecoder.decode(input, "utf-8") + + private val cipherKey: String + private val decipherKey: String + + init { + val allJsScript = runBlocking { + client.newCall( + GET( + url = "https://s2.bunnycdn.ru/assets/_9anime/min/all.js", + cache = CacheControl.FORCE_NETWORK + ) + ).execute().body!!.string() + } + val keys = getKeys(allJsScript, json) + cipherKey = keys.first + decipherKey = keys.second + } } private const val nineAnimeKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -private const val cipherKey = "Ml6DEBjOkhWXxXg4" -private const val decipherKey = "hlPeNwkncH0fq9so" -// will implement the new algorithm soon (TM)