Refactor the MangaDex code a bit (#17267)
* Refactor the MangaDex code a bit. * Bump the extension version.
This commit is contained in:
parent
873f752d61
commit
a59ef3a817
|
@ -6,7 +6,7 @@ ext {
|
||||||
extName = 'MangaDex'
|
extName = 'MangaDex'
|
||||||
pkgNameSuffix = 'all.mangadex'
|
pkgNameSuffix = 'all.mangadex'
|
||||||
extClass = '.MangaDexFactory'
|
extClass = '.MangaDexFactory'
|
||||||
extVersionCode = 183
|
extVersionCode = 184
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension.all.mangadex
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
object MDConstants {
|
object MDConstants {
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ object MDConstants {
|
||||||
const val atHomePostUrl = "https://api.mangadex.network/report"
|
const val atHomePostUrl = "https://api.mangadex.network/report"
|
||||||
val whitespaceRegex = "\\s".toRegex()
|
val whitespaceRegex = "\\s".toRegex()
|
||||||
|
|
||||||
const val mdAtHomeTokenLifespan = 5 * 60 * 1000
|
val mdAtHomeTokenLifespan = 5.minutes.inWholeMilliseconds
|
||||||
|
|
||||||
val dateFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss+SSS", Locale.US)
|
val dateFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss+SSS", Locale.US)
|
||||||
.apply { timeZone = TimeZone.getTimeZone("UTC") }
|
.apply { timeZone = TimeZone.getTimeZone("UTC") }
|
||||||
|
@ -74,6 +75,12 @@ object MDConstants {
|
||||||
const val contentRatingPrefValErotica = "erotica"
|
const val contentRatingPrefValErotica = "erotica"
|
||||||
const val contentRatingPrefValPornographic = "pornographic"
|
const val contentRatingPrefValPornographic = "pornographic"
|
||||||
val contentRatingPrefDefaults = setOf(contentRatingPrefValSafe, contentRatingPrefValSuggestive)
|
val contentRatingPrefDefaults = setOf(contentRatingPrefValSafe, contentRatingPrefValSuggestive)
|
||||||
|
val allContentRatings = setOf(
|
||||||
|
contentRatingPrefValSafe,
|
||||||
|
contentRatingPrefValSuggestive,
|
||||||
|
contentRatingPrefValErotica,
|
||||||
|
contentRatingPrefValPornographic,
|
||||||
|
)
|
||||||
|
|
||||||
fun getContentRatingPrefKey(dexLang: String): String {
|
fun getContentRatingPrefKey(dexLang: String): String {
|
||||||
return "${contentRatingPref}_$dexLang"
|
return "${contentRatingPref}_$dexLang"
|
||||||
|
|
|
@ -34,7 +34,6 @@ import okhttp3.CacheControl
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
@ -42,9 +41,8 @@ import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
abstract class MangaDex(final override val lang: String, private val dexLang: String) :
|
abstract class MangaDex(final override val lang: String, private val dexLang: String = lang) :
|
||||||
ConfigurableSource,
|
ConfigurableSource, HttpSource() {
|
||||||
HttpSource() {
|
|
||||||
|
|
||||||
override val name = MangaDexIntl.MANGADEX_NAME
|
override val name = MangaDexIntl.MANGADEX_NAME
|
||||||
|
|
||||||
|
@ -82,12 +80,9 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
.addQueryParameter("includes[]", MDConstants.coverArt)
|
.addQueryParameter("includes[]", MDConstants.coverArt)
|
||||||
.addQueryParameter("contentRating[]", preferences.contentRating)
|
.addQueryParameter("contentRating[]", preferences.contentRating)
|
||||||
.addQueryParameter("originalLanguage[]", preferences.originalLanguages)
|
.addQueryParameter("originalLanguage[]", preferences.originalLanguages)
|
||||||
|
.build()
|
||||||
|
|
||||||
return GET(
|
return GET(url, headers, CacheControl.FORCE_NETWORK)
|
||||||
url = url.build().toString(),
|
|
||||||
headers = headers,
|
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
|
@ -96,7 +91,6 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
}
|
}
|
||||||
|
|
||||||
val mangaListDto = response.parseAs<MangaListDto>()
|
val mangaListDto = response.parseAs<MangaListDto>()
|
||||||
val hasMoreResults = mangaListDto.limit + mangaListDto.offset < mangaListDto.total
|
|
||||||
|
|
||||||
val coverSuffix = preferences.coverQuality
|
val coverSuffix = preferences.coverQuality
|
||||||
val firstVolumeCovers = fetchFirstVolumeCovers(mangaListDto.data).orEmpty()
|
val firstVolumeCovers = fetchFirstVolumeCovers(mangaListDto.data).orEmpty()
|
||||||
|
@ -109,17 +103,37 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang)
|
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang)
|
||||||
}
|
}
|
||||||
|
|
||||||
return MangasPage(mangaList, hasMoreResults)
|
return MangasPage(mangaList, mangaListDto.hasNextPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Latest manga section
|
// Latest manga section
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
val url = MDConstants.apiChapterUrl.toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("offset", helper.getLatestChapterOffset(page))
|
||||||
|
.addQueryParameter("limit", MDConstants.latestChapterLimit.toString())
|
||||||
|
.addQueryParameter("translatedLanguage[]", dexLang)
|
||||||
|
.addQueryParameter("order[publishAt]", "desc")
|
||||||
|
.addQueryParameter("includeFutureUpdates", "0")
|
||||||
|
.addQueryParameter("originalLanguage[]", preferences.originalLanguages)
|
||||||
|
.addQueryParameter("contentRating[]", preferences.contentRating)
|
||||||
|
.addQueryParameter(
|
||||||
|
"excludedGroups[]",
|
||||||
|
MDConstants.defaultBlockedGroups + preferences.blockedGroups,
|
||||||
|
)
|
||||||
|
.addQueryParameter("excludedUploaders[]", preferences.blockedUploaders)
|
||||||
|
.addQueryParameter("includeFuturePublishAt", "0")
|
||||||
|
.addQueryParameter("includeEmptyPages", "0")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return GET(url, headers, CacheControl.FORCE_NETWORK)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The API endpoint can't sort by date yet, so not implemented.
|
* The API endpoint can't sort by date yet, so not implemented.
|
||||||
*/
|
*/
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||||
val chapterListDto = response.parseAs<ChapterListDto>()
|
val chapterListDto = response.parseAs<ChapterListDto>()
|
||||||
val hasMoreResults = chapterListDto.limit + chapterListDto.offset < chapterListDto.total
|
|
||||||
|
|
||||||
val mangaIds = chapterListDto.data
|
val mangaIds = chapterListDto.data
|
||||||
.flatMap { it.relationships }
|
.flatMap { it.relationships }
|
||||||
|
@ -128,13 +142,14 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
.distinct()
|
.distinct()
|
||||||
.toSet()
|
.toSet()
|
||||||
|
|
||||||
val mangaUrl = MDConstants.apiMangaUrl.toHttpUrlOrNull()!!.newBuilder()
|
val mangaApiUrl = MDConstants.apiMangaUrl.toHttpUrl().newBuilder()
|
||||||
.addQueryParameter("includes[]", MDConstants.coverArt)
|
.addQueryParameter("includes[]", MDConstants.coverArt)
|
||||||
.addQueryParameter("limit", mangaIds.size.toString())
|
.addQueryParameter("limit", mangaIds.size.toString())
|
||||||
.addQueryParameter("contentRating[]", preferences.contentRating)
|
.addQueryParameter("contentRating[]", preferences.contentRating)
|
||||||
.addQueryParameter("ids[]", mangaIds)
|
.addQueryParameter("ids[]", mangaIds)
|
||||||
|
.build()
|
||||||
|
|
||||||
val mangaRequest = GET(mangaUrl.build().toString(), headers, CacheControl.FORCE_NETWORK)
|
val mangaRequest = GET(mangaApiUrl, headers, CacheControl.FORCE_NETWORK)
|
||||||
val mangaResponse = client.newCall(mangaRequest).execute()
|
val mangaResponse = client.newCall(mangaRequest).execute()
|
||||||
val mangaListDto = mangaResponse.parseAs<MangaListDto>()
|
val mangaListDto = mangaResponse.parseAs<MangaListDto>()
|
||||||
val firstVolumeCovers = fetchFirstVolumeCovers(mangaListDto.data).orEmpty()
|
val firstVolumeCovers = fetchFirstVolumeCovers(mangaListDto.data).orEmpty()
|
||||||
|
@ -151,27 +166,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang)
|
helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang)
|
||||||
}
|
}
|
||||||
|
|
||||||
return MangasPage(mangaList, hasMoreResults)
|
return MangasPage(mangaList, chapterListDto.hasNextPage)
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
.addQueryParameter("includeFutureUpdates", "0")
|
|
||||||
.addQueryParameter("originalLanguage[]", preferences.originalLanguages)
|
|
||||||
.addQueryParameter("contentRating[]", preferences.contentRating)
|
|
||||||
.addQueryParameter(
|
|
||||||
"excludedGroups[]",
|
|
||||||
MDConstants.defaultBlockedGroups + preferences.blockedGroups,
|
|
||||||
)
|
|
||||||
.addQueryParameter("excludedUploaders[]", preferences.blockedUploaders)
|
|
||||||
.addQueryParameter("includeFuturePublishAt", "0")
|
|
||||||
.addQueryParameter("includeEmptyPages", "0")
|
|
||||||
|
|
||||||
return GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search manga section
|
// Search manga section
|
||||||
|
@ -228,26 +223,31 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
}
|
}
|
||||||
|
|
||||||
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)) {
|
||||||
|
val mangaId = query.removePrefix(MDConstants.prefixIdSearch)
|
||||||
|
|
||||||
|
if (!helper.containsUuid(mangaId)) {
|
||||||
|
throw Exception(helper.intl.invalidMangaId)
|
||||||
|
}
|
||||||
|
|
||||||
|
val url = MDConstants.apiMangaUrl.toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("ids[]", query.removePrefix(MDConstants.prefixIdSearch))
|
||||||
|
.addQueryParameter("includes[]", MDConstants.coverArt)
|
||||||
|
.addQueryParameter("contentRating[]", MDConstants.allContentRatings)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return GET(url, headers, CacheControl.FORCE_NETWORK)
|
||||||
|
}
|
||||||
|
|
||||||
val tempUrl = MDConstants.apiMangaUrl.toHttpUrl().newBuilder()
|
val tempUrl = MDConstants.apiMangaUrl.toHttpUrl().newBuilder()
|
||||||
.addQueryParameter("limit", MDConstants.mangaLimit.toString())
|
.addQueryParameter("limit", MDConstants.mangaLimit.toString())
|
||||||
.addQueryParameter("offset", helper.getMangaListOffset(page))
|
.addQueryParameter("offset", helper.getMangaListOffset(page))
|
||||||
.addQueryParameter("includes[]", MDConstants.coverArt)
|
.addQueryParameter("includes[]", MDConstants.coverArt)
|
||||||
|
|
||||||
when {
|
when {
|
||||||
query.startsWith(MDConstants.prefixIdSearch) -> {
|
|
||||||
val url = MDConstants.apiMangaUrl.toHttpUrlOrNull()!!.newBuilder()
|
|
||||||
.addQueryParameter("ids[]", query.removePrefix(MDConstants.prefixIdSearch))
|
|
||||||
.addQueryParameter("includes[]", MDConstants.coverArt)
|
|
||||||
.addQueryParameter("contentRating[]", "safe")
|
|
||||||
.addQueryParameter("contentRating[]", "suggestive")
|
|
||||||
.addQueryParameter("contentRating[]", "erotica")
|
|
||||||
.addQueryParameter("contentRating[]", "pornographic")
|
|
||||||
|
|
||||||
return GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK)
|
|
||||||
}
|
|
||||||
|
|
||||||
query.startsWith(MDConstants.prefixGrpSearch) -> {
|
query.startsWith(MDConstants.prefixGrpSearch) -> {
|
||||||
val groupId = query.removePrefix(MDConstants.prefixGrpSearch)
|
val groupId = query.removePrefix(MDConstants.prefixGrpSearch)
|
||||||
|
|
||||||
if (!helper.containsUuid(groupId)) {
|
if (!helper.containsUuid(groupId)) {
|
||||||
throw Exception(helper.intl.invalidGroupId)
|
throw Exception(helper.intl.invalidGroupId)
|
||||||
}
|
}
|
||||||
|
@ -257,6 +257,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
|
|
||||||
query.startsWith(MDConstants.prefixAuthSearch) -> {
|
query.startsWith(MDConstants.prefixAuthSearch) -> {
|
||||||
val authorId = query.removePrefix(MDConstants.prefixAuthSearch)
|
val authorId = query.removePrefix(MDConstants.prefixAuthSearch)
|
||||||
|
|
||||||
if (!helper.containsUuid(authorId)) {
|
if (!helper.containsUuid(authorId)) {
|
||||||
throw Exception(helper.intl.invalidAuthorId)
|
throw Exception(helper.intl.invalidAuthorId)
|
||||||
}
|
}
|
||||||
|
@ -311,7 +312,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
|
|
||||||
url.addQueryParameter("ids[]", ids)
|
url.addQueryParameter("ids[]", ids)
|
||||||
|
|
||||||
val mangaRequest = GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK)
|
val mangaRequest = GET(url.build(), headers, CacheControl.FORCE_NETWORK)
|
||||||
val mangaResponse = client.newCall(mangaRequest).execute()
|
val mangaResponse = client.newCall(mangaRequest).execute()
|
||||||
val mangaList = searchMangaListParse(mangaResponse)
|
val mangaList = searchMangaListParse(mangaResponse)
|
||||||
|
|
||||||
|
@ -345,7 +346,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun searchMangaUploaderRequest(page: Int, uploader: String): Request {
|
private fun searchMangaUploaderRequest(page: Int, uploader: String): Request {
|
||||||
val url = MDConstants.apiChapterUrl.toHttpUrlOrNull()!!.newBuilder()
|
val url = MDConstants.apiChapterUrl.toHttpUrl().newBuilder()
|
||||||
.addQueryParameter("offset", helper.getLatestChapterOffset(page))
|
.addQueryParameter("offset", helper.getLatestChapterOffset(page))
|
||||||
.addQueryParameter("limit", MDConstants.latestChapterLimit.toString())
|
.addQueryParameter("limit", MDConstants.latestChapterLimit.toString())
|
||||||
.addQueryParameter("translatedLanguage[]", dexLang)
|
.addQueryParameter("translatedLanguage[]", dexLang)
|
||||||
|
@ -361,8 +362,9 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
MDConstants.defaultBlockedGroups + preferences.blockedGroups,
|
MDConstants.defaultBlockedGroups + preferences.blockedGroups,
|
||||||
)
|
)
|
||||||
.addQueryParameter("excludedUploaders[]", preferences.blockedUploaders)
|
.addQueryParameter("excludedUploaders[]", preferences.blockedUploaders)
|
||||||
|
.build()
|
||||||
|
|
||||||
return GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK)
|
return GET(url, headers, CacheControl.FORCE_NETWORK)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manga Details section
|
// Manga Details section
|
||||||
|
@ -370,7 +372,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
override fun getMangaUrl(manga: SManga): String {
|
override fun getMangaUrl(manga: SManga): String {
|
||||||
// TODO: Remove once redirect for /manga is fixed.
|
// TODO: Remove once redirect for /manga is fixed.
|
||||||
val title = manga.title
|
val title = manga.title
|
||||||
val url = "${baseUrl}${manga.url.replace("manga", "title")}"
|
val url = baseUrl + manga.url.replaceFirst("manga", "title")
|
||||||
|
|
||||||
return "$url/" + helper.titleToSlug(title)
|
return "$url/" + helper.titleToSlug(title)
|
||||||
}
|
}
|
||||||
|
@ -389,8 +391,9 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
.addQueryParameter("includes[]", MDConstants.coverArt)
|
.addQueryParameter("includes[]", MDConstants.coverArt)
|
||||||
.addQueryParameter("includes[]", MDConstants.author)
|
.addQueryParameter("includes[]", MDConstants.author)
|
||||||
.addQueryParameter("includes[]", MDConstants.artist)
|
.addQueryParameter("includes[]", MDConstants.artist)
|
||||||
|
.build()
|
||||||
|
|
||||||
return GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK)
|
return GET(url, headers, CacheControl.FORCE_NETWORK)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response): SManga {
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
|
@ -453,7 +456,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
.addQueryParameter("locales[]", locales.toSet())
|
.addQueryParameter("locales[]", locales.toSet())
|
||||||
.addQueryParameter("limit", limit.toString())
|
.addQueryParameter("limit", limit.toString())
|
||||||
.addQueryParameter("offset", "0")
|
.addQueryParameter("offset", "0")
|
||||||
.toString()
|
.build()
|
||||||
|
|
||||||
val result = runCatching {
|
val result = runCatching {
|
||||||
client.newCall(GET(apiUrl, headers)).execute().parseAs<CoverArtListDto>().data
|
client.newCall(GET(apiUrl, headers)).execute().parseAs<CoverArtListDto>().data
|
||||||
|
@ -499,8 +502,9 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
.addQueryParameter("contentRating[]", "pornographic")
|
.addQueryParameter("contentRating[]", "pornographic")
|
||||||
.addQueryParameter("excludedGroups[]", preferences.blockedGroups)
|
.addQueryParameter("excludedGroups[]", preferences.blockedGroups)
|
||||||
.addQueryParameter("excludedUploaders[]", preferences.blockedUploaders)
|
.addQueryParameter("excludedUploaders[]", preferences.blockedUploaders)
|
||||||
|
.build()
|
||||||
|
|
||||||
return GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK)
|
return GET(url, headers, CacheControl.FORCE_NETWORK)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
@ -508,7 +512,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
val chapterListResponse = response.parseAs<ChapterListDto>()
|
var chapterListResponse = response.parseAs<ChapterListDto>()
|
||||||
|
|
||||||
val chapterListResults = chapterListResponse.data.toMutableList()
|
val chapterListResults = chapterListResponse.data.toMutableList()
|
||||||
|
|
||||||
|
@ -516,21 +520,20 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
.substringBefore("/feed")
|
.substringBefore("/feed")
|
||||||
.substringAfter("${MDConstants.apiMangaUrl}/")
|
.substringAfter("${MDConstants.apiMangaUrl}/")
|
||||||
|
|
||||||
val limit = chapterListResponse.limit
|
|
||||||
|
|
||||||
var offset = chapterListResponse.offset
|
var offset = chapterListResponse.offset
|
||||||
|
var hasNextPage = chapterListResponse.hasNextPage
|
||||||
var hasMoreResults = (limit + offset) < chapterListResponse.total
|
|
||||||
|
|
||||||
// Max results that can be returned is 500 so need to make more API
|
// Max results that can be returned is 500 so need to make more API
|
||||||
// calls if limit + offset > total chapters
|
// calls if the chapter list response has a next page.
|
||||||
while (hasMoreResults) {
|
while (hasNextPage) {
|
||||||
offset += limit
|
offset += chapterListResponse.limit
|
||||||
|
|
||||||
val newRequest = paginatedChapterListRequest(mangaId, offset)
|
val newRequest = paginatedChapterListRequest(mangaId, offset)
|
||||||
val newResponse = client.newCall(newRequest).execute()
|
val newResponse = client.newCall(newRequest).execute()
|
||||||
val newChapterList = newResponse.parseAs<ChapterListDto>()
|
val newChapterList = newResponse.parseAs<ChapterListDto>()
|
||||||
chapterListResults.addAll(newChapterList.data)
|
chapterListResults.addAll(newChapterList.data)
|
||||||
hasMoreResults = (limit + offset) < newChapterList.total
|
|
||||||
|
hasNextPage = newChapterList.hasNextPage
|
||||||
}
|
}
|
||||||
|
|
||||||
return chapterListResults
|
return chapterListResults
|
||||||
|
|
|
@ -53,48 +53,48 @@ class MangaDexFactory : SourceFactory {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class MangaDexArabic : MangaDex("ar", "ar")
|
class MangaDexArabic : MangaDex("ar")
|
||||||
class MangaDexBengali : MangaDex("bn", "bn")
|
class MangaDexBengali : MangaDex("bn")
|
||||||
class MangaDexBulgarian : MangaDex("bg", "bg")
|
class MangaDexBulgarian : MangaDex("bg")
|
||||||
class MangaDexBurmese : MangaDex("my", "my")
|
class MangaDexBurmese : MangaDex("my")
|
||||||
class MangaDexCatalan : MangaDex("ca", "ca")
|
class MangaDexCatalan : MangaDex("ca")
|
||||||
class MangaDexChineseSimplified : MangaDex("zh-Hans", "zh")
|
class MangaDexChineseSimplified : MangaDex("zh-Hans", "zh")
|
||||||
class MangaDexChineseTraditional : MangaDex("zh-Hant", "zh-hk")
|
class MangaDexChineseTraditional : MangaDex("zh-Hant", "zh-hk")
|
||||||
class MangaDexCzech : MangaDex("cs", "cs")
|
class MangaDexCzech : MangaDex("cs")
|
||||||
class MangaDexDanish : MangaDex("da", "da")
|
class MangaDexDanish : MangaDex("da")
|
||||||
class MangaDexDutch : MangaDex("nl", "nl")
|
class MangaDexDutch : MangaDex("nl")
|
||||||
class MangaDexEnglish : MangaDex("en", "en")
|
class MangaDexEnglish : MangaDex("en")
|
||||||
class MangaDexFilipino : MangaDex("fil", "tl")
|
class MangaDexFilipino : MangaDex("fil", "tl")
|
||||||
class MangaDexFinnish : MangaDex("fi", "fi")
|
class MangaDexFinnish : MangaDex("fi")
|
||||||
class MangaDexFrench : MangaDex("fr", "fr")
|
class MangaDexFrench : MangaDex("fr")
|
||||||
class MangaDexGerman : MangaDex("de", "de")
|
class MangaDexGerman : MangaDex("de")
|
||||||
class MangaDexGreek : MangaDex("el", "el")
|
class MangaDexGreek : MangaDex("el")
|
||||||
class MangaDexHebrew : MangaDex("he", "he")
|
class MangaDexHebrew : MangaDex("he")
|
||||||
class MangaDexHindi : MangaDex("hi", "hi")
|
class MangaDexHindi : MangaDex("hi")
|
||||||
class MangaDexHungarian : MangaDex("hu", "hu")
|
class MangaDexHungarian : MangaDex("hu")
|
||||||
class MangaDexIndonesian : MangaDex("id", "id")
|
class MangaDexIndonesian : MangaDex("id")
|
||||||
class MangaDexItalian : MangaDex("it", "it")
|
class MangaDexItalian : MangaDex("it")
|
||||||
class MangaDexJapanese : MangaDex("ja", "ja")
|
class MangaDexJapanese : MangaDex("ja")
|
||||||
class MangaDexKazakh : MangaDex("kk", "kk")
|
class MangaDexKazakh : MangaDex("kk")
|
||||||
class MangaDexKorean : MangaDex("ko", "ko")
|
class MangaDexKorean : MangaDex("ko")
|
||||||
class MangaDexLatin : MangaDex("la", "la")
|
class MangaDexLatin : MangaDex("la")
|
||||||
class MangaDexLithuanian : MangaDex("lt", "lt")
|
class MangaDexLithuanian : MangaDex("lt")
|
||||||
class MangaDexMalay : MangaDex("ms", "ms")
|
class MangaDexMalay : MangaDex("ms")
|
||||||
class MangaDexMongolian : MangaDex("mn", "mn")
|
class MangaDexMongolian : MangaDex("mn")
|
||||||
class MangaDexNepali : MangaDex("ne", "ne")
|
class MangaDexNepali : MangaDex("ne")
|
||||||
class MangaDexNorwegian : MangaDex("no", "no")
|
class MangaDexNorwegian : MangaDex("no")
|
||||||
class MangaDexPersian : MangaDex("fa", "fa")
|
class MangaDexPersian : MangaDex("fa")
|
||||||
class MangaDexPolish : MangaDex("pl", "pl")
|
class MangaDexPolish : MangaDex("pl")
|
||||||
class MangaDexPortugueseBrazil : MangaDex("pt-BR", "pt-br")
|
class MangaDexPortugueseBrazil : MangaDex("pt-BR", "pt-br")
|
||||||
class MangaDexPortuguesePortugal : MangaDex("pt", "pt")
|
class MangaDexPortuguesePortugal : MangaDex("pt")
|
||||||
class MangaDexRomanian : MangaDex("ro", "ro")
|
class MangaDexRomanian : MangaDex("ro")
|
||||||
class MangaDexRussian : MangaDex("ru", "ru")
|
class MangaDexRussian : MangaDex("ru")
|
||||||
class MangaDexSerboCroatian : MangaDex("sh", "sh")
|
class MangaDexSerboCroatian : MangaDex("sh")
|
||||||
class MangaDexSpanishLatinAmerica : MangaDex("es-419", "es-la")
|
class MangaDexSpanishLatinAmerica : MangaDex("es-419", "es-la")
|
||||||
class MangaDexSpanishSpain : MangaDex("es", "es")
|
class MangaDexSpanishSpain : MangaDex("es")
|
||||||
class MangaDexSwedish : MangaDex("sv", "sv")
|
class MangaDexSwedish : MangaDex("sv")
|
||||||
class MangaDexTamil : MangaDex("ta", "ta")
|
class MangaDexTamil : MangaDex("ta")
|
||||||
class MangaDexThai : MangaDex("th", "th")
|
class MangaDexThai : MangaDex("th")
|
||||||
class MangaDexTurkish : MangaDex("tr", "tr")
|
class MangaDexTurkish : MangaDex("tr")
|
||||||
class MangaDexUkrainian : MangaDex("uk", "uk")
|
class MangaDexUkrainian : MangaDex("uk")
|
||||||
class MangaDexVietnamese : MangaDex("vi", "vi")
|
class MangaDexVietnamese : MangaDex("vi")
|
||||||
|
|
|
@ -373,11 +373,11 @@ class MangaDexFilters {
|
||||||
TagExclusionMode(intl, getTagModes(intl)),
|
TagExclusionMode(intl, getTagModes(intl)),
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun addFiltersToUrl(url: HttpUrl.Builder, filters: FilterList, dexLang: String): String {
|
internal fun addFiltersToUrl(url: HttpUrl.Builder, filters: FilterList, dexLang: String): HttpUrl {
|
||||||
filters.filterIsInstance<UrlQueryFilter>()
|
filters.filterIsInstance<UrlQueryFilter>()
|
||||||
.forEach { filter -> filter.addQueryParameter(url, dexLang) }
|
.forEach { filter -> filter.addQueryParameter(url, dexLang) }
|
||||||
|
|
||||||
return url.toString()
|
return url.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<Tag>.sortIfTranslated(intl: MangaDexIntl): List<Tag> = apply {
|
private fun List<Tag>.sortIfTranslated(intl: MangaDexIntl): List<Tag> = apply {
|
||||||
|
|
|
@ -199,20 +199,18 @@ class MangaDexHelper(lang: String) {
|
||||||
* Check the token map to see if the MD@Home host is still valid.
|
* Check the token map to see if the MD@Home host is still valid.
|
||||||
*/
|
*/
|
||||||
fun getValidImageUrlForPage(page: Page, headers: Headers, client: OkHttpClient): Request {
|
fun getValidImageUrlForPage(page: Page, headers: Headers, client: OkHttpClient): Request {
|
||||||
val data = page.url.split(",")
|
val (host, tokenRequestUrl, time) = page.url.split(",")
|
||||||
|
|
||||||
val mdAtHomeServerUrl =
|
val mdAtHomeServerUrl =
|
||||||
when (Date().time - data[2].toLong() > MDConstants.mdAtHomeTokenLifespan) {
|
when (Date().time - time.toLong() > MDConstants.mdAtHomeTokenLifespan) {
|
||||||
false -> data[0]
|
false -> host
|
||||||
true -> {
|
true -> {
|
||||||
val tokenRequestUrl = data[1]
|
|
||||||
val tokenLifespan = Date().time - (tokenTracker[tokenRequestUrl] ?: 0)
|
val tokenLifespan = Date().time - (tokenTracker[tokenRequestUrl] ?: 0)
|
||||||
val cacheControl =
|
val cacheControl = if (tokenLifespan > MDConstants.mdAtHomeTokenLifespan) {
|
||||||
if (tokenLifespan > MDConstants.mdAtHomeTokenLifespan) {
|
CacheControl.FORCE_NETWORK
|
||||||
CacheControl.FORCE_NETWORK
|
} else {
|
||||||
} else {
|
USE_CACHE
|
||||||
USE_CACHE
|
}
|
||||||
}
|
|
||||||
getMdAtHomeUrl(tokenRequestUrl, client, headers, cacheControl)
|
getMdAtHomeUrl(tokenRequestUrl, client, headers, cacheControl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -265,22 +263,20 @@ class MangaDexHelper(lang: String) {
|
||||||
coverFileName: String?,
|
coverFileName: String?,
|
||||||
coverSuffix: String?,
|
coverSuffix: String?,
|
||||||
lang: String,
|
lang: String,
|
||||||
): SManga {
|
): SManga = SManga.create().apply {
|
||||||
return SManga.create().apply {
|
url = "/manga/${mangaDataDto.id}"
|
||||||
url = "/manga/${mangaDataDto.id}"
|
val titleMap = mangaDataDto.attributes!!.title
|
||||||
val titleMap = mangaDataDto.attributes!!.title
|
val dirtyTitle =
|
||||||
val dirtyTitle =
|
titleMap.values.firstOrNull() // use literally anything from title as first resort
|
||||||
titleMap.values.firstOrNull() // use literally anything from title as first resort
|
?: mangaDataDto.attributes.altTitles
|
||||||
?: mangaDataDto.attributes.altTitles
|
.find { (it[lang] ?: it["en"]) !== null }
|
||||||
.find { (it[lang] ?: it["en"]) !== null }
|
?.values?.singleOrNull() // find something else from alt titles
|
||||||
?.values?.singleOrNull() // find something else from alt titles
|
title = dirtyTitle?.removeEntitiesAndMarkdown().orEmpty()
|
||||||
title = (dirtyTitle ?: "").removeEntitiesAndMarkdown()
|
|
||||||
|
|
||||||
coverFileName?.let {
|
coverFileName?.let {
|
||||||
thumbnail_url = when (!coverSuffix.isNullOrEmpty()) {
|
thumbnail_url = when (!coverSuffix.isNullOrEmpty()) {
|
||||||
true -> "${MDConstants.cdnUrl}/covers/${mangaDataDto.id}/$coverFileName$coverSuffix"
|
true -> "${MDConstants.cdnUrl}/covers/${mangaDataDto.id}/$coverFileName$coverSuffix"
|
||||||
else -> "${MDConstants.cdnUrl}/covers/${mangaDataDto.id}/$coverFileName"
|
else -> "${MDConstants.cdnUrl}/covers/${mangaDataDto.id}/$coverFileName"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -335,10 +331,12 @@ class MangaDexHelper(lang: String) {
|
||||||
|
|
||||||
val genreList = MDConstants.tagGroupsOrder.flatMap { genresMap[it].orEmpty() } + nonGenres
|
val genreList = MDConstants.tagGroupsOrder.flatMap { genresMap[it].orEmpty() } + nonGenres
|
||||||
|
|
||||||
var desc = (attr.description[lang] ?: attr.description["en"])?.removeEntitiesAndMarkdown() ?: ""
|
var desc = (attr.description[lang] ?: attr.description["en"])
|
||||||
|
?.removeEntitiesAndMarkdown()
|
||||||
|
.orEmpty()
|
||||||
|
|
||||||
if (altTitlesInDesc) {
|
if (altTitlesInDesc) {
|
||||||
val romanizedOriginalLang = MDConstants.romanizedLangCodes[attr.originalLanguage] ?: ""
|
val romanizedOriginalLang = MDConstants.romanizedLangCodes[attr.originalLanguage].orEmpty()
|
||||||
val altTitles = attr.altTitles
|
val altTitles = attr.altTitles
|
||||||
.filter { it.containsKey(lang) || it.containsKey(romanizedOriginalLang) }
|
.filter { it.containsKey(lang) || it.containsKey(romanizedOriginalLang) }
|
||||||
.mapNotNull { it.values.singleOrNull() }
|
.mapNotNull { it.values.singleOrNull() }
|
||||||
|
@ -447,13 +445,9 @@ class MangaDexHelper(lang: String) {
|
||||||
editText.addTextChangedListener(
|
editText.addTextChangedListener(
|
||||||
object : TextWatcher {
|
object : TextWatcher {
|
||||||
|
|
||||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun afterTextChanged(editable: Editable?) {
|
override fun afterTextChanged(editable: Editable?) {
|
||||||
requireNotNull(editable)
|
requireNotNull(editable)
|
||||||
|
|
|
@ -18,6 +18,11 @@ class MangaDexIntl(lang: String) {
|
||||||
else -> "The text contains invalid UUIDs"
|
else -> "The text contains invalid UUIDs"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val invalidMangaId: String = when (availableLang) {
|
||||||
|
BRAZILIAN_PORTUGUESE, PORTUGUESE -> "ID do mangá inválido"
|
||||||
|
else -> "Not a valid manga ID"
|
||||||
|
}
|
||||||
|
|
||||||
val invalidGroupId: String = when (availableLang) {
|
val invalidGroupId: String = when (availableLang) {
|
||||||
BRAZILIAN_PORTUGUESE, PORTUGUESE -> "ID do grupo inválido"
|
BRAZILIAN_PORTUGUESE, PORTUGUESE -> "ID do grupo inválido"
|
||||||
SPANISH_LATAM, SPANISH -> "ID de grupo inválida"
|
SPANISH_LATAM, SPANISH -> "ID de grupo inválida"
|
||||||
|
|
|
@ -14,10 +14,9 @@ import okhttp3.OkHttpClient
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interceptor to post to md@home for MangaDex Stats
|
* Interceptor to post to MD@Home for MangaDex Stats
|
||||||
*/
|
*/
|
||||||
class MdAtHomeReportInterceptor(
|
class MdAtHomeReportInterceptor(
|
||||||
private val client: OkHttpClient,
|
private val client: OkHttpClient,
|
||||||
|
@ -26,23 +25,20 @@ class MdAtHomeReportInterceptor(
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private val mdAtHomeUrlRegex =
|
|
||||||
Regex("""^https://[\w\d]+\.[\w\d]+\.mangadex(\b-test\b)?\.network.*${'$'}""")
|
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val originalRequest = chain.request()
|
val originalRequest = chain.request()
|
||||||
val response = chain.proceed(chain.request())
|
val response = chain.proceed(chain.request())
|
||||||
val url = originalRequest.url.toString()
|
val url = originalRequest.url.toString()
|
||||||
|
|
||||||
if (!url.contains(mdAtHomeUrlRegex)) {
|
if (!url.contains(MD_AT_HOME_URL_REGEX)) {
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = ImageReportDto(
|
val result = ImageReportDto(
|
||||||
url,
|
url = url,
|
||||||
success = response.isSuccessful,
|
success = response.isSuccessful,
|
||||||
bytes = response.peekBody(Long.MAX_VALUE).bytes().size,
|
bytes = response.peekBody(Long.MAX_VALUE).bytes().size,
|
||||||
cached = response.header("X-Cache", "") == "HIT",
|
cached = response.headers["X-Cache"] == "HIT",
|
||||||
duration = response.receivedResponseAtMillis - response.sentRequestAtMillis,
|
duration = response.receivedResponseAtMillis - response.sentRequestAtMillis,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,22 +53,28 @@ class MdAtHomeReportInterceptor(
|
||||||
// Execute the report endpoint network call asynchronously to avoid blocking
|
// Execute the report endpoint network call asynchronously to avoid blocking
|
||||||
// the reader from showing the image once it's fully loaded if the report call
|
// the reader from showing the image once it's fully loaded if the report call
|
||||||
// gets stuck, as it tend to happens sometimes.
|
// gets stuck, as it tend to happens sometimes.
|
||||||
client.newCall(reportRequest).enqueue(
|
client.newCall(reportRequest).enqueue(REPORT_CALLBACK)
|
||||||
object : Callback {
|
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
|
||||||
Log.e("MangaDex", "Error trying to POST report to MD@Home: ${e.message}")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
|
||||||
response.close()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val JSON_MEDIA_TYPE = "application/json".toMediaType()
|
private val JSON_MEDIA_TYPE = "application/json".toMediaType()
|
||||||
|
private val MD_AT_HOME_URL_REGEX =
|
||||||
|
"""^https://[\w\d]+\.[\w\d]+\.mangadex(\b-test\b)?\.network.*${'$'}""".toRegex()
|
||||||
|
|
||||||
|
private val REPORT_CALLBACK = object : Callback {
|
||||||
|
override fun onFailure(call: Call, e: okio.IOException) {
|
||||||
|
Log.e("MangaDex", "Error trying to POST report to MD@Home: ${e.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
Log.e("MangaDex", "Error trying to POST report to MD@Home: HTTP error ${response.code}")
|
||||||
|
}
|
||||||
|
|
||||||
|
response.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,11 @@ data class PaginatedResponseDto<T : EntityDto>(
|
||||||
val limit: Int = 0,
|
val limit: Int = 0,
|
||||||
val offset: Int = 0,
|
val offset: Int = 0,
|
||||||
val total: Int = 0,
|
val total: Int = 0,
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
val hasNextPage: Boolean
|
||||||
|
get() = limit + offset < total
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ResponseDto<T : EntityDto>(
|
data class ResponseDto<T : EntityDto>(
|
||||||
|
|
Loading…
Reference in New Issue