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'
|
extName = 'Bato.to'
|
||||||
pkgNameSuffix = 'all.batoto'
|
pkgNameSuffix = 'all.batoto'
|
||||||
extClass = '.BatoToFactory'
|
extClass = '.BatoToFactory'
|
||||||
extVersionCode = 28
|
extVersionCode = 29
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import android.content.SharedPreferences
|
||||||
import androidx.preference.CheckBoxPreference
|
import androidx.preference.CheckBoxPreference
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
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.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
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 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)
|
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
|
override val supportsLatest = true
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||||
|
@ -185,7 +142,7 @@ open class BatoTo(
|
||||||
val id = query.substringAfter("ID:")
|
val id = query.substringAfter("ID:")
|
||||||
client.newCall(GET("$baseUrl/series/$id", headers)).asObservableSuccess()
|
client.newCall(GET("$baseUrl/series/$id", headers)).asObservableSuccess()
|
||||||
.map { response ->
|
.map { response ->
|
||||||
queryIDParse(response, id)
|
queryIDParse(response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
query.isNotBlank() -> {
|
query.isNotBlank() -> {
|
||||||
|
@ -199,6 +156,7 @@ open class BatoTo(
|
||||||
url.addQueryParameter("mode", "letter")
|
url.addQueryParameter("mode", "letter")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else -> { /* Do Nothing */ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
client.newCall(GET(url.build().toString(), headers)).asObservableSuccess()
|
client.newCall(GET(url.build().toString(), headers)).asObservableSuccess()
|
||||||
|
@ -263,6 +221,7 @@ open class BatoTo(
|
||||||
}
|
}
|
||||||
is MinChapterTextFilter -> min = filter.state
|
is MinChapterTextFilter -> min = filter.state
|
||||||
is MaxChapterTextFilter -> max = filter.state
|
is MaxChapterTextFilter -> max = filter.state
|
||||||
|
else -> { /* Do Nothing */ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
url.addQueryParameter("page", page.toString())
|
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 document = response.asJsoup()
|
||||||
val infoElement = document.select("div#mainer div.container-fluid")
|
val infoElement = document.select("div#mainer div.container-fluid")
|
||||||
val manga = SManga.create()
|
val manga = SManga.create()
|
||||||
|
@ -496,76 +455,26 @@ open class BatoTo(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
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 =")) {
|
val decryptScript = cryptoJS + "CryptoJS.AES.decrypt($batoWord, $batoPass).toString(CryptoJS.enc.Utf8);"
|
||||||
/*
|
|
||||||
* 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()
|
|
||||||
|
|
||||||
json.parseToJsonElement(script.substringAfter("const images = ").substringBefore(";")).jsonArray
|
val imgAccListString = QuickJs.create().use { it.evaluate(decryptScript).toString() }
|
||||||
.forEachIndexed { i, it ->
|
val imgAccList = json.parseToJsonElement(imgAccListString).jsonArray.map { it.jsonPrimitive.content }
|
||||||
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()
|
|
||||||
|
|
||||||
if (imgHttpLis.size == imgWordLis.size) {
|
return imgHttpLis.zip(imgAccList).mapIndexed { i, (imgUrl, imgAcc) ->
|
||||||
imgHttpLis.forEachIndexed { i: Int, item ->
|
Page(i, imageUrl = "$imgUrl?$imgAcc")
|
||||||
val imageUrl =
|
|
||||||
"${item.jsonPrimitive.content}?${imgWordLis.get(i).jsonPrimitive.content}"
|
|
||||||
pages.add(
|
|
||||||
Page(
|
|
||||||
i,
|
|
||||||
imageUrl = imageUrl
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return pages
|
|
||||||
}
|
|
||||||
|
|
||||||
private val cryptoJS by lazy {
|
private val cryptoJS by lazy {
|
||||||
client.newCall(
|
client.newCall(GET(CryptoJSUrl, headers)).execute().body!!.string()
|
||||||
GET(
|
|
||||||
"https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js",
|
|
||||||
headers
|
|
||||||
)
|
|
||||||
).execute().body!!.string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
|
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
|
||||||
|
@ -1001,4 +910,49 @@ open class BatoTo(
|
||||||
CheckboxFilterOption("eu", "Basque"),
|
CheckboxFilterOption("eu", "Basque"),
|
||||||
CheckboxFilterOption("pt-PT", "Portuguese (Portugal)"),
|
CheckboxFilterOption("pt-PT", "Portuguese (Portugal)"),
|
||||||
).filterNot { it.value == siteLang }
|
).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