Mangago: Add random UA and mobile UA support (#4978)
* randomua * Revert "randomua" This reverts commit d7dab1d59099bb2a2ef32eab4249e2dd9c3b76c8. * mmmmmmm * reduce cache time * extract function
This commit is contained in:
parent
66244d5c2c
commit
606e70fc75
|
@ -1,7 +1,7 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'Mangago'
|
extName = 'Mangago'
|
||||||
extClass = '.Mangago'
|
extClass = '.Mangago'
|
||||||
extVersionCode = 16
|
extVersionCode = 17
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,4 +9,5 @@ apply from: "$rootDir/common.gradle"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(':lib:cryptoaes'))
|
implementation(project(':lib:cryptoaes'))
|
||||||
|
implementation(project(':lib:randomua'))
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,10 @@ import android.util.Base64
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import app.cash.quickjs.QuickJs
|
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.GET
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
|
@ -54,6 +58,10 @@ class Mangago : ParsedHttpSource(), ConfigurableSource {
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder()
|
override val client = network.cloudflareClient.newBuilder()
|
||||||
.rateLimit(1, 2)
|
.rateLimit(1, 2)
|
||||||
|
.setRandomUserAgent(
|
||||||
|
preferences.getPrefUAType(),
|
||||||
|
preferences.getPrefCustomUA(),
|
||||||
|
)
|
||||||
.addInterceptor { chain ->
|
.addInterceptor { chain ->
|
||||||
val request = chain.request()
|
val request = chain.request()
|
||||||
val response = chain.proceed(request)
|
val response = chain.proceed(request)
|
||||||
|
@ -209,6 +217,10 @@ class Mangago : ParsedHttpSource(), ConfigurableSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
|
if (!document.select("div.controls ul#dropdown-menu-page").isNullOrEmpty()) {
|
||||||
|
return pageListParseMobile(document)
|
||||||
|
}
|
||||||
|
|
||||||
val imgsrcsScript = document.selectFirst("script:containsData(imgsrcs)")?.html()
|
val imgsrcsScript = document.selectFirst("script:containsData(imgsrcs)")?.html()
|
||||||
?: throw Exception("Could not find imgsrcs")
|
?: throw Exception("Could not find imgsrcs")
|
||||||
val imgsrcRaw = imgSrcsRegex.find(imgsrcsScript)?.groupValues?.get(1)
|
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)
|
var imageList = cipher.doFinal(imgsrcs).toString(Charsets.UTF_8)
|
||||||
|
|
||||||
try {
|
imageList = unescrambleImageList(imageList, deobfChapterJs)
|
||||||
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.
|
|
||||||
}
|
|
||||||
|
|
||||||
val cols = colsRegex.find(deobfChapterJs)?.groupValues?.get(1) ?: ""
|
val cols = colsRegex.find(deobfChapterJs)?.groupValues?.get(1) ?: ""
|
||||||
|
|
||||||
|
@ -272,8 +267,56 @@ class Mangago : ParsedHttpSource(), ConfigurableSource {
|
||||||
return super.pageListRequest(chapter)
|
return super.pageListRequest(chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document): String =
|
private fun pageListParseMobile(document: Document): List<Page> {
|
||||||
throw UnsupportedOperationException()
|
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(
|
override fun getFilterList(): FilterList = FilterList(
|
||||||
Filter.Header("Ignored if using text search"),
|
Filter.Header("Ignored if using text search"),
|
||||||
|
@ -397,6 +440,29 @@ class Mangago : ParsedHttpSource(), ConfigurableSource {
|
||||||
return s
|
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 {
|
private fun unscrambleImage(image: InputStream, key: String, cols: Int): ByteArray {
|
||||||
val bitmap = BitmapFactory.decodeStream(image)
|
val bitmap = BitmapFactory.decodeStream(image)
|
||||||
|
|
||||||
|
@ -508,6 +574,7 @@ class Mangago : ParsedHttpSource(), ConfigurableSource {
|
||||||
"You might also want to clear the database in advanced settings."
|
"You might also want to clear the database in advanced settings."
|
||||||
setDefaultValue(false)
|
setDefaultValue(false)
|
||||||
}.let(screen::addPreference)
|
}.let(screen::addPreference)
|
||||||
|
addRandomUAPreferenceToScreen(screen)
|
||||||
}
|
}
|
||||||
companion object {
|
companion object {
|
||||||
private const val REMOVE_TITLE_VERSION_PREF = "REMOVE_TITLE_VERSION"
|
private const val REMOVE_TITLE_VERSION_PREF = "REMOVE_TITLE_VERSION"
|
||||||
|
|
Loading…
Reference in New Issue