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:
parent
5dc5159bdf
commit
b3aaf136c2
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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 ♥ will show ♥
|
* chapter name to actual characters for example ♥ 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)
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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() }
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue