mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 22:37:56 +01:00 
			
		
		
		
	Score formatting. Hide API from Anilist/Kitsu services.
This commit is contained in:
		| @@ -21,6 +21,23 @@ abstract class TrackService(val id: Int) { | ||||
|     // Name of the manga sync service to display | ||||
|     abstract val name: String | ||||
|  | ||||
|     @DrawableRes | ||||
|     abstract fun getLogo(): Int | ||||
|  | ||||
|     abstract fun getLogoColor(): Int | ||||
|  | ||||
|     abstract fun getStatusList(): List<Int> | ||||
|  | ||||
|     abstract fun getStatus(status: Int): String | ||||
|  | ||||
|     abstract fun getScoreList(): List<String> | ||||
|  | ||||
|     open fun indexToScore(index: Int): Float { | ||||
|         return index.toFloat() | ||||
|     } | ||||
|  | ||||
|     abstract fun displayScore(track: Track): String | ||||
|  | ||||
|     abstract fun login(username: String, password: String): Completable | ||||
|  | ||||
|     open val isLogged: Boolean | ||||
| @@ -37,20 +54,6 @@ abstract class TrackService(val id: Int) { | ||||
|  | ||||
|     abstract fun refresh(track: Track): Observable<Track> | ||||
|  | ||||
|     abstract fun getStatus(status: Int): String | ||||
|  | ||||
|     abstract fun getStatusList(): List<Int> | ||||
|  | ||||
|     @DrawableRes | ||||
|     abstract fun getLogo(): Int | ||||
|  | ||||
|     abstract fun getLogoColor(): Int | ||||
|  | ||||
|     // TODO better support (decimals) | ||||
|     abstract fun maxScore(): Int | ||||
|  | ||||
|     abstract fun formatScore(track: Track): String | ||||
|  | ||||
|     fun saveCredentials(username: String, password: String) { | ||||
|         preferences.setTrackCredentials(this, username, password) | ||||
|     } | ||||
|   | ||||
| @@ -2,15 +2,12 @@ package eu.kanade.tachiyomi.data.track.anilist | ||||
|  | ||||
| import android.content.Context | ||||
| import android.graphics.Color | ||||
| import com.github.salomonbrys.kotson.int | ||||
| import com.github.salomonbrys.kotson.string | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.database.models.Track | ||||
| import eu.kanade.tachiyomi.data.preference.getOrDefault | ||||
| import eu.kanade.tachiyomi.data.track.TrackService | ||||
| import rx.Completable | ||||
| import rx.Observable | ||||
| import timber.log.Timber | ||||
|  | ||||
| class Anilist(private val context: Context, id: Int) : TrackService(id) { | ||||
|  | ||||
| @@ -29,110 +26,12 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) { | ||||
|  | ||||
|     private val interceptor by lazy { AnilistInterceptor(getPassword()) } | ||||
|  | ||||
|     private val api by lazy { | ||||
|         AnilistApi.createService(networkService.client.newBuilder() | ||||
|                 .addInterceptor(interceptor) | ||||
|                 .build()) | ||||
|     } | ||||
|     private val api by lazy { AnilistApi(client, interceptor) } | ||||
|  | ||||
|     override fun getLogo() = R.drawable.al | ||||
|  | ||||
|     override fun getLogoColor() = Color.rgb(18, 25, 35) | ||||
|  | ||||
|     override fun maxScore() = 100 | ||||
|  | ||||
|     override fun login(username: String, password: String) = login(password) | ||||
|  | ||||
|     fun login(authCode: String): Completable { | ||||
|         // Create a new api with the default client to avoid request interceptions. | ||||
|         return AnilistApi.createService(client) | ||||
|                 // Request the access token from the API with the authorization code. | ||||
|                 .requestAccessToken(authCode) | ||||
|                 // Save the token in the interceptor. | ||||
|                 .doOnNext { interceptor.setAuth(it) } | ||||
|                 // Obtain the authenticated user from the API. | ||||
|                 .zipWith(api.getCurrentUser().map { | ||||
|                     preferences.anilistScoreType().set(it["score_type"].int) | ||||
|                     it["id"].string | ||||
|                 }, { oauth, user -> Pair(user, oauth.refresh_token!!) }) | ||||
|                 // Save service credentials (username and refresh token). | ||||
|                 .doOnNext { saveCredentials(it.first, it.second) } | ||||
|                 // Logout on any error. | ||||
|                 .doOnError { logout() } | ||||
|                 .toCompletable() | ||||
|     } | ||||
|  | ||||
|     override fun logout() { | ||||
|         super.logout() | ||||
|         interceptor.setAuth(null) | ||||
|     } | ||||
|  | ||||
|     override fun search(query: String): Observable<List<Track>> { | ||||
|         return api.search(query, 1) | ||||
|                 .flatMap { Observable.from(it) } | ||||
|                 .filter { it.type != "Novel" } | ||||
|                 .map { it.toTrack() } | ||||
|                 .toList() | ||||
|     } | ||||
|  | ||||
|     fun getList(): Observable<List<Track>> { | ||||
|         return api.getList(getUsername()) | ||||
|                 .flatMap { Observable.from(it.flatten()) } | ||||
|                 .map { it.toTrack() } | ||||
|                 .toList() | ||||
|     } | ||||
|  | ||||
|     override fun add(track: Track): Observable<Track> { | ||||
|         return api.addManga(track.remote_id, track.last_chapter_read, track.getAnilistStatus()) | ||||
|                 .doOnNext { it.body().close() } | ||||
|                 .doOnNext { if (!it.isSuccessful) throw Exception("Could not add manga") } | ||||
|                 .doOnError { Timber.e(it) } | ||||
|                 .map { track } | ||||
|     } | ||||
|  | ||||
|     override fun update(track: Track): Observable<Track> { | ||||
|         if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) { | ||||
|             track.status = COMPLETED | ||||
|         } | ||||
|         return api.updateManga(track.remote_id, track.last_chapter_read, track.getAnilistStatus(), | ||||
|                 track.getAnilistScore()) | ||||
|                 .doOnNext { it.body().close() } | ||||
|                 .doOnNext { if (!it.isSuccessful) throw Exception("Could not update manga") } | ||||
|                 .doOnError { Timber.e(it) } | ||||
|                 .map { track } | ||||
|     } | ||||
|  | ||||
|     override fun bind(track: Track): Observable<Track> { | ||||
|         return getList() | ||||
|                 .flatMap { userlist -> | ||||
|                     track.sync_id = id | ||||
|                     val remoteTrack = userlist.find { it.remote_id == track.remote_id } | ||||
|                     if (remoteTrack != null) { | ||||
|                         track.copyPersonalFrom(remoteTrack) | ||||
|                         update(track) | ||||
|                     } else { | ||||
|                         // Set default fields if it's not found in the list | ||||
|                         track.score = DEFAULT_SCORE.toFloat() | ||||
|                         track.status = DEFAULT_STATUS | ||||
|                         add(track) | ||||
|                     } | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     override fun refresh(track: Track): Observable<Track> { | ||||
|         return getList() | ||||
|                 .map { myList -> | ||||
|                     val remoteTrack = myList.find { it.remote_id == track.remote_id } | ||||
|                     if (remoteTrack != null) { | ||||
|                         track.copyPersonalFrom(remoteTrack) | ||||
|                         track.total_chapters = remoteTrack.total_chapters | ||||
|                         track | ||||
|                     } else { | ||||
|                         throw Exception("Could not find manga") | ||||
|                     } | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     override fun getStatusList(): List<Int> { | ||||
|         return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ) | ||||
|     } | ||||
| @@ -148,43 +47,118 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun Track.getAnilistStatus() = when (status) { | ||||
|         READING -> "reading" | ||||
|         COMPLETED -> "completed" | ||||
|         ON_HOLD -> "on-hold" | ||||
|         DROPPED -> "dropped" | ||||
|         PLAN_TO_READ -> "plan to read" | ||||
|         else -> throw NotImplementedError("Unknown status") | ||||
|     override fun getScoreList(): List<String> { | ||||
|         return when (preferences.anilistScoreType().getOrDefault()) { | ||||
|             // 10 point | ||||
|             0 -> IntRange(0, 10).map(Int::toString) | ||||
|             // 100 point | ||||
|             1 -> IntRange(0, 100).map(Int::toString) | ||||
|             // 5 stars | ||||
|             2 -> IntRange(0, 5).map { "$it ★" } | ||||
|             // Smiley | ||||
|             3 -> listOf("-", "😦", "😐", "😊") | ||||
|             // 10 point decimal | ||||
|             4 -> IntRange(0, 100).map { (it / 10f).toString() } | ||||
|             else -> throw Exception("Unknown score type") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun Track.getAnilistScore(): String = when (preferences.anilistScoreType().getOrDefault()) { | ||||
|         // 10 point | ||||
|         0 -> Math.floor(score.toDouble() / 10).toInt().toString() | ||||
|         // 100 point | ||||
|         1 -> score.toInt().toString() | ||||
|         // 5 stars | ||||
|         2 -> when { | ||||
|             score == 0f -> "0" | ||||
|             score < 30 -> "1" | ||||
|             score < 50 -> "2" | ||||
|             score < 70 -> "3" | ||||
|             score < 90 -> "4" | ||||
|             else -> "5" | ||||
|     override fun indexToScore(index: Int): Float { | ||||
|         return when (preferences.anilistScoreType().getOrDefault()) { | ||||
|             // 10 point | ||||
|             0 -> index * 10f | ||||
|             // 100 point | ||||
|             1 -> index.toFloat() | ||||
|             // 5 stars | ||||
|             2 -> index * 20f | ||||
|             // Smiley | ||||
|             3 -> index * 30f | ||||
|             // 10 point decimal | ||||
|             4 -> index / 10f | ||||
|             else -> throw Exception("Unknown score type") | ||||
|         } | ||||
|         // Smiley | ||||
|         3 -> when { | ||||
|             score == 0f -> "0" | ||||
|             score <= 30 -> ":(" | ||||
|             score <= 60 -> ":|" | ||||
|             else -> ":)" | ||||
|         } | ||||
|         // 10 point decimal | ||||
|         4 -> (score / 10).toString() | ||||
|         else -> throw Exception("Unknown score type") | ||||
|     } | ||||
|  | ||||
|     override fun formatScore(track: Track): String { | ||||
|         return track.getAnilistScore() | ||||
|     override fun displayScore(track: Track): String { | ||||
|         val score = track.score | ||||
|         return when (preferences.anilistScoreType().getOrDefault()) { | ||||
|             2 -> "${(score / 20).toInt()} ★" | ||||
|             3 -> when { | ||||
|                 score == 0f -> "0" | ||||
|                 score <= 30 -> "😦" | ||||
|                 score <= 60 -> "😐" | ||||
|                 else -> "😊" | ||||
|             } | ||||
|             else -> track.toAnilistScore() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun login(username: String, password: String) = login(password) | ||||
|  | ||||
|     fun login(authCode: String): Completable { | ||||
|         return api.login(authCode) | ||||
|                 // Save the token in the interceptor. | ||||
|                 .doOnNext { interceptor.setAuth(it) } | ||||
|                 // Obtain the authenticated user from the API. | ||||
|                 .zipWith(api.getCurrentUser().map { pair -> | ||||
|                     preferences.anilistScoreType().set(pair.second) | ||||
|                     pair.first | ||||
|                 }, { oauth, user -> Pair(user, oauth.refresh_token!!) }) | ||||
|                 // Save service credentials (username and refresh token). | ||||
|                 .doOnNext { saveCredentials(it.first, it.second) } | ||||
|                 // Logout on any error. | ||||
|                 .doOnError { logout() } | ||||
|                 .toCompletable() | ||||
|     } | ||||
|  | ||||
|     override fun logout() { | ||||
|         super.logout() | ||||
|         interceptor.setAuth(null) | ||||
|     } | ||||
|  | ||||
|     override fun search(query: String): Observable<List<Track>> { | ||||
|         return api.search(query) | ||||
|     } | ||||
|  | ||||
|     override fun add(track: Track): Observable<Track> { | ||||
|         return api.addLibManga(track) | ||||
|     } | ||||
|  | ||||
|     override fun update(track: Track): Observable<Track> { | ||||
|         if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) { | ||||
|             track.status = COMPLETED | ||||
|         } | ||||
|  | ||||
|         return api.updateLibManga(track) | ||||
|     } | ||||
|  | ||||
|     override fun bind(track: Track): Observable<Track> { | ||||
|         return api.findLibManga(getUsername(), track) | ||||
|                 .flatMap { remoteTrack -> | ||||
|                     if (remoteTrack != null) { | ||||
|                         track.copyPersonalFrom(remoteTrack) | ||||
|                         update(track) | ||||
|                     } else { | ||||
|                         // Set default fields if it's not found in the list | ||||
|                         track.score = DEFAULT_SCORE.toFloat() | ||||
|                         track.status = DEFAULT_STATUS | ||||
|                         add(track) | ||||
|                     } | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     override fun refresh(track: Track): Observable<Track> { | ||||
|         // TODO getLibManga method? | ||||
|         return api.findLibManga(getUsername(), track) | ||||
|                 .map { remoteTrack -> | ||||
|                     if (remoteTrack != null) { | ||||
|                         track.copyPersonalFrom(remoteTrack) | ||||
|                         track.total_chapters = remoteTrack.total_chapters | ||||
|                         track | ||||
|                     } else { | ||||
|                         throw Exception("Could not find manga") | ||||
|                     } | ||||
|                 } | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| package eu.kanade.tachiyomi.data.track.anilist | ||||
|  | ||||
| import android.net.Uri | ||||
| import com.github.salomonbrys.kotson.int | ||||
| import com.github.salomonbrys.kotson.string | ||||
| import com.google.gson.JsonObject | ||||
| import eu.kanade.tachiyomi.data.database.models.Track | ||||
| import eu.kanade.tachiyomi.data.network.POST | ||||
| import eu.kanade.tachiyomi.data.track.anilist.model.ALManga | ||||
| import eu.kanade.tachiyomi.data.track.anilist.model.ALUserLists | ||||
| import eu.kanade.tachiyomi.data.track.anilist.model.OAuth | ||||
| import okhttp3.FormBody | ||||
| import okhttp3.OkHttpClient | ||||
| import okhttp3.ResponseBody | ||||
| @@ -16,7 +16,110 @@ import retrofit2.converter.gson.GsonConverterFactory | ||||
| import retrofit2.http.* | ||||
| import rx.Observable | ||||
|  | ||||
| interface AnilistApi { | ||||
| class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { | ||||
|  | ||||
|     private val rest = restBuilder() | ||||
|             .client(client.newBuilder().addInterceptor(interceptor).build()) | ||||
|             .build() | ||||
|             .create(Rest::class.java) | ||||
|  | ||||
|     private fun restBuilder() = Retrofit.Builder() | ||||
|             .baseUrl(baseUrl) | ||||
|             .addConverterFactory(GsonConverterFactory.create()) | ||||
|             .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) | ||||
|  | ||||
|     fun login(authCode: String): Observable<OAuth> { | ||||
|         return restBuilder() | ||||
|                 .client(client) | ||||
|                 .build() | ||||
|                 .create(Rest::class.java) | ||||
|                 .requestAccessToken(authCode) | ||||
|     } | ||||
|  | ||||
|     fun getCurrentUser(): Observable<Pair<String, Int>> { | ||||
|         return rest.getCurrentUser() | ||||
|                 .map { it["id"].string to it["score_type"].int } | ||||
|     } | ||||
|  | ||||
|     fun search(query: String): Observable<List<Track>> { | ||||
|         return rest.search(query, 1) | ||||
|                 .map { list -> | ||||
|                     list.filter { it.type != "Novel" }.map { it.toTrack() } | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     fun getList(username: String): Observable<List<Track>> { | ||||
|         return rest.getLib(username) | ||||
|                 .map { lib -> | ||||
|                     lib.flatten().map { it.toTrack() } | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     fun addLibManga(track: Track): Observable<Track> { | ||||
|         return rest.addLibManga(track.remote_id, track.last_chapter_read, track.toAnilistStatus()) | ||||
|                 .doOnNext { it.body().close() } | ||||
|                 .doOnNext { if (!it.isSuccessful) throw Exception("Could not add manga") } | ||||
|                 .map { track } | ||||
|     } | ||||
|  | ||||
|     fun updateLibManga(track: Track): Observable<Track> { | ||||
|         return rest.updateLibManga(track.remote_id, track.last_chapter_read, track.toAnilistStatus(), | ||||
|                 track.toAnilistScore()) | ||||
|                 .doOnNext { it.body().close() } | ||||
|                 .doOnNext { if (!it.isSuccessful) throw Exception("Could not update manga") } | ||||
|                 .map { track } | ||||
|     } | ||||
|  | ||||
|     fun findLibManga(username: String, track: Track) : Observable<Track?> { | ||||
|         // TODO avoid getting the entire list | ||||
|         return getList(username) | ||||
|                 .map { list -> list.find { it.remote_id == track.remote_id } } | ||||
|     } | ||||
|  | ||||
|     private interface Rest { | ||||
|  | ||||
|         @FormUrlEncoded | ||||
|         @POST("auth/access_token") | ||||
|         fun requestAccessToken( | ||||
|                 @Field("code") code: String, | ||||
|                 @Field("grant_type") grant_type: String = "authorization_code", | ||||
|                 @Field("client_id") client_id: String = clientId, | ||||
|                 @Field("client_secret") client_secret: String = clientSecret, | ||||
|                 @Field("redirect_uri") redirect_uri: String = clientUrl | ||||
|         ) : Observable<OAuth> | ||||
|  | ||||
|         @GET("user") | ||||
|         fun getCurrentUser(): Observable<JsonObject> | ||||
|  | ||||
|         @GET("manga/search/{query}") | ||||
|         fun search( | ||||
|                 @Path("query") query: String, | ||||
|                 @Query("page") page: Int | ||||
|         ): Observable<List<ALManga>> | ||||
|  | ||||
|         @GET("user/{username}/mangalist") | ||||
|         fun getLib( | ||||
|                 @Path("username") username: String | ||||
|         ): Observable<ALUserLists> | ||||
|  | ||||
|         @FormUrlEncoded | ||||
|         @PUT("mangalist") | ||||
|         fun addLibManga( | ||||
|                 @Field("id") id: Int, | ||||
|                 @Field("chapters_read") chapters_read: Int, | ||||
|                 @Field("list_status") list_status: String | ||||
|         ) : Observable<Response<ResponseBody>> | ||||
|  | ||||
|         @FormUrlEncoded | ||||
|         @PUT("mangalist") | ||||
|         fun updateLibManga( | ||||
|                 @Field("id") id: Int, | ||||
|                 @Field("chapters_read") chapters_read: Int, | ||||
|                 @Field("list_status") list_status: String, | ||||
|                 @Field("score") score_raw: String | ||||
|         ) : Observable<Response<ResponseBody>> | ||||
|  | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         private const val clientId = "tachiyomi-hrtje" | ||||
| @@ -39,50 +142,6 @@ interface AnilistApi { | ||||
|                         .add("refresh_token", token) | ||||
|                         .build()) | ||||
|  | ||||
|         fun createService(client: OkHttpClient) = Retrofit.Builder() | ||||
|                 .baseUrl(baseUrl) | ||||
|                 .client(client) | ||||
|                 .addConverterFactory(GsonConverterFactory.create()) | ||||
|                 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) | ||||
|                 .build() | ||||
|                 .create(AnilistApi::class.java) | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @FormUrlEncoded | ||||
|     @POST("auth/access_token") | ||||
|     fun requestAccessToken( | ||||
|             @Field("code") code: String, | ||||
|             @Field("grant_type") grant_type: String = "authorization_code", | ||||
|             @Field("client_id") client_id: String = clientId, | ||||
|             @Field("client_secret") client_secret: String = clientSecret, | ||||
|             @Field("redirect_uri") redirect_uri: String = clientUrl) | ||||
|             : Observable<OAuth> | ||||
|  | ||||
|     @GET("user") | ||||
|     fun getCurrentUser(): Observable<JsonObject> | ||||
|  | ||||
|     @GET("manga/search/{query}") | ||||
|     fun search(@Path("query") query: String, @Query("page") page: Int): Observable<List<ALManga>> | ||||
|  | ||||
|     @GET("user/{username}/mangalist") | ||||
|     fun getList(@Path("username") username: String): Observable<ALUserLists> | ||||
|  | ||||
|     @FormUrlEncoded | ||||
|     @PUT("mangalist") | ||||
|     fun addManga( | ||||
|             @Field("id") id: Int, | ||||
|             @Field("chapters_read") chapters_read: Int, | ||||
|             @Field("list_status") list_status: String) | ||||
|             : Observable<Response<ResponseBody>> | ||||
|  | ||||
|     @FormUrlEncoded | ||||
|     @PUT("mangalist") | ||||
|     fun updateManga( | ||||
|             @Field("id") id: Int, | ||||
|             @Field("chapters_read") chapters_read: Int, | ||||
|             @Field("list_status") list_status: String, | ||||
|             @Field("score") score_raw: String) | ||||
|             : Observable<Response<ResponseBody>> | ||||
|  | ||||
| } | ||||
| @@ -1,7 +1,7 @@ | ||||
| package eu.kanade.tachiyomi.data.track.anilist | ||||
|  | ||||
| import com.google.gson.Gson | ||||
| import eu.kanade.tachiyomi.data.track.anilist.model.OAuth | ||||
| import eu.kanade.tachiyomi.data.track.anilist.OAuth | ||||
| import okhttp3.Interceptor | ||||
| import okhttp3.Response | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,86 @@ | ||||
| package eu.kanade.tachiyomi.data.track.anilist | ||||
|  | ||||
| import eu.kanade.tachiyomi.data.database.models.Track | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.data.preference.getOrDefault | ||||
| import eu.kanade.tachiyomi.data.track.TrackManager | ||||
| import uy.kohesive.injekt.injectLazy | ||||
|  | ||||
| data class ALManga( | ||||
|         val id: Int, | ||||
|         val title_romaji: String, | ||||
|         val type: String, | ||||
|         val total_chapters: Int) { | ||||
|  | ||||
|     fun toTrack() = Track.create(TrackManager.ANILIST).apply { | ||||
|         remote_id = this@ALManga.id | ||||
|         title = title_romaji | ||||
|         total_chapters = this@ALManga.total_chapters | ||||
|     } | ||||
| } | ||||
|  | ||||
| data class ALUserManga( | ||||
|         val id: Int, | ||||
|         val list_status: String, | ||||
|         val score_raw: Int, | ||||
|         val chapters_read: Int, | ||||
|         val manga: ALManga) { | ||||
|  | ||||
|     fun toTrack() = Track.create(TrackManager.ANILIST).apply { | ||||
|         remote_id = manga.id | ||||
|         status = toTrackStatus() | ||||
|         score = score_raw.toFloat() | ||||
|         last_chapter_read = chapters_read | ||||
|     } | ||||
|  | ||||
|     fun toTrackStatus() = when (list_status) { | ||||
|         "reading" -> Anilist.READING | ||||
|         "completed" -> Anilist.COMPLETED | ||||
|         "on-hold" -> Anilist.ON_HOLD | ||||
|         "dropped" -> Anilist.DROPPED | ||||
|         "plan to read" -> Anilist.PLAN_TO_READ | ||||
|         else -> throw NotImplementedError("Unknown status") | ||||
|     } | ||||
| } | ||||
|  | ||||
| data class ALUserLists(val lists: Map<String, List<ALUserManga>>) { | ||||
|  | ||||
|     fun flatten() = lists.values.flatten() | ||||
| } | ||||
|  | ||||
| fun Track.toAnilistStatus() = when (status) { | ||||
|     Anilist.READING -> "reading" | ||||
|     Anilist.COMPLETED -> "completed" | ||||
|     Anilist.ON_HOLD -> "on-hold" | ||||
|     Anilist.DROPPED -> "dropped" | ||||
|     Anilist.PLAN_TO_READ -> "plan to read" | ||||
|     else -> throw NotImplementedError("Unknown status") | ||||
| } | ||||
|  | ||||
| private val preferences: PreferencesHelper by injectLazy() | ||||
|  | ||||
| fun Track.toAnilistScore(): String = when (preferences.anilistScoreType().getOrDefault()) { | ||||
|     // 10 point | ||||
|     0 -> Math.floor(score.toDouble() / 10).toInt().toString() | ||||
|     // 100 point | ||||
|     1 -> score.toInt().toString() | ||||
|     // 5 stars | ||||
|     2 -> when { | ||||
|         score == 0f -> "0" | ||||
|         score < 30 -> "1" | ||||
|         score < 50 -> "2" | ||||
|         score < 70 -> "3" | ||||
|         score < 90 -> "4" | ||||
|         else -> "5" | ||||
|     } | ||||
|     // Smiley | ||||
|     3 -> when { | ||||
|         score == 0f -> "0" | ||||
|         score <= 30 -> ":(" | ||||
|         score <= 60 -> ":|" | ||||
|         else -> ":)" | ||||
|     } | ||||
|     // 10 point decimal | ||||
|     4 -> (score / 10).toString() | ||||
|     else -> throw Exception("Unknown score type") | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| package eu.kanade.tachiyomi.data.track.anilist.model | ||||
| package eu.kanade.tachiyomi.data.track.anilist | ||||
| 
 | ||||
| data class OAuth( | ||||
|         val access_token: String, | ||||
| @@ -1,17 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.data.track.anilist.model | ||||
|  | ||||
| import eu.kanade.tachiyomi.data.database.models.Track | ||||
| import eu.kanade.tachiyomi.data.track.TrackManager | ||||
|  | ||||
| data class ALManga( | ||||
|         val id: Int, | ||||
|         val title_romaji: String, | ||||
|         val type: String, | ||||
|         val total_chapters: Int) { | ||||
|  | ||||
|     fun toTrack() = Track.create(TrackManager.ANILIST).apply { | ||||
|         remote_id = this@ALManga.id | ||||
|         title = title_romaji | ||||
|         total_chapters = this@ALManga.total_chapters | ||||
|     } | ||||
| } | ||||
| @@ -1,6 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.data.track.anilist.model | ||||
|  | ||||
| data class ALUserLists(val lists: Map<String, List<ALUserManga>>) { | ||||
|  | ||||
|     fun flatten() = lists.values.flatten() | ||||
| } | ||||
| @@ -1,29 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.data.track.anilist.model | ||||
|  | ||||
| import eu.kanade.tachiyomi.data.database.models.Track | ||||
| import eu.kanade.tachiyomi.data.track.TrackManager | ||||
| import eu.kanade.tachiyomi.data.track.anilist.Anilist | ||||
|  | ||||
| data class ALUserManga( | ||||
|         val id: Int, | ||||
|         val list_status: String, | ||||
|         val score_raw: Int, | ||||
|         val chapters_read: Int, | ||||
|         val manga: ALManga) { | ||||
|  | ||||
|     fun toTrack() = Track.create(TrackManager.ANILIST).apply { | ||||
|         remote_id = manga.id | ||||
|         status = toTrackStatus() | ||||
|         score = score_raw.toFloat() | ||||
|         last_chapter_read = chapters_read | ||||
|     } | ||||
|  | ||||
|     fun toTrackStatus() = when (list_status) { | ||||
|         "reading" -> Anilist.READING | ||||
|         "completed" -> Anilist.COMPLETED | ||||
|         "on-hold" -> Anilist.ON_HOLD | ||||
|         "dropped" -> Anilist.DROPPED | ||||
|         "plan to read" -> Anilist.PLAN_TO_READ | ||||
|         else -> throw NotImplementedError("Unknown status") | ||||
|     } | ||||
| } | ||||
| @@ -2,14 +2,12 @@ package eu.kanade.tachiyomi.data.track.kitsu | ||||
|  | ||||
| import android.content.Context | ||||
| import android.graphics.Color | ||||
| import com.github.salomonbrys.kotson.* | ||||
| import com.google.gson.Gson | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.database.models.Track | ||||
| import eu.kanade.tachiyomi.data.track.TrackService | ||||
| import rx.Completable | ||||
| import rx.Observable | ||||
| import timber.log.Timber | ||||
| import uy.kohesive.injekt.injectLazy | ||||
|  | ||||
| class Kitsu(private val context: Context, id: Int) : TrackService(id) { | ||||
| @@ -31,10 +29,37 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) { | ||||
|  | ||||
|     private val interceptor by lazy { KitsuInterceptor(this, gson) } | ||||
|  | ||||
|     private val api by lazy { | ||||
|         KitsuApi.createService(client.newBuilder() | ||||
|                 .addInterceptor(interceptor) | ||||
|                 .build()) | ||||
|     private val api by lazy { KitsuApi(client, interceptor) } | ||||
|  | ||||
|     override fun getLogo(): Int { | ||||
|         return R.drawable.kitsu | ||||
|     } | ||||
|  | ||||
|     override fun getLogoColor(): Int { | ||||
|         return Color.rgb(51, 37, 50) | ||||
|     } | ||||
|  | ||||
|     override fun getStatusList(): List<Int> { | ||||
|         return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ) | ||||
|     } | ||||
|  | ||||
|     override fun getStatus(status: Int): String = with(context) { | ||||
|         when (status) { | ||||
|             READING -> getString(R.string.reading) | ||||
|             COMPLETED -> getString(R.string.completed) | ||||
|             ON_HOLD -> getString(R.string.on_hold) | ||||
|             DROPPED -> getString(R.string.dropped) | ||||
|             PLAN_TO_READ -> getString(R.string.plan_to_read) | ||||
|             else -> "" | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun getScoreList(): List<String> { | ||||
|         return IntRange(0, 10).map { (it.toFloat() / 2).toString() } | ||||
|     } | ||||
|  | ||||
|     override fun displayScore(track: Track): String { | ||||
|         return track.toKitsuScore() | ||||
|     } | ||||
|  | ||||
|     private fun getUserId(): String { | ||||
| @@ -55,10 +80,9 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) { | ||||
|     } | ||||
|  | ||||
|     override fun login(username: String, password: String): Completable { | ||||
|         return KitsuApi.createLoginService(client) | ||||
|                 .requestAccessToken(username, password) | ||||
|         return api.login(username, password) | ||||
|                 .doOnNext { interceptor.newAuth(it) } | ||||
|                 .flatMap { api.getCurrentUser().map { it["data"].array[0]["id"].string } } | ||||
|                 .flatMap { api.getCurrentUser() } | ||||
|                 .doOnNext { userId -> saveCredentials(username, userId) } | ||||
|                 .doOnError { logout() } | ||||
|                 .toCompletable() | ||||
| @@ -71,11 +95,6 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) { | ||||
|  | ||||
|     override fun search(query: String): Observable<List<Track>> { | ||||
|         return api.search(query) | ||||
|                 .map { json -> | ||||
|                     val data = json["data"].array | ||||
|                     data.map { KitsuManga(it.obj).toTrack() } | ||||
|                 } | ||||
|                 .doOnError { Timber.e(it) } | ||||
|     } | ||||
|  | ||||
|     override fun bind(track: Track): Observable<Track> { | ||||
| @@ -95,125 +114,26 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) { | ||||
|  | ||||
|     private fun find(track: Track): Observable<Track?> { | ||||
|         return api.findLibManga(getUserId(), track.remote_id) | ||||
|                 .map { json -> | ||||
|                     val data = json["data"].array | ||||
|                     if (data.size() > 0) { | ||||
|                         KitsuLibManga(data[0].obj, json["included"].array[0].obj).toTrack() | ||||
|                     } else { | ||||
|                         null | ||||
|                     } | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     override fun add(track: Track): Observable<Track> { | ||||
|         // @formatter:off | ||||
|         val data = jsonObject( | ||||
|             "type" to "libraryEntries", | ||||
|             "attributes" to jsonObject( | ||||
|                 "status" to track.getKitsuStatus(), | ||||
|                 "progress" to track.last_chapter_read | ||||
|             ), | ||||
|             "relationships" to jsonObject( | ||||
|                 "user" to jsonObject( | ||||
|                     "data" to jsonObject( | ||||
|                         "id" to getUserId(), | ||||
|                         "type" to "users" | ||||
|                     ) | ||||
|                 ), | ||||
|                 "media" to jsonObject( | ||||
|                     "data" to jsonObject( | ||||
|                         "id" to track.remote_id, | ||||
|                         "type" to "manga" | ||||
|                     ) | ||||
|                 ) | ||||
|             ) | ||||
|         ) | ||||
|         // @formatter:on | ||||
|  | ||||
|         return api.addLibManga(jsonObject("data" to data)) | ||||
|                 .doOnNext { json -> track.remote_id = json["data"]["id"].int } | ||||
|                 .doOnError { Timber.e(it) } | ||||
|                 .map { track } | ||||
|         return api.addLibManga(track, getUserId()) | ||||
|     } | ||||
|  | ||||
|     override fun update(track: Track): Observable<Track> { | ||||
|         if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) { | ||||
|             track.status = COMPLETED | ||||
|         } | ||||
|         // @formatter:off | ||||
|         val data = jsonObject( | ||||
|             "type" to "libraryEntries", | ||||
|             "id" to track.remote_id, | ||||
|             "attributes" to jsonObject( | ||||
|                 "status" to track.getKitsuStatus(), | ||||
|                 "progress" to track.last_chapter_read, | ||||
|                 "rating" to track.getKitsuScore() | ||||
|             ) | ||||
|         ) | ||||
|         // @formatter:on | ||||
|  | ||||
|         return api.updateLibManga(track.remote_id, jsonObject("data" to data)) | ||||
|                 .map { track } | ||||
|         return api.updateLibManga(track) | ||||
|     } | ||||
|  | ||||
|     override fun refresh(track: Track): Observable<Track> { | ||||
|         return api.getLibManga(track.remote_id) | ||||
|                 .map { json -> | ||||
|                     val data = json["data"].array | ||||
|                     if (data.size() > 0) { | ||||
|                         val include = json["included"].array[0].obj | ||||
|                         val remoteTrack = KitsuLibManga(data[0].obj, include).toTrack() | ||||
|                         track.copyPersonalFrom(remoteTrack) | ||||
|                         track.total_chapters = remoteTrack.total_chapters | ||||
|                         track | ||||
|                     } else { | ||||
|                         throw Exception("Could not find manga") | ||||
|                     } | ||||
|         return api.getLibManga(track) | ||||
|                 .doOnNext { remoteTrack -> | ||||
|                     track.copyPersonalFrom(remoteTrack) | ||||
|                     track.total_chapters = remoteTrack.total_chapters | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     override fun getStatusList(): List<Int> { | ||||
|         return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ) | ||||
|     } | ||||
|  | ||||
|     override fun getStatus(status: Int): String = with(context) { | ||||
|         when (status) { | ||||
|             READING -> getString(R.string.reading) | ||||
|             COMPLETED -> getString(R.string.completed) | ||||
|             ON_HOLD -> getString(R.string.on_hold) | ||||
|             DROPPED -> getString(R.string.dropped) | ||||
|             PLAN_TO_READ -> getString(R.string.plan_to_read) | ||||
|             else -> "" | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun Track.getKitsuStatus() = when (status) { | ||||
|         READING -> "current" | ||||
|         COMPLETED -> "completed" | ||||
|         ON_HOLD -> "on_hold" | ||||
|         DROPPED -> "dropped" | ||||
|         PLAN_TO_READ -> "planned" | ||||
|         else -> throw Exception("Unknown status") | ||||
|     } | ||||
|  | ||||
|     private fun Track.getKitsuScore(): String { | ||||
|         return if (score > 0) (score / 2).toString() else "" | ||||
|     } | ||||
|  | ||||
|     override fun getLogo(): Int { | ||||
|         return R.drawable.kitsu | ||||
|     } | ||||
|  | ||||
|     override fun getLogoColor(): Int { | ||||
|         return Color.rgb(51, 37, 50) | ||||
|     } | ||||
|  | ||||
|     override fun maxScore(): Int { | ||||
|         return 10 | ||||
|     } | ||||
|  | ||||
|     override fun formatScore(track: Track): String { | ||||
|         return track.getKitsuScore() | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,6 +1,8 @@ | ||||
| package eu.kanade.tachiyomi.data.track.kitsu | ||||
|  | ||||
| import com.github.salomonbrys.kotson.* | ||||
| import com.google.gson.JsonObject | ||||
| import eu.kanade.tachiyomi.data.database.models.Track | ||||
| import eu.kanade.tachiyomi.data.network.POST | ||||
| import okhttp3.FormBody | ||||
| import okhttp3.OkHttpClient | ||||
| @@ -10,7 +12,171 @@ import retrofit2.converter.gson.GsonConverterFactory | ||||
| import retrofit2.http.* | ||||
| import rx.Observable | ||||
|  | ||||
| interface KitsuApi { | ||||
| class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) { | ||||
|  | ||||
|     private val rest = Retrofit.Builder() | ||||
|             .baseUrl(baseUrl) | ||||
|             .client(client.newBuilder().addInterceptor(interceptor).build()) | ||||
|             .addConverterFactory(GsonConverterFactory.create()) | ||||
|             .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) | ||||
|             .build() | ||||
|             .create(KitsuApi.Rest::class.java) | ||||
|  | ||||
|     fun login(username: String, password: String): Observable<OAuth> { | ||||
|         return Retrofit.Builder() | ||||
|                 .baseUrl(loginUrl) | ||||
|                 .client(client) | ||||
|                 .addConverterFactory(GsonConverterFactory.create()) | ||||
|                 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) | ||||
|                 .build() | ||||
|                 .create(KitsuApi.LoginRest::class.java) | ||||
|                 .requestAccessToken(username, password) | ||||
|     } | ||||
|  | ||||
|     fun getCurrentUser(): Observable<String> { | ||||
|         return rest.getCurrentUser().map { it["data"].array[0]["id"].string } | ||||
|     } | ||||
|  | ||||
|     fun search(query: String): Observable<List<Track>> { | ||||
|         return rest.search(query) | ||||
|                 .map { json -> | ||||
|                     val data = json["data"].array | ||||
|                     data.map { KitsuManga(it.obj).toTrack() } | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     fun findLibManga(userId: String, remoteId: Int): Observable<Track?> { | ||||
|         return rest.findLibManga(userId, remoteId) | ||||
|                 .map { json -> | ||||
|                     val data = json["data"].array | ||||
|                     if (data.size() > 0) { | ||||
|                         KitsuLibManga(data[0].obj, json["included"].array[0].obj).toTrack() | ||||
|                     } else { | ||||
|                         null | ||||
|                     } | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     fun addLibManga(track: Track, userId: String): Observable<Track> { | ||||
|         return Observable.defer { | ||||
|             // @formatter:off | ||||
|             val data = jsonObject( | ||||
|                 "type" to "libraryEntries", | ||||
|                 "attributes" to jsonObject( | ||||
|                     "status" to track.toKitsuStatus(), | ||||
|                     "progress" to track.last_chapter_read | ||||
|                 ), | ||||
|                 "relationships" to jsonObject( | ||||
|                     "user" to jsonObject( | ||||
|                         "data" to jsonObject( | ||||
|                             "id" to userId, | ||||
|                             "type" to "users" | ||||
|                         ) | ||||
|                     ), | ||||
|                     "media" to jsonObject( | ||||
|                         "data" to jsonObject( | ||||
|                             "id" to track.remote_id, | ||||
|                             "type" to "manga" | ||||
|                         ) | ||||
|                     ) | ||||
|                 ) | ||||
|             ) | ||||
|             // @formatter:on | ||||
|  | ||||
|             rest.addLibManga(jsonObject("data" to data)) | ||||
|                     .map { json -> | ||||
|                         track.remote_id = json["data"]["id"].int | ||||
|                         track | ||||
|                     } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun updateLibManga(track: Track): Observable<Track> { | ||||
|         return Observable.defer { | ||||
|             // @formatter:off | ||||
|             val data = jsonObject( | ||||
|                 "type" to "libraryEntries", | ||||
|                 "id" to track.remote_id, | ||||
|                 "attributes" to jsonObject( | ||||
|                     "status" to track.toKitsuStatus(), | ||||
|                     "progress" to track.last_chapter_read, | ||||
|                     "rating" to track.toKitsuScore() | ||||
|                 ) | ||||
|             ) | ||||
|             // @formatter:on | ||||
|  | ||||
|             rest.updateLibManga(track.remote_id, jsonObject("data" to data)) | ||||
|                     .map { track } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun getLibManga(track: Track): Observable<Track> { | ||||
|         return rest.getLibManga(track.remote_id) | ||||
|                 .map { json -> | ||||
|                     val data = json["data"].array | ||||
|                     if (data.size() > 0) { | ||||
|                         val include = json["included"].array[0].obj | ||||
|                         KitsuLibManga(data[0].obj, include).toTrack() | ||||
|                     } else { | ||||
|                         throw Exception("Could not find manga") | ||||
|                     } | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     private interface Rest { | ||||
|  | ||||
|         @GET("users") | ||||
|         fun getCurrentUser( | ||||
|                 @Query("filter[self]", encoded = true) self: Boolean = true | ||||
|         ): Observable<JsonObject> | ||||
|  | ||||
|         @GET("manga") | ||||
|         fun search( | ||||
|                 @Query("filter[text]", encoded = true) query: String | ||||
|         ): Observable<JsonObject> | ||||
|  | ||||
|         @GET("library-entries") | ||||
|         fun getLibManga( | ||||
|                 @Query("filter[id]", encoded = true) remoteId: Int, | ||||
|                 @Query("include") includes: String = "media" | ||||
|         ): Observable<JsonObject> | ||||
|  | ||||
|         @GET("library-entries") | ||||
|         fun findLibManga( | ||||
|                 @Query("filter[user_id]", encoded = true) userId: String, | ||||
|                 @Query("filter[media_id]", encoded = true) remoteId: Int, | ||||
|                 @Query("page[limit]", encoded = true) limit: Int = 10000, | ||||
|                 @Query("include") includes: String = "media" | ||||
|         ): Observable<JsonObject> | ||||
|  | ||||
|         @Headers("Content-Type: application/vnd.api+json") | ||||
|         @POST("library-entries") | ||||
|         fun addLibManga( | ||||
|                 @Body data: JsonObject | ||||
|         ): Observable<JsonObject> | ||||
|  | ||||
|         @Headers("Content-Type: application/vnd.api+json") | ||||
|         @PATCH("library-entries/{id}") | ||||
|         fun updateLibManga( | ||||
|                 @Path("id") remoteId: Int, | ||||
|                 @Body data: JsonObject | ||||
|         ): Observable<JsonObject> | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private interface LoginRest { | ||||
|  | ||||
|         @FormUrlEncoded | ||||
|         @POST("oauth/token") | ||||
|         fun requestAccessToken( | ||||
|                 @Field("username") username: String, | ||||
|                 @Field("password") password: String, | ||||
|                 @Field("grant_type") grantType: String = "password", | ||||
|                 @Field("client_id") client_id: String = clientId, | ||||
|                 @Field("client_secret") client_secret: String = clientSecret | ||||
|         ): Observable<OAuth> | ||||
|  | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         private const val clientId = "dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd" | ||||
| @@ -18,21 +184,6 @@ interface KitsuApi { | ||||
|         private const val baseUrl = "https://kitsu.io/api/edge/" | ||||
|         private const val loginUrl = "https://kitsu.io/api/" | ||||
|  | ||||
|         fun createService(client: OkHttpClient) = Retrofit.Builder() | ||||
|                 .baseUrl(baseUrl) | ||||
|                 .client(client) | ||||
|                 .addConverterFactory(GsonConverterFactory.create()) | ||||
|                 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) | ||||
|                 .build() | ||||
|                 .create(KitsuApi::class.java) | ||||
|  | ||||
|         fun createLoginService(client: OkHttpClient) = Retrofit.Builder() | ||||
|                 .baseUrl(loginUrl) | ||||
|                 .client(client) | ||||
|                 .addConverterFactory(GsonConverterFactory.create()) | ||||
|                 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) | ||||
|                 .build() | ||||
|                 .create(KitsuApi::class.java) | ||||
|  | ||||
|         fun refreshTokenRequest(token: String) = POST("${loginUrl}oauth/token", | ||||
|                 body = FormBody.Builder() | ||||
| @@ -41,53 +192,7 @@ interface KitsuApi { | ||||
|                         .add("client_secret", clientSecret) | ||||
|                         .add("refresh_token", token) | ||||
|                         .build()) | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @FormUrlEncoded | ||||
|     @POST("oauth/token") | ||||
|     fun requestAccessToken( | ||||
|             @Field("username") username: String, | ||||
|             @Field("password") password: String, | ||||
|             @Field("grant_type") grantType: String = "password", | ||||
|             @Field("client_id") client_id: String = clientId, | ||||
|             @Field("client_secret") client_secret: String = clientSecret | ||||
|     ) : Observable<OAuth> | ||||
|  | ||||
|     @GET("users") | ||||
|     fun getCurrentUser( | ||||
|             @Query("filter[self]", encoded = true) self: Boolean = true | ||||
|     ) : Observable<JsonObject> | ||||
|  | ||||
|     @GET("manga") | ||||
|     fun search( | ||||
|             @Query("filter[text]", encoded = true) query: String | ||||
|     ): Observable<JsonObject> | ||||
|  | ||||
|     @GET("library-entries") | ||||
|     fun getLibManga( | ||||
|             @Query("filter[id]", encoded = true) remoteId: Int, | ||||
|             @Query("include") includes: String = "media" | ||||
|     ) : Observable<JsonObject> | ||||
|  | ||||
|     @GET("library-entries") | ||||
|     fun findLibManga( | ||||
|             @Query("filter[user_id]", encoded = true) userId: String, | ||||
|             @Query("filter[media_id]", encoded = true) remoteId: Int, | ||||
|             @Query("page[limit]", encoded = true) limit: Int = 10000, | ||||
|             @Query("include") includes: String = "media" | ||||
|     ) : Observable<JsonObject> | ||||
|  | ||||
|     @Headers("Content-Type: application/vnd.api+json") | ||||
|     @POST("library-entries") | ||||
|     fun addLibManga( | ||||
|             @Body data: JsonObject | ||||
|     ) : Observable<JsonObject> | ||||
|  | ||||
|     @Headers("Content-Type: application/vnd.api+json") | ||||
|     @PATCH("library-entries/{id}") | ||||
|     fun updateLibManga( | ||||
|             @Path("id") remoteId: Int, | ||||
|             @Body data: JsonObject | ||||
|     ) : Observable<JsonObject> | ||||
|  | ||||
| } | ||||
| } | ||||
| @@ -42,3 +42,16 @@ class KitsuLibManga(obj: JsonObject, manga: JsonObject) : KitsuManga(manga) { | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| fun Track.toKitsuStatus() = when (status) { | ||||
|     Kitsu.READING -> "current" | ||||
|     Kitsu.COMPLETED -> "completed" | ||||
|     Kitsu.ON_HOLD -> "on_hold" | ||||
|     Kitsu.DROPPED -> "dropped" | ||||
|     Kitsu.PLAN_TO_READ -> "planned" | ||||
|     else -> throw Exception("Unknown status") | ||||
| } | ||||
|  | ||||
| fun Track.toKitsuScore(): String { | ||||
|     return if (score > 0) (score / 2).toString() else "" | ||||
| } | ||||
|   | ||||
| @@ -62,9 +62,26 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) { | ||||
|  | ||||
|     override fun getLogoColor() = Color.rgb(46, 81, 162) | ||||
|  | ||||
|     override fun maxScore() = 10 | ||||
|     override fun getStatus(status: Int): String = with(context) { | ||||
|         when (status) { | ||||
|             READING -> getString(R.string.reading) | ||||
|             COMPLETED -> getString(R.string.completed) | ||||
|             ON_HOLD -> getString(R.string.on_hold) | ||||
|             DROPPED -> getString(R.string.dropped) | ||||
|             PLAN_TO_READ -> getString(R.string.plan_to_read) | ||||
|             else -> "" | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun formatScore(track: Track): String { | ||||
|     override fun getStatusList(): List<Int> { | ||||
|         return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ) | ||||
|     } | ||||
|  | ||||
|     override fun getScoreList(): List<String> { | ||||
|         return IntRange(0, 10).map(Int::toString) | ||||
|     } | ||||
|  | ||||
|     override fun displayScore(track: Track): String { | ||||
|         return track.score.toInt().toString() | ||||
|     } | ||||
|  | ||||
| @@ -238,21 +255,6 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) { | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     override fun getStatus(status: Int): String = with(context) { | ||||
|         when (status) { | ||||
|             READING -> getString(R.string.reading) | ||||
|             COMPLETED -> getString(R.string.completed) | ||||
|             ON_HOLD -> getString(R.string.on_hold) | ||||
|             DROPPED -> getString(R.string.dropped) | ||||
|             PLAN_TO_READ -> getString(R.string.plan_to_read) | ||||
|             else -> "" | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun getStatusList(): List<Int> { | ||||
|         return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ) | ||||
|     } | ||||
|  | ||||
|     fun createHeaders(username: String, password: String) { | ||||
|         val builder = Headers.Builder() | ||||
|         builder.add("Authorization", Credentials.basic(username, password)) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user