Fix Kavita interceptor crashing app + minor cleanup
This commit is contained in:
parent
7e74949d38
commit
a54d9912d0
4 changed files with 93 additions and 93 deletions
|
@ -1,6 +1,5 @@
|
||||||
package eu.kanade.tachiyomi.data.track.kavita
|
package eu.kanade.tachiyomi.data.track.kavita
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.graphics.Color
|
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.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.security.MessageDigest
|
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 {
|
class Kavita(private val context: Context, id: Long) : TrackService(id), EnhancedTrackService, NoLoginTrackService {
|
||||||
var authentications: OAuth? = null
|
|
||||||
companion object {
|
companion object {
|
||||||
const val UNREAD = 1
|
const val UNREAD = 1
|
||||||
const val READING = 2
|
const val READING = 2
|
||||||
const val COMPLETED = 3
|
const val COMPLETED = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var authentications: OAuth? = null
|
||||||
|
|
||||||
private val interceptor by lazy { KavitaInterceptor(this) }
|
private val interceptor by lazy { KavitaInterceptor(this) }
|
||||||
val api by lazy { KavitaApi(client, interceptor) }
|
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) {
|
override fun getStatus(status: Int): String = with(context) {
|
||||||
when (status) {
|
when (status) {
|
||||||
Kavita.UNREAD -> getString(R.string.unread)
|
UNREAD -> getString(R.string.unread)
|
||||||
Kavita.READING -> getString(R.string.reading)
|
READING -> getString(R.string.reading)
|
||||||
Kavita.COMPLETED -> getString(R.string.completed)
|
COMPLETED -> getString(R.string.completed)
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getReadingStatus(): Int = Kavita.READING
|
override fun getReadingStatus(): Int = READING
|
||||||
|
|
||||||
override fun getRereadingStatus(): Int = -1
|
override fun getRereadingStatus(): Int = -1
|
||||||
|
|
||||||
override fun getCompletionStatus(): Int = Kavita.COMPLETED
|
override fun getCompletionStatus(): Int = COMPLETED
|
||||||
|
|
||||||
override fun getScoreList(): List<String> = emptyList()
|
override fun getScoreList(): List<String> = emptyList()
|
||||||
|
|
||||||
|
@ -103,10 +104,10 @@ class Kavita(private val context: Context, id: Long) : TrackService(id), Enhance
|
||||||
null
|
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
|
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)) {
|
if (accept(newSource)) {
|
||||||
track.copy(remoteUrl = manga.url)
|
track.copy(remoteUrl = manga.url)
|
||||||
} else {
|
} else {
|
||||||
|
@ -118,13 +119,13 @@ class Kavita(private val context: Context, id: Long) : TrackService(id), Enhance
|
||||||
for (sourceId in 1..3) {
|
for (sourceId in 1..3) {
|
||||||
val authentication = oauth.authentications[sourceId - 1]
|
val authentication = oauth.authentications[sourceId - 1]
|
||||||
val sourceSuffixID by lazy {
|
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())
|
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
|
||||||
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }
|
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }
|
||||||
.reduce(Long::or) and Long.MAX_VALUE
|
.reduce(Long::or) and Long.MAX_VALUE
|
||||||
}
|
}
|
||||||
val preferences: SharedPreferences by lazy {
|
val preferences: SharedPreferences by lazy {
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$sourceSuffixID", 0x0000)
|
context.getSharedPreferences("source_$sourceSuffixID", 0x0000)
|
||||||
}
|
}
|
||||||
val prefApiUrl = preferences.getString("APIURL", "")!!
|
val prefApiUrl = preferences.getString("APIURL", "")!!
|
||||||
if (prefApiUrl.isEmpty()) {
|
if (prefApiUrl.isEmpty()) {
|
||||||
|
@ -133,7 +134,6 @@ class Kavita(private val context: Context, id: Long) : TrackService(id), Enhance
|
||||||
}
|
}
|
||||||
val prefApiKey = preferences.getString("APIKEY", "")!!
|
val prefApiKey = preferences.getString("APIKEY", "")!!
|
||||||
val token = api.getNewToken(apiUrl = prefApiUrl, apiKey = prefApiKey)
|
val token = api.getNewToken(apiUrl = prefApiUrl, apiKey = prefApiKey)
|
||||||
|
|
||||||
if (token.isNullOrEmpty()) {
|
if (token.isNullOrEmpty()) {
|
||||||
// Source is not accessible. Skip
|
// Source is not accessible. Skip
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -13,51 +13,57 @@ import okhttp3.Dns
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import java.io.IOException
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
|
|
||||||
class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor) {
|
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 {
|
fun getApiFromUrl(url: String): String {
|
||||||
return url.split("/api/").first() + "/api"
|
return url.split("/api/").first() + "/api"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNewToken(apiUrl: String, apiKey: String): String? {
|
|
||||||
/*
|
/*
|
||||||
* Uses url to compare against each source APIURL's to get the correct custom source preference.
|
* 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")
|
* Now having source preference we can do getString("APIKEY")
|
||||||
* Authenticates to get the token
|
* Authenticates to get the token
|
||||||
* Saves the token in the var jwtToken
|
* Saves the token in the var jwtToken
|
||||||
*/
|
*/
|
||||||
|
fun getNewToken(apiUrl: String, apiKey: String): String? {
|
||||||
val request = POST(
|
val request = POST(
|
||||||
"$apiUrl/Plugin/authenticate?apiKey=$apiKey&pluginName=Tachiyomi-Kavita",
|
"$apiUrl/Plugin/authenticate?apiKey=$apiKey&pluginName=Tachiyomi-Kavita",
|
||||||
body = "{}".toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()),
|
body = "{}".toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()),
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
client.newCall(request).execute().use {
|
client.newCall(request).execute().use {
|
||||||
if (it.code == 200) {
|
when (it.code) {
|
||||||
return it.parseAs<AuthenticationDto>().token
|
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")
|
||||||
}
|
}
|
||||||
if (it.code == 401) {
|
500 -> {
|
||||||
logcat(LogPriority.WARN) { "Unauthorized / api key not valid:Cleaned api URL:${apiUrl}Api key is empty:${apiKey.isEmpty()}" }
|
logcat(LogPriority.WARN) { "Error fetching JWT token. Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" }
|
||||||
throw Exception("Unauthorized / api key not valid")
|
throw IOException("Error fetching JWT token")
|
||||||
}
|
}
|
||||||
if (it.code == 500) {
|
else -> {}
|
||||||
logcat(LogPriority.WARN) { "Error fetching jwt token. Cleaned api URL:$apiUrl Api key is empty:${apiKey.isEmpty()}" }
|
|
||||||
throw Exception("Error fetching jwt token")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Not sure which one to cathc
|
// Not sure which one to catch
|
||||||
} catch (e: SocketTimeoutException) {
|
} catch (e: SocketTimeoutException) {
|
||||||
logcat(LogPriority.WARN) {
|
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
|
return null
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR) {
|
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
|
return null
|
||||||
|
@ -67,16 +73,17 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
||||||
return "${getApiFromUrl(url)}/Series/volumes?seriesId=${getIdFromUrl(url)}"
|
return "${getApiFromUrl(url)}/Series/volumes?seriesId=${getIdFromUrl(url)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Strips serie id from URL */
|
||||||
private fun getIdFromUrl(url: String): Int {
|
private fun getIdFromUrl(url: String): Int {
|
||||||
/*Strips serie id from Url*/
|
|
||||||
return url.substringAfterLast("/").toInt()
|
return url.substringAfterLast("/").toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTotalChapters(url: String): Int {
|
/*
|
||||||
/*Returns total chapters in the series.
|
* Returns total chapters in the series.
|
||||||
* Ignores volumes.
|
* Ignores volumes.
|
||||||
* Volumes consisting of 1 file treated as chapter
|
* Volumes consisting of 1 file treated as chapter
|
||||||
*/
|
*/
|
||||||
|
private fun getTotalChapters(url: String): Int {
|
||||||
val requestUrl = getApiVolumesUrl(url)
|
val requestUrl = getApiVolumesUrl(url)
|
||||||
try {
|
try {
|
||||||
val listVolumeDto = authClient.newCall(GET(requestUrl))
|
val listVolumeDto = authClient.newCall(GET(requestUrl))
|
||||||
|
@ -103,8 +110,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
||||||
val serieId = getIdFromUrl(url)
|
val serieId = getIdFromUrl(url)
|
||||||
val requestUrl = "${getApiFromUrl(url)}/Tachiyomi/latest-chapter?seriesId=$serieId"
|
val requestUrl = "${getApiFromUrl(url)}/Tachiyomi/latest-chapter?seriesId=$serieId"
|
||||||
try {
|
try {
|
||||||
authClient.newCall(GET(requestUrl))
|
authClient.newCall(GET(requestUrl)).execute().use {
|
||||||
.execute().use {
|
|
||||||
if (it.code == 200) {
|
if (it.code == 200) {
|
||||||
return it.parseAs<ChapterDto>().number!!.replace(",", ".").toFloat()
|
return it.parseAs<ChapterDto>().number!!.replace(",", ".").toFloat()
|
||||||
}
|
}
|
||||||
|
@ -119,16 +125,13 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
||||||
return 0F
|
return 0F
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getTrackSearch(url: String): TrackSearch =
|
suspend fun getTrackSearch(url: String): TrackSearch = withIOContext {
|
||||||
withIOContext {
|
|
||||||
try {
|
try {
|
||||||
val serieDto: SeriesDto =
|
val serieDto: SeriesDto = authClient.newCall(GET(url))
|
||||||
authClient.newCall(GET(url))
|
|
||||||
.await()
|
.await()
|
||||||
.parseAs<SeriesDto>()
|
.parseAs()
|
||||||
|
|
||||||
val track = serieDto.toTrack()
|
val track = serieDto.toTrack()
|
||||||
|
|
||||||
track.apply {
|
track.apply {
|
||||||
cover_url = serieDto.thumbnail_url.toString()
|
cover_url = serieDto.thumbnail_url.toString()
|
||||||
tracking_url = url
|
tracking_url = url
|
||||||
|
|
|
@ -21,7 +21,6 @@ data class SeriesDto(
|
||||||
val created: String? = "",
|
val created: String? = "",
|
||||||
val libraryId: Int,
|
val libraryId: Int,
|
||||||
val libraryName: String? = "",
|
val libraryName: String? = "",
|
||||||
|
|
||||||
) {
|
) {
|
||||||
fun toTrack(): TrackSearch = TrackSearch.create(TrackManager.KAVITA).also {
|
fun toTrack(): TrackSearch = TrackSearch.create(TrackManager.KAVITA).also {
|
||||||
it.title = name
|
it.title = name
|
||||||
|
@ -63,6 +62,23 @@ data class AuthenticationDto(
|
||||||
val apiKey: String,
|
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(
|
data class SourceAuth(
|
||||||
var sourceId: Int,
|
var sourceId: Int,
|
||||||
var apiUrl: String = "",
|
var apiUrl: String = "",
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
Reference in a new issue