From 67704dce6dd5255a464c2380900948a3507fb030 Mon Sep 17 00:00:00 2001 From: "Mio." Date: Tue, 15 Jul 2025 02:36:10 +0200 Subject: [PATCH] consistency for volumes --- .../tachiyomi/extension/all/kavita/Kavita.kt | 38 +++++++----- .../extension/all/kavita/KavitaConstants.kt | 2 +- .../extension/all/kavita/KavitaHelper.kt | 60 ++++++++++++------- 3 files changed, 62 insertions(+), 38 deletions(-) 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 1a81c9eb..ad890790 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 @@ -145,7 +145,6 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou when { // Handle both API-style and web-style URLs manga.url.contains("/series/") || manga.url.contains("/Series/") -> { - // Extract series ID from URL (works for both formats) val seriesId = manga.url .substringAfterLast("/series/") .substringAfterLast("/Series/") @@ -166,7 +165,6 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou // Use web-friendly URL format "$baseUrl/library/$libraryId/series/$seriesId" } else { - // Fallback to original URL format if we don't have libraryId "$baseUrl/library/1/series/$seriesId" } } @@ -1439,7 +1437,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou } ?: false volumes.forEach { volume -> - // This fixes specials being parsed as volumes + // This fixes specials being parsed as volumes sometimes if (volume.chapters.size == 1 && volume.minNumber.toInt() != KavitaConstants.SPECIAL_NUMBER) { val chapter = volume.chapters.first() val sChapter = helper.chapterFromVolume( @@ -1450,11 +1448,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou isWebtoon = isWebtoon, mangaTitle = seriesName, ) - sChapter.url = "/Chapter/${chapter.id}" - sChapter.name = when { - volume.name.any { it.isLetter() } -> "v${helper.formatVolumeNumber(volume)} - ${volume.name}" - else -> "Volume ${helper.formatVolumeNumber(volume)}" - } + sChapter.url = "/Chapter/${chapter.id}" // Needed to read volumes as chapters sChapter.chapter_number = volume.minNumber.toFloat() sChapter.scanlator = if (isWebtoon) "Season" else "Volume" volumeItems.add(sChapter) @@ -1469,6 +1463,8 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou mangaTitle = seriesName, ) if (type == ChapterType.SingleFileVolume) { + sChapter.url = "/Chapter/${chapter.id}" // Needed to read volumes as chapters + sChapter.chapter_number = volume.minNumber.toFloat() volumeItems.add(sChapter) } else { chapters.add(sChapter) @@ -1488,11 +1484,14 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou // Case 3: Mixed content - chapters first, then volumes else -> { - // Convert volume numbers to negative for proper sorting - volumeItems.forEach { it.chapter_number = -it.chapter_number } + volumeItems.forEachIndexed { index, it -> + if (it.chapter_number <= 0f) { + it.chapter_number = chapters.size + index + 1f + } + } ( chapters.sortedByDescending { it.chapter_number } + - volumeItems.sortedBy { it.chapter_number } + volumeItems.sortedByDescending { it.chapter_number } ) } } @@ -1521,6 +1520,12 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou return GET("$apiUrl/${chapterItem.chapterId}", headersBuilder().build()) } + // Handle volume URLs + if (chapter.url.startsWith("Volume/")) { + val volumeId = chapter.url.substringAfter("Volume/").substringBefore("_") + return GET("$apiUrl/Volume/$volumeId", headersBuilder().build()) + } + // Original handling for regular chapters val chapterId = chapter.url.substringBefore("_") return GET("$apiUrl/$chapterId", headersBuilder().build()) @@ -1581,10 +1586,13 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou // For normal titles return Observable.fromCallable { - // Check if this is a volume URL (contains "Volume/") - if (chapter.url.contains("/Volume/")) { - val volumeId = chapter.url.substringAfter("/Volume/").substringBefore("?").toIntOrNull() - ?: throw IOException("Invalid volume ID") + // Check if this is a volume URL + if (chapter.url.contains("/Volume/") || chapter.url.startsWith("volume_")) { + val volumeId = when { + chapter.url.startsWith("volume_") -> chapter.url.substringAfter("volume_").substringBefore("_") + chapter.url.contains("/Volume/") -> chapter.url.substringAfter("/Volume/").substringBefore("_") + else -> throw IOException("Invalid volume URL format") + } // Get all pages in this volume val volumeRequest = GET("$apiUrl/Volume/$volumeId", headersBuilder().build()) diff --git a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaConstants.kt b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaConstants.kt index 60eea756..8b2fdabf 100644 --- a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaConstants.kt +++ b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaConstants.kt @@ -5,7 +5,7 @@ object KavitaConstants { const val UNNUMBERED_VOLUME = -100000 const val UNNUMBERED_VOLUME_STR = "-100000" const val SPECIAL_NUMBER = 100_000 - const val VOLUME_NUMBER_OFFSET = 100000f + const val VOLUME_NUMBER_OFFSET = 10000f val PERSON_ROLES = listOf( "Writer", "Penciller", "Inker", "Colorist", diff --git a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaHelper.kt b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaHelper.kt index 4f80291d..1cdb9c39 100644 --- a/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaHelper.kt +++ b/src/all/kavita/src/eu/kanade/tachiyomi/extension/all/kavita/KavitaHelper.kt @@ -104,8 +104,8 @@ class KavitaHelper { ChapterType.SingleFileVolume -> { cleanChapterTitle( when { - volume.name.any { it.isLetter() } -> "v${volume.minNumber} - ${volume.name}" - else -> "Volume ${volume.minNumber}" + volume.name.any { it.isLetter() } -> "v${formatVolumeNumber(volume)} - ${volume.name}" + else -> "Volume ${formatVolumeNumber(volume)}" }, ChapterTitleContext( mangaTitle = mangaTitle, @@ -164,7 +164,6 @@ class KavitaHelper { } // @todo Keeping this in case we're switching the logic here this after the Mihon PR - // Handle decimal chapter numbers properly // chapter_number = when { // type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null -> // singleFileVolumeNumber.toFloat() @@ -206,16 +205,38 @@ class KavitaHelper { // } // Handle decimal chapter numbers properly - chapter_number = if (type == ChapterType.SingleFileVolume) { - if (volume.minNumber % 1 != 0.0) { + chapter_number = when { + type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null -> + volume.minNumber.toFloat() + + type == ChapterType.SingleFileVolume -> { + // For standalone volumes, use positive numbers volume.minNumber.toFloat() - } else { - volume.minNumber.toInt().toFloat() } - } else if (chapter.minNumber % 1 != 0.0) { - chapter.minNumber.toFloat() - } else { - chapter.minNumber.toInt().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 { @@ -224,11 +245,9 @@ class KavitaHelper { else -> "chapter_${chapter.id}" } - // Handle multi-file chapters if (chapter.fileCount > 1) { - // Add small offset to maintain order while keeping the main chapter number + // salt/offset to recognize chapters with new merged part-chapters as new and hence unread chapter_number += 0.001f * chapter.fileCount - // Append file count to URL for reference url = "${url}_${chapter.fileCount}" } @@ -242,7 +261,7 @@ class KavitaHelper { } } - fun formatVolumeNumber(volume: VolumeDto): String { + private fun formatVolumeNumber(volume: VolumeDto): String { return when { volume.maxNumber > volume.minNumber -> "${removeTrailingZero(volume.minNumber)}-${removeTrailingZero(volume.maxNumber)}" @@ -268,14 +287,11 @@ class KavitaHelper { // Helper function to remove .0 from whole numbers while preserving actual decimals @SuppressLint("DefaultLocale") private fun removeTrailingZero(number: Double): String { - return if (number.isInfinite() || number.isNaN()) { - number.toString() + return if (number % 1 == 0.0) { + number.toInt().toString() } else { - val formatted = String.format("%.10f", number) - .replace(",", ".") - .trimEnd('0') - .trimEnd('.') - formatted.ifEmpty { "0" } + // Use String.format to avoid scientific notation for very small decimals + String.format("%.10f", number).replace(",", ".").trimEnd('0').trimEnd('.') } }