From 49c930d622c6e6c835720da014b82e34924fade0 Mon Sep 17 00:00:00 2001 From: Carlos <2092019+CarlosEsco@users.noreply.github.com> Date: Tue, 8 Jun 2021 20:58:56 -0400 Subject: [PATCH] MangaDex: Switch to serialization, use lang descriptions if available, fix open in webview, fix publication status, possibly fix md@home retry (#7540) * switch to kotlinx * use baseurl in referer * remove default sort cause we don't use at this time update build.gradle * Use correct field when parsing manga pub status * Use current language if available in description map. * potentially fix md@host refresh issue * add default sort back since that's by number of follows * use work around for webview credit @Nar1n Co-authored-by: animusfracto <50589737+animusfracto@users.noreply.github.com> Co-authored-by: Alessandro Jean --- src/all/mangadex/build.gradle | 3 +- .../extension/all/mangadex/MangaDex.kt | 78 ++++----- .../extension/all/mangadex/MangaDexFilters.kt | 4 +- .../extension/all/mangadex/MangaDexHelper.kt | 160 ++++++++++-------- .../extension/all/mangadex/dto/AtHomeDto.kt | 17 ++ .../extension/all/mangadex/dto/ChapterDto.kt | 61 +++++++ .../extension/all/mangadex/dto/MangaDto.kt | 94 ++++++++++ 7 files changed, 303 insertions(+), 114 deletions(-) create mode 100644 src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/dto/AtHomeDto.kt create mode 100644 src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/dto/ChapterDto.kt create mode 100644 src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/dto/MangaDto.kt diff --git a/src/all/mangadex/build.gradle b/src/all/mangadex/build.gradle index 568d1b57d..942bcaad7 100644 --- a/src/all/mangadex/build.gradle +++ b/src/all/mangadex/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'MangaDex' pkgNameSuffix = 'all.mangadex' extClass = '.MangaDexFactory' - extVersionCode = 117 + extVersionCode = 118 libVersion = '1.2' containsNsfw = true } diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDex.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDex.kt index 67a4da80b..7937585f7 100644 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDex.kt +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDex.kt @@ -5,12 +5,10 @@ import android.content.SharedPreferences import android.util.Log import androidx.preference.PreferenceScreen import androidx.preference.SwitchPreferenceCompat -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.obj -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonParser +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.MangaDto +import eu.kanade.tachiyomi.extension.all.mangadex.dto.MangaListDto import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.source.ConfigurableSource @@ -20,6 +18,7 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource +import kotlinx.serialization.decodeFromString import okhttp3.CacheControl import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrl @@ -35,7 +34,7 @@ abstract class MangaDex(override val lang: String, val dexLang: String) : ConfigurableSource, HttpSource() { override val name = "MangaDex" - override val baseUrl = "https://www.mangadex.org" + override val baseUrl = "https://mangadex.org" // after mvp comes out make current popular becomes latest (mvp doesnt have a browse page) override val supportsLatest = false @@ -47,7 +46,7 @@ abstract class MangaDex(override val lang: String, val dexLang: String) : private val helper = MangaDexHelper() override fun headersBuilder() = Headers.Builder() - .add("Referer", "https://mangadex.org/") + .add("Referer", "$baseUrl/") .add("User-Agent", "Tachiyomi " + System.getProperty("http.agent")) override val client = network.client.newBuilder() @@ -103,25 +102,27 @@ abstract class MangaDex(override val lang: String, val dexLang: String) : if (response.code == 204) { return MangasPage(emptyList(), false) } + val mangaListDto = helper.json.decodeFromString(response.body!!.string()) + val hasMoreResults = mangaListDto.limit + mangaListDto.offset < mangaListDto.total - val mangaListResponse = JsonParser.parseString(response.body!!.string()).obj - val hasMoreResults = - (mangaListResponse["limit"].int + mangaListResponse["offset"].int) < mangaListResponse["total"].int - - val idsAndCoverIds = mangaListResponse["results"].array.map { mangaJson -> - val mangaId = mangaJson["data"].obj["id"].string - val coverId = mangaJson["relationships"].array.filter { relationship -> - relationship["type"].string.equals("cover_art", true) - }.map { relationship -> relationship["id"].string }.first() - Pair(mangaId, coverId) + val idsAndCoverIds = mangaListDto.results.mapNotNull { mangaDto -> + val mangaId = mangaDto.data.id + val coverId = mangaDto.relationships.firstOrNull { relationshipDto -> + relationshipDto.type.equals("cover_art", true) + }?.id + if (coverId == null) { + null + } else { + Pair(mangaId, coverId) + } }.toMap() val results = runCatching { helper.getBatchCoversUrl(idsAndCoverIds, client) }.getOrNull()!! - val mangaList = mangaListResponse["results"].array.map { - helper.createBasicManga(it, client).apply { + val mangaList = mangaListDto.results.map { + helper.createBasicManga(it).apply { thumbnail_url = results[url.substringAfter("/manga/")] } } @@ -173,7 +174,8 @@ abstract class MangaDex(override val lang: String, val dexLang: String) : } override fun mangaDetailsRequest(manga: SManga): Request { - return GET("${baseUrl}${manga.url}", headers) + //remove once redirect for /manga is fixed + return GET("${baseUrl}${manga.url.replace("manga", "title")}", headers) } /** @@ -187,8 +189,8 @@ abstract class MangaDex(override val lang: String, val dexLang: String) : } override fun mangaDetailsParse(response: Response): SManga { - val manga = JsonParser.parseString(response.body!!.string()).obj - return helper.createManga(manga, client) + val manga = helper.json.decodeFromString(response.body!!.string()) + return helper.createManga(manga, client, lang.substringBefore("-")) } // Chapter list section @@ -221,29 +223,28 @@ abstract class MangaDex(override val lang: String, val dexLang: String) : return emptyList() } try { - val chapterListResponse = JsonParser.parseString(response.body!!.string()).obj + val chapterListResponse = helper.json.decodeFromString(response.body!!.string()) - val chapterListResults = - chapterListResponse["results"].array.map { it.obj }.toMutableList() + val chapterListResults = chapterListResponse.results.toMutableList() val mangaId = response.request.url.toString().substringBefore("/feed") .substringAfter("${MDConstants.apiMangaUrl}/") - val limit = chapterListResponse["limit"].int + val limit = chapterListResponse.limit - var offset = chapterListResponse["offset"].int + var offset = chapterListResponse.offset - var hasMoreResults = (limit + offset) < chapterListResponse["total"].int + var hasMoreResults = (limit + offset) < chapterListResponse.total // max results that can be returned is 500 so need to make more api calls if limit+offset > total chapters while (hasMoreResults) { offset += limit val newResponse = client.newCall(actualChapterListRequest(mangaId, offset)).execute() - val newChapterListJson = JsonParser.parseString(newResponse.body!!.string()).obj - chapterListResults.addAll(newChapterListJson["results"].array.map { it.obj }) - hasMoreResults = (limit + offset) < newChapterListJson["total"].int + val newChapterList = helper.json.decodeFromString(newResponse.body!!.string()) + chapterListResults.addAll(newChapterList.results) + hasMoreResults = (limit + offset) < newChapterList.total } val groupMap = helper.createGroupMap(chapterListResults.toList(), client) @@ -272,14 +273,14 @@ abstract class MangaDex(override val lang: String, val dexLang: String) : if (response.code == 204) { return emptyList() } - val chapterJson = JsonParser.parseString(response.body!!.string()).obj["data"] + val chapterDto = helper.json.decodeFromString(response.body!!.string()).data val usingStandardHTTPS = preferences.getBoolean(MDConstants.getStandardHttpsPreferenceKey(dexLang), false) val atHomeRequestUrl = if (usingStandardHTTPS) { - "${MDConstants.apiUrl}/at-home/server/${chapterJson["id"].string}?forcePort443=true" + "${MDConstants.apiUrl}/at-home/server/${chapterDto.id}?forcePort443=true" } else { - "${MDConstants.apiUrl}/at-home/server/${chapterJson["id"].string}" + "${MDConstants.apiUrl}/at-home/server/${chapterDto.id}" } val host = @@ -290,11 +291,12 @@ abstract class MangaDex(override val lang: String, val dexLang: String) : // have to add the time, and url to the page because pages timeout within 30mins now val now = Date().time - val hash = chapterJson["attributes"]["hash"].string + + val hash = chapterDto.attributes.hash val pageSuffix = if (usingDataSaver) { - chapterJson["attributes"]["dataSaver"].array.map { "/data-saver/$hash/${it.string}" } + chapterDto.attributes.dataSaver.map { "/data-saver/$hash/$it" } } else { - chapterJson["attributes"]["data"].array.map { "/data/$hash/${it.string}" } + chapterDto.attributes.data.map { "/data/$hash/$it" } } return pageSuffix.mapIndexed { index, imgUrl -> diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFilters.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFilters.kt index d10188225..918d21510 100644 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFilters.kt +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFilters.kt @@ -173,12 +173,12 @@ class MangaDexFilters { Filter.Select("Excluded tags mode", arrayOf("And", "Or"), 1) val sortableList = listOf( - Pair("Default (Asc/Desc doesn't matter)", ""), + Pair("Number of follows", ""), Pair("Created at", "createdAt"), Pair("Updated at", "updatedAt"), ) - class SortFilter(sortables: Array) : Filter.Sort("Sort", sortables, Selection(0, false)) + class SortFilter(sortables: Array) : Filter.Sort("Sort", sortables, Selection(1, false)) internal fun addFiltersToUrl(url: HttpUrl.Builder, filters: FilterList): String { url.apply { diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexHelper.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexHelper.kt index fe3f2b3d9..9cdf19ff8 100644 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexHelper.kt +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexHelper.kt @@ -1,17 +1,19 @@ package eu.kanade.tachiyomi.extension.all.mangadex import android.util.Log -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.nullString -import com.github.salomonbrys.kotson.obj -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonElement -import com.google.gson.JsonParser +import eu.kanade.tachiyomi.extension.all.mangadex.dto.AtHomeDto +import eu.kanade.tachiyomi.extension.all.mangadex.dto.AuthorListDto +import eu.kanade.tachiyomi.extension.all.mangadex.dto.ChapterDto +import eu.kanade.tachiyomi.extension.all.mangadex.dto.CoverDto +import eu.kanade.tachiyomi.extension.all.mangadex.dto.CoverListDto +import eu.kanade.tachiyomi.extension.all.mangadex.dto.GroupListDto +import eu.kanade.tachiyomi.extension.all.mangadex.dto.MangaDto 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 okhttp3.CacheControl import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrl @@ -25,6 +27,14 @@ class MangaDexHelper() { val mdFilters = MangaDexFilters() + val json = Json { + isLenient = true + ignoreUnknownKeys = true + allowSpecialFloatingPointValues = true + useArrayPolymorphism = true + prettyPrint = true + } + /** * Gets the UUID from the url */ @@ -85,6 +95,7 @@ class MangaDexHelper() { // Check the token map to see if the md@home host is still valid fun getValidImageUrlForPage(page: Page, headers: Headers, client: OkHttpClient): Request { val data = page.url.split(",") + val mdAtHomeServerUrl = when (Date().time - data[2].toLong() > MDConstants.mdAtHomeTokenLifespan) { false -> data[0] @@ -96,7 +107,6 @@ class MangaDexHelper() { ?: 0 ) > MDConstants.mdAtHomeTokenLifespan ) { - tokenTracker[tokenRequestUrl] = Date().time CacheControl.FORCE_NETWORK } else { CacheControl.FORCE_CACHE @@ -116,37 +126,35 @@ class MangaDexHelper() { headers: Headers, cacheControl: CacheControl ): String { + if (cacheControl == CacheControl.FORCE_NETWORK) { + tokenTracker[tokenRequestUrl] = Date().time + } val response = client.newCall(GET(tokenRequestUrl, headers, cacheControl)).execute() - return JsonParser.parseString(response.body!!.string()).obj["baseUrl"].string + return json.decodeFromString(response.body!!.string()).baseUrl } /** * create an SManga from json element only basic elements */ - fun createBasicManga(mangaJson: JsonElement, client: OkHttpClient): SManga { - val data = mangaJson["data"].obj - val dexId = data["id"].string - val attr = data["attributes"].obj - + fun createBasicManga(mangaDto: MangaDto): SManga { return SManga.create().apply { - url = "/manga/$dexId" - title = cleanString(attr["title"]["en"].string) + url = "/manga/${mangaDto.data.id}" + title = cleanString(mangaDto.data.attributes.title["en"] ?: "") } } /** * Create an SManga from json element with all details */ - fun createManga(mangaJson: JsonElement, client: OkHttpClient): SManga { + fun createManga(mangaDto: MangaDto, client: OkHttpClient, lang: String): SManga { try { - val data = mangaJson["data"].obj - val dexId = data["id"].string - val attr = data["attributes"].obj + val data = mangaDto.data + val attr = data.attributes // things that will go with the genre tags but aren't actually genre - val tempContentRating = attr["contentRating"].nullString + val tempContentRating = attr.contentRating val contentRating = if (tempContentRating == null || tempContentRating.equals("safe", true)) { null @@ -155,58 +163,59 @@ class MangaDexHelper() { } val nonGenres = listOf( - (attr["publicationDemographic"]?.nullString ?: "").capitalize(Locale.US), + (attr.publicationDemographic ?: "").capitalize(Locale.US), contentRating, - Locale(attr["originalLanguage"].nullString ?: "").displayLanguage + Locale(attr.originalLanguage ?: "").displayLanguage ) // get authors ignore if they error, artists are labelled as authors currently - val authorIds = mangaJson["relationships"].array.filter { relationship -> - relationship["type"].string.equals("author", true) - }.map { relationship -> relationship["id"].string } + val authorIds = mangaDto.relationships.filter { relationship -> + relationship.type.equals("author", true) + }.map { relationship -> relationship.id } .distinct() - val artistIds = mangaJson["relationships"].array.filter { relationship -> - relationship["type"].string.equals("artist", true) - }.map { relationship -> relationship["id"].string } + + val artistIds = mangaDto.relationships.filter { relationship -> + relationship.type.equals("artist", true) + }.map { relationship -> relationship.id } .distinct() val authorMap = runCatching { val ids = listOf(authorIds, artistIds).flatten().distinct() .joinToString("&ids[]=", "?ids[]=") val response = client.newCall(GET("${MDConstants.apiUrl}/author$ids")).execute() - val json = JsonParser.parseString(response.body!!.string()) - json.obj["results"].array.map { result -> - result["data"]["id"].string to - cleanString(result["data"]["attributes"]["name"].string) + val authorListDto = json.decodeFromString(response.body!!.string()) + authorListDto.results.map { result -> + result.data.id to cleanString(result.data.attributes.name) }.toMap() }.getOrNull() ?: emptyMap() - val coverId = mangaJson["relationships"].array.filter { relationship -> - relationship["type"].string.equals("cover_art", true) - }.map { relationship -> relationship["id"].string }.firstOrNull()!! + val coverId = mangaDto.relationships.filter { relationship -> + relationship.type.equals("cover_art", true) + }.map { relationship -> relationship.id }.firstOrNull()!! // get tag list val tags = mdFilters.getTags() // map ids to tag names val genreList = ( - attr["tags"].array - .map { it["id"].string } + attr.tags + .map { it.id } .map { dexId -> tags.firstOrNull { it.id == dexId } - }.map { it?.name } + + } + .map { it?.name } + nonGenres ) .filter { it.isNullOrBlank().not() } return SManga.create().apply { - url = "/manga/$dexId" - title = cleanString(attr["title"]["en"].string) - description = cleanString(attr["description"]["en"].string) + url = "/manga/${data.id}" + title = cleanString(attr.title["en"] ?: "") + description = cleanString(attr.description[lang] ?: attr.description["en"] ?: "") author = authorIds.mapNotNull { authorMap[it] }.joinToString(", ") artist = artistIds.mapNotNull { authorMap[it] }.joinToString(", ") - status = getPublicationStatus(attr["publicationDemographic"].nullString) - thumbnail_url = getCoverUrl(dexId, coverId, client) + status = getPublicationStatus(attr.status) + thumbnail_url = getCoverUrl(data.id, coverId, client) genre = genreList.joinToString(", ") } } catch (e: Exception) { @@ -220,14 +229,14 @@ class MangaDexHelper() { * batch ids */ fun createGroupMap( - chapterListResults: List, + chapterListDto: List, client: OkHttpClient ): Map { val groupIds = - chapterListResults.map { it["relationships"].array } + chapterListDto.map { chapterDto -> chapterDto.relationships } .flatten() - .filter { it["type"].string == "scanlation_group" } - .map { it["id"].string }.distinct() + .filter { relationshipDto -> relationshipDto.type.equals("scanlation_group", true) } + .map { relationshipDto -> relationshipDto.id }.distinct() // ignore errors if request fails, there is no batch group search yet.. return runCatching { @@ -236,12 +245,10 @@ class MangaDexHelper() { val groupResponse = client.newCall(GET("${MDConstants.apiUrl}/group$ids")).execute() // map results to pair id and name - JsonParser.parseString(groupResponse.body!!.string()) - .obj["results"].array.map { result -> - val id = result["data"]["id"].string - val name = result["data"]["attributes"]["name"].string - Pair(id, cleanString(name)) - } + json.decodeFromString(groupResponse.body!!.string()) + .results.map { result -> + result.data.id to result.data.attributes.name + } }.flatten().toMap() }.getOrNull() ?: emptyMap() } @@ -249,31 +256,38 @@ class MangaDexHelper() { /** * create the SChapter from json */ - fun createChapter(chapterJsonResponse: JsonElement, groupMap: Map): SChapter { + fun createChapter(chapterDto: ChapterDto, groupMap: Map): SChapter { try { - val data = chapterJsonResponse["data"].obj + val data = chapterDto.data + val attr = data.attributes + val scanlatorGroupIds = - chapterJsonResponse["relationships"].array.filter { it["type"].string == "scanlation_group" } - .map { groupMap[it["id"].string] } + chapterDto.relationships + .filter { relationshipDto -> + relationshipDto.type.equals( + "scanlation_group", + true + ) + } + .map { relationshipDto -> groupMap[relationshipDto.id] } .joinToString(" & ") - val attr = data["attributes"] val chapterName = mutableListOf() // Build chapter name - attr["volume"].nullString?.let { + attr.volume?.let { if (it.isNotEmpty()) { chapterName.add("Vol.$it") } } - attr["chapter"].nullString?.let { + attr.chapter?.let { if (it.isNotEmpty()) { chapterName.add("Ch.$it") } } - attr["title"].nullString?.let { + attr.title?.let { if (it.isNotEmpty()) { if (chapterName.isNotEmpty()) { chapterName.add("-") @@ -289,9 +303,9 @@ class MangaDexHelper() { // In future calculate [END] if non mvp api doesnt provide it return SChapter.create().apply { - url = "/chapter/${data["id"].string}" + url = "/chapter/${data.id}" name = cleanString(chapterName.joinToString(" ")) - date_upload = parseDate(attr["publishAt"].string) + date_upload = parseDate(attr.publishAt) scanlator = scanlatorGroupIds } } catch (e: Exception) { @@ -304,9 +318,8 @@ class MangaDexHelper() { val response = client.newCall(GET("${MDConstants.apiCoverUrl}/$coverId")) .execute() - val coverJson = JsonParser.parseString(response.body!!.string()).obj - val fileName = - coverJson.obj["data"].obj["attributes"].obj["fileName"]?.nullString!! + val coverDto = json.decodeFromString(response.body!!.string()) + val fileName = coverDto.data.attributes.fileName return "${MDConstants.cdnUrl}/covers/$dexId/$fileName" } @@ -320,13 +333,14 @@ class MangaDexHelper() { }.build().toString() val response = client.newCall(GET(url)).execute() - val coverJson = JsonParser.parseString(response.body!!.string()).obj + val coverListDto = json.decodeFromString(response.body!!.string()) - return coverJson.obj["results"].array.map { coverResult -> - val fileName = coverResult.obj["data"].obj["attributes"].obj["fileName"].string - val mangaId = coverResult.obj["relationships"].array.first { it["type"].string.equals("manga", true) }["id"].string - val url = "${MDConstants.cdnUrl}/covers/$mangaId/$fileName" - Pair(mangaId, url) + return coverListDto.results.map { coverDto -> + val fileName = coverDto.data.attributes.fileName + val mangaId = coverDto.relationships + .first { relationshipDto -> relationshipDto.type.equals("manga", true) } + .id + mangaId to "${MDConstants.cdnUrl}/covers/$mangaId/$fileName" }.toMap() } } diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/dto/AtHomeDto.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/dto/AtHomeDto.kt new file mode 100644 index 000000000..49f141218 --- /dev/null +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/dto/AtHomeDto.kt @@ -0,0 +1,17 @@ +package eu.kanade.tachiyomi.extension.all.mangadex.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class AtHomeDto( + val baseUrl: String +) + +@Serializable +data class ImageReportDto( + val url: String, + val success: Boolean, + val bytes: Int?, + val cached: Boolean, + val duration: Long, +) diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/dto/ChapterDto.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/dto/ChapterDto.kt new file mode 100644 index 000000000..974e0f06b --- /dev/null +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/dto/ChapterDto.kt @@ -0,0 +1,61 @@ +package eu.kanade.tachiyomi.extension.all.mangadex.dto +import kotlinx.serialization.Serializable + +@Serializable +data class ChapterListDto( + val limit: Int, + val offset: Int, + val total: Int, + val results: List +) + +@Serializable +data class ChapterDto( + val result: String, + val data: ChapterDataDto, + val relationships: List +) + +@Serializable +data class ChapterDataDto( + val id: String, + val type: String, + val attributes: ChapterAttributesDto, +) + +@Serializable +data class ChapterAttributesDto( + val title: String?, + val volume: String?, + val chapter: String?, + val translatedLanguage: String, + val publishAt: String, + val data: List, + val dataSaver: List, + val hash: String, +) + +@Serializable +data class GroupListDto( + val limit: Int, + val offset: Int, + val total: Int, + val results: List +) + +@Serializable +data class GroupDto( + val result: String, + val data: GroupDataDto, +) + +@Serializable +data class GroupDataDto( + val id: String, + val attributes: GroupAttributesDto, +) + +@Serializable +data class GroupAttributesDto( + val name: String, +) diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/dto/MangaDto.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/dto/MangaDto.kt new file mode 100644 index 000000000..c5532a8e6 --- /dev/null +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/dto/MangaDto.kt @@ -0,0 +1,94 @@ +package eu.kanade.tachiyomi.extension.all.mangadex.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class MangaListDto( + val limit: Int, + val offset: Int, + val total: Int, + val results: List, +) + +@Serializable +data class MangaDto( + val result: String, + val data: MangaDataDto, + val relationships: List, +) + +@Serializable +data class RelationshipDto( + val id: String, + val type: String, +) + +@Serializable +data class MangaDataDto( + val id: String, + val type: String, + val attributes: MangaAttributesDto +) + +@Serializable +data class MangaAttributesDto( + val title: Map, + val altTitles: List>, + val description: Map, + val links: Map?, + val originalLanguage: String, + val lastVolume: String?, + val lastChapter: String?, + val contentRating: String?, + val publicationDemographic: String?, + val status: String?, + val year: Int?, + val tags: List, +) + +@Serializable +data class TagDto( + val id: String +) + +@Serializable +data class AuthorListDto( + val results: List, +) + +@Serializable +data class AuthorDto( + val result: String, + val data: AuthorDataDto, +) + +@Serializable +data class AuthorDataDto( + val id: String, + val attributes: AuthorAttributesDto, +) + +@Serializable +data class AuthorAttributesDto( + val name: String, +) + +@Serializable +data class CoverListDto( + val results: List, +) +@Serializable +data class CoverDto( + val data: CoverDataDto, + val relationships: List +) + +@Serializable +data class CoverDataDto( + val attributes: CoverAttributesDto, +) + +@Serializable +data class CoverAttributesDto( + val fileName: String, +)