Aniwave updated keys and custom keys option (#81)

* Added custom keys functionality with preferences

* changed encryption and decryption utils

* updated extVersionCode number
This commit is contained in:
Josef František Straka 2024-07-29 04:15:29 +02:00 committed by GitHub
parent 2611f03893
commit 2110f77008
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 65 additions and 46 deletions

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Aniwave' extName = 'Aniwave'
extClass = '.Aniwave' extClass = '.Aniwave'
extVersionCode = 69 extVersionCode = 70
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.animeextension.en.nineanime
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.widget.Toast import android.widget.Toast
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference import androidx.preference.MultiSelectListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
@ -89,7 +90,7 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filters = AniwaveFilters.getSearchParameters(filters) val filters = AniwaveFilters.getSearchParameters(filters)
val vrf = if (query.isNotBlank()) utils.vrfEncrypt(query) else "" val vrf = if (query.isNotBlank()) utils.vrfEncrypt(getEncryptionKey(), query) else ""
var url = "$baseUrl/filter?keyword=$query" var url = "$baseUrl/filter?keyword=$query"
if (filters.genre.isNotBlank()) url += filters.genre if (filters.genre.isNotBlank()) url += filters.genre
@ -101,7 +102,7 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
if (filters.language.isNotBlank()) url += filters.language if (filters.language.isNotBlank()) url += filters.language
if (filters.rating.isNotBlank()) url += filters.rating if (filters.rating.isNotBlank()) url += filters.rating
return GET("$url&sort=${filters.sort}&page=$page&$vrf", refererHeaders) return GET("$url&sort=${filters.sort}&page=$page&vrf=$vrf", refererHeaders)
} }
override fun searchAnimeSelector(): String = popularAnimeSelector() override fun searchAnimeSelector(): String = popularAnimeSelector()
@ -139,7 +140,7 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun episodeListRequest(anime: SAnime): Request { override fun episodeListRequest(anime: SAnime): Request {
val id = client.newCall(GET(baseUrl + anime.url)).execute().asJsoup() val id = client.newCall(GET(baseUrl + anime.url)).execute().asJsoup()
.selectFirst("div[data-id]")!!.attr("data-id") .selectFirst("div[data-id]")!!.attr("data-id")
val vrf = utils.vrfEncrypt(id) val vrf = utils.vrfEncrypt(getEncryptionKey(), id)
val listHeaders = headers.newBuilder().apply { val listHeaders = headers.newBuilder().apply {
add("Accept", "application/json, text/javascript, */*; q=0.01") add("Accept", "application/json, text/javascript, */*; q=0.01")
@ -147,7 +148,7 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
add("X-Requested-With", "XMLHttpRequest") add("X-Requested-With", "XMLHttpRequest")
}.build() }.build()
return GET("$baseUrl/ajax/episode/list/$id?$vrf#${anime.url}", listHeaders) return GET("$baseUrl/ajax/episode/list/$id?vrf=$vrf#${anime.url}", listHeaders)
} }
override fun episodeListSelector() = "div.episodes ul > li > a" override fun episodeListSelector() = "div.episodes ul > li > a"
@ -195,8 +196,8 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun videoListRequest(episode: SEpisode): Request { override fun videoListRequest(episode: SEpisode): Request {
val ids = episode.url.substringBefore("&") val ids = episode.url.substringBefore("&")
val vrf = utils.vrfEncrypt(ids) val vrf = utils.vrfEncrypt(getEncryptionKey(), ids)
val url = "/ajax/server/list/$ids?$vrf" val url = "/ajax/server/list/$ids?vrf=$vrf"
val epurl = episode.url.substringAfter("epurl=") val epurl = episode.url.substringAfter("epurl=")
val listHeaders = headers.newBuilder().apply { val listHeaders = headers.newBuilder().apply {
@ -248,7 +249,7 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) } private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) }
private fun extractVideo(server: VideoData, epUrl: String): List<Video> { private fun extractVideo(server: VideoData, epUrl: String): List<Video> {
val vrf = utils.vrfEncrypt(server.serverId) val vrf = utils.vrfEncrypt(getEncryptionKey(), server.serverId)
val listHeaders = headers.newBuilder().apply { val listHeaders = headers.newBuilder().apply {
add("Accept", "application/json, text/javascript, */*; q=0.01") add("Accept", "application/json, text/javascript, */*; q=0.01")
@ -257,13 +258,13 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}.build() }.build()
val response = client.newCall( val response = client.newCall(
GET("$baseUrl/ajax/server/${server.serverId}?$vrf", listHeaders), GET("$baseUrl/ajax/server/${server.serverId}?vrf=$vrf", listHeaders),
).execute() ).execute()
if (response.code != 200) return emptyList() if (response.code != 200) return emptyList()
return runCatching { return runCatching {
val parsed = response.parseAs<ServerResponse>() val parsed = response.parseAs<ServerResponse>()
val embedLink = utils.vrfDecrypt(parsed.result.url) val embedLink = utils.vrfDecrypt(getDecryptionKey(), parsed.result.url)
when (server.serverName) { when (server.serverName) {
"vidplay", "mycloud" -> { "vidplay", "mycloud" -> {
val hosterName = when (server.serverName) { val hosterName = when (server.serverName) {
@ -312,6 +313,22 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
} }
private fun getDecryptionKey(): String {
var prefKey = preferences.getString(PREF_VERIFY_KEY_DECRYPT_KEY, null)
if (prefKey.isNullOrBlank()) {
prefKey = PREF_VERIFY_KEY_DECRYPT_VALUE
}
return prefKey
}
private fun getEncryptionKey(): String {
var prefKey = preferences.getString(PREF_VERIFY_KEY_ENCRYPT_KEY, null)
if (prefKey.isNullOrBlank()) {
prefKey = PREF_VERIFY_KEY_ENCRYPT_VALUE
}
return prefKey
}
companion object { companion object {
private val SOFTSUB_REGEX by lazy { Regex("""\bsoftsub\b""", RegexOption.IGNORE_CASE) } private val SOFTSUB_REGEX by lazy { Regex("""\bsoftsub\b""", RegexOption.IGNORE_CASE) }
private val RELEASE_REGEX by lazy { Regex("""Release: (\d+\/\d+\/\d+ \d+:\d+)""") } private val RELEASE_REGEX by lazy { Regex("""Release: (\d+\/\d+\/\d+ \d+:\d+)""") }
@ -355,6 +372,13 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
private const val PREF_TYPE_TOGGLE_KEY = "type_selection" private const val PREF_TYPE_TOGGLE_KEY = "type_selection"
private val TYPES = arrayOf("Sub", "Softsub", "Dub") private val TYPES = arrayOf("Sub", "Softsub", "Dub")
private val PREF_TYPES_TOGGLE_DEFAULT = TYPES.toSet() private val PREF_TYPES_TOGGLE_DEFAULT = TYPES.toSet()
// https://rowdy-avocado.github.io/multi-keys/
private const val PREF_VERIFY_KEY_DECRYPT_KEY = "verify_key_decrypt"
private const val PREF_VERIFY_KEY_DECRYPT_VALUE = "ctpAbOz5u7S6OMkx"
private const val PREF_VERIFY_KEY_ENCRYPT_KEY = "verify_key_encrypt"
private const val PREF_VERIFY_KEY_ENCRYPT_VALUE = "p01EDKu734HJP1Tm"
} }
// ============================== Settings ============================== // ============================== Settings ==============================
@ -460,5 +484,29 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
preferences.edit().putStringSet(key, newValue as Set<String>).commit() preferences.edit().putStringSet(key, newValue as Set<String>).commit()
} }
}.also(screen::addPreference) }.also(screen::addPreference)
EditTextPreference(screen.context).apply {
key = PREF_VERIFY_KEY_DECRYPT_KEY
title = "Custom decryption key"
setDefaultValue("")
setOnPreferenceChangeListener { _, newValue ->
@Suppress("UNCHECKED_CAST")
val newKey = newValue as String
preferences.edit().putString(key, newKey).commit()
}
}.also(screen::addPreference)
EditTextPreference(screen.context).apply {
key = PREF_VERIFY_KEY_ENCRYPT_KEY
title = "Custom encryption key"
setDefaultValue("")
setOnPreferenceChangeListener { _, newValue ->
@Suppress("UNCHECKED_CAST")
val newKey = newValue as String
preferences.edit().putString(key, newKey).commit()
}
}.also(screen::addPreference)
} }
} }

