fix RCO (#16900)
* lib-synchrony ported from aniyomiorg/aniyomi-extensions Co-authored-by: jmir1 <43830312+jmir1@users.noreply.github.com> * RCO: deobfuscate and extract beau function from rguard script * fix genre filter * bump --------- Co-authored-by: jmir1 <43830312+jmir1@users.noreply.github.com>
This commit is contained in:
parent
e429326371
commit
6a106c8648
|
@ -0,0 +1,22 @@
|
||||||
|
plugins {
|
||||||
|
id("com.android.library")
|
||||||
|
kotlin("android")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdk = AndroidConfig.compileSdk
|
||||||
|
namespace = "eu.kanade.tachiyomi.lib.synchrony"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = AndroidConfig.minSdk
|
||||||
|
targetSdk = AndroidConfig.targetSdk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly(libs.bundles.common)
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,43 @@
|
||||||
|
package eu.kanade.tachiyomi.lib.synchrony
|
||||||
|
|
||||||
|
import app.cash.quickjs.QuickJs
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to deobfuscate JavaScript strings with synchrony.
|
||||||
|
*/
|
||||||
|
object Deobfuscator {
|
||||||
|
fun deobfuscateScript(source: String): String? {
|
||||||
|
val originalScript = javaClass.getResource("/assets/$SCRIPT_NAME")
|
||||||
|
?.readText() ?: return null
|
||||||
|
|
||||||
|
// Sadly needed until QuickJS properly supports module imports:
|
||||||
|
// Regex for finding one and two in "export{one as Deobfuscator,two as Transformer};"
|
||||||
|
val regex = """export\{(.*) as Deobfuscator,(.*) as Transformer\};""".toRegex()
|
||||||
|
val synchronyScript = regex.find(originalScript)?.let { match ->
|
||||||
|
val (deob, trans) = match.destructured
|
||||||
|
val replacement = "const Deobfuscator = $deob, Transformer = $trans;"
|
||||||
|
originalScript.replace(match.value, replacement)
|
||||||
|
} ?: return null
|
||||||
|
|
||||||
|
return QuickJs.create().use { engine ->
|
||||||
|
engine.evaluate("globalThis.console = { log: () => {}, warn: () => {}, error: () => {}, trace: () => {} };")
|
||||||
|
engine.evaluate(synchronyScript)
|
||||||
|
|
||||||
|
engine.set(
|
||||||
|
"source", TestInterface::class.java,
|
||||||
|
object : TestInterface {
|
||||||
|
override fun getValue() = source
|
||||||
|
},
|
||||||
|
)
|
||||||
|
engine.evaluate("new Deobfuscator().deobfuscateSource(source.getValue())") as? String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
private interface TestInterface {
|
||||||
|
fun getValue(): String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update this when the script is updated!
|
||||||
|
private const val SCRIPT_NAME = "synchrony-v2.4.2.1.js"
|
|
@ -1,6 +1,6 @@
|
||||||
include(":core")
|
include(":core")
|
||||||
|
|
||||||
listOf("dataimage", "unpacker", "cryptoaes", "textinterceptor").forEach {
|
listOf("dataimage", "unpacker", "cryptoaes", "textinterceptor", "synchrony").forEach {
|
||||||
include(":lib-$it")
|
include(":lib-$it")
|
||||||
project(":lib-$it").projectDir = File("lib/$it")
|
project(":lib-$it").projectDir = File("lib/$it")
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,11 @@ ext {
|
||||||
extName = 'ReadComicOnline'
|
extName = 'ReadComicOnline'
|
||||||
pkgNameSuffix = 'en.readcomiconline'
|
pkgNameSuffix = 'en.readcomiconline'
|
||||||
extClass = '.Readcomiconline'
|
extClass = '.Readcomiconline'
|
||||||
extVersionCode = 14
|
extVersionCode = 15
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":lib-synchrony"))
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -3,8 +3,8 @@ package eu.kanade.tachiyomi.extension.en.readcomiconline
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import app.cash.quickjs.QuickJs
|
import app.cash.quickjs.QuickJs
|
||||||
|
import eu.kanade.tachiyomi.lib.synchrony.Deobfuscator
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
@ -16,8 +16,8 @@ import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.encodeToJsonElement
|
import kotlinx.serialization.json.encodeToJsonElement
|
||||||
import okhttp3.CacheControl
|
import okhttp3.CacheControl
|
||||||
import okhttp3.FormBody
|
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
@ -100,19 +100,22 @@ class Readcomiconline : ConfigurableSource, ParsedHttpSource() {
|
||||||
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val form = FormBody.Builder().apply {
|
val url = "$baseUrl/AdvanceSearch".toHttpUrl().newBuilder().apply {
|
||||||
add("comicName", query)
|
addQueryParameter("comicName", query.trim())
|
||||||
add("page", page.toString())
|
addQueryParameter("page", page.toString())
|
||||||
|
|
||||||
for (filter in if (filters.isEmpty()) getFilterList() else filters) {
|
for (filter in if (filters.isEmpty()) getFilterList() else filters) {
|
||||||
when (filter) {
|
when (filter) {
|
||||||
is Status -> add("status", arrayOf("", "Completed", "Ongoing")[filter.state])
|
is Status -> addQueryParameter("status", arrayOf("", "Completed", "Ongoing")[filter.state])
|
||||||
is GenreList -> filter.state.forEach { genre -> add("genres", genre.state.toString()) }
|
is GenreList -> {
|
||||||
|
addQueryParameter("ig", filter.included.joinToString(","))
|
||||||
|
addQueryParameter("eg", filter.excluded.joinToString(","))
|
||||||
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}.build()
|
||||||
return POST("$baseUrl/AdvanceSearch", headers, form.build())
|
|
||||||
|
return GET(url, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaSelector() = popularMangaSelector()
|
override fun searchMangaSelector() = popularMangaSelector()
|
||||||
|
@ -192,8 +195,14 @@ class Readcomiconline : ConfigurableSource, ParsedHttpSource() {
|
||||||
override fun imageUrlParse(document: Document) = ""
|
override fun imageUrlParse(document: Document) = ""
|
||||||
|
|
||||||
private class Status : Filter.TriState("Completed")
|
private class Status : Filter.TriState("Completed")
|
||||||
private class Genre(name: String) : Filter.TriState(name)
|
private class Genre(name: String, val gid: String) : Filter.TriState(name)
|
||||||
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
|
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) {
|
||||||
|
val included: List<String>
|
||||||
|
get() = state.filter { it.isIncluded() }.map { it.gid }
|
||||||
|
|
||||||
|
val excluded: List<String>
|
||||||
|
get() = state.filter { it.isExcluded() }.map { it.gid }
|
||||||
|
}
|
||||||
|
|
||||||
override fun getFilterList() = FilterList(
|
override fun getFilterList() = FilterList(
|
||||||
Status(),
|
Status(),
|
||||||
|
@ -203,54 +212,55 @@ class Readcomiconline : ConfigurableSource, ParsedHttpSource() {
|
||||||
// $("select[name=\"genres\"]").map((i,el) => `Genre("${$(el).next().text().trim()}", ${i})`).get().join(',\n')
|
// $("select[name=\"genres\"]").map((i,el) => `Genre("${$(el).next().text().trim()}", ${i})`).get().join(',\n')
|
||||||
// on https://readcomiconline.li/AdvanceSearch
|
// on https://readcomiconline.li/AdvanceSearch
|
||||||
private fun getGenreList() = listOf(
|
private fun getGenreList() = listOf(
|
||||||
Genre("Action"),
|
Genre("Action", "1"),
|
||||||
Genre("Adventure"),
|
Genre("Adventure", "2"),
|
||||||
Genre("Anthology"),
|
Genre("Anthology", "38"),
|
||||||
Genre("Anthropomorphic"),
|
Genre("Anthropomorphic", "46"),
|
||||||
Genre("Biography"),
|
Genre("Biography", "41"),
|
||||||
Genre("Children"),
|
Genre("Children", "49"),
|
||||||
Genre("Comedy"),
|
Genre("Comedy", "3"),
|
||||||
Genre("Crime"),
|
Genre("Crime", "17"),
|
||||||
Genre("Drama"),
|
Genre("Drama", "19"),
|
||||||
Genre("Family"),
|
Genre("Family", "25"),
|
||||||
Genre("Fantasy"),
|
Genre("Fantasy", "20"),
|
||||||
Genre("Fighting"),
|
Genre("Fighting", "31"),
|
||||||
Genre("Graphic Novels"),
|
Genre("Graphic Novels", "5"),
|
||||||
Genre("Historical"),
|
Genre("Historical", "28"),
|
||||||
Genre("Horror"),
|
Genre("Horror", "15"),
|
||||||
Genre("Leading Ladies"),
|
Genre("Leading Ladies", "35"),
|
||||||
Genre("LGBTQ"),
|
Genre("LGBTQ", "51"),
|
||||||
Genre("Literature"),
|
Genre("Literature", "44"),
|
||||||
Genre("Manga"),
|
Genre("Manga", "40"),
|
||||||
Genre("Martial Arts"),
|
Genre("Martial Arts", "4"),
|
||||||
Genre("Mature"),
|
Genre("Mature", "8"),
|
||||||
Genre("Military"),
|
Genre("Military", "33"),
|
||||||
Genre("Movies & TV"),
|
Genre("Mini-Series", "56"),
|
||||||
Genre("Music"),
|
Genre("Movies & TV", "47"),
|
||||||
Genre("Mystery"),
|
Genre("Music", "55"),
|
||||||
Genre("Mythology"),
|
Genre("Mystery", "23"),
|
||||||
Genre("Personal"),
|
Genre("Mythology", "21"),
|
||||||
Genre("Political"),
|
Genre("Personal", "48"),
|
||||||
Genre("Post-Apocalyptic"),
|
Genre("Political", "42"),
|
||||||
Genre("Psychological"),
|
Genre("Post-Apocalyptic", "43"),
|
||||||
Genre("Pulp"),
|
Genre("Psychological", "27"),
|
||||||
Genre("Religious"),
|
Genre("Pulp", "39"),
|
||||||
Genre("Robots"),
|
Genre("Religious", "53"),
|
||||||
Genre("Romance"),
|
Genre("Robots", "9"),
|
||||||
Genre("School Life"),
|
Genre("Romance", "32"),
|
||||||
Genre("Sci-Fi"),
|
Genre("School Life", "52"),
|
||||||
Genre("Slice of Life"),
|
Genre("Sci-Fi", "16"),
|
||||||
Genre("Sport"),
|
Genre("Slice of Life", "50"),
|
||||||
Genre("Spy"),
|
Genre("Sport", "54"),
|
||||||
Genre("Superhero"),
|
Genre("Spy", "30"),
|
||||||
Genre("Supernatural"),
|
Genre("Superhero", "22"),
|
||||||
Genre("Suspense"),
|
Genre("Supernatural", "24"),
|
||||||
Genre("Thriller"),
|
Genre("Suspense", "29"),
|
||||||
Genre("Vampires"),
|
Genre("Thriller", "18"),
|
||||||
Genre("Video Games"),
|
Genre("Vampires", "34"),
|
||||||
Genre("War"),
|
Genre("Video Games", "37"),
|
||||||
Genre("Western"),
|
Genre("War", "26"),
|
||||||
Genre("Zombies"),
|
Genre("Western", "45"),
|
||||||
|
Genre("Zombies", "36"),
|
||||||
)
|
)
|
||||||
// Preferences Code
|
// Preferences Code
|
||||||
|
|
||||||
|
@ -287,11 +297,14 @@ class Readcomiconline : ConfigurableSource, ParsedHttpSource() {
|
||||||
val scriptResponse = client.newCall(scriptRequest).execute()
|
val scriptResponse = client.newCall(scriptRequest).execute()
|
||||||
val scriptBody = scriptResponse.body.string()
|
val scriptBody = scriptResponse.body.string()
|
||||||
|
|
||||||
val scriptParts = RGUARD_REGEX.find(scriptBody)?.groupValues?.drop(1)
|
val deobfuscatedBody = Deobfuscator.deobfuscateScript(scriptBody)
|
||||||
|
?: throw Exception("Unable to de-obfuscate rguard script")
|
||||||
|
|
||||||
|
val beauFunc = RGUARD_REGEX.find(deobfuscatedBody)?.groupValues
|
||||||
?: throw Exception("Unable to parse rguard script")
|
?: throw Exception("Unable to parse rguard script")
|
||||||
|
|
||||||
QuickJs.create().use {
|
QuickJs.create().use {
|
||||||
it.compile(scriptParts.joinToString("") + ATOB_SCRIPT, "?")
|
it.compile(beauFunc.joinToString("") + ATOB_SCRIPT, "?")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,7 +327,7 @@ class Readcomiconline : ConfigurableSource, ParsedHttpSource() {
|
||||||
private const val QUALITY_PREF = "qualitypref"
|
private const val QUALITY_PREF = "qualitypref"
|
||||||
|
|
||||||
private val CHAPTER_IMAGES_REGEX = "lstImages\\.push\\([\"'](.*)[\"']\\)".toRegex()
|
private val CHAPTER_IMAGES_REGEX = "lstImages\\.push\\([\"'](.*)[\"']\\)".toRegex()
|
||||||
private val RGUARD_REGEX = "(^.+?)var \\w=\\(function\\(\\).+?;(function \\w+.+?\\})if.+?(function beau.+)".toRegex()
|
private val RGUARD_REGEX = Regex("""(function beau[\s\S]*\})""")
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The MIT License (MIT)
|
* The MIT License (MIT)
|
||||||
|
|
Loading…
Reference in New Issue