Add MangaUpdates as a tracker (#7170)

* Add MangaUpdates as a tracker

- jobobby04 co-authored for suggestion in BackupTracking.kt

Co-authored-by: jobobby04 <jobobby04@users.noreply.github.com>

* Changes from code review

Co-authored-by: arkon <arkon@users.noreply.github.com>

Co-authored-by: jobobby04 <jobobby04@users.noreply.github.com>
Co-authored-by: arkon <arkon@users.noreply.github.com>
This commit is contained in:
Andreas 2022-05-25 00:00:33 +02:00 committed by GitHub
parent 9b0d85bf6c
commit 0c631a4990
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 513 additions and 24 deletions

View file

@ -12,7 +12,7 @@ data class BackupTracking(
@ProtoNumber(1) var syncId: Int,
// LibraryId is not null in 1.x
@ProtoNumber(2) var libraryId: Long,
@ProtoNumber(3) var mediaId: Int = 0,
@Deprecated("Use mediaId instead", level = DeprecationLevel.WARNING) @ProtoNumber(3) var mediaIdInt: Int = 0,
// trackingUrl is called mediaUrl in 1.x
@ProtoNumber(4) var trackingUrl: String = "",
@ProtoNumber(5) var title: String = "",
@ -25,11 +25,17 @@ data class BackupTracking(
@ProtoNumber(10) var startedReadingDate: Long = 0,
// finishedReadingDate is called endReadTime in 1.x
@ProtoNumber(11) var finishedReadingDate: Long = 0,
@ProtoNumber(100) var mediaId: Long = 0,
) {
fun getTrackingImpl(): TrackImpl {
return TrackImpl().apply {
sync_id = this@BackupTracking.syncId
media_id = this@BackupTracking.mediaId
media_id = if (this@BackupTracking.mediaIdInt != 0) {
this@BackupTracking.mediaIdInt.toLong()
} else {
this@BackupTracking.mediaId
}
library_id = this@BackupTracking.libraryId
title = this@BackupTracking.title
last_chapter_read = this@BackupTracking.lastChapterRead

View file

@ -45,7 +45,7 @@ open class TrackBaseSerializer<T : Track> : KSerializer<T> {
val jsonObject = decoder.decodeJsonElement().jsonObject
title = jsonObject[TITLE]!!.jsonPrimitive.content
sync_id = jsonObject[SYNC]!!.jsonPrimitive.int
media_id = jsonObject[MEDIA]!!.jsonPrimitive.int
media_id = jsonObject[MEDIA]!!.jsonPrimitive.long
library_id = jsonObject[LIBRARY]!!.jsonPrimitive.long
last_chapter_read = jsonObject[LAST_READ]!!.jsonPrimitive.float
tracking_url = jsonObject[TRACKING_URL]!!.jsonPrimitive.content

View file

@ -68,7 +68,7 @@ class TrackGetResolver : DefaultGetResolver<Track>() {
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
manga_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID))
sync_id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_SYNC_ID))
media_id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_MEDIA_ID))
media_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MEDIA_ID))
library_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_LIBRARY_ID))
title = cursor.getString(cursor.getColumnIndexOrThrow(COL_TITLE))
last_chapter_read = cursor.getFloat(cursor.getColumnIndexOrThrow(COL_LAST_CHAPTER_READ))

View file

