switch to dex covers

(cherry picked from commit 28d83509dadb0ddaca2e13e98f5e5f6e3df2d2a7)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/source/online/MangaDexCache.kt
#	app/src/main/java/eu/kanade/tachiyomi/source/online/handlers/ApiMangaParser.kt
#	app/src/main/java/eu/kanade/tachiyomi/source/online/handlers/FollowsHandler.kt
#	app/src/main/java/eu/kanade/tachiyomi/source/online/handlers/PopularHandler.kt
#	app/src/main/java/eu/kanade/tachiyomi/source/online/handlers/SearchHandler.kt
#	app/src/main/java/eu/kanade/tachiyomi/source/online/handlers/SimilarHandler.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt
#	app/src/main/java/exh/md/utils/MdUtil.kt
This commit is contained in:
Carlos 2021-06-05 10:38:21 -04:00 committed by Jobobby04
parent eab7571feb
commit d82967ea3d
8 changed files with 118 additions and 84 deletions

View File

@ -78,18 +78,8 @@ class ApiMangaParser(
title = MdUtil.cleanString(networkManga.title[lang] ?: networkManga.title["en"]!!) title = MdUtil.cleanString(networkManga.title[lang] ?: networkManga.title["en"]!!)
altTitles = networkManga.altTitles.mapNotNull { it[lang] }.nullIfEmpty() altTitles = networkManga.altTitles.mapNotNull { it[lang] }.nullIfEmpty()
val coverUrl = MdUtil.formThumbUrl(networkApiManga.data.id) val coverId = networkApiManga.relationships.firstOrNull { it.type.equals("cover_art", true) }?.id
/*val coverUrlId = networkApiManga.relationships.firstOrNull { it.type == "cover_art" }?.id cover = MdUtil.getCoverUrl(networkApiManga.data.id, coverId, client)
if (coverUrlId != null) {
runCatching {
val json = client.newCall(GET(MdUtil.coverUrl(networkApiManga.data.id, coverUrlId))).await()
.parseAs<CoverListResponse>(MdUtil.jsonParser)
json.results.firstOrNull()?.data?.attributes?.fileName?.let { fileName ->
coverUrl = "${MdUtil.cdnUrl}/covers/${networkApiManga.data.id}/$fileName"
}
}
}*/
cover = coverUrl
description = MdUtil.cleanDescription(networkManga.description[lang] ?: networkManga.description["en"]!!) description = MdUtil.cleanDescription(networkManga.description[lang] ?: networkManga.description["en"]!!)

View File

@ -75,23 +75,13 @@ class FollowsHandler(
val comparator = compareBy<Pair<SManga, MangaDexSearchMetadata>> { it.second.followStatus } val comparator = compareBy<Pair<SManga, MangaDexSearchMetadata>> { it.second.followStatus }
.thenBy { it.first.title } .thenBy { it.first.title }
return response.map { val coverMap = MdUtil.getCoversFromMangaList(response, client)
val coverUrl = MdUtil.formThumbUrl(it.data.id)
/*val coverUrlId = it.relationships.firstOrNull { it.type == "cover_art" }?.id
if (coverUrlId != null) {
runCatching {
val covers = client.newCall(GET(MdUtil.coverUrl(it.data.id, coverUrlId))).await()
.parseAs<CoverListResponse>()
covers.results.firstOrNull()?.data?.attributes?.fileName?.let { fileName ->
coverUrl = "${MdUtil.cdnUrl}/covers/${it.data.id}/$fileName"
}
}
}*/
return response.map {
MdUtil.createMangaEntry( MdUtil.createMangaEntry(
it, it,
lang, lang,
coverUrl coverMap[it.data.id]
).toSManga() to MangaDexSearchMetadata().apply { ).toSManga() to MangaDexSearchMetadata().apply {
followStatus = FollowStatus.fromDex(statuses[it.data.id]).int followStatus = FollowStatus.fromDex(statuses[it.data.id]).int
} }

View File

@ -14,9 +14,10 @@ import rx.Observable
class PageHandler( class PageHandler(
private val client: OkHttpClient, private val client: OkHttpClient,
private val headers: Headers, private val headers: Headers,
private val dataSaver: Boolean,
private val apiChapterParser: ApiChapterParser, private val apiChapterParser: ApiChapterParser,
private val mangaPlusHandler: MangaPlusHandler private val mangaPlusHandler: MangaPlusHandler,
private val usePort443Only: () -> Boolean,
private val dataSaver: () -> Boolean
) { ) {
fun fetchPageList(chapter: SChapter): Observable<List<Page>> { fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
@ -28,11 +29,18 @@ class PageHandler(
mangaPlusHandler.fetchPageList(chapterId) mangaPlusHandler.fetchPageList(chapterId)
} }
} }
val atHomeRequestUrl = if (usePort443Only()) {
"${MdUtil.atHomeUrl}/${MdUtil.getChapterId(chapter.url)}?forcePort443=true"
} else {
"${MdUtil.atHomeUrl}/${MdUtil.getChapterId(chapter.url)}"
}
return client.newCall(pageListRequest(chapter)) return client.newCall(pageListRequest(chapter))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
val host = MdUtil.atHomeUrlHostUrl("${MdUtil.atHomeUrl}/${MdUtil.getChapterId(chapter.url)}", client) val host = MdUtil.atHomeUrlHostUrl(atHomeRequestUrl, client, CacheControl.FORCE_NETWORK)
apiChapterParser.pageListParse(response, host, dataSaver) apiChapterParser.pageListParse(response, host, dataSaver())
} }
} }

