diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangahub/MangaHub.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangahub/MangaHub.kt index aecac9b6f..97e1ad134 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangahub/MangaHub.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangahub/MangaHub.kt @@ -56,42 +56,90 @@ abstract class MangaHub( } private fun apiAuthInterceptor(chain: Interceptor.Chain): Response { - val request = chain.request().also { request -> - parseApiKey(request).let { if (it.isNotEmpty()) cdnApiKey = it } - } + val originalRequest = chain.request() + parseApiKey(originalRequest).let { cdnApiKey = it } - if (request.url.toString() == "$cdnApiUrl/graphql" && cdnApiKey!!.isNotEmpty()) { - val newRequest = request.newBuilder() - .addHeader("x-mhub-access", cdnApiKey!!) + val request = if (originalRequest.url.toString() == "$cdnApiUrl/graphql") { + val newRequest = originalRequest.newBuilder() + .header("x-mhub-access", cdnApiKey!!) .build() - return chain.proceed(newRequest) + newRequest + } else { + originalRequest } - return chain.proceed(request).also { response -> - parseApiKey(response).let { if (it.isNotEmpty()) cdnApiKey = it } + val originalResponse = chain.proceed(request) + parseApiKey(originalResponse).let { cdnApiKey = it } + + return if (request.url.toString() == "$cdnApiUrl/graphql") { + var newResponse: Response? = null + runCatching { + val responseData = json + .decodeFromString<GraphQLDataDto<ChapterDto>>(originalResponse.peekBody(1 * 1024 * 1024).string()) + + if ( + responseData.errors?.isNotEmpty() == true || + responseData.data.chapter == null + ) { + val buffer = okio.Buffer() + request.body!!.writeTo(buffer) + val requestBody = buffer.readUtf8() + + val slug = PATTERN_SLUG.find(requestBody)?.groupValues?.get(1) + cdnApiKey = fetchCdnApiKey(slug) + + val newRequest = originalRequest.newBuilder() + .header("x-mhub-access", cdnApiKey!!) + .build() + + // Bypass rate limit for this request + newResponse = super.client.newCall(newRequest).execute() + } + } + newResponse ?: originalResponse + } else { + originalResponse } } private var cdnApiKey: String? = null - get() = field ?: client.newCall(GET(baseUrl, headers)).execute().let(::parseApiKey) + get() { + if (field == null) + field = fetchCdnApiKey(null) + return field + } + set(value) { + if (value == null) return + field = value + } - private fun parseApiKey(request: Request): String = + private fun fetchCdnApiKey(slug: String?): String? { + val request = if (slug == null) { + GET("$baseUrl/?reloadKey=1", headers) + } else { + GET("$baseUrl/manga/$slug?reloadKey=1", headers) + } + + // Bypass rate limit for this request + val response = super.client.newCall(request).execute() + return parseApiKey(response) + } + + private fun parseApiKey(request: Request): String? = request.header("Cookie") ?.split("; ") ?.mapNotNull { kv -> val (k, v) = kv.split('=') if (k == "mhub_access") v else null }?.firstOrNull() - ?: "" - private fun parseApiKey(response: Response): String = + private fun parseApiKey(response: Response): String? = response.headers("Set-Cookie") .mapNotNull { kv -> val (k, v) = kv.split("; ").first().split('=') if (k == "mhub_access") v else null }.firstOrNull() - ?: "" override fun headersBuilder(): Headers.Builder { val defaultUserAgent = super.headersBuilder()["User-Agent"] @@ -329,6 +377,13 @@ abstract class MangaHub( val chapterObject = json .decodeFromString<GraphQLDataDto<ChapterDto>>(response.body!!.string()) + if (chapterObject.errors != null) { + val errors = chapterObject.errors.joinToString("\n") { it.message } + throw Exception(errors) + } else if (chapterObject.data.chapter == null) { + throw Exception("Unknown error while fetching pages") + } + val pagesObject = json .decodeFromString<JsonObject>(chapterObject.data.chapter.pages) val pages = pagesObject.values.map { it.jsonPrimitive.content } @@ -448,17 +503,27 @@ abstract class MangaHub( Genre("Yaoi", "yaoi"), Genre("Yuri", "yuri") ) + + companion object { + private val PATTERN_SLUG = ",slug:\\\\\"([^\\\\]+)\\\\\",".toRegex() + } } // DTO @Serializable data class GraphQLDataDto<T>( + val errors: List<ErrorDto>? = null, val data: T ) +@Serializable +data class ErrorDto( + val message: String +) + @Serializable data class ChapterDto( - val chapter: ChapterInnerDto + val chapter: ChapterInnerDto? ) @Serializable diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangahub/MangaHubGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangahub/MangaHubGenerator.kt index 7eefb9f56..2441527f7 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangahub/MangaHubGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangahub/MangaHubGenerator.kt @@ -9,7 +9,7 @@ class MangaHubGenerator : ThemeSourceGenerator { override val themeClass = "MangaHub" - override val baseVersionCode: Int = 7 + override val baseVersionCode: Int = 8 override val sources = listOf( SingleLang("1Manga.co", "https://1manga.co", "en", isNsfw = true, className = "OneMangaCo"),