订阅源网页重定向功能 #171
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -65,7 +65,7 @@ val appDb by lazy {
|
||||
}
|
||||
|
||||
@Database(
|
||||
version = 77,
|
||||
version = 78,
|
||||
exportSchema = true,
|
||||
entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
|
||||
ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
|
||||
@@ -107,7 +107,8 @@ val appDb by lazy {
|
||||
AutoMigration(from = 73, to = 74),
|
||||
AutoMigration(from = 74, to = 75),
|
||||
AutoMigration(from = 75, to = 76),
|
||||
AutoMigration(from = 76, to = 77)
|
||||
AutoMigration(from = 76, to = 77),
|
||||
AutoMigration(from = 77, to = 78)
|
||||
]
|
||||
)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
@@ -128,6 +128,9 @@ interface RssSourceDao {
|
||||
@Query("update rssSources set enabled = :enable where sourceUrl = :sourceUrl")
|
||||
fun enable(sourceUrl: String, enable: Boolean)
|
||||
|
||||
@Query("UPDATE rssSources SET redirectPolicy = :redirectPolicy WHERE sourceUrl = :sourceUrl")
|
||||
suspend fun updateRedirectPolicy(sourceUrl: String, redirectPolicy: String)
|
||||
|
||||
private fun dealGroups(list: List<String>): List<String> {
|
||||
val groups = linkedSetOf<String>()
|
||||
list.forEach {
|
||||
|
||||
@@ -91,7 +91,10 @@ data class RssSource(
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var lastUpdateTime: Long = 0,
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var customOrder: Int = 0
|
||||
var customOrder: Int = 0,
|
||||
//用户控制的重定向
|
||||
@ColumnInfo(defaultValue = "ASK_CROSS_ORIGIN")
|
||||
var redirectPolicy: String = "ASK_CROSS_ORIGIN"
|
||||
) : Parcelable, BaseSource {
|
||||
|
||||
override fun getTag(): String {
|
||||
|
||||
@@ -5,6 +5,7 @@ package io.legado.app.ui.rss.article
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.os.Bundle
|
||||
import android.view.Gravity
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.viewModels
|
||||
@@ -19,6 +20,7 @@ import io.legado.app.base.VMBaseActivity
|
||||
import io.legado.app.databinding.ActivityRssArtivlesBinding
|
||||
import io.legado.app.help.source.sortUrls
|
||||
import io.legado.app.ui.login.SourceLoginActivity
|
||||
import io.legado.app.ui.rss.read.ReadRssActivity.RedirectPolicy
|
||||
import io.legado.app.ui.rss.source.edit.RssSourceEditActivity
|
||||
import io.legado.app.ui.widget.dialog.VariableDialog
|
||||
import io.legado.app.utils.*
|
||||
@@ -45,16 +47,19 @@ class RssSortActivity : VMBaseActivity<ActivityRssArtivlesBinding, RssSortViewMo
|
||||
}
|
||||
}
|
||||
|
||||
var redirectPolicy: RedirectPolicy = RedirectPolicy.ALLOW_ALL
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setSupportActionBar(binding.topBar)
|
||||
|
||||
viewModel.titleLiveData.observe(this) {
|
||||
binding.topBar.title = it
|
||||
binding.ctlBar.title = it
|
||||
}
|
||||
|
||||
viewModel.initData(intent) {
|
||||
upFragments()
|
||||
redirectPolicy = RedirectPolicy.fromString(viewModel.rssSource?.redirectPolicy)
|
||||
}
|
||||
|
||||
binding.titleBar.addOnOffsetChangedListener({ appBarLayout, verticalOffset ->
|
||||
@@ -98,7 +103,8 @@ class RssSortActivity : VMBaseActivity<ActivityRssArtivlesBinding, RssSortViewMo
|
||||
putExtra("sourceUrl", it)
|
||||
}
|
||||
}
|
||||
|
||||
R.id.menu_redirect_policy ->
|
||||
showRedirectPolicySubMenu()
|
||||
R.id.menu_clear -> {
|
||||
viewModel.url?.let {
|
||||
viewModel.clearArticles()
|
||||
@@ -117,6 +123,49 @@ class RssSortActivity : VMBaseActivity<ActivityRssArtivlesBinding, RssSortViewMo
|
||||
return super.onCompatOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun showRedirectPolicySubMenu() {
|
||||
val popup = PopupMenu(this, binding.titleBar, Gravity.END)
|
||||
popup.menuInflater.inflate(R.menu.menu_redirect_policy_submenu, popup.menu)
|
||||
setCurrentRedirectPolicyChecked(popup.menu)
|
||||
popup.setOnMenuItemClickListener { menuItem ->
|
||||
handleRedirectPolicySelection(menuItem)
|
||||
true
|
||||
}
|
||||
popup.show()
|
||||
}
|
||||
|
||||
private fun setCurrentRedirectPolicyChecked(menu: Menu) {
|
||||
val currentPolicy = redirectPolicy
|
||||
val menuItemId = when (currentPolicy) {
|
||||
RedirectPolicy.ALLOW_ALL -> R.id.menu_redirect_allow_all
|
||||
RedirectPolicy.ASK_ALWAYS -> R.id.menu_redirect_ask_always
|
||||
RedirectPolicy.ASK_CROSS_ORIGIN -> R.id.menu_redirect_ask_cross_origin
|
||||
RedirectPolicy.BLOCK_CROSS_ORIGIN -> R.id.menu_redirect_block_cross_origin
|
||||
}
|
||||
menu.findItem(menuItemId)?.isChecked = true
|
||||
}
|
||||
|
||||
private fun handleRedirectPolicySelection(menuItem: MenuItem) {
|
||||
val selectedPolicy = when (menuItem.itemId) {
|
||||
R.id.menu_redirect_allow_all -> RedirectPolicy.ALLOW_ALL
|
||||
R.id.menu_redirect_ask_always -> RedirectPolicy.ASK_ALWAYS
|
||||
R.id.menu_redirect_ask_cross_origin -> RedirectPolicy.ASK_CROSS_ORIGIN
|
||||
R.id.menu_redirect_block_cross_origin -> RedirectPolicy.BLOCK_CROSS_ORIGIN
|
||||
else -> RedirectPolicy.ALLOW_ALL
|
||||
}
|
||||
|
||||
updateRedirectPolicy(selectedPolicy)
|
||||
toastOnUi("重定向策略已更新")
|
||||
}
|
||||
|
||||
fun updateRedirectPolicy(policy: RedirectPolicy) {
|
||||
viewModel.rssSource?.let { source ->
|
||||
source.redirectPolicy = policy.name
|
||||
redirectPolicy = policy
|
||||
viewModel.updateRssSourceRedirectPolicy(source.sourceUrl)
|
||||
}
|
||||
}
|
||||
|
||||
private fun upFragments() {
|
||||
lifecycleScope.launch {
|
||||
val sorts = viewModel.rssSource?.sortUrls().orEmpty()
|
||||
|
||||
@@ -9,6 +9,8 @@ import io.legado.app.data.entities.RssArticle
|
||||
import io.legado.app.data.entities.RssReadRecord
|
||||
import io.legado.app.data.entities.RssSource
|
||||
import io.legado.app.help.source.removeSortCache
|
||||
import io.legado.app.utils.toastOnUi
|
||||
import splitties.init.appCtx
|
||||
|
||||
|
||||
class RssSortViewModel(application: Application) : BaseViewModel(application) {
|
||||
@@ -92,4 +94,14 @@ class RssSortViewModel(application: Application) : BaseViewModel(application) {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateRssSourceRedirectPolicy(redirectPolicy: String) {
|
||||
execute {
|
||||
val source = rssSource ?: return@execute
|
||||
source.redirectPolicy = redirectPolicy
|
||||
appDb.rssSourceDao.updateRedirectPolicy(source.sourceUrl, redirectPolicy)
|
||||
}.onError {
|
||||
appCtx.toastOnUi("保存失败: ${it.localizedMessage}")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import android.net.Uri
|
||||
import android.net.http.SslError
|
||||
import android.os.Bundle
|
||||
import android.os.SystemClock
|
||||
import android.view.Gravity
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
@@ -22,6 +23,8 @@ import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.activity.addCallback
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.size
|
||||
@@ -73,11 +76,28 @@ import java.util.regex.PatternSyntaxException
|
||||
class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>(),
|
||||
RssFavoritesDialog.Callback {
|
||||
|
||||
enum class RedirectPolicy {
|
||||
ALLOW_ALL,
|
||||
ASK_ALWAYS,
|
||||
ASK_CROSS_ORIGIN,
|
||||
BLOCK_CROSS_ORIGIN;
|
||||
|
||||
companion object {
|
||||
fun fromString(value: String?): RedirectPolicy {
|
||||
return entries.find { it.name.equals(value, ignoreCase = true) } ?: ALLOW_ALL
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var redirectPolicy: RedirectPolicy = RedirectPolicy.ALLOW_ALL
|
||||
|
||||
override val binding by viewBinding(ActivityRssReadBinding::inflate)
|
||||
override val viewModel by viewModels<ReadRssViewModel>()
|
||||
|
||||
private var starMenuItem: MenuItem? = null
|
||||
private var ttsMenuItem: MenuItem? = null
|
||||
private var redirectPolicyMenu: MenuItem? = null
|
||||
private var isFullScreen = false
|
||||
private var customWebViewCallback: WebChromeClient.CustomViewCallback? = null
|
||||
private val rssJsExtensions by lazy { RssJsExtensions(this) }
|
||||
@@ -138,6 +158,11 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
|
||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||
starMenuItem = menu.findItem(R.id.menu_rss_star)
|
||||
ttsMenuItem = menu.findItem(R.id.menu_aloud)
|
||||
redirectPolicyMenu = menu.findItem(R.id.menu_redirect_policy)
|
||||
redirectPolicyMenu?.setOnMenuItemClickListener {
|
||||
showRedirectPolicySubMenu()
|
||||
true
|
||||
}
|
||||
upStarMenu()
|
||||
return super.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
@@ -160,7 +185,6 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
|
||||
showDialogFragment(RssFavoritesDialog(it))
|
||||
}
|
||||
}
|
||||
|
||||
R.id.menu_share_it -> {
|
||||
binding.webView.url?.let {
|
||||
share(it)
|
||||
@@ -203,6 +227,41 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
|
||||
return AppConfig.isNightTheme
|
||||
}
|
||||
|
||||
private fun showRedirectPolicySubMenu() {
|
||||
val popup = PopupMenu(this, binding.titleBar, Gravity.END)
|
||||
popup.menuInflater.inflate(R.menu.menu_redirect_policy_submenu, popup.menu)
|
||||
setCurrentRedirectPolicyChecked(popup.menu)
|
||||
popup.setOnMenuItemClickListener { menuItem ->
|
||||
handleRedirectPolicySelection(menuItem)
|
||||
true
|
||||
}
|
||||
popup.show()
|
||||
}
|
||||
|
||||
private fun setCurrentRedirectPolicyChecked(menu: Menu) {
|
||||
val currentPolicy = redirectPolicy
|
||||
val menuItemId = when (currentPolicy) {
|
||||
RedirectPolicy.ALLOW_ALL -> R.id.menu_redirect_allow_all
|
||||
RedirectPolicy.ASK_ALWAYS -> R.id.menu_redirect_ask_always
|
||||
RedirectPolicy.ASK_CROSS_ORIGIN -> R.id.menu_redirect_ask_cross_origin
|
||||
RedirectPolicy.BLOCK_CROSS_ORIGIN -> R.id.menu_redirect_block_cross_origin
|
||||
}
|
||||
menu.findItem(menuItemId)?.isChecked = true
|
||||
}
|
||||
|
||||
private fun handleRedirectPolicySelection(menuItem: MenuItem) {
|
||||
val selectedPolicy = when (menuItem.itemId) {
|
||||
R.id.menu_redirect_allow_all -> RedirectPolicy.ALLOW_ALL
|
||||
R.id.menu_redirect_ask_always -> RedirectPolicy.ASK_ALWAYS
|
||||
R.id.menu_redirect_ask_cross_origin -> RedirectPolicy.ASK_CROSS_ORIGIN
|
||||
R.id.menu_redirect_block_cross_origin -> RedirectPolicy.BLOCK_CROSS_ORIGIN
|
||||
else -> RedirectPolicy.ALLOW_ALL
|
||||
}
|
||||
|
||||
updateRedirectPolicy(selectedPolicy)
|
||||
toastOnUi("重定向策略已更新")
|
||||
}
|
||||
|
||||
private fun toggleFullScreen() {
|
||||
isFullScreen = !isFullScreen
|
||||
|
||||
@@ -277,6 +336,9 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
|
||||
private fun initLiveData() {
|
||||
viewModel.contentLiveData.observe(this) { content ->
|
||||
viewModel.rssArticle?.let {
|
||||
if (viewModel.rssSource != null) {
|
||||
redirectPolicy = RedirectPolicy.fromString(viewModel.rssSource?.redirectPolicy)
|
||||
}
|
||||
upJavaScriptEnable()
|
||||
val url = NetworkUtils.getAbsoluteURL(it.origin, it.link)
|
||||
val html = viewModel.clHtml(content)
|
||||
@@ -306,6 +368,14 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
|
||||
}
|
||||
}
|
||||
|
||||
fun updateRedirectPolicy(policy: RedirectPolicy) {
|
||||
viewModel.rssSource?.let { source ->
|
||||
source.redirectPolicy = policy.name
|
||||
redirectPolicy = policy
|
||||
viewModel.updateRssSourceRedirectPolicy(source.sourceUrl)
|
||||
}
|
||||
}
|
||||
|
||||
private fun upStarMenu() {
|
||||
starMenuItem?.isVisible = viewModel.rssArticle != null
|
||||
if (viewModel.rssStar != null) {
|
||||
@@ -382,19 +452,74 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
|
||||
}
|
||||
|
||||
inner class CustomWebViewClient : WebViewClient() {
|
||||
|
||||
private var lastUrl: String? = null
|
||||
override fun shouldOverrideUrlLoading(
|
||||
view: WebView,
|
||||
request: WebResourceRequest
|
||||
): Boolean {
|
||||
val currentUrl = lastUrl ?: view.url
|
||||
val targetUrl = request.url.toString()
|
||||
lastUrl = targetUrl
|
||||
if (!request.isForMainFrame) return false
|
||||
if (handleRedirect(view, currentUrl, targetUrl)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return shouldOverrideUrlLoading(request.url)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION", "KotlinRedundantDiagnosticSuppress")
|
||||
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
|
||||
val currentUrl = lastUrl ?: view.url
|
||||
val targetUrl = url
|
||||
lastUrl = targetUrl
|
||||
if (handleRedirect(view, currentUrl, targetUrl)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return shouldOverrideUrlLoading(url.toUri())
|
||||
}
|
||||
|
||||
private fun handleRedirect(view: WebView, fromUrl: String?, toUrl: String): Boolean {
|
||||
val fromHost = fromUrl?.toUri()?.host
|
||||
val toHost = toUrl.toUri().host
|
||||
val crossOrigin = fromHost != null && toHost != null && fromHost != toHost
|
||||
|
||||
return when (redirectPolicy) {
|
||||
RedirectPolicy.ALLOW_ALL -> false
|
||||
RedirectPolicy.ASK_ALWAYS -> {
|
||||
askUser(fromUrl, toUrl) { if (it) view.loadUrl(toUrl) }
|
||||
true
|
||||
}
|
||||
RedirectPolicy.ASK_CROSS_ORIGIN -> {
|
||||
if (crossOrigin) {
|
||||
askUser(fromUrl, toUrl) { if (it) view.loadUrl(toUrl) }
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
RedirectPolicy.BLOCK_CROSS_ORIGIN -> {
|
||||
if (crossOrigin) {
|
||||
toastOnUi("已阻止跨域重定向")
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun askUser(fromUrl: String?, toUrl: String, onResult: (Boolean) -> Unit) {
|
||||
AlertDialog.Builder(this@ReadRssActivity)
|
||||
.setTitle("重定向请求")
|
||||
.setMessage("是否允许页面跳转?\n\n来源:${fromUrl ?: "未知"}\n目标:$toUrl")
|
||||
.setPositiveButton("允许") { _, _ -> onResult(true) }
|
||||
.setNegativeButton("拒绝") { _, _ -> onResult(false) }
|
||||
.setCancelable(true)
|
||||
.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果有黑名单,黑名单匹配返回空白,
|
||||
* 没有黑名单再判断白名单,在白名单中的才通过,
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.script.rhino.runScriptWithContext
|
||||
import io.legado.app.base.BaseViewModel
|
||||
import io.legado.app.constant.AppLog
|
||||
import io.legado.app.data.appDb
|
||||
import io.legado.app.data.entities.RssArticle
|
||||
import io.legado.app.data.entities.RssSource
|
||||
@@ -242,6 +243,16 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application) {
|
||||
tts?.speak(text)
|
||||
}
|
||||
|
||||
fun updateRssSourceRedirectPolicy(redirectPolicy: String) {
|
||||
execute {
|
||||
val source = rssSource ?: return@execute
|
||||
source.redirectPolicy = redirectPolicy
|
||||
appDb.rssSourceDao.updateRedirectPolicy(source.sourceUrl, redirectPolicy)
|
||||
}.onError {
|
||||
appCtx.toastOnUi("保存失败: ${it.localizedMessage}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
tts?.clearTts()
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
android:stateListAnimator="@null">
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
android:id="@+id/ctl_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
|
||||
style="?attr/collapsingToolbarLayoutMediumStyle"
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group android:checkableBehavior="single">
|
||||
<item
|
||||
android:id="@+id/menu_redirect_allow_all"
|
||||
android:title="@string/redirect_allow_all" />
|
||||
<item
|
||||
android:id="@+id/menu_redirect_ask_always"
|
||||
android:title="@string/redirect_ask_always" />
|
||||
<item
|
||||
android:id="@+id/menu_redirect_ask_cross_origin"
|
||||
android:title="@string/redirect_ask_cross_origin" />
|
||||
<item
|
||||
android:id="@+id/menu_redirect_block_cross_origin"
|
||||
android:title="@string/redirect_block_cross_origin" />
|
||||
</group>
|
||||
</menu>
|
||||
@@ -37,4 +37,9 @@
|
||||
android:title="@string/clear"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_redirect_policy"
|
||||
android:title="@string/redirect_policy"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
||||
@@ -38,6 +38,11 @@
|
||||
android:title="@string/login"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_redirect_policy"
|
||||
android:title="@string/redirect_policy"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_browser_open"
|
||||
android:title="@string/open_in_browser"
|
||||
|
||||
@@ -1313,4 +1313,9 @@
|
||||
<string name="screen_settings">屏幕显示与亮度</string>
|
||||
<string name="reading_behavior">阅读行为</string>
|
||||
<string name="page_control">翻页控制</string>
|
||||
<string name="redirect_policy">网页重定向策略</string>
|
||||
<string name="redirect_allow_all">允许所有重定向</string>
|
||||
<string name="redirect_ask_always">总是询问</string>
|
||||
<string name="redirect_ask_cross_origin">跨域时询问</string>
|
||||
<string name="redirect_block_cross_origin">阻止跨域重定向</string>
|
||||
</resources>
|
||||
|
||||
@@ -1314,4 +1314,9 @@
|
||||
<string name="screen_settings">屏幕显示与亮度</string>
|
||||
<string name="reading_behavior">阅读行为</string>
|
||||
<string name="page_control">翻页控制</string>
|
||||
<string name="redirect_policy">网页重定向策略</string>
|
||||
<string name="redirect_allow_all">允许所有重定向</string>
|
||||
<string name="redirect_ask_always">总是询问</string>
|
||||
<string name="redirect_ask_cross_origin">跨域时询问</string>
|
||||
<string name="redirect_block_cross_origin">阻止跨域重定向</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user