Merge pull request #4193 from NihilityT/master

验证流程问题修复
This commit is contained in:
Horis
2024-09-09 19:21:41 +08:00
committed by GitHub
9 changed files with 79 additions and 50 deletions
+2 -1
View File
@@ -20,7 +20,8 @@
|变量名|调用类| |变量名|调用类|
|------|-----| |------|-----|
|java|当前类| |java|当前类|
|baseUrl|当前url,String | |baseUrl|String,当前 url,如 `https://example.com/page/1`|
|baseUrlWithOptions|String,带参数的当前 url,如 `https://example.com/page/1,{"webView": true}`|
|result|上一步的结果| |result|上一步的结果|
|book|[书籍类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/Book.kt)| |book|[书籍类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/Book.kt)|
|chapter|[章节类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/BookChapter.kt)| |chapter|[章节类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/BookChapter.kt)|
@@ -226,10 +226,10 @@ interface JsExtensions : JsEncodeUtils {
/** /**
* 使用内置浏览器打开链接,并等待网页结果 * 使用内置浏览器打开链接,并等待网页结果
*/ */
fun startBrowserAwait(url: String, title: String): StrResponse { fun startBrowserAwait(url: String, title: String, refetchAfterSuccess: Boolean = true): StrResponse {
return StrResponse( return StrResponse(
url, url,
SourceVerificationHelp.getVerificationResult(getSource(), url, title, true) SourceVerificationHelp.getVerificationResult(getSource(), url, title, true, refetchAfterSuccess)
) )
} }
@@ -19,8 +19,8 @@ object SourceVerificationHelp {
private val waitTime = 1.minutes.inWholeNanoseconds private val waitTime = 1.minutes.inWholeNanoseconds
private fun getKey(source: BaseSource) = getKey(source.getKey()) private fun getVerificationResultKey(source: BaseSource) = getVerificationResultKey(source.getKey())
fun getKey(sourceKey: String) = "${sourceKey}_verificationResult" private fun getVerificationResultKey(sourceKey: String) = "${sourceKey}_verificationResult"
/** /**
* 获取书源验证结果 * 获取书源验证结果
@@ -30,27 +30,27 @@ object SourceVerificationHelp {
source: BaseSource?, source: BaseSource?,
url: String, url: String,
title: String, title: String,
useBrowser: Boolean useBrowser: Boolean,
refetchAfterSuccess: Boolean = true
): String { ): String {
source source
?: throw NoStackTraceException("getVerificationResult parameter source cannot be null") ?: throw NoStackTraceException("getVerificationResult parameter source cannot be null")
val key = getKey(source) clearResult(source.getKey())
CacheManager.delete(key)
if (!useBrowser) { if (!useBrowser) {
appCtx.startActivity<VerificationCodeActivity> { appCtx.startActivity<VerificationCodeActivity> {
putExtra("imageUrl", url) putExtra("imageUrl", url)
putExtra("sourceOrigin", source.getKey()) putExtra("sourceOrigin", source.getKey())
putExtra("sourceName", source.getTag()) putExtra("sourceName", source.getTag())
IntentData.put(key, Thread.currentThread()) IntentData.put(getVerificationResultKey(source), Thread.currentThread())
} }
} else { } else {
startBrowser(source, url, title, true) startBrowser(source, url, title, true, refetchAfterSuccess)
} }
var waitUserInput = false var waitUserInput = false
while (CacheManager.get(key) == null) { while (getResult(source.getKey()) == null) {
if (!waitUserInput) { if (!waitUserInput) {
AppLog.putDebug("等待返回验证结果...") AppLog.putDebug("等待返回验证结果...")
waitUserInput = true waitUserInput = true
@@ -58,7 +58,7 @@ object SourceVerificationHelp {
LockSupport.parkNanos(this, waitTime) LockSupport.parkNanos(this, waitTime)
} }
return CacheManager.get(key)!!.let { return getResult(source.getKey())!!.let {
it.ifBlank { it.ifBlank {
throw NoStackTraceException("验证结果为空") throw NoStackTraceException("验证结果为空")
} }
@@ -73,25 +73,38 @@ object SourceVerificationHelp {
source: BaseSource?, source: BaseSource?,
url: String, url: String,
title: String, title: String,
saveResult: Boolean? = false saveResult: Boolean? = false,
refetchAfterSuccess: Boolean? = true
) { ) {
source ?: throw NoStackTraceException("startBrowser parameter source cannot be null") source ?: throw NoStackTraceException("startBrowser parameter source cannot be null")
val key = getKey(source)
appCtx.startActivity<WebViewActivity> { appCtx.startActivity<WebViewActivity> {
putExtra("title", title) putExtra("title", title)
putExtra("url", url) putExtra("url", url)
putExtra("sourceOrigin", source.getKey()) putExtra("sourceOrigin", source.getKey())
putExtra("sourceName", source.getTag()) putExtra("sourceName", source.getTag())
putExtra("sourceVerificationEnable", saveResult) putExtra("sourceVerificationEnable", saveResult)
putExtra("refetchAfterSuccess", refetchAfterSuccess)
IntentData.put(url, source.getHeaderMap(true)) IntentData.put(url, source.getHeaderMap(true))
IntentData.put(key, Thread.currentThread()) IntentData.put(getVerificationResultKey(source), Thread.currentThread())
} }
} }
fun checkResult(key: String) { fun checkResult(sourceKey: String) {
CacheManager.get(key) ?: CacheManager.putMemory(key, "") getResult(sourceKey) ?: setResult(sourceKey, "")
val thread = IntentData.get<Thread>(key) val thread = IntentData.get<Thread>(getVerificationResultKey(sourceKey))
LockSupport.unpark(thread) LockSupport.unpark(thread)
} }
fun setResult(sourceKey: String, result: String?) {
CacheManager.putMemory(getVerificationResultKey(sourceKey), result ?: "")
}
fun getResult(sourceKey: String): String? {
return CacheManager.get(getVerificationResultKey(sourceKey))
}
fun clearResult(sourceKey: String) {
CacheManager.delete(getVerificationResultKey(sourceKey))
}
} }
@@ -54,6 +54,8 @@ class AnalyzeRule(
private set private set
var baseUrl: String? = null var baseUrl: String? = null
private set private set
var optionStr: String = "{}"
private set
var redirectUrl: URL? = null var redirectUrl: URL? = null
private set private set
private var isJSON: Boolean = false private var isJSON: Boolean = false
@@ -63,10 +65,6 @@ class AnalyzeRule(
private var analyzeByJSoup: AnalyzeByJSoup? = null private var analyzeByJSoup: AnalyzeByJSoup? = null
private var analyzeByJSonPath: AnalyzeByJSonPath? = null private var analyzeByJSonPath: AnalyzeByJSonPath? = null
private var objectChangedXP = false
private var objectChangedJS = false
private var objectChangedJP = false
private val stringRuleCache = hashMapOf<String, List<SourceRule>>() private val stringRuleCache = hashMapOf<String, List<SourceRule>>()
private var coroutineContext: CoroutineContext = EmptyCoroutineContext private var coroutineContext: CoroutineContext = EmptyCoroutineContext
@@ -80,9 +78,9 @@ class AnalyzeRule(
else -> content.toString().isJson() else -> content.toString().isJson()
} }
setBaseUrl(baseUrl) setBaseUrl(baseUrl)
objectChangedXP = true analyzeByXPath = null
objectChangedJS = true analyzeByJSoup = null
objectChangedJP = true analyzeByJSonPath = null
return this return this
} }
@@ -98,6 +96,11 @@ class AnalyzeRule(
return this return this
} }
fun setOption(option: String): AnalyzeRule {
optionStr = option
return this
}
fun setRedirectUrl(url: String): URL? { fun setRedirectUrl(url: String): URL? {
try { try {
redirectUrl = URL(url) redirectUrl = URL(url)
@@ -114,9 +117,8 @@ class AnalyzeRule(
return if (o != content) { return if (o != content) {
AnalyzeByXPath(o) AnalyzeByXPath(o)
} else { } else {
if (analyzeByXPath == null || objectChangedXP) { if (analyzeByXPath == null) {
analyzeByXPath = AnalyzeByXPath(content!!) analyzeByXPath = AnalyzeByXPath(content!!)
objectChangedXP = false
} }
analyzeByXPath!! analyzeByXPath!!
} }
@@ -129,9 +131,8 @@ class AnalyzeRule(
return if (o != content) { return if (o != content) {
AnalyzeByJSoup(o) AnalyzeByJSoup(o)
} else { } else {
if (analyzeByJSoup == null || objectChangedJS) { if (analyzeByJSoup == null) {
analyzeByJSoup = AnalyzeByJSoup(content!!) analyzeByJSoup = AnalyzeByJSoup(content!!)
objectChangedJS = false
} }
analyzeByJSoup!! analyzeByJSoup!!
} }
@@ -144,9 +145,8 @@ class AnalyzeRule(
return if (o != content) { return if (o != content) {
AnalyzeByJSonPath(o) AnalyzeByJSonPath(o)
} else { } else {
if (analyzeByJSonPath == null || objectChangedJP) { if (analyzeByJSonPath == null) {
analyzeByJSonPath = AnalyzeByJSonPath(content!!) analyzeByJSonPath = AnalyzeByJSonPath(content!!)
objectChangedJP = false
} }
analyzeByJSonPath!! analyzeByJSonPath!!
} }
@@ -754,6 +754,7 @@ class AnalyzeRule(
bindings["book"] = book bindings["book"] = book
bindings["result"] = result bindings["result"] = result
bindings["baseUrl"] = baseUrl bindings["baseUrl"] = baseUrl
bindings["baseUrlWithOptions"] = "$baseUrl,$optionStr"
bindings["chapter"] = chapter bindings["chapter"] = chapter
bindings["title"] = chapter?.title bindings["title"] = chapter?.title
bindings["src"] = content bindings["src"] = content
@@ -76,6 +76,7 @@ class AnalyzeUrl(
var type: String? = null var type: String? = null
private set private set
val headerMap = HashMap<String, String>() val headerMap = HashMap<String, String>()
var optionStr: String = ""
private var urlNoQuery: String = "" private var urlNoQuery: String = ""
private var queryStr: String? = null private var queryStr: String? = null
private val fieldMap = LinkedHashMap<String, String>() private val fieldMap = LinkedHashMap<String, String>()
@@ -193,8 +194,10 @@ class AnalyzeUrl(
baseUrl = it baseUrl = it
} }
if (urlNoOption.length != ruleUrl.length) { if (urlNoOption.length != ruleUrl.length) {
GSON.fromJsonObject<UrlOption>(ruleUrl.substring(urlMatcher.end())).getOrNull() val optionStr = ruleUrl.substring(urlMatcher.end())
GSON.fromJsonObject<UrlOption>(optionStr).getOrNull()
?.let { option -> ?.let { option ->
this.optionStr = optionStr
option.getMethod()?.let { option.getMethod()?.let {
if (it.equals("POST", true)) method = RequestMethod.POST if (it.equals("POST", true)) method = RequestMethod.POST
} }
@@ -43,7 +43,7 @@ object BookList {
Debug.log(bookSource.bookSourceUrl, "≡获取成功:${analyzeUrl.ruleUrl}") Debug.log(bookSource.bookSourceUrl, "≡获取成功:${analyzeUrl.ruleUrl}")
Debug.log(bookSource.bookSourceUrl, body, state = 10) Debug.log(bookSource.bookSourceUrl, body, state = 10)
val analyzeRule = AnalyzeRule(ruleData, bookSource) val analyzeRule = AnalyzeRule(ruleData, bookSource)
analyzeRule.setContent(body).setBaseUrl(baseUrl) analyzeRule.setContent(body).setBaseUrl(baseUrl).setOption(analyzeUrl.optionStr)
analyzeRule.setRedirectUrl(baseUrl) analyzeRule.setRedirectUrl(baseUrl)
analyzeRule.setCoroutineContext(coroutineContext) analyzeRule.setCoroutineContext(coroutineContext)
if (isSearch) bookSource.bookUrlPattern?.let { if (isSearch) bookSource.bookUrlPattern?.let {
@@ -57,7 +57,6 @@ class VerificationCodeDialog() : BaseDialogFragment(R.layout.dialog_verification
} }
private var sourceOrigin: String? = null private var sourceOrigin: String? = null
private var key = ""
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?): Unit = binding.run { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?): Unit = binding.run {
initMenu() initMenu()
@@ -65,7 +64,6 @@ class VerificationCodeDialog() : BaseDialogFragment(R.layout.dialog_verification
toolBar.setBackgroundColor(primaryColor) toolBar.setBackgroundColor(primaryColor)
toolBar.subtitle = arguments.getString("sourceName") toolBar.subtitle = arguments.getString("sourceName")
sourceOrigin = arguments.getString("sourceOrigin") sourceOrigin = arguments.getString("sourceOrigin")
key = SourceVerificationHelp.getKey(sourceOrigin!!)
val imageUrl = arguments.getString("imageUrl") ?: return@run val imageUrl = arguments.getString("imageUrl") ?: return@run
loadImage(imageUrl, sourceOrigin) loadImage(imageUrl, sourceOrigin)
verificationCodeImageView.setOnClickListener { verificationCodeImageView.setOnClickListener {
@@ -119,7 +117,7 @@ class VerificationCodeDialog() : BaseDialogFragment(R.layout.dialog_verification
when (item.itemId) { when (item.itemId) {
R.id.menu_ok -> { R.id.menu_ok -> {
val verificationCode = binding.verificationCode.text.toString() val verificationCode = binding.verificationCode.text.toString()
CacheManager.putMemory(key, verificationCode) SourceVerificationHelp.setResult(sourceOrigin!!, verificationCode)
dismiss() dismiss()
} }
} }
@@ -127,7 +125,7 @@ class VerificationCodeDialog() : BaseDialogFragment(R.layout.dialog_verification
} }
override fun onDestroy() { override fun onDestroy() {
SourceVerificationHelp.checkResult(key) SourceVerificationHelp.checkResult(sourceOrigin!!)
super.onDestroy() super.onDestroy()
activity?.finish() activity?.finish()
} }
@@ -82,7 +82,7 @@ class WebViewActivity : VMBaseActivity<ActivityWebViewBinding, WebViewModel>() {
R.id.menu_copy_url -> sendToClip(viewModel.baseUrl) R.id.menu_copy_url -> sendToClip(viewModel.baseUrl)
R.id.menu_ok -> { R.id.menu_ok -> {
if (viewModel.sourceVerificationEnable) { if (viewModel.sourceVerificationEnable) {
viewModel.saveVerificationResult(intent) { viewModel.saveVerificationResult(binding.webView) {
finish() finish()
} }
} else { } else {
@@ -158,7 +158,7 @@ class WebViewActivity : VMBaseActivity<ActivityWebViewBinding, WebViewModel>() {
} }
override fun finish() { override fun finish() {
SourceVerificationHelp.checkResult(viewModel.key) SourceVerificationHelp.checkResult(viewModel.sourceOrigin)
super.finish() super.finish()
} }
@@ -224,7 +224,7 @@ class WebViewActivity : VMBaseActivity<ActivityWebViewBinding, WebViewModel>() {
if (it == "true") { if (it == "true") {
isCloudflareChallenge = true isCloudflareChallenge = true
} else if (isCloudflareChallenge && viewModel.sourceVerificationEnable) { } else if (isCloudflareChallenge && viewModel.sourceVerificationEnable) {
viewModel.saveVerificationResult(intent) { viewModel.saveVerificationResult(binding.webView) {
finish() finish()
} }
} }
@@ -5,6 +5,7 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.util.Base64 import android.util.Base64
import android.webkit.URLUtil import android.webkit.URLUtil
import android.webkit.WebView
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst
@@ -22,27 +23,30 @@ import io.legado.app.utils.isContentScheme
import io.legado.app.utils.printOnDebug import io.legado.app.utils.printOnDebug
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import io.legado.app.utils.writeBytes import io.legado.app.utils.writeBytes
import org.apache.commons.text.StringEscapeUtils
import java.io.File import java.io.File
import java.util.Date import java.util.Date
class WebViewModel(application: Application) : BaseViewModel(application) { class WebViewModel(application: Application) : BaseViewModel(application) {
var intent: Intent? = null
var baseUrl: String = "" var baseUrl: String = ""
var html: String? = null var html: String? = null
val headerMap: HashMap<String, String> = hashMapOf() val headerMap: HashMap<String, String> = hashMapOf()
var sourceVerificationEnable: Boolean = false var sourceVerificationEnable: Boolean = false
var refetchAfterSuccess: Boolean = true
var sourceOrigin: String = "" var sourceOrigin: String = ""
var key = ""
fun initData( fun initData(
intent: Intent, intent: Intent,
success: () -> Unit success: () -> Unit
) { ) {
execute { execute {
this@WebViewModel.intent = intent
val url = intent.getStringExtra("url") val url = intent.getStringExtra("url")
?: throw NoStackTraceException("url不能为空") ?: throw NoStackTraceException("url不能为空")
sourceOrigin = intent.getStringExtra("sourceOrigin") ?: "" sourceOrigin = intent.getStringExtra("sourceOrigin") ?: ""
key = SourceVerificationHelp.getKey(sourceOrigin)
sourceVerificationEnable = intent.getBooleanExtra("sourceVerificationEnable", false) sourceVerificationEnable = intent.getBooleanExtra("sourceVerificationEnable", false)
refetchAfterSuccess = intent.getBooleanExtra("refetchAfterSuccess", true)
val headerMapF = IntentData.get<Map<String, String>>(url) val headerMapF = IntentData.get<Map<String, String>>(url)
val analyzeUrl = AnalyzeUrl(url, headerMapF = headerMapF) val analyzeUrl = AnalyzeUrl(url, headerMapF = headerMapF)
baseUrl = analyzeUrl.url baseUrl = analyzeUrl.url
@@ -91,21 +95,30 @@ class WebViewModel(application: Application) : BaseViewModel(application) {
} }
} }
fun saveVerificationResult(intent: Intent, success: () -> Unit) { fun saveVerificationResult(webView: WebView, success: () -> Unit) {
execute { if (!sourceVerificationEnable) {
if (sourceVerificationEnable) { execute { success.invoke() }
val url = intent.getStringExtra("url")!! }
if (refetchAfterSuccess) {
execute {
val url = intent!!.getStringExtra("url")!!
val source = appDb.bookSourceDao.getBookSource(sourceOrigin) val source = appDb.bookSourceDao.getBookSource(sourceOrigin)
val key = "${sourceOrigin}_verificationResult"
html = AnalyzeUrl( html = AnalyzeUrl(
url, url,
headerMapF = headerMap, headerMapF = headerMap,
source = source source = source
).getStrResponseAwait(useWebView = false).body ).getStrResponseAwait(useWebView = false).body
CacheManager.putMemory(key, html ?: "") SourceVerificationHelp.setResult(sourceOrigin, html ?: "")
success.invoke()
}
} else {
webView.evaluateJavascript("document.documentElement.outerHTML") {
execute {
html = StringEscapeUtils.unescapeJson(it).trim('"')
SourceVerificationHelp.setResult(sourceOrigin, html ?: "")
success.invoke()
}
} }
}.onSuccess {
success.invoke()
} }
} }