mirror of
https://github.com/mihonapp/mihon.git
synced 2024-12-24 18:08:24 +01:00
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 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,20 +62,43 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
|
||||
return track.toKitsuScore()
|
||||
}
|
||||
|
||||
private fun getUserId(): String {
|
||||
return getPassword()
|
||||
override fun add(track: Track): Observable<Track> {
|
||||
return api.addLibManga(track, getUserId())
|
||||
}
|
||||
|
||||
fun saveToken(oauth: OAuth?) {
|
||||
val json = gson.toJson(oauth)
|
||||
preferences.trackToken(this).set(json)
|
||||
override fun update(track: Track): Observable<Track> {
|
||||
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
||||
track.status = COMPLETED
|
||||
}
|
||||
|
||||
fun restoreToken(): OAuth? {
|
||||
return try {
|
||||
gson.fromJson(preferences.trackToken(this).get(), OAuth::class.java)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,47 +116,20 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
|
||||
interceptor.newAuth(null)
|
||||
}
|
||||
|
||||
override fun search(query: String): Observable<List<Track>> {
|
||||
return api.search(query)
|
||||
private fun getUserId(): String {
|
||||
return getPassword()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
fun saveToken(oauth: OAuth?) {
|
||||
val json = gson.toJson(oauth)
|
||||
preferences.trackToken(this).set(json)
|
||||
}
|
||||
|
||||
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
|
||||
fun restoreToken(): OAuth? {
|
||||
return try {
|
||||
gson.fromJson(preferences.trackToken(this).get(), OAuth::class.java)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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:"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user