Add handling for previously unhandled preferences (delegated MD) (jobobby04/TachiyomiSY#1524)

* Include romanized titles of the original language in description

* Implement handling for `finalChapterInDesc` preference.

* Handle `preferExtensionLangTitle` preference when fetching manga details.

* Address some warnings, clean up unused code and spotless apply.

(cherry picked from commit 582d0ef1219ae2a2e1dd275d969b35a2e5511a73)

Co-authored-by: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com>
This commit is contained in:
NGB-Was-Taken 2025-12-12 00:43:56 +05:45 committed by Cuong-Tran
parent 8828da80b7
commit db22e8add3
6 changed files with 216 additions and 142 deletions

View file

@ -168,11 +168,11 @@ class MdList(id: Long) : BaseTracker(id, "MDList") {
val manga = mdex.getMangaMetadata(track.toDbTrack()) val manga = mdex.getMangaMetadata(track.toDbTrack())
TrackMangaMetadata( TrackMangaMetadata(
remoteId = 0, remoteId = 0,
title = manga?.title, title = manga.title,
thumbnailUrl = manga?.thumbnail_url, // Doesn't load the actual cover because of Refer header thumbnailUrl = manga.thumbnail_url, // Doesn't load the actual cover because of Refer header
description = manga?.description, description = manga.description,
authors = manga?.author, authors = manga.author,
artists = manga?.artist, artists = manga.artist,
) )
} }
} }

View file

