LeitorDeManga: Bypass jschallenge (#9804)

* Fix 403

* Rewrite to an eligible tailrec statement

* Bump version

* Remove use function
This commit is contained in:
Chopper 2025-07-28 07:41:29 -03:00 committed by Draff
parent 9840ff97d5
commit 9755776353
Signed by: Draff
GPG Key ID: E8A89F3211677653
4 changed files with 94 additions and 20 deletions

View File

@ -3,7 +3,7 @@ ext {
extClass = '.YakshaScans'
themePkg = 'madara'
baseUrl = 'https://yakshascans.com'
overrideVersionCode = 1
overrideVersionCode = 2
isNsfw = true
}

View File

@ -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()
}
}

View File

@ -3,7 +3,7 @@ ext {
extClass = '.LeitorDeManga'
themePkg = 'madara'
baseUrl = 'https://leitordemanga.com'
overrideVersionCode = 0
overrideVersionCode = 1
isNsfw = false
}

View File

@ -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()
}
}