This commit is contained in:
Horis
2025-05-22 19:39:01 +08:00
parent 7960ed9a06
commit 53dea64715
14 changed files with 171 additions and 61 deletions

View File

@@ -12,13 +12,24 @@ import android.os.Build
import com.github.liuyueyi.quick.transfer.constants.TransType
import com.jeremyliao.liveeventbus.LiveEventBus
import com.jeremyliao.liveeventbus.logger.DefaultLogger
import com.script.rhino.ReadOnlyJavaObject
import com.script.rhino.RhinoScriptEngine
import com.script.rhino.RhinoWrapFactory
import io.legado.app.base.AppContextWrapper
import io.legado.app.constant.AppConst.channelIdDownload
import io.legado.app.constant.AppConst.channelIdReadAloud
import io.legado.app.constant.AppConst.channelIdWeb
import io.legado.app.constant.PreferKey
import io.legado.app.data.appDb
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.HttpTTS
import io.legado.app.data.entities.RssSource
import io.legado.app.data.entities.rule.BookInfoRule
import io.legado.app.data.entities.rule.ContentRule
import io.legado.app.data.entities.rule.ExploreRule
import io.legado.app.data.entities.rule.SearchRule
import io.legado.app.help.AppFreezeMonitor
import io.legado.app.help.AppWebDav
import io.legado.app.help.CrashHandler
@@ -33,6 +44,7 @@ import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.http.Cronet
import io.legado.app.help.http.ObsoleteUrlFactory
import io.legado.app.help.http.okHttpClient
import io.legado.app.help.rhino.NativeBaseSource
import io.legado.app.help.source.SourceHelp
import io.legado.app.help.storage.Backup
import io.legado.app.model.BookCover
@@ -79,7 +91,7 @@ class App : Application() {
AppFreezeMonitor.init(this@App)
URL.setURLStreamHandlerFactory(ObsoleteUrlFactory(okHttpClient))
launch { installGmsTlsProvider(appCtx) }
RhinoScriptEngine
initRhino()
//初始化封面
BookCover.toString()
//清除过期数据
@@ -200,6 +212,19 @@ class App : Application() {
)
}
private fun initRhino() {
RhinoScriptEngine
RhinoWrapFactory.register(BookSource::class.java, NativeBaseSource.factory)
RhinoWrapFactory.register(RssSource::class.java, NativeBaseSource.factory)
RhinoWrapFactory.register(HttpTTS::class.java, NativeBaseSource.factory)
RhinoWrapFactory.register(ExploreRule::class.java, ReadOnlyJavaObject.factory)
RhinoWrapFactory.register(SearchRule::class.java, ReadOnlyJavaObject.factory)
RhinoWrapFactory.register(BookInfoRule::class.java, ReadOnlyJavaObject.factory)
RhinoWrapFactory.register(ContentRule::class.java, ReadOnlyJavaObject.factory)
RhinoWrapFactory.register(BookChapter::class.java, ReadOnlyJavaObject.factory)
RhinoWrapFactory.register(Book.ReadConfig::class.java, ReadOnlyJavaObject.factory)
}
class EventLogger : DefaultLogger() {
override fun log(level: Level, msg: String) {

View File

@@ -12,7 +12,6 @@ import io.legado.app.help.JsExtensions
import io.legado.app.help.config.AppConfig
import io.legado.app.help.crypto.SymmetricCryptoAndroid
import io.legado.app.help.http.CookieStore
import io.legado.app.help.source.copy
import io.legado.app.help.source.getShareScope
import io.legado.app.model.Debug
import io.legado.app.utils.GSON
@@ -63,7 +62,7 @@ interface BaseSource : JsExtensions {
fun getKey(): String
override fun getSource(): BaseSource? {
return copy()
return this
}
fun loginUi(): List<RowUi>? {
@@ -240,10 +239,9 @@ interface BaseSource : JsExtensions {
*/
@Throws(Exception::class)
fun evalJS(jsStr: String, bindingsConfig: ScriptBindings.() -> Unit = {}): Any? {
val sourceCopy = copy()
val bindings = buildScriptBindings { bindings ->
bindings["java"] = sourceCopy
bindings["source"] = sourceCopy
bindings["java"] = this
bindings["source"] = this
bindings["baseUrl"] = getKey()
bindings["cookie"] = CookieStore
bindings["cache"] = CacheManager

View File

@@ -1,8 +1,11 @@
package io.legado.app.help.crypto
import androidx.annotation.Keep
import cn.hutool.core.codec.Base64
import cn.hutool.core.util.HexUtil
import cn.hutool.crypto.symmetric.SymmetricCrypto
import io.legado.app.utils.EncoderUtils
import io.legado.app.utils.isHex
import java.io.InputStream
import java.nio.charset.Charset
@@ -32,4 +35,13 @@ class SymmetricCryptoAndroid(
return EncoderUtils.base64Encode(encrypt(data))
}
override fun decrypt(data: String): ByteArray {
val bytes = if (data.isHex()) {
HexUtil.decodeHex(data)
} else {
Base64.decode(data)
}
return decrypt(bytes)
}
}

View File

@@ -0,0 +1,46 @@
package io.legado.app.help.rhino
import com.script.rhino.JavaObjectWrapFactory
import org.mozilla.javascript.NativeJavaObject
import org.mozilla.javascript.Scriptable
class NativeBaseSource(scope: Scriptable?, javaObject: Any, staticType: Class<*>?) :
NativeJavaObject(scope, javaObject, staticType) {
override fun has(name: String, start: Scriptable): Boolean {
if (name != "setVariable" && name.length > 3 && name.startsWith("set")) {
val name = name.substring(3).replaceFirstChar { it.lowercase() }
if (super.has(name, start)) {
return false
}
}
return super.has(name, start)
}
override fun get(name: String, start: Scriptable): Any? {
if (name != "setVariable" && name.length > 3 && name.startsWith("set")) {
val name = name.substring(3).replaceFirstChar { it.lowercase() }
if (super.has(name, start)) {
return NOT_FOUND
}
}
return super.get(name, start)
}
override fun put(
name: String,
start: Scriptable,
value: Any?
) {
if (name == "variable") {
super.put(name, start, value)
}
}
companion object {
val factory = JavaObjectWrapFactory { scope, javaObject, staticType ->
NativeBaseSource(scope, javaObject, staticType)
}
}
}

View File

@@ -3,7 +3,6 @@ package io.legado.app.help.source
import io.legado.app.constant.SourceType
import io.legado.app.data.entities.BaseSource
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.HttpTTS
import io.legado.app.data.entities.RssSource
import io.legado.app.model.SharedJsScope
import org.mozilla.javascript.Scriptable
@@ -20,18 +19,3 @@ fun BaseSource.getSourceType(): Int {
else -> error("unknown source type: ${this::class.simpleName}.")
}
}
fun BaseSource.copy(): BaseSource {
return when (this) {
is BookSource -> copy(
ruleExplore = ruleExplore?.copy(),
ruleSearch = ruleSearch?.copy(),
ruleBookInfo = ruleBookInfo?.copy(),
ruleToc = ruleToc?.copy()
)
is RssSource -> copy()
is HttpTTS -> copy()
else -> error("unknown copy source type: ${this::class.simpleName}.")
}
}

View File

@@ -16,7 +16,6 @@ import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.CacheManager
import io.legado.app.help.JsExtensions
import io.legado.app.help.http.CookieStore
import io.legado.app.help.source.copy
import io.legado.app.help.source.getShareScope
import io.legado.app.model.Debug
import io.legado.app.model.webBook.WebBook
@@ -30,7 +29,6 @@ import io.legado.app.utils.isJson
import io.legado.app.utils.printOnDebug
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.stackTraceStr
import io.legado.app.utils.updateVariableTo
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
@@ -777,15 +775,15 @@ class AnalyzeRule(
bindings["java"] = this
bindings["cookie"] = CookieStore
bindings["cache"] = CacheManager
bindings["source"] = source?.copy()
bindings["source"] = source
bindings["book"] = book
bindings["result"] = result
bindings["baseUrl"] = baseUrl
bindings["chapter"] = chapterCopy
bindings["title"] = chapterCopy?.title
bindings["chapter"] = chapter
bindings["title"] = chapter?.title
bindings["src"] = content
bindings["nextChapterUrl"] = nextChapterUrl
bindings["rssArticle"] = rssArticleCopy
bindings["rssArticle"] = rssArticle
}
val topScope = source?.getShareScope(coroutineContext) ?: topScopeRef?.get()
val scope = if (topScope == null) {
@@ -801,19 +799,9 @@ class AnalyzeRule(
}
val script = compileScriptCache(jsStr)
val result = script.eval(scope, coroutineContext)
updateVariable(chapterCopy, rssArticleCopy)
return result
}
private fun updateVariable(chapterCopy: BookChapter?, rssArticleCopy: RssArticle?) {
chapter?.let {
chapterCopy?.updateVariableTo(it)
}
rssArticle?.let {
rssArticleCopy?.updateVariableTo(it)
}
}
private fun compileScriptCache(jsStr: String): CompiledScript {
return scriptCache.getOrPutLimit(jsStr, 16) {
RhinoScriptEngine.compile(jsStr)
@@ -821,7 +809,7 @@ class AnalyzeRule(
}
override fun getSource(): BaseSource? {
return source?.copy()
return source
}
/**

View File

@@ -38,7 +38,6 @@ import io.legado.app.help.http.newCallStrResponse
import io.legado.app.help.http.postForm
import io.legado.app.help.http.postJson
import io.legado.app.help.http.postMultipart
import io.legado.app.help.source.copy
import io.legado.app.help.source.getShareScope
import io.legado.app.model.Debug
import io.legado.app.utils.EncoderUtils
@@ -357,7 +356,7 @@ class AnalyzeUrl(
bindings["speakText"] = speakText
bindings["speakSpeed"] = speakSpeed
bindings["book"] = ruleData as? Book
bindings["source"] = source?.copy()
bindings["source"] = source
bindings["result"] = result
}
val sharedScope = source?.getShareScope(coroutineContext)
@@ -660,7 +659,7 @@ class AnalyzeUrl(
}
override fun getSource(): BaseSource? {
return source?.copy()
return source
}
companion object {

View File

@@ -10,7 +10,7 @@ import io.legado.app.utils.showDialogFragment
class RssJsExtensions(private val activity: ReadRssActivity) : JsExtensions {
override fun getSource(): BaseSource? {
return activity.getSource()?.copy()
return activity.getSource()
}
fun searchBook(key: String) {

View File

@@ -6,10 +6,3 @@ fun BookChapter.internString() {
title = title.intern()
bookUrl = bookUrl.intern()
}
fun BookChapter.updateVariableTo(chapter: BookChapter) {
if (variable != chapter.variable) {
chapter.variableMap.clear()
chapter.variableMap.putAll(variableMap)
}
}

View File

@@ -1,10 +0,0 @@
package io.legado.app.utils
import io.legado.app.data.entities.RssArticle
fun RssArticle.updateVariableTo(rssArticle: RssArticle) {
if (variable != rssArticle.variable) {
rssArticle.variableMap.clear()
rssArticle.variableMap.putAll(variableMap)
}
}

View File

@@ -78,6 +78,12 @@ fun String?.isTrue(nullIsTrue: Boolean = false): Boolean {
return !this.trim().matches("(?i)^(false|no|not|0)$".toRegex())
}
fun String.isHex(): Boolean {
return all {c ->
c in '0'..'9' || c in 'A'..'F' || c in 'a'..'f'
}
}
fun String.splitNotBlank(vararg delimiter: String, limit: Int = 0): Array<String> = run {
this.split(*delimiter, limit = limit).map { it.trim() }.filterNot { it.isBlank() }
.toTypedArray()

View File

@@ -0,0 +1,9 @@
package com.script.rhino
import org.mozilla.javascript.Scriptable
fun interface JavaObjectWrapFactory {
fun wrap(scope: Scriptable?, javaObject: Any, staticType: Class<*>?): Scriptable
}

View File

@@ -0,0 +1,43 @@
package com.script.rhino
import org.mozilla.javascript.NativeJavaObject
import org.mozilla.javascript.Scriptable
class ReadOnlyJavaObject(scope: Scriptable?, javaObject: Any, staticType: Class<*>?) :
NativeJavaObject(scope, javaObject, staticType) {
override fun has(name: String, start: Scriptable): Boolean {
if (name.length > 3 && name.startsWith("set")) {
val name = name.substring(3).replaceFirstChar { it.lowercase() }
if (super.has(name, start)) {
return false
}
}
return super.has(name, start)
}
override fun get(name: String, start: Scriptable): Any? {
if (name.length > 3 && name.startsWith("set")) {
val name = name.substring(3).replaceFirstChar { it.lowercase() }
if (super.has(name, start)) {
return NOT_FOUND
}
}
return super.get(name, start)
}
override fun put(
name: String?,
start: Scriptable?,
value: Any?
) {
// do nothing
}
companion object {
val factory = JavaObjectWrapFactory { scope, javaObject, staticType ->
ReadOnlyJavaObject(scope, javaObject, staticType)
}
}
}

View File

@@ -45,6 +45,8 @@ import org.mozilla.javascript.WrapFactory
*/
object RhinoWrapFactory : WrapFactory() {
private val factories = hashMapOf<Class<*>, JavaObjectWrapFactory>()
override fun wrapAsJavaObject(
cx: Context,
scope: Scriptable?,
@@ -54,7 +56,8 @@ object RhinoWrapFactory : WrapFactory() {
if (!RhinoClassShutter.visibleToScripts(javaObject)) {
return null
}
return super.wrapAsJavaObject(cx, scope, javaObject, staticType)
return wrapOrNull(scope, javaObject, staticType)
?: super.wrapAsJavaObject(cx, scope, javaObject, staticType)
}
override fun wrapJavaClass(
@@ -71,4 +74,18 @@ object RhinoWrapFactory : WrapFactory() {
return RhinoClassShutter.wrapJavaClass(scope, javaClass)
}
private fun wrapOrNull(
scope: Scriptable?,
javaObject: Any,
staticType: Class<*>?
): Scriptable? {
return factories[javaObject.javaClass]?.wrap(scope, javaObject, staticType)
}
fun register(clazz: Class<*>, factory: JavaObjectWrapFactory) {
if (!factories.contains(clazz)) {
factories.put(clazz, factory)
}
}
}