View file

@ -7,51 +7,22 @@ import javax.crypto.spec.SecretKeySpec
class AniwaveUtils { class AniwaveUtils {
fun vrfEncrypt(input: String): String { fun vrfEncrypt(key: String, input: String): String {
val rc4Key = SecretKeySpec("tGn6kIpVXBEUmqjD".toByteArray(), "RC4") val rc4Key = SecretKeySpec(key.toByteArray(), "RC4")
val cipher = Cipher.getInstance("RC4") val cipher = Cipher.getInstance("RC4")
cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters) cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters)
var vrf = cipher.doFinal(input.toByteArray()) var vrf = cipher.doFinal(input.toByteArray())
vrf = Base64.encode(vrf, Base64.URL_SAFE or Base64.NO_WRAP) vrf = Base64.encode(vrf, Base64.URL_SAFE or Base64.NO_WRAP)
vrf = Base64.encode(vrf, Base64.DEFAULT or Base64.NO_WRAP) var vrfString = vrf.toString(Charsets.UTF_8)
vrf = vrfShift(vrf) return java.net.URLEncoder.encode(vrfString, "utf-8")
vrf = vrf.reversed().toByteArray()
vrf = Base64.encode(vrf, Base64.URL_SAFE or Base64.NO_WRAP)
// vrf = rot13(vrf)
val stringVrf = vrf.toString(Charsets.UTF_8)
return "vrf=${java.net.URLEncoder.encode(stringVrf, "utf-8")}"
} }
fun vrfDecrypt(input: String): String { fun vrfDecrypt(key: String, input: String): String {
var vrf = input.toByteArray() var vrf = Base64.decode(input.toByteArray(), Base64.URL_SAFE)
vrf = Base64.decode(vrf, Base64.URL_SAFE) val rc4Key = SecretKeySpec(key.toByteArray(), "RC4")
val rc4Key = SecretKeySpec("LUyDrL4qIxtIxOGs".toByteArray(), "RC4")
val cipher = Cipher.getInstance("RC4") val cipher = Cipher.getInstance("RC4")
cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters) cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters)
vrf = cipher.doFinal(vrf) vrf = cipher.doFinal(vrf)
return URLDecoder.decode(vrf.toString(Charsets.UTF_8), "utf-8") return URLDecoder.decode(vrf.toString(Charsets.UTF_8), "utf-8")
} }
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
}
} }