consistency for volumes

This commit is contained in:
Mio.
2025-07-15 02:36:10 +02:00
parent 94cbfbd394
commit 67704dce6d
3 changed files with 62 additions and 38 deletions

View File

@@ -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())

View File

@@ -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",

View File

@@ -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) {
chapter_number = when {
type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null ->
volume.minNumber.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()
}
} else if (chapter.minNumber % 1 != 0.0) {
chapter.minNumber.toFloat()
} else {
chapter.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('.')
}
}