From ae5c347dafad1d86f240cf04e682ad00bd2eb45b Mon Sep 17 00:00:00 2001 From: Samfun75 <38332931+Samfun75@users.noreply.github.com> Date: Thu, 2 Mar 2023 09:37:54 +0300 Subject: [PATCH] Yomiroll: give option to unlock local region locked subs (#1350) for old anime like one piece --- .../all/kamyroll/AccessTokenInterceptor.kt | 88 ++++++++---- .../animeextension/all/kamyroll/DataModel.kt | 8 +- .../animeextension/all/kamyroll/Yomiroll.kt | 127 +++++++++++++++--- 3 files changed, 174 insertions(+), 49 deletions(-) diff --git a/src/all/kamyroll/src/eu/kanade/tachiyomi/animeextension/all/kamyroll/AccessTokenInterceptor.kt b/src/all/kamyroll/src/eu/kanade/tachiyomi/animeextension/all/kamyroll/AccessTokenInterceptor.kt index 27636ac8c..6d5567c7c 100644 --- a/src/all/kamyroll/src/eu/kanade/tachiyomi/animeextension/all/kamyroll/AccessTokenInterceptor.kt +++ b/src/all/kamyroll/src/eu/kanade/tachiyomi/animeextension/all/kamyroll/AccessTokenInterceptor.kt @@ -18,6 +18,8 @@ import java.net.HttpURLConnection import java.net.InetSocketAddress import java.net.PasswordAuthentication import java.net.Proxy +import java.text.SimpleDateFormat +import java.util.Locale class AccessTokenInterceptor( private val crUrl: String, @@ -39,7 +41,7 @@ class AccessTokenInterceptor( if (accessTokenN != newAccessToken) { return chain.proceed(newRequestWithAccessToken(request, newAccessToken)) } - val refreshedToken = refreshAccessToken() + val refreshedToken = refreshAccessToken(TOKEN_PREF_KEY) // Retry the request return chain.proceed( newRequestWithAccessToken(chain.request(), refreshedToken), @@ -58,21 +60,28 @@ class AccessTokenInterceptor( fun getAccessToken(): AccessToken { return preferences.getString(TOKEN_PREF_KEY, null)?.toAccessToken() - ?: refreshAccessToken() + ?: refreshAccessToken(TOKEN_PREF_KEY) } - private fun refreshAccessToken(): AccessToken { - val client = OkHttpClient() - .newBuilder().build() - val proxy = client.newBuilder() - .proxy( - Proxy( - Proxy.Type.SOCKS, - InetSocketAddress("cr-unblocker.us.to", 1080), - ), - ) - .build() + fun getLocalToken(force: Boolean = false): AccessToken? { + if (!preferences.getBoolean(PREF_FETCH_LOCAL_SUBS, false) && !force) return null + synchronized(this) { + val now = System.currentTimeMillis() + 1800000 // add 30 minutes for safety + val localToken = preferences.getString(LOCAL_TOKEN_PREF_KEY, null)?.toAccessToken() + return if (force || localToken == null || localToken.policyExpire!! < now) { + refreshAccessToken(LOCAL_TOKEN_PREF_KEY, false) + } else { + localToken + } + } + } + fun removeLocalToken() { + preferences.edit().putString(LOCAL_TOKEN_PREF_KEY, null).apply() + } + + private fun refreshAccessToken(PREF_KEY: String, useProxy: Boolean = true): AccessToken { + val client = OkHttpClient().newBuilder().build() Authenticator.setDefault( object : Authenticator() { override fun getPasswordAuthentication(): PasswordAuthentication { @@ -80,21 +89,22 @@ class AccessTokenInterceptor( } }, ) - - // Thanks Stormzy - val refreshTokenResp = client.newCall(GET("https://raw.githubusercontent.com/Samfun75/File-host/main/aniyomi/refreshToken.txt")).execute() - val refreshToken = refreshTokenResp.body.string().replace("[\n\r]".toRegex(), "") - val headers = Headers.headersOf( - "Content-Type", - "application/x-www-form-urlencoded", - "Authorization", - "Basic a3ZvcGlzdXZ6Yy0teG96Y21kMXk6R21JSTExenVPVnRnTjdlSWZrSlpibzVuLTRHTlZ0cU8=", - ) - val postBody = "grant_type=refresh_token&refresh_token=$refreshToken&scope=offline_access".toRequestBody("application/x-www-form-urlencoded".toMediaType()) - val response = proxy.newCall(POST("$crUrl/auth/v1/token", headers, postBody)).execute() + val usedClient = if (useProxy) { + client.newBuilder() + .proxy( + Proxy( + Proxy.Type.SOCKS, + InetSocketAddress("cr-unblocker.us.to", 1080), + ), + ) + .build() + } else { + client + } + val response = usedClient.newCall(getRequest(client)).execute() val parsedJson = json.decodeFromString(response.body.string()) - val policy = proxy.newCall(newRequestWithAccessToken(GET("$crUrl/index/v2"), parsedJson)).execute() + val policy = usedClient.newCall(newRequestWithAccessToken(GET("$crUrl/index/v2"), parsedJson)).execute() val policyJson = json.decodeFromString(policy.body.string()) val allTokens = AccessToken( parsedJson.access_token, @@ -103,11 +113,27 @@ class AccessTokenInterceptor( policyJson.cms.signature, policyJson.cms.key_pair_id, policyJson.cms.bucket, + DateFormatter.parse(policyJson.cms.expires)?.time, ) - preferences.edit().putString(TOKEN_PREF_KEY, allTokens.toJsonString()).apply() + preferences.edit().putString(PREF_KEY, allTokens.toJsonString()).apply() return allTokens } + private fun getRequest(client: OkHttpClient): Request { + val refreshTokenResp = client.newCall( + GET("https://raw.githubusercontent.com/Samfun75/File-host/main/aniyomi/refreshToken.txt"), + ).execute() + val refreshToken = refreshTokenResp.body.string().replace("[\n\r]".toRegex(), "") + val headers = Headers.headersOf( + "Content-Type", "application/x-www-form-urlencoded", + "Authorization", "Basic a3ZvcGlzdXZ6Yy0teG96Y21kMXk6R21JSTExenVPVnRnTjdlSWZrSlpibzVuLTRHTlZ0cU8=" + ) + val postBody = "grant_type=refresh_token&refresh_token=$refreshToken&scope=offline_access".toRequestBody( + "application/x-www-form-urlencoded".toMediaType(), + ) + return POST("$crUrl/auth/v1/token", headers, postBody) + } + private fun AccessToken.toJsonString(): String { return json.encodeToString(this) } @@ -117,6 +143,12 @@ class AccessTokenInterceptor( } companion object { - val TOKEN_PREF_KEY = "access_token_data" + private const val TOKEN_PREF_KEY = "access_token_data" + private const val LOCAL_TOKEN_PREF_KEY = "local_access_token_data_test_adwa" + private const val PREF_FETCH_LOCAL_SUBS = "preferred_local_subs" + + private val DateFormatter by lazy { + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH) + } } } diff --git a/src/all/kamyroll/src/eu/kanade/tachiyomi/animeextension/all/kamyroll/DataModel.kt b/src/all/kamyroll/src/eu/kanade/tachiyomi/animeextension/all/kamyroll/DataModel.kt index d27822fca..79269cc0f 100644 --- a/src/all/kamyroll/src/eu/kanade/tachiyomi/animeextension/all/kamyroll/DataModel.kt +++ b/src/all/kamyroll/src/eu/kanade/tachiyomi/animeextension/all/kamyroll/DataModel.kt @@ -12,6 +12,7 @@ data class AccessToken( val signature: String? = null, val key_pair_id: String? = null, val bucket: String? = null, + val policyExpire: Long? = null, ) @Serializable @@ -24,6 +25,7 @@ data class Policy( val signature: String, val key_pair_id: String, val bucket: String, + val expires: String, ) } @@ -156,9 +158,9 @@ data class EpisodeData( @Serializable data class VideoStreams( - val streams: Stream, - val subtitles: JsonObject, - val audio_locale: String, + val streams: Stream? = null, + val subtitles: JsonObject? = null, + val audio_locale: String? = null, ) { @Serializable data class Stream( diff --git a/src/all/kamyroll/src/eu/kanade/tachiyomi/animeextension/all/kamyroll/Yomiroll.kt b/src/all/kamyroll/src/eu/kanade/tachiyomi/animeextension/all/kamyroll/Yomiroll.kt index 90b25efc4..a77b2e865 100644 --- a/src/all/kamyroll/src/eu/kanade/tachiyomi/animeextension/all/kamyroll/Yomiroll.kt +++ b/src/all/kamyroll/src/eu/kanade/tachiyomi/animeextension/all/kamyroll/Yomiroll.kt @@ -1,9 +1,11 @@ package eu.kanade.tachiyomi.animeextension.all.kamyroll import android.app.Application +import android.content.Context import android.content.SharedPreferences import androidx.preference.ListPreference import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimesPage @@ -64,6 +66,13 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() { private val DateFormatter by lazy { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH) } + + private const val PREF_QLT = "preferred_quality" + private const val PREF_AUD = "preferred_audio" + private const val PREF_SUB = "preferred_sub" + private const val PREF_SUB_TYPE = "preferred_sub_type" + // there is one in AccessTokenInterceptor too for below + private const val PREF_FETCH_LOCAL_SUBS = "preferred_local_subs" } // ============================== Popular =============================== @@ -243,12 +252,13 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() { override fun fetchVideoList(episode: SEpisode): Observable> { val urlJson = json.decodeFromString(episode.url) val dubLocale = preferences.getString("preferred_audio", "en-US")!! - val policyJson = tokenInterceptor.getAccessToken() + val proxyToken = tokenInterceptor.getAccessToken() + val localToken = tokenInterceptor.getLocalToken() val videoList = urlJson.ids.filter { it.second == dubLocale || it.second == "ja-JP" || it.second == "en-US" || it.second == "" }.parallelMap { media -> runCatching { - extractVideo(media, policyJson) + extractVideo(media, proxyToken, localToken) }.getOrNull() }.filterNotNull().flatten() @@ -257,18 +267,31 @@ class Yomiroll : ConfigurableAnimeSource, AnimeHttpSource() { // ============================= Utilities ============================== - private fun extractVideo(media: Pair, policyJson: AccessToken): List