@ -10,7 +10,7 @@ interface Track : Serializable {
var sync_id: Int
var media_id: Int
var media_id: Long
var library_id: Long?

View file

@ -8,7 +8,7 @@ class TrackImpl : Track {
override var sync_id: Int = 0
override var media_id: Int = 0
override var media_id: Long = 0
override var library_id: Long? = null
@ -42,7 +42,7 @@ class TrackImpl : Track {
override fun hashCode(): Int {
var result = (manga_id xor manga_id.ushr(32)).toInt()
result = 31 * result + sync_id
result = 31 * result + media_id
result = 31 * result + media_id.toInt()
return result
}
}

View file

@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.data.track.anilist.Anilist
import eu.kanade.tachiyomi.data.track.bangumi.Bangumi
import eu.kanade.tachiyomi.data.track.kitsu.Kitsu
import eu.kanade.tachiyomi.data.track.komga.Komga
import eu.kanade.tachiyomi.data.track.mangaupdates.MangaUpdates
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList
import eu.kanade.tachiyomi.data.track.shikimori.Shikimori
@ -17,6 +18,7 @@ class TrackManager(context: Context) {
const val SHIKIMORI = 4
const val BANGUMI = 5
const val KOMGA = 6
const val MANGA_UPDATES = 7
}
val myAnimeList = MyAnimeList(context, MYANIMELIST)
@ -31,7 +33,9 @@ class TrackManager(context: Context) {
val komga = Komga(context, KOMGA)
val services = listOf(myAnimeList, aniList, kitsu, shikimori, bangumi, komga)
val mangaUpdates = MangaUpdates(context, MANGA_UPDATES)
val services = listOf(myAnimeList, aniList, kitsu, shikimori, bangumi, komga, mangaUpdates)
fun getService(id: Int) = services.find { it.id == id }

View file

@ -268,7 +268,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
private fun jsonToALManga(struct: JsonObject): ALManga {
return ALManga(
struct["id"]!!.jsonPrimitive.int,
struct["id"]!!.jsonPrimitive.long,
struct["title"]!!.jsonObject["userPreferred"]!!.jsonPrimitive.content,
struct["coverImage"]!!.jsonObject["large"]!!.jsonPrimitive.content,
struct["description"]!!.jsonPrimitive.contentOrNull,
@ -329,7 +329,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
private const val baseUrl = "https://anilist.co/api/v2/"
private const val baseMangaUrl = "https://anilist.co/manga/"
fun mangaUrl(mediaId: Int): String {
fun mangaUrl(mediaId: Long): String {
return baseMangaUrl + mediaId
}

View file

@ -9,7 +9,7 @@ import java.text.SimpleDateFormat
import java.util.Locale
data class ALManga(
val media_id: Int,
val media_id: Long,
val title_user_pref: String,
val image_url_lge: String,
val description: String?,

View file

@ -18,6 +18,7 @@ import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.long
import okhttp3.CacheControl
import okhttp3.FormBody
import okhttp3.OkHttpClient
@ -106,7 +107,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
0
}
return TrackSearch.create(TrackManager.BANGUMI).apply {
media_id = obj["id"]!!.jsonPrimitive.int
media_id = obj["id"]!!.jsonPrimitive.long
title = obj["name_cn"]!!.jsonPrimitive.content
cover_url = coverUrl
summary = obj["name"]!!.jsonPrimitive.content

View file

@ -11,10 +11,10 @@ import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.lang.withIOContext
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.long
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonObject
import okhttp3.FormBody
@ -70,7 +70,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
.await()
.parseAs<JsonObject>()
.let {
track.media_id = it["data"]!!.jsonObject["id"]!!.jsonPrimitive.int
track.media_id = it["data"]!!.jsonObject["id"]!!.jsonPrimitive.long
track
}
}
@ -241,7 +241,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
private const val algoliaFilter =
"&facetFilters=%5B%22kind%3Amanga%22%5D&attributesToRetrieve=%5B%22synopsis%22%2C%22canonicalTitle%22%2C%22chapterCount%22%2C%22posterImage%22%2C%22startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D"
fun mangaUrl(remoteId: Int): String {
fun mangaUrl(remoteId: Long): String {
return baseMangaUrl + remoteId
}

View file

@ -10,12 +10,13 @@ import kotlinx.serialization.json.int
import kotlinx.serialization.json.intOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.long
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class KitsuSearchManga(obj: JsonObject) {
val id = obj["id"]!!.jsonPrimitive.int
val id = obj["id"]!!.jsonPrimitive.long
private val canonicalTitle = obj["canonicalTitle"]!!.jsonPrimitive.content
private val chapterCount = obj["chapterCount"]?.jsonPrimitive?.intOrNull
val subType = obj["subtype"]?.jsonPrimitive?.contentOrNull
@ -60,7 +61,7 @@ class KitsuLibManga(obj: JsonObject, manga: JsonObject) {
private val startDate = manga["attributes"]!!.jsonObject["startDate"]?.jsonPrimitive?.contentOrNull.orEmpty()
private val startedAt = obj["attributes"]!!.jsonObject["startedAt"]?.jsonPrimitive?.contentOrNull
private val finishedAt = obj["attributes"]!!.jsonObject["finishedAt"]?.jsonPrimitive?.contentOrNull
private val libraryId = obj["id"]!!.jsonPrimitive.int
private val libraryId = obj["id"]!!.jsonPrimitive.long
val status = obj["attributes"]!!.jsonObject["status"]!!.jsonPrimitive.content
private val ratingTwenty = obj["attributes"]!!.jsonObject["ratingTwenty"]?.jsonPrimitive?.contentOrNull
val progress = obj["attributes"]!!.jsonObject["progress"]!!.jsonPrimitive.int

View file

@ -0,0 +1,97 @@
package eu.kanade.tachiyomi.data.track.mangaupdates
import android.content.Context
import android.graphics.Color
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
import eu.kanade.tachiyomi.data.track.model.TrackSearch
class MangaUpdates(private val context: Context, id: Int) : TrackService(id) {
companion object {
const val READING_LIST = 0
const val WISH_LIST = 1
const val COMPLETE_LIST = 2
const val UNFINISHED_LIST = 3
const val ON_HOLD_LIST = 4
}
private val interceptor by lazy { MangaUpdatesInterceptor(this) }
private val api by lazy { MangaUpdatesApi(interceptor, client) }
@StringRes
override fun nameRes(): Int = R.string.tracker_manga_updates
override fun getLogo(): Int = R.drawable.ic_manga_updates
override fun getLogoColor(): Int = Color.rgb(146, 160, 173)
override fun getStatusList(): List<Int> {
return listOf(READING_LIST, COMPLETE_LIST, ON_HOLD_LIST, UNFINISHED_LIST, WISH_LIST)
}
override fun getStatus(status: Int): String = with(context) {
when (status) {
READING_LIST -> getString(R.string.reading_list)
WISH_LIST -> getString(R.string.wish_list)
COMPLETE_LIST -> getString(R.string.complete_list)
ON_HOLD_LIST -> getString(R.string.on_hold_list)
UNFINISHED_LIST -> getString(R.string.unfinished_list)
else -> ""
}
}
override fun getReadingStatus(): Int = READING_LIST
override fun getRereadingStatus(): Int = -1
override fun getCompletionStatus(): Int = COMPLETE_LIST
override fun getScoreList(): List<String> = (0..10).map(Int::toString)
override fun displayScore(track: Track): String = track.score.toInt().toString()
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
api.updateSeriesListItem(track)
return track
}
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
return try {
val (series, rating) = api.getSeriesListItem(track)
series.copyTo(track)
rating?.copyTo(track) ?: track
} catch (e: Exception) {
api.addSeriesToList(track, hasReadChapters)
track
}
}
override suspend fun search(query: String): List<TrackSearch> {
return api.search(query)
.map {
it.toTrackSearch(id)
}
}
override suspend fun refresh(track: Track): Track {
val (series, rating) = api.getSeriesListItem(track)
series.copyTo(track)
return rating?.copyTo(track) ?: track
}
override suspend fun login(username: String, password: String) {
val authenticated = api.authenticate(username, password) ?: throw Throwable("Unable to login")
saveCredentials(authenticated.uid.toString(), authenticated.sessionToken)
interceptor.newAuth(authenticated.sessionToken)
}
fun restoreSession(): String? {
return preferences.trackPassword(this)
}
}

View file

@ -0,0 +1,189 @@
package eu.kanade.tachiyomi.data.track.mangaupdates
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.mangaupdates.MangaUpdates.Companion.READING_LIST
import eu.kanade.tachiyomi.data.track.mangaupdates.MangaUpdates.Companion.WISH_LIST
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.Context
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.ListItem
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.Rating
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.Record
import eu.kanade.tachiyomi.network.DELETE
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.PUT
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.addJsonObject
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonObject
import logcat.LogPriority
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
import uy.kohesive.injekt.injectLazy
class MangaUpdatesApi(
interceptor: MangaUpdatesInterceptor,
private val client: OkHttpClient,
) {
private val baseUrl = "https://api.mangaupdates.com"
private val contentType = "application/vnd.api+json".toMediaType()
private val json by injectLazy<Json>()
private val authClient by lazy {
client.newBuilder()
.addInterceptor(interceptor)
.build()
}
suspend fun getSeriesListItem(track: Track): Pair<ListItem, Rating?> {
val listItem =
authClient.newCall(
GET(
url = "$baseUrl/v1/lists/series/${track.media_id}",
),
)
.await()
.parseAs<ListItem>()
val rating = getSeriesRating(track)
return listItem to rating
}
suspend fun addSeriesToList(track: Track, hasReadChapters: Boolean) {
val status = if (hasReadChapters) READING_LIST else WISH_LIST
val body = buildJsonArray {
addJsonObject {
putJsonObject("series") {
put("id", track.media_id)
}
put("list_id", status)
}
}
authClient.newCall(
POST(
url = "$baseUrl/v1/lists/series",
body = body.toString().toRequestBody(contentType),
),
)
.await()
.let {
if (it.code == 200) {
track.status = status
track.last_chapter_read = 1f
}
}
}
suspend fun updateSeriesListItem(track: Track) {
val body = buildJsonArray {
addJsonObject {
putJsonObject("series") {
put("id", track.media_id)
}
put("list_id", track.status)
putJsonObject("status") {
put("chapter", track.last_chapter_read.toInt())
}
}
}
authClient.newCall(
POST(
url = "$baseUrl/v1/lists/series/update",
body = body.toString().toRequestBody(contentType),
),
)
.await()
updateSeriesRating(track)
}
suspend fun getSeriesRating(track: Track): Rating? {
return try {
authClient.newCall(
GET(
url = "$baseUrl/v1/series/${track.media_id}/rating",
),
)
.await()
.parseAs<Rating>()
} catch (e: Exception) {
null
}
}
suspend fun updateSeriesRating(track: Track) {
if (track.score != 0f) {
val body = buildJsonObject {
put("rating", track.score.toInt())
}
authClient.newCall(
PUT(
url = "$baseUrl/v1/series/${track.media_id}/rating",
body = body.toString().toRequestBody(contentType),
),
)
.await()
} else {
authClient.newCall(
DELETE(
url = "$baseUrl/v1/series/${track.media_id}/rating",
),
)
.await()
}
}
suspend fun search(query: String): List<Record> {
val body = buildJsonObject {
put("search", query)
}
return client.newCall(
POST(
url = "$baseUrl/v1/series/search",
body = body.toString().toRequestBody(contentType),
),
)
.await()
.parseAs<JsonObject>()
.let { obj ->
obj["results"]?.jsonArray?.map { element ->
json.decodeFromJsonElement<Record>(element.jsonObject["record"]!!)
}
}
.orEmpty()
}
suspend fun authenticate(username: String, password: String): Context? {
val body = buildJsonObject {
put("username", username)
put("password", password)
}
return client.newCall(
PUT(
url = "$baseUrl/v1/account/login",
body = body.toString().toRequestBody(contentType),
),
)
.await()
.parseAs<JsonObject>()
.let { obj ->
try {
json.decodeFromJsonElement<Context>(obj["context"]!!)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
null
}
}
}
}

View file

@ -0,0 +1,29 @@
package eu.kanade.tachiyomi.data.track.mangaupdates
import okhttp3.Interceptor
import okhttp3.Response
import java.io.IOException
class MangaUpdatesInterceptor(
mangaUpdates: MangaUpdates,
) : Interceptor {
private var token: String? = mangaUpdates.restoreSession()
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val token = token ?: throw IOException("Not authenticated with MangaUpdates")
// Add the authorization header to the original request.
val authRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer $token")
.build()
return chain.proceed(authRequest)
}
fun newAuth(token: String?) {
this.token = token
}
}

View file

@ -0,0 +1,11 @@
package eu.kanade.tachiyomi.data.track.mangaupdates.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class Context(
@SerialName("session_token")
val sessionToken: String,
val uid: Long,
)

View file

@ -0,0 +1,10 @@
package eu.kanade.tachiyomi.data.track.mangaupdates.dto
import kotlinx.serialization.Serializable
@Serializable
data class Image(
val url: Url? = null,
val height: Int? = null,
val width: Int? = null,
)

View file

@ -0,0 +1,22 @@
package eu.kanade.tachiyomi.data.track.mangaupdates.dto
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.mangaupdates.MangaUpdates.Companion.READING_LIST
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ListItem(
val series: Series? = null,
@SerialName("list_id")
val listId: Int? = null,
val status: Status? = null,
val priority: Int? = null,
)
fun ListItem.copyTo(track: Track): Track {
return track.apply {
this.status = listId ?: READING_LIST
this.last_chapter_read = this@copyTo.status?.chapter?.toFloat() ?: 0f
}
}

View file

@ -0,0 +1,15 @@
package eu.kanade.tachiyomi.data.track.mangaupdates.dto
import eu.kanade.tachiyomi.data.database.models.Track
import kotlinx.serialization.Serializable
@Serializable
data class Rating(
val rating: Int? = null,
)
fun Rating.copyTo(track: Track): Track {
return track.apply {
this.score = rating?.toFloat() ?: 0f
}
}

View file

@ -0,0 +1,37 @@
package eu.kanade.tachiyomi.data.track.mangaupdates.dto
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class Record(
@SerialName("series_id")
val seriesId: Long? = null,
val title: String? = null,
val url: String? = null,
val description: String? = null,
val image: Image? = null,
val type: String? = null,
val year: String? = null,
@SerialName("bayesian_rating")
val bayesianRating: Double? = null,
@SerialName("rating_votes")
val ratingVotes: Int? = null,
@SerialName("latest_chapter")
val latestChapter: Int? = null,
)
fun Record.toTrackSearch(id: Int): TrackSearch {
return TrackSearch.create(id).apply {
media_id = this@toTrackSearch.seriesId ?: 0L
title = this@toTrackSearch.title ?: ""
total_chapters = 0
cover_url = this@toTrackSearch.image?.url?.original ?: ""
summary = this@toTrackSearch.description ?: ""
tracking_url = this@toTrackSearch.url ?: ""
publishing_status = ""
publishing_type = this@toTrackSearch.type.toString()
start_date = this@toTrackSearch.year.toString()
}
}

View file

@ -0,0 +1,9 @@
package eu.kanade.tachiyomi.data.track.mangaupdates.dto
import kotlinx.serialization.Serializable
@Serializable
data class Series(
val id: Long? = null,
val title: String? = null,
)

View file

@ -0,0 +1,9 @@
package eu.kanade.tachiyomi.data.track.mangaupdates.dto
import kotlinx.serialization.Serializable
@Serializable
data class Status(
val volume: Int? = null,
val chapter: Int? = null,
)

View file

@ -0,0 +1,9 @@
package eu.kanade.tachiyomi.data.track.mangaupdates.dto
import kotlinx.serialization.Serializable
@Serializable
data class Url(
val original: String? = null,
val thumb: String? = null,
)

View file

@ -10,7 +10,7 @@ class TrackSearch : Track {
override var sync_id: Int = 0
override var media_id: Int = 0
override var media_id: Long = 0
override var library_id: Long? = null
@ -54,7 +54,7 @@ class TrackSearch : Track {
override fun hashCode(): Int {
var result = (manga_id xor manga_id.ushr(32)).toInt()
result = 31 * result + sync_id
result = 31 * result + media_id
result = 31 * result + media_id.toInt()
return result
}

View file

@ -21,6 +21,7 @@ import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.long
import okhttp3.FormBody
import okhttp3.OkHttpClient
import okhttp3.Request
@ -94,7 +95,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.let {
val obj = it.jsonObject
TrackSearch.create(TrackManager.MYANIMELIST).apply {
media_id = obj["id"]!!.jsonPrimitive.int
media_id = obj["id"]!!.jsonPrimitive.long
title = obj["title"]!!.jsonPrimitive.content
summary = obj["synopsis"]?.jsonPrimitive?.content ?: ""
total_chapters = obj["num_chapters"]!!.jsonPrimitive.int
@ -251,7 +252,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.appendQueryParameter("response_type", "code")
.build()
fun mangaUrl(id: Int): Uri = "$baseApiUrl/manga".toUri().buildUpon()
fun mangaUrl(id: Long): Uri = "$baseApiUrl/manga".toUri().buildUpon()
.appendPath(id.toString())
.appendPath("my_list_status")
.build()

View file

@ -19,6 +19,7 @@ import kotlinx.serialization.json.float
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.long
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonObject
import okhttp3.FormBody
@ -73,7 +74,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
private fun jsonToSearch(obj: JsonObject): TrackSearch {
return TrackSearch.create(TrackManager.SHIKIMORI).apply {
media_id = obj["id"]!!.jsonPrimitive.int
media_id = obj["id"]!!.jsonPrimitive.long
title = obj["name"]!!.jsonPrimitive.content
total_chapters = obj["chapters"]!!.jsonPrimitive.int
cover_url = baseUrl + obj["image"]!!.jsonObject["preview"]!!.jsonPrimitive.content
@ -88,7 +89,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
private fun jsonToTrack(obj: JsonObject, mangas: JsonObject): Track {
return Track.create(TrackManager.SHIKIMORI).apply {
title = mangas["name"]!!.jsonPrimitive.content
media_id = obj["id"]!!.jsonPrimitive.int
media_id = obj["id"]!!.jsonPrimitive.long
total_chapters = mangas["chapters"]!!.jsonPrimitive.int
last_chapter_read = obj["chapters"]!!.jsonPrimitive.float
score = (obj["score"]!!.jsonPrimitive.int).toFloat()

View file

@ -36,3 +36,31 @@ fun POST(
.cacheControl(cache)
.build()
}
fun PUT(
url: String,
headers: Headers = DEFAULT_HEADERS,
body: RequestBody = DEFAULT_BODY,
cache: CacheControl = DEFAULT_CACHE_CONTROL,
): Request {
return Request.Builder()
.url(url)
.put(body)
.headers(headers)
.cacheControl(cache)
.build()
}
fun DELETE(
url: String,
headers: Headers = DEFAULT_HEADERS,
body: RequestBody = DEFAULT_BODY,
cache: CacheControl = DEFAULT_CACHE_CONTROL,
): Request {
return Request.Builder()
.url(url)
.delete(body)
.headers(headers)
.cacheControl(cache)
.build()
}

View file

@ -63,13 +63,17 @@ class SettingsTrackingController :
dialog.targetController = this@SettingsTrackingController
dialog.showDialog(router)
}
trackPreference(trackManager.mangaUpdates) {
val dialog = TrackLoginDialog(trackManager.mangaUpdates, R.string.username)
dialog.targetController = this@SettingsTrackingController
dialog.showDialog(router)
}
trackPreference(trackManager.shikimori) {
activity?.openInBrowser(ShikimoriApi.authUrl(), forceDefaultBrowser = true)
}
trackPreference(trackManager.bangumi) {
activity?.openInBrowser(BangumiApi.authUrl(), forceDefaultBrowser = true)
}
infoPreference(R.string.tracking_info)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -643,6 +643,7 @@
<string name="tracker_komga_warning">This tracker is only compatible with the Komga source.</string>
<string name="tracker_bangumi" translatable="false">Bangumi</string>
<string name="tracker_shikimori" translatable="false">Shikimori</string>
<string name="tracker_manga_updates" translatable="false">MangaUpdates</string>
<string name="manga_tracking_tab">Tracking</string>
<plurals name="num_trackers">
<item quantity="one">%d tracker</item>
@ -657,6 +658,11 @@
<string name="paused">Paused</string>
<string name="plan_to_read">Plan to read</string>
<string name="repeating">Rereading</string>
<string name="reading_list">Reading List</string>
<string name="wish_list">Wish List</string>
<string name="complete_list">Complete List</string>
<string name="on_hold_list">On Hold List</string>
<string name="unfinished_list">Unfinished List</string>
<string name="score">Score</string>
<string name="title">Title</string>
<string name="status">Status</string>