From 5d297244ad32a51772c86004d8d9d8963bb1887d Mon Sep 17 00:00:00 2001 From: kunfei Date: Sun, 16 Jul 2023 12:24:23 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/assets/defaultData/rssSources.json | 5 +- .../io/legado/app/data/dao/RssSourceDao.kt | 3 + .../java/io/legado/app/help/DefaultData.kt | 7 +- .../java/io/legado/app/help/JsExtensions.kt | 37 +++++ .../legado/app/help/http/BackstageWebView.kt | 131 +++++++++++------- .../io/legado/app/ui/main/rss/RssFragment.kt | 19 +-- .../io/legado/app/ui/main/rss/RssViewModel.kt | 74 ++++++++++ 7 files changed, 216 insertions(+), 60 deletions(-) create mode 100644 app/src/main/java/io/legado/app/ui/main/rss/RssViewModel.kt diff --git a/app/src/main/assets/defaultData/rssSources.json b/app/src/main/assets/defaultData/rssSources.json index d57b35e62..5271f6c32 100644 --- a/app/src/main/assets/defaultData/rssSources.json +++ b/app/src/main/assets/defaultData/rssSources.json @@ -1,14 +1,15 @@ [ { - "articleStyle": 0, "customOrder": 1, "enableJs": true, "enabled": true, "loadWithBaseUrl": true, "singleUrl": true, + "sourceGroup": "legado", "sourceIcon": "https://funimg.pddpic.com/ktt/762ee1c9-a117-46e4-b0c7-9d820420c08b.png.slim.png", "sourceName": "我的小店", - "sourceUrl": "https://ktt.pinduoduo.com/t/tlQQsofbQM" + "sourceUrl": "https://ktt.pinduoduo.com/t/tlQQsofbQM", + "sortUrl": "@js:java.webViewGetOverrideUrl(null, source.sourceUrl, null, \"weixin:.*\")" }, { "customOrder": 2, diff --git a/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt b/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt index fa11d3e8b..41266a7a9 100644 --- a/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt @@ -104,6 +104,9 @@ interface RssSourceDao { @Query("delete from rssSources where sourceUrl = :sourceUrl") fun delete(sourceUrl: String) + @Query("delete from rssSources where sourceGroup like 'legado'") + fun deleteDefault() + @get:Query("select * from rssSources where sourceGroup is null or sourceGroup = ''") val noGroup: List diff --git a/app/src/main/java/io/legado/app/help/DefaultData.kt b/app/src/main/java/io/legado/app/help/DefaultData.kt index cc94b65b8..b99f7953b 100644 --- a/app/src/main/java/io/legado/app/help/DefaultData.kt +++ b/app/src/main/java/io/legado/app/help/DefaultData.kt @@ -2,7 +2,11 @@ package io.legado.app.help import io.legado.app.constant.AppConst import io.legado.app.data.appDb -import io.legado.app.data.entities.* +import io.legado.app.data.entities.DictRule +import io.legado.app.data.entities.HttpTTS +import io.legado.app.data.entities.KeyboardAssist +import io.legado.app.data.entities.RssSource +import io.legado.app.data.entities.TxtTocRule import io.legado.app.help.config.LocalConfig import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ThemeConfig @@ -117,6 +121,7 @@ object DefaultData { } fun importDefaultRssSources() { + appDb.rssSourceDao.deleteDefault() appDb.rssSourceDao.insert(*rssSources.toTypedArray()) } diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt index c82b849e6..7366712ff 100644 --- a/app/src/main/java/io/legado/app/help/JsExtensions.kt +++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt @@ -145,6 +145,43 @@ interface JsExtensions : JsEncodeUtils { } } + /** + * 使用webView获取资源url + */ + fun webViewGetSource(html: String?, url: String?, js: String?, sourceRegex: String): String? { + return runBlocking { + BackstageWebView( + url = url, + html = html, + javaScript = js, + headerMap = getSource()?.getHeaderMap(true), + tag = getSource()?.getKey(), + sourceRegex = sourceRegex + ).getStrResponse().body + } + } + + /** + * 使用webView获取跳转url + */ + fun webViewGetOverrideUrl( + html: String?, + url: String?, + js: String?, + overrideUrlRegex: String + ): String? { + return runBlocking { + BackstageWebView( + url = url, + html = html, + javaScript = js, + headerMap = getSource()?.getHeaderMap(true), + tag = getSource()?.getKey(), + overrideUrlRegex = overrideUrlRegex + ).getStrResponse().body + } + } + /** * 使用内置浏览器打开链接,手动验证网站防爬 * @param url 要打开的链接 diff --git a/app/src/main/java/io/legado/app/help/http/BackstageWebView.kt b/app/src/main/java/io/legado/app/help/http/BackstageWebView.kt index 3dfb8cff2..16b89cd63 100644 --- a/app/src/main/java/io/legado/app/help/http/BackstageWebView.kt +++ b/app/src/main/java/io/legado/app/help/http/BackstageWebView.kt @@ -7,6 +7,7 @@ import android.os.Looper import android.util.AndroidRuntimeException import android.webkit.CookieManager import android.webkit.SslErrorHandler +import android.webkit.WebResourceRequest import android.webkit.WebSettings import android.webkit.WebView import android.webkit.WebViewClient @@ -33,6 +34,7 @@ class BackstageWebView( private val tag: String? = null, private val headerMap: Map? = null, private val sourceRegex: String? = null, + private val overrideUrlRegex: String? = null, private val javaScript: String? = null, ) { @@ -102,7 +104,7 @@ class BackstageWebView( settings.blockNetworkImage = true settings.userAgentString = headerMap?.get(AppConst.UA_NAME) ?: AppConfig.userAgent settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW - if (sourceRegex.isNullOrEmpty()) { + if (sourceRegex.isNullOrBlank() && overrideUrlRegex.isNullOrBlank()) { webView.webViewClient = HtmlWebViewClient() } else { webView.webViewClient = SnifferWebClient() @@ -153,50 +155,84 @@ class BackstageWebView( handler?.proceed() } - } + private inner class EvalJsRunnable( + webView: WebView, + private val url: String, + private val mJavaScript: String + ) : Runnable { + var retry = 0 + private val mWebView: WeakReference = WeakReference(webView) + override fun run() { + mWebView.get()?.evaluateJavascript(mJavaScript) { + handleResult(it) + } + } - private inner class EvalJsRunnable( - webView: WebView, - private val url: String, - private val mJavaScript: String - ) : Runnable { - var retry = 0 - private val mWebView: WeakReference = WeakReference(webView) - override fun run() { - mWebView.get()?.evaluateJavascript(mJavaScript) { - handleResult(it) + private fun handleResult(result: String) = Coroutine.async { + if (result.isNotEmpty() && result != "null") { + val content = StringEscapeUtils.unescapeJson(result) + .replace(quoteRegex, "") + try { + val response = StrResponse(url, content) + callback?.onResult(response) + } catch (e: Exception) { + callback?.onError(e) + } + mHandler.post { + destroy() + } + return@async + } + if (retry > 30) { + callback?.onError(NoStackTraceException("js执行超时")) + mHandler.post { + destroy() + } + return@async + } + retry++ + mHandler.postDelayed(this@EvalJsRunnable, 1000) } } - private fun handleResult(result: String) = Coroutine.async { - if (result.isNotEmpty() && result != "null") { - val content = StringEscapeUtils.unescapeJson(result) - .replace(quoteRegex, "") - try { - val response = StrResponse(url, content) - callback?.onResult(response) - } catch (e: Exception) { - callback?.onError(e) - } - mHandler.post { - destroy() - } - return@async - } - if (retry > 30) { - callback?.onError(NoStackTraceException("js执行超时")) - mHandler.post { - destroy() - } - return@async - } - retry++ - mHandler.postDelayed(this@EvalJsRunnable, 1000) - } } private inner class SnifferWebClient : WebViewClient() { + override fun shouldOverrideUrlLoading( + view: WebView, + request: WebResourceRequest + ): Boolean { + if (shouldOverrideUrlLoading(request.url.toString())) { + return true + } + return super.shouldOverrideUrlLoading(view, request) + } + + @Suppress("DEPRECATION", "OVERRIDE_DEPRECATION", "KotlinRedundantDiagnosticSuppress") + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + if (shouldOverrideUrlLoading(url)) { + return true + } + return super.shouldOverrideUrlLoading(view, url) + } + + private fun shouldOverrideUrlLoading(requestUrl: String): Boolean { + overrideUrlRegex?.let { + if (requestUrl.matches(it.toRegex())) { + try { + val response = StrResponse(url!!, requestUrl) + callback?.onResult(response) + } catch (e: Exception) { + callback?.onError(e) + } + destroy() + return true + } + } + return false + } + override fun onLoadResource(view: WebView, resUrl: String) { sourceRegex?.let { if (resUrl.matches(it.toRegex())) { @@ -213,8 +249,7 @@ class BackstageWebView( override fun onPageFinished(webView: WebView, url: String) { setCookie(url) - val js = javaScript - if (!js.isNullOrEmpty()) { + if (!javaScript.isNullOrEmpty()) { val runnable = LoadJsRunnable(webView, javaScript) mHandler.postDelayed(runnable, 1000L) } @@ -229,16 +264,16 @@ class BackstageWebView( handler?.proceed() } - } - - private class LoadJsRunnable( - webView: WebView, - private val mJavaScript: String? - ) : Runnable { - private val mWebView: WeakReference = WeakReference(webView) - override fun run() { - mWebView.get()?.loadUrl("javascript:${mJavaScript}") + private inner class LoadJsRunnable( + webView: WebView, + private val mJavaScript: String? + ) : Runnable { + private val mWebView: WeakReference = WeakReference(webView) + override fun run() { + mWebView.get()?.loadUrl("javascript:${mJavaScript}") + } } + } companion object { diff --git a/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt b/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt index d661617c5..cc8b86996 100644 --- a/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt @@ -24,7 +24,6 @@ import io.legado.app.ui.rss.favorites.RssFavoritesActivity import io.legado.app.ui.rss.read.ReadRssActivity import io.legado.app.ui.rss.source.edit.RssSourceEditActivity import io.legado.app.ui.rss.source.manage.RssSourceActivity -import io.legado.app.ui.rss.source.manage.RssSourceViewModel import io.legado.app.ui.rss.subscription.RuleSubActivity import io.legado.app.utils.applyTint import io.legado.app.utils.cnCompare @@ -42,7 +41,7 @@ import kotlinx.coroutines.launch /** * 订阅界面 */ -class RssFragment() : VMBaseFragment(R.layout.fragment_rss), +class RssFragment() : VMBaseFragment(R.layout.fragment_rss), MainFragmentInterface, RssAdapter.CallBack { @@ -55,7 +54,7 @@ class RssFragment() : VMBaseFragment(R.layout.fragment_rss), override val position: Int? get() = arguments?.getInt("position") private val binding by viewBinding(FragmentRssBinding::bind) - override val viewModel by viewModels() + override val viewModel by viewModels() private val adapter by lazy { RssAdapter(requireContext(), this) } private val searchView: SearchView by lazy { binding.titleBar.findViewById(R.id.search_view) @@ -169,13 +168,15 @@ class RssFragment() : VMBaseFragment(R.layout.fragment_rss), override fun openRss(rssSource: RssSource) { if (rssSource.singleUrl) { - if (rssSource.sourceUrl.startsWith("http", true)) { - startActivity { - putExtra("title", rssSource.sourceName) - putExtra("origin", rssSource.sourceUrl) + viewModel.getSingleUrl(rssSource) { url -> + if (url.startsWith("http", true)) { + startActivity { + putExtra("title", rssSource.sourceName) + putExtra("origin", url) + } + } else { + context?.openUrl(url) } - } else { - context?.openUrl(rssSource.sourceUrl) } } else { startActivity { diff --git a/app/src/main/java/io/legado/app/ui/main/rss/RssViewModel.kt b/app/src/main/java/io/legado/app/ui/main/rss/RssViewModel.kt new file mode 100644 index 000000000..fe0bad699 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/main/rss/RssViewModel.kt @@ -0,0 +1,74 @@ +package io.legado.app.ui.main.rss + +import android.app.Application +import io.legado.app.base.BaseViewModel +import io.legado.app.data.appDb +import io.legado.app.data.entities.RssSource +import io.legado.app.utils.toastOnUi + +class RssViewModel(application: Application) : BaseViewModel(application) { + + fun topSource(vararg sources: RssSource) { + execute { + sources.sortBy { it.customOrder } + val minOrder = appDb.rssSourceDao.minOrder - 1 + val array = Array(sources.size) { + sources[it].copy(customOrder = minOrder - it) + } + appDb.rssSourceDao.update(*array) + } + } + + fun bottomSource(vararg sources: RssSource) { + execute { + sources.sortBy { it.customOrder } + val maxOrder = appDb.rssSourceDao.maxOrder + 1 + val array = Array(sources.size) { + sources[it].copy(customOrder = maxOrder + it) + } + appDb.rssSourceDao.update(*array) + } + } + + fun del(vararg rssSource: RssSource) { + execute { appDb.rssSourceDao.delete(*rssSource) } + } + + fun disable(rssSource: RssSource) { + execute { + rssSource.enabled = false + appDb.rssSourceDao.update(rssSource) + } + } + + fun getSingleUrl(rssSource: RssSource, onSuccess: (url: String) -> Unit) { + execute { + val url = rssSource.sortUrl + if (!url.isNullOrBlank()) { + if (url.startsWith("", false) + || url.startsWith("@js:", false) + ) { + val jsStr = if (url.startsWith("@")) { + url.substring(4) + } else { + url.substring(4, url.lastIndexOf("<")) + } + val result = rssSource.evalJS(jsStr)?.toString() + if (!result.isNullOrBlank()) { + return@execute result + } + } else { + return@execute url + } + } + rssSource.sourceUrl + }.timeout(10000) + .onSuccess { + onSuccess.invoke(it) + }.onError { + context.toastOnUi(it.localizedMessage) + } + } + + +} \ No newline at end of file