Bato.to: Cleanup `pageListParse()` (#13047)
Also move to QuickJS The change was tested on https://bato.to/chapter/1 and many recent chapters
This commit is contained in:
parent
372fa06b85
commit
a83e04f237
|
@ -6,7 +6,7 @@ ext {
|
|||
extName = 'Bato.to'
|
||||
pkgNameSuffix = 'all.batoto'
|
||||
extClass = '.BatoToFactory'
|
||||
extVersionCode = 28
|
||||
extVersionCode = 29
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import android.content.SharedPreferences
|
|||
import androidx.preference.CheckBoxPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import com.squareup.duktape.Duktape
|
||||
import app.cash.quickjs.QuickJs
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
|
@ -95,49 +95,6 @@ open class BatoTo(
|
|||
private fun getMirrorPref(): String? = preferences.getString("${MIRROR_PREF_KEY}_$lang", MIRROR_PREF_DEFAULT_VALUE)
|
||||
private fun getAltChapterListPref(): Boolean = preferences.getBoolean("${ALT_CHAPTER_LIST_PREF_KEY}_$lang", ALT_CHAPTER_LIST_PREF_DEFAULT_VALUE)
|
||||
|
||||
companion object {
|
||||
private const val MIRROR_PREF_KEY = "MIRROR"
|
||||
private const val MIRROR_PREF_TITLE = "Mirror"
|
||||
private val MIRROR_PREF_ENTRIES = arrayOf(
|
||||
"bato.to",
|
||||
"batocc.com",
|
||||
"batotoo.com",
|
||||
"batotwo.com",
|
||||
"battwo.com",
|
||||
"comiko.net",
|
||||
"mangatoto.com",
|
||||
"mangatoto.net",
|
||||
"mangatoto.org",
|
||||
"mycordant.co.uk",
|
||||
"dto.to",
|
||||
"hto.to",
|
||||
"mto.to",
|
||||
"wto.to"
|
||||
)
|
||||
private val MIRROR_PREF_ENTRY_VALUES = arrayOf(
|
||||
"https://bato.to",
|
||||
"https://batocc.com",
|
||||
"https://batotoo.com",
|
||||
"https://batotwo.com",
|
||||
"https://battwo.com",
|
||||
"https://comiko.net",
|
||||
"https://mangatoto.com",
|
||||
"https://mangatoto.net",
|
||||
"https://mangatoto.org",
|
||||
"https://mycordant.co.uk",
|
||||
"https://dto.to",
|
||||
"https://hto.to",
|
||||
"https://mto.to",
|
||||
"https://wto.to"
|
||||
)
|
||||
private val MIRROR_PREF_DEFAULT_VALUE = MIRROR_PREF_ENTRY_VALUES[0]
|
||||
|
||||
private const val ALT_CHAPTER_LIST_PREF_KEY = "ALT_CHAPTER_LIST"
|
||||
private const val ALT_CHAPTER_LIST_PREF_TITLE = "Alternative Chapter List"
|
||||
private const val ALT_CHAPTER_LIST_PREF_SUMMARY = "If checked, uses an alternate chapter list"
|
||||
private const val ALT_CHAPTER_LIST_PREF_DEFAULT_VALUE = false
|
||||
}
|
||||
|
||||
override val supportsLatest = true
|
||||
private val json: Json by injectLazy()
|
||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||
|
@ -185,7 +142,7 @@ open class BatoTo(
|
|||
val id = query.substringAfter("ID:")
|
||||
client.newCall(GET("$baseUrl/series/$id", headers)).asObservableSuccess()
|
||||
.map { response ->
|
||||
queryIDParse(response, id)
|
||||
queryIDParse(response)
|
||||
}
|
||||
}
|
||||
query.isNotBlank() -> {
|
||||
|
@ -199,6 +156,7 @@ open class BatoTo(
|
|||
url.addQueryParameter("mode", "letter")
|
||||
}
|
||||
}
|
||||
else -> { /* Do Nothing */ }
|
||||
}
|
||||
}
|
||||
client.newCall(GET(url.build().toString(), headers)).asObservableSuccess()
|
||||
|
@ -263,6 +221,7 @@ open class BatoTo(
|
|||
}
|
||||
is MinChapterTextFilter -> min = filter.state
|
||||
is MaxChapterTextFilter -> max = filter.state
|
||||
else -> { /* Do Nothing */ }
|
||||
}
|
||||
}
|
||||
url.addQueryParameter("page", page.toString())
|
||||
|
@ -279,7 +238,7 @@ open class BatoTo(
|
|||
}
|
||||
}
|
||||
|
||||
private fun queryIDParse(response: Response, id: String): MangasPage {
|
||||
private fun queryIDParse(response: Response): MangasPage {
|
||||
val document = response.asJsoup()
|
||||
val infoElement = document.select("div#mainer div.container-fluid")
|
||||
val manga = SManga.create()
|
||||
|
@ -496,76 +455,26 @@ open class BatoTo(
|
|||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val pages = mutableListOf<Page>()
|
||||
val script = document.selectFirst("script:containsData(imgHttpLis):containsData(batoWord):containsData(batoPass)")?.html()
|
||||
?: throw RuntimeException("Couldn't find script with image data.")
|
||||
|
||||
val script = document.select("script").html()
|
||||
val imgHttpLisString = script.substringAfter("const imgHttpLis =").substringBefore(";").trim()
|
||||
val imgHttpLis = json.parseToJsonElement(imgHttpLisString).jsonArray.map { it.jsonPrimitive.content }
|
||||
val batoWord = script.substringAfter("const batoWord =").substringBefore(";").trim()
|
||||
val batoPass = script.substringAfter("const batoPass =").substringBefore(";").trim()
|
||||
|
||||
if (script.contains("var images =")) {
|
||||
/*
|
||||
* During kotlinx.serialization migration, the pre-existing code seemed to not work
|
||||
* Could not find a case where code would run in practice, so it was commented out.
|
||||
*/
|
||||
throw RuntimeException("Unexpected Branch: Please File A Bug Report describing this issue")
|
||||
// val imgJson = json.parseToJsonElement(script.substringAfter("var images = ").substringBefore(";")).jsonObject
|
||||
// imgJson.keys.forEachIndexed { i, s -> pages.add(Page(i, imageUrl = imgJson[s]!!.jsonPrimitive.content)) }
|
||||
} else if (script.contains("const server =")) { // bato.to
|
||||
val duktape = Duktape.create()
|
||||
val encryptedServer = script.substringAfter("const server = ").substringBefore(";")
|
||||
val batojs = duktape.evaluate(script.substringAfter("const batojs = ").substringBefore(";")).toString()
|
||||
val decryptScript = cryptoJS + "CryptoJS.AES.decrypt($encryptedServer, \"$batojs\").toString(CryptoJS.enc.Utf8);"
|
||||
val server = duktape.evaluate(decryptScript).toString().replace("\"", "")
|
||||
duktape.close()
|
||||
val decryptScript = cryptoJS + "CryptoJS.AES.decrypt($batoWord, $batoPass).toString(CryptoJS.enc.Utf8);"
|
||||
|
||||
json.parseToJsonElement(script.substringAfter("const images = ").substringBefore(";")).jsonArray
|
||||
.forEachIndexed { i, it ->
|
||||
val imgUrl = it.jsonPrimitive.content
|
||||
if (script.contains("bato.to/images")) {
|
||||
pages.add(Page(i, imageUrl = imgUrl))
|
||||
} else {
|
||||
pages.add(Page(i, imageUrl = if (server.startsWith("http")) "${server}$imgUrl" else "https:${server}$imgUrl"))
|
||||
}
|
||||
}
|
||||
} else if (script.contains("const imgHttpLis = ") && script.contains("const batoWord = ") && script.contains(
|
||||
"const batoPass = "
|
||||
)
|
||||
) {
|
||||
val duktape = Duktape.create()
|
||||
val imgHttpLis = json.parseToJsonElement(
|
||||
script.substringAfter("const imgHttpLis = ").substringBefore(";")
|
||||
).jsonArray
|
||||
val batoWord = script.substringAfter("const batoWord = ").substringBefore(";")
|
||||
val batoPass =
|
||||
duktape.evaluate(script.substringAfter("const batoPass = ").substringBefore(";"))
|
||||
.toString()
|
||||
val input =
|
||||
cryptoJS + "CryptoJS.AES.decrypt($batoWord, \"$batoPass\").toString(CryptoJS.enc.Utf8);"
|
||||
val imgWordLis = json.parseToJsonElement(duktape.evaluate(input).toString()).jsonArray
|
||||
duktape.close()
|
||||
val imgAccListString = QuickJs.create().use { it.evaluate(decryptScript).toString() }
|
||||
val imgAccList = json.parseToJsonElement(imgAccListString).jsonArray.map { it.jsonPrimitive.content }
|
||||
|
||||
if (imgHttpLis.size == imgWordLis.size) {
|
||||
imgHttpLis.forEachIndexed { i: Int, item ->
|
||||
val imageUrl =
|
||||
"${item.jsonPrimitive.content}?${imgWordLis.get(i).jsonPrimitive.content}"
|
||||
pages.add(
|
||||
Page(
|
||||
i,
|
||||
imageUrl = imageUrl
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return imgHttpLis.zip(imgAccList).mapIndexed { i, (imgUrl, imgAcc) ->
|
||||
Page(i, imageUrl = "$imgUrl?$imgAcc")
|
||||
}
|
||||
|
||||
return pages
|
||||
}
|
||||
|
||||
private val cryptoJS by lazy {
|
||||
client.newCall(
|
||||
GET(
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js",
|
||||
headers
|
||||
)
|
||||
).execute().body!!.string()
|
||||
client.newCall(GET(CryptoJSUrl, headers)).execute().body!!.string()
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
|
||||
|
@ -1001,4 +910,49 @@ open class BatoTo(
|
|||
CheckboxFilterOption("eu", "Basque"),
|
||||
CheckboxFilterOption("pt-PT", "Portuguese (Portugal)"),
|
||||
).filterNot { it.value == siteLang }
|
||||
|
||||
companion object {
|
||||
const val CryptoJSUrl = "https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"
|
||||
|
||||
private const val MIRROR_PREF_KEY = "MIRROR"
|
||||
private const val MIRROR_PREF_TITLE = "Mirror"
|
||||
private val MIRROR_PREF_ENTRIES = arrayOf(
|
||||
"bato.to",
|
||||
"batocc.com",
|
||||
"batotoo.com",
|
||||
"batotwo.com",
|
||||
"battwo.com",
|
||||
"comiko.net",
|
||||
"mangatoto.com",
|
||||
"mangatoto.net",
|
||||
"mangatoto.org",
|
||||
"mycordant.co.uk",
|
||||
"dto.to",
|
||||
"hto.to",
|
||||
"mto.to",
|
||||
"wto.to"
|
||||
)
|
||||
private val MIRROR_PREF_ENTRY_VALUES = arrayOf(
|
||||
"https://bato.to",
|
||||
"https://batocc.com",
|
||||
"https://batotoo.com",
|
||||
"https://batotwo.com",
|
||||
"https://battwo.com",
|
||||
"https://comiko.net",
|
||||
"https://mangatoto.com",
|
||||
"https://mangatoto.net",
|
||||
"https://mangatoto.org",
|
||||
"https://mycordant.co.uk",
|
||||
"https://dto.to",
|
||||
"https://hto.to",
|
||||
"https://mto.to",
|
||||
"https://wto.to"
|
||||
)
|
||||
private val MIRROR_PREF_DEFAULT_VALUE = MIRROR_PREF_ENTRY_VALUES[0]
|
||||
|
||||
private const val ALT_CHAPTER_LIST_PREF_KEY = "ALT_CHAPTER_LIST"
|
||||
private const val ALT_CHAPTER_LIST_PREF_TITLE = "Alternative Chapter List"
|
||||
private const val ALT_CHAPTER_LIST_PREF_SUMMARY = "If checked, uses an alternate chapter list"
|
||||
private const val ALT_CHAPTER_LIST_PREF_DEFAULT_VALUE = false
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue