Fix Kavita interceptor crashing app + minor cleanup

This commit is contained in:
arkon 2022-11-25 23:03:33 -05:00
parent 7e74949d38
commit a54d9912d0
4 changed files with 93 additions and 93 deletions

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.kavita
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Color
@ -13,18 +12,20 @@ import eu.kanade.tachiyomi.data.track.NoLoginTrackService
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.Source
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.security.MessageDigest
import eu.kanade.domain.manga.model.Manga as DomainManga
import eu.kanade.domain.track.model.Track as DomainTrack
class Kavita(private val context: Context, id: Long) : TrackService(id), EnhancedTrackService, NoLoginTrackService {
var authentications: OAuth? = null
companion object {
const val UNREAD = 1
const val READING = 2
const val COMPLETED = 3
}
var authentications: OAuth? = null
private val interceptor by lazy { KavitaInterceptor(this) }
val api by lazy { KavitaApi(client, interceptor) }
@ -39,18 +40,18 @@ class Kavita(private val context: Context, id: Long) : TrackService(id), Enhance
override fun getStatus(status: Int): String = with(context) {
when (status) {
Kavita.UNREAD -> getString(R.string.unread)
Kavita.READING -> getString(R.string.reading)
Kavita.COMPLETED -> getString(R.string.completed)
UNREAD -> getString(R.string.unread)
READING -> getString(R.string.reading)
COMPLETED -> getString(R.string.completed)
else -> ""
}
}
override fun getReadingStatus(): Int = Kavita.READING
override fun getReadingStatus(): Int = READING
override fun getRereadingStatus(): Int = -1
override fun getCompletionStatus(): Int = Kavita.COMPLETED
override fun getCompletionStatus(): Int = COMPLETED
override fun getScoreList(): List<String> = emptyList()
@ -103,10 +104,10 @@ class Kavita(private val context: Context, id: Long) : TrackService(id), Enhance
null
}
override fun isTrackFrom(track: eu.kanade.domain.track.model.Track, manga: eu.kanade.domain.manga.model.Manga, source: Source?): Boolean =
override fun isTrackFrom(track: DomainTrack, manga: DomainManga, source: Source?): Boolean =
track.remoteUrl == manga.url && source?.let { accept(it) } == true
override fun migrateTrack(track: eu.kanade.domain.track.model.Track, manga: eu.kanade.domain.manga.model.Manga, newSource: Source): eu.kanade.domain.track.model.Track? =
override fun migrateTrack(track: DomainTrack, manga: DomainManga, newSource: Source): DomainTrack? =
if (accept(newSource)) {
track.copy(remoteUrl = manga.url)
} else {
@ -118,13 +119,13 @@ class Kavita(private val context: Context, id: Long) : TrackService(id), Enhance
for (sourceId in 1..3) {
val authentication = oauth.authentications[sourceId - 1]
val sourceSuffixID by lazy {
val key = "${"kavita_$sourceId"}/all/1" // Hardcoded versionID to 1
val key = "kavita_$sourceId/all/1" // Hardcoded versionID to 1
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }
.reduce(Long::or) and Long.MAX_VALUE
}
val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$sourceSuffixID", 0x0000)
context.getSharedPreferences("source_$sourceSuffixID", 0x0000)
}
val prefApiUrl = preferences.getString("APIURL", "")!!
if (prefApiUrl.isEmpty()) {
@ -133,7 +134,6 @@ class Kavita(private val context: Context, id: Long) : TrackService(id), Enhance
}
val prefApiKey = preferences.getString("APIKEY", "")!!
val token = api.getNewToken(apiUrl = prefApiUrl, apiKey = prefApiKey)
if (token.isNullOrEmpty()) {
// Source is not accessible. Skip
continue

View File

@ -13,51 +13,57 @@ import okhttp3.Dns
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.IOException
import java.net.SocketTimeoutException
class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor) {
private val authClient = client.newBuilder().dns(Dns.SYSTEM).addInterceptor(interceptor).build()
private val authClient = client.newBuilder()
.dns(Dns.SYSTEM)
.addInterceptor(interceptor)
.build()
fun getApiFromUrl(url: String): String {
return url.split("/api/").first() + "/api"
}
/*
* Uses url to compare against each source APIURL's to get the correct custom source preference.
* Now having source preference we can do getString("APIKEY")
* Authenticates to get the token
* Saves the token in the var jwtToken
*/
fun getNewToken(apiUrl: String, apiKey: String): String? {
/*
* Uses url to compare against each source APIURL's to get the correct custom source preference.
* Now having source preference we can do getString("APIKEY")
* Authenticates to get the token
* Saves the token in the var jwtToken
*/
val request = POST(
"$apiUrl/Plugin/authenticate?apiKey=$apiKey&pluginName=Tachiyomi-Kavita",
body = "{}".toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()),
)
try {
client.newCall(request).execute().use {
if (it.code == 200) {
return it.parseAs<AuthenticationDto>().token
}
if (it.code == 401) {
logcat(LogPriority.WARN) { "Unauthorized / api key not valid:Cleaned api URL:${apiUrl}Api key is empty:${apiKey.isEmpty()}" }
throw Exception("Unauthorized / api key not valid")
}
if (it.code == 500) {
logcat(LogPriority.WARN) { "Error fetching jwt token. Cleaned api URL:$apiUrl Api key is empty:${apiKey.isEmpty()}" }
throw Exception("Error fetching jwt token")
when (it.code) {
200 -> return it.parseAs<AuthenticationDto>().token
401 -> {
logcat(LogPriority.WARN) { "Unauthorized / api key not valid: Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" }
throw IOException("Unauthorized / api key not valid")
}
500 -> {
logcat(LogPriority.WARN) { "Error fetching JWT token. Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" }
throw IOException("Error fetching JWT token")
}
else -> {}
}
}
// Not sure which one to cathc
// Not sure which one to catch
} catch (e: SocketTimeoutException) {
logcat(LogPriority.WARN) {
"Could not fetch jwt token. Probably due to connectivity issue or the url '$apiUrl' is not available. Skipping"
"Could not fetch JWT token. Probably due to connectivity issue or the url '$apiUrl' is not available, skipping"
}
return null
} catch (e: Exception) {
logcat(LogPriority.ERROR) {
"Unhandled Exception fetching jwt token for url: '$apiUrl'"
"Unhandled exception fetching JWT token for url: '$apiUrl'"
}
throw e
throw IOException(e)
}
return null
@ -67,16 +73,17 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
return "${getApiFromUrl(url)}/Series/volumes?seriesId=${getIdFromUrl(url)}"
}
/* Strips serie id from URL */
private fun getIdFromUrl(url: String): Int {
/*Strips serie id from Url*/
return url.substringAfterLast("/").toInt()
}
/*
* Returns total chapters in the series.
* Ignores volumes.
* Volumes consisting of 1 file treated as chapter
*/
private fun getTotalChapters(url: String): Int {
/*Returns total chapters in the series.
* Ignores volumes.
* Volumes consisting of 1 file treated as chapter
*/
val requestUrl = getApiVolumesUrl(url)
try {
val listVolumeDto = authClient.newCall(GET(requestUrl))
@ -103,50 +110,46 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
val serieId = getIdFromUrl(url)
val requestUrl = "${getApiFromUrl(url)}/Tachiyomi/latest-chapter?seriesId=$serieId"
try {
authClient.newCall(GET(requestUrl))
.execute().use {
if (it.code == 200) {
return it.parseAs<ChapterDto>().number!!.replace(",", ".").toFloat()
}
if (it.code == 204) {
return 0F
}
authClient.newCall(GET(requestUrl)).execute().use {
if (it.code == 200) {
return it.parseAs<ChapterDto>().number!!.replace(",", ".").toFloat()
}
if (it.code == 204) {
return 0F
}
}
} catch (e: Exception) {
logcat(LogPriority.WARN, e) { "Exception getting latest chapter read. Could not get itemRequest:$requestUrl" }
logcat(LogPriority.WARN, e) { "Exception getting latest chapter read. Could not get itemRequest: $requestUrl" }
throw e
}
return 0F
}
suspend fun getTrackSearch(url: String): TrackSearch =
withIOContext {
try {
val serieDto: SeriesDto =
authClient.newCall(GET(url))
.await()
.parseAs<SeriesDto>()
suspend fun getTrackSearch(url: String): TrackSearch = withIOContext {
try {
val serieDto: SeriesDto = authClient.newCall(GET(url))
.await()
.parseAs()
val track = serieDto.toTrack()
val track = serieDto.toTrack()
track.apply {
cover_url = serieDto.thumbnail_url.toString()
tracking_url = url
total_chapters = getTotalChapters(url)
track.apply {
cover_url = serieDto.thumbnail_url.toString()
tracking_url = url
total_chapters = getTotalChapters(url)
title = serieDto.name
status = when (serieDto.pagesRead) {
serieDto.pages -> Kavita.COMPLETED
0 -> Kavita.UNREAD
else -> Kavita.READING
}
last_chapter_read = getLatestChapterRead(url)
title = serieDto.name
status = when (serieDto.pagesRead) {
serieDto.pages -> Kavita.COMPLETED
0 -> Kavita.UNREAD
else -> Kavita.READING
}
} catch (e: Exception) {
logcat(LogPriority.WARN, e) { "Could not get item: $url" }
throw e
last_chapter_read = getLatestChapterRead(url)
}
} catch (e: Exception) {
logcat(LogPriority.WARN, e) { "Could not get item: $url" }
throw e
}
}
suspend fun updateProgress(track: Track): Track {
val requestUrl = "${getApiFromUrl(track.tracking_url)}/Tachiyomi/mark-chapter-until-as-read?seriesId=${getIdFromUrl(track.tracking_url)}&chapterNumber=${track.last_chapter_read}"

View File

@ -21,7 +21,6 @@ data class SeriesDto(
val created: String? = "",
val libraryId: Int,
val libraryName: String? = "",
) {
fun toTrack(): TrackSearch = TrackSearch.create(TrackManager.KAVITA).also {
it.title = name
@ -63,6 +62,23 @@ data class AuthenticationDto(
val apiKey: String,
)
class OAuth(
val authentications: List<SourceAuth> = listOf(
SourceAuth(1),
SourceAuth(2),
SourceAuth(3),
),
) {
fun getToken(apiUrl: String): String? {
for (authentication in authentications) {
if (authentication.apiUrl == apiUrl) {
return authentication.jwtToken
}
}
return null
}
}
data class SourceAuth(
var sourceId: Int,
var apiUrl: String = "",

View File

@ -1,19 +0,0 @@
package eu.kanade.tachiyomi.data.track.kavita
class OAuth(
val authentications: List<SourceAuth> = listOf<SourceAuth>(
SourceAuth(1),
SourceAuth(2),
SourceAuth(3),
),
) {
fun getToken(apiUrl: String): String? {
for (authentication in authentications) {
if (authentication.apiUrl == apiUrl) {
return authentication.jwtToken
}
}
return null
}
}