diff --git a/src/all/kavita/build.gradle b/src/all/kavita/build.gradle index ecf80205..52fd69b4 100644 --- a/src/all/kavita/build.gradle +++ b/src/all/kavita/build.gradle @@ -2,7 +2,7 @@ ext { kmkVersionCode = 1 extName = 'Kavita' extClass = '.KavitaFactory' - extVersionCode = 34 + extVersionCode = 35 isNsfw = false } diff --git a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/Kavita.kt b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/Kavita.kt index 44e5b7ed..da467352 100644 --- a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/Kavita.kt +++ b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/Kavita.kt @@ -1727,6 +1727,21 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou } } + seriesDtoForContext?.let { currentSeries -> + allChapters += adminActionChapter( + seriesId = currentSeries.id, + libraryId = currentSeries.libraryId, + action = ADMIN_ACTION_REFRESH_COVER, + title = "Kavita: 표지 새로고침", + ) + allChapters += adminActionChapter( + seriesId = currentSeries.id, + libraryId = currentSeries.libraryId, + action = ADMIN_ACTION_SCAN_SERIES, + title = "Kavita: 시리즈 스캔", + ) + } + // 1.0 (Chapter) > 0.0001 (Volume) > 0.00001 (Special) return allChapters.sortedByDescending { it.chapter_number } } catch (e: Exception) { @@ -1735,6 +1750,15 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou } } + private fun adminActionChapter(seriesId: Int, libraryId: Int, action: String, title: String): SChapter = + SChapter.create().apply { + name = title + url = "$ADMIN_ACTION_URL?action=$action&seriesId=$seriesId&libraryId=$libraryId" + chapter_number = Float.MAX_VALUE + scanlator = "Kavita 관리" + date_upload = System.currentTimeMillis() + } + /** * Fetches the "url" of each page from the chapter * **/ @@ -1861,6 +1885,10 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou private fun String.urlEncode(): String = URLEncoder.encode(this, Charsets.UTF_8.name()) override suspend fun getPageList(chapter: SChapter): List = withContext(Dispatchers.IO) { + if (chapter.url.startsWith(ADMIN_ACTION_URL)) { + return@withContext executeAdminAction(chapter.url) + } + // Check if this is a reading list item (has readingListId in URL) if (chapter.url.contains("readingListId=")) { try { @@ -2009,6 +2037,62 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou } } + private fun executeAdminAction(url: String): List { + val action = queryParam(url, "action") ?: throw IOException("Missing Kavita action") + val seriesId = queryParam(url, "seriesId")?.toIntOrNull() ?: throw IOException("Missing Kavita seriesId") + val libraryId = queryParam(url, "libraryId")?.toIntOrNull() ?: throw IOException("Missing Kavita libraryId") + + val endpoint = when (action) { + ADMIN_ACTION_REFRESH_COVER -> "$apiUrl/Series/refresh-metadata" + ADMIN_ACTION_SCAN_SERIES -> "$apiUrl/Series/scan" + else -> throw IOException("Unknown Kavita action: $action") + } + + val payload = buildJsonObject { + put("libraryId", libraryId) + put("seriesId", seriesId) + put("forceUpdate", true) + put("forceColorscape", true) + }.toString() + + client.newCall( + POST( + endpoint, + headersBuilder().build(), + payload.toRequestBody(JSON_MEDIA_TYPE), + ), + ).execute().use { response -> + if (!response.isSuccessful) { + val message = when (response.code) { + 401, 403 -> "Kavita 관리자 권한이 필요합니다." + else -> "Kavita action failed: HTTP ${response.code}" + } + throw IOException(message) + } + } + + val coverUrl = "$apiUrl/image/series-cover".toHttpUrlOrNull()!!.newBuilder() + .addQueryParameter("seriesId", seriesId.toString()) + .addQueryParameter("apiKey", apiKey) + .addQueryParameter("t", System.currentTimeMillis().toString()) + .build() + .toString() + + return listOf(Page(index = 0, url = coverUrl, imageUrl = coverUrl)) + } + + private fun queryParam(url: String, key: String): String? { + val query = url.substringAfter("?", "") + if (query.isBlank()) return null + + return query.split("&") + .firstNotNullOfOrNull { part -> + val name = part.substringBefore("=") + val value = part.substringAfter("=", "") + value.takeIf { name == key && it.isNotBlank() } + } + } + override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") @@ -2613,6 +2697,10 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou private const val SCANLATOR_FORMAT_PREF = "scanlatorFormat" const val SCANLATOR_FORMAT_DEFAULT = "\$Type" + + private const val ADMIN_ACTION_URL = "kavita-admin://series" + private const val ADMIN_ACTION_REFRESH_COVER = "refresh-cover" + private const val ADMIN_ACTION_SCAN_SERIES = "scan-series" } /**