fix broken pages

This commit is contained in:
Mio.
2025-07-14 22:15:48 +02:00
parent d37600f74b
commit 94cbfbd394
2 changed files with 86 additions and 63 deletions

View File

@@ -1009,7 +1009,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
if (chapter.coverImage.isNotBlank()) { if (chapter.coverImage.isNotBlank()) {
val url = "$apiUrl/Image/chapter-cover?chapterId=${chapter.id}&apiKey=$apiKey" val url = "$apiUrl/Image/chapter-cover?chapterId=${chapter.id}&apiKey=$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),
) )
chapterMap[url] = chapter chapterMap[url] = chapter
} }
@@ -1567,16 +1567,12 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
throw IOException(helper.intl["error_invalid_reading_list_item"]) throw IOException(helper.intl["error_invalid_reading_list_item"])
} }
val pages = mutableListOf<Page>() (0 until chapterDetails.pages).map { i ->
for (i in 0 until chapterDetails.pages) { Page(
pages.add( index = i,
Page( imageUrl = "$apiUrl/Reader/image?chapterId=${chapterDetails.id}&page=$i&extractPdf=true&apiKey=$apiKey",
index = i,
imageUrl = "$apiUrl/Reader/image?chapterId=${chapterDetails.id}&page=$i&extractPdf=true&apiKey=$apiKey",
),
) )
} }
pages.toList()
}.onErrorResumeNext { error -> }.onErrorResumeNext { error ->
Log.e(LOG_TAG, "Error fetching reading list chapter pages", error) Log.e(LOG_TAG, "Error fetching reading list chapter pages", error)
Observable.error(error) Observable.error(error)
@@ -1596,30 +1592,36 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
val matchingChapter = volume.chapters.firstOrNull() // or match a chapterId if available val matchingChapter = volume.chapters.firstOrNull() // or match a chapterId if available
?: throw IOException(helper.intl["error_no_chapters_found"]) ?: throw IOException(helper.intl["error_no_chapters_found"])
val pages = (0 until matchingChapter.pages).map { i -> val initialPages = (0 until matchingChapter.pages).map { i ->
Page( Page(
index = i, index = i,
imageUrl = "$apiUrl/Reader/image?chapterId=${matchingChapter.id}&page=$i&extractPdf=true&apiKey=$apiKey", imageUrl = "$apiUrl/Reader/image?chapterId=${matchingChapter.id}&page=$i&extractPdf=true&apiKey=$apiKey",
) )
}.toMutableList()
var pageOffset = matchingChapter.pages
volume.chapters.sortedBy { it.number.toFloatOrNull() ?: 0f }.forEach { chapterDto ->
val chapterPages = (0 until chapterDto.pages).map { i ->
Page(
index = pageOffset + i,
imageUrl = "$apiUrl/Reader/image?chapterId=${chapterDto.id}&page=$i&extractPdf=true&apiKey=$apiKey",
)
}
pages.addAll(chapterPages)
pageOffset += chapterDto.pages
} }
pages.toList() val remainingPages = volume.chapters
.sortedBy { it.number.toFloatOrNull() ?: 0f }
.runningFold(initialPages.size) { acc, chapter -> acc + chapter.pages }
.drop(1)
.zip(volume.chapters.sortedBy { it.number.toFloatOrNull() ?: 0f })
.flatMap { (offset, chapter) ->
(0 until chapter.pages).map { i ->
Page(
index = offset - chapter.pages + i,
imageUrl = "$apiUrl/Reader/image?chapterId=${chapter.id}&page=$i&extractPdf=true&apiKey=$apiKey",
)
}
}
(initialPages + remainingPages).toList()
} else { } else {
// Original chapter handling // Original chapter handling
val chapterId = chapter.url.substringAfter("/Chapter/").substringBefore("_") val chapterId = when {
chapter.url.startsWith("chapter_") -> chapter.url.substringAfter("chapter_").substringBefore("_")
chapter.url.contains("/Chapter/") -> chapter.url.substringAfter("/Chapter/").substringBefore("_")
else -> throw IOException("Invalid chapter URL format")
}
val chapterRequest = GET("$apiUrl/Chapter?chapterId=$chapterId", headersBuilder().build()) val chapterRequest = GET("$apiUrl/Chapter?chapterId=$chapterId", headersBuilder().build())
val response = client.newCall(chapterRequest).execute() val response = client.newCall(chapterRequest).execute()
@@ -1633,25 +1635,23 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
throw IOException("${helper.intl["error_failed_parse_chapter"]}: ${e.message}") throw IOException("${helper.intl["error_failed_parse_chapter"]}: ${e.message}")
} }
val pages = (0 until chapterDetails.pages).map { i -> (0 until chapterDetails.pages).map { i ->
Page( Page(
index = i, index = i,
imageUrl = "$apiUrl/Reader/image?chapterId=$chapterId&page=$i&extractPdf=true&apiKey=$apiKey", imageUrl = "$apiUrl/Reader/image?chapterId=$chapterId&page=$i&extractPdf=true&apiKey=$apiKey",
) )
} }
pages.toList()
} }
}.onErrorResumeNext { error -> }.onErrorResumeNext { error ->
// Fallback to using the scanlator field if we can't get chapter details // Fallback to using the scanlator field if we can't get chapter details
Log.e(LOG_TAG, "Error fetching chapter details, using fallback", error) Log.e(LOG_TAG, "Error fetching chapter details, using fallback", error)
val fallbackPageCount = chapter.scanlator?.replace(" pages", "")?.toIntOrNull() ?: 1 val fallbackPageCount = chapter.scanlator?.replace(" pages", "")?.toIntOrNull() ?: 1
val pages = (0 until fallbackPageCount).map { i -> (0 until fallbackPageCount).map { i ->
Page( Page(
index = i, index = i,
imageUrl = "$apiUrl/Reader/image?chapterId=${chapter.url.substringBefore("_")}&page=$i&extractPdf=true&apiKey=$apiKey", imageUrl = "$apiUrl/Reader/image?chapterId=${chapter.url.substringBefore("_")}&page=$i&extractPdf=true&apiKey=$apiKey",
) )
} }.let { Observable.just(it) }
Observable.just(pages.toList())
} }
} }

