Build cover image URLs safely
This commit is contained in:
@@ -2,7 +2,7 @@ ext {
|
|||||||
kmkVersionCode = 1
|
kmkVersionCode = 1
|
||||||
extName = 'Kavita'
|
extName = 'Kavita'
|
||||||
extClass = '.KavitaFactory'
|
extClass = '.KavitaFactory'
|
||||||
extVersionCode = 25
|
extVersionCode = 26
|
||||||
isNsfw = false
|
isNsfw = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -980,9 +980,9 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
title = (if (readingList.promoted) "🔺 " else "") + readingList.title
|
title = (if (readingList.promoted) "🔺 " else "") + readingList.title
|
||||||
artist = "${readingList.itemCount} items"
|
artist = "${readingList.itemCount} items"
|
||||||
thumbnail_url = if (!readingList.coverImage.isNullOrBlank()) {
|
thumbnail_url = if (!readingList.coverImage.isNullOrBlank()) {
|
||||||
"$apiUrl/image/${readingList.coverImage}?apiKey=$apiKey"
|
KavitaImageUrls.imagePath(apiUrl, readingList.coverImage, apiKey)
|
||||||
} else {
|
} else {
|
||||||
"$apiUrl/image/readinglist-cover?readingListId=$readingListId&apiKey=$apiKey"
|
KavitaImageUrls.cover(apiUrl, "readinglist-cover", "readingListId", readingListId ?: 0, apiKey)
|
||||||
}
|
}
|
||||||
description = readingList.summary ?: "Reading List"
|
description = readingList.summary ?: "Reading List"
|
||||||
genre = genreString
|
genre = genreString
|
||||||
@@ -1110,7 +1110,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
if (isComicLibrary && volume.minNumber.toInt() == KavitaConstants.UNNUMBERED_VOLUME) {
|
if (isComicLibrary && volume.minNumber.toInt() == KavitaConstants.UNNUMBERED_VOLUME) {
|
||||||
for (chapter in volume.chapters) {
|
for (chapter in volume.chapters) {
|
||||||
if (chapter.coverImage.isNotBlank()) {
|
if (chapter.coverImage.isNotBlank()) {
|
||||||
val url = "$apiUrl/Image/chapter-cover?chapterId=${chapter.id}&apiKey=$apiKey"
|
val url = KavitaImageUrls.cover(apiUrl, "chapter-cover", "chapterId", chapter.id, apiKey)
|
||||||
coverCandidates.add(
|
coverCandidates.add(
|
||||||
Triple(url, chapter.pagesRead < chapter.pages, chapter.number.toFloatOrNull() ?: 0f),
|
Triple(url, chapter.pagesRead < chapter.pages, chapter.number.toFloatOrNull() ?: 0f),
|
||||||
)
|
)
|
||||||
@@ -1124,7 +1124,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
ChapterType.of(chapter, volume) == ChapterType.SingleFileVolume
|
ChapterType.of(chapter, volume) == ChapterType.SingleFileVolume
|
||||||
}
|
}
|
||||||
if (hasSingleFile && volume.coverImage.isNotBlank()) {
|
if (hasSingleFile && volume.coverImage.isNotBlank()) {
|
||||||
val url = "$apiUrl/Image/volume-cover?volumeId=${volume.id}&apiKey=$apiKey"
|
val url = KavitaImageUrls.cover(apiUrl, "volume-cover", "volumeId", volume.id, apiKey)
|
||||||
val isUnread = volume.pagesRead < volume.pages
|
val isUnread = volume.pagesRead < volume.pages
|
||||||
val number = volume.minNumber.toFloat()
|
val number = volume.minNumber.toFloat()
|
||||||
coverCandidates.add(Triple(url, isUnread, number))
|
coverCandidates.add(Triple(url, isUnread, number))
|
||||||
@@ -1155,14 +1155,14 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
} ?: System.currentTimeMillis().toString()
|
} ?: System.currentTimeMillis().toString()
|
||||||
|
|
||||||
// Append cache-busting timestamp to always get the latest cover
|
// Append cache-busting timestamp to always get the latest cover
|
||||||
"$baseUrl&ts=$timestamp"
|
KavitaImageUrls.withTimestamp(baseUrl, timestamp)
|
||||||
} ?: "$apiUrl/image/series-cover?seriesId=$seriesId&apiKey=$apiKey"
|
} ?: KavitaImageUrls.cover(apiUrl, "series-cover", "seriesId", seriesId, apiKey)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(LOG_TAG, "Error fetching volumes for cover selection", e)
|
Log.e(LOG_TAG, "Error fetching volumes for cover selection", e)
|
||||||
"$apiUrl/image/series-cover?seriesId=${result.seriesId ?: 0}&apiKey=$apiKey"
|
KavitaImageUrls.cover(apiUrl, "series-cover", "seriesId", result.seriesId ?: 0, apiKey)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
"$apiUrl/image/series-cover?seriesId=${result.seriesId ?: 0}&apiKey=$apiKey"
|
KavitaImageUrls.cover(apiUrl, "series-cover", "seriesId", result.seriesId ?: 0, apiKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
manga.status = when (result.publicationStatus) {
|
manga.status = when (result.publicationStatus) {
|
||||||
@@ -1252,9 +1252,9 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
artist = "${list.itemCount} items"
|
artist = "${list.itemCount} items"
|
||||||
author = list.ownerUserName
|
author = list.ownerUserName
|
||||||
thumbnail_url = if (!list.coverImage.isNullOrBlank()) {
|
thumbnail_url = if (!list.coverImage.isNullOrBlank()) {
|
||||||
"$apiUrl/image/${list.coverImage}?apiKey=$apiKey"
|
KavitaImageUrls.imagePath(apiUrl, list.coverImage, apiKey)
|
||||||
} else {
|
} else {
|
||||||
"$apiUrl/Image/readinglist-cover?readingListId=${list.id}&apiKey=$apiKey"
|
KavitaImageUrls.cover(apiUrl, "readinglist-cover", "readingListId", list.id, apiKey)
|
||||||
}
|
}
|
||||||
description = list.summary ?: "Reading List"
|
description = list.summary ?: "Reading List"
|
||||||
url = "$baseUrl/ReadingList/items?readingListId=${list.id}&source=readinglist"
|
url = "$baseUrl/ReadingList/items?readingListId=${list.id}&source=readinglist"
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class KavitaHelper {
|
|||||||
SManga.create().apply {
|
SManga.create().apply {
|
||||||
url = "$baseUrl/Series/${obj.id}"
|
url = "$baseUrl/Series/${obj.id}"
|
||||||
title = obj.name
|
title = obj.name
|
||||||
thumbnail_url = "$baseUrl/image/series-cover?seriesId=${obj.id}&apiKey=$apiKey"
|
thumbnail_url = KavitaImageUrls.cover(baseUrl, "series-cover", "seriesId", obj.id, apiKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun chapterFromVolume(
|
fun chapterFromVolume(
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.all.kavita
|
||||||
|
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
import java.net.URLEncoder
|
||||||
|
|
||||||
|
internal object KavitaImageUrls {
|
||||||
|
fun cover(
|
||||||
|
apiUrl: String,
|
||||||
|
endpoint: String,
|
||||||
|
idParam: String,
|
||||||
|
id: Int,
|
||||||
|
apiKey: String,
|
||||||
|
timestamp: String? = null,
|
||||||
|
): String {
|
||||||
|
val builder = "$apiUrl/image/$endpoint".toHttpUrlOrNull()?.newBuilder()
|
||||||
|
?.addQueryParameter(idParam, id.toString())
|
||||||
|
?.addQueryParameter("apiKey", apiKey)
|
||||||
|
|
||||||
|
if (!timestamp.isNullOrBlank()) {
|
||||||
|
builder?.addQueryParameter("ts", timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder?.build()?.toString()
|
||||||
|
?: "$apiUrl/image/$endpoint?$idParam=$id&apiKey=${encodeQuery(apiKey)}${timestampQuery(timestamp)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun imagePath(apiUrl: String, path: String, apiKey: String): String {
|
||||||
|
val separator = if (path.startsWith("/")) "" else "/"
|
||||||
|
val url = "$apiUrl/image$separator$path"
|
||||||
|
return url.toHttpUrlOrNull()?.newBuilder()
|
||||||
|
?.addQueryParameter("apiKey", apiKey)
|
||||||
|
?.build()
|
||||||
|
?.toString()
|
||||||
|
?: "$url?apiKey=${encodeQuery(apiKey)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withTimestamp(url: String, timestamp: String): String =
|
||||||
|
url.toHttpUrlOrNull()?.newBuilder()
|
||||||
|
?.addQueryParameter("ts", timestamp)
|
||||||
|
?.build()
|
||||||
|
?.toString()
|
||||||
|
?: "$url${if (url.contains("?")) "&" else "?"}ts=${encodeQuery(timestamp)}"
|
||||||
|
|
||||||
|
private fun timestampQuery(timestamp: String?): String =
|
||||||
|
if (timestamp.isNullOrBlank()) "" else "&ts=${encodeQuery(timestamp)}"
|
||||||
|
|
||||||
|
private fun encodeQuery(value: String): String =
|
||||||
|
URLEncoder.encode(value, "UTF-8").replace("+", "%20")
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.extension.all.kavita.dto
|
package eu.kanade.tachiyomi.extension.all.kavita.dto
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.extension.all.kavita.KavitaConstants
|
import eu.kanade.tachiyomi.extension.all.kavita.KavitaConstants
|
||||||
|
import eu.kanade.tachiyomi.extension.all.kavita.KavitaImageUrls
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -169,7 +170,7 @@ data class RelatedSeriesItem(
|
|||||||
url = "$baseUrl/Series/$id"
|
url = "$baseUrl/Series/$id"
|
||||||
thumbnail_url = when {
|
thumbnail_url = when {
|
||||||
!coverImage.isNullOrBlank() && (coverImage.startsWith("http://") || coverImage.startsWith("https://")) -> coverImage
|
!coverImage.isNullOrBlank() && (coverImage.startsWith("http://") || coverImage.startsWith("https://")) -> coverImage
|
||||||
else -> "$apiUrl/image/series-cover?seriesId=$id&apiKey=$apiKey"
|
else -> KavitaImageUrls.cover(apiUrl, "series-cover", "seriesId", id, apiKey)
|
||||||
}
|
}
|
||||||
initialized = true
|
initialized = true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user