MangaDex fix deeplinking, parsing, clean up dto's, add latest chapter (#8035)

* MangaDex fix deeplinking, parsing, clean up dto's, add latest chapter

* change wording for sort
This commit is contained in:
Carlos 2021-07-09 16:39:34 -04:00 committed by GitHub
parent 5dc5159bdf
commit b3aaf136c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 99 additions and 45 deletions

View File

@ -6,7 +6,7 @@ ext {
extName = 'MangaDex' extName = 'MangaDex'
pkgNameSuffix = 'all.mangadex' pkgNameSuffix = 'all.mangadex'
extClass = '.MangaDexFactory' extClass = '.MangaDexFactory'
extVersionCode = 121 extVersionCode = 122
libVersion = '1.2' libVersion = '1.2'
containsNsfw = true containsNsfw = true
} }

View File

@ -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}") 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 mangaLimit = 20
const val latestChapterLimit = 100
const val manga = "manga"
const val coverArt = "cover_art" const val coverArt = "cover_art"
const val scanlator = "scanlation_group" const val scanlator = "scanlation_group"
const val author = "author" const val author = "author"
const val artist = "artist" 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 apiUrl = "https://api.mangadex.org"
const val apiMangaUrl = "$apiUrl/manga" const val apiMangaUrl = "$apiUrl/manga"
const val apiChapterUrl = "$apiUrl/chapter"
const val atHomePostUrl = "https://api.mangadex.network/report" const val atHomePostUrl = "https://api.mangadex.network/report"
val whitespaceRegex = "\\s".toRegex() val whitespaceRegex = "\\s".toRegex()

View File

@ -38,8 +38,7 @@ abstract class MangaDex(override val lang: String, val dexLang: String) :
override val name = "MangaDex" override val name = "MangaDex"
override val baseUrl = "https://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 = true
override val supportsLatest = false
private val preferences: SharedPreferences by lazy { private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().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 // 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<ChapterListDto>(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<MangaListDto>(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 // SEARCH section
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (query.startsWith(MDConstants.prefixIdSearch)) { if (query.startsWith(MDConstants.prefixIdSearch)) {
val url = MDConstants.apiMangaUrl.toHttpUrlOrNull()!!.newBuilder() val url = MDConstants.apiMangaUrl.toHttpUrlOrNull()!!.newBuilder().apply {
.addQueryParameter("ids[]", query.removePrefix(MDConstants.prefixIdSearch)) addQueryParameter("ids[]", query.removePrefix(MDConstants.prefixIdSearch))
.addQueryParameter("includes[]", MDConstants.coverArt) addQueryParameter("includes[]", MDConstants.coverArt)
return GET(url.toString(), headers, CacheControl.FORCE_NETWORK)
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() val tempUrl = MDConstants.apiMangaUrl.toHttpUrl().newBuilder()

View File

@ -9,34 +9,10 @@ import java.util.Locale
class MangaDexFilters { class MangaDexFilters {
internal fun getMDFilterList(preferences: SharedPreferences, dexLang: String): FilterList { 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( return FilterList(
OriginalLanguageList(getOriginalLanguage()), OriginalLanguageList(getOriginalLanguage()),
ContentRatingList(contentRatings), ContentRatingList(getContentRating(preferences, dexLang)),
DemographicList(getDemographics()), DemographicList(getDemographics()),
StatusList(getStatus()), StatusList(getStatus()),
SortFilter(sortableList.map { it.first }.toTypedArray()), 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 Demographic(name: String) : Filter.CheckBox(name)
private class DemographicList(demographics: List<Demographic>) : private class DemographicList(demographics: List<Demographic>) :
Filter.Group<Demographic>("Publication Demographic", demographics) Filter.Group<Demographic>("Publication Demographic", demographics)
@ -174,8 +175,8 @@ class MangaDexFilters {
val sortableList = listOf( val sortableList = listOf(
Pair("Number of follows", ""), Pair("Number of follows", ""),
Pair("Created at", "createdAt"), Pair("Manga created at", "createdAt"),
Pair("Updated at", "updatedAt"), Pair("Manga info updated at", "updatedAt"),
) )
class SortFilter(sortables: Array<String>) : Filter.Sort("Sort", sortables, Selection(1, false)) class SortFilter(sortables: Array<String>) : Filter.Sort("Sort", sortables, Selection(1, false))

View File

@ -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.ChapterDto
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.MangaDto 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.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
@ -52,6 +53,11 @@ class MangaDexHelper() {
*/ */
fun getMangaListOffset(page: Int): String = (MDConstants.mangaLimit * (page - 1)).toString() 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 * Remove bbcode tags as well as parses any html characters in description or
* chapter name to actual characters for example &hearts; will show * chapter name to actual characters for example &hearts; will show
@ -148,7 +154,7 @@ class MangaDexHelper() {
fun createBasicManga(mangaDto: MangaDto, coverFileName: String?): SManga { fun createBasicManga(mangaDto: MangaDto, coverFileName: String?): SManga {
return SManga.create().apply { return SManga.create().apply {
url = "/manga/${mangaDto.data.id}" url = "/manga/${mangaDto.data.id}"
title = cleanString(mangaDto.data.attributes.title["en"] ?: "") title = cleanString(mangaDto.data.attributes.title.asMdMap()["en"] ?: "")
coverFileName?.let { coverFileName?.let {
thumbnail_url = "${MDConstants.cdnUrl}/covers/${mangaDto.data.id}/$coverFileName" thumbnail_url = "${MDConstants.cdnUrl}/covers/${mangaDto.data.id}/$coverFileName"
} }
@ -206,8 +212,9 @@ class MangaDexHelper() {
) )
.filter { it.isNullOrBlank().not() } .filter { it.isNullOrBlank().not() }
val desc = attr.description.asMdMap()
return createBasicManga(mangaDto, coverFileName).apply { return createBasicManga(mangaDto, coverFileName).apply {
description = cleanString(attr.description[lang] ?: attr.description["en"] ?: "") description = cleanString(desc[lang] ?: desc["en"] ?: "")
author = authors.joinToString(", ") author = authors.joinToString(", ")
artist = artists.joinToString(", ") artist = artists.joinToString(", ")
status = getPublicationStatus(attr, chapters) status = getPublicationStatus(attr, chapters)

View File

@ -29,7 +29,6 @@ data class ChapterAttributesDto(
val title: String?, val title: String?,
val volume: String?, val volume: String?,
val chapter: String?, val chapter: String?,
val translatedLanguage: String,
val publishAt: String, val publishAt: String,
val data: List<String>, val data: List<String>,
val dataSaver: List<String>, val dataSaver: List<String>,

View File

@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.extension.all.mangadex.dto package eu.kanade.tachiyomi.extension.all.mangadex.dto
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
@Serializable @Serializable
data class MangaListDto( data class MangaListDto(
@ -39,17 +41,14 @@ data class MangaDataDto(
@Serializable @Serializable
data class MangaAttributesDto( data class MangaAttributesDto(
val title: Map<String, String>, val title: JsonElement,
val altTitles: List<Map<String, String>>, val description: JsonElement,
val description: Map<String, String>,
val links: Map<String, String>?,
val originalLanguage: String, val originalLanguage: String,
val lastVolume: String?, val lastVolume: String?,
val lastChapter: String?, val lastChapter: String?,
val contentRating: String?, val contentRating: String?,
val publicationDemographic: String?, val publicationDemographic: String?,
val status: String?, val status: String?,
val year: Int?,
val tags: List<TagDto>, val tags: List<TagDto>,
) )
@ -57,3 +56,9 @@ data class MangaAttributesDto(
data class TagDto( data class TagDto(
val id: String, val id: String,
) )
fun JsonElement.asMdMap(): Map<String, String> {
return runCatching {
(this as JsonObject).map { it.key to it.value.toString() }.toMap()
}.getOrElse { emptyMap() }
}