diff --git a/src/all/mangadex/build.gradle b/src/all/mangadex/build.gradle index 942bcaad7..eba2427e9 100644 --- a/src/all/mangadex/build.gradle +++ b/src/all/mangadex/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'MangaDex' pkgNameSuffix = 'all.mangadex' extClass = '.MangaDexFactory' - extVersionCode = 118 + extVersionCode = 119 libVersion = '1.2' containsNsfw = true } diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MDConstants.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MDConstants.kt index c13cc6736..d3cd39774 100644 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MDConstants.kt +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MDConstants.kt @@ -10,11 +10,14 @@ object MDConstants { Regex("[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}") const val mangaLimit = 20 + const val coverArt = "cover_art" + const val scanlator = "scanlation_group" + const val author = "author" + const val artist = "artist" const val cdnUrl = "https://uploads.mangadex.org" const val apiUrl = "https://api.mangadex.org" const val apiMangaUrl = "$apiUrl/manga" - const val apiCoverUrl = "$apiUrl/cover" const val atHomePostUrl = "https://api.mangadex.network/report" val whitespaceRegex = "\\s".toRegex() 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 7937585f7..5ba7af6c7 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 @@ -62,6 +62,7 @@ abstract class MangaDex(override val lang: String, val dexLang: String) : addQueryParameter("order[updatedAt]", "desc") addQueryParameter("limit", MDConstants.mangaLimit.toString()) addQueryParameter("offset", helper.getMangaListOffset(page)) + addQueryParameter("includes[]", MDConstants.coverArt) if (preferences.getBoolean(MDConstants.getContentRatingSafePrefKey(dexLang), false)) { addQueryParameter("contentRating[]", "safe") } @@ -105,26 +106,11 @@ abstract class MangaDex(override val lang: String, val dexLang: String) : val mangaListDto = helper.json.decodeFromString(response.body!!.string()) val hasMoreResults = mangaListDto.limit + mangaListDto.offset < mangaListDto.total - 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 = mangaListDto.results.map { - helper.createBasicManga(it).apply { - thumbnail_url = results[url.substringAfter("/manga/")] - } + val mangaList = mangaListDto.results.map { mangaDto -> + val fileName = mangaDto.relationships.firstOrNull { relationshipDto -> + relationshipDto.type.equals(MDConstants.coverArt, true) + }?.attributes?.fileName + helper.createBasicManga(mangaDto, fileName) } return MangasPage(mangaList, hasMoreResults) @@ -141,6 +127,7 @@ abstract class MangaDex(override val lang: String, val dexLang: String) : if (query.startsWith(MDConstants.prefixIdSearch)) { val url = MDConstants.apiMangaUrl.toHttpUrlOrNull()!!.newBuilder() .addQueryParameter("ids[]", query.removePrefix(MDConstants.prefixIdSearch)) + .addQueryParameter("includes[]", MDConstants.coverArt) return GET(url.toString(), headers, CacheControl.FORCE_NETWORK) } @@ -149,6 +136,7 @@ abstract class MangaDex(override val lang: String, val dexLang: String) : tempUrl.apply { addQueryParameter("limit", MDConstants.mangaLimit.toString()) addQueryParameter("offset", (helper.getMangaListOffset(page))) + addQueryParameter("includes[]", MDConstants.coverArt) val actualQuery = query.replace(MDConstants.whitespaceRegex, " ") if (actualQuery.isNotBlank()) { addQueryParameter("title", actualQuery) @@ -174,23 +162,28 @@ abstract class MangaDex(override val lang: String, val dexLang: String) : } override fun mangaDetailsRequest(manga: SManga): Request { - //remove once redirect for /manga is fixed + // remove once redirect for /manga is fixed return GET("${baseUrl}${manga.url.replace("manga", "title")}", headers) } /** * get manga details url throws exception if the url is the old format so people migrate */ - fun apiMangaDetailsRequest(manga: SManga): Request { + private fun apiMangaDetailsRequest(manga: SManga): Request { if (!helper.containsUuid(manga.url.trim())) { throw Exception("Migrate this manga from MangaDex to MangaDex to update it") } - return GET("${MDConstants.apiUrl}${manga.url}", headers, CacheControl.FORCE_NETWORK) + val url = (MDConstants.apiUrl + manga.url).toHttpUrl().newBuilder().apply { + addQueryParameter("includes[]", MDConstants.coverArt) + addQueryParameter("includes[]", MDConstants.author) + addQueryParameter("includes[]", MDConstants.artist) + }.build().toString() + return GET(url, headers, CacheControl.FORCE_NETWORK) } override fun mangaDetailsParse(response: Response): SManga { val manga = helper.json.decodeFromString(response.body!!.string()) - return helper.createManga(manga, client, lang.substringBefore("-")) + return helper.createManga(manga, lang.substringBefore("-")) } // Chapter list section @@ -247,11 +240,9 @@ abstract class MangaDex(override val lang: String, val dexLang: String) : hasMoreResults = (limit + offset) < newChapterList.total } - val groupMap = helper.createGroupMap(chapterListResults.toList(), client) - val now = Date().time - return chapterListResults.map { helper.createChapter(it, groupMap) } + return chapterListResults.map { helper.createChapter(it) } .filter { it.date_upload <= now && "MangaPlus" != it.scanlator } } catch (e: Exception) { Log.e("MangaDex", "error parsing chapter list", e) 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 9cdf19ff8..d969bb651 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 @@ -2,11 +2,7 @@ package eu.kanade.tachiyomi.extension.all.mangadex import android.util.Log 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 @@ -16,7 +12,6 @@ import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import okhttp3.CacheControl import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient import okhttp3.Request import org.jsoup.parser.Parser @@ -44,7 +39,7 @@ class MangaDexHelper() { * get chapters for manga (aka manga/$id/feed endpoint) */ fun getChapterEndpoint(mangaId: String, offset: Int, langCode: String) = - "${MDConstants.apiMangaUrl}/$mangaId/feed?limit=500&offset=$offset&translatedLanguage[]=$langCode&order[volume]=desc&order[chapter]=desc" + "${MDConstants.apiMangaUrl}/$mangaId/feed?includes[]=${MDConstants.scanlator}&limit=500&offset=$offset&translatedLanguage[]=$langCode&order[volume]=desc&order[chapter]=desc" /** * Check if the manga url is a valid uuid @@ -124,7 +119,7 @@ class MangaDexHelper() { tokenRequestUrl: String, client: OkHttpClient, headers: Headers, - cacheControl: CacheControl + cacheControl: CacheControl, ): String { if (cacheControl == CacheControl.FORCE_NETWORK) { tokenTracker[tokenRequestUrl] = Date().time @@ -137,17 +132,20 @@ class MangaDexHelper() { /** * create an SManga from json element only basic elements */ - fun createBasicManga(mangaDto: MangaDto): SManga { + fun createBasicManga(mangaDto: MangaDto, coverFileName: String?): SManga { return SManga.create().apply { url = "/manga/${mangaDto.data.id}" title = cleanString(mangaDto.data.attributes.title["en"] ?: "") + coverFileName?.let { + thumbnail_url = "${MDConstants.cdnUrl}/covers/${mangaDto.data.id}/$coverFileName" + } } } /** * Create an SManga from json element with all details */ - fun createManga(mangaDto: MangaDto, client: OkHttpClient, lang: String): SManga { + fun createManga(mangaDto: MangaDto, lang: String): SManga { try { val data = mangaDto.data val attr = data.attributes @@ -168,30 +166,17 @@ class MangaDexHelper() { Locale(attr.originalLanguage ?: "").displayLanguage ) - // get authors ignore if they error, artists are labelled as authors currently - val authorIds = mangaDto.relationships.filter { relationship -> - relationship.type.equals("author", true) - }.map { relationship -> relationship.id } - .distinct() + val authors = mangaDto.relationships.filter { relationshipDto -> + relationshipDto.type.equals(MDConstants.author, true) + }.mapNotNull { it.attributes!!.name }.distinct() - val artistIds = mangaDto.relationships.filter { relationship -> - relationship.type.equals("artist", true) - }.map { relationship -> relationship.id } - .distinct() + val artists = mangaDto.relationships.filter { relationshipDto -> + relationshipDto.type.equals(MDConstants.artist, true) + }.mapNotNull { it.attributes!!.name }.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 authorListDto = json.decodeFromString(response.body!!.string()) - authorListDto.results.map { result -> - result.data.id to cleanString(result.data.attributes.name) - }.toMap() - }.getOrNull() ?: emptyMap() - - val coverId = mangaDto.relationships.filter { relationship -> - relationship.type.equals("cover_art", true) - }.map { relationship -> relationship.id }.firstOrNull()!! + val coverFileName = mangaDto.relationships.firstOrNull { relationshipDto -> + relationshipDto.type.equals(MDConstants.coverArt, true) + }?.attributes?.fileName // get tag list val tags = mdFilters.getTags() @@ -208,14 +193,11 @@ class MangaDexHelper() { ) .filter { it.isNullOrBlank().not() } - return SManga.create().apply { - url = "/manga/${data.id}" - title = cleanString(attr.title["en"] ?: "") + return createBasicManga(mangaDto, coverFileName).apply { description = cleanString(attr.description[lang] ?: attr.description["en"] ?: "") - author = authorIds.mapNotNull { authorMap[it] }.joinToString(", ") - artist = artistIds.mapNotNull { authorMap[it] }.joinToString(", ") + author = authors.joinToString(", ") + artist = artists.joinToString(", ") status = getPublicationStatus(attr.status) - thumbnail_url = getCoverUrl(data.id, coverId, client) genre = genreList.joinToString(", ") } } catch (e: Exception) { @@ -224,53 +206,21 @@ class MangaDexHelper() { } } - /** - * This makes an api call per a unique group id found in the chapters hopefully Dex will eventually support - * batch ids - */ - fun createGroupMap( - chapterListDto: List, - client: OkHttpClient - ): Map { - val groupIds = - chapterListDto.map { chapterDto -> chapterDto.relationships } - .flatten() - .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 { - groupIds.chunked(100).map { chunkIds -> - val ids = chunkIds.joinToString("&ids[]=", "?ids[]=") - val groupResponse = - client.newCall(GET("${MDConstants.apiUrl}/group$ids")).execute() - // map results to pair id and name - json.decodeFromString(groupResponse.body!!.string()) - .results.map { result -> - result.data.id to result.data.attributes.name - } - }.flatten().toMap() - }.getOrNull() ?: emptyMap() - } - /** * create the SChapter from json */ - fun createChapter(chapterDto: ChapterDto, groupMap: Map): SChapter { + fun createChapter(chapterDto: ChapterDto): SChapter { try { val data = chapterDto.data val attr = data.attributes - val scanlatorGroupIds = - chapterDto.relationships - .filter { relationshipDto -> - relationshipDto.type.equals( - "scanlation_group", - true - ) - } - .map { relationshipDto -> groupMap[relationshipDto.id] } - .joinToString(" & ") + val groups = chapterDto.relationships.filter { relationshipDto -> + relationshipDto.type.equals( + MDConstants.scanlator, + true + ) + }.mapNotNull { it.attributes!!.name } + .joinToString(" & ") val chapterName = mutableListOf() // Build chapter name @@ -306,41 +256,11 @@ class MangaDexHelper() { url = "/chapter/${data.id}" name = cleanString(chapterName.joinToString(" ")) date_upload = parseDate(attr.publishAt) - scanlator = scanlatorGroupIds + scanlator = groups } } catch (e: Exception) { Log.e("MangaDex", "error parsing chapter", e) throw(e) } } - - private fun getCoverUrl(dexId: String, coverId: String, client: OkHttpClient): String { - val response = - client.newCall(GET("${MDConstants.apiCoverUrl}/$coverId")) - .execute() - val coverDto = json.decodeFromString(response.body!!.string()) - val fileName = coverDto.data.attributes.fileName - return "${MDConstants.cdnUrl}/covers/$dexId/$fileName" - } - - fun getBatchCoversUrl(ids: Map, client: OkHttpClient): Map { - - val url = MDConstants.apiCoverUrl.toHttpUrl().newBuilder().apply { - ids.values.forEach { coverArtId -> - addQueryParameter("ids[]", coverArtId) - } - addQueryParameter("limit", ids.size.toString()) - }.build().toString() - - val response = client.newCall(GET(url)).execute() - val coverListDto = json.decodeFromString(response.body!!.string()) - - 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/ChapterDto.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/dto/ChapterDto.kt index 974e0f06b..7db38a79a 100644 --- 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 @@ -1,4 +1,5 @@ package eu.kanade.tachiyomi.extension.all.mangadex.dto + import kotlinx.serialization.Serializable @Serializable @@ -6,14 +7,14 @@ data class ChapterListDto( val limit: Int, val offset: Int, val total: Int, - val results: List + val results: List, ) @Serializable data class ChapterDto( val result: String, val data: ChapterDataDto, - val relationships: List + val relationships: List, ) @Serializable @@ -34,28 +35,3 @@ data class ChapterAttributesDto( 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 index c5532a8e6..611f804a8 100644 --- 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 @@ -21,13 +21,20 @@ data class MangaDto( 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, ) @Serializable data class MangaDataDto( val id: String, val type: String, - val attributes: MangaAttributesDto + val attributes: MangaAttributesDto, ) @Serializable @@ -48,47 +55,5 @@ data class MangaAttributesDto( @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, )