MangaGreat: Solve JS challenge (#17217)
* MangaGreat: Solve JS challenge * wording
This commit is contained in:
parent
bad7674de5
commit
41fe45de25
|
@ -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
|
||||
//
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in New Issue