@ -92,6 +92,8 @@ class MangaDex(delegate: HttpSource, val context: Context) :
private fun coverQuality() = sourcePreferences.getString(getCoverQualityPrefKey(mdLang.lang), "").orEmpty() private fun coverQuality() = sourcePreferences.getString(getCoverQualityPrefKey(mdLang.lang), "").orEmpty()
private fun tryUsingFirstVolumeCover() = sourcePreferences.getBoolean(getTryUsingFirstVolumeCoverKey(mdLang.lang), false) private fun tryUsingFirstVolumeCover() = sourcePreferences.getBoolean(getTryUsingFirstVolumeCoverKey(mdLang.lang), false)
private fun altTitlesInDesc() = sourcePreferences.getBoolean(getAltTitlesInDescKey(mdLang.lang), false) private fun altTitlesInDesc() = sourcePreferences.getBoolean(getAltTitlesInDescKey(mdLang.lang), false)
private fun finalChapterInDesc() = sourcePreferences.getBoolean(getFinalChapterInDescPrefKey(mdLang.lang), false)
private fun preferExtensionLangTitle() = sourcePreferences.getBoolean(getPreferExtensionLangTitlePrefKey(mdLang.extLang), true)
private val mangadexService by lazy { private val mangadexService by lazy {
MangaDexService(client) MangaDexService(client)
@ -109,7 +111,7 @@ class MangaDex(delegate: HttpSource, val context: Context) :
FollowsHandler(mdLang.lang, mangadexAuthService) FollowsHandler(mdLang.lang, mangadexAuthService)
} }
private val mangaHandler by lazy { private val mangaHandler by lazy {
MangaHandler(mdLang.lang, mangadexService, apiMangaParser, followsHandler) MangaHandler(mdLang.lang, mangadexService, apiMangaParser)
} }
private val similarHandler by lazy { private val similarHandler by lazy {
SimilarHandler(mdLang.lang, mangadexService, similarService) SimilarHandler(mdLang.lang, mangadexService, similarService)
@ -194,11 +196,27 @@ class MangaDex(delegate: HttpSource, val context: Context) :
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getMangaDetails")) @Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getMangaDetails"))
override fun fetchMangaDetails(manga: SManga): Observable<SManga> { override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return mangaHandler.fetchMangaDetailsObservable(manga, id, coverQuality(), tryUsingFirstVolumeCover(), altTitlesInDesc()) return mangaHandler.fetchMangaDetailsObservable(
manga,
id,
coverQuality(),
tryUsingFirstVolumeCover(),
altTitlesInDesc(),
finalChapterInDesc(),
preferExtensionLangTitle(),
)
} }
override suspend fun getMangaDetails(manga: SManga): SManga { override suspend fun getMangaDetails(manga: SManga): SManga {
return mangaHandler.getMangaDetails(manga, id, coverQuality(), tryUsingFirstVolumeCover(), altTitlesInDesc()) return mangaHandler.getMangaDetails(
manga,
id,
coverQuality(),
tryUsingFirstVolumeCover(),
altTitlesInDesc(),
finalChapterInDesc(),
preferExtensionLangTitle(),
)
} }
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getChapterList")) @Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getChapterList"))
@ -243,8 +261,21 @@ class MangaDex(delegate: HttpSource, val context: Context) :
override fun newMetaInstance() = MangaDexSearchMetadata() override fun newMetaInstance() = MangaDexSearchMetadata()
override suspend fun parseIntoMetadata(metadata: MangaDexSearchMetadata, input: Triple<MangaDto, List<String>, StatisticsMangaDto>) { override suspend fun parseIntoMetadata(
apiMangaParser.parseIntoMetadata(metadata, input.first, input.second, input.third, null, coverQuality(), altTitlesInDesc()) metadata: MangaDexSearchMetadata,
input: Triple<MangaDto, List<String>, StatisticsMangaDto>,
) {
apiMangaParser.parseIntoMetadata(
metadata,
input.first,
input.second,
input.third,
null,
coverQuality(),
altTitlesInDesc(),
finalChapterInDesc(),
preferExtensionLangTitle(),
)
} }
// LoginSource methods // LoginSource methods
@ -298,10 +329,6 @@ class MangaDex(delegate: HttpSource, val context: Context) :
return followsHandler.updateRating(track) return followsHandler.updateRating(track)
} }
suspend fun getTrackingAndMangaInfo(track: Track): Pair<Track, MangaDexSearchMetadata?> {
return mangaHandler.getTrackingInfo(track)
}
// RandomMangaSource method // RandomMangaSource method
override suspend fun fetchRandomMangaUrl(): String { override suspend fun fetchRandomMangaUrl(): String {
return mangaHandler.fetchRandomMangaId() return mangaHandler.fetchRandomMangaId()
@ -315,51 +342,62 @@ class MangaDex(delegate: HttpSource, val context: Context) :
return similarHandler.getRelated(manga) return similarHandler.getRelated(manga)
} }
suspend fun getMangaMetadata(track: Track): SManga? { suspend fun getMangaMetadata(track: Track): SManga {
return mangaHandler.getMangaMetadata(track, id, coverQuality(), tryUsingFirstVolumeCover(), altTitlesInDesc()) return mangaHandler.getMangaMetadata(
track,
id,
coverQuality(),
tryUsingFirstVolumeCover(),
altTitlesInDesc(),
finalChapterInDesc(),
preferExtensionLangTitle(),
)
} }
companion object { companion object {
private const val dataSaverPref = "dataSaverV5" private const val dataSaverPref = "dataSaverV5"
fun getDataSaverPreferenceKey(dexLang: String): String { fun getDataSaverPreferenceKey(dexLang: String): String {
return "${dataSaverPref}_$dexLang" return "${dataSaverPref}_$dexLang"
} }
private const val standardHttpsPortPref = "usePort443" private const val standardHttpsPortPref = "usePort443"
fun getStandardHttpsPreferenceKey(dexLang: String): String { fun getStandardHttpsPreferenceKey(dexLang: String): String {
return "${standardHttpsPortPref}_$dexLang" return "${standardHttpsPortPref}_$dexLang"
} }
private const val blockedGroupsPref = "blockedGroups" private const val blockedGroupsPref = "blockedGroups"
fun getBlockedGroupsPrefKey(dexLang: String): String { fun getBlockedGroupsPrefKey(dexLang: String): String {
return "${blockedGroupsPref}_$dexLang" return "${blockedGroupsPref}_$dexLang"
} }
private const val blockedUploaderPref = "blockedUploader" private const val blockedUploaderPref = "blockedUploader"
fun getBlockedUploaderPrefKey(dexLang: String): String { fun getBlockedUploaderPrefKey(dexLang: String): String {
return "${blockedUploaderPref}_$dexLang" return "${blockedUploaderPref}_$dexLang"
} }
private const val coverQualityPref = "thumbnailQuality" private const val coverQualityPref = "thumbnailQuality"
fun getCoverQualityPrefKey(dexLang: String): String { fun getCoverQualityPrefKey(dexLang: String): String {
return "${coverQualityPref}_$dexLang" return "${coverQualityPref}_$dexLang"
} }
private const val tryUsingFirstVolumeCover = "tryUsingFirstVolumeCover" private const val tryUsingFirstVolumeCoverPref = "tryUsingFirstVolumeCover"
fun getTryUsingFirstVolumeCoverKey(dexLang: String): String { fun getTryUsingFirstVolumeCoverKey(dexLang: String): String {
return "${tryUsingFirstVolumeCover}_$dexLang" return "${tryUsingFirstVolumeCoverPref}_$dexLang"
} }
private const val altTitlesInDesc = "altTitlesInDesc" private const val altTitlesInDescPref = "altTitlesInDesc"
fun getAltTitlesInDescKey(dexLang: String): String { fun getAltTitlesInDescKey(dexLang: String): String {
return "${altTitlesInDesc}_$dexLang" return "${altTitlesInDescPref}_$dexLang"
}
private const val finalChapterInDescPref = "finalChapterInDesc"
fun getFinalChapterInDescPrefKey(dexLang: String): String {
return "${finalChapterInDescPref}_$dexLang"
}
private const val preferExtensionLangTitlePref = "preferExtensionLangTitle"
fun getPreferExtensionLangTitlePrefKey(dexLang: String): String {
return "${preferExtensionLangTitlePref}_$dexLang"
} }
} }
} }

