diff --git a/src/all/mangadex/build.gradle b/src/all/mangadex/build.gradle index c31e0ca25..6be736073 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 = 121 + extVersionCode = 122 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 d3cd39774..57c929e4f 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,14 +10,18 @@ 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 latestChapterLimit = 100 + + const val manga = "manga" 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 cdnUrl = "https://uploads.mangadex.org" const val apiUrl = "https://api.mangadex.org" const val apiMangaUrl = "$apiUrl/manga" + const val apiChapterUrl = "$apiUrl/chapter" 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 b394390d4..d1d94db57 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 @@ -38,8 +38,7 @@ abstract class MangaDex(override val lang: String, val dexLang: String) : override val name = "MangaDex" 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 + override val supportsLatest = true private val preferences: SharedPreferences by lazy { Injekt.get().getSharedPreferences("source_$id", 0x0000) @@ -119,18 +118,57 @@ abstract class MangaDex(override val lang: String, val dexLang: String) : } // LATEST section API can't sort by date yet so not implemented - override fun latestUpdatesParse(response: Response): MangasPage = throw Exception("Not used") + override fun latestUpdatesParse(response: Response): MangasPage { + val chapterListDto = helper.json.decodeFromString(response.body!!.string()) + val hasMoreResults = chapterListDto.limit + chapterListDto.offset < chapterListDto.total - override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used") + val mangaIds = chapterListDto.results.map { it.relationships }.flatten() + .filter { it.type == MDConstants.manga }.map { it.id }.distinct() + + val mangaUrl = MDConstants.apiMangaUrl.toHttpUrlOrNull()!!.newBuilder().apply { + addQueryParameter("includes[]", MDConstants.coverArt) + mangaIds.forEach { id -> + addQueryParameter("ids[]", id) + } + }.build().toString() + + val mangaResponse = client.newCall(GET(mangaUrl, headers, CacheControl.FORCE_NETWORK)).execute() + val mangaListDto = helper.json.decodeFromString(mangaResponse.body!!.string()) + + 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) + } + override fun latestUpdatesRequest(page: Int): Request { + val url = MDConstants.apiChapterUrl.toHttpUrlOrNull()!!.newBuilder() + .addQueryParameter("offset", helper.getLatestChapterOffset(page)) + .addQueryParameter("limit", MDConstants.latestChapterLimit.toString()) + .addQueryParameter("translatedLanguage[]", dexLang) + .addQueryParameter("order[publishAt]", "desc") + .build().toString() + return GET(url, headers, CacheControl.FORCE_NETWORK) + } // SEARCH section override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { 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) + val url = MDConstants.apiMangaUrl.toHttpUrlOrNull()!!.newBuilder().apply { + addQueryParameter("ids[]", query.removePrefix(MDConstants.prefixIdSearch)) + addQueryParameter("includes[]", MDConstants.coverArt) + + addQueryParameter("contentRating[]", "safe") + addQueryParameter("contentRating[]", "suggestive") + addQueryParameter("contentRating[]", "erotica") + addQueryParameter("contentRating[]", "pornographic") + }.build().toString() + + return GET(url, headers, CacheControl.FORCE_NETWORK) } val tempUrl = MDConstants.apiMangaUrl.toHttpUrl().newBuilder() 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 918d21510..a26972a1e 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 @@ -9,34 +9,10 @@ import java.util.Locale class MangaDexFilters { internal fun getMDFilterList(preferences: SharedPreferences, dexLang: String): FilterList { - val contentRatings = listOf( - ContentRating("Safe").apply { - state = - preferences.getBoolean(MDConstants.getContentRatingSafePrefKey(dexLang), true) - }, - ContentRating("Suggestive").apply { - state = preferences.getBoolean( - MDConstants.getContentRatingSuggestivePrefKey(dexLang), - true - ) - }, - ContentRating("Erotica").apply { - state = preferences.getBoolean( - MDConstants.getContentRatingEroticaPrefKey(dexLang), - false - ) - }, - ContentRating("Pornographic").apply { - state = preferences.getBoolean( - MDConstants.getContentRatingPornographicPrefKey(dexLang), - false - ) - }, - ) return FilterList( OriginalLanguageList(getOriginalLanguage()), - ContentRatingList(contentRatings), + ContentRatingList(getContentRating(preferences, dexLang)), DemographicList(getDemographics()), StatusList(getStatus()), SortFilter(sortableList.map { it.first }.toTypedArray()), @@ -46,6 +22,31 @@ class MangaDexFilters { ) } + private fun getContentRating(preferences: SharedPreferences, dexLang: String) = listOf( + ContentRating("Safe").apply { + state = + preferences.getBoolean(MDConstants.getContentRatingSafePrefKey(dexLang), true) + }, + ContentRating("Suggestive").apply { + state = preferences.getBoolean( + MDConstants.getContentRatingSuggestivePrefKey(dexLang), + true + ) + }, + ContentRating("Erotica").apply { + state = preferences.getBoolean( + MDConstants.getContentRatingEroticaPrefKey(dexLang), + false + ) + }, + ContentRating("Pornographic").apply { + state = preferences.getBoolean( + MDConstants.getContentRatingPornographicPrefKey(dexLang), + false + ) + }, + ) + private class Demographic(name: String) : Filter.CheckBox(name) private class DemographicList(demographics: List) : Filter.Group("Publication Demographic", demographics) @@ -174,8 +175,8 @@ class MangaDexFilters { val sortableList = listOf( Pair("Number of follows", ""), - Pair("Created at", "createdAt"), - Pair("Updated at", "updatedAt"), + Pair("Manga created at", "createdAt"), + Pair("Manga info updated at", "updatedAt"), ) class SortFilter(sortables: Array) : Filter.Sort("Sort", sortables, Selection(1, false)) 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 39b741cf0..3df9fd2ab 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 @@ -5,6 +5,7 @@ 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.MangaAttributesDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.MangaDto +import eu.kanade.tachiyomi.extension.all.mangadex.dto.asMdMap import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter @@ -52,6 +53,11 @@ class MangaDexHelper() { */ fun getMangaListOffset(page: Int): String = (MDConstants.mangaLimit * (page - 1)).toString() + /** + * Get the latest chapter offset pages are 1 based, so subtract 1 + */ + fun getLatestChapterOffset(page: Int): String = (MDConstants.latestChapterLimit * (page - 1)).toString() + /** * Remove bbcode tags as well as parses any html characters in description or * chapter name to actual characters for example ♥ will show ♥ @@ -148,7 +154,7 @@ class MangaDexHelper() { fun createBasicManga(mangaDto: MangaDto, coverFileName: String?): SManga { return SManga.create().apply { url = "/manga/${mangaDto.data.id}" - title = cleanString(mangaDto.data.attributes.title["en"] ?: "") + title = cleanString(mangaDto.data.attributes.title.asMdMap()["en"] ?: "") coverFileName?.let { thumbnail_url = "${MDConstants.cdnUrl}/covers/${mangaDto.data.id}/$coverFileName" } @@ -206,8 +212,9 @@ class MangaDexHelper() { ) .filter { it.isNullOrBlank().not() } + val desc = attr.description.asMdMap() return createBasicManga(mangaDto, coverFileName).apply { - description = cleanString(attr.description[lang] ?: attr.description["en"] ?: "") + description = cleanString(desc[lang] ?: desc["en"] ?: "") author = authors.joinToString(", ") artist = artists.joinToString(", ") status = getPublicationStatus(attr, chapters) 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 7db38a79a..f89b7a34f 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 @@ -29,7 +29,6 @@ data class ChapterAttributesDto( val title: String?, val volume: String?, val chapter: String?, - val translatedLanguage: String, val publishAt: String, val data: List, val dataSaver: List, 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 611f804a8..e67b41ab2 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 @@ -1,6 +1,8 @@ package eu.kanade.tachiyomi.extension.all.mangadex.dto import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject @Serializable data class MangaListDto( @@ -39,17 +41,14 @@ data class MangaDataDto( @Serializable data class MangaAttributesDto( - val title: Map, - val altTitles: List>, - val description: Map, - val links: Map?, + val title: JsonElement, + val description: JsonElement, val originalLanguage: String, val lastVolume: String?, val lastChapter: String?, val contentRating: String?, val publicationDemographic: String?, val status: String?, - val year: Int?, val tags: List, ) @@ -57,3 +56,9 @@ data class MangaAttributesDto( data class TagDto( val id: String, ) + +fun JsonElement.asMdMap(): Map { + return runCatching { + (this as JsonObject).map { it.key to it.value.toString() }.toMap() + }.getOrElse { emptyMap() } +}