diff --git a/src/en/nineanime/build.gradle b/src/en/nineanime/build.gradle index 88b81902..728eee5b 100644 --- a/src/en/nineanime/build.gradle +++ b/src/en/nineanime/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Aniwave' extClass = '.Aniwave' - extVersionCode = 74 + extVersionCode = 75 } apply from: "$rootDir/common.gradle" diff --git a/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/Aniwave.kt b/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/Aniwave.kt index b40fc5bb..c9d85d87 100644 --- a/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/Aniwave.kt +++ b/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/Aniwave.kt @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.animeextension.en.nineanime import android.app.Application import android.content.SharedPreferences -import android.util.Log import android.webkit.URLUtil import android.widget.Toast import androidx.preference.EditTextPreference @@ -97,7 +96,7 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { val filters = AniwaveFilters.getSearchParameters(filters) - val vrf = if (query.isNotBlank()) utils.vrfEncrypt(ENCRYPTION_KEY, query) else "" + val vrf = if (query.isNotBlank()) utils.vrfEncrypt(query) else "" var url = "$baseUrl/filter?keyword=$query" if (filters.genre.isNotBlank()) url += filters.genre @@ -150,13 +149,12 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { // ============================== Episodes ============================== override fun episodeListRequest(anime: SAnime): Request { - Log.i(name, "episodeListRequest") val response = client.newCall(GET(baseUrl + anime.url)).execute() var document = response.asJsoup() document = resolveSearchAnime(anime, document) val id = document.selectFirst("div[data-id]")?.attr("data-id") ?: throw Exception("ID not found") - val vrf = utils.vrfEncrypt(ENCRYPTION_KEY, id) + val vrf = utils.vrfEncrypt(id) val listHeaders = headers.newBuilder().apply { add("Accept", "application/json, text/javascript, */*; q=0.01") @@ -212,7 +210,7 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun videoListRequest(episode: SEpisode): Request { val ids = episode.url.substringBefore("&") - val vrf = utils.vrfEncrypt(ENCRYPTION_KEY, ids) + val vrf = utils.vrfEncrypt(ids) val url = "/ajax/server/list/$ids?vrf=$vrf" val epurl = episode.url.substringAfter("epurl=") @@ -265,7 +263,7 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) } private fun extractVideo(server: VideoData, epUrl: String): List<Video> { - val vrf = utils.vrfEncrypt(ENCRYPTION_KEY, server.serverId) + val vrf = utils.vrfEncrypt(server.serverId) val listHeaders = headers.newBuilder().apply { add("Accept", "application/json, text/javascript, */*; q=0.01") @@ -280,7 +278,7 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { return runCatching { val parsed = response.parseAs<ServerResponse>() - val embedLink = utils.vrfDecrypt(DECRYPTION_KEY, parsed.result.url) + val embedLink = utils.vrfDecrypt(parsed.result.url) when (server.serverName) { "vidstream" -> vidsrcExtractor.videosFromUrl(embedLink, "Vidstream", server.type) "megaf" -> vidsrcExtractor.videosFromUrl(embedLink, "MegaF", server.type) @@ -327,7 +325,8 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { private fun resolveSearchAnime(anime: SAnime, document: Document): Document { if (document.location().startsWith("$baseUrl/filter?keyword=")) { // redirected to search val element = document.selectFirst(searchAnimeSelector()) - val foundAnimePath = element?.selectFirst("a[href]")?.attr("href") ?: throw Exception("Search element not found (resolveSearch)") + val foundAnimePath = element?.selectFirst("a[href]")?.attr("href") + ?: throw Exception("Search element not found (resolveSearch)") anime.url = foundAnimePath // probably doesn't work as intended return client.newCall(GET(baseUrl + foundAnimePath)).execute().asJsoup() } @@ -398,9 +397,6 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { private const val PREF_TYPE_TOGGLE_KEY = "type_selection" private val TYPES = arrayOf("Sub", "Softsub", "Dub") private val PREF_TYPES_TOGGLE_DEFAULT = TYPES.toSet() - - private const val DECRYPTION_KEY = "ctpAbOz5u7S6OMkx" - private const val ENCRYPTION_KEY = "T78s2WjTc7hSIZZR" } // ============================== Settings ============================== @@ -410,7 +406,7 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { try { getHosters() } catch (e: Exception) { - Log.w(name, e.toString()) + Toast.makeText(screen.context, e.toString(), Toast.LENGTH_LONG).show() } ListPreference(screen.context).apply { diff --git a/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/AniwaveUtils.kt b/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/AniwaveUtils.kt index 9c11922a..39cbf7ca 100644 --- a/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/AniwaveUtils.kt +++ b/src/en/nineanime/src/eu/kanade/tachiyomi/animeextension/en/nineanime/AniwaveUtils.kt @@ -7,22 +7,111 @@ import javax.crypto.spec.SecretKeySpec class AniwaveUtils { - fun vrfEncrypt(key: String, input: String): String { + fun vrfEncrypt(input: String): String { + var vrf = input + ORDER.sortedBy { + it.first + }.forEach { item -> + when (item.second) { + "exchange" -> vrf = exchange(vrf, item.third) + "rc4" -> vrf = rc4Encrypt(item.third.get(0), vrf) + "reverse" -> vrf = vrf.reversed() + "base64" -> vrf = Base64.encode(vrf.toByteArray(), Base64.URL_SAFE or Base64.NO_WRAP).toString(Charsets.UTF_8) + else -> {} + } + } + + return java.net.URLEncoder.encode(vrf, "utf-8") + } + + fun vrfDecrypt(input: String): String { + var vrf = input + ORDER.sortedByDescending { + it.first + }.forEach { item -> + when (item.second) { + "exchange" -> vrf = exchange(vrf, item.third.reversed()) + "rc4" -> vrf = rc4Decrypt(item.third.get(0), vrf) + "reverse" -> vrf = vrf.reversed() + "base64" -> vrf = Base64.decode(vrf, Base64.URL_SAFE).toString(Charsets.UTF_8) + else -> {} + } + } + + return URLDecoder.decode(vrf, "utf-8") + } + + private fun rc4Encrypt(key: String, input: String): String { val rc4Key = SecretKeySpec(key.toByteArray(), "RC4") val cipher = Cipher.getInstance("RC4") cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters) - var vrf = cipher.doFinal(input.toByteArray()) - vrf = Base64.encode(vrf, Base64.URL_SAFE or Base64.NO_WRAP) - var vrfString = vrf.toString(Charsets.UTF_8) - return java.net.URLEncoder.encode(vrfString, "utf-8") + + var output = cipher.doFinal(input.toByteArray()) + output = Base64.encode(output, Base64.URL_SAFE or Base64.NO_WRAP) + return output.toString(Charsets.UTF_8) } - fun vrfDecrypt(key: String, input: String): String { - var vrf = Base64.decode(input.toByteArray(), Base64.URL_SAFE) + private fun rc4Decrypt(key: String, input: String): String { + var vrf = input.toByteArray() + vrf = Base64.decode(vrf, Base64.URL_SAFE) + val rc4Key = SecretKeySpec(key.toByteArray(), "RC4") val cipher = Cipher.getInstance("RC4") cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters) vrf = cipher.doFinal(vrf) - return URLDecoder.decode(vrf.toString(Charsets.UTF_8), "utf-8") + return vrf.toString(Charsets.UTF_8) + } + + private fun exchange(input: String, keys: List<String>): String { + val key1 = keys.get(0) + val key2 = keys.get(1) + return input.map { i -> + val index = key1.indexOf(i) + if (index != -1) { + key2[index] + } else { + i + } + }.joinToString("") + } + + private fun rot13(vrf: ByteArray): ByteArray { + for (i in vrf.indices) { + val byte = vrf[i] + if (byte in 'A'.code..'Z'.code) { + vrf[i] = ((byte - 'A'.code + 13) % 26 + 'A'.code).toByte() + } else if (byte in 'a'.code..'z'.code) { + vrf[i] = ((byte - 'a'.code + 13) % 26 + 'a'.code).toByte() + } + } + return vrf + } + + private fun vrfShift(vrf: ByteArray): ByteArray { + for (i in vrf.indices) { + val shift = arrayOf(-2, -4, -5, 6, 2, -3, 3, 6)[i % 8] + vrf[i] = vrf[i].plus(shift).toByte() + } + return vrf + } + + companion object { + private val EXCHANGE_KEY_1 = listOf("AP6GeR8H0lwUz1", "UAz8Gwl10P6ReH") + private val KEY_1 = "ItFKjuWokn4ZpB" + private val KEY_2 = "fOyt97QWFB3" + private val EXCHANGE_KEY_2 = listOf("1majSlPQd2M5", "da1l2jSmP5QM") + private val EXCHANGE_KEY_3 = listOf("CPYvHj09Au3", "0jHA9CPYu3v") + private val KEY_3 = "736y1uTJpBLUX" + + private val ORDER = listOf( + Triple(1, "exchange", EXCHANGE_KEY_1), + Triple(2, "rc4", listOf(KEY_1)), + Triple(3, "rc4", listOf(KEY_2)), + Triple(4, "exchange", EXCHANGE_KEY_2), + Triple(5, "exchange", EXCHANGE_KEY_3), + Triple(5, "reverse", emptyList()), + Triple(6, "rc4", listOf(KEY_3)), + Triple(7, "base64", emptyList()), + ) } }