View file

@ -44,6 +44,8 @@ class ApiMangaParser(
coverFileName: String?, coverFileName: String?,
coverQuality: String, coverQuality: String,
altTitlesInDesc: Boolean, altTitlesInDesc: Boolean,
finalChapterInDesc: Boolean,
preferExtensionLangTitle: Boolean,
): SManga { ): SManga {
val mangaId = getManga.await(manga.url, sourceId)?.id val mangaId = getManga.await(manga.url, sourceId)?.id
val metadata = if (mangaId != null) { val metadata = if (mangaId != null) {
@ -53,7 +55,17 @@ class ApiMangaParser(
newMetaInstance() newMetaInstance()
} }
parseIntoMetadata(metadata, input, simpleChapters, statistics, coverFileName, coverQuality, altTitlesInDesc) parseIntoMetadata(
metadata,
input,
simpleChapters,
statistics,
coverFileName,
coverQuality,
altTitlesInDesc,
finalChapterInDesc,
preferExtensionLangTitle,
)
if (mangaId != null) { if (mangaId != null) {
metadata.mangaId = mangaId metadata.mangaId = mangaId
insertFlatMetadata.await(metadata.flatten()) insertFlatMetadata.await(metadata.flatten())
@ -70,13 +82,24 @@ class ApiMangaParser(
coverFileName: String?, coverFileName: String?,
coverQuality: String, coverQuality: String,
altTitlesInDesc: Boolean, altTitlesInDesc: Boolean,
finalChapterInDesc: Boolean,
preferExtensionLangTitle: Boolean,
) { ) {
with(metadata) { with(metadata) {
try { try {
val mangaAttributesDto = mangaDto.data.attributes val mangaAttributesDto = mangaDto.data.attributes
mdUuid = mangaDto.data.id mdUuid = mangaDto.data.id
title = MdUtil.getTitleFromManga(mangaAttributesDto, lang) title = MdUtil.getTitleFromManga(mangaAttributesDto, lang, preferExtensionLangTitle)
altTitles = mangaAttributesDto.altTitles.mapNotNull { it[lang] }.nullIfEmpty() altTitles = mangaAttributesDto.altTitles
// KMK -->
.mapNotNull { langMap ->
langMap
.filter { it.key == lang || it.key == "${mangaAttributesDto.originalLanguage}-ro" }
.takeIf { it.isNotEmpty() }
}
.flatMap { it.values }
// KMK <--
.nullIfEmpty()
val mangaRelationshipsDto = mangaDto.data.relationships val mangaRelationshipsDto = mangaDto.data.relationships
cover = if (!coverFileName.isNullOrEmpty()) { cover = if (!coverFileName.isNullOrEmpty()) {
@ -96,9 +119,19 @@ class ApiMangaParser(
originalLanguage = mangaAttributesDto.originalLanguage, originalLanguage = mangaAttributesDto.originalLanguage,
).orEmpty() ).orEmpty()
val cleanDesc = MdUtil.cleanDescription(rawDesc) description = MdUtil.cleanDescription(rawDesc)
.let { if (altTitlesInDesc) MdUtil.addAltTitleToDesc(it, altTitles) else it }
description = if (altTitlesInDesc) MdUtil.addAltTitleToDesc(cleanDesc, altTitles) else cleanDesc .let {
if (finalChapterInDesc) {
MdUtil.addFinalChapterToDesc(
it,
mangaAttributesDto.lastVolume,
mangaAttributesDto.lastChapter,
)
} else {
it
}
}
authors = mangaRelationshipsDto.filter { relationshipDto -> authors = mangaRelationshipsDto.filter { relationshipDto ->
relationshipDto.type.equals(MdConstants.Types.author, true) relationshipDto.type.equals(MdConstants.Types.author, true)
@ -148,7 +181,11 @@ class ApiMangaParser(
mangaAttributesDto.contentRating mangaAttributesDto.contentRating
?.takeUnless { it == "safe" } ?.takeUnless { it == "safe" }
?.let { ?.let {
RaisedTag("Content Rating", it.capitalize(Locale.US), MangaDexSearchMetadata.TAG_TYPE_DEFAULT) RaisedTag(
"Content Rating",
it.capitalize(Locale.US),
MangaDexSearchMetadata.TAG_TYPE_DEFAULT,
)
}, },
) )

View file

@ -8,7 +8,6 @@ import exh.md.service.MangaDexService
import exh.md.utils.MdConstants import exh.md.utils.MdConstants
import exh.md.utils.MdUtil import exh.md.utils.MdUtil
import exh.md.utils.mdListCall import exh.md.utils.mdListCall
import exh.metadata.metadata.MangaDexSearchMetadata
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
@ -21,7 +20,6 @@ class MangaHandler(
private val lang: String, private val lang: String,
private val service: MangaDexService, private val service: MangaDexService,
private val apiMangaParser: ApiMangaParser, private val apiMangaParser: ApiMangaParser,
private val followsHandler: FollowsHandler,
) { ) {
suspend fun getMangaDetails( suspend fun getMangaDetails(
manga: SManga, manga: SManga,
@ -29,6 +27,8 @@ class MangaHandler(
coverQuality: String, coverQuality: String,
tryUsingFirstVolumeCover: Boolean, tryUsingFirstVolumeCover: Boolean,
altTitlesInDesc: Boolean, altTitlesInDesc: Boolean,
finalChapterInDesc: Boolean,
preferExtensionLangTitle: Boolean,
): SManga { ): SManga {
return coroutineScope { return coroutineScope {
val mangaId = MdUtil.getMangaId(manga.url) val mangaId = MdUtil.getMangaId(manga.url)
@ -55,13 +55,31 @@ class MangaHandler(
coverFileName?.await(), coverFileName?.await(),
coverQuality, coverQuality,
altTitlesInDesc, altTitlesInDesc,
finalChapterInDesc,
preferExtensionLangTitle,
) )
} }
} }
fun fetchMangaDetailsObservable(manga: SManga, sourceId: Long, coverQuality: String, tryUsingFirstVolumeCover: Boolean, altTitlesInDesc: Boolean): Observable<SManga> { fun fetchMangaDetailsObservable(
manga: SManga,
sourceId: Long,
coverQuality: String,
tryUsingFirstVolumeCover: Boolean,
altTitlesInDesc: Boolean,
finalChapterInDesc: Boolean,
preferExtensionLangTitle: Boolean,
): Observable<SManga> {
return runAsObservable { return runAsObservable {
getMangaDetails(manga, sourceId, coverQuality, tryUsingFirstVolumeCover, altTitlesInDesc) getMangaDetails(
manga,
sourceId,
coverQuality,
tryUsingFirstVolumeCover,
altTitlesInDesc,
finalChapterInDesc,
preferExtensionLangTitle,
)
} }
} }
@ -92,11 +110,15 @@ class MangaHandler(
} }
private fun getGroupMap(results: List<ChapterDataDto>): Map<String, String> { private fun getGroupMap(results: List<ChapterDataDto>): Map<String, String> {
return results.map { chapter -> chapter.relationships } return results
.flatten() .flatMap { it.relationships }
.filter { it.type == MdConstants.Types.scanlator } .filter { it.type == MdConstants.Types.scanlator }
.map { it.id to it.attributes!!.name!! } // KMK -->
.mapNotNull { relationship ->
relationship.attributes?.name?.let { relationship.id to it }
}
.toMap() .toMap()
// KMK <--
} }
suspend fun fetchRandomMangaId(): String { suspend fun fetchRandomMangaId(): String {
@ -105,23 +127,6 @@ class MangaHandler(
} }
} }
suspend fun getTrackingInfo(track: Track): Pair<Track, MangaDexSearchMetadata?> {
return withIOContext {
/*val metadata = async {
val mangaUrl = MdUtil.buildMangaUrl(MdUtil.getMangaId(track.tracking_url))
val manga = MangaInfo(mangaUrl, track.title)
val response = client.newCall(mangaRequest(manga)).await()
val metadata = MangaDexSearchMetadata()
apiMangaParser.parseIntoMetadata(metadata, response, emptyList())
metadata
}*/
val remoteTrack = async {
followsHandler.fetchTrackingInfo(track.tracking_url)
}
remoteTrack.await() to null
}
}
suspend fun getMangaFromChapterId(chapterId: String): String? { suspend fun getMangaFromChapterId(chapterId: String): String? {
return withIOContext { return withIOContext {
apiMangaParser.chapterParseForMangaId(service.viewChapter(chapterId)) apiMangaParser.chapterParseForMangaId(service.viewChapter(chapterId))
@ -134,7 +139,9 @@ class MangaHandler(
coverQuality: String, coverQuality: String,
tryUsingFirstVolumeCover: Boolean, tryUsingFirstVolumeCover: Boolean,
altTitlesInDesc: Boolean, altTitlesInDesc: Boolean,
): SManga? { finalChapterInDesc: Boolean,
preferExtensionLangTitle: Boolean,
): SManga {
return withIOContext { return withIOContext {
val mangaId = MdUtil.getMangaId(track.tracking_url) val mangaId = MdUtil.getMangaId(track.tracking_url)
val response = service.viewManga(mangaId) val response = service.viewManga(mangaId)
@ -154,6 +161,8 @@ class MangaHandler(
coverFileName, coverFileName,
coverQuality, coverQuality,
altTitlesInDesc, altTitlesInDesc,
finalChapterInDesc,
preferExtensionLangTitle,
) )
} }
} }

View file

@ -5,19 +5,15 @@ package exh.md.utils
import android.app.Application import android.app.Application
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.track.service.TrackPreferences import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.mdlist.MdList import eu.kanade.tachiyomi.data.track.mdlist.MdList
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.all.MangaDex import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.util.PkceUtil import eu.kanade.tachiyomi.util.PkceUtil
import exh.md.dto.MangaAttributesDto import exh.md.dto.MangaAttributesDto
import exh.md.dto.MangaDataDto import exh.md.dto.MangaDataDto
import exh.source.getMainSource import exh.source.getMainSource
import exh.util.dropBlank
import exh.util.floor
import exh.util.nullIfZero import exh.util.nullIfZero
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.FormBody import okhttp3.FormBody
@ -27,7 +23,9 @@ import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import org.jsoup.parser.Parser import org.jsoup.parser.Parser
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.sy.SYMR
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -41,21 +39,10 @@ class MdUtil {
const val baseUrl = "https://mangadex.org" const val baseUrl = "https://mangadex.org"
const val chapterSuffix = "/chapter/" const val chapterSuffix = "/chapter/"
const val similarCacheMapping = "https://api.similarmanga.com/mapping/mdex2search.csv"
const val similarCacheMangas = "https://api.similarmanga.com/manga/"
const val similarBaseApi = "https://api.similarmanga.com/similar/" const val similarBaseApi = "https://api.similarmanga.com/similar/"
const val groupSearchUrl = "$baseUrl/groups/0/1/"
const val reportUrl = "https://api.mangadex.network/report"
const val mdAtHomeTokenLifespan = 10 * 60 * 1000
const val mangaLimit = 20 const val mangaLimit = 20
/**
* Get the manga offset pages are 1 based, so subtract 1
*/
fun getMangaListOffset(page: Int): String = (mangaLimit * (page - 1)).toString()
val jsonParser = val jsonParser =
Json { Json {
isLenient = true isLenient = true
@ -67,15 +54,8 @@ class MdUtil {
private const val scanlatorSeparator = " & " private const val scanlatorSeparator = " & "
const val contentRatingSafe = "safe" val markdownLinksRegex = "\\[([^]]+)]\\(([^)]+)\\)".toRegex()
const val contentRatingSuggestive = "suggestive" val markdownItalicBoldRegex = "\\*+\\s*([^*]*)\\s*\\*+".toRegex()
const val contentRatingErotica = "erotica"
const val contentRatingPornographic = "pornographic"
val validOneShotFinalChapters = listOf("0", "1")
val markdownLinksRegex = "\\[([^]]+)\\]\\(([^)]+)\\)".toRegex()
val markdownItalicBoldRegex = "\\*+\\s*([^\\*]*)\\s*\\*+".toRegex()
val markdownItalicRegex = "_+\\s*([^_]*)\\s*_+".toRegex() val markdownItalicRegex = "_+\\s*([^_]*)\\s*_+".toRegex()
fun buildMangaUrl(mangaUuid: String): String { fun buildMangaUrl(mangaUuid: String): String {
@ -96,47 +76,10 @@ class MdUtil {
.trim() .trim()
} }
fun getImageUrl(attr: String): String {
// Some images are hosted elsewhere
if (attr.startsWith("http")) {
return attr
}
return baseUrl + attr
}
fun getScanlators(scanlators: String?): Set<String> {
return scanlators?.split(scanlatorSeparator)?.dropBlank()?.toSet().orEmpty()
}
fun getScanlatorString(scanlators: Set<String>): String { fun getScanlatorString(scanlators: Set<String>): String {
return scanlators.sorted().joinToString(scanlatorSeparator) return scanlators.sorted().joinToString(scanlatorSeparator)
} }
fun getMissingChapterCount(chapters: List<SChapter>, mangaStatus: Int): String? {
if (mangaStatus == SManga.COMPLETED) return null
val remove0ChaptersFromCount = chapters.distinctBy {
/*if (it.chapter_txt.isNotEmpty()) {
it.vol + it.chapter_txt
} else {*/
it.name
/*}*/
}.sortedByDescending { it.chapter_number }
remove0ChaptersFromCount.firstOrNull()?.let { chapter ->
val chpNumber = chapter.chapter_number.floor()
val allChapters = (1..chpNumber).toMutableSet()
remove0ChaptersFromCount.forEach {
allChapters.remove(it.chapter_number.floor())
}
if (allChapters.isEmpty()) return null
return allChapters.size.toString()
}
return null
}
val dateFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss+SSS", Locale.US) val dateFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss+SSS", Locale.US)
.apply { timeZone = TimeZone.getTimeZone("UTC") } .apply { timeZone = TimeZone.getTimeZone("UTC") }
@ -146,7 +89,7 @@ class MdUtil {
fun createMangaEntry(json: MangaDataDto, lang: String): SManga { fun createMangaEntry(json: MangaDataDto, lang: String): SManga {
return SManga( return SManga(
url = buildMangaUrl(json.id), url = buildMangaUrl(json.id),
title = getTitleFromManga(json.attributes, lang), title = getTitleFromManga(json.attributes, lang, true),
thumbnail_url = json.relationships thumbnail_url = json.relationships
.firstOrNull { relationshipDto -> relationshipDto.type == MdConstants.Types.coverArt } .firstOrNull { relationshipDto -> relationshipDto.type == MdConstants.Types.coverArt }
?.attributes ?.attributes
@ -157,12 +100,33 @@ class MdUtil {
) )
} }
fun getTitleFromManga(json: MangaAttributesDto, lang: String): String { fun getTitleFromManga(json: MangaAttributesDto, lang: String, preferExtensionLangTitle: Boolean): String {
return getFromLangMap(json.title.asMdMap(), lang, json.originalLanguage) val titleMap = json.title.asMdMap<String>()
?: getAltTitle(json.altTitles, lang, json.originalLanguage) val altTitles = json.altTitles
?: json.title.asMdMap<String>()[json.originalLanguage] val originalLang = json.originalLanguage
?: json.altTitles.firstNotNullOfOrNull { it[json.originalLanguage] }
.orEmpty() titleMap[lang]?.let { return it }
val mainTitle = titleMap.values.firstOrNull()
// KMK -->
val langAltTitle = altTitles.firstNotNullOfOrNull { it[lang] }
// KMK <--
val enTitle = findTitleInMaps("en", titleMap, altTitles)
val originalLangTitle = findTitleInMaps("$originalLang-ro", titleMap, altTitles) ?: findTitleInMaps(
originalLang,
titleMap,
altTitles,
)
val ordered = if (preferExtensionLangTitle) {
listOf(langAltTitle, mainTitle, enTitle, originalLangTitle)
} else {
listOf(mainTitle, langAltTitle, enTitle, originalLangTitle)
}
// KMM -->
return ordered.firstNotNullOfOrNull { it } ?: ""
// KMM <--
} }
fun getFromLangMap(langMap: Map<String, String>, currentLang: String, originalLanguage: String): String? { fun getFromLangMap(langMap: Map<String, String>, currentLang: String, originalLanguage: String): String? {
@ -176,15 +140,12 @@ class MdUtil {
} }
} }
fun getAltTitle(langMaps: List<Map<String, String>>, currentLang: String, originalLanguage: String): String? { fun findTitleInMaps(
return langMaps.firstNotNullOfOrNull { it[currentLang] } lang: String,
?: langMaps.firstNotNullOfOrNull { it["en"] } titleMap: Map<String, String>,
?: if (originalLanguage == "ja") { altTitleMaps: List<Map<String, String>>,
langMaps.firstNotNullOfOrNull { it["ja-ro"] } ): String? {
?: langMaps.firstNotNullOfOrNull { it["jp-ro"] } return titleMap[lang] ?: altTitleMaps.firstNotNullOfOrNull { it[lang] }
} else {
null
}
} }
fun cdnCoverUrl(dexId: String, fileName: String): String { fun cdnCoverUrl(dexId: String, fileName: String): String {
@ -202,7 +163,7 @@ class MdUtil {
fun loadOAuth(preferences: TrackPreferences, mdList: MdList): MALOAuth? { fun loadOAuth(preferences: TrackPreferences, mdList: MdList): MALOAuth? {
return try { return try {
jsonParser.decodeFromString<MALOAuth>(preferences.trackToken(mdList).get()) jsonParser.decodeFromString<MALOAuth>(preferences.trackToken(mdList).get())
} catch (e: Exception) { } catch (_: Exception) {
null null
} }
} }
@ -232,7 +193,10 @@ class MdUtil {
return codeVerifier ?: PkceUtil.generateCodeVerifier().also { codeVerifier = it } return codeVerifier ?: PkceUtil.generateCodeVerifier().also { codeVerifier = it }
} }
fun getEnabledMangaDex(sourcePreferences: SourcePreferences = Injekt.get(), sourceManager: SourceManager = Injekt.get()): MangaDex? { fun getEnabledMangaDex(
sourcePreferences: SourcePreferences = Injekt.get(),
sourceManager: SourceManager = Injekt.get(),
): MangaDex? {
return getEnabledMangaDexs(sourcePreferences, sourceManager).let { mangadexs -> return getEnabledMangaDexs(sourcePreferences, sourceManager).let { mangadexs ->
sourcePreferences.preferredMangaDexId().get().toLongOrNull()?.nullIfZero() sourcePreferences.preferredMangaDexId().get().toLongOrNull()?.nullIfZero()
?.let { preferredMangaDexId -> ?.let { preferredMangaDexId ->
@ -242,7 +206,10 @@ class MdUtil {
} }
} }
fun getEnabledMangaDexs(preferences: SourcePreferences, sourceManager: SourceManager = Injekt.get()): List<MangaDex> { fun getEnabledMangaDexs(
preferences: SourcePreferences,
sourceManager: SourceManager = Injekt.get(),
): List<MangaDex> {
val languages = preferences.enabledLanguages().get() val languages = preferences.enabledLanguages().get()
val disabledSourceIds = preferences.disabledSources().get() val disabledSourceIds = preferences.disabledSources().get()
@ -264,8 +231,30 @@ class MdUtil {
description description
} else { } else {
val altTitlesDesc = altTitles val altTitlesDesc = altTitles
.joinToString("\n", "${Injekt.get<Application>().getString(R.string.alt_titles)}:\n") { "$it" } .joinToString(
description + (if (description.isBlank()) "" else "\n\n") + Parser.unescapeEntities(altTitlesDesc, false) "\n",
"${Injekt.get<Application>().stringResource(SYMR.strings.alt_titles)}:\n",
) { "$it" }
description + (if (description.isBlank()) "" else "\n\n") + Parser.unescapeEntities(
altTitlesDesc,
false,
)
}
}
fun addFinalChapterToDesc(description: String, lastVolume: String?, lastChapter: String?): String {
val parts = listOfNotNull(
lastVolume?.takeIf { it.isNotEmpty() }?.let { "Vol.$it" },
lastChapter?.takeIf { it.isNotEmpty() }?.let { "Ch.$it" },
)
return if (parts.isEmpty()) {
description
} else {
description + (if (description.isBlank()) "" else "\n\n") + parts.joinToString(
" ",
"${Injekt.get<Application>().stringResource(SYMR.strings.final_chapter)}:\n",
)
} }
} }
} }

View file

@ -704,7 +704,8 @@
<string name="mangadex_push_favorites_to_mangadex_summary">Syncs any non MdList tracked entries to MangaDex as reading.</string> <string name="mangadex_push_favorites_to_mangadex_summary">Syncs any non MdList tracked entries to MangaDex as reading.</string>
<string name="community_recommendations">Community recommendations</string> <string name="community_recommendations">Community recommendations</string>
<string name="similar_titles">Similar titles</string> <string name="similar_titles">Similar titles</string>
<string name="alt_titles">Alternative Titles</string> <string name="alt_titles">Alternative titles</string>
<string name="final_chapter">Final chapter</string>
<!-- Scanlator filters --> <!-- Scanlator filters -->
<string name="select_scanlators">Scanlator groups to show</string> <string name="select_scanlators">Scanlator groups to show</string>