Update MangaDex to use API v5.7.5 (#14087)
* Update MangaDex to use API v5.7.5. * Remove unused line. * Fix list type string. * Make usage of `authorOrArtist` in deeplink. * Use proper custom serializer for `LocalizedString`. * Use actual enums for manga properties. * Fix list search not working. Co-authored-by: nicki <72807749+curche@users.noreply.github.com> Co-authored-by: nicki <72807749+curche@users.noreply.github.com>
This commit is contained in:
parent
6dff9d8cfa
commit
bd4faa01d9
|
@ -6,7 +6,7 @@ ext {
|
|||
extName = 'MangaDex'
|
||||
pkgNameSuffix = 'all.mangadex'
|
||||
extClass = '.MangaDexFactory'
|
||||
extVersionCode = 174
|
||||
extVersionCode = 175
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -12,12 +12,15 @@ object MDConstants {
|
|||
const val mangaLimit = 20
|
||||
const val latestChapterLimit = 100
|
||||
|
||||
const val chapter = "chapter"
|
||||
const val manga = "manga"
|
||||
const val coverArt = "cover_art"
|
||||
const val scanlator = "scanlation_group"
|
||||
const val uploader = "user"
|
||||
const val scanlationGroup = "scanlation_group"
|
||||
const val user = "user"
|
||||
const val author = "author"
|
||||
const val artist = "artist"
|
||||
const val tag = "tag"
|
||||
const val list = "custom_list"
|
||||
const val legacyNoGroupId = "00e03853-1b96-4f41-9542-c71b8692033b"
|
||||
|
||||
const val cdnUrl = "https://uploads.mangadex.org"
|
||||
|
|
|
@ -13,12 +13,12 @@ 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.ChapterDto
|
||||
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.CoverArtDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.CoverArtListDto
|
||||
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.MangaListDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.RelationshipDto
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
|
@ -103,7 +103,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
|
||||
val mangaList = mangaListDto.data.map { mangaDataDto ->
|
||||
val fileName = firstVolumeCovers[mangaDataDto.id] ?: mangaDataDto.relationships
|
||||
.firstOrNull { it.type.equals(MDConstants.coverArt, true) }
|
||||
.filterIsInstance<CoverArtDto>()
|
||||
.firstOrNull()
|
||||
?.attributes?.fileName
|
||||
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang)
|
||||
}
|
||||
|
@ -118,7 +119,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
|
||||
val mangaIds = chapterListDto.data
|
||||
.flatMap { it.relationships }
|
||||
.filter { it.type == MDConstants.manga }
|
||||
.filterIsInstance<MangaDataDto>()
|
||||
.map { it.id }
|
||||
.distinct()
|
||||
.toSet()
|
||||
|
@ -140,7 +141,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
|
||||
val mangaList = mangaIds.mapNotNull { mangaDtoMap[it] }.map { mangaDataDto ->
|
||||
val fileName = firstVolumeCovers[mangaDataDto.id] ?: mangaDataDto.relationships
|
||||
.firstOrNull { it.type.equals(MDConstants.coverArt, true) }
|
||||
.filterIsInstance<CoverArtDto>()
|
||||
.firstOrNull()
|
||||
?.attributes?.fileName
|
||||
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang)
|
||||
}
|
||||
|
@ -162,6 +164,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
MDConstants.defaultBlockedGroups + preferences.blockedGroups
|
||||
)
|
||||
.addQueryParameter("excludedUploaders[]", preferences.blockedUploaders)
|
||||
.addQueryParameter("includeFuturePublishAt", "0")
|
||||
.addQueryParameter("includeEmptyPages", "0")
|
||||
|
||||
return GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK)
|
||||
}
|
||||
|
@ -169,24 +173,39 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
// SEARCH section
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
when {
|
||||
return when {
|
||||
query.startsWith(MDConstants.prefixChSearch) ->
|
||||
return getMangaIdFromChapterId(query.removePrefix(MDConstants.prefixChSearch)).flatMap { manga_id ->
|
||||
super.fetchSearchManga(page, MDConstants.prefixIdSearch + manga_id, filters)
|
||||
}
|
||||
getMangaIdFromChapterId(query.removePrefix(MDConstants.prefixChSearch))
|
||||
.flatMap { mangaId ->
|
||||
super.fetchSearchManga(
|
||||
page = page,
|
||||
query = MDConstants.prefixIdSearch + mangaId,
|
||||
filters = filters
|
||||
)
|
||||
}
|
||||
|
||||
query.startsWith(MDConstants.prefixUsrSearch) ->
|
||||
return client.newCall(searchMangaUploaderRequest(page, query.removePrefix(MDConstants.prefixUsrSearch)))
|
||||
client
|
||||
.newCall(
|
||||
request = searchMangaUploaderRequest(
|
||||
page = page,
|
||||
uploader = query.removePrefix(MDConstants.prefixUsrSearch)
|
||||
)
|
||||
)
|
||||
.asObservableSuccess()
|
||||
.map { latestUpdatesParse(it) }
|
||||
|
||||
query.startsWith(MDConstants.prefixListSearch) ->
|
||||
return client.newCall(GET(MDConstants.apiListUrl + "/" + query.removePrefix(MDConstants.prefixListSearch), headers, CacheControl.FORCE_NETWORK))
|
||||
client
|
||||
.newCall(
|
||||
request = searchMangaListRequest(
|
||||
list = query.removePrefix(MDConstants.prefixListSearch)
|
||||
)
|
||||
)
|
||||
.asObservableSuccess()
|
||||
.map { searchMangaListRequest(it, page) }
|
||||
.map { searchMangaListParse(it, page) }
|
||||
|
||||
else ->
|
||||
return super.fetchSearchManga(page, query.trim(), filters)
|
||||
else -> super.fetchSearchManga(page, query.trim(), filters)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,8 +217,9 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
throw Exception(helper.intl.unableToProcessChapterRequest(response.code))
|
||||
}
|
||||
|
||||
response.parseAs<ChapterDto>().data.relationships
|
||||
.find { it.type == MDConstants.manga }!!.id
|
||||
response.parseAs<ChapterDto>().data!!.relationships
|
||||
.filterIsInstance<MangaDataDto>()
|
||||
.firstOrNull()!!.id
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -223,23 +243,21 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
}
|
||||
|
||||
query.startsWith(MDConstants.prefixGrpSearch) -> {
|
||||
val groupID = query.removePrefix(MDConstants.prefixGrpSearch)
|
||||
if (!helper.containsUuid(groupID)) {
|
||||
val groupId = query.removePrefix(MDConstants.prefixGrpSearch)
|
||||
if (!helper.containsUuid(groupId)) {
|
||||
throw Exception(helper.intl.invalidGroupId)
|
||||
}
|
||||
|
||||
tempUrl.addQueryParameter("group", groupID)
|
||||
tempUrl.addQueryParameter("group", groupId)
|
||||
}
|
||||
|
||||
query.startsWith(MDConstants.prefixAuthSearch) -> {
|
||||
val authorID = query.removePrefix(MDConstants.prefixAuthSearch)
|
||||
if (!helper.containsUuid(authorID)) {
|
||||
val authorId = query.removePrefix(MDConstants.prefixAuthSearch)
|
||||
if (!helper.containsUuid(authorId)) {
|
||||
throw Exception(helper.intl.invalidAuthorId)
|
||||
}
|
||||
|
||||
tempUrl
|
||||
.addQueryParameter("authors[]", authorID)
|
||||
.addQueryParameter("artists[]", authorID)
|
||||
tempUrl.addQueryParameter("authorOrArtist", authorId)
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
@ -262,9 +280,13 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
|
||||
override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
|
||||
|
||||
private fun searchMangaListRequest(response: Response, page: Int): MangasPage {
|
||||
private fun searchMangaListRequest(list: String): Request {
|
||||
return GET("${MDConstants.apiListUrl}/$list", headers, CacheControl.FORCE_NETWORK)
|
||||
}
|
||||
|
||||
private fun searchMangaListParse(response: Response, page: Int): MangasPage {
|
||||
val listDto = response.parseAs<ListDto>()
|
||||
val listDtoFiltered = listDto.data.relationships.filter { it.type != "Manga" }
|
||||
val listDtoFiltered = listDto.data!!.relationships.filterIsInstance<MangaDataDto>()
|
||||
val amount = listDtoFiltered.count()
|
||||
|
||||
if (amount < 1) {
|
||||
|
@ -280,7 +302,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
|
||||
val ids = listDtoFiltered
|
||||
.filterIndexed { i, _ -> i >= minIndex && i < (minIndex + MDConstants.mangaLimit) }
|
||||
.map(RelationshipDto::id)
|
||||
.map(MangaDataDto::id)
|
||||
.toSet()
|
||||
|
||||
url.addQueryParameter("ids[]", ids)
|
||||
|
@ -306,7 +328,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
|
||||
val mangaList = mangaListDto.data.map { mangaDataDto ->
|
||||
val fileName = firstVolumeCovers[mangaDataDto.id] ?: mangaDataDto.relationships
|
||||
.firstOrNull { it.type.equals(MDConstants.coverArt, true) }
|
||||
.filterIsInstance<CoverArtDto>()
|
||||
.firstOrNull()
|
||||
?.attributes?.fileName
|
||||
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang)
|
||||
}
|
||||
|
@ -321,6 +344,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
.addQueryParameter("translatedLanguage[]", dexLang)
|
||||
.addQueryParameter("order[publishAt]", "desc")
|
||||
.addQueryParameter("includeFutureUpdates", "0")
|
||||
.addQueryParameter("includeFuturePublishAt", "0")
|
||||
.addQueryParameter("includeEmptyPages", "0")
|
||||
.addQueryParameter("uploader", uploader)
|
||||
.addQueryParameter("originalLanguage[]", preferences.originalLanguages)
|
||||
.addQueryParameter("contentRating[]", preferences.contentRating)
|
||||
|
@ -372,7 +397,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
val manga = response.parseAs<MangaDto>()
|
||||
|
||||
return helper.createManga(
|
||||
manga.data,
|
||||
manga.data!!,
|
||||
fetchSimpleChapterList(manga, dexLang),
|
||||
fetchFirstVolumeCover(manga),
|
||||
dexLang,
|
||||
|
@ -388,7 +413,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
* @see AggregateDto
|
||||
*/
|
||||
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()
|
||||
|
||||
return runCatching { response.parseAs<AggregateDto>() }
|
||||
|
@ -399,26 +424,26 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
* Attempt to get the first volume cover if the setting is enabled.
|
||||
* Uses the 'covers' endpoint.
|
||||
*
|
||||
* @see CoverListDto
|
||||
* @see CoverArtListDto
|
||||
*/
|
||||
private fun fetchFirstVolumeCover(manga: MangaDto): String? {
|
||||
return fetchFirstVolumeCovers(listOf(manga.data))?.get(manga.data.id)
|
||||
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
|
||||
* @see CoverArtListDto
|
||||
*/
|
||||
private fun fetchFirstVolumeCovers(mangaList: List<MangaDataDto>): Map<String, String>? {
|
||||
if (!preferences.tryUsingFirstVolumeCover || mangaList.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val mangaMap = mangaList.associate { it.id to it.attributes }
|
||||
val mangaMap = mangaList.associate { it.id to it.attributes!! }
|
||||
.filterValues { !it.originalLanguage.isNullOrEmpty() }
|
||||
val locales = mangaList.mapNotNull { it.attributes.originalLanguage }.distinct()
|
||||
val locales = mangaList.mapNotNull { it.attributes!!.originalLanguage }.distinct()
|
||||
val limit = (mangaMap.size * locales.size).coerceAtMost(100)
|
||||
|
||||
val apiUrl = "${MDConstants.apiUrl}/cover".toHttpUrl().newBuilder()
|
||||
|
@ -430,13 +455,16 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
.toString()
|
||||
|
||||
val result = runCatching {
|
||||
client.newCall(GET(apiUrl, headers)).execute().parseAs<CoverListDto>().data
|
||||
client.newCall(GET(apiUrl, headers)).execute().parseAs<CoverArtListDto>().data
|
||||
}
|
||||
|
||||
val covers = result.getOrNull() ?: return null
|
||||
|
||||
return covers
|
||||
.groupBy { it.relationships.find { r -> r.type == MDConstants.manga }!!.id }
|
||||
.groupBy {
|
||||
it.relationships.filterIsInstance<MangaDataDto>()
|
||||
.firstOrNull()!!.id
|
||||
}
|
||||
.mapValues {
|
||||
it.value.find { c -> c.attributes?.locale == mangaMap[it.key]?.originalLanguage }
|
||||
}
|
||||
|
@ -502,16 +530,13 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
hasMoreResults = (limit + offset) < newChapterList.total
|
||||
}
|
||||
|
||||
val now = Date().time
|
||||
|
||||
return chapterListResults
|
||||
.mapNotNull { helper.createChapter(it) }
|
||||
.filter { it.date_upload <= now }
|
||||
return chapterListResults.mapNotNull(helper::createChapter)
|
||||
} catch (e: Exception) {
|
||||
Log.e("MangaDex", "error parsing chapter list", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
override fun pageListRequest(chapter: SChapter): Request {
|
||||
if (!helper.containsUuid(chapter.url)) {
|
||||
throw Exception(helper.intl.migrateWarning)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package eu.kanade.tachiyomi.extension.all.mangadex
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.ContentRatingDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.PublicationDemographicDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.StatusDto
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import okhttp3.HttpUrl
|
||||
|
@ -108,21 +111,17 @@ class MangaDexFilters {
|
|||
)
|
||||
|
||||
return listOf(
|
||||
ContentRating(intl.contentRatingSafe, MDConstants.contentRatingPrefValSafe).apply {
|
||||
state = contentRatings
|
||||
?.contains(MDConstants.contentRatingPrefValSafe) ?: true
|
||||
ContentRating(intl.contentRatingSafe, ContentRatingDto.SAFE.value).apply {
|
||||
state = contentRatings?.contains(MDConstants.contentRatingPrefValSafe) ?: true
|
||||
},
|
||||
ContentRating(intl.contentRatingSuggestive, MDConstants.contentRatingPrefValSuggestive).apply {
|
||||
state = contentRatings
|
||||
?.contains(MDConstants.contentRatingPrefValSuggestive) ?: true
|
||||
ContentRating(intl.contentRatingSuggestive, ContentRatingDto.SUGGESTIVE.value).apply {
|
||||
state = contentRatings?.contains(MDConstants.contentRatingPrefValSuggestive) ?: true
|
||||
},
|
||||
ContentRating(intl.contentRatingErotica, MDConstants.contentRatingPrefValErotica).apply {
|
||||
state = contentRatings
|
||||
?.contains(MDConstants.contentRatingPrefValErotica) ?: false
|
||||
ContentRating(intl.contentRatingErotica, ContentRatingDto.EROTICA.value).apply {
|
||||
state = contentRatings?.contains(MDConstants.contentRatingPrefValErotica) ?: false
|
||||
},
|
||||
ContentRating(intl.contentRatingPornographic, MDConstants.contentRatingPrefValPornographic).apply {
|
||||
state = contentRatings
|
||||
?.contains(MDConstants.contentRatingPrefValPornographic) ?: false
|
||||
ContentRating(intl.contentRatingPornographic, ContentRatingDto.PORNOGRAPHIC.value).apply {
|
||||
state = contentRatings?.contains(MDConstants.contentRatingPrefValPornographic) ?: false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -142,11 +141,11 @@ class MangaDexFilters {
|
|||
}
|
||||
|
||||
private fun getDemographics(intl: MangaDexIntl) = listOf(
|
||||
Demographic(intl.publicationDemographicNone, "none"),
|
||||
Demographic(intl.publicationDemographicShounen, "shounen"),
|
||||
Demographic(intl.publicationDemographicShoujo, "shoujo"),
|
||||
Demographic(intl.publicationDemographicSeinen, "seinen"),
|
||||
Demographic(intl.publicationDemographicJosei, "josei")
|
||||
Demographic(intl.publicationDemographicNone, PublicationDemographicDto.NONE.value),
|
||||
Demographic(intl.publicationDemographicShounen, PublicationDemographicDto.SHOUNEN.value),
|
||||
Demographic(intl.publicationDemographicShoujo, PublicationDemographicDto.SHOUJO.value),
|
||||
Demographic(intl.publicationDemographicSeinen, PublicationDemographicDto.SEINEN.value),
|
||||
Demographic(intl.publicationDemographicJosei, PublicationDemographicDto.JOSEI.value)
|
||||
)
|
||||
|
||||
private class Status(name: String, val value: String) : Filter.CheckBox(name)
|
||||
|
@ -164,10 +163,10 @@ class MangaDexFilters {
|
|||
}
|
||||
|
||||
private fun getStatus(intl: MangaDexIntl) = listOf(
|
||||
Status(intl.statusOngoing, "ongoing"),
|
||||
Status(intl.statusCompleted, "completed"),
|
||||
Status(intl.statusHiatus, "hiatus"),
|
||||
Status(intl.statusCancelled, "cancelled"),
|
||||
Status(intl.statusOngoing, StatusDto.ONGOING.value),
|
||||
Status(intl.statusCompleted, StatusDto.COMPLETED.value),
|
||||
Status(intl.statusHiatus, StatusDto.HIATUS.value),
|
||||
Status(intl.statusCancelled, StatusDto.CANCELLED.value),
|
||||
)
|
||||
|
||||
data class Sortable(val title: String, val value: String) {
|
||||
|
|
|
@ -6,17 +6,38 @@ import android.util.Log
|
|||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.AggregateVolume
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.ArtistDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.AtHomeDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.AttributesDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.AuthorArtistAttributesDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.AuthorDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.ChapterAttributesDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.ChapterDataDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.ContentRatingDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.CoverArtAttributesDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.CoverArtDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.EntityDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.ListAttributesDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.ListDataDto
|
||||
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.toLocalizedString
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.ScanlationGroupAttributes
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.ScanlationGroupDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.StatusDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.TagAttributesDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.TagDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.UserAttributes
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.UserDto
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.plus
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
import kotlinx.serialization.modules.subclass
|
||||
import okhttp3.CacheControl
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
|
@ -35,8 +56,31 @@ class MangaDexHelper(lang: String) {
|
|||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
allowSpecialFloatingPointValues = true
|
||||
useArrayPolymorphism = true
|
||||
prettyPrint = true
|
||||
serializersModule += SerializersModule {
|
||||
polymorphic(EntityDto::class) {
|
||||
subclass(AuthorDto::class)
|
||||
subclass(ArtistDto::class)
|
||||
subclass(ChapterDataDto::class)
|
||||
subclass(CoverArtDto::class)
|
||||
subclass(ListDataDto::class)
|
||||
subclass(MangaDataDto::class)
|
||||
subclass(ScanlationGroupDto::class)
|
||||
subclass(TagDto::class)
|
||||
subclass(UserDto::class)
|
||||
}
|
||||
|
||||
polymorphic(AttributesDto::class) {
|
||||
subclass(AuthorArtistAttributesDto::class)
|
||||
subclass(ChapterAttributesDto::class)
|
||||
subclass(CoverArtAttributesDto::class)
|
||||
subclass(ListAttributesDto::class)
|
||||
subclass(MangaAttributesDto::class)
|
||||
subclass(ScanlationGroupAttributes::class)
|
||||
subclass(TagAttributesDto::class)
|
||||
subclass(UserAttributes::class)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val intl = MangaDexIntl(lang)
|
||||
|
@ -51,13 +95,15 @@ class MangaDexHelper(lang: String) {
|
|||
*/
|
||||
fun getChapterEndpoint(mangaId: String, offset: Int, langCode: String) =
|
||||
"${MDConstants.apiMangaUrl}/$mangaId/feed".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("includes[]", MDConstants.scanlator)
|
||||
.addQueryParameter("includes[]", MDConstants.uploader)
|
||||
.addQueryParameter("includes[]", MDConstants.scanlationGroup)
|
||||
.addQueryParameter("includes[]", MDConstants.user)
|
||||
.addQueryParameter("limit", "500")
|
||||
.addQueryParameter("offset", offset.toString())
|
||||
.addQueryParameter("translatedLanguage[]", langCode)
|
||||
.addQueryParameter("order[volume]", "desc")
|
||||
.addQueryParameter("order[chapter]", "desc")
|
||||
.addQueryParameter("includeFuturePublishAt", "0")
|
||||
.addQueryParameter("includeEmptyPages", "0")
|
||||
.toString()
|
||||
|
||||
/**
|
||||
|
@ -104,10 +150,10 @@ class MangaDexHelper(lang: String) {
|
|||
.map { it.chapter }
|
||||
|
||||
val tempStatus = when (attr.status) {
|
||||
"ongoing" -> SManga.ONGOING
|
||||
"cancelled" -> SManga.CANCELLED
|
||||
"completed" -> SManga.PUBLISHING_FINISHED
|
||||
"hiatus" -> SManga.ON_HIATUS
|
||||
StatusDto.ONGOING -> SManga.ONGOING
|
||||
StatusDto.CANCELLED -> SManga.CANCELLED
|
||||
StatusDto.COMPLETED -> SManga.PUBLISHING_FINISHED
|
||||
StatusDto.HIATUS -> SManga.ON_HIATUS
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
|
@ -217,12 +263,11 @@ class MangaDexHelper(lang: String) {
|
|||
): SManga {
|
||||
return SManga.create().apply {
|
||||
url = "/manga/${mangaDataDto.id}"
|
||||
val titleMap = mangaDataDto.attributes.title.toLocalizedString()
|
||||
val titleMap = mangaDataDto.attributes!!.title
|
||||
val dirtyTitle = titleMap[lang]
|
||||
?: titleMap["en"]
|
||||
?: titleMap["ja-ro"]
|
||||
?: mangaDataDto.attributes.altTitles
|
||||
.map { it.toLocalizedString() }
|
||||
.find { (it[lang] ?: it["en"]) !== null }
|
||||
?.values?.singleOrNull()
|
||||
?: titleMap["ja"] // romaji titles are sometimes ja (and are not altTitles)
|
||||
|
@ -249,58 +294,46 @@ class MangaDexHelper(lang: String) {
|
|||
coverSuffix: String?
|
||||
): SManga {
|
||||
try {
|
||||
val attr = mangaDataDto.attributes
|
||||
val attr = mangaDataDto.attributes!!
|
||||
|
||||
// things that will go with the genre tags but aren't actually genre
|
||||
|
||||
val tempContentRating = attr.contentRating
|
||||
val contentRating =
|
||||
if (tempContentRating == null || tempContentRating.equals("safe", true)) {
|
||||
null
|
||||
} else {
|
||||
intl.contentRatingGenre(tempContentRating)
|
||||
}
|
||||
|
||||
val dexLocale = Locale.forLanguageTag(lang)
|
||||
|
||||
val nonGenres = listOf(
|
||||
(attr.publicationDemographic ?: "")
|
||||
.replaceFirstChar { it.uppercase(Locale.US) },
|
||||
contentRating,
|
||||
Locale(attr.originalLanguage ?: "")
|
||||
.getDisplayLanguage(dexLocale)
|
||||
.replaceFirstChar { it.uppercase(dexLocale) }
|
||||
val nonGenres = listOfNotNull(
|
||||
attr.publicationDemographic?.let { intl.publicationDemographic(it) },
|
||||
attr.contentRating
|
||||
.takeIf { it != ContentRatingDto.SAFE }
|
||||
?.let { intl.contentRatingGenre(it) },
|
||||
attr.originalLanguage
|
||||
?.let { Locale.forLanguageTag(it) }
|
||||
?.getDisplayName(dexLocale)
|
||||
?.replaceFirstChar { it.uppercase(dexLocale) }
|
||||
)
|
||||
|
||||
val authors = mangaDataDto.relationships
|
||||
.filter { it.type.equals(MDConstants.author, true) }
|
||||
.mapNotNull { it.attributes!!.name }
|
||||
.filterIsInstance<AuthorDto>()
|
||||
.mapNotNull { it.attributes?.name }
|
||||
.distinct()
|
||||
|
||||
val artists = mangaDataDto.relationships
|
||||
.filter { it.type.equals(MDConstants.artist, true) }
|
||||
.mapNotNull { it.attributes!!.name }
|
||||
.filterIsInstance<ArtistDto>()
|
||||
.mapNotNull { it.attributes?.name }
|
||||
.distinct()
|
||||
|
||||
val coverFileName = firstVolumeCover ?: mangaDataDto.relationships
|
||||
.firstOrNull { it.type.equals(MDConstants.coverArt, true) }
|
||||
.filterIsInstance<CoverArtDto>()
|
||||
.firstOrNull()
|
||||
?.attributes?.fileName
|
||||
|
||||
val tags = mdFilters.getTags(intl)
|
||||
val tags = mdFilters.getTags(intl).associate { it.id to it.name }
|
||||
|
||||
val genresMap = attr.tags
|
||||
.groupBy({ it.attributes.group }) { tagDto ->
|
||||
tags.firstOrNull { it.id == tagDto.id }?.name
|
||||
}
|
||||
.map { (group, tags) ->
|
||||
group to tags.filterNotNull().sortedWith(intl.collator)
|
||||
}
|
||||
.toMap()
|
||||
.groupBy({ it.attributes!!.group }) { tagDto -> tags[tagDto.id] }
|
||||
.mapValues { it.value.filterNotNull().sortedWith(intl.collator) }
|
||||
|
||||
val genreList = MDConstants.tagGroupsOrder.flatMap { genresMap[it].orEmpty() } +
|
||||
nonGenres.filterNotNull()
|
||||
val genreList = MDConstants.tagGroupsOrder.flatMap { genresMap[it].orEmpty() } + nonGenres
|
||||
|
||||
val desc = attr.description.toLocalizedString()
|
||||
val desc = attr.description
|
||||
|
||||
return createBasicManga(mangaDataDto, coverFileName, coverSuffix, lang).apply {
|
||||
description = cleanString(desc[lang] ?: desc["en"] ?: "")
|
||||
|
@ -322,18 +355,18 @@ class MangaDexHelper(lang: String) {
|
|||
*/
|
||||
fun createChapter(chapterDataDto: ChapterDataDto): SChapter? {
|
||||
try {
|
||||
val attr = chapterDataDto.attributes
|
||||
val attr = chapterDataDto.attributes!!
|
||||
|
||||
val groups = chapterDataDto.relationships
|
||||
.filter { it.type.equals(MDConstants.scanlator, true) }
|
||||
.filterIsInstance<ScanlationGroupDto>()
|
||||
.filterNot { it.id == MDConstants.legacyNoGroupId } // 'no group' left over from MDv3
|
||||
.mapNotNull { it.attributes!!.name }
|
||||
.mapNotNull { it.attributes?.name }
|
||||
.joinToString(" & ")
|
||||
.ifEmpty {
|
||||
// fall back to uploader name if no group
|
||||
val users = chapterDataDto.relationships
|
||||
.filter { it.type.equals(MDConstants.uploader, true) }
|
||||
.mapNotNull { it.attributes!!.username }
|
||||
.filterIsInstance<UserDto>()
|
||||
.mapNotNull { it.attributes?.username }
|
||||
if (users.isNotEmpty()) intl.uploadedBy(users) else ""
|
||||
}
|
||||
.ifEmpty { intl.noGroup } // "No Group" as final resort
|
||||
|
@ -407,9 +440,13 @@ class MangaDexHelper(lang: String) {
|
|||
fun setupEditTextUuidValidator(editText: EditText) {
|
||||
editText.addTextChangedListener(object : TextWatcher {
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
override fun afterTextChanged(editable: Editable?) {
|
||||
requireNotNull(editable)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package eu.kanade.tachiyomi.extension.all.mangadex
|
||||
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.ContentRatingDto
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.PublicationDemographicDto
|
||||
import java.text.Collator
|
||||
import java.util.Locale
|
||||
|
||||
|
@ -162,18 +164,18 @@ class MangaDexIntl(lang: String) {
|
|||
else -> "Pornographic"
|
||||
}
|
||||
|
||||
private val contentRatingMap: Map<String, String> = mapOf(
|
||||
"safe" to contentRatingSafe,
|
||||
"suggestive" to contentRatingSuggestive,
|
||||
"erotica" to contentRatingErotica,
|
||||
"pornographic" to contentRatingPornographic
|
||||
private val contentRatingMap: Map<ContentRatingDto, String> = mapOf(
|
||||
ContentRatingDto.SAFE to contentRatingSafe,
|
||||
ContentRatingDto.SUGGESTIVE to contentRatingSuggestive,
|
||||
ContentRatingDto.EROTICA to contentRatingErotica,
|
||||
ContentRatingDto.PORNOGRAPHIC to contentRatingPornographic
|
||||
)
|
||||
|
||||
fun contentRatingGenre(contentRatingKey: String): String = when (availableLang) {
|
||||
BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Classificação: ${contentRatingMap[contentRatingKey]}"
|
||||
SPANISH_LATAM, SPANISH -> "Clasificación: ${contentRatingMap[contentRatingKey]}"
|
||||
RUSSIAN -> "Рейтинг контента: ${contentRatingMap[contentRatingKey]}"
|
||||
else -> "$contentRating: ${contentRatingMap[contentRatingKey]}"
|
||||
fun contentRatingGenre(contentRating: ContentRatingDto): String = when (availableLang) {
|
||||
BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Classificação: ${contentRatingMap[contentRating]}"
|
||||
SPANISH_LATAM, SPANISH -> "Clasificación: ${contentRatingMap[contentRating]}"
|
||||
RUSSIAN -> "Рейтинг контента: ${contentRatingMap[contentRating]}"
|
||||
else -> "${this.contentRating}: ${contentRatingMap[contentRating]}"
|
||||
}
|
||||
|
||||
val originalLanguage: String = when (availableLang) {
|
||||
|
@ -313,6 +315,14 @@ class MangaDexIntl(lang: String) {
|
|||
else -> "Josei"
|
||||
}
|
||||
|
||||
fun publicationDemographic(demographic: PublicationDemographicDto): String = when (demographic) {
|
||||
PublicationDemographicDto.NONE -> publicationDemographicNone
|
||||
PublicationDemographicDto.SHOUNEN -> publicationDemographicShounen
|
||||
PublicationDemographicDto.SHOUJO -> publicationDemographicShoujo
|
||||
PublicationDemographicDto.SEINEN -> publicationDemographicSeinen
|
||||
PublicationDemographicDto.JOSEI -> publicationDemographicJosei
|
||||
}
|
||||
|
||||
val status: String = when (availableLang) {
|
||||
BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Estado"
|
||||
SPANISH_LATAM, SPANISH -> "Estado"
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package eu.kanade.tachiyomi.extension.all.mangadex.dto
|
||||
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.MDConstants
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName(MDConstants.author)
|
||||
data class AuthorDto(override val attributes: AuthorArtistAttributesDto? = null) : EntityDto()
|
||||
|
||||
@Serializable
|
||||
@SerialName(MDConstants.artist)
|
||||
data class ArtistDto(override val attributes: AuthorArtistAttributesDto? = null) : EntityDto()
|
||||
|
||||
@Serializable
|
||||
data class AuthorArtistAttributesDto(val name: String) : AttributesDto()
|
|
@ -1,28 +1,16 @@
|
|||
package eu.kanade.tachiyomi.extension.all.mangadex.dto
|
||||
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.MDConstants
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ChapterListDto(
|
||||
val limit: Int,
|
||||
val offset: Int,
|
||||
val total: Int,
|
||||
val data: List<ChapterDataDto>,
|
||||
)
|
||||
typealias ChapterListDto = PaginatedResponseDto<ChapterDataDto>
|
||||
|
||||
typealias ChapterDto = ResponseDto<ChapterDataDto>
|
||||
|
||||
@Serializable
|
||||
data class ChapterDto(
|
||||
val result: String,
|
||||
val data: ChapterDataDto,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ChapterDataDto(
|
||||
val id: String,
|
||||
val type: String,
|
||||
val attributes: ChapterAttributesDto,
|
||||
val relationships: List<RelationshipDto>,
|
||||
)
|
||||
@SerialName(MDConstants.chapter)
|
||||
data class ChapterDataDto(override val attributes: ChapterAttributesDto? = null) : EntityDto()
|
||||
|
||||
@Serializable
|
||||
data class ChapterAttributesDto(
|
||||
|
@ -32,4 +20,4 @@ data class ChapterAttributesDto(
|
|||
val pages: Int,
|
||||
val publishAt: String,
|
||||
val externalUrl: String?,
|
||||
)
|
||||
) : AttributesDto()
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package eu.kanade.tachiyomi.extension.all.mangadex.dto
|
||||
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.MDConstants
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
typealias CoverArtListDto = PaginatedResponseDto<CoverArtDto>
|
||||
|
||||
@Serializable
|
||||
@SerialName(MDConstants.coverArt)
|
||||
data class CoverArtDto(override val attributes: CoverArtAttributesDto? = null) : EntityDto()
|
||||
|
||||
@Serializable
|
||||
data class CoverArtAttributesDto(
|
||||
val fileName: String? = null,
|
||||
val locale: String? = null
|
||||
) : AttributesDto()
|
|
@ -1,22 +0,0 @@
|
|||
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
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
package eu.kanade.tachiyomi.extension.all.mangadex.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
abstract class EntityDto {
|
||||
val id: String = ""
|
||||
val relationships: List<EntityDto> = emptyList()
|
||||
abstract val attributes: AttributesDto?
|
||||
}
|
||||
|
||||
@Serializable
|
||||
abstract class AttributesDto
|
|
@ -1,25 +1,18 @@
|
|||
package eu.kanade.tachiyomi.extension.all.mangadex.dto
|
||||
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.MDConstants
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ListDto(
|
||||
val result: String,
|
||||
val response: String,
|
||||
val data: ListDataDto,
|
||||
)
|
||||
typealias ListDto = ResponseDto<ListDataDto>
|
||||
|
||||
@Serializable
|
||||
data class ListDataDto(
|
||||
val id: String,
|
||||
val type: String,
|
||||
val attributes: ListAttributesDto,
|
||||
val relationships: List<RelationshipDto>,
|
||||
)
|
||||
@SerialName(MDConstants.list)
|
||||
data class ListDataDto(override val attributes: ListAttributesDto? = null) : EntityDto()
|
||||
|
||||
@Serializable
|
||||
data class ListAttributesDto(
|
||||
val name: String,
|
||||
val visibility: String,
|
||||
val version: Int,
|
||||
)
|
||||
) : AttributesDto()
|
||||
|
|
|
@ -1,81 +1,90 @@
|
|||
package eu.kanade.tachiyomi.extension.all.mangadex.dto
|
||||
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.MDConstants
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.contentOrNull
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.serializer
|
||||
|
||||
typealias MangaListDto = PaginatedResponseDto<MangaDataDto>
|
||||
|
||||
typealias MangaDto = ResponseDto<MangaDataDto>
|
||||
|
||||
@Serializable
|
||||
data class MangaListDto(
|
||||
val limit: Int,
|
||||
val offset: Int,
|
||||
val total: Int,
|
||||
val data: List<MangaDataDto>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MangaDto(
|
||||
val result: String,
|
||||
val data: MangaDataDto,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RelationshipDto(
|
||||
val id: String,
|
||||
val type: String,
|
||||
val attributes: IncludesAttributesDto? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class IncludesAttributesDto(
|
||||
val name: String? = null,
|
||||
val fileName: String? = null,
|
||||
val username: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MangaDataDto(
|
||||
val id: String,
|
||||
val type: String,
|
||||
val attributes: MangaAttributesDto,
|
||||
val relationships: List<RelationshipDto>,
|
||||
)
|
||||
@SerialName(MDConstants.manga)
|
||||
data class MangaDataDto(override val attributes: MangaAttributesDto? = null) : EntityDto()
|
||||
|
||||
@Serializable
|
||||
data class MangaAttributesDto(
|
||||
val title: JsonElement,
|
||||
val altTitles: JsonArray,
|
||||
val description: JsonElement,
|
||||
val title: LocalizedString,
|
||||
val altTitles: List<LocalizedString>,
|
||||
val description: LocalizedString,
|
||||
val originalLanguage: String?,
|
||||
val lastVolume: String?,
|
||||
val lastChapter: String?,
|
||||
val contentRating: String?,
|
||||
val publicationDemographic: String?,
|
||||
val status: String?,
|
||||
val contentRating: ContentRatingDto? = null,
|
||||
val publicationDemographic: PublicationDemographicDto? = null,
|
||||
val status: StatusDto? = null,
|
||||
val tags: List<TagDto>,
|
||||
)
|
||||
) : AttributesDto()
|
||||
|
||||
@Serializable
|
||||
data class TagDto(
|
||||
val id: String,
|
||||
val attributes: TagAttributesDto
|
||||
)
|
||||
enum class ContentRatingDto(val value: String) {
|
||||
@SerialName("safe") SAFE("safe"),
|
||||
@SerialName("suggestive") SUGGESTIVE("suggestive"),
|
||||
@SerialName("erotica") EROTICA("erotica"),
|
||||
@SerialName("pornographic") PORNOGRAPHIC("pornographic")
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class TagAttributesDto(
|
||||
val group: String
|
||||
)
|
||||
enum class PublicationDemographicDto(val value: String) {
|
||||
@SerialName("none") NONE("none"),
|
||||
@SerialName("shounen") SHOUNEN("shounen"),
|
||||
@SerialName("shoujo") SHOUJO("shoujo"),
|
||||
@SerialName("josei") JOSEI("josei"),
|
||||
@SerialName("seinen") SEINEN("seinen")
|
||||
}
|
||||
|
||||
typealias LocalizedString = Map<String, String>
|
||||
@Serializable
|
||||
enum class StatusDto(val value: String) {
|
||||
@SerialName("ongoing") ONGOING("ongoing"),
|
||||
@SerialName("completed") COMPLETED("completed"),
|
||||
@SerialName("hiatus") HIATUS("hiatus"),
|
||||
@SerialName("cancelled") CANCELLED("cancelled")
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName(MDConstants.tag)
|
||||
data class TagDto(override val attributes: TagAttributesDto? = null) : EntityDto()
|
||||
|
||||
@Serializable
|
||||
data class TagAttributesDto(val group: String) : AttributesDto()
|
||||
|
||||
typealias LocalizedString = @Serializable(LocalizedStringSerializer::class) Map<String, String>
|
||||
|
||||
/**
|
||||
* 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()
|
||||
object LocalizedStringSerializer : KSerializer<Map<String, String>> {
|
||||
override val descriptor = buildClassSerialDescriptor("LocalizedString")
|
||||
|
||||
override fun deserialize(decoder: Decoder): Map<String, String> {
|
||||
require(decoder is JsonDecoder)
|
||||
|
||||
return (decoder.decodeJsonElement() as? JsonObject)
|
||||
?.mapValues { it.value.jsonPrimitive.contentOrNull ?: "" }
|
||||
.orEmpty()
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Map<String, String>) {
|
||||
encoder.encodeSerializableValue(serializer(), value)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package eu.kanade.tachiyomi.extension.all.mangadex.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class PaginatedResponseDto<T : EntityDto>(
|
||||
val result: String,
|
||||
val response: String = "",
|
||||
val data: List<T> = emptyList(),
|
||||
val limit: Int = 0,
|
||||
val offset: Int = 0,
|
||||
val total: Int = 0
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ResponseDto<T : EntityDto>(
|
||||
val result: String,
|
||||
val response: String = "",
|
||||
val data: T? = null
|
||||
)
|
|
@ -0,0 +1,12 @@
|
|||
package eu.kanade.tachiyomi.extension.all.mangadex.dto
|
||||
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.MDConstants
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName(MDConstants.scanlationGroup)
|
||||
data class ScanlationGroupDto(override val attributes: ScanlationGroupAttributes? = null) : EntityDto()
|
||||
|
||||
@Serializable
|
||||
data class ScanlationGroupAttributes(val name: String) : AttributesDto()
|
|
@ -0,0 +1,12 @@
|
|||
package eu.kanade.tachiyomi.extension.all.mangadex.dto
|
||||
|
||||
import eu.kanade.tachiyomi.extension.all.mangadex.MDConstants
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName(MDConstants.user)
|
||||
data class UserDto(override val attributes: UserAttributes? = null) : EntityDto()
|
||||
|
||||
@Serializable
|
||||
data class UserAttributes(val username: String) : AttributesDto()
|
Loading…
Reference in New Issue