mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 22:37:56 +01:00 
			
		
		
		
	Hide API implementation from MAL service. Reorder methods and minor changes
This commit is contained in:
		| @@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.data.track | ||||
| import android.content.Context | ||||
| import eu.kanade.tachiyomi.data.track.anilist.Anilist | ||||
| import eu.kanade.tachiyomi.data.track.kitsu.Kitsu | ||||
| import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList | ||||
| import eu.kanade.tachiyomi.data.track.myanimelist.Myanimelist | ||||
|  | ||||
| class TrackManager(private val context: Context) { | ||||
|  | ||||
| @@ -13,7 +13,7 @@ class TrackManager(private val context: Context) { | ||||
|         const val KITSU = 3 | ||||
|     } | ||||
|  | ||||
|     val myAnimeList = MyAnimeList(context, MYANIMELIST) | ||||
|     val myAnimeList = Myanimelist(context, MYANIMELIST) | ||||
|  | ||||
|     val aniList = Anilist(context, ANILIST) | ||||
|  | ||||
|   | ||||
| @@ -38,12 +38,6 @@ abstract class TrackService(val id: Int) { | ||||
|  | ||||
|     abstract fun displayScore(track: Track): String | ||||
|  | ||||
|     abstract fun login(username: String, password: String): Completable | ||||
|  | ||||
|     open val isLogged: Boolean | ||||
|         get() = !getUsername().isEmpty() && | ||||
|                 !getPassword().isEmpty() | ||||
|  | ||||
|     abstract fun add(track: Track): Observable<Track> | ||||
|  | ||||
|     abstract fun update(track: Track): Observable<Track> | ||||
| @@ -54,17 +48,23 @@ abstract class TrackService(val id: Int) { | ||||
|  | ||||
|     abstract fun refresh(track: Track): Observable<Track> | ||||
|  | ||||
|     fun saveCredentials(username: String, password: String) { | ||||
|         preferences.setTrackCredentials(this, username, password) | ||||
|     } | ||||
|     abstract fun login(username: String, password: String): Completable | ||||
|  | ||||
|     @CallSuper | ||||
|     open fun logout() { | ||||
|         preferences.setTrackCredentials(this, "", "") | ||||
|     } | ||||
|  | ||||
|     open val isLogged: Boolean | ||||
|         get() = !getUsername().isEmpty() && | ||||
|                 !getPassword().isEmpty() | ||||
|  | ||||
|     fun getUsername() = preferences.trackUsername(this) | ||||
|  | ||||
|     fun getPassword() = preferences.trackPassword(this) | ||||
|  | ||||
|     fun saveCredentials(username: String, password: String) { | ||||
|         preferences.setTrackCredentials(this, username, password) | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -93,6 +93,46 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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(track, getUsername()) | ||||
|                 .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 search(query: String): Observable<List<Track>> { | ||||
|         return api.search(query) | ||||
|     } | ||||
|  | ||||
|     override fun refresh(track: Track): Observable<Track> { | ||||
|         return api.getLibManga(track, getUsername()) | ||||
|                 .map { remoteTrack -> | ||||
|                     track.copyPersonalFrom(remoteTrack) | ||||
|                     track.total_chapters = remoteTrack.total_chapters | ||||
|                     track | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     override fun login(username: String, password: String) = login(password) | ||||
|  | ||||
|     fun login(authCode: String): Completable { | ||||
| @@ -116,50 +156,5 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) { | ||||
|         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") | ||||
|                     } | ||||
|                 } | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -23,22 +23,27 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { | ||||
|             .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 addLibManga(track: Track): Observable<Track> { | ||||
|         return rest.addLibManga(track.remote_id, track.last_chapter_read, track.toAnilistStatus()) | ||||
|                 .map { response -> | ||||
|                     response.body().close() | ||||
|                     if (!response.isSuccessful) { | ||||
|                         throw Exception("Could not add manga") | ||||
|                     } | ||||
|                     track | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     fun getCurrentUser(): Observable<Pair<String, Int>> { | ||||
|         return rest.getCurrentUser() | ||||
|                 .map { it["id"].string to it["score_type"].int } | ||||
|     fun updateLibManga(track: Track): Observable<Track> { | ||||
|         return rest.updateLibManga(track.remote_id, track.last_chapter_read, track.toAnilistStatus(), | ||||
|                 track.toAnilistScore()) | ||||
|                 .map { response -> | ||||
|                     response.body().close() | ||||
|                     if (!response.isSuccessful) { | ||||
|                         throw Exception("Could not update manga") | ||||
|                     } | ||||
|                     track | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     fun search(query: String): Observable<List<Track>> { | ||||
| @@ -55,27 +60,35 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     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?> { | ||||
|     fun findLibManga(track: Track, username: String) : Observable<Track?> { | ||||
|         // TODO avoid getting the entire list | ||||
|         return getList(username) | ||||
|                 .map { list -> list.find { it.remote_id == track.remote_id } } | ||||
|     } | ||||
|  | ||||
|     fun getLibManga(track: Track, username: String): Observable<Track> { | ||||
|         return findLibManga(track, username) | ||||
|                 .map { it ?: throw Exception("Could not find manga") } | ||||
|     } | ||||
|  | ||||
|     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 } | ||||
|     } | ||||
|  | ||||
|     private fun restBuilder() = Retrofit.Builder() | ||||
|             .baseUrl(baseUrl) | ||||
|             .addConverterFactory(GsonConverterFactory.create()) | ||||
|             .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) | ||||
|  | ||||
|     private interface Rest { | ||||
|  | ||||
|         @FormUrlEncoded | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| package eu.kanade.tachiyomi.data.track.anilist | ||||
|  | ||||
| import com.google.gson.Gson | ||||
| import eu.kanade.tachiyomi.data.track.anilist.OAuth | ||||
| import okhttp3.Interceptor | ||||
| import okhttp3.Response | ||||
|  | ||||
|   | ||||
| @@ -62,6 +62,60 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) { | ||||
|         return track.toKitsuScore() | ||||
|     } | ||||
|  | ||||
|     override fun add(track: Track): Observable<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 | ||||
|         } | ||||
|  | ||||
|         return api.updateLibManga(track) | ||||
|     } | ||||
|  | ||||
|     override fun bind(track: Track): Observable<Track> { | ||||
|         return api.findLibManga(track, getUserId()) | ||||
|                 .flatMap { remoteTrack -> | ||||
|                     if (remoteTrack != null) { | ||||
|                         track.copyPersonalFrom(remoteTrack) | ||||
|                         track.remote_id = remoteTrack.remote_id | ||||
|                         update(track) | ||||
|                     } else { | ||||
|                         track.score = DEFAULT_SCORE | ||||
|                         track.status = DEFAULT_STATUS | ||||
|                         add(track) | ||||
|                     } | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     override fun search(query: String): Observable<List<Track>> { | ||||
|         return api.search(query) | ||||
|     } | ||||
|  | ||||
|     override fun refresh(track: Track): Observable<Track> { | ||||
|         return api.getLibManga(track) | ||||
|                 .map { remoteTrack -> | ||||
|                     track.copyPersonalFrom(remoteTrack) | ||||
|                     track.total_chapters = remoteTrack.total_chapters | ||||
|                     track | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     override fun login(username: String, password: String): Completable { | ||||
|         return api.login(username, password) | ||||
|                 .doOnNext { interceptor.newAuth(it) } | ||||
|                 .flatMap { api.getCurrentUser() } | ||||
|                 .doOnNext { userId -> saveCredentials(username, userId) } | ||||
|                 .doOnError { logout() } | ||||
|                 .toCompletable() | ||||
|     } | ||||
|  | ||||
|     override fun logout() { | ||||
|         super.logout() | ||||
|         interceptor.newAuth(null) | ||||
|     } | ||||
|  | ||||
|     private fun getUserId(): String { | ||||
|         return getPassword() | ||||
|     } | ||||
| @@ -79,62 +133,4 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun login(username: String, password: String): Completable { | ||||
|         return api.login(username, password) | ||||
|                 .doOnNext { interceptor.newAuth(it) } | ||||
|                 .flatMap { api.getCurrentUser() } | ||||
|                 .doOnNext { userId -> saveCredentials(username, userId) } | ||||
|                 .doOnError { logout() } | ||||
|                 .toCompletable() | ||||
|     } | ||||
|  | ||||
|     override fun logout() { | ||||
|         super.logout() | ||||
|         interceptor.newAuth(null) | ||||
|     } | ||||
|  | ||||
|     override fun search(query: String): Observable<List<Track>> { | ||||
|         return api.search(query) | ||||
|     } | ||||
|  | ||||
|     override fun bind(track: Track): Observable<Track> { | ||||
|         return find(track) | ||||
|                 .flatMap { remoteTrack -> | ||||
|                     if (remoteTrack != null) { | ||||
|                         track.copyPersonalFrom(remoteTrack) | ||||
|                         track.remote_id = remoteTrack.remote_id | ||||
|                         update(track) | ||||
|                     } else { | ||||
|                         track.score = DEFAULT_SCORE | ||||
|                         track.status = DEFAULT_STATUS | ||||
|                         add(track) | ||||
|                     } | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     private fun find(track: Track): Observable<Track?> { | ||||
|         return api.findLibManga(getUserId(), track.remote_id) | ||||
|     } | ||||
|  | ||||
|     override fun add(track: Track): Observable<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 | ||||
|         } | ||||
|  | ||||
|         return api.updateLibManga(track) | ||||
|     } | ||||
|  | ||||
|     override fun refresh(track: Track): Observable<Track> { | ||||
|         return api.getLibManga(track) | ||||
|                 .map { remoteTrack -> | ||||
|                     track.copyPersonalFrom(remoteTrack) | ||||
|                     track.total_chapters = remoteTrack.total_chapters | ||||
|                     track | ||||
|                 } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -22,41 +22,6 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) | ||||
|             .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 | ||||
| @@ -110,6 +75,26 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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(track: Track, userId: String): Observable<Track?> { | ||||
|         return rest.findLibManga(track.remote_id, userId) | ||||
|                 .map { json -> | ||||
|                     val data = json["data"].array | ||||
|                     if (data.size() > 0) { | ||||
|                         KitsuLibManga(data[0].obj, json["included"].array[0].obj).toTrack() | ||||
|                     } else { | ||||
|                         null | ||||
|                     } | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     fun getLibManga(track: Track): Observable<Track> { | ||||
|         return rest.getLibManga(track.remote_id) | ||||
|                 .map { json -> | ||||
| @@ -123,32 +108,23 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     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 } | ||||
|     } | ||||
|  | ||||
|     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( | ||||
| @@ -162,6 +138,30 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) | ||||
|                 @Body data: JsonObject | ||||
|         ): Observable<JsonObject> | ||||
|  | ||||
|         @GET("manga") | ||||
|         fun search( | ||||
|                 @Query("filter[text]", encoded = true) query: String | ||||
|         ): Observable<JsonObject> | ||||
|  | ||||
|         @GET("library-entries") | ||||
|         fun findLibManga( | ||||
|                 @Query("filter[media_id]", encoded = true) remoteId: Int, | ||||
|                 @Query("filter[user_id]", encoded = true) userId: String, | ||||
|                 @Query("page[limit]", encoded = true) limit: Int = 10000, | ||||
|                 @Query("include") includes: String = "media" | ||||
|         ): Observable<JsonObject> | ||||
|  | ||||
|         @GET("library-entries") | ||||
|         fun getLibManga( | ||||
|                 @Query("filter[id]", encoded = true) remoteId: Int, | ||||
|                 @Query("include") includes: String = "media" | ||||
|         ): Observable<JsonObject> | ||||
|  | ||||
|         @GET("users") | ||||
|         fun getCurrentUser( | ||||
|                 @Query("filter[self]", encoded = true) self: Boolean = true | ||||
|         ): Observable<JsonObject> | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private interface LoginRest { | ||||
|   | ||||
| @@ -2,37 +2,15 @@ package eu.kanade.tachiyomi.data.track.myanimelist | ||||
|  | ||||
| import android.content.Context | ||||
| import android.graphics.Color | ||||
| import android.net.Uri | ||||
| import android.util.Xml | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.database.models.Track | ||||
| import eu.kanade.tachiyomi.data.network.GET | ||||
| import eu.kanade.tachiyomi.data.network.POST | ||||
| import eu.kanade.tachiyomi.data.network.asObservable | ||||
| import eu.kanade.tachiyomi.data.track.TrackService | ||||
| import eu.kanade.tachiyomi.util.selectInt | ||||
| import eu.kanade.tachiyomi.util.selectText | ||||
| import okhttp3.Credentials | ||||
| import okhttp3.FormBody | ||||
| import okhttp3.Headers | ||||
| import okhttp3.RequestBody | ||||
| import org.jsoup.Jsoup | ||||
| import org.xmlpull.v1.XmlSerializer | ||||
| import rx.Completable | ||||
| import rx.Observable | ||||
| import java.io.StringWriter | ||||
|  | ||||
| class MyAnimeList(private val context: Context, id: Int) : TrackService(id) { | ||||
|  | ||||
|     private lateinit var headers: Headers | ||||
| class Myanimelist(private val context: Context, id: Int) : TrackService(id) { | ||||
|  | ||||
|     companion object { | ||||
|         const val BASE_URL = "https://myanimelist.net" | ||||
|  | ||||
|         private val ENTRY_TAG = "entry" | ||||
|         private val CHAPTER_TAG = "chapter" | ||||
|         private val SCORE_TAG = "score" | ||||
|         private val STATUS_TAG = "status" | ||||
|  | ||||
|         const val READING = 1 | ||||
|         const val COMPLETED = 2 | ||||
| @@ -42,18 +20,9 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) { | ||||
|  | ||||
|         const val DEFAULT_STATUS = READING | ||||
|         const val DEFAULT_SCORE = 0 | ||||
|  | ||||
|         const val PREFIX_MY = "my:" | ||||
|     } | ||||
|  | ||||
|     init { | ||||
|         val username = getUsername() | ||||
|         val password = getPassword() | ||||
|  | ||||
|         if (!username.isEmpty() && !password.isEmpty()) { | ||||
|             createHeaders(username, password) | ||||
|         } | ||||
|     } | ||||
|     private val api by lazy { MyanimelistApi(client, getUsername(), getPassword()) } | ||||
|  | ||||
|     override val name: String | ||||
|         get() = "MyAnimeList" | ||||
| @@ -85,164 +54,21 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) { | ||||
|         return track.score.toInt().toString() | ||||
|     } | ||||
|  | ||||
|     fun getLoginUrl() = Uri.parse(BASE_URL).buildUpon() | ||||
|             .appendEncodedPath("api/account/verify_credentials.xml") | ||||
|             .toString() | ||||
|  | ||||
|     fun getSearchUrl(query: String) = Uri.parse(BASE_URL).buildUpon() | ||||
|             .appendEncodedPath("api/manga/search.xml") | ||||
|             .appendQueryParameter("q", query) | ||||
|             .toString() | ||||
|  | ||||
|     fun getListUrl(username: String) = Uri.parse(BASE_URL).buildUpon() | ||||
|             .appendPath("malappinfo.php") | ||||
|             .appendQueryParameter("u", username) | ||||
|             .appendQueryParameter("status", "all") | ||||
|             .appendQueryParameter("type", "manga") | ||||
|             .toString() | ||||
|  | ||||
|     fun getUpdateUrl(track: Track) = Uri.parse(BASE_URL).buildUpon() | ||||
|             .appendEncodedPath("api/mangalist/update") | ||||
|             .appendPath("${track.remote_id}.xml") | ||||
|             .toString() | ||||
|  | ||||
|     fun getAddUrl(track: Track) = Uri.parse(BASE_URL).buildUpon() | ||||
|             .appendEncodedPath("api/mangalist/add") | ||||
|             .appendPath("${track.remote_id}.xml") | ||||
|             .toString() | ||||
|  | ||||
|     override fun login(username: String, password: String): Completable { | ||||
|         createHeaders(username, password) | ||||
|         return client.newCall(GET(getLoginUrl(), headers)) | ||||
|                 .asObservable() | ||||
|                 .doOnNext { it.close() } | ||||
|                 .doOnNext { if (it.code() != 200) throw Exception("Login error") } | ||||
|                 .doOnNext { saveCredentials(username, password) } | ||||
|                 .doOnError { logout() } | ||||
|                 .toCompletable() | ||||
|     } | ||||
|  | ||||
|     override fun search(query: String): Observable<List<Track>> { | ||||
|         return if (query.startsWith(PREFIX_MY)) { | ||||
|             val realQuery = query.substring(PREFIX_MY.length).toLowerCase().trim() | ||||
|             getList() | ||||
|                     .flatMap { Observable.from(it) } | ||||
|                     .filter { realQuery in it.title.toLowerCase() } | ||||
|                     .toList() | ||||
|         } else { | ||||
|             client.newCall(GET(getSearchUrl(query), headers)) | ||||
|                     .asObservable() | ||||
|                     .map { Jsoup.parse(it.body().string()) } | ||||
|                     .flatMap { Observable.from(it.select("entry")) } | ||||
|                     .filter { it.select("type").text() != "Novel" } | ||||
|                     .map { | ||||
|                         Track.create(id).apply { | ||||
|                             title = it.selectText("title")!! | ||||
|                             remote_id = it.selectInt("id") | ||||
|                             total_chapters = it.selectInt("chapters") | ||||
|                         } | ||||
|                     } | ||||
|                     .toList() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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") | ||||
|                     } | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     // MAL doesn't support score with decimals | ||||
|     fun getList(): Observable<List<Track>> { | ||||
|         return networkService.forceCacheClient | ||||
|                 .newCall(GET(getListUrl(getUsername()), headers)) | ||||
|                 .asObservable() | ||||
|                 .map { Jsoup.parse(it.body().string()) } | ||||
|                 .flatMap { Observable.from(it.select("manga")) } | ||||
|                 .map { | ||||
|                     Track.create(id).apply { | ||||
|                         title = it.selectText("series_title")!! | ||||
|                         remote_id = it.selectInt("series_mangadb_id") | ||||
|                         last_chapter_read = it.selectInt("my_read_chapters") | ||||
|                         status = it.selectInt("my_status") | ||||
|                         score = it.selectInt("my_score").toFloat() | ||||
|                         total_chapters = it.selectInt("series_chapters") | ||||
|                     } | ||||
|                 } | ||||
|                 .toList() | ||||
|     override fun add(track: Track): Observable<Track> { | ||||
|         return api.addLibManga(track) | ||||
|     } | ||||
|  | ||||
|     override fun update(track: Track): Observable<Track> { | ||||
|         return Observable.defer { | ||||
|             if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) { | ||||
|                 track.status = COMPLETED | ||||
|             } | ||||
|             client.newCall(POST(getUpdateUrl(track), headers, getMangaPostPayload(track))) | ||||
|                     .asObservable() | ||||
|                     .doOnNext { it.close() } | ||||
|                     .doOnNext { if (!it.isSuccessful) throw Exception("Could not update manga") } | ||||
|                     .map { track } | ||||
|         if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) { | ||||
|             track.status = COMPLETED | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     override fun add(track: Track): Observable<Track> { | ||||
|         return Observable.defer { | ||||
|             client.newCall(POST(getAddUrl(track), headers, getMangaPostPayload(track))) | ||||
|                     .asObservable() | ||||
|                     .doOnNext { it.close() } | ||||
|                     .doOnNext { if (!it.isSuccessful) throw Exception("Could not add manga") } | ||||
|                     .map { track } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun getMangaPostPayload(track: Track): RequestBody { | ||||
|         val xml = Xml.newSerializer() | ||||
|         val writer = StringWriter() | ||||
|  | ||||
|         with(xml) { | ||||
|             setOutput(writer) | ||||
|             startDocument("UTF-8", false) | ||||
|             startTag("", ENTRY_TAG) | ||||
|  | ||||
|             // Last chapter read | ||||
|             if (track.last_chapter_read != 0) { | ||||
|                 inTag(CHAPTER_TAG, track.last_chapter_read.toString()) | ||||
|             } | ||||
|             // Manga status in the list | ||||
|             inTag(STATUS_TAG, track.status.toString()) | ||||
|  | ||||
|             // Manga score | ||||
|             inTag(SCORE_TAG, track.score.toString()) | ||||
|  | ||||
|             endTag("", ENTRY_TAG) | ||||
|             endDocument() | ||||
|         } | ||||
|  | ||||
|         val form = FormBody.Builder() | ||||
|         form.add("data", writer.toString()) | ||||
|         return form.build() | ||||
|     } | ||||
|  | ||||
|     fun XmlSerializer.inTag(tag: String, body: String, namespace: String = "") { | ||||
|         startTag(namespace, tag) | ||||
|         text(body) | ||||
|         endTag(namespace, tag) | ||||
|         return api.updateLibManga(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 } | ||||
|         return api.findLibManga(track, getUsername()) | ||||
|                 .flatMap { remoteTrack -> | ||||
|                     if (remoteTrack != null) { | ||||
|                         track.copyPersonalFrom(remoteTrack) | ||||
|                         update(track) | ||||
| @@ -255,11 +81,24 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) { | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     fun createHeaders(username: String, password: String) { | ||||
|         val builder = Headers.Builder() | ||||
|         builder.add("Authorization", Credentials.basic(username, password)) | ||||
|         builder.add("User-Agent", "api-indiv-9F93C52A963974CF674325391990191C") | ||||
|         headers = builder.build() | ||||
|     override fun search(query: String): Observable<List<Track>> { | ||||
|         return api.search(query, getUsername()) | ||||
|     } | ||||
|  | ||||
|     override fun refresh(track: Track): Observable<Track> { | ||||
|         return api.getLibManga(track, getUsername()) | ||||
|                 .map { remoteTrack -> | ||||
|                     track.copyPersonalFrom(remoteTrack) | ||||
|                     track.total_chapters = remoteTrack.total_chapters | ||||
|                     track | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     override fun login(username: String, password: String): Completable { | ||||
|         return api.login(username, password) | ||||
|                 .doOnNext { saveCredentials(username, password) } | ||||
|                 .doOnError { logout() } | ||||
|                 .toCompletable() | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,190 @@ | ||||
| package eu.kanade.tachiyomi.data.track.myanimelist | ||||
|  | ||||
| import android.net.Uri | ||||
| import android.util.Xml | ||||
| import eu.kanade.tachiyomi.data.database.models.Track | ||||
| import eu.kanade.tachiyomi.data.network.GET | ||||
| import eu.kanade.tachiyomi.data.network.POST | ||||
| import eu.kanade.tachiyomi.data.network.asObservable | ||||
| import eu.kanade.tachiyomi.data.track.TrackManager | ||||
| import eu.kanade.tachiyomi.util.selectInt | ||||
| import eu.kanade.tachiyomi.util.selectText | ||||
| import okhttp3.* | ||||
| import org.jsoup.Jsoup | ||||
| import org.xmlpull.v1.XmlSerializer | ||||
| import rx.Observable | ||||
| import java.io.StringWriter | ||||
|  | ||||
| class MyanimelistApi(private val client: OkHttpClient, username: String, password: String) { | ||||
|  | ||||
|     private var headers = createHeaders(username, password) | ||||
|  | ||||
|     fun addLibManga(track: Track): Observable<Track> { | ||||
|         return Observable.defer { | ||||
|             client.newCall(POST(getAddUrl(track), headers, getMangaPostPayload(track))) | ||||
|                     .asObservable() | ||||
|                     .map { response -> | ||||
|                         response.body().close() | ||||
|                         if (!response.isSuccessful) { | ||||
|                             throw Exception("Could not add manga") | ||||
|                         } | ||||
|                         track | ||||
|                     } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun updateLibManga(track: Track): Observable<Track> { | ||||
|         return Observable.defer { | ||||
|             client.newCall(POST(getUpdateUrl(track), headers, getMangaPostPayload(track))) | ||||
|                     .asObservable() | ||||
|                     .map { response -> | ||||
|                         response.body().close() | ||||
|                         if (!response.isSuccessful) { | ||||
|                             throw Exception("Could not update manga") | ||||
|                         } | ||||
|                         track | ||||
|                     } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun search(query: String, username: String): Observable<List<Track>> { | ||||
|         return if (query.startsWith(PREFIX_MY)) { | ||||
|             val realQuery = query.substring(PREFIX_MY.length).toLowerCase().trim() | ||||
|             getList(username) | ||||
|                     .flatMap { Observable.from(it) } | ||||
|                     .filter { realQuery in it.title.toLowerCase() } | ||||
|                     .toList() | ||||
|         } else { | ||||
|             client.newCall(GET(getSearchUrl(query), headers)) | ||||
|                     .asObservable() | ||||
|                     .map { Jsoup.parse(it.body().string()) } | ||||
|                     .flatMap { Observable.from(it.select("entry")) } | ||||
|                     .filter { it.select("type").text() != "Novel" } | ||||
|                     .map { | ||||
|                         Track.create(TrackManager.MYANIMELIST).apply { | ||||
|                             title = it.selectText("title")!! | ||||
|                             remote_id = it.selectInt("id") | ||||
|                             total_chapters = it.selectInt("chapters") | ||||
|                         } | ||||
|                     } | ||||
|                     .toList() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun getList(username: String): Observable<List<Track>> { | ||||
|         return client | ||||
|                 .newCall(GET(getListUrl(username), headers)) | ||||
|                 .asObservable() | ||||
|                 .map { Jsoup.parse(it.body().string()) } | ||||
|                 .flatMap { Observable.from(it.select("manga")) } | ||||
|                 .map { | ||||
|                     Track.create(TrackManager.MYANIMELIST).apply { | ||||
|                         title = it.selectText("series_title")!! | ||||
|                         remote_id = it.selectInt("series_mangadb_id") | ||||
|                         last_chapter_read = it.selectInt("my_read_chapters") | ||||
|                         status = it.selectInt("my_status") | ||||
|                         score = it.selectInt("my_score").toFloat() | ||||
|                         total_chapters = it.selectInt("series_chapters") | ||||
|                     } | ||||
|                 } | ||||
|                 .toList() | ||||
|     } | ||||
|  | ||||
|     fun findLibManga(track: Track, username: String): Observable<Track?> { | ||||
|         return getList(username) | ||||
|                 .map { list -> list.find { it.remote_id == track.remote_id } } | ||||
|     } | ||||
|  | ||||
|     fun getLibManga(track: Track, username: String): Observable<Track> { | ||||
|         return findLibManga(track, username) | ||||
|                 .map { it ?: throw Exception("Could not find manga") } | ||||
|     } | ||||
|  | ||||
|     fun login(username: String, password: String): Observable<Response> { | ||||
|         headers = createHeaders(username, password) | ||||
|         return client.newCall(GET(getLoginUrl(), headers)) | ||||
|                 .asObservable() | ||||
|                 .doOnNext { response -> | ||||
|                     response.close() | ||||
|                     if (response.code() != 200) throw Exception("Login error") | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     private fun getMangaPostPayload(track: Track): RequestBody { | ||||
|         val xml = Xml.newSerializer() | ||||
|         val writer = StringWriter() | ||||
|  | ||||
|         with(xml) { | ||||
|             setOutput(writer) | ||||
|             startDocument("UTF-8", false) | ||||
|             startTag("", ENTRY_TAG) | ||||
|  | ||||
|             // Last chapter read | ||||
|             if (track.last_chapter_read != 0) { | ||||
|                 inTag(CHAPTER_TAG, track.last_chapter_read.toString()) | ||||
|             } | ||||
|             // Manga status in the list | ||||
|             inTag(STATUS_TAG, track.status.toString()) | ||||
|  | ||||
|             // Manga score | ||||
|             inTag(SCORE_TAG, track.score.toString()) | ||||
|  | ||||
|             endTag("", ENTRY_TAG) | ||||
|             endDocument() | ||||
|         } | ||||
|  | ||||
|         val form = FormBody.Builder() | ||||
|         form.add("data", writer.toString()) | ||||
|         return form.build() | ||||
|     } | ||||
|  | ||||
|     fun XmlSerializer.inTag(tag: String, body: String, namespace: String = "") { | ||||
|         startTag(namespace, tag) | ||||
|         text(body) | ||||
|         endTag(namespace, tag) | ||||
|     } | ||||
|  | ||||
|     fun getLoginUrl() = Uri.parse(baseUrl).buildUpon() | ||||
|             .appendEncodedPath("api/account/verify_credentials.xml") | ||||
|             .toString() | ||||
|  | ||||
|     fun getSearchUrl(query: String) = Uri.parse(baseUrl).buildUpon() | ||||
|             .appendEncodedPath("api/manga/search.xml") | ||||
|             .appendQueryParameter("q", query) | ||||
|             .toString() | ||||
|  | ||||
|     fun getListUrl(username: String) = Uri.parse(baseUrl).buildUpon() | ||||
|             .appendPath("malappinfo.php") | ||||
|             .appendQueryParameter("u", username) | ||||
|             .appendQueryParameter("status", "all") | ||||
|             .appendQueryParameter("type", "manga") | ||||
|             .toString() | ||||
|  | ||||
|     fun getUpdateUrl(track: Track) = Uri.parse(baseUrl).buildUpon() | ||||
|             .appendEncodedPath("api/mangalist/update") | ||||
|             .appendPath("${track.remote_id}.xml") | ||||
|             .toString() | ||||
|  | ||||
|     fun getAddUrl(track: Track) = Uri.parse(baseUrl).buildUpon() | ||||
|             .appendEncodedPath("api/mangalist/add") | ||||
|             .appendPath("${track.remote_id}.xml") | ||||
|             .toString() | ||||
|  | ||||
|     fun createHeaders(username: String, password: String): Headers { | ||||
|         return Headers.Builder() | ||||
|                 .add("Authorization", Credentials.basic(username, password)) | ||||
|                 .add("User-Agent", "api-indiv-9F93C52A963974CF674325391990191C") | ||||
|                 .build() | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         const val baseUrl = "https://myanimelist.net" | ||||
|  | ||||
|         private val ENTRY_TAG = "entry" | ||||
|         private val CHAPTER_TAG = "chapter" | ||||
|         private val SCORE_TAG = "score" | ||||
|         private val STATUS_TAG = "status" | ||||
|  | ||||
|         const val PREFIX_MY = "my:" | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user