* 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:
AwkwardPeak7 2023-06-29 20:14:43 +05:00 committed by GitHub
parent e429326371
commit 6a106c8648
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 436 additions and 65 deletions

View File

@ -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

View File

@ -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"

View File

@ -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")
} }

View File

@ -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"

View File

@ -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)