MangaGreat: Solve JS challenge (#17217)

* MangaGreat: Solve JS challenge

* wording
This commit is contained in:
Vetle Ledaal 2023-07-22 21:59:55 +02:00 committed by GitHub
parent bad7674de5
commit 41fe45de25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 118 additions and 1 deletions

View File

@ -1,9 +1,126 @@
package eu.kanade.tachiyomi.extension.en.mangagreat
import android.util.Base64
import app.cash.quickjs.QuickJs
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Cookie
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import java.io.IOException
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class MangaGreat : Madara("MangaGreat", "https://mangagreat.com", "en") {
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(::JSChallengeInterceptor)
.build()
// The website does not flag the content.
override val filterNonMangaItems = false
// /manga/page/1/ redirects to /manga/
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
//
// JS Challenge logic start
//
@Suppress("FunctionName")
private fun JSChallengeInterceptor(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
if (response.code != 202) return response
val url = request.url
Cookie.parse(url, getJSChallengeCookie(response))
?.let { client.cookieJar.saveFromResponse(url, listOf(it)) }
?: throw IOException("Failed JavaScript challenge. Check WebView.")
return client.newCall(request).execute()
}
private fun getJSChallengeCookie(response: Response): String {
val document = response.asJsoup()
val jsPatch = ";function atob(a){return base64.atob(a)};document.cookie"
val jsPayload = document.select("script")
.joinToString("\n") { it.data() }
.trimIndent() + jsPatch
val fauxBase64 = FauxBase64()
val fauxLocation = FauxLocation()
val fauxDocument = FauxDocument()
val slowAES = FauxSlowAES()
QuickJs.create().use { context ->
context.set("base64", FauxBase64Interface::class.java, fauxBase64)
context.set("location", FauxLocationInterface::class.java, fauxLocation)
context.set("document", FauxDocumentInterface::class.java, fauxDocument)
context.set("slowAES", FauxSlowAESInterface::class.java, slowAES)
return context.evaluate(jsPayload) as String
}
}
class FauxBase64 : FauxBase64Interface {
override fun atob(base64: String): String {
return String(Base64.decode(base64, Base64.DEFAULT))
}
}
class FauxLocation : FauxLocationInterface {
override var href: String = ""
}
class FauxDocument : FauxDocumentInterface {
override var cookie: String = ""
}
class FauxSlowAES : FauxSlowAESInterface {
private fun Array<Int>.toByteArray(): ByteArray {
return map { it.toByte() }.toByteArray()
}
private fun ByteArray.toTypedArray(): Array<Int> {
return map { it.toInt() and 0xFF }.toTypedArray()
}
override fun decrypt(cipherIn: Array<Int>, mode: Int, key: Array<Int>, iv: Array<Int>): Array<Int> {
val modeStr = when (mode) {
0 -> "OFB"
1 -> "CFB"
else -> "CBC" // 2 = CBC, 3+ = unknown
}
val cipher = Cipher.getInstance("AES/$modeStr/NoPadding")
val keyS = SecretKeySpec(key.toByteArray(), "AES")
cipher.init(Cipher.DECRYPT_MODE, keyS, IvParameterSpec(iv.toByteArray()))
return cipher.doFinal(cipherIn.toByteArray()).toTypedArray()
}
}
interface FauxBase64Interface {
fun atob(base64: String): String
}
interface FauxLocationInterface {
val href: String
}
private interface FauxDocumentInterface {
val cookie: String
}
@Suppress("unused")
private interface FauxSlowAESInterface {
fun decrypt(cipherIn: Array<Int>, mode: Int, key: Array<Int>, iv: Array<Int>): Array<Int>
}
//
// JS Challenge logic end
//
}

View File

@ -237,7 +237,7 @@ class MadaraGenerator : ThemeSourceGenerator {
SingleLang("MangaFoxFull", "https://mangafoxfull.com", "en"),
SingleLang("MangaFreak.online", "https://mangafreak.online", "en", className = "MangaFreakOnline"),
SingleLang("MangaGG", "https://mangagg.com", "en", overrideVersionCode = 2),
SingleLang("MangaGreat", "https://mangagreat.com", "en", overrideVersionCode = 3),
SingleLang("MangaGreat", "https://mangagreat.com", "en", overrideVersionCode = 4),
SingleLang("MangaHub.fr", "https://mangahub.fr", "fr", isNsfw = true, className = "MangaHubFr", pkgName = "mangahubfr"),
SingleLang("MangaHZ", "https://www.mangahz.com", "en", isNsfw = true, overrideVersionCode = 2),
SingleLang("MangaK2", "https://mangak2.com", "en", isNsfw = true),