[新增] 正则分段标题以及更多Sigma分支功能(@Luoyacheng)
This commit is contained in:
@@ -76,7 +76,6 @@ import java.util.zip.ZipInputStream
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
|
||||
/**
|
||||
* js扩展类, 在js中通过java变量调用
|
||||
* 添加方法,请更新文档/legado/app/src/main/assets/help/JsHelp.md
|
||||
@@ -128,7 +127,6 @@ interface JsExtensions : JsEncodeUtils {
|
||||
fun ajaxAll(urlList: Array<String>): Array<StrResponse> {
|
||||
return ajaxAll(urlList, false)
|
||||
}
|
||||
|
||||
fun ajaxAll(urlList: Array<String>, skipRateLimit: Boolean): Array<StrResponse> {
|
||||
return runBlocking(context) {
|
||||
urlList.asFlow().mapAsync(AppConfig.threadCount) { url ->
|
||||
@@ -148,7 +146,6 @@ interface JsExtensions : JsEncodeUtils {
|
||||
fun ajaxTestAll(urlList: Array<String>, timeout: Int): Array<StrResponse> {
|
||||
return ajaxTestAll(urlList, timeout, false)
|
||||
}
|
||||
|
||||
fun ajaxTestAll(
|
||||
urlList: Array<String>,
|
||||
timeout: Int,
|
||||
@@ -238,7 +235,17 @@ interface JsExtensions : JsEncodeUtils {
|
||||
}
|
||||
|
||||
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?,
|
||||
js: String?,
|
||||
sourceRegex: String,
|
||||
cacheFirst: Boolean
|
||||
cacheFirst: Boolean,
|
||||
delayTime: Long
|
||||
): String? {
|
||||
if (isMainThread) {
|
||||
error("webViewGetSource must be called on a background thread")
|
||||
@@ -261,7 +269,8 @@ interface JsExtensions : JsEncodeUtils {
|
||||
javaScript = js,
|
||||
headerMap = getSource()?.getHeaderMap(true),
|
||||
tag = getSource()?.getKey(),
|
||||
sourceRegex = sourceRegex
|
||||
sourceRegex = sourceRegex,
|
||||
delayTime = delayTime
|
||||
).getStrResponse().body
|
||||
}
|
||||
}
|
||||
@@ -272,7 +281,17 @@ interface JsExtensions : JsEncodeUtils {
|
||||
js: String?,
|
||||
overrideUrlRegex: 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?,
|
||||
js: String?,
|
||||
overrideUrlRegex: String,
|
||||
cacheFirst: Boolean
|
||||
cacheFirst: Boolean,
|
||||
delayTime: Long
|
||||
): String? {
|
||||
if (isMainThread) {
|
||||
error("webViewGetOverrideUrl must be called on a background thread")
|
||||
@@ -295,11 +315,23 @@ interface JsExtensions : JsEncodeUtils {
|
||||
javaScript = js,
|
||||
headerMap = getSource()?.getHeaderMap(true),
|
||||
tag = getSource()?.getKey(),
|
||||
overrideUrlRegex = overrideUrlRegex
|
||||
overrideUrlRegex = overrideUrlRegex,
|
||||
delayTime = delayTime
|
||||
).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 要打开的链接
|
||||
@@ -1164,9 +1196,9 @@ interface JsExtensions : JsEncodeUtils {
|
||||
return GSON.toJson(ReadBookConfig.durConfig)
|
||||
}
|
||||
|
||||
/* fun getReadBookConfigMap(): Map<String, Any> {
|
||||
return ReadBookConfig.durConfig.toMap()
|
||||
} */
|
||||
//fun getReadBookConfigMap(): Map<String, Any> {
|
||||
// return ReadBookConfig.durConfig.toMap()
|
||||
//}
|
||||
|
||||
/**
|
||||
* 获取主题模式
|
||||
@@ -1179,14 +1211,14 @@ interface JsExtensions : JsEncodeUtils {
|
||||
/**
|
||||
* 获取主题配置
|
||||
*/
|
||||
//@JavascriptInterface
|
||||
/*fun getThemeConfig(): String {
|
||||
val themeConfig = ThemeConfig.getDurConfig(appCtx)
|
||||
return GSON.toJson(themeConfig)
|
||||
}*/
|
||||
// @JavascriptInterface
|
||||
//fun getThemeConfig(): String {
|
||||
// val themeConfig = ThemeConfig.getDurConfig(appCtx)
|
||||
// return GSON.toJson(themeConfig)
|
||||
//}
|
||||
|
||||
/* fun getThemeConfigMap(): Map<String, Any?> {
|
||||
return ThemeConfig.getDurConfig(appCtx).toMap()
|
||||
} */
|
||||
//fun getThemeConfigMap(): Map<String, Any?> {
|
||||
// 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.escapeForJs
|
||||
import io.legado.app.utils.fromJsonObject
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.UUID
|
||||
|
||||
@Suppress("unused")
|
||||
class WebJsExtensions(
|
||||
source: BaseSource,
|
||||
activity: AppCompatActivity,
|
||||
source: BaseSource, activity: AppCompatActivity,
|
||||
private val webView: WebView,
|
||||
private val bookType: Int = 0
|
||||
private val bookType: Int = 0,
|
||||
callback: Callback? = null
|
||||
) : 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 {
|
||||
var book: Book? = null
|
||||
var chapter: BookChapter? = null
|
||||
@@ -64,35 +76,37 @@ class WebJsExtensions(
|
||||
setChapter(chapter)
|
||||
evalJS(jsParam[0]).toString()
|
||||
}
|
||||
|
||||
"ajaxAwait" -> {
|
||||
ajax(jsParam[0], jsParam[1].toIntOrNull()).toString()
|
||||
}
|
||||
|
||||
"connectAwait" -> {
|
||||
connect(jsParam[0], jsParam[1], jsParam[2].toIntOrNull())
|
||||
}
|
||||
|
||||
"getAwait" -> {
|
||||
get(jsParam[0], jsParam[1], jsParam[2].toIntOrNull())
|
||||
}
|
||||
|
||||
"headAwait" -> {
|
||||
head(jsParam[0], jsParam[1], jsParam[2].toIntOrNull())
|
||||
}
|
||||
|
||||
"postAwait" -> {
|
||||
post(jsParam[0], jsParam[1], jsParam[2], jsParam[3].toIntOrNull())
|
||||
}
|
||||
|
||||
"webViewAwait" -> {
|
||||
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" -> {
|
||||
createSymmetricCrypto(jsParam[0], jsParam[1], jsParam[2]).decryptStr(jsParam[3])
|
||||
}
|
||||
|
||||
"encryptBase64Await" -> {
|
||||
createSymmetricCrypto(
|
||||
jsParam[0],
|
||||
@@ -100,34 +114,27 @@ class WebJsExtensions(
|
||||
jsParam[2]
|
||||
).encryptBase64(jsParam[3])
|
||||
}
|
||||
|
||||
"encryptHexAwait" -> {
|
||||
createSymmetricCrypto(jsParam[0], jsParam[1], jsParam[2]).encryptHex(jsParam[3])
|
||||
}
|
||||
|
||||
"createSignHexAwait" -> {
|
||||
createSign(jsParam[0]).setPublicKey(jsParam[1]).setPrivateKey(jsParam[2])
|
||||
.signHex(jsParam[3])
|
||||
}
|
||||
|
||||
"downloadFileAwait" -> {
|
||||
downloadFile(jsParam[0])
|
||||
}
|
||||
|
||||
"readTxtFileAwait" -> {
|
||||
readTxtFile(jsParam[0])
|
||||
}
|
||||
|
||||
"importScriptAwait" -> {
|
||||
importScript(jsParam[0])
|
||||
}
|
||||
|
||||
"getStringAwait" -> AnalyzeRule(book, getSource()).run {
|
||||
setCoroutineContext(coroutineContext)
|
||||
setChapter(chapter)
|
||||
getString(jsParam[0], jsParam[1])
|
||||
}
|
||||
|
||||
else -> "error funName"
|
||||
}
|
||||
}.onSuccess { data ->
|
||||
@@ -142,78 +149,64 @@ class WebJsExtensions(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun toast(msg: String?) {
|
||||
super.toast(msg)
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun longToast(msg: String?) {
|
||||
super.longToast(msg)
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun log(msg: String?): String {
|
||||
return super.log(msg).toString()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun ajax(url: String): String? {
|
||||
return super.ajax(url, 9000)
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun ajax(url: String, callTimeout: Int?): String? {
|
||||
return super.ajax(url, callTimeout?.toLong())
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun connect(urlStr: String?): String {
|
||||
if (urlStr.isNullOrEmpty()) return "error empty url"
|
||||
return super.connect(urlStr, null, 9000).toString()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun connect(urlStr: String, header: String): String {
|
||||
return super.connect(urlStr, header, 9000).toString()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun connect(urlStr: String, header: String, callTimeout: Int?): String {
|
||||
return super.connect(urlStr, header, callTimeout?.toLong()).toString()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun get(urlStr: String, headers: String): String {
|
||||
val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap()
|
||||
return super.get(urlStr, headerMap, 9000).body()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun get(urlStr: String, headers: String, timeout: Int?): String {
|
||||
val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap()
|
||||
return super.get(urlStr, headerMap, timeout).body()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun post(urlStr: String, body: String, headers: String): String {
|
||||
val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap()
|
||||
return super.post(urlStr, body, headerMap, 9000).body()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun post(urlStr: String, body: String, headers: String, timeout: Int?): String {
|
||||
val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap()
|
||||
return super.post(urlStr, body, headerMap, timeout).body()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun head(urlStr: String, headers: String): String {
|
||||
val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap()
|
||||
return GSON.toJson(super.head(urlStr, headerMap, 9000).headers())
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun head(urlStr: String, headers: String, timeout: Int?): String {
|
||||
val headerMap = GSON.fromJsonObject<Map<String, String>>(headers).getOrNull() ?: emptyMap()
|
||||
@@ -222,127 +215,146 @@ class WebJsExtensions(
|
||||
|
||||
companion object {
|
||||
private fun getRandomLetter(): Char {
|
||||
val letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
|
||||
val letters = "abcdefghijklmnopqrstuvwxyz"
|
||||
return letters.random()
|
||||
}
|
||||
|
||||
val uuid by lazy { UUID.randomUUID().toString().split("-") }
|
||||
val uuid2 by lazy { UUID.randomUUID().toString().split("-") }
|
||||
val nameJava by lazy { getRandomLetter() + uuid[0] + uuid[1] }
|
||||
val nameCache by lazy { getRandomLetter() + uuid[2] + uuid[3] }
|
||||
val nameSource by lazy { getRandomLetter() + uuid[4] }
|
||||
val nameBasic by lazy { getRandomLetter() + uuid2[1] + uuid2[2] }
|
||||
val JSBridgeResult by lazy { getRandomLetter() + uuid2[3] + uuid2[4] }
|
||||
val uuid by lazy {
|
||||
UUID.randomUUID().toString().replace('-', getRandomLetter()).chunked(6)
|
||||
}
|
||||
val uuid2 by lazy {
|
||||
UUID.randomUUID().toString().replace('-', getRandomLetter()).chunked(6)
|
||||
}
|
||||
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 {
|
||||
"""
|
||||
const requestId = n => 'req_' + n + '_' + Date.now() + '_' + Math.random().toString(36).slice(-3);
|
||||
const JSBridgeCallbacks = {};
|
||||
const java = window.$nameJava;
|
||||
delete window.$nameJava;
|
||||
const source = window.$nameSource;
|
||||
delete window.$nameSource;
|
||||
const cache = window.$nameCache;
|
||||
delete window.$nameCache;
|
||||
function run(jsCode) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = requestId("run");
|
||||
JSBridgeCallbacks[id] = { resolve, reject };
|
||||
window.$nameJava?.request("run", [String(jsCode)], id);
|
||||
java?.request("run", [String(jsCode)], id);
|
||||
});
|
||||
};
|
||||
function ajaxAwait(url, callTimeout) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = requestId("ajaxAwait");
|
||||
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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = requestId("connectAwait");
|
||||
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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = requestId("getAwait");
|
||||
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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = requestId("headAwait");
|
||||
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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = requestId("postAwait");
|
||||
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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = requestId("webViewAwait");
|
||||
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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = requestId("decryptStrAwait");
|
||||
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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = requestId("encryptBase64Await");
|
||||
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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = requestId("encryptHexAwait");
|
||||
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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = requestId("createSignHexAwait");
|
||||
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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = requestId("downloadFileAwait");
|
||||
JSBridgeCallbacks[id] = { resolve, reject };
|
||||
window.$nameJava?.request("downloadFileAwait", [String(url)], id);
|
||||
java?.request("downloadFileAwait", [String(url)], id);
|
||||
});
|
||||
};
|
||||
function readTxtFileAwait(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = requestId("readTxtFileAwait");
|
||||
JSBridgeCallbacks[id] = { resolve, reject };
|
||||
window.$nameJava?.request("readTxtFileAwait", [String(path)], id);
|
||||
java?.request("readTxtFileAwait", [String(path)], id);
|
||||
});
|
||||
};
|
||||
function importScriptAwait(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = requestId("importScriptAwait");
|
||||
JSBridgeCallbacks[id] = { resolve, reject };
|
||||
window.$nameJava?.request("importScriptAwait", [String(url)], id);
|
||||
java?.request("importScriptAwait", [String(url)], id);
|
||||
});
|
||||
};
|
||||
function getStringAwait(ruleStr, mContent) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = requestId("getStringAwait");
|
||||
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) {
|
||||
@@ -354,31 +366,55 @@ class WebJsExtensions(
|
||||
}
|
||||
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 {
|
||||
"""
|
||||
(function() {
|
||||
if (screen.orientation && !screen.orientation.__patched) {
|
||||
if (screen.orientation) {
|
||||
screen.orientation.lock = function(orientation) {
|
||||
return new Promise((resolve, reject) => {
|
||||
window.$nameBasic?.lockOrientation(orientation)
|
||||
window.$nameBasic?.lockOrientation(orientation);
|
||||
resolve()
|
||||
});
|
||||
};
|
||||
screen.orientation.unlock = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
window.$nameBasic?.lockOrientation('unlock')
|
||||
window.$nameBasic?.lockOrientation('unlock');
|
||||
resolve()
|
||||
});
|
||||
};
|
||||
screen.orientation.__patched = true;
|
||||
};
|
||||
window.close = function() {
|
||||
window.$nameBasic?.onCloseRequested();
|
||||
};
|
||||
})();"""
|
||||
})();""".trimIndent()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,6 +255,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
override val pageFactory get() = binding.readView.pageFactory
|
||||
override val pageDelegate get() = binding.readView.pageDelegate
|
||||
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 prevPageDebounce by lazy { Debounce { keyPage(PageDirection.PREV) } }
|
||||
private var bookChanged = false
|
||||
|
||||
@@ -165,6 +165,36 @@ class TipConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_tip_config
|
||||
}.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 -> {
|
||||
toastOnUi("当前分段模式无需配置参数")
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
private var autoPager: AutoPager? = null
|
||||
private var isScroll = false
|
||||
private val renderRunnable by lazy { Runnable { preRenderPage() } }
|
||||
private var lastClickTime = 0L
|
||||
private var doubleClick = false
|
||||
|
||||
//绘制图片的paint
|
||||
val imagePaint by lazy {
|
||||
@@ -226,6 +228,11 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
column.selected = true
|
||||
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")
|
||||
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
|
||||
touch(x, y) { _, textPos, textPage, textLine, column ->
|
||||
when (column) {
|
||||
@@ -254,45 +269,51 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
activity?.showDialogFragment(PhotoDialog(column.src))
|
||||
handled = true
|
||||
}
|
||||
|
||||
"2" -> { //兼容处理
|
||||
if (ReadBook.book?.isOnLineTxt == true) {
|
||||
val click = column.click
|
||||
val src = column.src
|
||||
if (!click.isNullOrBlank()) {
|
||||
callBack.clickImg(click, src)
|
||||
handled = true
|
||||
} else {
|
||||
handled = callBack.oldClickImg(src)
|
||||
if (!debounceClick) {
|
||||
if (ReadBook.book?.isOnLineTxt == true) {
|
||||
val click = column.click
|
||||
val src = column.src
|
||||
if (!click.isNullOrBlank()) {
|
||||
callBack.clickImg(click, src)
|
||||
handled = true
|
||||
} else {
|
||||
handled = callBack.oldClickImg(src)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"3" -> { //关闭
|
||||
handled = false
|
||||
}
|
||||
|
||||
else -> { //默认点击
|
||||
val click = column.click
|
||||
if (!click.isNullOrBlank()) {
|
||||
callBack.clickImg(click, column.src)
|
||||
"4" -> { //双击
|
||||
if (doubleClick) {
|
||||
val click = column.click
|
||||
if (!click.isNullOrBlank()) {
|
||||
callBack.clickImg(click, column.src)
|
||||
handled = true
|
||||
}
|
||||
} else {
|
||||
handled = true
|
||||
}
|
||||
}
|
||||
else -> { //默认点击
|
||||
if (!debounceClick) {
|
||||
val click = column.click
|
||||
if (!click.isNullOrBlank()) {
|
||||
callBack.clickImg(click, column.src)
|
||||
handled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is TextHtmlColumn -> {
|
||||
column.linkUrl?.let {
|
||||
activity?.startActivity<OpenUrlConfirmActivity> {
|
||||
putExtra("uri", it)
|
||||
// putExtra("mimeType", mimeType)
|
||||
// putExtra("sourceOrigin", source.getKey())
|
||||
// putExtra("sourceName", source.getTag())
|
||||
// putExtra("sourceType", source.getSourceType())
|
||||
}
|
||||
handled = true
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,7 +329,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
select: (textPos: TextPos) -> Unit,
|
||||
) {
|
||||
touchRough(x, y) { _, textPos, _, _, column ->
|
||||
if (column is TextColumn) {
|
||||
if (column is TextBaseColumn) {
|
||||
column.selected = true
|
||||
select(textPos)
|
||||
}
|
||||
@@ -582,7 +603,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
textPos.lineIndex = lineIndex
|
||||
for ((charIndex, column) in textLine.columns.withIndex()) {
|
||||
textPos.columnIndex = charIndex
|
||||
if (column is TextColumn) {
|
||||
if (column is TextBaseColumn) {
|
||||
val compareStart = textPos.compare(selectStart)
|
||||
val compareEnd = textPos.compare(selectEnd)
|
||||
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) {
|
||||
callBack.run {
|
||||
upSelectedStart(x, y + headerHeight, top + headerHeight)
|
||||
upSelectedStart(x + imgBgPaddingStart, y + headerHeight, top + headerHeight)
|
||||
}
|
||||
}
|
||||
|
||||
private fun upSelectedEnd(x: Float, y: Float) {
|
||||
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)
|
||||
textPage.lines.forEach { textLine ->
|
||||
textLine.columns.forEach {
|
||||
if (it is TextColumn) {
|
||||
if (it is TextBaseColumn) {
|
||||
it.selected = false
|
||||
if (clearSearchResult) {
|
||||
it.isSearchResult = false
|
||||
@@ -649,7 +670,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
textPos.columnIndex = charIndex
|
||||
val compareStart = textPos.compare(selectStart)
|
||||
val compareEnd = textPos.compare(selectEnd)
|
||||
if (column is TextColumn) {
|
||||
if (column is TextBaseColumn) {
|
||||
when {
|
||||
compareStart == -1 -> if (
|
||||
selectStart.columnIndex == textLine.columns.size
|
||||
@@ -750,6 +771,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
|
||||
interface CallBack {
|
||||
val headerHeight: Int
|
||||
val imgBgPaddingStart: Int
|
||||
val pageFactory: TextPageFactory
|
||||
val pageDelegate: PageDelegate?
|
||||
val isScroll: Boolean
|
||||
|
||||
@@ -11,7 +11,6 @@ import androidx.core.graphics.drawable.toDrawable
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isInvisible
|
||||
import io.legado.app.R
|
||||
import io.legado.app.constant.AppConst.timeFormat
|
||||
import io.legado.app.data.entities.Bookmark
|
||||
@@ -72,6 +71,11 @@ class PageView(context: Context) : FrameLayout(context) {
|
||||
return h1 + h2 + binding.vwRoot.paddingTop
|
||||
}
|
||||
|
||||
val imgBgPaddingStart: Int
|
||||
get() {
|
||||
return binding.vwRoot.paddingStart
|
||||
}
|
||||
|
||||
init {
|
||||
upStyle()
|
||||
binding.vwStatusBar.applyStatusBarPadding()
|
||||
@@ -132,7 +136,7 @@ class PageView(context: Context) : FrameLayout(context) {
|
||||
*/
|
||||
fun upStatusBar() = with(binding.vwStatusBar) {
|
||||
setPadding(paddingLeft, context.statusBarHeight, paddingRight, paddingBottom)
|
||||
isGone = ReadBookConfig.hideStatusBar
|
||||
isGone = ReadBookConfig.hideStatusBar || readBookActivity?.isInMultiWindow == true
|
||||
}
|
||||
|
||||
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.ReadBookConfig
|
||||
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.column.TextColumn
|
||||
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
||||
@@ -49,9 +50,8 @@ data class TextPage(
|
||||
val lines: List<TextLine> get() = textLines
|
||||
val lineSize: Int get() = textLines.size
|
||||
val charSize: Int get() = text.length.coerceAtLeast(1)
|
||||
val chapterPosition: Int
|
||||
get() = textLines.firstOrNull()?.chapterPosition ?: 0
|
||||
val searchResult = hashSetOf<TextColumn>()
|
||||
val chapterPosition: Int get() = textLines.first().chapterPosition
|
||||
val searchResult = hashSetOf<TextBaseColumn>()
|
||||
var isMsgPage: Boolean = false
|
||||
var canvasRecorder = CanvasRecorderFactory.create(true)
|
||||
var doublePage = false
|
||||
@@ -271,7 +271,7 @@ data class TextPage(
|
||||
val columns = textLines[maxIndex].columns
|
||||
for (index in 0 until columnIndex) {
|
||||
val column = columns[index]
|
||||
if (column is TextColumn) {
|
||||
if (column is TextBaseColumn) {
|
||||
length += column.charData.length
|
||||
}
|
||||
}
|
||||
@@ -292,10 +292,10 @@ data class TextPage(
|
||||
* @return
|
||||
*/
|
||||
fun containPos(chapterPos: Int): Boolean {
|
||||
val line = lines.firstOrNull() ?: return false // lines 为空时直接返回 false
|
||||
val line = lines.first()
|
||||
val startPos = line.chapterPosition
|
||||
val endPos = startPos + charSize
|
||||
return chapterPos in startPos until endPos
|
||||
return chapterPos in startPos..<endPos
|
||||
}
|
||||
|
||||
fun draw(view: ContentTextView, canvas: Canvas, relativeOffset: Float) {
|
||||
@@ -337,7 +337,10 @@ data class TextPage(
|
||||
|
||||
fun render(view: ContentTextView): Boolean {
|
||||
if (!isCompleted) return false
|
||||
return canvasRecorder.recordIfNeeded(view.width, renderHeight) {
|
||||
return canvasRecorder.recordIfNeeded(
|
||||
view.width,
|
||||
renderHeight + 10.dpToPx()
|
||||
) { //高度留余,避免图片过高时被截断 下划线最远10dp
|
||||
drawPage(view, this)
|
||||
}
|
||||
}
|
||||
@@ -365,15 +368,10 @@ data class TextPage(
|
||||
}
|
||||
|
||||
fun upRenderHeight() {
|
||||
if (lines.isEmpty()) {
|
||||
renderHeight = 0
|
||||
return
|
||||
}
|
||||
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()
|
||||
renderHeight = max(renderHeight, leftHeight)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,13 +20,21 @@ object TitleStyleParser {
|
||||
listOf(rawTitle.take(segDistance), rawTitle.substring(segDistance))
|
||||
}
|
||||
|
||||
2 -> {
|
||||
val flags = segFlag.split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||
if (flags.isEmpty()) listOf(rawTitle)
|
||||
else {
|
||||
val pattern = flags.joinToString("|") { Regex.escape(it) }
|
||||
val regex = Regex("(?<=$pattern)")
|
||||
rawTitle.split(regex).map { it.trim() }.filter { it.isNotEmpty() }
|
||||
2, 3 -> {
|
||||
if (segFlag.isEmpty()) {
|
||||
listOf(rawTitle)
|
||||
} else {
|
||||
val regex = if (segType == 3) {
|
||||
Regex("(?<=$segFlag)")
|
||||
} 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.launch
|
||||
import java.io.File
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
@Suppress("unused")
|
||||
class SourceLoginJsExtensions(
|
||||
activity: AppCompatActivity?, source: BaseSource?,
|
||||
private val bookType: Int = 0,
|
||||
private val callback: Callback? = null
|
||||
callback: Callback? = null
|
||||
) : RssJsExtensions(activity, source) {
|
||||
|
||||
private val callbackRef: WeakReference<Callback> = WeakReference(callback)
|
||||
interface Callback {
|
||||
fun upUiData(data: Map<String, String?>?)
|
||||
fun reUiView()
|
||||
}
|
||||
|
||||
fun upLoginData(data: Map<String, String?>?) {
|
||||
callback?.upUiData(data)
|
||||
callbackRef.get()?.upUiData(data)
|
||||
}
|
||||
|
||||
fun reLoginView() {
|
||||
callback?.reUiView()
|
||||
callbackRef.get()?.reUiView()
|
||||
}
|
||||
|
||||
fun refreshExplore() {
|
||||
callback?.reUiView()
|
||||
callbackRef.get()?.reUiView()
|
||||
}
|
||||
|
||||
fun refreshBookInfo() {
|
||||
@@ -65,7 +66,12 @@ class SourceLoginJsExtensions(
|
||||
}
|
||||
|
||||
@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 source = getSource() ?: return
|
||||
activity.showDialogFragment(
|
||||
@@ -74,7 +80,8 @@ class SourceLoginJsExtensions(
|
||||
bookType,
|
||||
url,
|
||||
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.login.SourceLoginActivity
|
||||
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.showDialogFragment
|
||||
import io.legado.app.utils.startActivity
|
||||
@@ -25,6 +26,7 @@ import kotlinx.coroutines.withContext
|
||||
import org.json.JSONObject
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
open class RssJsExtensions(activity: AppCompatActivity?, source: BaseSource?) : JsExtensions {
|
||||
|
||||
@@ -36,7 +38,7 @@ open class RssJsExtensions(activity: AppCompatActivity?, source: BaseSource?) :
|
||||
}
|
||||
|
||||
override fun getTag(): String? {
|
||||
return sourceRef.get()?.getTag()
|
||||
return getSource()?.getTag()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
@@ -67,6 +69,11 @@ open class RssJsExtensions(activity: AppCompatActivity?, source: BaseSource?) :
|
||||
activityRef.get()?.showDialogFragment(AddToBookshelfDialog(bookUrl))
|
||||
}
|
||||
|
||||
fun showPhoto(src: String) {
|
||||
activityRef.get()?.showDialogFragment(PhotoDialog(src, getSource()?.getKey()))
|
||||
}
|
||||
|
||||
|
||||
@JavascriptInterface
|
||||
@JvmOverloads
|
||||
fun open(name: String, url: String? = null, title: String? = null, origin: String? = null) {
|
||||
|
||||
@@ -59,8 +59,9 @@ class BottomWebViewDialog() :
|
||||
sourceKey: String,
|
||||
bookType: Int,
|
||||
url: String,
|
||||
html: String,
|
||||
preloadJs: String? = null
|
||||
html: String? = null,
|
||||
preloadJs: String? = null,
|
||||
config: String? = null
|
||||
) : this() {
|
||||
arguments = Bundle().apply {
|
||||
putString("sourceKey", sourceKey)
|
||||
@@ -68,6 +69,7 @@ class BottomWebViewDialog() :
|
||||
putString("url", url)
|
||||
putString("html", html)
|
||||
putString("preloadJs", preloadJs)
|
||||
putString("config", config)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user