diff --git a/CHANGELOG.md b/CHANGELOG.md index 4318f5151..6d8cb0027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co - Add `id:` prefix search to remaining trackers (AniList, Bangumi, Kitsu, MangaUpdates, Shikimori, and Hikka) ([@MajorTanya](https://github.com/MajorTanya)) ([#3776](https://github.com/mihonapp/mihon/pull/3776)) - Allow `id:` to search for slugs on Kitsu ([@MajorTanya](https://github.com/MajorTanya)) ([#3792](https://github.com/mihonapp/mihon/pull/3792)) - Add support for using the user's chosen rating system for Kitsu ([@MajorTanya](https://github.com/MajorTanya)) ([#3818](https://github.com/mihonapp/mihon/pull/3818)) +- Add support for the Year, Month, and Day fields in ComicInfo.xml files for chapter dating ([@MajorTanya](https://github.com/MajorTanya)) ([#3967](https://github.com/mihonapp/mihon/pull/3967)) ### Improved - Show updates and upcoming filter icon as active for categories ([@Secozzi](https://github.com/Secozzi)) ([#3772](https://github.com/mihonapp/mihon/pull/3772)) diff --git a/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt b/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt index 5eb4fed81..a3c03ad35 100644 --- a/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt +++ b/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt @@ -5,6 +5,9 @@ import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode +import kotlinx.datetime.TimeZone +import kotlinx.datetime.number +import kotlinx.datetime.toLocalDateTime import mihon.app.di.appGraph import tachiyomi.core.common.preference.TriState import tachiyomi.core.metadata.comicinfo.ComicInfo @@ -13,6 +16,7 @@ import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.manga.model.Manga import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import kotlin.time.Instant // TODO: move these into the domain model val Manga.readingMode: Long @@ -85,30 +89,41 @@ fun getComicInfo( urls: List, categories: List?, sourceName: String, -) = ComicInfo( - title = ComicInfo.Title(chapter.name), - series = ComicInfo.Series(manga.title), - number = chapter.chapterNumber.takeIf { it >= 0 }?.let { - if ((it.rem(1) == 0.0)) { - ComicInfo.Number(it.toInt().toString()) - } else { - ComicInfo.Number(it.toString()) +): ComicInfo { + val date = chapter.dateUpload + .takeIf { it != 0L } + ?.let { + Instant.fromEpochMilliseconds(it).toLocalDateTime(TimeZone.currentSystemDefault()) } - }, - web = ComicInfo.Web(urls.joinToString(" ")), - summary = manga.description?.let { ComicInfo.Summary(it) }, - writer = manga.author?.let { ComicInfo.Writer(it) }, - penciller = manga.artist?.let { ComicInfo.Penciller(it) }, - translator = chapter.scanlator?.let { ComicInfo.Translator(it) }, - genre = manga.genre?.let { ComicInfo.Genre(it.joinToString()) }, - publishingStatus = ComicInfo.PublishingStatusTachiyomi( - ComicInfoPublishingStatus.toComicInfoValue(manga.status), - ), - categories = categories?.let { ComicInfo.CategoriesTachiyomi(it.joinToString()) }, - source = ComicInfo.SourceMihon(sourceName), - inker = null, - colorist = null, - letterer = null, - coverArtist = null, - tags = null, -) + + return ComicInfo( + title = ComicInfo.Title(chapter.name), + series = ComicInfo.Series(manga.title), + number = chapter.chapterNumber.takeIf { it >= 0 }?.let { + if ((it.rem(1) == 0.0)) { + ComicInfo.Number(it.toInt().toString()) + } else { + ComicInfo.Number(it.toString()) + } + }, + web = ComicInfo.Web(urls.joinToString(" ")), + summary = manga.description?.let { ComicInfo.Summary(it) }, + writer = manga.author?.let { ComicInfo.Writer(it) }, + penciller = manga.artist?.let { ComicInfo.Penciller(it) }, + translator = chapter.scanlator?.let { ComicInfo.Translator(it) }, + genre = manga.genre?.let { ComicInfo.Genre(it.joinToString()) }, + publishingStatus = ComicInfo.PublishingStatusTachiyomi( + ComicInfoPublishingStatus.toComicInfoValue(manga.status), + ), + categories = categories?.let { ComicInfo.CategoriesTachiyomi(it.joinToString()) }, + source = ComicInfo.SourceMihon(sourceName), + inker = null, + colorist = null, + letterer = null, + coverArtist = null, + tags = null, + year = date?.year?.let { ComicInfo.Year(it) }, + month = date?.month?.number?.let { ComicInfo.Month(it) }, + day = date?.day?.let { ComicInfo.Day(it) }, + ) +} diff --git a/core-metadata/src/main/java/tachiyomi/core/metadata/comicinfo/ComicInfo.kt b/core-metadata/src/main/java/tachiyomi/core/metadata/comicinfo/ComicInfo.kt index 5d4d92277..c522394ab 100644 --- a/core-metadata/src/main/java/tachiyomi/core/metadata/comicinfo/ComicInfo.kt +++ b/core-metadata/src/main/java/tachiyomi/core/metadata/comicinfo/ComicInfo.kt @@ -26,6 +26,9 @@ fun SManga.getComicInfo() = ComicInfo( letterer = null, coverArtist = null, tags = null, + year = null, + month = null, + day = null, categories = null, source = null, ) @@ -80,6 +83,9 @@ data class ComicInfo( val genre: Genre?, val tags: Tags?, val web: Web?, + val year: Year?, + val month: Month?, + val day: Day?, val publishingStatus: PublishingStatusTachiyomi?, val categories: CategoriesTachiyomi?, val source: SourceMihon?, @@ -148,6 +154,18 @@ data class ComicInfo( @XmlSerialName("Web", "", "") data class Web(@XmlValue(true) val value: String = "") + @Serializable + @XmlSerialName("Year", "", "") + data class Year(@XmlValue(true) val value: Int = -1) + + @Serializable + @XmlSerialName("Month", "", "") + data class Month(@XmlValue(true) val value: Int = -1) + + @Serializable + @XmlSerialName("Day", "", "") + data class Day(@XmlValue(true) val value: Int = -1) + // The spec doesn't have a good field for this @Serializable @XmlSerialName("PublishingStatusTachiyomi", "http://www.w3.org/2001/XMLSchema", "ty") diff --git a/source-local/build.gradle.kts b/source-local/build.gradle.kts index a8f6d3b3e..a5075b496 100644 --- a/source-local/build.gradle.kts +++ b/source-local/build.gradle.kts @@ -31,4 +31,6 @@ dependencies { implementation(libs.injekt) implementation(libs.jsoup) + + implementation(libs.kotlinx.datetime) } diff --git a/source-local/src/main/kotlin/tachiyomi/source/local/LocalSource.kt b/source-local/src/main/kotlin/tachiyomi/source/local/LocalSource.kt index 28e111861..1ccb356b9 100644 --- a/source-local/src/main/kotlin/tachiyomi/source/local/LocalSource.kt +++ b/source-local/src/main/kotlin/tachiyomi/source/local/LocalSource.kt @@ -17,6 +17,9 @@ import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.supervisorScope +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.atStartOfDayIn import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream import logcat.LogPriority @@ -267,6 +270,19 @@ class LocalSource( comicInfo.title?.let { chapter.name = it.value } comicInfo.number?.value?.toFloatOrNull()?.let { chapter.chapter_number = it } comicInfo.translator?.let { chapter.scanlator = it.value } + + // only bother with partial dates if the year is not null, abandon date parsing otherwise + val year = comicInfo.year?.value?.takeIf { it > 0 }?.toString()?.padStart(2, '0') ?: return + val month = (comicInfo.month?.value?.coerceIn(1, 31) ?: 1).toString().padStart(2, '0') + val day = (comicInfo.day?.value?.coerceIn(1, 31) ?: 1).toString().padStart(2, '0') + + val dateInstant = try { + LocalDate.parse("$year-$month-$day").atStartOfDayIn(TimeZone.currentSystemDefault()) + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + null + } + dateInstant?.let { chapter.date_upload = it.toEpochMilliseconds() } } // Chapters