Use DTOs to parse tracking API responses (#1103)

* Migrate tracking APIs to DTOs

Changes the handling of tracker API responses to be parsed to DTOs
instead of doing so "manually" by use of `jsonPrimitive`s and/or
`Json.decodeFromString` invocations.

This greatly simplifies the API response handling.

Renamed constants to SCREAMING_SNAKE_CASE.

Largely tried to name the DTOs in a uniform pattern, with the
tracker's (short) name at the beginning of file and data class names
(ALOAuth instead of OAuth, etc).

With these changes, no area of the code base should be using
`jsonPrimitive` and/or `Json.decodeFromString` anymore.

* Fix wrong types in KitsuAlgoliaSearchItem

This API returns start and end dates as Long and the score as Double.

Kitsu's docs claim they're strings (and they are, when requesting
manga details from Kitsu directly) but the Algolia search results
return Longs and Double, respectively.

* Apply review changes

- Renamed `BangumiX` classes to `BGMX` classes.
- Renamed `toXStatus` and `toXScore` to `toApiStatus` and `toApiScore`

* Handle migration from detekt to spotless

Removed Suppressions added for detekt.

Specifically removed:
- `SwallowedException` where an exception ends as a default value
- `MagicNumber`
- `CyclomaticComplexMethod`
- `TooGenericExceptionThrown`

Also ran spotlessApply which changed SMAddMangaResponse

* Fix Kitsu failing to add series

The `included` attribute seems to only appear when the user already
has the entry in their Kitsu list.

Since both `data` and `included` are required for `firstToTrack`, a
guard clause has been added before all its calls.

* Fix empty Bangumi error when entry doesn't exist

Previously, the non-null assertion (!!) would cause a
NullPointerException and a Toast with
"Bangumi error: " (no message) when the user had removed their list
entry from Bangumi through other means like the website.

Now it will show "Bangumi error: Could not find manga".

This is analogous to the error shown by Kitsu under these
circumstances.

* Fix Shikimori ignoring missing remote entry

The user would see no indication that Shikimori could not properly
refresh the track from the remote. This change causes the error Toast
notification to pop up with the following message
"Shikimori error: Could not find manga".

This is analogous to Kitsu and Bangumi.

* Remove usage of let where not needed

These particular occurrences weren't needed because properties are
directly accessible to further act upon. This neatly simplifies these
clauses.

* Remove missed let

(cherry picked from commit 9f99f038f341e325c4f56372a5ce950cf9f7cd6d)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistInterceptor.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistModels.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuInterceptor.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriInterceptor.kt
This commit is contained in:
MajorTanya
2024-09-02 21:46:08 +02:00
committed by Jobobby04
parent 3972d7fe4b
commit 051c559840
67 changed files with 1215 additions and 841 deletions
@@ -2,7 +2,7 @@ package exh.md.network
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.tachiyomi.data.track.mdlist.MdList
import eu.kanade.tachiyomi.data.track.myanimelist.OAuth
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
import eu.kanade.tachiyomi.network.parseAs
import exh.md.utils.MdUtil
import exh.util.nullIfBlank
@@ -18,7 +18,7 @@ class MangaDexAuthInterceptor(
var token = trackPreferences.trackToken(mdList).get().nullIfBlank()
private var oauth: OAuth? = null
private var oauth: MALOAuth? = null
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
@@ -40,7 +40,7 @@ class MangaDexAuthInterceptor(
// Add the authorization header to the original request
val authRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
.addHeader("Authorization", "Bearer ${oauth!!.accessToken}")
.build()
val response = chain.proceed(authRequest)
@@ -57,7 +57,7 @@ class MangaDexAuthInterceptor(
response.close()
val newRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer ${newToken.access_token}")
.addHeader("Authorization", "Bearer ${newToken.accessToken}")
.build()
return chain.proceed(newRequest)
@@ -70,18 +70,18 @@ class MangaDexAuthInterceptor(
* Called when the user authenticates with MangaDex for the first time. Sets the refresh token
* and the oauth object.
*/
fun setAuth(oauth: OAuth?) {
token = oauth?.access_token
fun setAuth(oauth: MALOAuth?) {
token = oauth?.accessToken
this.oauth = oauth
MdUtil.saveOAuth(trackPreferences, mdList, oauth)
}
private fun refreshToken(chain: Interceptor.Chain): OAuth? {
private fun refreshToken(chain: Interceptor.Chain): MALOAuth? {
val newOauth = runCatching {
val oauthResponse = chain.proceed(MdUtil.refreshTokenRequest(oauth!!))
if (oauthResponse.isSuccessful) {
with(MdUtil.jsonParser) { oauthResponse.parseAs<OAuth>() }
with(MdUtil.jsonParser) { oauthResponse.parseAs<MALOAuth>() }
} else {
oauthResponse.close()
null
@@ -2,7 +2,7 @@ package exh.md.network
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.tachiyomi.data.track.mdlist.MdList
import eu.kanade.tachiyomi.data.track.myanimelist.OAuth
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.parseAs
@@ -38,7 +38,7 @@ class MangaDexLoginHelper(
val data = with(MdUtil.jsonParser) {
client.newCall(
POST(MdApi.baseAuthUrl + MdApi.token, body = loginFormBody),
).awaitSuccess().parseAs<OAuth>()
).awaitSuccess().parseAs<MALOAuth>()
}
mangaDexAuthInterceptor.setAuth(data)
}.exceptionOrNull()
@@ -55,8 +55,8 @@ class MangaDexLoginHelper(
suspend fun logout(): Boolean {
val oauth = MdUtil.loadOAuth(preferences, mdList)
val sessionToken = oauth?.access_token
val refreshToken = oauth?.refresh_token
val sessionToken = oauth?.accessToken
val refreshToken = oauth?.refreshToken
if (refreshToken.isNullOrEmpty() || sessionToken.isNullOrEmpty()) {
mdList.logout()
return true
+7 -7
View File
@@ -3,7 +3,7 @@ package exh.md.utils
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.tachiyomi.data.track.mdlist.MdList
import eu.kanade.tachiyomi.data.track.myanimelist.OAuth
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
@@ -189,7 +189,7 @@ class MdUtil {
return "$cdnUrl/covers/$dexId/$fileName"
}
fun saveOAuth(preferences: TrackPreferences, mdList: MdList, oAuth: OAuth?) {
fun saveOAuth(preferences: TrackPreferences, mdList: MdList, oAuth: MALOAuth?) {
if (oAuth == null) {
preferences.trackToken(mdList).delete()
} else {
@@ -197,9 +197,9 @@ class MdUtil {
}
}
fun loadOAuth(preferences: TrackPreferences, mdList: MdList): OAuth? {
fun loadOAuth(preferences: TrackPreferences, mdList: MdList): MALOAuth? {
return try {
jsonParser.decodeFromString<OAuth>(preferences.trackToken(mdList).get())
jsonParser.decodeFromString<MALOAuth>(preferences.trackToken(mdList).get())
} catch (e: Exception) {
null
}
@@ -207,11 +207,11 @@ class MdUtil {
private var codeVerifier: String? = null
fun refreshTokenRequest(oauth: OAuth): Request {
fun refreshTokenRequest(oauth: MALOAuth): Request {
val formBody = FormBody.Builder()
.add("client_id", MdConstants.Login.clientId)
.add("grant_type", MdConstants.Login.refreshToken)
.add("refresh_token", oauth.refresh_token)
.add("refresh_token", oauth.refreshToken)
.add("code_verifier", getPkceChallengeCode())
.add("redirect_uri", MdConstants.Login.redirectUri)
.build()
@@ -220,7 +220,7 @@ class MdUtil {
// request is called by the interceptor itself so it doesn't reach
// the part where the token is added automatically.
val headers = Headers.Builder()
.add("Authorization", "Bearer ${oauth.access_token}")
.add("Authorization", "Bearer ${oauth.accessToken}")
.build()
return POST(MdApi.baseAuthUrl + MdApi.token, body = formBody, headers = headers)