View File

@@ -163,39 +163,59 @@ class KavitaHelper {
} }
} }
// @todo Keeping this in case we're switching the logic here this after the Mihon PR
// Handle decimal chapter numbers properly // Handle decimal chapter numbers properly
chapter_number = when { // chapter_number = when {
type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null -> // type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null ->
singleFileVolumeNumber.toFloat() // singleFileVolumeNumber.toFloat()
//
// type == ChapterType.SingleFileVolume -> {
// // For standalone volumes, use positive numbers
// volume.minNumber.toFloat()
// }
//
// // For regular chapters
// type != ChapterType.SingleFileVolume && type != ChapterType.Special -> {
// // Handle decimal chapter numbers
// if (chapter.minNumber % 1 != 0.0) {
// chapter.minNumber.toFloat()
// } else {
// chapter.minNumber.toInt().toFloat()
// }
// }
//
// // For volumes/specials in mixed content, place them below chapter 0
// else -> {
// val volumeNum = try {
// if (volume.minNumber % 1 != 0.0) {
// volume.minNumber.toFloat()
// } else {
// volume.minNumber.toInt().toFloat()
// }
// } catch (e: NumberFormatException) {
// 0f
// }
// -volumeNum - KavitaConstants.VOLUME_NUMBER_OFFSET
// }
// }
//
// url = when {
// type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null ->
// "volume_${volume.id}"
// else -> "chapter_${chapter.id}"
// }
type == ChapterType.SingleFileVolume -> { // Handle decimal chapter numbers properly
// For standalone volumes, use positive numbers chapter_number = if (type == ChapterType.SingleFileVolume) {
if (volume.minNumber % 1 != 0.0) {
volume.minNumber.toFloat() volume.minNumber.toFloat()
} else {
volume.minNumber.toInt().toFloat()
} }
} else if (chapter.minNumber % 1 != 0.0) {
// For regular chapters chapter.minNumber.toFloat()
type != ChapterType.SingleFileVolume && type != ChapterType.Special -> { } else {
// Handle decimal chapter numbers chapter.minNumber.toInt().toFloat()
if (chapter.minNumber % 1 != 0.0) {
chapter.minNumber.toFloat()
} else {
chapter.minNumber.toInt().toFloat()
}
}
// For volumes/specials in mixed content, place them below chapter 0
else -> {
val volumeNum = try {
if (volume.minNumber % 1 != 0.0) {
volume.minNumber.toFloat()
} else {
volume.minNumber.toInt().toFloat()
}
} catch (e: NumberFormatException) {
0f
}
-volumeNum - KavitaConstants.VOLUME_NUMBER_OFFSET
}
} }
url = when { url = when {
@@ -248,11 +268,14 @@ class KavitaHelper {
// Helper function to remove .0 from whole numbers while preserving actual decimals // Helper function to remove .0 from whole numbers while preserving actual decimals
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
private fun removeTrailingZero(number: Double): String { private fun removeTrailingZero(number: Double): String {
return if (number % 1 == 0.0) { return if (number.isInfinite() || number.isNaN()) {
number.toInt().toString() number.toString()
} else { } else {
// Use String.format to avoid scientific notation for very small decimals val formatted = String.format("%.10f", number)
String.format("%.10f", number).replace(",", ".").trimEnd('0').trimEnd('.') .replace(",", ".")
.trimEnd('0')
.trimEnd('.')
formatted.ifEmpty { "0" }
} }
} }