Browse Source

Show help banner when Cloudflare captcha page is shown in WebView

arkon 1 năm trước cách đây
mục cha
commit
6d69caf59e

+ 81 - 52
app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt

@@ -4,8 +4,9 @@ import android.content.pm.ApplicationInfo
 import android.graphics.Bitmap
 import android.webkit.WebResourceRequest
 import android.webkit.WebView
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material.icons.Icons
@@ -17,9 +18,11 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalUriHandler
 import androidx.compose.ui.res.stringResource
 import com.google.accompanist.web.AccompanistWebViewClient
 import com.google.accompanist.web.LoadingState
@@ -28,9 +31,12 @@ import com.google.accompanist.web.rememberWebViewNavigator
 import com.google.accompanist.web.rememberWebViewState
 import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.components.AppBarActions
+import eu.kanade.presentation.components.WarningBanner
 import eu.kanade.tachiyomi.BuildConfig
 import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.util.system.getHtml
 import eu.kanade.tachiyomi.util.system.setDefaultSettings
+import kotlinx.coroutines.launch
 import tachiyomi.presentation.core.components.material.Scaffold
 
 @Composable
@@ -46,7 +52,53 @@ fun WebViewScreenContent(
 ) {
     val state = rememberWebViewState(url = url, additionalHttpHeaders = headers)
     val navigator = rememberWebViewNavigator()
+    val uriHandler = LocalUriHandler.current
+    val scope = rememberCoroutineScope()
+
     var currentUrl by remember { mutableStateOf(url) }
+    var showCloudflareHelp by remember { mutableStateOf(false) }
+
+    val webClient = remember {
+        object : AccompanistWebViewClient() {
+            override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
+                super.onPageStarted(view, url, favicon)
+                url?.let {
+                    currentUrl = it
+                    onUrlChange(it)
+                }
+            }
+
+            override fun onPageFinished(view: WebView, url: String?) {
+                super.onPageFinished(view, url)
+                scope.launch {
+                    val html = view.getHtml()
+                    showCloudflareHelp = "Checking if the site connection is secure" in html
+                }
+            }
+
+            override fun doUpdateVisitedHistory(
+                view: WebView,
+                url: String?,
+                isReload: Boolean,
+            ) {
+                super.doUpdateVisitedHistory(view, url, isReload)
+                url?.let {
+                    currentUrl = it
+                    onUrlChange(it)
+                }
+            }
+
+            override fun shouldOverrideUrlLoading(
+                view: WebView?,
+                request: WebResourceRequest?,
+            ): Boolean {
+                request?.let {
+                    view?.loadUrl(it.url.toString(), headers)
+                }
+                return super.shouldOverrideUrlLoading(view, request)
+            }
+        }
+    }
 
     Scaffold(
         topBar = {
@@ -116,61 +168,38 @@ fun WebViewScreenContent(
             }
         },
     ) { contentPadding ->
-        val webClient = remember {
-            object : AccompanistWebViewClient() {
-                override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
-                    super.onPageStarted(view, url, favicon)
-                    url?.let {
-                        currentUrl = it
-                        onUrlChange(it)
-                    }
-                }
+        Column(
+            modifier = Modifier.padding(contentPadding),
+        ) {
+            if (showCloudflareHelp) {
+                WarningBanner(
+                    textRes = R.string.information_cloudflare_help,
+                    modifier = Modifier.clickable {
+                        uriHandler.openUri("https://tachiyomi.org/help/guides/troubleshooting/#solving-cloudflare-issues")
+                    },
+                )
+            }
+
+            WebView(
+                state = state,
+                modifier = Modifier.weight(1f),
+                navigator = navigator,
+                onCreated = { webView ->
+                    webView.setDefaultSettings()
 
-                override fun doUpdateVisitedHistory(
-                    view: WebView,
-                    url: String?,
-                    isReload: Boolean,
-                ) {
-                    super.doUpdateVisitedHistory(view, url, isReload)
-                    url?.let {
-                        currentUrl = it
-                        onUrlChange(it)
+                    // Debug mode (chrome://inspect/#devices)
+                    if (BuildConfig.DEBUG &&
+                        0 != webView.context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
+                    ) {
+                        WebView.setWebContentsDebuggingEnabled(true)
                     }
-                }
 
-                override fun shouldOverrideUrlLoading(
-                    view: WebView?,
-                    request: WebResourceRequest?,
-                ): Boolean {
-                    request?.let {
-                        view?.loadUrl(it.url.toString(), headers)
+                    headers["user-agent"]?.let {
+                        webView.settings.userAgentString = it
                     }
-                    return super.shouldOverrideUrlLoading(view, request)
-                }
-            }
+                },
+                client = webClient,
+            )
         }
-
-        WebView(
-            state = state,
-            modifier = Modifier
-                .padding(contentPadding)
-                .fillMaxSize(),
-            navigator = navigator,
-            onCreated = { webView ->
-                webView.setDefaultSettings()
-
-                // Debug mode (chrome://inspect/#devices)
-                if (BuildConfig.DEBUG &&
-                    0 != webView.context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
-                ) {
-                    WebView.setWebContentsDebuggingEnabled(true)
-                }
-
-                headers["user-agent"]?.let {
-                    webView.settings.userAgentString = it
-                }
-            },
-            client = webClient,
-        )
     }
 }

+ 1 - 1
core/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt

@@ -44,7 +44,7 @@ class CloudflareInterceptor(
         // Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
         // we don't crash the entire app
         catch (e: CloudflareBypassException) {
-            throw IOException(context.getString(R.string.information_cloudflare_bypass_failure))
+            throw IOException(context.getString(R.string.information_cloudflare_bypass_failure), e)
         } catch (e: Exception) {
             throw IOException(e)
         }

+ 6 - 0
core/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt

@@ -6,8 +6,10 @@ import android.content.pm.PackageManager
 import android.webkit.CookieManager
 import android.webkit.WebSettings
 import android.webkit.WebView
+import kotlinx.coroutines.suspendCancellableCoroutine
 import logcat.LogPriority
 import tachiyomi.core.util.system.logcat
+import kotlin.coroutines.resume
 
 object WebViewUtil {
     const val SPOOF_PACKAGE_NAME = "org.chromium.chrome"
@@ -32,6 +34,10 @@ fun WebView.isOutdated(): Boolean {
     return getWebViewMajorVersion() < WebViewUtil.MINIMUM_WEBVIEW_VERSION
 }
 
+suspend fun WebView.getHtml(): String = suspendCancellableCoroutine {
+    evaluateJavascript("document.documentElement.outerHTML") { html -> it.resume(html) }
+}
+
 @SuppressLint("SetJavaScriptEnabled")
 fun WebView.setDefaultSettings() {
     with(settings) {

+ 1 - 0
i18n/src/main/res/values/strings.xml

@@ -923,6 +923,7 @@
     <string name="information_empty_category">You have no categories. Tap the plus button to create one for organizing your library.</string>
     <string name="information_empty_category_dialog">You don\'t have any categories yet.</string>
     <string name="information_cloudflare_bypass_failure">Failed to bypass Cloudflare</string>
+    <string name="information_cloudflare_help">Tap here for help with Cloudflare</string>
     <string name="information_required_plain">*required</string>
     <!-- Do not translate "WebView" -->
     <string name="information_webview_required">WebView is required for Tachiyomi</string>