Add option to attempt to use first volume cover on MangaDex (#14031)

* Add option to attempt to use first volume cover on MangaDex.

* Fix missing first volume covers and remove logical symbols.

* Reinforce isOneShot check and reword preference.
This commit is contained in:
Alessandro Jean 2022-10-29 22:59:22 -03:00 committed by GitHub
parent c18a8d5860
commit eaa7b15bae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 172 additions and 45 deletions

View File

@ -6,7 +6,7 @@ ext {
extName = 'MangaDex' extName = 'MangaDex'
pkgNameSuffix = 'all.mangadex' pkgNameSuffix = 'all.mangadex'
extClass = '.MangaDexFactory' extClass = '.MangaDexFactory'
extVersionCode = 170 extVersionCode = 171
isNsfw = true isNsfw = true
} }

View File

@ -108,9 +108,18 @@ object MDConstants {
return "${hasSanitizedUuidsPref}_$dexLang" return "${hasSanitizedUuidsPref}_$dexLang"
} }
private const val tryUsingFirstVolumeCoverPref = "tryUsingFirstVolumeCover"
const val tryUsingFirstVolumeCoverDefault = false
fun getTryUsingFirstVolumeCoverPrefKey(dexLang: String): String {
return "${tryUsingFirstVolumeCoverPref}_$dexLang"
}
private const val tagGroupContent = "content" private const val tagGroupContent = "content"
private const val tagGroupFormat = "format" private const val tagGroupFormat = "format"
private const val tagGroupGenre = "genre" private const val tagGroupGenre = "genre"
private const val tagGroupTheme = "theme" private const val tagGroupTheme = "theme"
val tagGroupsOrder = arrayOf(tagGroupContent, tagGroupFormat, tagGroupGenre, tagGroupTheme) val tagGroupsOrder = arrayOf(tagGroupContent, tagGroupFormat, tagGroupGenre, tagGroupTheme)
const val tagAnthologyUuid = "51d83883-4103-437c-b4b1-731cb73d786c"
const val tagOneShotUuid = "0234a31e-a729-4e28-9d6a-3f87c4966b9e"
} }

View File

@ -9,10 +9,13 @@ import androidx.preference.MultiSelectListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.extension.all.mangadex.dto.AggregateDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.AggregateDto
import eu.kanade.tachiyomi.extension.all.mangadex.dto.AggregateVolume
import eu.kanade.tachiyomi.extension.all.mangadex.dto.AtHomeDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.AtHomeDto
import eu.kanade.tachiyomi.extension.all.mangadex.dto.ChapterDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.ChapterDto
import eu.kanade.tachiyomi.extension.all.mangadex.dto.ChapterListDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.ChapterListDto
import eu.kanade.tachiyomi.extension.all.mangadex.dto.CoverListDto
import eu.kanade.tachiyomi.extension.all.mangadex.dto.ListDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.ListDto
import eu.kanade.tachiyomi.extension.all.mangadex.dto.MangaDataDto
import eu.kanade.tachiyomi.extension.all.mangadex.dto.MangaDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.MangaDto
import eu.kanade.tachiyomi.extension.all.mangadex.dto.MangaListDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.MangaListDto
import eu.kanade.tachiyomi.extension.all.mangadex.dto.RelationshipDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.RelationshipDto
@ -26,7 +29,6 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter 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.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.SerializationException
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import okhttp3.CacheControl import okhttp3.CacheControl
import okhttp3.Headers import okhttp3.Headers
@ -97,9 +99,10 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
val hasMoreResults = mangaListDto.limit + mangaListDto.offset < mangaListDto.total val hasMoreResults = mangaListDto.limit + mangaListDto.offset < mangaListDto.total
val coverSuffix = preferences.coverQuality val coverSuffix = preferences.coverQuality
val firstVolumeCovers = fetchFirstVolumeCovers(mangaListDto.data).orEmpty()
val mangaList = mangaListDto.data.map { mangaDataDto -> val mangaList = mangaListDto.data.map { mangaDataDto ->
val fileName = mangaDataDto.relationships val fileName = firstVolumeCovers[mangaDataDto.id] ?: mangaDataDto.relationships
.firstOrNull { it.type.equals(MDConstants.coverArt, true) } .firstOrNull { it.type.equals(MDConstants.coverArt, true) }
?.attributes?.fileName ?.attributes?.fileName
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang) helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang)
@ -129,13 +132,14 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
val mangaRequest = GET(mangaUrl.build().toString(), headers, CacheControl.FORCE_NETWORK) val mangaRequest = GET(mangaUrl.build().toString(), headers, CacheControl.FORCE_NETWORK)
val mangaResponse = client.newCall(mangaRequest).execute() val mangaResponse = client.newCall(mangaRequest).execute()
val mangaListDto = mangaResponse.parseAs<MangaListDto>() val mangaListDto = mangaResponse.parseAs<MangaListDto>()
val firstVolumeCovers = fetchFirstVolumeCovers(mangaListDto.data).orEmpty()
val mangaDtoMap = mangaListDto.data.associateBy({ it.id }, { it }) val mangaDtoMap = mangaListDto.data.associateBy({ it.id }, { it })
val coverSuffix = preferences.coverQuality val coverSuffix = preferences.coverQuality
val mangaList = mangaIds.mapNotNull { mangaDtoMap[it] }.map { mangaDataDto -> val mangaList = mangaIds.mapNotNull { mangaDtoMap[it] }.map { mangaDataDto ->
val fileName = mangaDataDto.relationships val fileName = firstVolumeCovers[mangaDataDto.id] ?: mangaDataDto.relationships
.firstOrNull { it.type.equals(MDConstants.coverArt, true) } .firstOrNull { it.type.equals(MDConstants.coverArt, true) }
?.attributes?.fileName ?.attributes?.fileName
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang) helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang)
@ -182,7 +186,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
.map { searchMangaListRequest(it, page) } .map { searchMangaListRequest(it, page) }
else -> else ->
return super.fetchSearchManga(page, query, filters) return super.fetchSearchManga(page, query.trim(), filters)
} }
} }
@ -292,11 +296,12 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
} }
val mangaListDto = response.parseAs<MangaListDto>() val mangaListDto = response.parseAs<MangaListDto>()
val firstVolumeCovers = fetchFirstVolumeCovers(mangaListDto.data).orEmpty()
val coverSuffix = preferences.coverQuality val coverSuffix = preferences.coverQuality
val mangaList = mangaListDto.data.map { mangaDataDto -> val mangaList = mangaListDto.data.map { mangaDataDto ->
val fileName = mangaDataDto.relationships val fileName = firstVolumeCovers[mangaDataDto.id] ?: mangaDataDto.relationships
.firstOrNull { it.type.equals(MDConstants.coverArt, true) } .firstOrNull { it.type.equals(MDConstants.coverArt, true) }
?.attributes?.fileName ?.attributes?.fileName
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang) helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang)
@ -365,33 +370,74 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
return helper.createManga( return helper.createManga(
manga.data, manga.data,
fetchSimpleChapterList(manga, dexLang), fetchSimpleChapterList(manga, dexLang),
fetchFirstVolumeCover(manga),
dexLang, dexLang,
preferences.coverQuality preferences.coverQuality
) )
} }
/** /**
* get a quick-n-dirty list of the chapters to be used in determining the manga status. * Get a quick-n-dirty list of the chapters to be used in determining the manga status.
* uses the 'aggregate' endpoint * Uses the 'aggregate' endpoint.
*
* @see MangaDexHelper.getPublicationStatus * @see MangaDexHelper.getPublicationStatus
* @see AggregateDto * @see AggregateDto
*/ */
private fun fetchSimpleChapterList(manga: MangaDto, langCode: String): List<String> { private fun fetchSimpleChapterList(manga: MangaDto, langCode: String): Map<String, AggregateVolume> {
val url = "${MDConstants.apiMangaUrl}/${manga.data.id}/aggregate?translatedLanguage[]=$langCode" val url = "${MDConstants.apiMangaUrl}/${manga.data.id}/aggregate?translatedLanguage[]=$langCode"
val response = client.newCall(GET(url, headers)).execute() val response = client.newCall(GET(url, headers)).execute()
val chapters: AggregateDto
try { return runCatching { response.parseAs<AggregateDto>() }
chapters = response.parseAs() .getOrNull()?.volumes.orEmpty()
} catch (e: SerializationException) { }
return emptyList()
/**
* Attempt to get the first volume cover if the setting is enabled.
* Uses the 'covers' endpoint.
*
* @see CoverListDto
*/
private fun fetchFirstVolumeCover(manga: MangaDto): String? {
return fetchFirstVolumeCovers(listOf(manga.data))?.get(manga.data.id)
}
/**
* Attempt to get the first volume cover if the setting is enabled.
* Uses the 'covers' endpoint.
*
* @see CoverListDto
*/
private fun fetchFirstVolumeCovers(mangaList: List<MangaDataDto>): Map<String, String>? {
if (!preferences.tryUsingFirstVolumeCover) {
return null
} }
if (chapters.volumes.isNullOrEmpty()) return emptyList() val mangaMap = mangaList.associate { it.id to it.attributes }
.filterValues { !it.originalLanguage.isNullOrEmpty() }
val locales = mangaList.mapNotNull { it.attributes.originalLanguage }.distinct()
val limit = (mangaMap.size * locales.size).coerceAtMost(100)
return chapters.volumes.values val apiUrl = "${MDConstants.apiUrl}/cover".toHttpUrl().newBuilder()
.flatMap { it.chapters.values } .addQueryParameter("order[volume]", "asc")
.map { it.chapter } .addQueryParameter("manga[]", mangaMap.keys)
.addQueryParameter("locales[]", locales.toSet())
.addQueryParameter("limit", limit.toString())
.addQueryParameter("offset", "0")
.toString()
val result = runCatching {
client.newCall(GET(apiUrl, headers)).execute().parseAs<CoverListDto>().data
}
val covers = result.getOrNull() ?: return null
return covers
.groupBy { it.relationships.find { r -> r.type == MDConstants.manga }!!.id }
.mapValues {
it.value.find { c -> c.attributes?.locale == mangaMap[it.key]?.originalLanguage }
}
.filterValues { !it?.attributes?.fileName.isNullOrEmpty() }
.mapValues { it.value!!.attributes!!.fileName!! }
} }
// Chapter list section // Chapter list section
@ -525,6 +571,21 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
} }
} }
val tryUsingFirstVolumeCoverPref = SwitchPreferenceCompat(screen.context).apply {
key = MDConstants.getTryUsingFirstVolumeCoverPrefKey(dexLang)
title = helper.intl.tryUsingFirstVolumeCover
summary = helper.intl.tryUsingFirstVolumeCoverSummary
setDefaultValue(MDConstants.tryUsingFirstVolumeCoverDefault)
setOnPreferenceChangeListener { _, newValue ->
val checkValue = newValue as Boolean
preferences.edit()
.putBoolean(MDConstants.getDataSaverPreferenceKey(dexLang), checkValue)
.commit()
}
}
val dataSaverPref = SwitchPreferenceCompat(screen.context).apply { val dataSaverPref = SwitchPreferenceCompat(screen.context).apply {
key = MDConstants.getDataSaverPreferenceKey(dexLang) key = MDConstants.getDataSaverPreferenceKey(dexLang)
title = helper.intl.dataSaver title = helper.intl.dataSaver
@ -636,6 +697,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
} }
screen.addPreference(coverQualityPref) screen.addPreference(coverQualityPref)
screen.addPreference(tryUsingFirstVolumeCoverPref)
screen.addPreference(dataSaverPref) screen.addPreference(dataSaverPref)
screen.addPreference(standardHttpsPortPref) screen.addPreference(standardHttpsPortPref)
screen.addPreference(contentRatingPref) screen.addPreference(contentRatingPref)
@ -680,6 +742,12 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
private val SharedPreferences.coverQuality private val SharedPreferences.coverQuality
get() = getString(MDConstants.getCoverQualityPreferenceKey(dexLang), "") get() = getString(MDConstants.getCoverQualityPreferenceKey(dexLang), "")
private val SharedPreferences.tryUsingFirstVolumeCover
get() = getBoolean(
MDConstants.getTryUsingFirstVolumeCoverPrefKey(dexLang),
MDConstants.tryUsingFirstVolumeCoverDefault
)
private val SharedPreferences.blockedGroups private val SharedPreferences.blockedGroups
get() = getString(MDConstants.getBlockedGroupsPrefKey(dexLang), "") get() = getString(MDConstants.getBlockedGroupsPrefKey(dexLang), "")
?.split(",") ?.split(",")

View File

@ -5,18 +5,18 @@ import android.text.TextWatcher
import android.util.Log import android.util.Log
import android.widget.Button import android.widget.Button
import android.widget.EditText import android.widget.EditText
import eu.kanade.tachiyomi.extension.all.mangadex.dto.AggregateVolume
import eu.kanade.tachiyomi.extension.all.mangadex.dto.AtHomeDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.AtHomeDto
import eu.kanade.tachiyomi.extension.all.mangadex.dto.ChapterDataDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.ChapterDataDto
import eu.kanade.tachiyomi.extension.all.mangadex.dto.MangaAttributesDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.MangaAttributesDto
import eu.kanade.tachiyomi.extension.all.mangadex.dto.MangaDataDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.MangaDataDto
import eu.kanade.tachiyomi.extension.all.mangadex.dto.asMdMap import eu.kanade.tachiyomi.extension.all.mangadex.dto.toLocalizedString
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import okhttp3.CacheControl import okhttp3.CacheControl
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
@ -27,7 +27,7 @@ import java.util.Date
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class MangaDexHelper(private val lang: String) { class MangaDexHelper(lang: String) {
val mdFilters = MangaDexFilters() val mdFilters = MangaDexFilters()
@ -98,7 +98,11 @@ class MangaDexHelper(private val lang: String) {
* Maps dex status to Tachi status. * Maps dex status to Tachi status.
* Adapted from the MangaDex handler from TachiyomiSY. * Adapted from the MangaDex handler from TachiyomiSY.
*/ */
fun getPublicationStatus(attr: MangaAttributesDto, chapters: List<String>): Int { fun getPublicationStatus(attr: MangaAttributesDto, volumes: Map<String, AggregateVolume>): Int {
val chaptersList = volumes.values
.flatMap { it.chapters.values }
.map { it.chapter }
val tempStatus = when (attr.status) { val tempStatus = when (attr.status) {
"ongoing" -> SManga.ONGOING "ongoing" -> SManga.ONGOING
"cancelled" -> SManga.CANCELLED "cancelled" -> SManga.CANCELLED
@ -110,10 +114,13 @@ class MangaDexHelper(private val lang: String) {
val publishedOrCancelled = tempStatus == SManga.PUBLISHING_FINISHED || val publishedOrCancelled = tempStatus == SManga.PUBLISHING_FINISHED ||
tempStatus == SManga.CANCELLED tempStatus == SManga.CANCELLED
return if (chapters.contains(attr.lastChapter) && publishedOrCancelled) { val isOneShot = attr.tags.any { it.id == MDConstants.tagOneShotUuid } &&
SManga.COMPLETED attr.tags.none { it.id == MDConstants.tagAnthologyUuid }
} else {
tempStatus return when {
chaptersList.contains(attr.lastChapter) && publishedOrCancelled -> SManga.COMPLETED
isOneShot && volumes["none"]?.chapters?.get("none") != null -> SManga.COMPLETED
else -> tempStatus
} }
} }
@ -210,15 +217,14 @@ class MangaDexHelper(private val lang: String) {
): SManga { ): SManga {
return SManga.create().apply { return SManga.create().apply {
url = "/manga/${mangaDataDto.id}" url = "/manga/${mangaDataDto.id}"
val titleMap = mangaDataDto.attributes.title.asMdMap() val titleMap = mangaDataDto.attributes.title.toLocalizedString()
val dirtyTitle = titleMap[lang] val dirtyTitle = titleMap[lang]
?: titleMap["en"] ?: titleMap["en"]
?: titleMap["ja-ro"] ?: titleMap["ja-ro"]
?: mangaDataDto.attributes.altTitles.jsonArray ?: mangaDataDto.attributes.altTitles
.find { .map { it.toLocalizedString() }
val altTitle = it.asMdMap() .find { (it[lang] ?: it["en"]) !== null }
(altTitle[lang] ?: altTitle["en"]) != null ?.values?.singleOrNull()
}?.asMdMap()?.values?.singleOrNull()
?: titleMap["ja"] // romaji titles are sometimes ja (and are not altTitles) ?: titleMap["ja"] // romaji titles are sometimes ja (and are not altTitles)
?: titleMap.values.firstOrNull() // use literally anything from title as a last resort ?: titleMap.values.firstOrNull() // use literally anything from title as a last resort
title = cleanString(dirtyTitle ?: "") title = cleanString(dirtyTitle ?: "")
@ -237,7 +243,8 @@ class MangaDexHelper(private val lang: String) {
*/ */
fun createManga( fun createManga(
mangaDataDto: MangaDataDto, mangaDataDto: MangaDataDto,
chapters: List<String>, chapters: Map<String, AggregateVolume>,
firstVolumeCover: String?,
lang: String, lang: String,
coverSuffix: String? coverSuffix: String?
): SManga { ): SManga {
@ -275,7 +282,7 @@ class MangaDexHelper(private val lang: String) {
.mapNotNull { it.attributes!!.name } .mapNotNull { it.attributes!!.name }
.distinct() .distinct()
val coverFileName = mangaDataDto.relationships val coverFileName = firstVolumeCover ?: mangaDataDto.relationships
.firstOrNull { it.type.equals(MDConstants.coverArt, true) } .firstOrNull { it.type.equals(MDConstants.coverArt, true) }
?.attributes?.fileName ?.attributes?.fileName
@ -293,7 +300,7 @@ class MangaDexHelper(private val lang: String) {
val genreList = MDConstants.tagGroupsOrder.flatMap { genresMap[it].orEmpty() } + val genreList = MDConstants.tagGroupsOrder.flatMap { genresMap[it].orEmpty() } +
nonGenres.filterNotNull() nonGenres.filterNotNull()
val desc = attr.description.asMdMap() val desc = attr.description.toLocalizedString()
return createBasicManga(mangaDataDto, coverFileName, coverSuffix, lang).apply { return createBasicManga(mangaDataDto, coverFileName, coverSuffix, lang).apply {
description = cleanString(desc[lang] ?: desc["en"] ?: "") description = cleanString(desc[lang] ?: desc["en"] ?: "")

View File

@ -96,18 +96,18 @@ class MangaDexIntl(lang: String) {
BRAZILIAN_PORTUGUESE, PORTUGUESE -> BRAZILIAN_PORTUGUESE, PORTUGUESE ->
"Ative para fazer requisições em somente servidores de imagem que usem a porta 443. " + "Ative para fazer requisições em somente servidores de imagem que usem a porta 443. " +
"Isso permite com que usuários com regras mais restritas de firewall possam acessar " + "Isso permite com que usuários com regras mais restritas de firewall possam acessar " +
"as imagens do MangaDex." "as imagens do $MANGADEX_NAME."
SPANISH_LATAM, SPANISH -> SPANISH_LATAM, SPANISH ->
"Habilite esta opción solicitar las imágenes a los servidores que usan el puerto 443. " + "Habilite esta opción solicitar las imágenes a los servidores que usan el puerto 443. " +
"Esto permite a los usuarios con restricciones estrictas de firewall acceder " + "Esto permite a los usuarios con restricciones estrictas de firewall acceder " +
"a las imagenes en MangaDex" "a las imagenes en $MANGADEX_NAME"
RUSSIAN -> RUSSIAN ->
"Запрашивает изображения только с серверов которые используют порт 443. " + "Запрашивает изображения только с серверов которые используют порт 443. " +
"Это позволяет пользователям со строгими правилами брандмауэра загружать " + "Это позволяет пользователям со строгими правилами брандмауэра загружать " +
"изображения с Mangadex." "изображения с $MANGADEX_NAME."
else -> else ->
"Enable to only request image servers that use port 443. This allows users with " + "Enable to only request image servers that use port 443. This allows users with " +
"stricter firewall restrictions to access MangaDex images" "stricter firewall restrictions to access $MANGADEX_NAME images"
} }
val contentRating: String = when (availableLang) { val contentRating: String = when (availableLang) {
@ -265,6 +265,20 @@ class MangaDexIntl(lang: String) {
"Enter as a Comma-separated list of uploader UUIDs" "Enter as a Comma-separated list of uploader UUIDs"
} }
val tryUsingFirstVolumeCover: String = when (availableLang) {
BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Tentar usar a capa do primeiro volume como capa"
else -> "Attempt to use the first volume cover as cover"
}
val tryUsingFirstVolumeCoverSummary: String = when (availableLang) {
BRAZILIAN_PORTUGUESE, PORTUGUESE ->
"Pode ser necessário atualizar os itens já adicionados na biblioteca. " +
"Alternativamente, limpe o banco de dados para as novas capas aparecerem."
else ->
"May need to manually refresh entries already in library. " +
"Otherwise, clear database to have new covers to show up."
}
val publicationDemographic: String = when (availableLang) { val publicationDemographic: String = when (availableLang) {
BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Demografia da publicação" BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Demografia da publicação"
SPANISH_LATAM, SPANISH -> "Demografía" SPANISH_LATAM, SPANISH -> "Demografía"
@ -748,7 +762,7 @@ class MangaDexIntl(lang: String) {
BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Pós-apocalíptico" BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Pós-apocalíptico"
SPANISH_LATAM, SPANISH -> "Post-Apocalíptico" SPANISH_LATAM, SPANISH -> "Post-Apocalíptico"
RUSSIAN -> "Постапокалиптика" RUSSIAN -> "Постапокалиптика"
else -> "Post-Apocalypytic" else -> "Post-Apocalyptic"
} }
val themePsychological: String = when (availableLang) { val themePsychological: String = when (availableLang) {

View File

@ -0,0 +1,22 @@
package eu.kanade.tachiyomi.extension.all.mangadex.dto
import kotlinx.serialization.Serializable
@Serializable
data class CoverListDto(
val data: List<CoverDto> = emptyList()
)
@Serializable
data class CoverDto(
val id: String,
val attributes: CoverAttributesDto? = null,
val relationships: List<RelationshipDto> = emptyList()
)
@Serializable
data class CoverAttributesDto(
val name: String? = null,
val fileName: String? = null,
val locale: String? = null
)

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.extension.all.mangadex.dto package eu.kanade.tachiyomi.extension.all.mangadex.dto
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.contentOrNull
@ -45,7 +46,7 @@ data class MangaDataDto(
@Serializable @Serializable
data class MangaAttributesDto( data class MangaAttributesDto(
val title: JsonElement, val title: JsonElement,
val altTitles: JsonElement, val altTitles: JsonArray,
val description: JsonElement, val description: JsonElement,
val originalLanguage: String?, val originalLanguage: String?,
val lastVolume: String?, val lastVolume: String?,
@ -67,8 +68,14 @@ data class TagAttributesDto(
val group: String val group: String
) )
fun JsonElement.asMdMap(): Map<String, String> { typealias LocalizedString = Map<String, String>
return runCatching {
(this as JsonObject).map { it.key to (it.value.jsonPrimitive.contentOrNull ?: "") }.toMap() /**
}.getOrElse { emptyMap() } * Temporary workaround while Dex API still returns arrays instead of objects
* in the places that uses [LocalizedString].
*/
fun JsonElement.toLocalizedString(): LocalizedString {
return (this as? JsonObject)?.entries
?.associate { (key, value) -> key to (value.jsonPrimitive.contentOrNull ?: "") }
.orEmpty()
} }