From dcb5c7ef2f2bd12fde0f117abe76de7e490c40c4 Mon Sep 17 00:00:00 2001 From: almightyhak <134626626+almightyhak@users.noreply.github.com> Date: Sun, 4 Aug 2024 00:32:19 +0700 Subject: [PATCH] Aniwave updates (#98) * new encryption keys * updated extVersionCode to 73 * fixed null exception in episodeListRequest * removed old domains * added custom domain preference * added new episode path id resolution * updated/fixed custom domain preference setting *updated build_push.yml --- .github/workflows/build_push.yml | 2 +- src/en/nineanime/build.gradle | 2 +- .../animeextension/en/nineanime/Aniwave.kt | 109 +++++++++++++----- 3 files changed, 85 insertions(+), 28 deletions(-) diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index d984a6c6..abcdb4d7 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -44,7 +44,7 @@ jobs: - name: Bump extensions that uses a modified lib if: steps.modified-libs.outputs.any_changed == 'true' run: | - ./.github/scripts/bump-versions.py ${{ steps.modified-libs.outputs.all_changed_files }} + chmod +x ./.github/scripts/bump-versions.py ${{ steps.modified-libs.outputs.all_changed_files }} - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@a494d935f4b56874c4a5a87d19af7afcf3a163d0 # v2 diff --git a/src/en/nineanime/build.gradle b/src/en/nineanime/build.gradle index e572ab0b..1b97c417 100644 --- a/src/en/nineanime/build.gradle +++ b/src/en/nineanime/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Aniwave' extClass = '.Aniwave' - extVersionCode = 72 + extVersionCode = 73 } 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 f8cdc324..ad57ef24 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,10 @@ 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 import androidx.preference.ListPreference import androidx.preference.MultiSelectListPreference import androidx.preference.PreferenceScreen @@ -38,7 +41,12 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override val id: Long = 98855593379717478 override val baseUrl by lazy { - preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!! + val customDomain = preferences.getString(PREF_CUSTOM_DOMAIN_KEY, null) + if (customDomain.isNullOrBlank()) { + preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!! + } else { + customDomain + } } override val lang = "en" @@ -89,7 +97,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(KEY_ENCRYPT, query) else "" + val vrf = if (query.isNotBlank()) utils.vrfEncrypt(ENCRYPTION_KEY, query) else "" var url = "$baseUrl/filter?keyword=$query" if (filters.genre.isNotBlank()) url += filters.genre @@ -116,30 +124,39 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { // =========================== Anime Details ============================ - override fun animeDetailsParse(document: Document): SAnime = SAnime.create().apply { - title = document.select("h1.title").text() - genre = document.select("div:contains(Genre) > span > a").joinToString { it.text() } - description = document.select("div.synopsis > div.shorting > div.content").text() - author = document.select("div:contains(Studio) > span > a").text() - status = parseStatus(document.select("div:contains(Status) > span").text()) + override fun animeDetailsParse(document: Document): SAnime { + val anime = SAnime.create() + val newDocument = resolveSearchAnime(anime, document) + anime.apply { + title = newDocument.select("h1.title").text() + genre = newDocument.select("div:contains(Genre) > span > a").joinToString { it.text() } + description = newDocument.select("div.synopsis > div.shorting > div.content").text() + author = newDocument.select("div:contains(Studio) > span > a").text() + status = parseStatus(newDocument.select("div:contains(Status) > span").text()) - val altName = "Other name(s): " - document.select("h1.title").attr("data-jp").let { - if (it.isNotBlank()) { - description = when { - description.isNullOrBlank() -> altName + it - else -> description + "\n\n$altName" + it + val altName = "Other name(s): " + newDocument.select("h1.title").attr("data-jp").let { + if (it.isNotBlank()) { + description = when { + description.isNullOrBlank() -> altName + it + else -> description + "\n\n$altName" + it + } } } } + return anime } // ============================== Episodes ============================== override fun episodeListRequest(anime: SAnime): Request { - val id = client.newCall(GET(baseUrl + anime.url)).execute().asJsoup() - .selectFirst("div[data-id]")!!.attr("data-id") - val vrf = utils.vrfEncrypt(KEY_ENCRYPT, id) + 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 listHeaders = headers.newBuilder().apply { add("Accept", "application/json, text/javascript, */*; q=0.01") @@ -195,7 +212,7 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun videoListRequest(episode: SEpisode): Request { val ids = episode.url.substringBefore("&") - val vrf = utils.vrfEncrypt(KEY_ENCRYPT, ids) + val vrf = utils.vrfEncrypt(ENCRYPTION_KEY, ids) val url = "/ajax/server/list/$ids?vrf=$vrf" val epurl = episode.url.substringAfter("epurl=") @@ -248,7 +265,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(KEY_ENCRYPT, server.serverId) + val vrf = utils.vrfEncrypt(ENCRYPTION_KEY, server.serverId) val listHeaders = headers.newBuilder().apply { add("Accept", "application/json, text/javascript, */*; q=0.01") @@ -263,7 +280,7 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { return runCatching { val parsed = response.parseAs<ServerResponse>() - val embedLink = utils.vrfDecrypt(KEY_DECRYPT, parsed.result.url) + val embedLink = utils.vrfDecrypt(DECRYPTION_KEY, parsed.result.url) when (server.serverName) { "vidstream", "megaf" -> { vidsrcExtractor.videosFromUrl(embedLink, server.serverName, server.type) @@ -308,6 +325,16 @@ 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)") + anime.url = foundAnimePath // probably doesn't work as intended + return client.newCall(GET(baseUrl + foundAnimePath)).execute().asJsoup() + } + return document + } + private fun getHosters(): Set<String> { val hosterSelection = preferences.getStringSet(PREF_HOSTER_KEY, PREF_HOSTER_DEFAULT)!! var invalidRecord = false @@ -338,6 +365,8 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { private const val PREF_DOMAIN_KEY = "preferred_domain" private const val PREF_DOMAIN_DEFAULT = "https://aniwave.to" + private const val PREF_CUSTOM_DOMAIN_KEY = "custom_domain" + private const val PREF_QUALITY_KEY = "preferred_quality" private const val PREF_QUALITY_DEFAULT = "1080" @@ -371,22 +400,25 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { private val TYPES = arrayOf("Sub", "Softsub", "Dub") private val PREF_TYPES_TOGGLE_DEFAULT = TYPES.toSet() - // https://rowdy-avocado.github.io/multi-keys/ - private const val KEY_DECRYPT = "ctpAbOz5u7S6OMkx" - private const val KEY_ENCRYPT = "p01EDKu734HJP1Tm" + private const val DECRYPTION_KEY = "ctpAbOz5u7S6OMkx" + private const val ENCRYPTION_KEY = "T78s2WjTc7hSIZZR" } // ============================== Settings ============================== override fun setupPreferenceScreen(screen: PreferenceScreen) { // validate hosters preferences and if invalid reset - getHosters() + try { + getHosters() + } catch (e: Exception) { + Log.w(name, e.toString()) + } ListPreference(screen.context).apply { key = PREF_DOMAIN_KEY title = "Preferred domain" - entries = arrayOf("aniwave.to", "aniwave.li", "aniwave.ws", "aniwave.vc") - entryValues = arrayOf("https://aniwave.to", "https://aniwave.li", "https://aniwave.ws", "https://aniwave.vc") + entries = arrayOf("aniwave.to", "aniwavetv.to (unofficial)") + entryValues = arrayOf("https://aniwave.to", "https://aniwavetv.to") setDefaultValue(PREF_DOMAIN_DEFAULT) summary = "%s" @@ -482,5 +514,30 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { preferences.edit().putStringSet(key, newValue as Set<String>).commit() } }.also(screen::addPreference) + + EditTextPreference(screen.context).apply { + key = PREF_CUSTOM_DOMAIN_KEY + title = "Custom domain" + setDefaultValue(null) + val currentValue = preferences.getString(PREF_CUSTOM_DOMAIN_KEY, null) + summary = if (currentValue.isNullOrBlank()) { + "Custom domain of your choosing" + } else { + "Domain: \"$currentValue\". \nLeave blank to disable. Overrides any domain preferences!" + } + + setOnPreferenceChangeListener { _, newValue -> + val newDomain = newValue as String + if (newDomain.isBlank() || URLUtil.isValidUrl(newDomain)) { + summary = "Restart to apply changes" + Toast.makeText(screen.context, "Restart Aniyomi to apply changes", Toast.LENGTH_LONG).show() + preferences.edit().putString(key, newDomain).apply() + true + } else { + Toast.makeText(screen.context, "Invalid url. Url example: https://aniwave.to", Toast.LENGTH_LONG).show() + false + } + } + }.also(screen::addPreference) } }