From 19d0fe97a0c486ed23f941ca83c3eb2e7be33c42 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 24 May 2025 21:13:33 +0300 Subject: [PATCH] Update parsers --- app/build.gradle | 1 - .../ContinuationResumeWebViewClient.kt | 16 ++++++++++++++ .../core/parser/MangaLoaderContextImpl.kt | 15 +++++++++++++ .../koitharu/kotatsu/core/util/ext/Android.kt | 1 + .../sources/SourceSettingsFragment.kt | 5 ++++- .../sources/SourceSettingsViewModel.kt | 3 +++ .../sources/auth/SourceAuthActivity.kt | 22 +++++++++++++++---- app/src/main/res/xml/pref_source_parser.xml | 1 + gradle/libs.versions.toml | 2 +- 9 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/network/webview/ContinuationResumeWebViewClient.kt diff --git a/app/build.gradle b/app/build.gradle index 0b89be3c3..4299ad75f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -76,7 +76,6 @@ android { '-opt-in=kotlin.contracts.ExperimentalContracts', '-opt-in=coil3.annotation.ExperimentalCoilApi', '-opt-in=coil3.annotation.InternalCoilApi', - '-opt-in=org.koitharu.kotatsu.parsers.InternalParsersApi', '-Xjspecify-annotations=strict', '-Xtype-enhancement-improvements-strict-mode' ] diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/network/webview/ContinuationResumeWebViewClient.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/webview/ContinuationResumeWebViewClient.kt new file mode 100644 index 000000000..c7d3a2794 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/network/webview/ContinuationResumeWebViewClient.kt @@ -0,0 +1,16 @@ +package org.koitharu.kotatsu.core.network.webview + +import android.webkit.WebView +import android.webkit.WebViewClient +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume + +class ContinuationResumeWebViewClient( + private val continuation: Continuation, +) : WebViewClient() { + + override fun onPageFinished(view: WebView?, url: String?) { + view?.webViewClient = WebViewClient() // reset to default + continuation.resume(Unit) + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt index b3230cccc..cf7ed4324 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt @@ -19,6 +19,7 @@ import org.koitharu.kotatsu.core.exceptions.InteractiveActionRequiredException import org.koitharu.kotatsu.core.image.BitmapDecoderCompat import org.koitharu.kotatsu.core.network.MangaHttpClient import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar +import org.koitharu.kotatsu.core.network.webview.ContinuationResumeWebViewClient import org.koitharu.kotatsu.core.prefs.SourceSettings import org.koitharu.kotatsu.core.util.ext.configureForParser import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug @@ -51,6 +52,7 @@ class MangaLoaderContextImpl @Inject constructor( private var webViewCached: WeakReference? = null private val webViewUserAgent by lazy { obtainWebViewUserAgent() } + @Deprecated("Provide a base url") @SuppressLint("SetJavaScriptEnabled") override suspend fun evaluateJs(script: String): String? = withContext(Dispatchers.Main.immediate) { val webView = obtainWebView() @@ -61,6 +63,19 @@ class MangaLoaderContextImpl @Inject constructor( } } + override suspend fun evaluateJs(baseUrl: String, script: String): String? = withContext(Dispatchers.Main.immediate) { + val webView = obtainWebView() + suspendCoroutine { cont -> + webView.webViewClient = ContinuationResumeWebViewClient(cont) + webView.loadDataWithBaseURL(baseUrl, " ", "text/html", null, null) + } + suspendCoroutine { cont -> + webView.evaluateJavascript(script) { result -> + cont.resume(result?.takeUnless { it == "null" }) + } + } + } + override fun getDefaultUserAgent(): String = webViewUserAgent override fun getConfig(source: MangaSource): MangaSourceConfig { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Android.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Android.kt index cfd5c5967..e8ce3eed1 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Android.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Android.kt @@ -215,6 +215,7 @@ fun WebView.configureForParser(userAgentOverride: String?) = with(settings) { WebViewCompat.setAudioMuted(this@configureForParser, true) } databaseEnabled = true + allowContentAccess = false if (userAgentOverride != null) { userAgentString = userAgentOverride } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/SourceSettingsFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/SourceSettingsFragment.kt index 68d5675d7..7bf90f703 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/SourceSettingsFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/SourceSettingsFragment.kt @@ -6,6 +6,7 @@ import androidx.fragment.app.viewModels import androidx.preference.Preference import androidx.preference.SwitchPreferenceCompat import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.filterNotNull import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.model.getTitle @@ -49,13 +50,15 @@ class SourceSettingsFragment : BasePreferenceFragment(0), Preference.OnPreferenc findPreference(KEY_AUTH)?.run { val authProvider = (viewModel.repository as? ParserMangaRepository)?.getAuthProvider() isVisible = authProvider != null - isEnabled = authProvider?.isAuthorized == false } findPreference(SourceSettings.KEY_SLOWDOWN)?.isVisible = isValidSource } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + viewModel.isAuthorized.filterNotNull().observe(viewLifecycleOwner) { isAuthorized -> + findPreference(KEY_AUTH)?.isEnabled = !isAuthorized + } viewModel.username.observe(viewLifecycleOwner) { username -> findPreference(KEY_AUTH)?.summary = username?.let { getString(R.string.logged_in_as, it) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/SourceSettingsViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/SourceSettingsViewModel.kt index 595e81dee..bcd57bc61 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/SourceSettingsViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/SourceSettingsViewModel.kt @@ -38,6 +38,7 @@ class SourceSettingsViewModel @Inject constructor( val onActionDone = MutableEventFlow() val username = MutableStateFlow(null) + val isAuthorized = MutableStateFlow(null) val browserUrl = MutableStateFlow(null) val isEnabled = mangaSourcesRepository.observeIsEnabled(source) private var usernameLoadJob: Job? = null @@ -103,6 +104,8 @@ class SourceSettingsViewModel @Inject constructor( launchLoadingJob(Dispatchers.Default) { try { username.value = null + isAuthorized.value = null + isAuthorized.value = authProvider?.isAuthorized() username.value = authProvider?.getUsername() } catch (_: AuthRequiredException) { } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/auth/SourceAuthActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/auth/SourceAuthActivity.kt index a119b0caa..56815f0d0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/auth/SourceAuthActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/auth/SourceAuthActivity.kt @@ -9,6 +9,7 @@ import androidx.activity.result.contract.ActivityResultContract import androidx.lifecycle.lifecycleScope import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Job import kotlinx.coroutines.launch import org.koitharu.kotatsu.R import org.koitharu.kotatsu.browser.BaseBrowserActivity @@ -20,12 +21,15 @@ import org.koitharu.kotatsu.core.parser.ParserMangaRepository import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.parsers.MangaParserAuthProvider import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.util.runCatchingCancellable @AndroidEntryPoint class SourceAuthActivity : BaseBrowserActivity(), BrowserCallback { private lateinit var authProvider: MangaParserAuthProvider + private var authCheckJob: Job? = null + override fun onCreate2(savedInstanceState: Bundle?, source: MangaSource, repository: ParserMangaRepository?) { if (repository == null) { finishAfterTransition() @@ -72,10 +76,20 @@ class SourceAuthActivity : BaseBrowserActivity(), BrowserCallback { override fun onLoadingStateChanged(isLoading: Boolean) { super.onLoadingStateChanged(isLoading) - if (!isLoading && authProvider.isAuthorized) { - Toast.makeText(this, R.string.auth_complete, Toast.LENGTH_SHORT).show() - setResult(RESULT_OK) - finishAfterTransition() + if (isLoading) { + return + } + val prevJob = authCheckJob + authCheckJob = lifecycleScope.launch { + prevJob?.join() + val isAuthorized = runCatchingCancellable { + authProvider.isAuthorized() + }.getOrDefault(false) + if (isAuthorized) { + Toast.makeText(this@SourceAuthActivity, R.string.auth_complete, Toast.LENGTH_SHORT).show() + setResult(RESULT_OK) + finishAfterTransition() + } } } diff --git a/app/src/main/res/xml/pref_source_parser.xml b/app/src/main/res/xml/pref_source_parser.xml index a50fd3354..36c592b69 100644 --- a/app/src/main/res/xml/pref_source_parser.xml +++ b/app/src/main/res/xml/pref_source_parser.xml @@ -4,6 +4,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto">