[新增] 正则分段标题以及更多Sigma分支功能(@Luoyacheng)

This commit is contained in:
HapeLee
2026-01-29 00:59:02 +08:00
parent b6a1814555
commit a76c25e450
12 changed files with 290 additions and 143 deletions
@@ -76,7 +76,6 @@ import java.util.zip.ZipInputStream
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
/** /**
* js扩展类, 在js中通过java变量调用 * js扩展类, 在js中通过java变量调用
* 添加方法,请更新文档/legado/app/src/main/assets/help/JsHelp.md * 添加方法,请更新文档/legado/app/src/main/assets/help/JsHelp.md
@@ -128,7 +127,6 @@ interface JsExtensions : JsEncodeUtils {
fun ajaxAll(urlList: Array<String>): Array<StrResponse> { fun ajaxAll(urlList: Array<String>): Array<StrResponse> {
return ajaxAll(urlList, false) return ajaxAll(urlList, false)
} }
fun ajaxAll(urlList: Array<String>, skipRateLimit: Boolean): Array<StrResponse> { fun ajaxAll(urlList: Array<String>, skipRateLimit: Boolean): Array<StrResponse> {
return runBlocking(context) { return runBlocking(context) {
urlList.asFlow().mapAsync(AppConfig.threadCount) { url -> urlList.asFlow().mapAsync(AppConfig.threadCount) { url ->
@@ -148,7 +146,6 @@ interface JsExtensions : JsEncodeUtils {
fun ajaxTestAll(urlList: Array<String>, timeout: Int): Array<StrResponse> { fun ajaxTestAll(urlList: Array<String>, timeout: Int): Array<StrResponse> {
return ajaxTestAll(urlList, timeout, false) return ajaxTestAll(urlList, timeout, false)
} }
fun ajaxTestAll( fun ajaxTestAll(
urlList: Array<String>, urlList: Array<String>,
timeout: Int, timeout: Int,
@@ -238,7 +235,17 @@ interface JsExtensions : JsEncodeUtils {
} }
fun webViewGetSource(html: String?, url: String?, js: String?, sourceRegex: String): String? { fun webViewGetSource(html: String?, url: String?, js: String?, sourceRegex: String): String? {
return webViewGetSource(html, url, js, sourceRegex, false) return webViewGetSource(html, url, js, sourceRegex, false, 0)
}
fun webViewGetSource(
html: String?,
url: String?,
js: String?,
sourceRegex: String,
cacheFirst: Boolean
): String? {
return webViewGetSource(html, url, js, sourceRegex, cacheFirst, 0)
} }
/** /**
@@ -249,7 +256,8 @@ interface JsExtensions : JsEncodeUtils {
url: String?, url: String?,
js: String?, js: String?,
sourceRegex: String, sourceRegex: String,
cacheFirst: Boolean cacheFirst: Boolean,
delayTime: Long
): String? { ): String? {
if (isMainThread) { if (isMainThread) {
error("webViewGetSource must be called on a background thread") error("webViewGetSource must be called on a background thread")
@@ -261,7 +269,8 @@ interface JsExtensions : JsEncodeUtils {
javaScript = js, javaScript = js,
headerMap = getSource()?.getHeaderMap(true), headerMap = getSource()?.getHeaderMap(true),
tag = getSource()?.getKey(), tag = getSource()?.getKey(),
sourceRegex = sourceRegex sourceRegex = sourceRegex,
delayTime = delayTime
).getStrResponse().body ).getStrResponse().body
} }
} }
@@ -272,7 +281,17 @@ interface JsExtensions : JsEncodeUtils {
js: String?, js: String?,
overrideUrlRegex: String overrideUrlRegex: String
): String? { ): String? {
return webViewGetOverrideUrl(html, url, js, overrideUrlRegex, false) return webViewGetOverrideUrl(html, url, js, overrideUrlRegex, false, 0)
}
fun webViewGetOverrideUrl(
html: String?,
url: String?,
js: String?,
overrideUrlRegex: String,
cacheFirst: Boolean
): String? {
return webViewGetOverrideUrl(html, url, js, overrideUrlRegex, cacheFirst, 0)
} }
/** /**
@@ -283,7 +302,8 @@ interface JsExtensions : JsEncodeUtils {
url: String?, url: String?,
js: String?, js: String?,
overrideUrlRegex: String, overrideUrlRegex: String,
cacheFirst: Boolean cacheFirst: Boolean,
delayTime: Long
): String? { ): String? {
if (isMainThread) { if (isMainThread) {
error("webViewGetOverrideUrl must be called on a background thread") error("webViewGetOverrideUrl must be called on a background thread")
@@ -295,11 +315,23 @@ interface JsExtensions : JsEncodeUtils {
javaScript = js, javaScript = js,
headerMap = getSource()?.getHeaderMap(true), headerMap = getSource()?.getHeaderMap(true),
tag = getSource()?.getKey(), tag = getSource()?.getKey(),
overrideUrlRegex = overrideUrlRegex overrideUrlRegex = overrideUrlRegex,
delayTime = delayTime
).getStrResponse().body ).getStrResponse().body
} }
} }
/**
* 打开内置视频播放器
* @param url 视频播放链接
* @param title 视频的标题
* @param float 是否悬浮窗打开
*/
@JavascriptInterface
fun openVideoPlayer(url: String, title: String, float: Boolean) {
//SourceHelp.openVideoPlayer(getSource(), url, title, float)
}
/** /**
* 使用内置浏览器打开链接,手动验证网站防爬 * 使用内置浏览器打开链接,手动验证网站防爬
* @param url 要打开的链接 * @param url 要打开的链接
@@ -1164,9 +1196,9 @@ interface JsExtensions : JsEncodeUtils {
return GSON.toJson(ReadBookConfig.durConfig) return GSON.toJson(ReadBookConfig.durConfig)
} }
/* fun getReadBookConfigMap(): Map<String, Any> { //fun getReadBookConfigMap(): Map<String, Any> {
return ReadBookConfig.durConfig.toMap() // return ReadBookConfig.durConfig.toMap()
} */ //}
/** /**
* 获取主题模式 * 获取主题模式
@@ -1179,14 +1211,14 @@ interface JsExtensions : JsEncodeUtils {
/** /**
* 获取主题配置 * 获取主题配置
*/ */
//@JavascriptInterface // @JavascriptInterface
/*fun getThemeConfig(): String { //fun getThemeConfig(): String {
val themeConfig = ThemeConfig.getDurConfig(appCtx) // val themeConfig = ThemeConfig.getDurConfig(appCtx)
return GSON.toJson(themeConfig) // return GSON.toJson(themeConfig)
}*/ //}
/* fun getThemeConfigMap(): Map<String, Any?> { //fun getThemeConfigMap(): Map<String, Any?> {
return ThemeConfig.getDurConfig(appCtx).toMap() // return ThemeConfig.getDurConfig(appCtx).toMap()
} */ //}
} }
@@ -19,15 +19,27 @@ import io.legado.app.ui.rss.read.RssJsExtensions
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.escapeForJs import io.legado.app.utils.escapeForJs
import io.legado.app.utils.fromJsonObject import io.legado.app.utils.fromJsonObject
import java.lang.ref.WeakReference
import java.util.UUID import java.util.UUID
@Suppress("unused") @Suppress("unused")
class WebJsExtensions( class WebJsExtensions(
source: BaseSource, source: BaseSource, activity: AppCompatActivity,
activity: AppCompatActivity,
private val webView: WebView, private val webView: WebView,
private val bookType: Int = 0 private val bookType: Int = 0,
callback: Callback? = null
) : RssJsExtensions(activity, source) { ) : RssJsExtensions(activity, source) {
private val callbackRef: WeakReference<Callback> = WeakReference(callback)
interface Callback {
fun upConfig(config: String)
}
@JavascriptInterface
fun upConfig(config: String) {
callbackRef.get()?.upConfig(config)
}
private val bookAndChapter by lazy { private val bookAndChapter by lazy {
var book: Book? = null var book: Book? = null
var chapter: BookChapter? = null var chapter: BookChapter? = null
@@ -64,35 +76,37 @@ class WebJsExtensions(
setChapter(chapter) setChapter(chapter)
evalJS(jsParam[0]).toString() evalJS(jsParam[0]).toString()
} }
"ajaxAwait" -> { "ajaxAwait" -> {
ajax(jsParam[0], jsParam[1].toIntOrNull()).toString() ajax(jsParam[0], jsParam[1].toIntOrNull()).toString()
} }
"connectAwait" -> { "connectAwait" -> {
connect(jsParam[0], jsParam[1], jsParam[2].toIntOrNull()) connect(jsParam[0], jsParam[1], jsParam[2].toIntOrNull())
} }
"getAwait" -> { "getAwait" -> {
get(jsParam[0], jsParam[1], jsParam[2].toIntOrNull()) get(jsParam[0], jsParam[1], jsParam[2].toIntOrNull())
} }
"headAwait" -> { "headAwait" -> {
head(jsParam[0], jsParam[1], jsParam[2].toIntOrNull()) head(jsParam[0], jsParam[1], jsParam[2].toIntOrNull())
} }
"postAwait" -> { "postAwait" -> {
post(jsParam[0], jsParam[1], jsParam[2], jsParam[3].toIntOrNull()) post(jsParam[0], jsParam[1], jsParam[2], jsParam[3].toIntOrNull())
} }
"webViewAwait" -> { "webViewAwait" -> {
webView(jsParam[0], jsParam[1], jsParam[2], jsParam[3].toBoolean()).toString() webView(jsParam[0], jsParam[1], jsParam[2], jsParam[3].toBoolean()).toString()
} }
"webViewGetSourceAwait" -> {
webViewGetSource(
jsParam[0],
jsParam[1],
jsParam[2],
jsParam[3],
jsParam[4].toBoolean(),
jsParam[5].toLongOrNull() ?: 0
).toString()
}
"decryptStrAwait" -> { "decryptStrAwait" -> {
createSymmetricCrypto(jsParam[0], jsParam[1], jsParam[2]).decryptStr(jsParam[3]) createSymmetricCrypto(jsParam[0], jsParam[1], jsParam[2]).decryptStr(jsParam[3])
} }
"encryptBase64Await" -> { "encryptBase64Await" -> {
createSymmetricCrypto( createSymmetricCrypto(
jsParam[0], jsParam[0],
@@ -100,34 +114,27 @@ class WebJsExtensions(
jsParam[2] jsParam[2]
).encryptBase64(jsParam[3]) ).encryptBase64(jsParam[3])
} }
"encryptHexAwait" -> { "encryptHexAwait" -> {
createSymmetricCrypto(jsParam[0], jsParam[1], jsParam[2]).encryptHex(jsParam[3]) createSymmetricCrypto(jsParam[0], jsParam[1], jsParam[2]).encryptHex(jsParam[3])
} }
"createSignHexAwait" -> { "createSignHexAwait" -> {
createSign(jsParam[0]).setPublicKey(jsParam[1]).setPrivateKey(jsParam[2]) createSign(jsParam[0]).setPublicKey(jsParam[1]).setPrivateKey(jsParam[2])
.signHex(jsParam[3]) .signHex(jsParam[3])
} }
"downloadFileAwait" -> { "downloadFileAwait" -> {
downloadFile(jsParam[0]) downloadFile(jsParam[0])
} }
"readTxtFileAwait" -> { "readTxtFileAwait" -> {
readTxtFile(jsParam[0]) readTxtFile(jsParam[0])
} }
"importScriptAwait" -> { "importScriptAwait" -> {
importScript(jsParam[0]) importScript(jsParam[0])
} }
"getStringAwait" -> AnalyzeRule(book, getSource()).run { "getStringAwait" -> AnalyzeRule(book, getSource()).run {
setCoroutineContext(coroutineContext) setCoroutineContext(coroutineContext)
setChapter(chapter) setChapter(chapter)
getString(jsParam[0], jsParam[1]) getString(jsParam[0], jsParam[1])
} }
else -> "error funName" else -> "error funName"
} }
}.onSuccess { data -> }.onSuccess { data ->
@@ -142,78 +149,64 @@ class WebJsExtensions(
) )
} }
} }
@JavascriptInterface @JavascriptInterface
fun toast(msg: String?) { fun toast(msg: String?) {
super.toast(msg) super.toast(msg)
} }
@JavascriptInterface @JavascriptInterface
fun longToast(msg: String?) { fun longToast(msg: String?) {
super.longToast(msg) super.longToast(msg)
} }
@JavascriptInterface @JavascriptInterface
fun log(msg: String?): String { fun log(msg: String?): String {
return super.log(msg).toString() return super.log(msg).toString()
} }
@JavascriptInterface @JavascriptInterface
fun ajax(url: String): String? { fun ajax(url: String): String? {
return super.ajax(url, 9000) return super.ajax(url, 9000)
} }
@JavascriptInterface @JavascriptInterface
fun ajax(url: String, callTimeout: Int?): String? { fun ajax(url: String, callTimeout: Int?): String? {
return super.ajax(url, callTimeout?.toLong()) return super.ajax(url, callTimeout?.toLong())
} }
@JavascriptInterface @JavascriptInterface
fun connect(urlStr: String?): String { fun connect(urlStr: String?): String {
if (urlStr.isNullOrEmpty()) return "error empty url" if (urlStr.isNullOrEmpty()) return "error empty url"
return super.connect(urlStr, null, 9000).toString() return super.connect(urlStr, null, 9000).toString()
} }
@JavascriptInterface @JavascriptInterface
fun connect(urlStr: String, header: String): String { fun connect(urlStr: String, header: String): String {
return super.connect(urlStr, header, 9000).toString() return super.connect(urlStr, header, 9000).toString()
} }
@JavascriptInterface @JavascriptInterface
fun connect(urlStr: String, header: String, callTimeout: Int?): String { fun connect(urlStr: String, header: String, callTimeout: Int?): String {
return super.connect(urlStr, header, callTimeout?.toLong()).toString() return super.connect(urlStr, header, callTimeout?.toLong()).toString()
} }
@JavascriptInterface @JavascriptInterface
fun get(urlStr: String, headers: String): String { fun get(urlStr: String, headers: String): String {
val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap() val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap()
return super.get(urlStr, headerMap, 9000).body() return super.get(urlStr, headerMap, 9000).body()
} }
@JavascriptInterface @JavascriptInterface
fun get(urlStr: String, headers: String, timeout: Int?): String { fun get(urlStr: String, headers: String, timeout: Int?): String {
val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap() val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap()
return super.get(urlStr, headerMap, timeout).body() return super.get(urlStr, headerMap, timeout).body()
} }
@JavascriptInterface @JavascriptInterface
fun post(urlStr: String, body: String, headers: String): String { fun post(urlStr: String, body: String, headers: String): String {
val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap() val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap()
return super.post(urlStr, body, headerMap, 9000).body() return super.post(urlStr, body, headerMap, 9000).body()
} }
@JavascriptInterface @JavascriptInterface
fun post(urlStr: String, body: String, headers: String, timeout: Int?): String { fun post(urlStr: String, body: String, headers: String, timeout: Int?): String {
val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap() val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap()
return super.post(urlStr, body, headerMap, timeout).body() return super.post(urlStr, body, headerMap, timeout).body()
} }
@JavascriptInterface @JavascriptInterface
fun head(urlStr: String, headers: String): String { fun head(urlStr: String, headers: String): String {
val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap() val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap()
return GSON.toJson(super.head(urlStr, headerMap, 9000).headers()) return GSON.toJson(super.head(urlStr, headerMap, 9000).headers())
} }
@JavascriptInterface @JavascriptInterface
fun head(urlStr: String, headers: String, timeout: Int?): String { fun head(urlStr: String, headers: String, timeout: Int?): String {
val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap() val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap()
@@ -222,127 +215,146 @@ class WebJsExtensions(
companion object { companion object {
private fun getRandomLetter(): Char { private fun getRandomLetter(): Char {
val letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_" val letters = "abcdefghijklmnopqrstuvwxyz"
return letters.random() return letters.random()
} }
val uuid by lazy { UUID.randomUUID().toString().split("-") } val uuid by lazy {
val uuid2 by lazy { UUID.randomUUID().toString().split("-") } UUID.randomUUID().toString().replace('-', getRandomLetter()).chunked(6)
val nameJava by lazy { getRandomLetter() + uuid[0] + uuid[1] } }
val nameCache by lazy { getRandomLetter() + uuid[2] + uuid[3] } val uuid2 by lazy {
val nameSource by lazy { getRandomLetter() + uuid[4] } UUID.randomUUID().toString().replace('-', getRandomLetter()).chunked(6)
val nameBasic by lazy { getRandomLetter() + uuid2[1] + uuid2[2] } }
val JSBridgeResult by lazy { getRandomLetter() + uuid2[3] + uuid2[4] } val nameUrl by lazy { "https://" + uuid[0] + ".com/" + uuid2[0] + ".js" }
val nameJava by lazy { getRandomLetter() + uuid[1] + uuid2[1] }
val nameCache by lazy { getRandomLetter() + uuid[2] + uuid2[2] }
val nameSource by lazy { getRandomLetter() + uuid[3] + uuid2[3] }
val nameBasic by lazy { getRandomLetter() + uuid[4] + uuid2[4] }
val JSBridgeResult by lazy { getRandomLetter() + uuid[5] + uuid2[5] }
val JS_URL by lazy {
"<script src=\"$nameUrl\"></script>"
}
val JS_INJECTION by lazy { val JS_INJECTION by lazy {
""" """
const requestId = n => 'req_' + n + '_' + Date.now() + '_' + Math.random().toString(36).slice(-3); const requestId = n => 'req_' + n + '_' + Date.now() + '_' + Math.random().toString(36).slice(-3);
const JSBridgeCallbacks = {}; const JSBridgeCallbacks = {};
const java = window.$nameJava; const java = window.$nameJava;
delete window.$nameJava;
const source = window.$nameSource; const source = window.$nameSource;
delete window.$nameSource;
const cache = window.$nameCache; const cache = window.$nameCache;
delete window.$nameCache;
function run(jsCode) { function run(jsCode) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = requestId("run"); const id = requestId("run");
JSBridgeCallbacks[id] = { resolve, reject }; JSBridgeCallbacks[id] = { resolve, reject };
window.$nameJava?.request("run", [String(jsCode)], id); java?.request("run", [String(jsCode)], id);
}); });
}; };
function ajaxAwait(url, callTimeout) { function ajaxAwait(url, callTimeout) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = requestId("ajaxAwait"); const id = requestId("ajaxAwait");
JSBridgeCallbacks[id] = { resolve, reject }; JSBridgeCallbacks[id] = { resolve, reject };
window.$nameJava?.request("ajaxAwait", [String(url), String(callTimeout)], id); java?.request("ajaxAwait", [String(url), String(callTimeout)], id);
}); });
}; };
function connectAwait(url, header, callTimeout) { function connectAwait(url, header, callTimeout) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = requestId("connectAwait"); const id = requestId("connectAwait");
JSBridgeCallbacks[id] = { resolve, reject }; JSBridgeCallbacks[id] = { resolve, reject };
window.$nameJava?.request("connectAwait", [String(url), String(header), String(callTimeout)], id); java?.request("connectAwait", [String(url), String(header), String(callTimeout)], id);
}); });
}; };
function getAwait(url, header, callTimeout) { function getAwait(url, header, callTimeout) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = requestId("getAwait"); const id = requestId("getAwait");
JSBridgeCallbacks[id] = { resolve, reject }; JSBridgeCallbacks[id] = { resolve, reject };
window.$nameJava?.request("getAwait", [String(url), String(header), String(callTimeout)], id); java?.request("getAwait", [String(url), String(header), String(callTimeout)], id);
}); });
}; };
function headAwait(url, header, callTimeout) { function headAwait(url, header, callTimeout) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = requestId("headAwait"); const id = requestId("headAwait");
JSBridgeCallbacks[id] = { resolve, reject }; JSBridgeCallbacks[id] = { resolve, reject };
window.$nameJava?.request("headAwait", [String(url), String(header), String(callTimeout)], id); java?.request("headAwait", [String(url), String(header), String(callTimeout)], id);
}); });
}; };
function postAwait(url, body, header, callTimeout) { function postAwait(url, body, header, callTimeout) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = requestId("postAwait"); const id = requestId("postAwait");
JSBridgeCallbacks[id] = { resolve, reject }; JSBridgeCallbacks[id] = { resolve, reject };
window.$nameJava?.request("postAwait", [String(url), String(body), String(header), String(callTimeout)], id); java?.request("postAwait", [String(url), String(body), String(header), String(callTimeout)], id);
}); });
}; };
function webViewAwait(html, url, js, cacheFirst) { function webViewAwait(html, url, js, cacheFirst) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = requestId("webViewAwait"); const id = requestId("webViewAwait");
JSBridgeCallbacks[id] = { resolve, reject }; JSBridgeCallbacks[id] = { resolve, reject };
window.$nameJava?.request("webViewAwait", [String(html), String(url), String(js), String(cacheFirst)], id); java?.request("webViewAwait", [String(html), String(url), String(js), String(cacheFirst)], id);
}); });
}; };
function webViewGetSourceAwait(html, url, js, sourceRegex, cacheFirst, delayTime) {
return new Promise((resolve, reject) => {
const id = requestId("webViewGetSourceAwait");
JSBridgeCallbacks[id] = { resolve, reject };
java?.request("webViewGetSourceAwait", [String(html), String(url), String(js), String(sourceRegex), String(cacheFirst), String(delayTime)], id);
});
}
function decryptStrAwait(transformation, key, iv, data) { function decryptStrAwait(transformation, key, iv, data) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = requestId("decryptStrAwait"); const id = requestId("decryptStrAwait");
JSBridgeCallbacks[id] = { resolve, reject }; JSBridgeCallbacks[id] = { resolve, reject };
window.$nameJava?.request("decryptStrAwait", [String(transformation), String(key), String(iv), String(data)], id); java?.request("decryptStrAwait", [String(transformation), String(key), String(iv), String(data)], id);
}); });
}; };
function encryptBase64Await(transformation, key, iv, data) { function encryptBase64Await(transformation, key, iv, data) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = requestId("encryptBase64Await"); const id = requestId("encryptBase64Await");
JSBridgeCallbacks[id] = { resolve, reject }; JSBridgeCallbacks[id] = { resolve, reject };
window.$nameJava?.request("encryptBase64Await", [String(transformation), String(key), String(iv), String(data)], id); java?.request("encryptBase64Await", [String(transformation), String(key), String(iv), String(data)], id);
}); });
}; };
function encryptHexAwait(transformation, key, iv, data) { function encryptHexAwait(transformation, key, iv, data) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = requestId("encryptHexAwait"); const id = requestId("encryptHexAwait");
JSBridgeCallbacks[id] = { resolve, reject }; JSBridgeCallbacks[id] = { resolve, reject };
window.$nameJava?.request("encryptHexAwait", [String(transformation), String(key), String(iv), String(data)], id); java?.request("encryptHexAwait", [String(transformation), String(key), String(iv), String(data)], id);
}); });
}; };
function createSignHexAwait(algorithm, publicKey, privateKey, data) { function createSignHexAwait(algorithm, publicKey, privateKey, data) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = requestId("createSignHexAwait"); const id = requestId("createSignHexAwait");
JSBridgeCallbacks[id] = { resolve, reject }; JSBridgeCallbacks[id] = { resolve, reject };
window.$nameJava?.request("createSignHexAwait", [String(algorithm), String(publicKey), String(privateKey), String(data)], id); java?.request("createSignHexAwait", [String(algorithm), String(publicKey), String(privateKey), String(data)], id);
}); });
}; };
function downloadFileAwait(url) { function downloadFileAwait(url) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = requestId("downloadFileAwait"); const id = requestId("downloadFileAwait");
JSBridgeCallbacks[id] = { resolve, reject }; JSBridgeCallbacks[id] = { resolve, reject };
window.$nameJava?.request("downloadFileAwait", [String(url)], id); java?.request("downloadFileAwait", [String(url)], id);
}); });
}; };
function readTxtFileAwait(path) { function readTxtFileAwait(path) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = requestId("readTxtFileAwait"); const id = requestId("readTxtFileAwait");
JSBridgeCallbacks[id] = { resolve, reject }; JSBridgeCallbacks[id] = { resolve, reject };
window.$nameJava?.request("readTxtFileAwait", [String(path)], id); java?.request("readTxtFileAwait", [String(path)], id);
}); });
}; };
function importScriptAwait(url) { function importScriptAwait(url) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = requestId("importScriptAwait"); const id = requestId("importScriptAwait");
JSBridgeCallbacks[id] = { resolve, reject }; JSBridgeCallbacks[id] = { resolve, reject };
window.$nameJava?.request("importScriptAwait", [String(url)], id); java?.request("importScriptAwait", [String(url)], id);
}); });
}; };
function getStringAwait(ruleStr, mContent) { function getStringAwait(ruleStr, mContent) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = requestId("getStringAwait"); const id = requestId("getStringAwait");
JSBridgeCallbacks[id] = { resolve, reject }; JSBridgeCallbacks[id] = { resolve, reject };
window.$nameJava?.request("getStringAwait", [String(ruleStr), String(mContent)], id); java?.request("getStringAwait", [String(ruleStr), String(mContent)], id);
}); });
}; };
window.$JSBridgeResult = function(requestId, result, error) { window.$JSBridgeResult = function(requestId, result, error) {
@@ -354,31 +366,55 @@ class WebJsExtensions(
} }
delete JSBridgeCallbacks[requestId]; delete JSBridgeCallbacks[requestId];
} }
};""" };""".trimIndent()
}
val JS_INJECTION2 by lazy {
"""
const requestId = n => 'req_' + n + '_' + Date.now() + '_' + Math.random().toString(36).slice(-3);
const JSBridgeCallbacks = {};
const java = window.$nameJava;
delete window.$nameJava;
function run(jsCode) {
return new Promise((resolve, reject) => {
const id = requestId("run");
JSBridgeCallbacks[id] = { resolve, reject };
java?.request("run", [String(jsCode)], id);
});
};
window.$JSBridgeResult = function(requestId, result, error) {
if (JSBridgeCallbacks[requestId]) {
if (error) {
JSBridgeCallbacks[requestId].reject(error);
} else {
JSBridgeCallbacks[requestId].resolve(result);
}
delete JSBridgeCallbacks[requestId];
}
};""".trimIndent()
} }
val basicJs by lazy { val basicJs by lazy {
""" """
(function() { (function() {
if (screen.orientation && !screen.orientation.__patched) { if (screen.orientation) {
screen.orientation.lock = function(orientation) { screen.orientation.lock = function(orientation) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
window.$nameBasic?.lockOrientation(orientation) window.$nameBasic?.lockOrientation(orientation);
resolve() resolve()
}); });
}; };
screen.orientation.unlock = function() { screen.orientation.unlock = function() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
window.$nameBasic?.lockOrientation('unlock') window.$nameBasic?.lockOrientation('unlock');
resolve() resolve()
}); });
}; };
screen.orientation.__patched = true;
}; };
window.close = function() { window.close = function() {
window.$nameBasic?.onCloseRequested(); window.$nameBasic?.onCloseRequested();
}; };
})();""" })();""".trimIndent()
} }
} }
} }
@@ -255,6 +255,7 @@ class ReadBookActivity : BaseReadBookActivity(),
override val pageFactory get() = binding.readView.pageFactory override val pageFactory get() = binding.readView.pageFactory
override val pageDelegate get() = binding.readView.pageDelegate override val pageDelegate get() = binding.readView.pageDelegate
override val headerHeight: Int get() = binding.readView.curPage.headerHeight override val headerHeight: Int get() = binding.readView.curPage.headerHeight
override val imgBgPaddingStart: Int get() = binding.readView.curPage.imgBgPaddingStart
private val nextPageDebounce by lazy { Debounce { keyPage(PageDirection.NEXT) } } private val nextPageDebounce by lazy { Debounce { keyPage(PageDirection.NEXT) } }
private val prevPageDebounce by lazy { Debounce { keyPage(PageDirection.PREV) } } private val prevPageDebounce by lazy { Debounce { keyPage(PageDirection.PREV) } }
private var bookChanged = false private var bookChanged = false
@@ -165,6 +165,36 @@ class TipConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_tip_config
}.requestInputMethod() }.requestInputMethod()
} }
3 -> { // 正则表达式分段
alert(title = "设置正则分段规则") {
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
editView.inputType = InputType.TYPE_CLASS_TEXT
editView.setText(ReadBookConfig.titleSegFlag)
editLayout.hint = "例如: [章回篇] 或 (第.{1,3}章)"
editView.isSingleLine = true
}
customView { alertBinding.root }
okButton {
val value = alertBinding.editView.text?.toString()?.trim()
if (!value.isNullOrEmpty()) {
try {
Regex(value)
ReadBookConfig.titleSegFlag = value
toastOnUi("正则规则已保存")
postEvent(EventBus.UP_CONFIG, arrayListOf(5))
} catch (e: Exception) {
toastOnUi("正则表达式格式错误")
}
} else {
toastOnUi("规则不能为空")
}
}
cancelButton()
}.requestInputMethod()
}
else -> { else -> {
toastOnUi("当前分段模式无需配置参数") toastOnUi("当前分段模式无需配置参数")
} }
@@ -64,6 +64,8 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
private var autoPager: AutoPager? = null private var autoPager: AutoPager? = null
private var isScroll = false private var isScroll = false
private val renderRunnable by lazy { Runnable { preRenderPage() } } private val renderRunnable by lazy { Runnable { preRenderPage() } }
private var lastClickTime = 0L
private var doubleClick = false
//绘制图片的paint //绘制图片的paint
val imagePaint by lazy { val imagePaint by lazy {
@@ -226,6 +228,11 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
column.selected = true column.selected = true
select(textPos) select(textPos)
} }
is TextHtmlColumn -> {
if (!selectAble) return@touch
column.selected = true
select(textPos)
}
} }
} }
} }
@@ -236,6 +243,14 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
*/ */
@Suppress("UNUSED_ANONYMOUS_PARAMETER") @Suppress("UNUSED_ANONYMOUS_PARAMETER")
fun click(x: Float, y: Float): Boolean { fun click(x: Float, y: Float): Boolean {
val currentTime = System.currentTimeMillis()
val debounceClick = currentTime - lastClickTime < 300L //300毫秒防抖和双击
lastClickTime = currentTime
doubleClick = if (debounceClick) {
!doubleClick
} else {
false
}
var handled = false var handled = false
touch(x, y) { _, textPos, textPage, textLine, column -> touch(x, y) { _, textPos, textPage, textLine, column ->
when (column) { when (column) {
@@ -254,45 +269,51 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
activity?.showDialogFragment(PhotoDialog(column.src)) activity?.showDialogFragment(PhotoDialog(column.src))
handled = true handled = true
} }
"2" -> { //兼容处理 "2" -> { //兼容处理
if (ReadBook.book?.isOnLineTxt == true) { if (!debounceClick) {
val click = column.click if (ReadBook.book?.isOnLineTxt == true) {
val src = column.src val click = column.click
if (!click.isNullOrBlank()) { val src = column.src
callBack.clickImg(click, src) if (!click.isNullOrBlank()) {
handled = true callBack.clickImg(click, src)
} else { handled = true
handled = callBack.oldClickImg(src) } else {
handled = callBack.oldClickImg(src)
}
} }
} }
} }
"3" -> { //关闭 "3" -> { //关闭
handled = false handled = false
} }
"4" -> { //双击
else -> { //默认点击 if (doubleClick) {
val click = column.click val click = column.click
if (!click.isNullOrBlank()) { if (!click.isNullOrBlank()) {
callBack.clickImg(click, column.src) callBack.clickImg(click, column.src)
handled = true
}
} else {
handled = true handled = true
} }
} }
else -> { //默认点击
if (!debounceClick) {
val click = column.click
if (!click.isNullOrBlank()) {
callBack.clickImg(click, column.src)
handled = true
}
}
}
} }
is TextHtmlColumn -> { is TextHtmlColumn -> {
column.linkUrl?.let { column.linkUrl?.let {
activity?.startActivity<OpenUrlConfirmActivity> { activity?.startActivity<OpenUrlConfirmActivity> {
putExtra("uri", it) putExtra("uri", it)
// putExtra("mimeType", mimeType)
// putExtra("sourceOrigin", source.getKey())
// putExtra("sourceName", source.getTag())
// putExtra("sourceType", source.getSourceType())
} }
handled = true handled = true
} }
} }
} }
} }
@@ -308,7 +329,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
select: (textPos: TextPos) -> Unit, select: (textPos: TextPos) -> Unit,
) { ) {
touchRough(x, y) { _, textPos, _, _, column -> touchRough(x, y) { _, textPos, _, _, column ->
if (column is TextColumn) { if (column is TextBaseColumn) {
column.selected = true column.selected = true
select(textPos) select(textPos)
} }
@@ -582,7 +603,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
textPos.lineIndex = lineIndex textPos.lineIndex = lineIndex
for ((charIndex, column) in textLine.columns.withIndex()) { for ((charIndex, column) in textLine.columns.withIndex()) {
textPos.columnIndex = charIndex textPos.columnIndex = charIndex
if (column is TextColumn) { if (column is TextBaseColumn) {
val compareStart = textPos.compare(selectStart) val compareStart = textPos.compare(selectStart)
val compareEnd = textPos.compare(selectEnd) val compareEnd = textPos.compare(selectEnd)
column.selected = compareStart >= 0 && compareEnd <= 0 column.selected = compareStart >= 0 && compareEnd <= 0
@@ -600,13 +621,13 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
private fun upSelectedStart(x: Float, y: Float, top: Float) { private fun upSelectedStart(x: Float, y: Float, top: Float) {
callBack.run { callBack.run {
upSelectedStart(x, y + headerHeight, top + headerHeight) upSelectedStart(x + imgBgPaddingStart, y + headerHeight, top + headerHeight)
} }
} }
private fun upSelectedEnd(x: Float, y: Float) { private fun upSelectedEnd(x: Float, y: Float) {
callBack.run { callBack.run {
upSelectedEnd(x, y + headerHeight) upSelectedEnd(x + imgBgPaddingStart, y + headerHeight)
} }
} }
@@ -621,7 +642,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
val textPage = relativePage(relativePos) val textPage = relativePage(relativePos)
textPage.lines.forEach { textLine -> textPage.lines.forEach { textLine ->
textLine.columns.forEach { textLine.columns.forEach {
if (it is TextColumn) { if (it is TextBaseColumn) {
it.selected = false it.selected = false
if (clearSearchResult) { if (clearSearchResult) {
it.isSearchResult = false it.isSearchResult = false
@@ -649,7 +670,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
textPos.columnIndex = charIndex textPos.columnIndex = charIndex
val compareStart = textPos.compare(selectStart) val compareStart = textPos.compare(selectStart)
val compareEnd = textPos.compare(selectEnd) val compareEnd = textPos.compare(selectEnd)
if (column is TextColumn) { if (column is TextBaseColumn) {
when { when {
compareStart == -1 -> if ( compareStart == -1 -> if (
selectStart.columnIndex == textLine.columns.size selectStart.columnIndex == textLine.columns.size
@@ -750,6 +771,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
interface CallBack { interface CallBack {
val headerHeight: Int val headerHeight: Int
val imgBgPaddingStart: Int
val pageFactory: TextPageFactory val pageFactory: TextPageFactory
val pageDelegate: PageDelegate? val pageDelegate: PageDelegate?
val isScroll: Boolean val isScroll: Boolean
@@ -11,7 +11,6 @@ import androidx.core.graphics.drawable.toDrawable
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isInvisible
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.AppConst.timeFormat import io.legado.app.constant.AppConst.timeFormat
import io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
@@ -72,6 +71,11 @@ class PageView(context: Context) : FrameLayout(context) {
return h1 + h2 + binding.vwRoot.paddingTop return h1 + h2 + binding.vwRoot.paddingTop
} }
val imgBgPaddingStart: Int
get() {
return binding.vwRoot.paddingStart
}
init { init {
upStyle() upStyle()
binding.vwStatusBar.applyStatusBarPadding() binding.vwStatusBar.applyStatusBarPadding()
@@ -132,7 +136,7 @@ class PageView(context: Context) : FrameLayout(context) {
*/ */
fun upStatusBar() = with(binding.vwStatusBar) { fun upStatusBar() = with(binding.vwStatusBar) {
setPadding(paddingLeft, context.statusBarHeight, paddingRight, paddingBottom) setPadding(paddingLeft, context.statusBarHeight, paddingRight, paddingBottom)
isGone = ReadBookConfig.hideStatusBar isGone = ReadBookConfig.hideStatusBar || readBookActivity?.isInMultiWindow == true
} }
fun upNavigationBar() { fun upNavigationBar() {
@@ -12,6 +12,7 @@ import io.legado.app.help.PaintPool
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.ui.book.read.page.ContentTextView import io.legado.app.ui.book.read.page.ContentTextView
import io.legado.app.ui.book.read.page.TextBaseColumn
import io.legado.app.ui.book.read.page.entities.TextChapter.Companion.emptyTextChapter import io.legado.app.ui.book.read.page.entities.TextChapter.Companion.emptyTextChapter
import io.legado.app.ui.book.read.page.entities.column.TextColumn import io.legado.app.ui.book.read.page.entities.column.TextColumn
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
@@ -49,9 +50,8 @@ data class TextPage(
val lines: List<TextLine> get() = textLines val lines: List<TextLine> get() = textLines
val lineSize: Int get() = textLines.size val lineSize: Int get() = textLines.size
val charSize: Int get() = text.length.coerceAtLeast(1) val charSize: Int get() = text.length.coerceAtLeast(1)
val chapterPosition: Int val chapterPosition: Int get() = textLines.first().chapterPosition
get() = textLines.firstOrNull()?.chapterPosition ?: 0 val searchResult = hashSetOf<TextBaseColumn>()
val searchResult = hashSetOf<TextColumn>()
var isMsgPage: Boolean = false var isMsgPage: Boolean = false
var canvasRecorder = CanvasRecorderFactory.create(true) var canvasRecorder = CanvasRecorderFactory.create(true)
var doublePage = false var doublePage = false
@@ -271,7 +271,7 @@ data class TextPage(
val columns = textLines[maxIndex].columns val columns = textLines[maxIndex].columns
for (index in 0 until columnIndex) { for (index in 0 until columnIndex) {
val column = columns[index] val column = columns[index]
if (column is TextColumn) { if (column is TextBaseColumn) {
length += column.charData.length length += column.charData.length
} }
} }
@@ -292,10 +292,10 @@ data class TextPage(
* @return * @return
*/ */
fun containPos(chapterPos: Int): Boolean { fun containPos(chapterPos: Int): Boolean {
val line = lines.firstOrNull() ?: return false // lines 为空时直接返回 false val line = lines.first()
val startPos = line.chapterPosition val startPos = line.chapterPosition
val endPos = startPos + charSize val endPos = startPos + charSize
return chapterPos in startPos until endPos return chapterPos in startPos..<endPos
} }
fun draw(view: ContentTextView, canvas: Canvas, relativeOffset: Float) { fun draw(view: ContentTextView, canvas: Canvas, relativeOffset: Float) {
@@ -337,7 +337,10 @@ data class TextPage(
fun render(view: ContentTextView): Boolean { fun render(view: ContentTextView): Boolean {
if (!isCompleted) return false if (!isCompleted) return false
return canvasRecorder.recordIfNeeded(view.width, renderHeight) { return canvasRecorder.recordIfNeeded(
view.width,
renderHeight + 10.dpToPx()
) { //高度留余,避免图片过高时被截断 下划线最远10dp
drawPage(view, this) drawPage(view, this)
} }
} }
@@ -365,15 +368,10 @@ data class TextPage(
} }
fun upRenderHeight() { fun upRenderHeight() {
if (lines.isEmpty()) {
renderHeight = 0
return
}
renderHeight = ceil(lines.last().lineBottom).toInt() renderHeight = ceil(lines.last().lineBottom).toInt()
if (leftLineSize > 0 && leftLineSize <= lines.size) { if (leftLineSize > 0 && leftLineSize != lines.size) {
val leftHeight = ceil(lines[leftLineSize - 1].lineBottom).toInt() val leftHeight = ceil(lines[leftLineSize - 1].lineBottom).toInt()
renderHeight = max(renderHeight, leftHeight) renderHeight = max(renderHeight, leftHeight)
} }
} }
} }
@@ -20,13 +20,21 @@ object TitleStyleParser {
listOf(rawTitle.take(segDistance), rawTitle.substring(segDistance)) listOf(rawTitle.take(segDistance), rawTitle.substring(segDistance))
} }
2 -> { 2, 3 -> {
val flags = segFlag.split(",").map { it.trim() }.filter { it.isNotEmpty() } if (segFlag.isEmpty()) {
if (flags.isEmpty()) listOf(rawTitle) listOf(rawTitle)
else { } else {
val pattern = flags.joinToString("|") { Regex.escape(it) } val regex = if (segType == 3) {
val regex = Regex("(?<=$pattern)") Regex("(?<=$segFlag)")
rawTitle.split(regex).map { it.trim() }.filter { it.isNotEmpty() } } else {
val flags = segFlag.split(",").map { it.trim() }.filter { it.isNotEmpty() }
val pattern = flags.joinToString("|") { Regex.escape(it) }
Regex("(?<=$pattern)")
}
rawTitle.split(regex)
.map { it.trim() }
.filter { it.isNotEmpty() }
} }
} }
@@ -17,29 +17,30 @@ import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.lang.ref.WeakReference
@Suppress("unused") @Suppress("unused")
class SourceLoginJsExtensions( class SourceLoginJsExtensions(
activity: AppCompatActivity?, source: BaseSource?, activity: AppCompatActivity?, source: BaseSource?,
private val bookType: Int = 0, private val bookType: Int = 0,
private val callback: Callback? = null callback: Callback? = null
) : RssJsExtensions(activity, source) { ) : RssJsExtensions(activity, source) {
private val callbackRef: WeakReference<Callback> = WeakReference(callback)
interface Callback { interface Callback {
fun upUiData(data: Map<String, String?>?) fun upUiData(data: Map<String, String?>?)
fun reUiView() fun reUiView()
} }
fun upLoginData(data: Map<String, String?>?) { fun upLoginData(data: Map<String, String?>?) {
callback?.upUiData(data) callbackRef.get()?.upUiData(data)
} }
fun reLoginView() { fun reLoginView() {
callback?.reUiView() callbackRef.get()?.reUiView()
} }
fun refreshExplore() { fun refreshExplore() {
callback?.reUiView() callbackRef.get()?.reUiView()
} }
fun refreshBookInfo() { fun refreshBookInfo() {
@@ -65,7 +66,12 @@ class SourceLoginJsExtensions(
} }
@JvmOverloads @JvmOverloads
fun showBrowser(url: String, html: String, preloadJs: String? = null) { fun showBrowser(
url: String,
html: String? = null,
preloadJs: String? = null,
config: String? = null
) {
val activity = activityRef.get() ?: return val activity = activityRef.get() ?: return
val source = getSource() ?: return val source = getSource() ?: return
activity.showDialogFragment( activity.showDialogFragment(
@@ -74,7 +80,8 @@ class SourceLoginJsExtensions(
bookType, bookType,
url, url,
html, html,
preloadJs preloadJs,
config
) )
) )
} }
@@ -14,6 +14,7 @@ import io.legado.app.ui.book.explore.ExploreShowActivity
import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.login.SourceLoginActivity import io.legado.app.ui.login.SourceLoginActivity
import io.legado.app.ui.rss.article.RssSortActivity import io.legado.app.ui.rss.article.RssSortActivity
import io.legado.app.ui.widget.dialog.PhotoDialog
import io.legado.app.utils.isJsonObject import io.legado.app.utils.isJsonObject
import io.legado.app.utils.showDialogFragment import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
@@ -25,6 +26,7 @@ import kotlinx.coroutines.withContext
import org.json.JSONObject import org.json.JSONObject
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
@Suppress("unused") @Suppress("unused")
open class RssJsExtensions(activity: AppCompatActivity?, source: BaseSource?) : JsExtensions { open class RssJsExtensions(activity: AppCompatActivity?, source: BaseSource?) : JsExtensions {
@@ -36,7 +38,7 @@ open class RssJsExtensions(activity: AppCompatActivity?, source: BaseSource?) :
} }
override fun getTag(): String? { override fun getTag(): String? {
return sourceRef.get()?.getTag() return getSource()?.getTag()
} }
@JavascriptInterface @JavascriptInterface
@@ -67,6 +69,11 @@ open class RssJsExtensions(activity: AppCompatActivity?, source: BaseSource?) :
activityRef.get()?.showDialogFragment(AddToBookshelfDialog(bookUrl)) activityRef.get()?.showDialogFragment(AddToBookshelfDialog(bookUrl))
} }
fun showPhoto(src: String) {
activityRef.get()?.showDialogFragment(PhotoDialog(src, getSource()?.getKey()))
}
@JavascriptInterface @JavascriptInterface
@JvmOverloads @JvmOverloads
fun open(name: String, url: String? = null, title: String? = null, origin: String? = null) { fun open(name: String, url: String? = null, title: String? = null, origin: String? = null) {
@@ -59,8 +59,9 @@ class BottomWebViewDialog() :
sourceKey: String, sourceKey: String,
bookType: Int, bookType: Int,
url: String, url: String,
html: String, html: String? = null,
preloadJs: String? = null preloadJs: String? = null,
config: String? = null
) : this() { ) : this() {
arguments = Bundle().apply { arguments = Bundle().apply {
putString("sourceKey", sourceKey) putString("sourceKey", sourceKey)
@@ -68,6 +69,7 @@ class BottomWebViewDialog() :
putString("url", url) putString("url", url)
putString("html", html) putString("html", html)
putString("preloadJs", preloadJs) putString("preloadJs", preloadJs)
putString("config", config)
} }
} }
@@ -1,6 +1,7 @@
package com.script.rhino package com.script.rhino
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import org.mozilla.javascript.Context import org.mozilla.javascript.Context
@@ -9,7 +10,6 @@ import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
import kotlin.coroutines.ContinuationInterceptor import kotlin.coroutines.ContinuationInterceptor
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
val rhinoContext: RhinoContext val rhinoContext: RhinoContext
get() = Context.getCurrentContext() as RhinoContext get() = Context.getCurrentContext() as RhinoContext
@@ -55,7 +55,7 @@ inline fun <T> runScriptWithContext(context: CoroutineContext, block: () -> T):
suspend inline fun <T> runScriptWithContext(block: () -> T): T { suspend inline fun <T> runScriptWithContext(block: () -> T): T {
val rhinoContext = Context.enter() as RhinoContext val rhinoContext = Context.enter() as RhinoContext
val previousCoroutineContext = rhinoContext.coroutineContext val previousCoroutineContext = rhinoContext.coroutineContext
rhinoContext.coroutineContext = coroutineContext.minusKey(ContinuationInterceptor) rhinoContext.coroutineContext = currentCoroutineContext().minusKey(ContinuationInterceptor)
try { try {
return block() return block()
} finally { } finally {