Hide API implementation from MAL service. Reorder methods and minor changes
This commit is contained in:
parent
ba428c401d
commit
725ceab00b
@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.data.track
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||||
import eu.kanade.tachiyomi.data.track.kitsu.Kitsu
|
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) {
|
class TrackManager(private val context: Context) {
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ class TrackManager(private val context: Context) {
|
|||||||
const val KITSU = 3
|
const val KITSU = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
val myAnimeList = MyAnimeList(context, MYANIMELIST)
|
val myAnimeList = Myanimelist(context, MYANIMELIST)
|
||||||
|
|
||||||
val aniList = Anilist(context, ANILIST)
|
val aniList = Anilist(context, ANILIST)
|
||||||
|
|
||||||
|
@ -38,12 +38,6 @@ abstract class TrackService(val id: Int) {
|
|||||||
|
|
||||||
abstract fun displayScore(track: Track): String
|
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 add(track: Track): Observable<Track>
|
||||||
|
|
||||||
abstract fun update(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>
|
abstract fun refresh(track: Track): Observable<Track>
|
||||||
|
|
||||||
fun saveCredentials(username: String, password: String) {
|
abstract fun login(username: String, password: String): Completable
|
||||||
preferences.setTrackCredentials(this, username, password)
|
|
||||||
}
|
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
open fun logout() {
|
open fun logout() {
|
||||||
preferences.setTrackCredentials(this, "", "")
|
preferences.setTrackCredentials(this, "", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open val isLogged: Boolean
|
||||||
|
get() = !getUsername().isEmpty() &&
|
||||||
|
!getPassword().isEmpty()
|
||||||
|
|
||||||
fun getUsername() = preferences.trackUsername(this)
|
fun getUsername() = preferences.trackUsername(this)
|
||||||
|
|
||||||
fun getPassword() = preferences.trackPassword(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)
|
override fun login(username: String, password: String) = login(password)
|
||||||
|
|
||||||
fun login(authCode: String): Completable {
|
fun login(authCode: String): Completable {
|
||||||
@ -116,50 +156,5 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
interceptor.setAuth(null)
|
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()
|
.build()
|
||||||
.create(Rest::class.java)
|
.create(Rest::class.java)
|
||||||
|
|
||||||
private fun restBuilder() = Retrofit.Builder()
|
fun addLibManga(track: Track): Observable<Track> {
|
||||||
.baseUrl(baseUrl)
|
return rest.addLibManga(track.remote_id, track.last_chapter_read, track.toAnilistStatus())
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
.map { response ->
|
||||||
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
|
response.body().close()
|
||||||
|
if (!response.isSuccessful) {
|
||||||
fun login(authCode: String): Observable<OAuth> {
|
throw Exception("Could not add manga")
|
||||||
return restBuilder()
|
}
|
||||||
.client(client)
|
track
|
||||||
.build()
|
}
|
||||||
.create(Rest::class.java)
|
|
||||||
.requestAccessToken(authCode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCurrentUser(): Observable<Pair<String, Int>> {
|
fun updateLibManga(track: Track): Observable<Track> {
|
||||||
return rest.getCurrentUser()
|
return rest.updateLibManga(track.remote_id, track.last_chapter_read, track.toAnilistStatus(),
|
||||||
.map { it["id"].string to it["score_type"].int }
|
track.toAnilistScore())
|
||||||
|
.map { response ->
|
||||||
|
response.body().close()
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
throw Exception("Could not update manga")
|
||||||
|
}
|
||||||
|
track
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun search(query: String): Observable<List<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> {
|
fun findLibManga(track: Track, username: String) : 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
|
// TODO avoid getting the entire list
|
||||||
return getList(username)
|
return getList(username)
|
||||||
.map { list -> list.find { it.remote_id == track.remote_id } }
|
.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 {
|
private interface Rest {
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.anilist
|
package eu.kanade.tachiyomi.data.track.anilist
|
||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.OAuth
|
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
|
||||||
|
@ -62,20 +62,43 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
return track.toKitsuScore()
|
return track.toKitsuScore()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getUserId(): String {
|
override fun add(track: Track): Observable<Track> {
|
||||||
return getPassword()
|
return api.addLibManga(track, getUserId())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveToken(oauth: OAuth?) {
|
override fun update(track: Track): Observable<Track> {
|
||||||
val json = gson.toJson(oauth)
|
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
||||||
preferences.trackToken(this).set(json)
|
track.status = COMPLETED
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restoreToken(): OAuth? {
|
return api.updateLibManga(track)
|
||||||
return try {
|
}
|
||||||
gson.fromJson(preferences.trackToken(this).get(), OAuth::class.java)
|
|
||||||
} catch (e: Exception) {
|
override fun bind(track: Track): Observable<Track> {
|
||||||
null
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,47 +116,20 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
interceptor.newAuth(null)
|
interceptor.newAuth(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun search(query: String): Observable<List<Track>> {
|
private fun getUserId(): String {
|
||||||
return api.search(query)
|
return getPassword()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bind(track: Track): Observable<Track> {
|
fun saveToken(oauth: OAuth?) {
|
||||||
return find(track)
|
val json = gson.toJson(oauth)
|
||||||
.flatMap { remoteTrack ->
|
preferences.trackToken(this).set(json)
|
||||||
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?> {
|
fun restoreToken(): OAuth? {
|
||||||
return api.findLibManga(getUserId(), track.remote_id)
|
return try {
|
||||||
}
|
gson.fromJson(preferences.trackToken(this).get(), OAuth::class.java)
|
||||||
|
} catch (e: Exception) {
|
||||||
override fun add(track: Track): Observable<Track> {
|
null
|
||||||
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()
|
.build()
|
||||||
.create(KitsuApi.Rest::class.java)
|
.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> {
|
fun addLibManga(track: Track, userId: String): Observable<Track> {
|
||||||
return Observable.defer {
|
return Observable.defer {
|
||||||
// @formatter:off
|
// @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> {
|
fun getLibManga(track: Track): Observable<Track> {
|
||||||
return rest.getLibManga(track.remote_id)
|
return rest.getLibManga(track.remote_id)
|
||||||
.map { json ->
|
.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 {
|
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")
|
@Headers("Content-Type: application/vnd.api+json")
|
||||||
@POST("library-entries")
|
@POST("library-entries")
|
||||||
fun addLibManga(
|
fun addLibManga(
|
||||||
@ -162,6 +138,30 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
|
|||||||
@Body data: JsonObject
|
@Body data: JsonObject
|
||||||
): Observable<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 {
|
private interface LoginRest {
|
||||||
|
@ -2,37 +2,15 @@ package eu.kanade.tachiyomi.data.track.myanimelist
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.net.Uri
|
|
||||||
import android.util.Xml
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
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.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.Completable
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import java.io.StringWriter
|
|
||||||
|
|
||||||
class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
|
class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
|
||||||
|
|
||||||
private lateinit var headers: Headers
|
|
||||||
|
|
||||||
companion object {
|
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 READING = 1
|
||||||
const val COMPLETED = 2
|
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_STATUS = READING
|
||||||
const val DEFAULT_SCORE = 0
|
const val DEFAULT_SCORE = 0
|
||||||
|
|
||||||
const val PREFIX_MY = "my:"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
private val api by lazy { MyanimelistApi(client, getUsername(), getPassword()) }
|
||||||
val username = getUsername()
|
|
||||||
val password = getPassword()
|
|
||||||
|
|
||||||
if (!username.isEmpty() && !password.isEmpty()) {
|
|
||||||
createHeaders(username, password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = "MyAnimeList"
|
get() = "MyAnimeList"
|
||||||
@ -85,164 +54,21 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
return track.score.toInt().toString()
|
return track.score.toInt().toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLoginUrl() = Uri.parse(BASE_URL).buildUpon()
|
override fun add(track: Track): Observable<Track> {
|
||||||
.appendEncodedPath("api/account/verify_credentials.xml")
|
return api.addLibManga(track)
|
||||||
.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 update(track: Track): Observable<Track> {
|
override fun update(track: Track): Observable<Track> {
|
||||||
return Observable.defer {
|
|
||||||
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
||||||
track.status = COMPLETED
|
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 }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
return api.updateLibManga(track)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bind(track: Track): Observable<Track> {
|
override fun bind(track: Track): Observable<Track> {
|
||||||
return getList()
|
return api.findLibManga(track, getUsername())
|
||||||
.flatMap { userlist ->
|
.flatMap { remoteTrack ->
|
||||||
track.sync_id = id
|
|
||||||
val remoteTrack = userlist.find { it.remote_id == track.remote_id }
|
|
||||||
if (remoteTrack != null) {
|
if (remoteTrack != null) {
|
||||||
track.copyPersonalFrom(remoteTrack)
|
track.copyPersonalFrom(remoteTrack)
|
||||||
update(track)
|
update(track)
|
||||||
@ -255,11 +81,24 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createHeaders(username: String, password: String) {
|
override fun search(query: String): Observable<List<Track>> {
|
||||||
val builder = Headers.Builder()
|
return api.search(query, getUsername())
|
||||||
builder.add("Authorization", Credentials.basic(username, password))
|
}
|
||||||
builder.add("User-Agent", "api-indiv-9F93C52A963974CF674325391990191C")
|
|
||||||
headers = builder.build()
|
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:"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user