From 97557763535151d50f786c561b2b991bf697e339 Mon Sep 17 00:00:00 2001 From: Chopper <156493704+choppeh@users.noreply.github.com> Date: Mon, 28 Jul 2025 07:41:29 -0300 Subject: [PATCH] LeitorDeManga: Bypass jschallenge (#9804) * Fix 403 * Rewrite to an eligible tailrec statement * Bump version * Remove use function --- src/en/yakshascans/build.gradle | 2 +- .../extension/en/yakshascans/YakshaScans.kt | 51 ++++++++++------ src/pt/leitordemanga/build.gradle | 2 +- .../pt/leitordemanga/LeitorDeManga.kt | 59 +++++++++++++++++++ 4 files changed, 94 insertions(+), 20 deletions(-) diff --git a/src/en/yakshascans/build.gradle b/src/en/yakshascans/build.gradle index 20b668659..8174bbf3b 100644 --- a/src/en/yakshascans/build.gradle +++ b/src/en/yakshascans/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.YakshaScans' themePkg = 'madara' baseUrl = 'https://yakshascans.com' - overrideVersionCode = 1 + overrideVersionCode = 2 isNsfw = true } diff --git a/src/en/yakshascans/src/eu/kanade/tachiyomi/extension/en/yakshascans/YakshaScans.kt b/src/en/yakshascans/src/eu/kanade/tachiyomi/extension/en/yakshascans/YakshaScans.kt index d712f79e4..96a32a011 100644 --- a/src/en/yakshascans/src/eu/kanade/tachiyomi/extension/en/yakshascans/YakshaScans.kt +++ b/src/en/yakshascans/src/eu/kanade/tachiyomi/extension/en/yakshascans/YakshaScans.kt @@ -22,33 +22,43 @@ class YakshaScans : Madara( .addInterceptor(::jsChallengeInterceptor) .build() + // Linked to src/pt/leitordemanga private fun jsChallengeInterceptor(chain: Interceptor.Chain): Response { - val request = chain.request() - val origRes = chain.proceed(request) - if (origRes.code != 403) return origRes - origRes.close() + val response = chain.proceed(chain.request()) + if (response.code != 403) { + return response + } + response.close() - // Same delay as the source Thread.sleep(3000L) val token = fetchToken(chain).sha256() + val body = FormBody.Builder() + .add("challenge", token) + .build() - val body = FormBody.Builder().add("challenge", token).build() - val challengeReq = POST("$baseUrl/hcdn-cgi/jschallenge-validate", headers, body = body) - - val challengeResponse = chain.proceed(challengeReq) - challengeResponse.close() - if (challengeResponse.code != 200) throw IOException("Failed to bypass js challenge!") - - return chain.proceed(request) + chain.proceed(POST("$baseUrl/hcdn-cgi/jschallenge-validate", headers, body)) + .apply(Response::close) + .run { + if (!isSuccessful) { + throw IOException("Failed to bypass js challenge!") + } + } + return chain.proceed(chain.request()) } private tailrec fun fetchToken(chain: Interceptor.Chain, attempt: Int = 0): String { - if (attempt > 5) throw IOException("Failed to fetch challenge token!") - val request = GET("$baseUrl/hcdn-cgi/jschallenge", headers) - val res = chain.proceed(request).body.string() + if (attempt > MAX_ATTEMPT) { + throw IOException("Failed to fetch challenge token!") + } - return res.substringAfter("cjs = '").substringBefore("'") - .takeUnless { it == "nil" } ?: fetchToken(chain, attempt + 1) + val response = chain.proceed(GET("$baseUrl/hcdn-cgi/jschallenge", headers)) + val token = TOKEN_REGEX.find(response.body.string())?.groups?.get(1)?.value + + return if (token != null && token != "nil") { + token + } else { + fetchToken(chain, attempt + 1) + } } private fun String.sha256(): String { @@ -62,4 +72,9 @@ class YakshaScans : Madara( override val mangaDetailsSelectorDescription: String = "div.description-summary div.summary__content h3 + p, div.description-summary div.summary__content:not(:has(h3)), div.summary_content div.post-content_item > h5 + div, div.summary_content div.manga-excerpt" + + companion object { + private const val MAX_ATTEMPT = 5 + private val TOKEN_REGEX = """cjs[^']+'([^']+)""".toRegex() + } } diff --git a/src/pt/leitordemanga/build.gradle b/src/pt/leitordemanga/build.gradle index 9d770832c..f9d2b5e5c 100644 --- a/src/pt/leitordemanga/build.gradle +++ b/src/pt/leitordemanga/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.LeitorDeManga' themePkg = 'madara' baseUrl = 'https://leitordemanga.com' - overrideVersionCode = 0 + overrideVersionCode = 1 isNsfw = false } diff --git a/src/pt/leitordemanga/src/eu/kanade/tachiyomi/extension/pt/leitordemanga/LeitorDeManga.kt b/src/pt/leitordemanga/src/eu/kanade/tachiyomi/extension/pt/leitordemanga/LeitorDeManga.kt index b644a5c08..4d263f05f 100644 --- a/src/pt/leitordemanga/src/eu/kanade/tachiyomi/extension/pt/leitordemanga/LeitorDeManga.kt +++ b/src/pt/leitordemanga/src/eu/kanade/tachiyomi/extension/pt/leitordemanga/LeitorDeManga.kt @@ -1,8 +1,15 @@ package eu.kanade.tachiyomi.extension.pt.leitordemanga import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.FormBody +import okhttp3.Interceptor import okhttp3.OkHttpClient +import okhttp3.Response +import java.io.IOException +import java.security.MessageDigest import java.text.SimpleDateFormat import java.util.Locale import java.util.concurrent.TimeUnit @@ -17,5 +24,57 @@ class LeitorDeManga : Madara( override val client: OkHttpClient = super.client.newBuilder() .rateLimit(1, 2, TimeUnit.SECONDS) + .addInterceptor(::jsChallengeInterceptor) .build() + + // Linked to src/en/yakshascans + private fun jsChallengeInterceptor(chain: Interceptor.Chain): Response { + val response = chain.proceed(chain.request()) + if (response.code != 403) { + return response + } + response.close() + + Thread.sleep(3000L) + val token = fetchToken(chain).sha256() + val body = FormBody.Builder() + .add("challenge", token) + .build() + + chain.proceed(POST("$baseUrl/hcdn-cgi/jschallenge-validate", headers, body)) + .apply(Response::close) + .run { + if (!isSuccessful) { + throw IOException("Failed to bypass js challenge!") + } + } + return chain.proceed(chain.request()) + } + + private tailrec fun fetchToken(chain: Interceptor.Chain, attempt: Int = 0): String { + if (attempt > MAX_ATTEMPT) { + throw IOException("Failed to fetch challenge token!") + } + + val response = chain.proceed(GET("$baseUrl/hcdn-cgi/jschallenge", headers)) + val token = TOKEN_REGEX.find(response.body.string())?.groups?.get(1)?.value + + return if (token != null && token != "nil") { + token + } else { + fetchToken(chain, attempt + 1) + } + } + + private fun String.sha256(): String { + return MessageDigest + .getInstance("SHA-256") + .digest(toByteArray()) + .fold("", { str, it -> str + "%02x".format(it) }) + } + + companion object { + private const val MAX_ATTEMPT = 5 + private val TOKEN_REGEX = """cjs[^']+'([^']+)""".toRegex() + } }