diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d33213e6e..da98accad 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -359,6 +359,13 @@ + + + >() + private val memoryLruCache = object : LruCache(100) { + override fun sizeOf(key: String, value: Cache): Int { + return 1 + } + } /** * saveTime 单位为秒 @@ -23,13 +29,26 @@ object CacheManager { is ByteArray -> ACache.get(appCtx).put(key, value, saveTime) else -> { val cache = Cache(key, value.toString(), deadline) + memoryLruCache.put(key, cache) appDb.cacheDao.insert(cache) } } } fun get(key: String): String? { - return appDb.cacheDao.get(key, System.currentTimeMillis()) + return getFromMemory(key) ?: appDb.cacheDao.get(key, System.currentTimeMillis()) + } + + //从内存中获取数据 使用lrucache 支持过期功能 + private fun getFromMemory(key: String): String? { + val cache = memoryLruCache.get(key) ?: return null + val deadline = cache!!.deadline + return if (deadline == 0L || deadline > System.currentTimeMillis()) { + cache!!.value + } else { + memoryLruCache.remove(key) + null + } } fun getInt(key: String): Int? { @@ -70,6 +89,7 @@ object CacheManager { fun delete(key: String) { appDb.cacheDao.delete(key) + memoryLruCache.remove(key) ACache.get(appCtx).remove(key) } } \ No newline at end of file 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 1d20f676b..e63dc6528 100644 --- a/app/src/main/java/io/legado/app/help/JsExtensions.kt +++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt @@ -15,7 +15,6 @@ import io.legado.app.help.http.* import io.legado.app.model.Debug import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.QueryTTF -import io.legado.app.ui.browser.WebViewActivity import io.legado.app.utils.* import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.async @@ -133,18 +132,28 @@ interface JsExtensions { } /** - * 使用内置浏览器打开链接,可用于获取验证码 手动验证网站防爬 + * 使用内置浏览器打开链接,手动验证网站防爬 * @param url 要打开的链接 * @param title 浏览器页面的标题 */ fun startBrowser(url: String, title: String) { - appCtx.startActivity { - putExtra("title", title) - putExtra("url", url) - IntentData.put(url, getSource()?.getHeaderMap(true)) - } + SourceVerificationHelp.startBrowser(getSource(), url, title) + } + + /** + * 使用内置浏览器打开链接,并等待网页结果 + */ + fun startBrowserAwait(url: String, title: String): StrResponse { + return StrResponse(url, SourceVerificationHelp.getVerificationResult(getSource(), url, title, true)) } + /** + * 打开图片验证码对话框,等待返回验证结果 + */ + fun getVerificationCode(imageUrl: String): String { + return SourceVerificationHelp.getVerificationResult(getSource(), imageUrl, "", false) + } + /** * 可从网络,本地文件(阅读私有缓存目录和书籍保存位置支持相对路径)导入JavaScript脚本 */ diff --git a/app/src/main/java/io/legado/app/help/SourceVerificationHelp.kt b/app/src/main/java/io/legado/app/help/SourceVerificationHelp.kt new file mode 100644 index 000000000..3f3a302dc --- /dev/null +++ b/app/src/main/java/io/legado/app/help/SourceVerificationHelp.kt @@ -0,0 +1,69 @@ +package io.legado.app.help + +import io.legado.app.utils.startActivity +import io.legado.app.constant.AppLog +import io.legado.app.exception.NoStackTraceException +import io.legado.app.data.entities.BaseSource +import io.legado.app.ui.browser.WebViewActivity +import io.legado.app.ui.association.VerificationCodeActivity +import splitties.init.appCtx +import kotlinx.coroutines.runBlocking + + +object SourceVerificationHelp { + + /** + * 获取书源验证结果 + * 图片验证码 防爬 滑动验证码 点击字符 等等 + */ + fun getVerificationResult(source: BaseSource?, url: String, title: String, useBrowser: Boolean): String { + source ?: throw NoStackTraceException("getVerificationResult parameter source cannot be null") + return runBlocking { + val key = "${source?.getKey() ?: ""}_verificationResult" + CacheManager.delete(key) + + if (!useBrowser) { + appCtx.startActivity { + putExtra("imageUrl", url) + putExtra("sourceOrigin", source?.getKey()) + } + } else { + startBrowser(source, url, title, true) + } + + var waitUserInput: Boolean = false + while(CacheManager.get(key) == null) { + if (!waitUserInput) { + AppLog.putDebug("等待返回验证结果...") + waitUserInput = true + } + } + CacheManager.get(key)!!.let { + if (it.isBlank()) { + throw NoStackTraceException("验证结果为空") + } else { + it + } + } + } + } + + /** + * 启动内置浏览器 + @param saveResult 保存网页源代码到数据库 + */ + fun startBrowser(source: BaseSource?, url: String, title: String, saveResult: Boolean? = false) { + source ?: throw NoStackTraceException("startBrowser parameter source cannot be null") + appCtx.startActivity { + putExtra("title", title) + putExtra("url", url) + putExtra("sourceOrigin", source?.getKey()) + putExtra("sourceVerificationEnable", saveResult) + IntentData.put(url, source?.getHeaderMap(true)) + } + } + + private fun checkActivityStatus(): Boolean { + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/association/VerificationCodeActivity.kt b/app/src/main/java/io/legado/app/ui/association/VerificationCodeActivity.kt new file mode 100644 index 000000000..13ade4694 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/VerificationCodeActivity.kt @@ -0,0 +1,26 @@ +package io.legado.app.ui.association + +import android.os.Bundle +import io.legado.app.base.BaseActivity +import io.legado.app.databinding.ActivityTranslucenceBinding +import io.legado.app.utils.showDialogFragment +import io.legado.app.utils.viewbindingdelegate.viewBinding + +/** + * 验证码 + */ +class VerificationCodeActivity : + BaseActivity() { + + override val binding by viewBinding(ActivityTranslucenceBinding::inflate) + + override fun onActivityCreated(savedInstanceState: Bundle?) { + intent.getStringExtra("imageUrl")?.let { + val sourceOrigin = intent.getStringExtra("sourceOrigin") + showDialogFragment( + VerificationCodeDialog(it, sourceOrigin) + ) + } ?: finish() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/association/VerificationCodeDialog.kt b/app/src/main/java/io/legado/app/ui/association/VerificationCodeDialog.kt new file mode 100644 index 000000000..0e8265f7a --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/VerificationCodeDialog.kt @@ -0,0 +1,84 @@ +package io.legado.app.ui.association + +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import android.net.Uri +import com.bumptech.glide.request.RequestOptions +import io.legado.app.R +import io.legado.app.base.BaseDialogFragment +import io.legado.app.databinding.DialogVerificationCodeViewBinding +import io.legado.app.help.CacheManager +import io.legado.app.help.glide.ImageLoader +import io.legado.app.help.glide.OkHttpModelLoader +import io.legado.app.lib.theme.primaryColor +import io.legado.app.ui.widget.dialog.PhotoDialog +import io.legado.app.utils.setLayout +import io.legado.app.utils.showDialogFragment +import io.legado.app.utils.viewbindingdelegate.viewBinding + +/** + * 图片验证码对话框 + * 结果保存在数据库中 + * val key = "${sourceOrigin ?: ""}_verificationResult" + * CacheManager.get(key) + */ +class VerificationCodeDialog() : BaseDialogFragment(R.layout.dialog_verification_code_view) { + + constructor(imageUrl: String, sourceOrigin: String? = null) : this() { + arguments = Bundle().apply { + putString("sourceOrigin", sourceOrigin) + putString("imageUrl", imageUrl) + } + } + + val binding by viewBinding(DialogVerificationCodeViewBinding::bind) + + override fun onStart() { + super.onStart() + setLayout(1f, ViewGroup.LayoutParams.WRAP_CONTENT) + } + + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + binding.run { + toolBar.setBackgroundColor(primaryColor) + val sourceOrigin = arguments?.getString("sourceOrigin") + val key = "${sourceOrigin ?: ""}_verificationResult" + arguments?.getString("imageUrl")?.let { imageUrl -> + ImageLoader.load(requireContext(), imageUrl).apply { + sourceOrigin?.let { + apply( + RequestOptions().set( + OkHttpModelLoader.sourceOriginOption, + it + ) + ) + } + }.error(R.drawable.image_loading_error) + .into(ivImage) + ivImage.setOnClickListener { + showDialogFragment(PhotoDialog(imageUrl, sourceOrigin)) + } + } + tvOk.setOnClickListener { + val verificationCode = binding.verificationCode.text.toString() + verificationCode?.let { + CacheManager.put(key, it) + dismiss() + } + } + tvCancel.setOnClickListener { + dismiss() + } + } + } + + override fun onDestroy() { + val sourceOrigin = arguments?.getString("sourceOrigin") + val key = "${sourceOrigin ?: ""}_verificationResult" + CacheManager.get(key) ?: CacheManager.put(key, "") + super.onDestroy() + activity?.finish() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/browser/WebViewActivity.kt b/app/src/main/java/io/legado/app/ui/browser/WebViewActivity.kt index e4532d566..3973b2023 100644 --- a/app/src/main/java/io/legado/app/ui/browser/WebViewActivity.kt +++ b/app/src/main/java/io/legado/app/ui/browser/WebViewActivity.kt @@ -26,6 +26,7 @@ import io.legado.app.ui.document.HandleFileContract import io.legado.app.utils.* import io.legado.app.utils.viewbindingdelegate.viewBinding import java.net.URLDecoder +import kotlinx.coroutines.runBlocking class WebViewActivity : VMBaseActivity() { @@ -65,6 +66,12 @@ class WebViewActivity : VMBaseActivity() { when (item.itemId) { R.id.menu_open_in_browser -> openUrl(viewModel.baseUrl) R.id.menu_copy_url -> sendToClip(viewModel.baseUrl) + R.id.menu_ok -> { + if (viewModel.sourceVerificationEnable) { + binding.titleBar.snackbar(R.string.ok) + finish() + } + } } return super.onCompatOptionsItemSelected(item) } @@ -135,7 +142,6 @@ class WebViewActivity : VMBaseActivity() { viewModel.saveImage(webPic, path) } } - private fun selectSaveFolder() { val default = arrayListOf>() val path = ACache.get(this).getAsString(imagePathKey) @@ -176,6 +182,9 @@ class WebViewActivity : VMBaseActivity() { override fun onDestroy() { super.onDestroy() + runBlocking { + viewModel.saveVerificationResult() + } binding.webView.destroy() } diff --git a/app/src/main/java/io/legado/app/ui/browser/WebViewModel.kt b/app/src/main/java/io/legado/app/ui/browser/WebViewModel.kt index 16c813ef4..7471db654 100644 --- a/app/src/main/java/io/legado/app/ui/browser/WebViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/browser/WebViewModel.kt @@ -9,6 +9,7 @@ import androidx.documentfile.provider.DocumentFile import io.legado.app.base.BaseViewModel import io.legado.app.constant.AppConst import io.legado.app.exception.NoStackTraceException +import io.legado.app.help.CacheManager import io.legado.app.help.IntentData import io.legado.app.help.http.newCallResponseBody import io.legado.app.help.http.okHttpClient @@ -21,6 +22,8 @@ class WebViewModel(application: Application) : BaseViewModel(application) { var baseUrl: String = "" var html: String? = null val headerMap: HashMap = hashMapOf() + var sourceVerificationEnable: Boolean = false + var sourceOrigin: String = "" fun initData( intent: Intent, @@ -29,6 +32,8 @@ class WebViewModel(application: Application) : BaseViewModel(application) { execute { val url = intent.getStringExtra("url") ?: throw NoStackTraceException("url不能为空") + sourceOrigin = intent.getStringExtra("sourceOrigin") ?: "" + sourceVerificationEnable = intent.getBooleanExtra("sourceVerificationEnable", false) val headerMapF = IntentData.get>(url) val analyzeUrl = AnalyzeUrl(url, headerMapF = headerMapF) baseUrl = analyzeUrl.url @@ -78,5 +83,12 @@ class WebViewModel(application: Application) : BaseViewModel(application) { } } + fun saveVerificationResult() { + if (sourceVerificationEnable) { + val key = "${sourceOrigin}_verificationResult" + html = AnalyzeUrl(baseUrl, headerMapF = headerMap).getStrResponse(useWebView = false).body + CacheManager.put(key, html ?: "") + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/login/WebViewLoginFragment.kt b/app/src/main/java/io/legado/app/ui/login/WebViewLoginFragment.kt index c9feb1c4d..01e3a323d 100644 --- a/app/src/main/java/io/legado/app/ui/login/WebViewLoginFragment.kt +++ b/app/src/main/java/io/legado/app/ui/login/WebViewLoginFragment.kt @@ -35,7 +35,7 @@ class WebViewLoginFragment : BaseFragment(R.layout.fragment_web_view_login) { } override fun onCompatCreateOptionsMenu(menu: Menu) { - menuInflater.inflate(R.menu.source_login, menu) + menuInflater.inflate(R.menu.source_webview_login, menu) } override fun onCompatOptionsItemSelected(item: MenuItem) { diff --git a/app/src/main/res/layout/dialog_verification_code_view.xml b/app/src/main/res/layout/dialog_verification_code_view.xml new file mode 100644 index 000000000..c218ba54d --- /dev/null +++ b/app/src/main/res/layout/dialog_verification_code_view.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/source_webview_login.xml b/app/src/main/res/menu/source_webview_login.xml new file mode 100644 index 000000000..5723c1b13 --- /dev/null +++ b/app/src/main/res/menu/source_webview_login.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/web_view.xml b/app/src/main/res/menu/web_view.xml index 281e17243..57b095c91 100644 --- a/app/src/main/res/menu/web_view.xml +++ b/app/src/main/res/menu/web_view.xml @@ -2,6 +2,12 @@ + + 批量换源 书籍类型不一样 是否确认换源 + 输入验证码 + 验证码 diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 3385b091b..11a2729de 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -972,5 +972,7 @@ 批量换源 书籍类型不一样 是否确认换源 + 输入验证码 + 验证码 diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 863b931ad..f0e4b8cf3 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -972,5 +972,7 @@ 批量换源 书籍类型不一样 是否确认换源 + 输入验证码 + 验证码 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 416e92a93..7b938e9f3 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -969,5 +969,7 @@ 批量换源 书籍类型不一样 是否确认换源 + 输入验证码 + 验证码 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 874d66764..6ba87de41 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -971,5 +971,7 @@ 批量换源 书籍类型不一样 是否确认换源 + 输入验证码 + 验证码 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 6b9b415d0..7b0f3ef18 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -971,5 +971,7 @@ 批量换源 书籍类型不一样 是否确认换源 + 输入验证码 + 验证码 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bc5f63327..05df4e53b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -972,5 +972,7 @@ 批量换源 书籍类型不一样 是否确认换源 + 输入验证码 + 验证码