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:
Alessandro Jean 2022-11-03 11:32:36 -03:00 committed by GitHub
parent 6dff9d8cfa
commit bd4faa01d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 368 additions and 236 deletions

View File

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

View File

@ -12,12 +12,15 @@ object MDConstants {
const val mangaLimit = 20 const val mangaLimit = 20
const val latestChapterLimit = 100 const val latestChapterLimit = 100
const val chapter = "chapter"
const val manga = "manga" const val manga = "manga"
const val coverArt = "cover_art" const val coverArt = "cover_art"
const val scanlator = "scanlation_group" const val scanlationGroup = "scanlation_group"
const val uploader = "user" const val user = "user"
const val author = "author" const val author = "author"
const val artist = "artist" const val artist = "artist"
const val tag = "tag"
const val list = "custom_list"
const val legacyNoGroupId = "00e03853-1b96-4f41-9542-c71b8692033b" const val legacyNoGroupId = "00e03853-1b96-4f41-9542-c71b8692033b"
const val cdnUrl = "https://uploads.mangadex.org" const val cdnUrl = "https://uploads.mangadex.org"

View File

@ -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.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.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.ListDto
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.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.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimit 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 mangaList = mangaListDto.data.map { mangaDataDto ->
val fileName = firstVolumeCovers[mangaDataDto.id] ?: mangaDataDto.relationships val fileName = firstVolumeCovers[mangaDataDto.id] ?: mangaDataDto.relationships
.firstOrNull { it.type.equals(MDConstants.coverArt, true) } .filterIsInstance<CoverArtDto>()
.firstOrNull()
?.attributes?.fileName ?.attributes?.fileName
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang) 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 val mangaIds = chapterListDto.data
.flatMap { it.relationships } .flatMap { it.relationships }
.filter { it.type == MDConstants.manga } .filterIsInstance<MangaDataDto>()
.map { it.id } .map { it.id }
.distinct() .distinct()
.toSet() .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 mangaList = mangaIds.mapNotNull { mangaDtoMap[it] }.map { mangaDataDto ->
val fileName = firstVolumeCovers[mangaDataDto.id] ?: mangaDataDto.relationships val fileName = firstVolumeCovers[mangaDataDto.id] ?: mangaDataDto.relationships
.firstOrNull { it.type.equals(MDConstants.coverArt, true) } .filterIsInstance<CoverArtDto>()
.firstOrNull()
?.attributes?.fileName ?.attributes?.fileName
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang) 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 MDConstants.defaultBlockedGroups + preferences.blockedGroups
) )
.addQueryParameter("excludedUploaders[]", preferences.blockedUploaders) .addQueryParameter("excludedUploaders[]", preferences.blockedUploaders)
.addQueryParameter("includeFuturePublishAt", "0")
.addQueryParameter("includeEmptyPages", "0")
return GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK) 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 // SEARCH section
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
when { return when {
query.startsWith(MDConstants.prefixChSearch) -> query.startsWith(MDConstants.prefixChSearch) ->
return getMangaIdFromChapterId(query.removePrefix(MDConstants.prefixChSearch)).flatMap { manga_id -> getMangaIdFromChapterId(query.removePrefix(MDConstants.prefixChSearch))
super.fetchSearchManga(page, MDConstants.prefixIdSearch + manga_id, filters) .flatMap { mangaId ->
super.fetchSearchManga(
page = page,
query = MDConstants.prefixIdSearch + mangaId,
filters = filters
)
} }
query.startsWith(MDConstants.prefixUsrSearch) -> 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() .asObservableSuccess()
.map { latestUpdatesParse(it) } .map { latestUpdatesParse(it) }
query.startsWith(MDConstants.prefixListSearch) -> 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() .asObservableSuccess()
.map { searchMangaListRequest(it, page) } .map { searchMangaListParse(it, page) }
else -> else -> super.fetchSearchManga(page, query.trim(), filters)
return 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)) throw Exception(helper.intl.unableToProcessChapterRequest(response.code))
} }
response.parseAs<ChapterDto>().data.relationships response.parseAs<ChapterDto>().data!!.relationships
.find { it.type == MDConstants.manga }!!.id .filterIsInstance<MangaDataDto>()
.firstOrNull()!!.id
} }
} }
@ -223,23 +243,21 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
} }
query.startsWith(MDConstants.prefixGrpSearch) -> { query.startsWith(MDConstants.prefixGrpSearch) -> {
val groupID = query.removePrefix(MDConstants.prefixGrpSearch) val groupId = query.removePrefix(MDConstants.prefixGrpSearch)
if (!helper.containsUuid(groupID)) { if (!helper.containsUuid(groupId)) {
throw Exception(helper.intl.invalidGroupId) throw Exception(helper.intl.invalidGroupId)
} }
tempUrl.addQueryParameter("group", groupID) tempUrl.addQueryParameter("group", groupId)
} }
query.startsWith(MDConstants.prefixAuthSearch) -> { query.startsWith(MDConstants.prefixAuthSearch) -> {
val authorID = query.removePrefix(MDConstants.prefixAuthSearch) val authorId = query.removePrefix(MDConstants.prefixAuthSearch)
if (!helper.containsUuid(authorID)) { if (!helper.containsUuid(authorId)) {
throw Exception(helper.intl.invalidAuthorId) throw Exception(helper.intl.invalidAuthorId)
} }
tempUrl tempUrl.addQueryParameter("authorOrArtist", authorId)
.addQueryParameter("authors[]", authorID)
.addQueryParameter("artists[]", authorID)
} }
else -> { 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) 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 listDto = response.parseAs<ListDto>()
val listDtoFiltered = listDto.data.relationships.filter { it.type != "Manga" } val listDtoFiltered = listDto.data!!.relationships.filterIsInstance<MangaDataDto>()
val amount = listDtoFiltered.count() val amount = listDtoFiltered.count()
if (amount < 1) { if (amount < 1) {
@ -280,7 +302,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
val ids = listDtoFiltered val ids = listDtoFiltered
.filterIndexed { i, _ -> i >= minIndex && i < (minIndex + MDConstants.mangaLimit) } .filterIndexed { i, _ -> i >= minIndex && i < (minIndex + MDConstants.mangaLimit) }
.map(RelationshipDto::id) .map(MangaDataDto::id)
.toSet() .toSet()
url.addQueryParameter("ids[]", ids) 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 mangaList = mangaListDto.data.map { mangaDataDto ->
val fileName = firstVolumeCovers[mangaDataDto.id] ?: mangaDataDto.relationships val fileName = firstVolumeCovers[mangaDataDto.id] ?: mangaDataDto.relationships
.firstOrNull { it.type.equals(MDConstants.coverArt, true) } .filterIsInstance<CoverArtDto>()
.firstOrNull()
?.attributes?.fileName ?.attributes?.fileName
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang) 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("translatedLanguage[]", dexLang)
.addQueryParameter("order[publishAt]", "desc") .addQueryParameter("order[publishAt]", "desc")
.addQueryParameter("includeFutureUpdates", "0") .addQueryParameter("includeFutureUpdates", "0")
.addQueryParameter("includeFuturePublishAt", "0")
.addQueryParameter("includeEmptyPages", "0")
.addQueryParameter("uploader", uploader) .addQueryParameter("uploader", uploader)
.addQueryParameter("originalLanguage[]", preferences.originalLanguages) .addQueryParameter("originalLanguage[]", preferences.originalLanguages)
.addQueryParameter("contentRating[]", preferences.contentRating) .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>() val manga = response.parseAs<MangaDto>()
return helper.createManga( return helper.createManga(
manga.data, manga.data!!,
fetchSimpleChapterList(manga, dexLang), fetchSimpleChapterList(manga, dexLang),
fetchFirstVolumeCover(manga), fetchFirstVolumeCover(manga),
dexLang, dexLang,
@ -388,7 +413,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
* @see AggregateDto * @see AggregateDto
*/ */
private fun fetchSimpleChapterList(manga: MangaDto, langCode: String): Map<String, AggregateVolume> { 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()
return runCatching { response.parseAs<AggregateDto>() } 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. * Attempt to get the first volume cover if the setting is enabled.
* Uses the 'covers' endpoint. * Uses the 'covers' endpoint.
* *
* @see CoverListDto * @see CoverArtListDto
*/ */
private fun fetchFirstVolumeCover(manga: MangaDto): String? { 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. * Attempt to get the first volume cover if the setting is enabled.
* Uses the 'covers' endpoint. * Uses the 'covers' endpoint.
* *
* @see CoverListDto * @see CoverArtListDto
*/ */
private fun fetchFirstVolumeCovers(mangaList: List<MangaDataDto>): Map<String, String>? { private fun fetchFirstVolumeCovers(mangaList: List<MangaDataDto>): Map<String, String>? {
if (!preferences.tryUsingFirstVolumeCover || mangaList.isEmpty()) { if (!preferences.tryUsingFirstVolumeCover || mangaList.isEmpty()) {
return null return null
} }
val mangaMap = mangaList.associate { it.id to it.attributes } val mangaMap = mangaList.associate { it.id to it.attributes!! }
.filterValues { !it.originalLanguage.isNullOrEmpty() } .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 limit = (mangaMap.size * locales.size).coerceAtMost(100)
val apiUrl = "${MDConstants.apiUrl}/cover".toHttpUrl().newBuilder() val apiUrl = "${MDConstants.apiUrl}/cover".toHttpUrl().newBuilder()
@ -430,13 +455,16 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
.toString() .toString()
val result = runCatching { 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 val covers = result.getOrNull() ?: return null
return covers return covers
.groupBy { it.relationships.find { r -> r.type == MDConstants.manga }!!.id } .groupBy {
it.relationships.filterIsInstance<MangaDataDto>()
.firstOrNull()!!.id
}
.mapValues { .mapValues {
it.value.find { c -> c.attributes?.locale == mangaMap[it.key]?.originalLanguage } 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 hasMoreResults = (limit + offset) < newChapterList.total
} }
val now = Date().time return chapterListResults.mapNotNull(helper::createChapter)
return chapterListResults
.mapNotNull { helper.createChapter(it) }
.filter { it.date_upload <= now }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("MangaDex", "error parsing chapter list", e) Log.e("MangaDex", "error parsing chapter list", e)
throw e throw e
} }
} }
override fun pageListRequest(chapter: SChapter): Request { override fun pageListRequest(chapter: SChapter): Request {
if (!helper.containsUuid(chapter.url)) { if (!helper.containsUuid(chapter.url)) {
throw Exception(helper.intl.migrateWarning) throw Exception(helper.intl.migrateWarning)

View File

@ -1,6 +1,9 @@
package eu.kanade.tachiyomi.extension.all.mangadex package eu.kanade.tachiyomi.extension.all.mangadex
import android.content.SharedPreferences 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.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import okhttp3.HttpUrl import okhttp3.HttpUrl
@ -108,21 +111,17 @@ class MangaDexFilters {
) )
return listOf( return listOf(
ContentRating(intl.contentRatingSafe, MDConstants.contentRatingPrefValSafe).apply { ContentRating(intl.contentRatingSafe, ContentRatingDto.SAFE.value).apply {
state = contentRatings state = contentRatings?.contains(MDConstants.contentRatingPrefValSafe) ?: true
?.contains(MDConstants.contentRatingPrefValSafe) ?: true
}, },
ContentRating(intl.contentRatingSuggestive, MDConstants.contentRatingPrefValSuggestive).apply { ContentRating(intl.contentRatingSuggestive, ContentRatingDto.SUGGESTIVE.value).apply {
state = contentRatings state = contentRatings?.contains(MDConstants.contentRatingPrefValSuggestive) ?: true
?.contains(MDConstants.contentRatingPrefValSuggestive) ?: true
}, },
ContentRating(intl.contentRatingErotica, MDConstants.contentRatingPrefValErotica).apply { ContentRating(intl.contentRatingErotica, ContentRatingDto.EROTICA.value).apply {
state = contentRatings state = contentRatings?.contains(MDConstants.contentRatingPrefValErotica) ?: false
?.contains(MDConstants.contentRatingPrefValErotica) ?: false
}, },
ContentRating(intl.contentRatingPornographic, MDConstants.contentRatingPrefValPornographic).apply { ContentRating(intl.contentRatingPornographic, ContentRatingDto.PORNOGRAPHIC.value).apply {
state = contentRatings state = contentRatings?.contains(MDConstants.contentRatingPrefValPornographic) ?: false
?.contains(MDConstants.contentRatingPrefValPornographic) ?: false
}, },
) )
} }
@ -142,11 +141,11 @@ class MangaDexFilters {
} }
private fun getDemographics(intl: MangaDexIntl) = listOf( private fun getDemographics(intl: MangaDexIntl) = listOf(
Demographic(intl.publicationDemographicNone, "none"), Demographic(intl.publicationDemographicNone, PublicationDemographicDto.NONE.value),
Demographic(intl.publicationDemographicShounen, "shounen"), Demographic(intl.publicationDemographicShounen, PublicationDemographicDto.SHOUNEN.value),
Demographic(intl.publicationDemographicShoujo, "shoujo"), Demographic(intl.publicationDemographicShoujo, PublicationDemographicDto.SHOUJO.value),
Demographic(intl.publicationDemographicSeinen, "seinen"), Demographic(intl.publicationDemographicSeinen, PublicationDemographicDto.SEINEN.value),
Demographic(intl.publicationDemographicJosei, "josei") Demographic(intl.publicationDemographicJosei, PublicationDemographicDto.JOSEI.value)
) )
private class Status(name: String, val value: String) : Filter.CheckBox(name) private class Status(name: String, val value: String) : Filter.CheckBox(name)
@ -164,10 +163,10 @@ class MangaDexFilters {
} }
private fun getStatus(intl: MangaDexIntl) = listOf( private fun getStatus(intl: MangaDexIntl) = listOf(
Status(intl.statusOngoing, "ongoing"), Status(intl.statusOngoing, StatusDto.ONGOING.value),
Status(intl.statusCompleted, "completed"), Status(intl.statusCompleted, StatusDto.COMPLETED.value),
Status(intl.statusHiatus, "hiatus"), Status(intl.statusHiatus, StatusDto.HIATUS.value),
Status(intl.statusCancelled, "cancelled"), Status(intl.statusCancelled, StatusDto.CANCELLED.value),
) )
data class Sortable(val title: String, val value: String) { data class Sortable(val title: String, val value: String) {

View File

@ -6,17 +6,38 @@ 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.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.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.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.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.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.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.modules.SerializersModule
import kotlinx.serialization.modules.plus
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
import okhttp3.CacheControl import okhttp3.CacheControl
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
@ -35,8 +56,31 @@ class MangaDexHelper(lang: String) {
isLenient = true isLenient = true
ignoreUnknownKeys = true ignoreUnknownKeys = true
allowSpecialFloatingPointValues = true allowSpecialFloatingPointValues = true
useArrayPolymorphism = true
prettyPrint = 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) val intl = MangaDexIntl(lang)
@ -51,13 +95,15 @@ class MangaDexHelper(lang: String) {
*/ */
fun getChapterEndpoint(mangaId: String, offset: Int, langCode: String) = fun getChapterEndpoint(mangaId: String, offset: Int, langCode: String) =
"${MDConstants.apiMangaUrl}/$mangaId/feed".toHttpUrl().newBuilder() "${MDConstants.apiMangaUrl}/$mangaId/feed".toHttpUrl().newBuilder()
.addQueryParameter("includes[]", MDConstants.scanlator) .addQueryParameter("includes[]", MDConstants.scanlationGroup)
.addQueryParameter("includes[]", MDConstants.uploader) .addQueryParameter("includes[]", MDConstants.user)
.addQueryParameter("limit", "500") .addQueryParameter("limit", "500")
.addQueryParameter("offset", offset.toString()) .addQueryParameter("offset", offset.toString())
.addQueryParameter("translatedLanguage[]", langCode) .addQueryParameter("translatedLanguage[]", langCode)
.addQueryParameter("order[volume]", "desc") .addQueryParameter("order[volume]", "desc")
.addQueryParameter("order[chapter]", "desc") .addQueryParameter("order[chapter]", "desc")
.addQueryParameter("includeFuturePublishAt", "0")
.addQueryParameter("includeEmptyPages", "0")
.toString() .toString()
/** /**
@ -104,10 +150,10 @@ class MangaDexHelper(lang: String) {
.map { it.chapter } .map { it.chapter }
val tempStatus = when (attr.status) { val tempStatus = when (attr.status) {
"ongoing" -> SManga.ONGOING StatusDto.ONGOING -> SManga.ONGOING
"cancelled" -> SManga.CANCELLED StatusDto.CANCELLED -> SManga.CANCELLED
"completed" -> SManga.PUBLISHING_FINISHED StatusDto.COMPLETED -> SManga.PUBLISHING_FINISHED
"hiatus" -> SManga.ON_HIATUS StatusDto.HIATUS -> SManga.ON_HIATUS
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
@ -217,12 +263,11 @@ class MangaDexHelper(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.toLocalizedString() val titleMap = mangaDataDto.attributes!!.title
val dirtyTitle = titleMap[lang] val dirtyTitle = titleMap[lang]
?: titleMap["en"] ?: titleMap["en"]
?: titleMap["ja-ro"] ?: titleMap["ja-ro"]
?: mangaDataDto.attributes.altTitles ?: mangaDataDto.attributes.altTitles
.map { it.toLocalizedString() }
.find { (it[lang] ?: it["en"]) !== null } .find { (it[lang] ?: it["en"]) !== null }
?.values?.singleOrNull() ?.values?.singleOrNull()
?: titleMap["ja"] // romaji titles are sometimes ja (and are not altTitles) ?: titleMap["ja"] // romaji titles are sometimes ja (and are not altTitles)
@ -249,58 +294,46 @@ class MangaDexHelper(lang: String) {
coverSuffix: String? coverSuffix: String?
): SManga { ): SManga {
try { try {
val attr = mangaDataDto.attributes val attr = mangaDataDto.attributes!!
// things that will go with the genre tags but aren't actually genre // 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 dexLocale = Locale.forLanguageTag(lang)
val nonGenres = listOf( val nonGenres = listOfNotNull(
(attr.publicationDemographic ?: "") attr.publicationDemographic?.let { intl.publicationDemographic(it) },
.replaceFirstChar { it.uppercase(Locale.US) }, attr.contentRating
contentRating, .takeIf { it != ContentRatingDto.SAFE }
Locale(attr.originalLanguage ?: "") ?.let { intl.contentRatingGenre(it) },
.getDisplayLanguage(dexLocale) attr.originalLanguage
.replaceFirstChar { it.uppercase(dexLocale) } ?.let { Locale.forLanguageTag(it) }
?.getDisplayName(dexLocale)
?.replaceFirstChar { it.uppercase(dexLocale) }
) )
val authors = mangaDataDto.relationships val authors = mangaDataDto.relationships
.filter { it.type.equals(MDConstants.author, true) } .filterIsInstance<AuthorDto>()
.mapNotNull { it.attributes!!.name } .mapNotNull { it.attributes?.name }
.distinct() .distinct()
val artists = mangaDataDto.relationships val artists = mangaDataDto.relationships
.filter { it.type.equals(MDConstants.artist, true) } .filterIsInstance<ArtistDto>()
.mapNotNull { it.attributes!!.name } .mapNotNull { it.attributes?.name }
.distinct() .distinct()
val coverFileName = firstVolumeCover ?: mangaDataDto.relationships val coverFileName = firstVolumeCover ?: mangaDataDto.relationships
.firstOrNull { it.type.equals(MDConstants.coverArt, true) } .filterIsInstance<CoverArtDto>()
.firstOrNull()
?.attributes?.fileName ?.attributes?.fileName
val tags = mdFilters.getTags(intl) val tags = mdFilters.getTags(intl).associate { it.id to it.name }
val genresMap = attr.tags val genresMap = attr.tags
.groupBy({ it.attributes.group }) { tagDto -> .groupBy({ it.attributes!!.group }) { tagDto -> tags[tagDto.id] }
tags.firstOrNull { it.id == tagDto.id }?.name .mapValues { it.value.filterNotNull().sortedWith(intl.collator) }
}
.map { (group, tags) ->
group to tags.filterNotNull().sortedWith(intl.collator)
}
.toMap()
val genreList = MDConstants.tagGroupsOrder.flatMap { genresMap[it].orEmpty() } + val genreList = MDConstants.tagGroupsOrder.flatMap { genresMap[it].orEmpty() } + nonGenres
nonGenres.filterNotNull()
val desc = attr.description.toLocalizedString() val desc = attr.description
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"] ?: "")
@ -322,18 +355,18 @@ class MangaDexHelper(lang: String) {
*/ */
fun createChapter(chapterDataDto: ChapterDataDto): SChapter? { fun createChapter(chapterDataDto: ChapterDataDto): SChapter? {
try { try {
val attr = chapterDataDto.attributes val attr = chapterDataDto.attributes!!
val groups = chapterDataDto.relationships val groups = chapterDataDto.relationships
.filter { it.type.equals(MDConstants.scanlator, true) } .filterIsInstance<ScanlationGroupDto>()
.filterNot { it.id == MDConstants.legacyNoGroupId } // 'no group' left over from MDv3 .filterNot { it.id == MDConstants.legacyNoGroupId } // 'no group' left over from MDv3
.mapNotNull { it.attributes!!.name } .mapNotNull { it.attributes?.name }
.joinToString(" & ") .joinToString(" & ")
.ifEmpty { .ifEmpty {
// fall back to uploader name if no group // fall back to uploader name if no group
val users = chapterDataDto.relationships val users = chapterDataDto.relationships
.filter { it.type.equals(MDConstants.uploader, true) } .filterIsInstance<UserDto>()
.mapNotNull { it.attributes!!.username } .mapNotNull { it.attributes?.username }
if (users.isNotEmpty()) intl.uploadedBy(users) else "" if (users.isNotEmpty()) intl.uploadedBy(users) else ""
} }
.ifEmpty { intl.noGroup } // "No Group" as final resort .ifEmpty { intl.noGroup } // "No Group" as final resort
@ -407,9 +440,13 @@ class MangaDexHelper(lang: String) {
fun setupEditTextUuidValidator(editText: EditText) { fun setupEditTextUuidValidator(editText: EditText) {
editText.addTextChangedListener(object : TextWatcher { 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?) { override fun afterTextChanged(editable: Editable?) {
requireNotNull(editable) requireNotNull(editable)

View File

@ -1,5 +1,7 @@
package eu.kanade.tachiyomi.extension.all.mangadex 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.text.Collator
import java.util.Locale import java.util.Locale
@ -162,18 +164,18 @@ class MangaDexIntl(lang: String) {
else -> "Pornographic" else -> "Pornographic"
} }
private val contentRatingMap: Map<String, String> = mapOf( private val contentRatingMap: Map<ContentRatingDto, String> = mapOf(
"safe" to contentRatingSafe, ContentRatingDto.SAFE to contentRatingSafe,
"suggestive" to contentRatingSuggestive, ContentRatingDto.SUGGESTIVE to contentRatingSuggestive,
"erotica" to contentRatingErotica, ContentRatingDto.EROTICA to contentRatingErotica,
"pornographic" to contentRatingPornographic ContentRatingDto.PORNOGRAPHIC to contentRatingPornographic
) )
fun contentRatingGenre(contentRatingKey: String): String = when (availableLang) { fun contentRatingGenre(contentRating: ContentRatingDto): String = when (availableLang) {
BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Classificação: ${contentRatingMap[contentRatingKey]}" BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Classificação: ${contentRatingMap[contentRating]}"
SPANISH_LATAM, SPANISH -> "Clasificación: ${contentRatingMap[contentRatingKey]}" SPANISH_LATAM, SPANISH -> "Clasificación: ${contentRatingMap[contentRating]}"
RUSSIAN -> "Рейтинг контента: ${contentRatingMap[contentRatingKey]}" RUSSIAN -> "Рейтинг контента: ${contentRatingMap[contentRating]}"
else -> "$contentRating: ${contentRatingMap[contentRatingKey]}" else -> "${this.contentRating}: ${contentRatingMap[contentRating]}"
} }
val originalLanguage: String = when (availableLang) { val originalLanguage: String = when (availableLang) {
@ -313,6 +315,14 @@ class MangaDexIntl(lang: String) {
else -> "Josei" 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) { val status: String = when (availableLang) {
BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Estado" BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Estado"
SPANISH_LATAM, SPANISH -> "Estado" SPANISH_LATAM, SPANISH -> "Estado"

View File

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

View File

@ -1,28 +1,16 @@
package eu.kanade.tachiyomi.extension.all.mangadex.dto package eu.kanade.tachiyomi.extension.all.mangadex.dto
import eu.kanade.tachiyomi.extension.all.mangadex.MDConstants
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable typealias ChapterListDto = PaginatedResponseDto<ChapterDataDto>
data class ChapterListDto(
val limit: Int, typealias ChapterDto = ResponseDto<ChapterDataDto>
val offset: Int,
val total: Int,
val data: List<ChapterDataDto>,
)
@Serializable @Serializable
data class ChapterDto( @SerialName(MDConstants.chapter)
val result: String, data class ChapterDataDto(override val attributes: ChapterAttributesDto? = null) : EntityDto()
val data: ChapterDataDto,
)
@Serializable
data class ChapterDataDto(
val id: String,
val type: String,
val attributes: ChapterAttributesDto,
val relationships: List<RelationshipDto>,
)
@Serializable @Serializable
data class ChapterAttributesDto( data class ChapterAttributesDto(
@ -32,4 +20,4 @@ data class ChapterAttributesDto(
val pages: Int, val pages: Int,
val publishAt: String, val publishAt: String,
val externalUrl: String?, val externalUrl: String?,
) ) : AttributesDto()

View File

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

View File

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

View File

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

View File

@ -1,25 +1,18 @@
package eu.kanade.tachiyomi.extension.all.mangadex.dto package eu.kanade.tachiyomi.extension.all.mangadex.dto
import eu.kanade.tachiyomi.extension.all.mangadex.MDConstants
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable typealias ListDto = ResponseDto<ListDataDto>
data class ListDto(
val result: String,
val response: String,
val data: ListDataDto,
)
@Serializable @Serializable
data class ListDataDto( @SerialName(MDConstants.list)
val id: String, data class ListDataDto(override val attributes: ListAttributesDto? = null) : EntityDto()
val type: String,
val attributes: ListAttributesDto,
val relationships: List<RelationshipDto>,
)
@Serializable @Serializable
data class ListAttributesDto( data class ListAttributesDto(
val name: String, val name: String,
val visibility: String, val visibility: String,
val version: Int, val version: Int,
) ) : AttributesDto()

View File

@ -1,81 +1,90 @@
package eu.kanade.tachiyomi.extension.all.mangadex.dto 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.Serializable
import kotlinx.serialization.json.JsonArray import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.serializer
typealias MangaListDto = PaginatedResponseDto<MangaDataDto>
typealias MangaDto = ResponseDto<MangaDataDto>
@Serializable @Serializable
data class MangaListDto( @SerialName(MDConstants.manga)
val limit: Int, data class MangaDataDto(override val attributes: MangaAttributesDto? = null) : EntityDto()
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>,
)
@Serializable @Serializable
data class MangaAttributesDto( data class MangaAttributesDto(
val title: JsonElement, val title: LocalizedString,
val altTitles: JsonArray, val altTitles: List<LocalizedString>,
val description: JsonElement, val description: LocalizedString,
val originalLanguage: String?, val originalLanguage: String?,
val lastVolume: String?, val lastVolume: String?,
val lastChapter: String?, val lastChapter: String?,
val contentRating: String?, val contentRating: ContentRatingDto? = null,
val publicationDemographic: String?, val publicationDemographic: PublicationDemographicDto? = null,
val status: String?, val status: StatusDto? = null,
val tags: List<TagDto>, val tags: List<TagDto>,
) ) : AttributesDto()
@Serializable @Serializable
data class TagDto( enum class ContentRatingDto(val value: String) {
val id: String, @SerialName("safe") SAFE("safe"),
val attributes: TagAttributesDto @SerialName("suggestive") SUGGESTIVE("suggestive"),
) @SerialName("erotica") EROTICA("erotica"),
@SerialName("pornographic") PORNOGRAPHIC("pornographic")
}
@Serializable @Serializable
data class TagAttributesDto( enum class PublicationDemographicDto(val value: String) {
val group: 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 * Temporary workaround while Dex API still returns arrays instead of objects
* in the places that uses [LocalizedString]. * in the places that uses [LocalizedString].
*/ */
fun JsonElement.toLocalizedString(): LocalizedString { object LocalizedStringSerializer : KSerializer<Map<String, String>> {
return (this as? JsonObject)?.entries override val descriptor = buildClassSerialDescriptor("LocalizedString")
?.associate { (key, value) -> key to (value.jsonPrimitive.contentOrNull ?: "") }
override fun deserialize(decoder: Decoder): Map<String, String> {
require(decoder is JsonDecoder)
return (decoder.decodeJsonElement() as? JsonObject)
?.mapValues { it.value.jsonPrimitive.contentOrNull ?: "" }
.orEmpty() .orEmpty()
}
override fun serialize(encoder: Encoder, value: Map<String, String>) {
encoder.encodeSerializableValue(serializer(), value)
}
} }

View File

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

View File

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

View File

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