mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-29 09:39:03 +03:00
add simkl support
This commit is contained in:
parent
fb0f34dbf5
commit
77d53b2ab8
11 changed files with 568 additions and 1 deletions
|
@ -188,6 +188,21 @@
|
|||
android:scheme="tachiyomi" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.setting.track.SimklLoginActivity"
|
||||
android:label="Simkl"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="simkl-auth"
|
||||
android:scheme="aniyomi" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver
|
||||
android:name=".data.notification.NotificationReceiver"
|
||||
|
|
|
@ -8,6 +8,7 @@ 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
|
||||
import eu.kanade.tachiyomi.data.track.simkl.Simkl
|
||||
|
||||
class TrackManager(context: Context) {
|
||||
|
||||
|
@ -19,6 +20,7 @@ class TrackManager(context: Context) {
|
|||
const val BANGUMI = 5
|
||||
const val KOMGA = 6
|
||||
const val MANGA_UPDATES = 7
|
||||
const val SIMKL = 101
|
||||
}
|
||||
|
||||
val myAnimeList = MyAnimeList(context, MYANIMELIST)
|
||||
|
@ -29,13 +31,15 @@ class TrackManager(context: Context) {
|
|||
|
||||
val shikimori = Shikimori(context, SHIKIMORI)
|
||||
|
||||
val simkl = Simkl(context, SIMKL)
|
||||
|
||||
val bangumi = Bangumi(context, BANGUMI)
|
||||
|
||||
val komga = Komga(context, KOMGA)
|
||||
|
||||
val mangaUpdates = MangaUpdates(context, MANGA_UPDATES)
|
||||
|
||||
val services = listOf(myAnimeList, aniList, kitsu, shikimori, bangumi, komga, mangaUpdates)
|
||||
val services = listOf(myAnimeList, aniList, kitsu, shikimori, bangumi, komga, mangaUpdates, simkl)
|
||||
|
||||
fun getService(id: Int) = services.find { it.id == id }
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package eu.kanade.tachiyomi.data.track.simkl
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class OAuth(
|
||||
val access_token: String,
|
||||
val token_type: String,
|
||||
val scope: String,
|
||||
)
|
165
app/src/main/java/eu/kanade/tachiyomi/data/track/simkl/Simkl.kt
Normal file
165
app/src/main/java/eu/kanade/tachiyomi/data/track/simkl/Simkl.kt
Normal file
|
@ -0,0 +1,165 @@
|
|||
package eu.kanade.tachiyomi.data.track.simkl
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import androidx.annotation.StringRes
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.track.AnimeTrackService
|
||||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class Simkl(private val context: Context, id: Int) : TrackService(id), AnimeTrackService {
|
||||
|
||||
companion object {
|
||||
const val WATCHING = 1
|
||||
const val COMPLETED = 2
|
||||
const val ON_HOLD = 3
|
||||
const val NOT_INTERESTING = 4
|
||||
const val PLAN_TO_WATCH = 5
|
||||
}
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val interceptor by lazy { SimklInterceptor(this) }
|
||||
|
||||
private val api by lazy { SimklApi(client, interceptor) }
|
||||
|
||||
@StringRes
|
||||
override fun nameRes() = R.string.tracker_simkl
|
||||
|
||||
override fun getScoreList(): List<String> {
|
||||
return IntRange(0, 10).map(Int::toString)
|
||||
}
|
||||
|
||||
override fun displayScore(track: Track): String {
|
||||
return track.score.toInt().toString()
|
||||
}
|
||||
|
||||
override fun displayScore(track: AnimeTrack): String {
|
||||
return track.score.toInt().toString()
|
||||
}
|
||||
|
||||
private suspend fun add(track: AnimeTrack): AnimeTrack {
|
||||
return api.addLibAnime(track)
|
||||
}
|
||||
|
||||
override suspend fun update(track: AnimeTrack, didWatchEpisode: Boolean): AnimeTrack {
|
||||
if (track.status != COMPLETED) {
|
||||
if (didWatchEpisode) {
|
||||
if (track.last_episode_seen.toInt() == track.total_episodes && track.total_episodes > 0) {
|
||||
track.status = COMPLETED
|
||||
} else {
|
||||
track.status = WATCHING
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return api.updateLibAnime(track)
|
||||
}
|
||||
|
||||
override suspend fun bind(track: AnimeTrack, hasReadChapters: Boolean): AnimeTrack {
|
||||
val remoteTrack = api.findLibAnime(track)
|
||||
return if (remoteTrack != null) {
|
||||
track.copyPersonalFrom(remoteTrack)
|
||||
track.library_id = remoteTrack.library_id
|
||||
|
||||
if (track.status != COMPLETED) {
|
||||
track.status = if (hasReadChapters) WATCHING else track.status
|
||||
}
|
||||
|
||||
update(track)
|
||||
} else {
|
||||
// Set default fields if it's not found in the list
|
||||
track.status = if (hasReadChapters) WATCHING else PLAN_TO_WATCH
|
||||
track.score = 0F
|
||||
add(track)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun searchAnime(query: String): List<AnimeTrackSearch> {
|
||||
logcat { getPassword() }
|
||||
return api.searchAnime(query, "anime") +
|
||||
api.searchAnime(query, "tv") +
|
||||
api.searchAnime(query, "movie")
|
||||
}
|
||||
|
||||
override suspend fun refresh(track: AnimeTrack): AnimeTrack {
|
||||
api.findLibAnime(track)?.let { remoteTrack ->
|
||||
track.copyPersonalFrom(remoteTrack)
|
||||
track.total_episodes = remoteTrack.total_episodes
|
||||
}
|
||||
return track
|
||||
}
|
||||
|
||||
override fun getLogo() = R.drawable.ic_tracker_simkl
|
||||
|
||||
override fun getLogoColor() = Color.rgb(0, 0, 0)
|
||||
|
||||
override fun getStatusListAnime(): List<Int> {
|
||||
return listOf(WATCHING, COMPLETED, ON_HOLD, NOT_INTERESTING, PLAN_TO_WATCH)
|
||||
}
|
||||
|
||||
override fun getStatus(status: Int): String = with(context) {
|
||||
when (status) {
|
||||
WATCHING -> getString(R.string.watching)
|
||||
PLAN_TO_WATCH -> getString(R.string.plan_to_watch)
|
||||
COMPLETED -> getString(R.string.completed)
|
||||
ON_HOLD -> getString(R.string.on_hold)
|
||||
NOT_INTERESTING -> getString(R.string.not_interesting)
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
override fun getWatchingStatus(): Int = WATCHING
|
||||
|
||||
override fun getRewatchingStatus(): Int = 0
|
||||
|
||||
override fun getCompletionStatus(): Int = COMPLETED
|
||||
|
||||
override suspend fun login(username: String, password: String) = login(password)
|
||||
|
||||
suspend fun login(code: String) {
|
||||
try {
|
||||
val oauth = api.accessToken(code)
|
||||
interceptor.newAuth(oauth)
|
||||
val user = api.getCurrentUser()
|
||||
saveCredentials(user.toString(), oauth.access_token)
|
||||
} catch (e: Throwable) {
|
||||
logout()
|
||||
}
|
||||
}
|
||||
|
||||
fun saveToken(oauth: OAuth?) {
|
||||
preferences.trackToken(this).set(json.encodeToString(oauth))
|
||||
}
|
||||
|
||||
fun restoreToken(): OAuth? {
|
||||
return try {
|
||||
json.decodeFromString<OAuth>(preferences.trackToken(this).get())
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun logout() {
|
||||
super.logout()
|
||||
preferences.trackToken(this).delete()
|
||||
interceptor.newAuth(null)
|
||||
}
|
||||
|
||||
override fun getReadingStatus(): Int = WATCHING
|
||||
override fun getRereadingStatus(): Int = 0
|
||||
override fun getStatusList(): List<Int> = throw NotImplementedError()
|
||||
override suspend fun update(track: Track, didReadChapter: Boolean): Track = throw NotImplementedError()
|
||||
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track = throw NotImplementedError()
|
||||
override suspend fun search(query: String): List<TrackSearch> = throw NotImplementedError()
|
||||
override suspend fun refresh(track: Track): Track = throw NotImplementedError()
|
||||
}
|
|
@ -0,0 +1,291 @@
|
|||
package eu.kanade.tachiyomi.data.track.simkl
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.network.jsonMime
|
||||
import eu.kanade.tachiyomi.network.parseAs
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.addJsonObject
|
||||
import kotlinx.serialization.json.booleanOrNull
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.contentOrNull
|
||||
import kotlinx.serialization.json.float
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.intOrNull
|
||||
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.putJsonArray
|
||||
import kotlinx.serialization.json.putJsonObject
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
|
||||
class SimklApi(private val client: OkHttpClient, interceptor: SimklInterceptor) {
|
||||
|
||||
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
||||
|
||||
suspend fun addLibAnime(track: AnimeTrack): AnimeTrack {
|
||||
return withIOContext {
|
||||
val type = track.tracking_url
|
||||
.substringAfter("/")
|
||||
.substringBefore("/")
|
||||
val mediaType = if (type == "movies") "movies" else "shows"
|
||||
addToList(track, mediaType)
|
||||
|
||||
track
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun addToList(track: AnimeTrack, mediaType: String) {
|
||||
val payload = buildJsonObject {
|
||||
putJsonArray(mediaType) {
|
||||
addJsonObject {
|
||||
putJsonObject("ids") {
|
||||
put("simkl", track.media_id)
|
||||
}
|
||||
put("to", track.toSimklStatus())
|
||||
}
|
||||
}
|
||||
}.toString().toRequestBody(jsonMime)
|
||||
authClient.newCall(
|
||||
POST("$apiUrl/sync/add-to-list", body = payload),
|
||||
).await()
|
||||
}
|
||||
|
||||
private suspend fun updateRating(track: AnimeTrack, mediaType: String) {
|
||||
val payload = buildJsonObject {
|
||||
putJsonArray(mediaType) {
|
||||
addJsonObject {
|
||||
putJsonObject("ids") {
|
||||
put("simkl", track.media_id)
|
||||
}
|
||||
put("rating", track.score.toInt())
|
||||
}
|
||||
}
|
||||
}.toString().toRequestBody(jsonMime)
|
||||
|
||||
if (track.score == 0F) {
|
||||
authClient.newCall(
|
||||
POST("$apiUrl/sync/ratings/remove", body = payload),
|
||||
).await()
|
||||
} else {
|
||||
authClient.newCall(
|
||||
POST("$apiUrl/sync/ratings", body = payload),
|
||||
).await()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateProgress(track: AnimeTrack) {
|
||||
// first remove
|
||||
authClient.newCall(
|
||||
POST("$apiUrl/sync/history/remove", body = buildProgressObject(track, false)),
|
||||
).await()
|
||||
// then add again
|
||||
authClient.newCall(
|
||||
POST("$apiUrl/sync/history", body = buildProgressObject(track, true)),
|
||||
).await()
|
||||
}
|
||||
|
||||
private fun buildProgressObject(track: AnimeTrack, add: Boolean = true) = buildJsonObject {
|
||||
putJsonArray("shows") {
|
||||
addJsonObject {
|
||||
putJsonObject("ids") {
|
||||
put("simkl", track.media_id)
|
||||
}
|
||||
putJsonArray("seasons") {
|
||||
addJsonObject {
|
||||
put("number", 1)
|
||||
if (add) putJsonArray("episodes") {
|
||||
for (epNum in 1..track.last_episode_seen.toInt()) {
|
||||
addJsonObject {
|
||||
put("number", epNum)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.toString().toRequestBody(jsonMime)
|
||||
|
||||
suspend fun updateLibAnime(track: AnimeTrack): AnimeTrack {
|
||||
return withIOContext {
|
||||
// determine media type
|
||||
val type = track.tracking_url
|
||||
.substringAfter("/")
|
||||
.substringBefore("/")
|
||||
val mediaType = if (type == "movies") "movies" else "shows"
|
||||
// update progress only for shows
|
||||
if (type != "movies") {
|
||||
updateProgress(track)
|
||||
}
|
||||
// add to correct list
|
||||
addToList(track, mediaType)
|
||||
// update rating
|
||||
updateRating(track, mediaType)
|
||||
|
||||
track
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun searchAnime(search: String, type: String): List<AnimeTrackSearch> {
|
||||
return withIOContext {
|
||||
val searchUrl = "$apiUrl/search/$type".toUri().buildUpon()
|
||||
.appendQueryParameter("q", search)
|
||||
.appendQueryParameter("extended", "full")
|
||||
.appendQueryParameter("client_id", clientId)
|
||||
.build()
|
||||
client.newCall(GET(searchUrl.toString()))
|
||||
.await()
|
||||
.parseAs<JsonArray>()
|
||||
.let { response ->
|
||||
response.map {
|
||||
jsonToAnimeSearch(it.jsonObject, type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun jsonToAnimeSearch(obj: JsonObject, type: String): AnimeTrackSearch {
|
||||
return AnimeTrackSearch.create(TrackManager.SIMKL).apply {
|
||||
media_id = obj["ids"]!!.jsonObject["simkl_id"]!!.jsonPrimitive.long
|
||||
title = obj["title_romaji"]?.jsonPrimitive?.content ?: obj["title"]!!.jsonPrimitive.content
|
||||
total_episodes = obj["ep_count"]?.jsonPrimitive?.intOrNull ?: 1
|
||||
cover_url = "https://simkl.in/posters/" + obj["poster"]!!.jsonPrimitive.content + "_m.webp"
|
||||
summary = obj["all_titles"]?.jsonArray?.joinToString("\n", "All titles:\n") { it.jsonPrimitive.content } ?: ""
|
||||
tracking_url = obj["url"]!!.jsonPrimitive.content
|
||||
publishing_status = obj["status"]?.jsonPrimitive?.content ?: "ended"
|
||||
publishing_type = obj["type"]?.jsonPrimitive?.content ?: type
|
||||
start_date = obj["year"]?.jsonPrimitive?.intOrNull?.toString() ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun jsonToAnimeTrack(obj: JsonObject, typeName: String, type: String, statusString: String): AnimeTrack {
|
||||
return AnimeTrack.create(TrackManager.SIMKL).apply {
|
||||
title = obj[typeName]!!.jsonObject["title"]!!.jsonPrimitive.content
|
||||
val id = obj[typeName]!!.jsonObject["ids"]!!.jsonObject["simkl"]!!.jsonPrimitive.long
|
||||
media_id = id
|
||||
if (typeName != "movie") {
|
||||
total_episodes =
|
||||
obj["total_episodes_count"]!!
|
||||
.jsonPrimitive.int
|
||||
last_episode_seen =
|
||||
obj["watched_episodes_count"]!!
|
||||
.jsonPrimitive.float
|
||||
} else {
|
||||
total_episodes = 1
|
||||
last_episode_seen = if (statusString == "completed") 1F else 0F
|
||||
}
|
||||
score = obj["user_rating"]!!.jsonPrimitive.intOrNull?.toFloat() ?: 0F
|
||||
status = toTrackStatus(statusString)
|
||||
tracking_url = "/$type/$id"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given [track] exists in the user's list and
|
||||
* returns all info about it or null if it isn't found.
|
||||
*/
|
||||
suspend fun findLibAnime(track: AnimeTrack): AnimeTrack? {
|
||||
return withIOContext {
|
||||
val payload = buildJsonArray {
|
||||
addJsonObject {
|
||||
put("simkl", track.media_id)
|
||||
}
|
||||
}.toString().toRequestBody(jsonMime)
|
||||
val foundAnime = authClient.newCall(
|
||||
POST("$apiUrl/sync/watched", body = payload),
|
||||
)
|
||||
.await()
|
||||
.parseAs<JsonArray>()
|
||||
.firstOrNull()?.jsonObject ?: return@withIOContext null
|
||||
|
||||
if (foundAnime["result"]?.jsonPrimitive?.booleanOrNull != true) return@withIOContext null
|
||||
val lastWatched = foundAnime["last_watched"]?.jsonPrimitive?.contentOrNull ?: return@withIOContext null
|
||||
val status = foundAnime["list"]!!.jsonPrimitive.content
|
||||
val type = track.tracking_url
|
||||
.substringAfter("/")
|
||||
.substringBefore("/")
|
||||
val queryType = if (type == "tv") "shows" else type
|
||||
val url = "$apiUrl/sync/all-items/$queryType/$status".toUri().buildUpon()
|
||||
.appendQueryParameter("date_from", lastWatched)
|
||||
.build()
|
||||
|
||||
val typeName = if (type == "movies") "movie" else "show"
|
||||
val listAnime = authClient.newCall(GET(url.toString()))
|
||||
.await()
|
||||
.parseAs<JsonObject>()[queryType]!!.jsonArray
|
||||
.firstOrNull {
|
||||
it.jsonObject[typeName]
|
||||
?.jsonObject?.get("ids")
|
||||
?.jsonObject?.get("simkl")
|
||||
?.jsonPrimitive?.long == track.media_id
|
||||
}?.jsonObject ?: return@withIOContext null
|
||||
logcat { listAnime.toString() }
|
||||
|
||||
jsonToAnimeTrack(listAnime, typeName, type, status)
|
||||
}
|
||||
}
|
||||
|
||||
fun getCurrentUser(): Int {
|
||||
return runBlocking {
|
||||
authClient.newCall(GET("$apiUrl/users/settings"))
|
||||
.await()
|
||||
.parseAs<JsonObject>()
|
||||
.let {
|
||||
it["account"]!!.jsonObject["id"]!!.jsonPrimitive.int
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun accessToken(code: String): OAuth {
|
||||
return withIOContext {
|
||||
client.newCall(accessTokenRequest(code))
|
||||
.await()
|
||||
.parseAs()
|
||||
}
|
||||
}
|
||||
|
||||
private fun accessTokenRequest(code: String) = POST(
|
||||
oauthUrl,
|
||||
body = buildJsonObject {
|
||||
put("code", code)
|
||||
put("client_id", clientId)
|
||||
put("client_secret", clientSecret)
|
||||
put("redirect_uri", redirectUrl)
|
||||
put("grant_type", "authorization_code")
|
||||
}.toString().toRequestBody(jsonMime),
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val clientId = "aa62a7da32518aae5d5049a658b87fa4837c3b739e06ed250b315aab6af82b0e"
|
||||
private const val clientSecret = "2bec9c1d0c00a1e9b0e9e096a71f88d555a6f52da7923df07906df3b21351783"
|
||||
|
||||
private const val baseUrl = "https://simkl.com"
|
||||
private const val apiUrl = "https://api.simkl.com"
|
||||
private const val oauthUrl = "$apiUrl/oauth/token"
|
||||
private const val loginUrl = "$baseUrl/oauth/authorize"
|
||||
|
||||
private const val redirectUrl = "aniyomi://simkl-auth"
|
||||
|
||||
fun authUrl(): Uri =
|
||||
loginUrl.toUri().buildUpon()
|
||||
.appendQueryParameter("response_type", "code")
|
||||
.appendQueryParameter("client_id", clientId)
|
||||
.appendQueryParameter("redirect_uri", redirectUrl)
|
||||
.build()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package eu.kanade.tachiyomi.data.track.simkl
|
||||
|
||||
import eu.kanade.tachiyomi.data.track.simkl.SimklApi.Companion.clientId
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
class SimklInterceptor(val simkl: Simkl) : Interceptor {
|
||||
|
||||
/**
|
||||
* OAuth object used for authenticated requests.
|
||||
*/
|
||||
private var oauth: OAuth? = simkl.restoreToken()
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val originalRequest = chain.request()
|
||||
|
||||
val oauth = oauth ?: throw Exception("Not authenticated with Simkl")
|
||||
|
||||
// Add the authorization header to the original request.
|
||||
val authRequest = originalRequest.newBuilder()
|
||||
.addHeader("Authorization", "Bearer ${oauth.access_token}")
|
||||
.addHeader("simkl-api-key", clientId)
|
||||
.header("User-Agent", "Aniyomi")
|
||||
.build()
|
||||
|
||||
return chain.proceed(authRequest)
|
||||
}
|
||||
|
||||
fun newAuth(oauth: OAuth?) {
|
||||
this.oauth = oauth
|
||||
simkl.saveToken(oauth)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package eu.kanade.tachiyomi.data.track.simkl
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
|
||||
|
||||
fun AnimeTrack.toSimklStatus() = when (status) {
|
||||
Simkl.WATCHING -> "watching"
|
||||
Simkl.COMPLETED -> "completed"
|
||||
Simkl.ON_HOLD -> "hold"
|
||||
Simkl.NOT_INTERESTING -> "notinteresting"
|
||||
Simkl.PLAN_TO_WATCH -> "plantowatch"
|
||||
else -> throw NotImplementedError("Unknown status: $status")
|
||||
}
|
||||
|
||||
fun toTrackStatus(status: String) = when (status) {
|
||||
"watching" -> Simkl.WATCHING
|
||||
"completed" -> Simkl.COMPLETED
|
||||
"hold" -> Simkl.ON_HOLD
|
||||
"dropped", "notinteresting" -> Simkl.NOT_INTERESTING
|
||||
"plantowatch" -> Simkl.PLAN_TO_WATCH
|
||||
else -> throw NotImplementedError("Unknown status: $status")
|
||||
}
|
|
@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.data.track.anilist.AnilistApi
|
|||
import eu.kanade.tachiyomi.data.track.bangumi.BangumiApi
|
||||
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeListApi
|
||||
import eu.kanade.tachiyomi.data.track.shikimori.ShikimoriApi
|
||||
import eu.kanade.tachiyomi.data.track.simkl.SimklApi
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.ui.setting.track.TrackLoginDialog
|
||||
import eu.kanade.tachiyomi.ui.setting.track.TrackLogoutDialog
|
||||
|
@ -71,6 +72,9 @@ class SettingsTrackingController :
|
|||
trackPreference(trackManager.shikimori) {
|
||||
activity?.openInBrowser(ShikimoriApi.authUrl(), forceDefaultBrowser = true)
|
||||
}
|
||||
trackPreference(trackManager.simkl) {
|
||||
activity?.openInBrowser(SimklApi.authUrl(), forceDefaultBrowser = true)
|
||||
}
|
||||
trackPreference(trackManager.bangumi) {
|
||||
activity?.openInBrowser(BangumiApi.authUrl(), forceDefaultBrowser = true)
|
||||
}
|
||||
|
@ -133,6 +137,7 @@ class SettingsTrackingController :
|
|||
updatePreference(trackManager.aniList.id)
|
||||
updatePreference(trackManager.shikimori.id)
|
||||
updatePreference(trackManager.bangumi.id)
|
||||
updatePreference(trackManager.simkl.id)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package eu.kanade.tachiyomi.ui.setting.track
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
|
||||
class SimklLoginActivity : BaseOAuthLoginActivity() {
|
||||
|
||||
override fun handleResult(data: Uri?) {
|
||||
val code = data?.getQueryParameter("code")
|
||||
if (code != null) {
|
||||
lifecycleScope.launchIO {
|
||||
trackManager.simkl.login(code)
|
||||
returnToSettings()
|
||||
}
|
||||
} else {
|
||||
trackManager.simkl.logout()
|
||||
returnToSettings()
|
||||
}
|
||||
}
|
||||
}
|
BIN
app/src/main/res/drawable-nodpi/ic_tracker_simkl.webp
Normal file
BIN
app/src/main/res/drawable-nodpi/ic_tracker_simkl.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.8 KiB |
|
@ -776,6 +776,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_simkl" translatable="false">Simkl</string>
|
||||
<string name="tracker_manga_updates" translatable="false">MangaUpdates</string>
|
||||
<string name="manga_tracking_tab">Tracking</string>
|
||||
<plurals name="num_trackers">
|
||||
|
@ -790,6 +791,7 @@
|
|||
<string name="currently_watching">Currently watching</string>
|
||||
<string name="completed">Completed</string>
|
||||
<string name="dropped">Dropped</string>
|
||||
<string name="not_interesting">Not interesting</string>
|
||||
<string name="on_hold">On hold</string>
|
||||
<string name="paused">Paused</string>
|
||||
<string name="plan_to_read">Plan to read</string>
|
||||
|
|
Loading…
Reference in a new issue