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' extClass = '.YakshaScans'
themePkg = 'madara' themePkg = 'madara'
baseUrl = 'https://yakshascans.com' baseUrl = 'https://yakshascans.com'
overrideVersionCode = 1 overrideVersionCode = 2
isNsfw = true isNsfw = true
} }

View File

@ -22,33 +22,43 @@ class YakshaScans : Madara(
.addInterceptor(::jsChallengeInterceptor) .addInterceptor(::jsChallengeInterceptor)
.build() .build()
// Linked to src/pt/leitordemanga
private fun jsChallengeInterceptor(chain: Interceptor.Chain): Response { private fun jsChallengeInterceptor(chain: Interceptor.Chain): Response {
val request = chain.request() val response = chain.proceed(chain.request())
val origRes = chain.proceed(request) if (response.code != 403) {
if (origRes.code != 403) return origRes return response
origRes.close() }
response.close()
// Same delay as the source
Thread.sleep(3000L) Thread.sleep(3000L)
val token = fetchToken(chain).sha256() val token = fetchToken(chain).sha256()
val body = FormBody.Builder()
.add("challenge", token)
.build()
val body = FormBody.Builder().add("challenge", token).build() chain.proceed(POST("$baseUrl/hcdn-cgi/jschallenge-validate", headers, body))
val challengeReq = POST("$baseUrl/hcdn-cgi/jschallenge-validate", headers, body = body) .apply(Response::close)
.run {
val challengeResponse = chain.proceed(challengeReq) if (!isSuccessful) {
challengeResponse.close() throw IOException("Failed to bypass js challenge!")
if (challengeResponse.code != 200) throw IOException("Failed to bypass js challenge!") }
}
return chain.proceed(request) return chain.proceed(chain.request())
} }
private tailrec fun fetchToken(chain: Interceptor.Chain, attempt: Int = 0): String { private tailrec fun fetchToken(chain: Interceptor.Chain, attempt: Int = 0): String {
if (attempt > 5) throw IOException("Failed to fetch challenge token!") if (attempt > MAX_ATTEMPT) {
val request = GET("$baseUrl/hcdn-cgi/jschallenge", headers) throw IOException("Failed to fetch challenge token!")
val res = chain.proceed(request).body.string() }
return res.substringAfter("cjs = '").substringBefore("'") val response = chain.proceed(GET("$baseUrl/hcdn-cgi/jschallenge", headers))
.takeUnless { it == "nil" } ?: fetchToken(chain, attempt + 1) 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 { private fun String.sha256(): String {
@ -62,4 +72,9 @@ class YakshaScans : Madara(
override val mangaDetailsSelectorDescription: String = 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" "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' extClass = '.LeitorDeManga'
themePkg = 'madara' themePkg = 'madara'
baseUrl = 'https://leitordemanga.com' baseUrl = 'https://leitordemanga.com'
overrideVersionCode = 0 overrideVersionCode = 1
isNsfw = false isNsfw = false
} }

View File

@ -1,8 +1,15 @@
package eu.kanade.tachiyomi.extension.pt.leitordemanga package eu.kanade.tachiyomi.extension.pt.leitordemanga
import eu.kanade.tachiyomi.multisrc.madara.Madara 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 eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.FormBody
import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Response
import java.io.IOException
import java.security.MessageDigest
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -17,5 +24,57 @@ class LeitorDeManga : Madara(
override val client: OkHttpClient = super.client.newBuilder() override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(1, 2, TimeUnit.SECONDS) .rateLimit(1, 2, TimeUnit.SECONDS)
.addInterceptor(::jsChallengeInterceptor)
.build() .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()
}
} }