From 606e70fc759f3d2454eb95b25c90f3ba364d4d0d Mon Sep 17 00:00:00 2001 From: bapeey <90949336+bapeey@users.noreply.github.com> Date: Wed, 11 Sep 2024 00:12:12 -0500 Subject: [PATCH] Mangago: Add random UA and mobile UA support (#4978) * randomua * Revert "randomua" This reverts commit d7dab1d59099bb2a2ef32eab4249e2dd9c3b76c8. * mmmmmmm * reduce cache time * extract function --- src/en/mangago/build.gradle | 3 +- .../tachiyomi/extension/en/mangago/Mangago.kt | 107 ++++++++++++++---- 2 files changed, 89 insertions(+), 21 deletions(-) diff --git a/src/en/mangago/build.gradle b/src/en/mangago/build.gradle index 020c24b79..2ad4f0678 100644 --- a/src/en/mangago/build.gradle +++ b/src/en/mangago/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Mangago' extClass = '.Mangago' - extVersionCode = 16 + extVersionCode = 17 isNsfw = true } @@ -9,4 +9,5 @@ apply from: "$rootDir/common.gradle" dependencies { implementation(project(':lib:cryptoaes')) + implementation(project(':lib:randomua')) } diff --git a/src/en/mangago/src/eu/kanade/tachiyomi/extension/en/mangago/Mangago.kt b/src/en/mangago/src/eu/kanade/tachiyomi/extension/en/mangago/Mangago.kt index 9a221c383..b3d94a82e 100644 --- a/src/en/mangago/src/eu/kanade/tachiyomi/extension/en/mangago/Mangago.kt +++ b/src/en/mangago/src/eu/kanade/tachiyomi/extension/en/mangago/Mangago.kt @@ -10,6 +10,10 @@ import android.util.Base64 import androidx.preference.PreferenceScreen import androidx.preference.SwitchPreferenceCompat import app.cash.quickjs.QuickJs +import eu.kanade.tachiyomi.lib.randomua.addRandomUAPreferenceToScreen +import eu.kanade.tachiyomi.lib.randomua.getPrefCustomUA +import eu.kanade.tachiyomi.lib.randomua.getPrefUAType +import eu.kanade.tachiyomi.lib.randomua.setRandomUserAgent import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.source.ConfigurableSource @@ -54,6 +58,10 @@ class Mangago : ParsedHttpSource(), ConfigurableSource { override val client = network.cloudflareClient.newBuilder() .rateLimit(1, 2) + .setRandomUserAgent( + preferences.getPrefUAType(), + preferences.getPrefCustomUA(), + ) .addInterceptor { chain -> val request = chain.request() val response = chain.proceed(request) @@ -209,6 +217,10 @@ class Mangago : ParsedHttpSource(), ConfigurableSource { } override fun pageListParse(document: Document): List { + if (!document.select("div.controls ul#dropdown-menu-page").isNullOrEmpty()) { + return pageListParseMobile(document) + } + val imgsrcsScript = document.selectFirst("script:containsData(imgsrcs)")?.html() ?: throw Exception("Could not find imgsrcs") val imgsrcRaw = imgSrcsRegex.find(imgsrcsScript)?.groupValues?.get(1) @@ -231,24 +243,7 @@ class Mangago : ParsedHttpSource(), ConfigurableSource { var imageList = cipher.doFinal(imgsrcs).toString(Charsets.UTF_8) - try { - val keyLocations = keyLocationRegex.findAll(deobfChapterJs).map { - it.groupValues[1].toInt() - }.distinct() - - val unscrambleKey = keyLocations.map { - imageList[it].toString().toInt() - }.toList() - - keyLocations.forEachIndexed { idx, it -> - imageList = imageList.removeRange(it - idx..it - idx) - } - - imageList = imageList.unscramble(unscrambleKey) - } catch (e: NumberFormatException) { - // Only call where it should throw is imageList[it].toString().toInt(). - // This usually means that the list is already unscrambled. - } + imageList = unescrambleImageList(imageList, deobfChapterJs) val cols = colsRegex.find(deobfChapterJs)?.groupValues?.get(1) ?: "" @@ -272,8 +267,56 @@ class Mangago : ParsedHttpSource(), ConfigurableSource { return super.pageListRequest(chapter) } - override fun imageUrlParse(document: Document): String = - throw UnsupportedOperationException() + private fun pageListParseMobile(document: Document): List { + val pagesCount = document.select("div.controls ul#dropdown-menu-page li").size + val pageUrl = document.location().removeSuffix("/").substringBeforeLast("-") + return IntRange(1, pagesCount).map { Page(it, url = "$pageUrl-$it/") } + } + + private var cachedDeofChapterJS: String? = null + private var cachedKey: ByteArray? = null + private var cachedIv: ByteArray? = null + private var cachedTime: Long = 0 + private val maxCacheTime = 1000 * 60 * 5 // 5 minutes + + override fun imageUrlParse(document: Document): String { + val imgsrcsScript = document.selectFirst("script:containsData(imgsrcs)")?.html() + ?: throw Exception("Could not find imgsrcs") + val imgsrcRaw = imgSrcsRegex.find(imgsrcsScript)?.groupValues?.get(1) + ?: throw Exception("Could not extract imgsrcs") + val imgsrcs = Base64.decode(imgsrcRaw, Base64.DEFAULT) + val chapterJsUrl = document.getElementsByTag("script").first { + it.attr("src").contains("chapter.js", ignoreCase = true) + }.attr("abs:src") + + if (cachedDeofChapterJS == null || cachedKey == null || cachedIv == null || System.currentTimeMillis() - cachedTime > maxCacheTime) { + val obfuscatedChapterJs = client.newCall(GET(chapterJsUrl, headers)).execute().body.string() + cachedDeofChapterJS = SoJsonV4Deobfuscator.decode(obfuscatedChapterJs) + cachedKey = findHexEncodedVariable(cachedDeofChapterJS!!, "key").decodeHex() + cachedIv = findHexEncodedVariable(cachedDeofChapterJS!!, "iv").decodeHex() + cachedTime = System.currentTimeMillis() + } + + val cipher = Cipher.getInstance(hashCipher) + val keyS = SecretKeySpec(cachedKey, aes) + cipher.init(Cipher.DECRYPT_MODE, keyS, IvParameterSpec(cachedIv)) + + var imageList = cipher.doFinal(imgsrcs).toString(Charsets.UTF_8) + + imageList = unescrambleImageList(imageList, cachedDeofChapterJS!!) + + val cols = colsRegex.find(cachedDeofChapterJS!!)?.groupValues?.get(1) ?: "" + + val pageNumber = document.location().removeSuffix("/").substringAfterLast("-").toInt() + + return imageList.split(",")[pageNumber - 1].let { + if (it.contains("cspiclink")) { + "$it#desckey=${getDescramblingKey(cachedDeofChapterJS!!, it)}&cols=$cols" + } else { + it + } + } + } override fun getFilterList(): FilterList = FilterList( Filter.Header("Ignored if using text search"), @@ -397,6 +440,29 @@ class Mangago : ParsedHttpSource(), ConfigurableSource { return s } + private fun unescrambleImageList(imageList: String, js: String): String { + var imgList = imageList + try { + val keyLocations = keyLocationRegex.findAll(js).map { + it.groupValues[1].toInt() + }.distinct() + + val unscrambleKey = keyLocations.map { + imgList[it].toString().toInt() + }.toList() + + keyLocations.forEachIndexed { idx, it -> + imgList = imgList.removeRange(it - idx..it - idx) + } + + imgList = imgList.unscramble(unscrambleKey) + } catch (e: NumberFormatException) { + // Only call where it should throw is imageList[it].toString().toInt(). + // This usually means that the list is already unscrambled. + } + return imgList + } + private fun unscrambleImage(image: InputStream, key: String, cols: Int): ByteArray { val bitmap = BitmapFactory.decodeStream(image) @@ -508,6 +574,7 @@ class Mangago : ParsedHttpSource(), ConfigurableSource { "You might also want to clear the database in advanced settings." setDefaultValue(false) }.let(screen::addPreference) + addRandomUAPreferenceToScreen(screen) } companion object { private const val REMOVE_TITLE_VERSION_PREF = "REMOVE_TITLE_VERSION"