kunfei 3 лет назад
Родитель
Сommit
f5bb30c44e

+ 3 - 2
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,

+ 3 - 0
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<RssSource>
 

+ 6 - 1
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())
     }
 

+ 37 - 0
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 要打开的链接

+ 80 - 45
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<String, String>? = 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,49 +155,83 @@ 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<WebView> = 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 inner class EvalJsRunnable(
-        webView: WebView,
-        private val url: String,
-        private val mJavaScript: String
-    ) : Runnable {
-        var retry = 0
-        private val mWebView: WeakReference<WebView> = WeakReference(webView)
-        override fun run() {
-            mWebView.get()?.evaluateJavascript(mJavaScript) {
-                handleResult(it)
+    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)
         }
 
-        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
+        @Suppress("DEPRECATION", "OVERRIDE_DEPRECATION", "KotlinRedundantDiagnosticSuppress")
+        override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
+            if (shouldOverrideUrlLoading(url)) {
+                return true
             }
-            if (retry > 30) {
-                callback?.onError(NoStackTraceException("js执行超时"))
-                mHandler.post {
+            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@async
             }
-            retry++
-            mHandler.postDelayed(this@EvalJsRunnable, 1000)
+            return false
         }
-    }
-
-    private inner class SnifferWebClient : WebViewClient() {
 
         override fun onLoadResource(view: WebView, resUrl: String) {
             sourceRegex?.let {
@@ -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<WebView> = 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<WebView> = WeakReference(webView)
+            override fun run() {
+                mWebView.get()?.loadUrl("javascript:${mJavaScript}")
+            }
         }
+
     }
 
     companion object {

+ 10 - 9
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<RssSourceViewModel>(R.layout.fragment_rss),
+class RssFragment() : VMBaseFragment<RssViewModel>(R.layout.fragment_rss),
     MainFragmentInterface,
     RssAdapter.CallBack {
 
@@ -55,7 +54,7 @@ class RssFragment() : VMBaseFragment<RssSourceViewModel>(R.layout.fragment_rss),
     override val position: Int? get() = arguments?.getInt("position")
 
     private val binding by viewBinding(FragmentRssBinding::bind)
-    override val viewModel by viewModels<RssSourceViewModel>()
+    override val viewModel by viewModels<RssViewModel>()
     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<RssSourceViewModel>(R.layout.fragment_rss),
 
     override fun openRss(rssSource: RssSource) {
         if (rssSource.singleUrl) {
-            if (rssSource.sourceUrl.startsWith("http", true)) {
-                startActivity<ReadRssActivity> {
-                    putExtra("title", rssSource.sourceName)
-                    putExtra("origin", rssSource.sourceUrl)
+            viewModel.getSingleUrl(rssSource) { url ->
+                if (url.startsWith("http", true)) {
+                    startActivity<ReadRssActivity> {
+                        putExtra("title", rssSource.sourceName)
+                        putExtra("origin", url)
+                    }
+                } else {
+                    context?.openUrl(url)
                 }
-            } else {
-                context?.openUrl(rssSource.sourceUrl)
             }
         } else {
             startActivity<RssSortActivity> {

+ 74 - 0
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("<js>", 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)
+            }
+    }
+
+
+}