mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 14:27:57 +01:00 
			
		
		
		
	Fix Kavita interceptor crashing app + minor cleanup
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
| @@ -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}" | ||||
|   | ||||
| @@ -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 = "", | ||||
|   | ||||
| @@ -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 New Issue
	
	Block a user