View File

@ -50,19 +50,10 @@ class PopularHandler(
val mlResponse = response.parseAs<MangaListResponse>(MdUtil.jsonParser) val mlResponse = response.parseAs<MangaListResponse>(MdUtil.jsonParser)
val hasMoreResults = mlResponse.limit + mlResponse.offset < mlResponse.total val hasMoreResults = mlResponse.limit + mlResponse.offset < mlResponse.total
val coverMap = MdUtil.getCoversFromMangaList(mlResponse.results, client)
val mangaList = mlResponse.results.map { val mangaList = mlResponse.results.map {
val coverUrl = MdUtil.formThumbUrl(it.data.id) MdUtil.createMangaEntry(it, lang, coverMap[it.data.id]).toSManga()
/*val coverUrlId = it.relationships.firstOrNull { it.type == "cover_art" }?.id
if (coverUrlId != null) {
runCatching {
val covers = client.newCall(GET(MdUtil.coverUrl(it.data.id, coverUrlId))).await()
.parseAs<CoverListResponse>()
covers.results.firstOrNull()?.data?.attributes?.fileName?.let { fileName ->
coverUrl = "${MdUtil.cdnUrl}/covers/${it.data.id}/$fileName"
}
}
}*/
MdUtil.createMangaEntry(it, lang, coverUrl).toSManga()
} }
return MangasPage(mangaList, hasMoreResults) return MangasPage(mangaList, hasMoreResults)
} }

View File

@ -2,13 +2,11 @@ package exh.md.handlers
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.toSManga import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.util.lang.runAsObservable import eu.kanade.tachiyomi.util.lang.runAsObservable
import exh.md.handlers.serializers.CoverListResponse
import exh.md.handlers.serializers.MangaListResponse import exh.md.handlers.serializers.MangaListResponse
import exh.md.handlers.serializers.MangaResponse import exh.md.handlers.serializers.MangaResponse
import exh.md.utils.MdUtil import exh.md.utils.MdUtil
@ -36,21 +34,8 @@ class SearchHandler(
.flatMap { response -> .flatMap { response ->
runAsObservable({ runAsObservable({
val mangaResponse = response.parseAs<MangaResponse>(MdUtil.jsonParser) val mangaResponse = response.parseAs<MangaResponse>(MdUtil.jsonParser)
val coverUrl = MdUtil.formThumbUrl(mangaResponse.data.id)
/*val coverUrlId = mangaResponse.relationships.firstOrNull { it.type == "cover_art" }?.id
if (coverUrlId != null) {
runCatching {
val covers = client.newCall(GET(MdUtil.coverUrl(mangaResponse.data.id, coverUrlId))).await()
.parseAs<CoverListResponse>()
covers.results.firstOrNull()?.data?.attributes?.fileName?.let { fileName ->
coverUrl = "${MdUtil.cdnUrl}/covers/${mangaResponse.data.id}/$fileName"
}
}
}*/
val details = apiMangaParser val details = apiMangaParser
.parseToManga(MdUtil.createMangaEntry(mangaResponse, lang, coverUrl), response, sourceId).toSManga() .parseToManga(MdUtil.createMangaEntry(mangaResponse, lang, null), response, sourceId).toSManga()
MangasPage(listOf(details), false) MangasPage(listOf(details), false)
}) })
} }
@ -67,20 +52,10 @@ class SearchHandler(
private suspend fun searchMangaParse(response: Response): MangasPage { private suspend fun searchMangaParse(response: Response): MangasPage {
val mlResponse = response.parseAs<MangaListResponse>(MdUtil.jsonParser) val mlResponse = response.parseAs<MangaListResponse>(MdUtil.jsonParser)
val coverMap = MdUtil.getCoversFromMangaList(mlResponse.results, client)
val hasMoreResults = mlResponse.limit + mlResponse.offset < mlResponse.total val hasMoreResults = mlResponse.limit + mlResponse.offset < mlResponse.total
val mangaList = mlResponse.results.map { val mangaList = mlResponse.results.map {
var coverUrl = MdUtil.formThumbUrl(it.data.id) MdUtil.createMangaEntry(it, lang, coverMap[it.data.id]).toSManga()
val coverUrlId = it.relationships.firstOrNull { it.type == "cover_art" }?.id
if (coverUrlId != null) {
runCatching {
val covers = client.newCall(GET(MdUtil.coverUrl(it.data.id, coverUrlId))).await()
.parseAs<CoverListResponse>()
covers.results.firstOrNull()?.data?.attributes?.fileName?.let { fileName ->
coverUrl = "${MdUtil.cdnUrl}/covers/${it.data.id}/$fileName"
}
}
}
MdUtil.createMangaEntry(it, lang, coverUrl).toSManga()
} }
return MangasPage(mangaList, hasMoreResults) return MangasPage(mangaList, hasMoreResults)
} }

View File

@ -5,13 +5,14 @@ import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import exh.md.handlers.serializers.CoverListResponse
import exh.md.handlers.serializers.SimilarMangaResponse import exh.md.handlers.serializers.SimilarMangaResponse
import exh.md.utils.MdUtil import exh.md.utils.MdUtil
import okhttp3.CacheControl import okhttp3.CacheControl
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response
import tachiyomi.source.model.MangaInfo import tachiyomi.source.model.MangaInfo
class SimilarHandler( class SimilarHandler(
@ -21,7 +22,29 @@ class SimilarHandler(
suspend fun getSimilar(manga: MangaInfo): MangasPage { suspend fun getSimilar(manga: MangaInfo): MangasPage {
val response = client.newCall(similarMangaRequest(manga)).await() val response = client.newCall(similarMangaRequest(manga)).await()
return similarMangaParse(response) .parseAs<SimilarMangaResponse>()
val ids = response.matches.map { it.id }
val coverUrl = MdUtil.coverUrl.toHttpUrl().newBuilder().apply {
ids.forEach { mangaId ->
addQueryParameter("manga[]", mangaId)
}
addQueryParameter("limit", ids.size.toString())
}.build().toString()
val coverListResponse = client.newCall(GET(coverUrl)).await()
.parseAs<CoverListResponse>()
val unique = coverListResponse.results.distinctBy { it.relationships[0].id }
val coverMap = unique.map { coverResponse ->
val fileName = coverResponse.data.attributes.fileName
val mangaId = coverResponse.relationships.first { it.type.equals("manga", true) }.id
val thumbnailUrl = "${MdUtil.cdnUrl}/covers/$mangaId/$fileName"
mangaId to thumbnailUrl
}.toMap()
return similarMangaParse(response, coverMap)
} }
private fun similarMangaRequest(manga: MangaInfo): Request { private fun similarMangaRequest(manga: MangaInfo): Request {
@ -29,12 +52,12 @@ class SimilarHandler(
return GET(tempUrl, Headers.Builder().build(), CacheControl.FORCE_NETWORK) return GET(tempUrl, Headers.Builder().build(), CacheControl.FORCE_NETWORK)
} }
private fun similarMangaParse(response: Response): MangasPage { private fun similarMangaParse(response: SimilarMangaResponse, coverMap: Map<String, String>): MangasPage {
val mangaList = response.parseAs<SimilarMangaResponse>().matches.map { val mangaList = response.matches.map {
SManga.create().apply { SManga.create().apply {
url = MdUtil.buildMangaUrl(it.id) url = MdUtil.buildMangaUrl(it.id)
title = MdUtil.cleanString(it.title[lang] ?: it.title["en"]!!) title = MdUtil.cleanString(it.title[lang] ?: it.title["en"]!!)
thumbnail_url = MdUtil.formThumbUrl(url) thumbnail_url = coverMap[it.id]
} }
} }
return MangasPage(mangaList, false) return MangasPage(mangaList, false)

View File

@ -98,6 +98,7 @@ data class CoverListResponse(
@Serializable @Serializable
data class CoverResponse( data class CoverResponse(
val data: Cover, val data: Cover,
val relationships: List<Relationships>
) )
@Serializable @Serializable

View File

@ -10,7 +10,10 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.all.MangaDex import eu.kanade.tachiyomi.source.online.all.MangaDex
import exh.log.xLogD import exh.log.xLogD
import exh.log.xLogE
import exh.md.handlers.serializers.AtHomeResponse import exh.md.handlers.serializers.AtHomeResponse
import exh.md.handlers.serializers.CoverListResponse
import exh.md.handlers.serializers.CoverResponse
import exh.md.handlers.serializers.ListCallResponse import exh.md.handlers.serializers.ListCallResponse
import exh.md.handlers.serializers.LoginBodyToken import exh.md.handlers.serializers.LoginBodyToken
import exh.md.handlers.serializers.MangaResponse import exh.md.handlers.serializers.MangaResponse
@ -24,6 +27,7 @@ import kotlinx.serialization.SerializationException
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.CacheControl
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -44,6 +48,7 @@ class MdUtil {
const val apiUrl = "https://api.mangadex.org" const val apiUrl = "https://api.mangadex.org"
const val imageUrlCacheNotFound = "https://cdn.statically.io/img/raw.githubusercontent.com/CarlosEsco/Neko/master/.github/manga_cover_not_found.png" const val imageUrlCacheNotFound = "https://cdn.statically.io/img/raw.githubusercontent.com/CarlosEsco/Neko/master/.github/manga_cover_not_found.png"
const val atHomeUrl = "$apiUrl/at-home/server" const val atHomeUrl = "$apiUrl/at-home/server"
const val coverUrl = "$apiUrl/cover"
const val chapterUrl = "$apiUrl/chapter/" const val chapterUrl = "$apiUrl/chapter/"
const val chapterSuffix = "/chapter/" const val chapterSuffix = "/chapter/"
const val checkTokenUrl = "$apiUrl/auth/check" const val checkTokenUrl = "$apiUrl/auth/check"
@ -68,10 +73,10 @@ class MdUtil {
}.build().toString() }.build().toString()
} }
fun coverUrl(mangaId: String, coverId: String) = "$apiUrl/cover/?manga[]=$mangaId&ids[]=$coverId" fun coverUrl(mangaId: String, coverId: String) = "$apiUrl/cover?manga[]=$mangaId&ids[]=$coverId"
const val similarCache = "https://raw.githubusercontent.com/goldbattle/MangadexRecomendations/master/output/api/" const val similarCacheMapping = "https://api.similarmanga.com/mapping/mdex2search.csv"
const val similarCacheCdn = "https://cdn.statically.io/gh/goldbattle/MangadexRecomendations/master/output/api/" const val similarCacheMangas = "https://api.similarmanga.com/manga/"
const val similarBaseApi = "https://api.similarmanga.com/similar/" const val similarBaseApi = "https://api.similarmanga.com/similar/"
const val groupSearchUrl = "$baseUrl/groups/0/1/" const val groupSearchUrl = "$baseUrl/groups/0/1/"
@ -96,6 +101,11 @@ class MdUtil {
private const val scanlatorSeparator = " & " private const val scanlatorSeparator = " & "
const val contentRatingSafe = "safe"
const val contentRatingSuggestive = "suggestive"
const val contentRatingErotica = "erotica"
const val contentRatingPornographic = "pornographic"
val validOneShotFinalChapters = listOf("0", "1") val validOneShotFinalChapters = listOf("0", "1")
val englishDescriptionTags = listOf( val englishDescriptionTags = listOf(
@ -288,10 +298,10 @@ class MdUtil {
return null return null
} }
fun atHomeUrlHostUrl(requestUrl: String, client: OkHttpClient): String { fun atHomeUrlHostUrl(requestUrl: String, client: OkHttpClient, cacheControl: CacheControl): String {
val atHomeRequest = GET(requestUrl) val atHomeRequest = GET(requestUrl, cache = cacheControl)
val atHomeResponse = client.newCall(atHomeRequest).execute() val atHomeResponse = client.newCall(atHomeRequest).execute()
return atHomeResponse.parseAs<AtHomeResponse>(jsonParser).baseUrl return jsonParser.decodeFromString<AtHomeResponse>(atHomeResponse.body!!.string()).baseUrl
} }
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)
@ -300,15 +310,61 @@ class MdUtil {
fun parseDate(dateAsString: String): Long = fun parseDate(dateAsString: String): Long =
dateFormatter.parse(dateAsString)?.time ?: 0 dateFormatter.parse(dateAsString)?.time ?: 0
fun createMangaEntry(json: MangaResponse, lang: String, coverUrl: String): MangaInfo { fun createMangaEntry(json: MangaResponse, lang: String, coverUrl: String?): MangaInfo {
val key = buildMangaUrl(json.data.id)
return MangaInfo( return MangaInfo(
key = key, key = buildMangaUrl(json.data.id),
title = cleanString(json.data.attributes.title[lang] ?: json.data.attributes.title["en"]!!), title = cleanString(json.data.attributes.title[lang] ?: json.data.attributes.title["en"]!!),
cover = coverUrl cover = coverUrl.orEmpty()
) )
} }
suspend fun getCoverUrl(dexId: String, coverId: String?, client: OkHttpClient): String {
coverId ?: return ""
val coverResponse = client.newCall(GET("$coverUrl/$coverId"))
.await().parseAs<CoverResponse>()
val fileName = coverResponse.data.attributes.fileName
return "$cdnUrl/covers/$dexId/$fileName"
}
suspend fun getCoversFromMangaList(mangaResponseList: List<MangaResponse>, client: OkHttpClient): Map<String, String> {
val idsAndCoverIds = mangaResponseList.mapNotNull { mangaResponse ->
val mangaId = mangaResponse.data.id
val coverId = mangaResponse.relationships.firstOrNull { relationship ->
relationship.type.equals("cover_art", true)
}?.id
if (coverId == null) {
null
} else {
Pair(mangaId, coverId)
}
}.toMap()
return runCatching {
getBatchCoverUrls(idsAndCoverIds, client)
}.getOrNull()!!
}
private suspend fun getBatchCoverUrls(ids: Map<String, String>, client: OkHttpClient): Map<String, String> {
try {
val url = coverUrl.toHttpUrl().newBuilder().apply {
ids.values.forEach { coverArtId ->
addQueryParameter("ids[]", coverArtId)
}
addQueryParameter("limit", ids.size.toString())
}.build().toString()
val coverList = client.newCall(GET(url)).await().parseAs<CoverListResponse>(jsonParser)
return coverList.results.map { coverResponse ->
val fileName = coverResponse.data.attributes.fileName
val mangaId = coverResponse.relationships.first { it.type.equals("manga", true) }.id
val thumbnailUrl = "$cdnUrl/covers/$mangaId/$fileName"
Pair(mangaId, thumbnailUrl)
}.toMap()
} catch (e: Exception) {
xLogE("Error getting covers", e)
throw e
}
}
fun getLoginBody(preferences: PreferencesHelper, mdList: MdList) = preferences.trackToken(mdList).get().nullIfBlank()?.let { fun getLoginBody(preferences: PreferencesHelper, mdList: MdList) = preferences.trackToken(mdList).get().nullIfBlank()?.let {
try { try {
jsonParser.decodeFromString<LoginBodyToken>(it) jsonParser.decodeFromString<LoginBodyToken>(it)