mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
fix: Refactor MAL code to not spam refresh token when it fails
This commit is contained in:
parent
27e7708803
commit
c926467c96
4 changed files with 67 additions and 54 deletions
|
@ -12,9 +12,15 @@ class TrackPreferences(
|
|||
|
||||
fun trackPassword(sync: TrackService) = preferenceStore.getString(trackPassword(sync.id), "")
|
||||
|
||||
fun trackAuthExpired(sync: TrackService) = preferenceStore.getBoolean(
|
||||
Preference.privateKey("pref_tracker_auth_expired_${sync.id}"),
|
||||
false,
|
||||
)
|
||||
|
||||
fun setCredentials(sync: TrackService, username: String, password: String) {
|
||||
trackUsername(sync).set(username)
|
||||
trackPassword(sync).set(password)
|
||||
trackAuthExpired(sync).set(false)
|
||||
}
|
||||
|
||||
fun trackToken(sync: TrackService) = preferenceStore.getString(trackToken(sync.id), "")
|
||||
|
|
|
@ -8,7 +8,6 @@ import eu.kanade.tachiyomi.data.database.models.Track
|
|||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import eu.kanade.tachiyomi.data.track.updateNewTrackInfo
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import timber.log.Timber
|
||||
|
@ -17,7 +16,7 @@ import uy.kohesive.injekt.injectLazy
|
|||
class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
private val interceptor by lazy { MyAnimeListInterceptor(this, getPassword()) }
|
||||
private val interceptor by lazy { MyAnimeListInterceptor(this) }
|
||||
private val api by lazy { MyAnimeListApi(client, interceptor) }
|
||||
|
||||
@StringRes
|
||||
|
@ -142,6 +141,14 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
|
|||
interceptor.setAuth(null)
|
||||
}
|
||||
|
||||
fun getIfAuthExpired(): Boolean {
|
||||
return trackPreferences.trackAuthExpired(this).get()
|
||||
}
|
||||
|
||||
fun setAuthExpired() {
|
||||
trackPreferences.trackAuthExpired(this).set(true)
|
||||
}
|
||||
|
||||
fun saveOAuth(oAuth: OAuth?) {
|
||||
trackPreferences.trackToken(this).set(json.encodeToString(oAuth))
|
||||
}
|
||||
|
@ -153,6 +160,7 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
|
|||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val READING = 1
|
||||
const val COMPLETED = 2
|
||||
|
|
|
@ -41,13 +41,13 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||
suspend fun getAccessToken(authCode: String): OAuth {
|
||||
return withIOContext {
|
||||
val formBody: RequestBody = FormBody.Builder()
|
||||
.add("client_id", clientId)
|
||||
.add("client_id", CLIENT_ID)
|
||||
.add("code", authCode)
|
||||
.add("code_verifier", codeVerifier)
|
||||
.add("grant_type", "authorization_code")
|
||||
.build()
|
||||
with(json) {
|
||||
client.newCall(POST("$baseOAuthUrl/token", body = formBody))
|
||||
client.newCall(POST("$BASE_OAUTH_URL/token", body = formBody))
|
||||
.awaitSuccess()
|
||||
.parseAs()
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||
suspend fun getCurrentUser(): String {
|
||||
return withIOContext {
|
||||
val request = Request.Builder()
|
||||
.url("$baseApiUrl/users/@me")
|
||||
.url("$BASE_API_URL/users/@me")
|
||||
.get()
|
||||
.build()
|
||||
with(json) {
|
||||
|
@ -71,7 +71,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||
|
||||
suspend fun search(query: String): List<TrackSearch> {
|
||||
return withIOContext {
|
||||
val url = "$baseApiUrl/manga".toUri().buildUpon()
|
||||
val url = "$BASE_API_URL/manga".toUri().buildUpon()
|
||||
// MAL API throws a 400 when the query is over 64 characters...
|
||||
.appendQueryParameter("q", query.take(64))
|
||||
.appendQueryParameter("nsfw", "true")
|
||||
|
@ -96,7 +96,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||
|
||||
suspend fun getMangaDetails(id: Int): TrackSearch {
|
||||
return withIOContext {
|
||||
val url = "$baseApiUrl/manga".toUri().buildUpon()
|
||||
val url = "$BASE_API_URL/manga".toUri().buildUpon()
|
||||
.appendPath(id.toString())
|
||||
.appendQueryParameter("fields", "id,title,synopsis,num_chapters,main_picture,status,media_type,start_date")
|
||||
.build()
|
||||
|
@ -156,7 +156,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||
|
||||
suspend fun findListItem(track: Track): Track? {
|
||||
return withIOContext {
|
||||
val uri = "$baseApiUrl/manga".toUri().buildUpon()
|
||||
val uri = "$BASE_API_URL/manga".toUri().buildUpon()
|
||||
.appendPath(track.media_id.toString())
|
||||
.appendQueryParameter("fields", "num_chapters,my_list_status{start_date,finish_date}")
|
||||
.build()
|
||||
|
@ -194,7 +194,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||
|
||||
// Check next page if there's more
|
||||
if (!obj["paging"]!!.jsonObject["next"]?.jsonPrimitive?.contentOrNull.isNullOrBlank()) {
|
||||
matches + findListItems(query, offset + listPaginationAmount)
|
||||
matches + findListItems(query, offset + LIST_PAGINATION_AMOUNT)
|
||||
} else {
|
||||
matches
|
||||
}
|
||||
|
@ -203,9 +203,9 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||
|
||||
private suspend fun getListPage(offset: Int): JsonObject {
|
||||
return withIOContext {
|
||||
val urlBuilder = "$baseApiUrl/users/@me/mangalist".toUri().buildUpon()
|
||||
val urlBuilder = "$BASE_API_URL/users/@me/mangalist".toUri().buildUpon()
|
||||
.appendQueryParameter("fields", "list_status{start_date,finish_date}")
|
||||
.appendQueryParameter("limit", listPaginationAmount.toString())
|
||||
.appendQueryParameter("limit", LIST_PAGINATION_AMOUNT.toString())
|
||||
if (offset > 0) {
|
||||
urlBuilder.appendQueryParameter("offset", offset.toString())
|
||||
}
|
||||
|
@ -277,30 +277,29 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||
}
|
||||
|
||||
companion object {
|
||||
// Registered under jay's MAL account
|
||||
private const val clientId = "9e6656c53d1910999cc3c537e0e6256a"
|
||||
private const val CLIENT_ID = "9e6656c53d1910999cc3c537e0e6256a"
|
||||
|
||||
private const val baseOAuthUrl = "https://myanimelist.net/v1/oauth2"
|
||||
private const val baseApiUrl = "https://api.myanimelist.net/v2"
|
||||
private const val BASE_OAUTH_URL = "https://myanimelist.net/v1/oauth2"
|
||||
private const val BASE_API_URL = "https://api.myanimelist.net/v2"
|
||||
|
||||
private const val listPaginationAmount = 250
|
||||
private const val LIST_PAGINATION_AMOUNT = 250
|
||||
|
||||
private var codeVerifier: String = ""
|
||||
|
||||
fun authUrl(): Uri = "$baseOAuthUrl/authorize".toUri().buildUpon()
|
||||
.appendQueryParameter("client_id", clientId)
|
||||
fun authUrl(): Uri = "$BASE_OAUTH_URL/authorize".toUri().buildUpon()
|
||||
.appendQueryParameter("client_id", CLIENT_ID)
|
||||
.appendQueryParameter("code_challenge", getPkceChallengeCode())
|
||||
.appendQueryParameter("response_type", "code")
|
||||
.build()
|
||||
|
||||
fun mangaUrl(id: Long): Uri = "$baseApiUrl/manga".toUri().buildUpon()
|
||||
fun mangaUrl(id: Long): Uri = "$BASE_API_URL/manga".toUri().buildUpon()
|
||||
.appendPath(id.toString())
|
||||
.appendPath("my_list_status")
|
||||
.build()
|
||||
|
||||
fun refreshTokenRequest(oauth: OAuth): Request {
|
||||
val formBody: RequestBody = FormBody.Builder()
|
||||
.add("client_id", clientId)
|
||||
.add("client_id", CLIENT_ID)
|
||||
.add("refresh_token", oauth.refresh_token)
|
||||
.add("grant_type", "refresh_token")
|
||||
.build()
|
||||
|
@ -312,7 +311,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||
.add("Authorization", "Bearer ${oauth.access_token}")
|
||||
.build()
|
||||
|
||||
return POST("$baseOAuthUrl/token", body = formBody, headers = headers)
|
||||
return POST("$BASE_OAUTH_URL/token", body = formBody, headers = headers)
|
||||
}
|
||||
|
||||
private fun getPkceChallengeCode(): String {
|
||||
|
|
|
@ -1,58 +1,40 @@
|
|||
package eu.kanade.tachiyomi.data.track.myanimelist
|
||||
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.network.parseAs
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import okhttp3.internal.closeQuietly
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.IOException
|
||||
|
||||
class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var token: String?) : Interceptor {
|
||||
class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor {
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private var oauth: OAuth? = null
|
||||
private var oauth: OAuth? = myanimelist.loadOAuth()
|
||||
private val tokenExpired get() = myanimelist.getIfAuthExpired()
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
if (tokenExpired) {
|
||||
throw MALTokenExpired()
|
||||
}
|
||||
|
||||
val originalRequest = chain.request()
|
||||
|
||||
if (token.isNullOrEmpty()) {
|
||||
throw IOException("Not authenticated with MyAnimeList")
|
||||
// Refresh access token if expired
|
||||
if (oauth != null && oauth!!.isExpired()) {
|
||||
setAuth(refreshToken(chain))
|
||||
}
|
||||
|
||||
if (oauth == null) {
|
||||
oauth = myanimelist.loadOAuth()
|
||||
}
|
||||
// Refresh access token if expired or created_at is freshly set
|
||||
if (oauth != null &&
|
||||
(oauth!!.isExpired() || oauth!!.created_at == System.currentTimeMillis())
|
||||
) {
|
||||
val newOauth = with(json) {
|
||||
runCatching {
|
||||
val oauthResponse = chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!))
|
||||
|
||||
if (oauthResponse.isSuccessful) {
|
||||
oauthResponse.parseAs<OAuth>()
|
||||
} else {
|
||||
oauthResponse.closeQuietly()
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newOauth.getOrNull() == null) {
|
||||
throw IOException("Failed to refresh the access token")
|
||||
}
|
||||
|
||||
setAuth(newOauth.getOrNull())
|
||||
}
|
||||
if (oauth == null) {
|
||||
throw IOException("No authentication token")
|
||||
throw IOException("MAL: User is not authenticated")
|
||||
}
|
||||
|
||||
// Add the authorization header to the original request
|
||||
val authRequest = originalRequest.newBuilder()
|
||||
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
||||
.header("User-Agent", "null2264/yokai/${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||
.build()
|
||||
|
||||
return chain.proceed(authRequest)
|
||||
|
@ -63,8 +45,26 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
|
|||
* and the oauth object.
|
||||
*/
|
||||
fun setAuth(oauth: OAuth?) {
|
||||
token = oauth?.access_token
|
||||
this.oauth = oauth
|
||||
myanimelist.saveOAuth(oauth)
|
||||
}
|
||||
|
||||
private fun refreshToken(chain: Interceptor.Chain): OAuth {
|
||||
return runCatching {
|
||||
val oauthResponse = chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!))
|
||||
if (oauthResponse.code == 401) {
|
||||
myanimelist.setAuthExpired()
|
||||
}
|
||||
if (oauthResponse.isSuccessful) {
|
||||
with(json) { oauthResponse.parseAs<OAuth>() }
|
||||
} else {
|
||||
oauthResponse.close()
|
||||
null
|
||||
}
|
||||
}
|
||||
.getOrNull()
|
||||
?: throw MALTokenExpired()
|
||||
}
|
||||
}
|
||||
|
||||
class MALTokenExpired: IOException("MAL: Login has expired")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue