Update Mangadex

This commit is contained in:
Jobobby04
2021-07-05 18:31:30 -04:00
parent efba76380a
commit 20d8cf6c10
36 changed files with 943 additions and 1200 deletions
@@ -11,6 +11,8 @@ enum class FollowStatus(val int: Int) {
DROPPED(5),
RE_READING(6);
fun toDex(): String = this.name.lowercase(Locale.US)
companion object {
fun fromDex(value: String?): FollowStatus = values().firstOrNull { it.name.lowercase(Locale.US) == value } ?: UNFOLLOWED
fun fromInt(value: Int): FollowStatus = values().firstOrNull { it.int == value } ?: UNFOLLOWED
+19
View File
@@ -0,0 +1,19 @@
package exh.md.utils
object MdApi {
const val baseUrl = "https://api.mangadex.org"
const val login = "$baseUrl/auth/login"
const val checkToken = "$baseUrl/auth/check"
const val refreshToken = "$baseUrl/auth/refresh"
const val logout = "$baseUrl/auth/logout"
const val manga = "$baseUrl/manga"
const val chapter = "$baseUrl/chapter"
const val group = "$baseUrl/group"
const val author = "$baseUrl/author"
const val chapterImageServer = "$baseUrl/at-home/server"
const val userFollows = "$baseUrl/user/follows/manga"
const val readingStatusForAllManga = "$baseUrl/manga/status"
const val atHomeServer = "$baseUrl/at-home/server"
const val legacyMapping = "$baseUrl/legacy/mapping"
}
@@ -0,0 +1,19 @@
package exh.md.utils
import exh.util.minutes
object MdConstants {
const val baseUrl = "https://mangadex.org"
const val cdnUrl = "https://uploads.mangadex.org"
const val atHomeReportUrl = "https://api.mangadex.network/report"
object Types {
const val author = "author"
const val artist = "artist"
const val coverArt = "cover_art"
const val manga = "manga"
const val scanlator = "scanlation_group"
}
val mdAtHomeTokenLifespan = 5.minutes.inWholeMilliseconds
}
@@ -0,0 +1,17 @@
package exh.md.utils
import exh.md.dto.ListCallDto
import exh.util.under
suspend fun <T> mdListCall(request: suspend (offset: Int) -> ListCallDto<T>): List<T> {
val results = mutableListOf<T>()
var offset = 0
do {
val list = request(offset)
results += list.results
offset += list.limit
} while (offset under list.total)
return results
}
+27 -120
View File
@@ -2,36 +2,26 @@ package exh.md.utils
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.mdlist.MdList
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.all.MangaDex
import exh.log.xLogD
import exh.log.xLogE
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.LoginBodyToken
import exh.md.handlers.serializers.MangaResponse
import exh.md.dto.LoginBodyTokenDto
import exh.md.dto.MangaDto
import exh.md.network.NoSessionException
import exh.source.getMainSource
import exh.util.floor
import exh.util.nullIfBlank
import exh.util.nullIfZero
import exh.util.under
import kotlinx.serialization.SerializationException
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.CacheControl
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.jsoup.parser.Parser
import tachiyomi.source.model.MangaInfo
import uy.kohesive.injekt.Injekt
@@ -45,35 +35,7 @@ class MdUtil {
companion object {
const val cdnUrl = "https://uploads.mangadex.org"
const val baseUrl = "https://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 atHomeUrl = "$apiUrl/at-home/server"
const val coverUrl = "$apiUrl/cover"
const val chapterUrl = "$apiUrl/chapter/"
const val chapterSuffix = "/chapter/"
const val checkTokenUrl = "$apiUrl/auth/check"
const val refreshTokenUrl = "$apiUrl/auth/refresh"
const val loginUrl = "$apiUrl/auth/login"
const val logoutUrl = "$apiUrl/auth/logout"
const val groupUrl = "$apiUrl/group"
const val authorUrl = "$apiUrl/author"
const val randomMangaUrl = "$apiUrl/manga/random"
const val mangaUrl = "$apiUrl/manga"
const val mangaStatus = "$apiUrl/manga/status"
const val userFollows = "$apiUrl/user/follows/manga"
fun updateReadingStatusUrl(id: String) = "$apiUrl/manga/$id/status"
fun mangaFeedUrl(id: String, offset: Int, language: String): String {
return "$mangaUrl/$id/feed".toHttpUrl().newBuilder().apply {
addQueryParameter("limit", "500")
addQueryParameter("offset", offset.toString())
addQueryParameter("translatedLanguage[]", language)
addQueryParameter("order[volume]", "desc")
addQueryParameter("order[chapter]", "desc")
}.build().toString()
}
fun coverUrl(mangaId: String, coverId: String) = "$apiUrl/cover?manga[]=$mangaId&ids[]=$coverId"
const val similarCacheMapping = "https://api.similarmanga.com/mapping/mdex2search.csv"
const val similarCacheMangas = "https://api.similarmanga.com/manga/"
@@ -83,7 +45,7 @@ class MdUtil {
const val reportUrl = "https://api.mangadex.network/report"
const val mdAtHomeTokenLifespan = 10 * 60 * 1000
const val mangaLimit = 25
const val mangaLimit = 20
/**
* Get the manga offset pages are 1 based, so subtract 1
@@ -217,10 +179,6 @@ class MdUtil {
return "/manga/$mangaUuid"
}
fun formThumbUrl(mangaUrl: String): String {
return "https://coverapi.orell.dev/api/v1/mdaltimage/manga/${getMangaId(mangaUrl)}/cover"
}
// Get the ID from the manga url
fun getMangaId(url: String): String = url.trimEnd('/').substringAfterLast("/")
@@ -298,76 +256,33 @@ class MdUtil {
return null
}
fun atHomeUrlHostUrl(requestUrl: String, client: OkHttpClient, headers: Headers, cacheControl: CacheControl): String {
val atHomeRequest = GET(requestUrl, headers, cache = cacheControl)
val atHomeResponse = client.newCall(atHomeRequest).execute()
return jsonParser.decodeFromString<AtHomeResponse>(atHomeResponse.body!!.string()).baseUrl
}
val dateFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss+SSS", Locale.US)
.apply { timeZone = TimeZone.getTimeZone("UTC") }
fun parseDate(dateAsString: String): Long =
dateFormatter.parse(dateAsString)?.time ?: 0
fun createMangaEntry(json: MangaResponse, lang: String, coverUrl: String?): MangaInfo {
fun createMangaEntry(json: MangaDto, lang: String): MangaInfo {
return MangaInfo(
key = buildMangaUrl(json.data.id),
title = cleanString(json.data.attributes.title[lang] ?: json.data.attributes.title["en"]!!),
cover = coverUrl.orEmpty()
cover = json.relationships
.firstOrNull { relationshipDto -> relationshipDto.type == MdConstants.Types.coverArt }
?.attributes
?.fileName
?.let { coverFileName ->
cdnCoverUrl(json.data.id, coverFileName)
}.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
fun cdnCoverUrl(dexId: String, fileName: String): String {
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 {
try {
jsonParser.decodeFromString<LoginBodyToken>(it)
jsonParser.decodeFromString<LoginBodyTokenDto>(it)
} catch (e: SerializationException) {
xLogD("Unable to load login body")
null
@@ -378,8 +293,10 @@ class MdUtil {
fun refreshToken(preferences: PreferencesHelper, mdList: MdList) = getLoginBody(preferences, mdList)?.refresh
fun updateLoginToken(token: LoginBodyToken, preferences: PreferencesHelper, mdList: MdList) {
preferences.trackToken(mdList).set(jsonParser.encodeToString(token))
fun updateLoginToken(token: LoginBodyTokenDto?, preferences: PreferencesHelper, mdList: MdList) {
if (token != null) {
preferences.trackToken(mdList).set(jsonParser.encodeToString(token))
} else preferences.trackToken(mdList).delete()
}
fun getAuthHeaders(headers: Headers, preferences: PreferencesHelper, mdList: MdList) =
@@ -400,27 +317,17 @@ class MdUtil {
val disabledSourceIds = preferences.disabledSources().get()
return sourceManager.getVisibleOnlineSources()
.asSequence()
.map { it.getMainSource() }
.filterIsInstance<MangaDex>()
.filter { it.lang in languages }
.filterNot { it.id.toString() in disabledSourceIds }
.toList()
}
inline fun <reified T> encodeToBody(body: T): RequestBody {
return jsonParser.encodeToString(body)
.toRequestBody("application/json".toMediaType())
}
}
}
suspend inline fun <reified T> OkHttpClient.mdListCall(request: (offset: Int) -> Request): List<T> {
val results = mutableListOf<T>()
var offset = 0
do {
val response = newCall(request(offset)).await()
if (response.code == 204) {
break
}
val mangaListResponse = response.parseAs<ListCallResponse<T>>(MdUtil.jsonParser)
results += mangaListResponse.results
offset += mangaListResponse.limit
} while (offset under mangaListResponse.total)
return results
}