From 1bbe1204e62173554e1c47f4a6b6cd23099a3b0b Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 11 May 2025 14:23:41 +0300 Subject: [PATCH] Update parsers --- .../kotatsu/browser/BrowserActivity.kt | 23 +++++++++++++++++++ .../InteractiveActionRequiredException.kt | 9 ++++++++ .../exceptions/resolve/ExceptionResolver.kt | 16 +++++++++++++ .../koitharu/kotatsu/core/nav/AppRouter.kt | 17 +++++++++----- .../core/parser/MangaLoaderContextImpl.kt | 7 ++++++ .../kotatsu/core/util/ext/Throwable.kt | 6 ++++- .../res/drawable/ic_interaction_large.xml | 12 ++++++++++ app/src/main/res/values/strings.xml | 1 + gradle/libs.versions.toml | 4 ++-- 9 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/InteractiveActionRequiredException.kt create mode 100644 app/src/main/res/drawable/ic_interaction_large.xml diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/browser/BrowserActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/browser/BrowserActivity.kt index 64044071f..9a2fa9480 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/browser/BrowserActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/browser/BrowserActivity.kt @@ -1,13 +1,17 @@ package org.koitharu.kotatsu.browser +import android.content.Context +import android.content.Intent import android.os.Bundle import android.view.Menu import android.view.MenuItem +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.launch import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.exceptions.InteractiveActionRequiredException import org.koitharu.kotatsu.core.nav.AppRouter import org.koitharu.kotatsu.core.nav.router import org.koitharu.kotatsu.core.parser.ParserMangaRepository @@ -65,4 +69,23 @@ class BrowserActivity : BaseBrowserActivity() { else -> super.onOptionsItemSelected(item) } + + class Contract : ActivityResultContract() { + override fun createIntent( + context: Context, + input: InteractiveActionRequiredException + ): Intent = AppRouter.browserIntent( + context = context, + url = input.url, + source = input.source, + title = null, + ) + + override fun parseResult(resultCode: Int, intent: Intent?): Unit = Unit + } + + companion object { + + const val TAG = "BrowserActivity" + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/InteractiveActionRequiredException.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/InteractiveActionRequiredException.kt new file mode 100644 index 000000000..2ff9cc4b5 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/InteractiveActionRequiredException.kt @@ -0,0 +1,9 @@ +package org.koitharu.kotatsu.core.exceptions + +import okio.IOException +import org.koitharu.kotatsu.parsers.model.MangaSource + +class InteractiveActionRequiredException( + val source: MangaSource, + val url: String, +) : IOException("Interactive action is required for ${source.name}") diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt index 1a9703786..b8720c1d4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt @@ -12,8 +12,10 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.browser.BrowserActivity import org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException +import org.koitharu.kotatsu.core.exceptions.InteractiveActionRequiredException import org.koitharu.kotatsu.core.exceptions.ProxyConfigException import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException import org.koitharu.kotatsu.core.nav.AppRouter @@ -43,6 +45,9 @@ class ExceptionResolver @AssistedInject constructor( ) { private val continuations = MutableScatterMap>(1) + private val browserActionContract = host.registerForActivityResult(BrowserActivity.Contract()) { + handleActivityResult(BrowserActivity.TAG, true) + } private val sourceAuthContract = host.registerForActivityResult(SourceAuthActivity.Contract()) { handleActivityResult(SourceAuthActivity.TAG, it) } @@ -63,6 +68,8 @@ class ExceptionResolver @AssistedInject constructor( false } + is InteractiveActionRequiredException -> resolveBrowserAction(e) + is ProxyConfigException -> { host.router()?.openProxySettings() false @@ -93,6 +100,13 @@ class ExceptionResolver @AssistedInject constructor( else -> false } + private suspend fun resolveBrowserAction( + e: InteractiveActionRequiredException + ): Boolean = suspendCoroutine { cont -> + continuations[BrowserActivity.TAG] = cont + browserActionContract.launch(e) + } + private suspend fun resolveCF(e: CloudFlareProtectedException): Boolean = suspendCoroutine { cont -> continuations[CloudFlareActivity.TAG] = cont cloudflareContract.launch(e) @@ -171,6 +185,8 @@ class ExceptionResolver @AssistedInject constructor( is ProxyConfigException -> R.string.settings + is InteractiveActionRequiredException -> R.string._continue + else -> 0 } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/nav/AppRouter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/nav/AppRouter.kt index af1d470f4..2d88ee025 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/nav/AppRouter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/nav/AppRouter.kt @@ -207,12 +207,7 @@ class AppRouter private constructor( fun openDirectoriesSettings() = startActivity(MangaDirectoriesActivity::class.java) fun openBrowser(url: String, source: MangaSource?, title: String?) { - startActivity( - Intent(contextOrNull() ?: return, BrowserActivity::class.java) - .setData(url.toUri()) - .putExtra(KEY_TITLE, title) - .putExtra(KEY_SOURCE, source?.name), - ) + startActivity(browserIntent(contextOrNull() ?: return, url, source, title)) } fun openColorFilterConfig(manga: Manga, page: MangaPage) { @@ -708,6 +703,16 @@ class AppRouter private constructor( } } + fun browserIntent( + context: Context, + url: String, + source: MangaSource?, + title: String? + ): Intent = Intent(context, BrowserActivity::class.java) + .setData(url.toUri()) + .putExtra(KEY_TITLE, title) + .putExtra(KEY_SOURCE, source?.name) + fun suggestionsIntent(context: Context) = Intent(context, SuggestionsActivity::class.java) fun homeIntent(context: Context) = Intent(context, MainActivity::class.java) 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 9d07a1c47..b3230cccc 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 @@ -15,6 +15,7 @@ import okhttp3.OkHttpClient import okhttp3.Response import okhttp3.ResponseBody.Companion.asResponseBody import okio.Buffer +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 @@ -26,6 +27,7 @@ import org.koitharu.kotatsu.core.util.ext.toList import org.koitharu.kotatsu.core.util.ext.toMimeType import org.koitharu.kotatsu.core.util.ext.use import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.bitmap.Bitmap import org.koitharu.kotatsu.parsers.config.MangaSourceConfig import org.koitharu.kotatsu.parsers.model.MangaSource @@ -77,6 +79,11 @@ class MangaLoaderContextImpl @Inject constructor( return LocaleListCompat.getAdjustedDefault().toList() } + override fun requestBrowserAction( + parser: MangaParser, + url: String, + ): Nothing = throw InteractiveActionRequiredException(parser.source, url) + override fun redrawImageResponse(response: Response, redraw: (image: Bitmap) -> Bitmap): Response { return response.map { body -> BitmapDecoderCompat.decode(body.byteStream(), body.contentType()?.toMimeType(), isMutable = true) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt index 43fee30d8..7447209cd 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt @@ -21,6 +21,7 @@ import org.koitharu.kotatsu.core.exceptions.CloudFlareBlockedException import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException import org.koitharu.kotatsu.core.exceptions.IncompatiblePluginException +import org.koitharu.kotatsu.core.exceptions.InteractiveActionRequiredException import org.koitharu.kotatsu.core.exceptions.NoDataReceivedException import org.koitharu.kotatsu.core.exceptions.NonFileUriException import org.koitharu.kotatsu.core.exceptions.ProxyConfigException @@ -68,6 +69,7 @@ private fun Throwable.getDisplayMessageOrNull(resources: Resources): String? = w ) is AuthRequiredException -> resources.getString(R.string.auth_required) + is InteractiveActionRequiredException -> resources.getString(R.string.additional_action_required) is CloudFlareProtectedException -> resources.getString(R.string.captcha_required_message) is CloudFlareBlockedException -> resources.getString(R.string.blocked_by_server_message) is ActivityNotFoundException, @@ -132,7 +134,7 @@ private fun Throwable.getDisplayMessageOrNull(resources: Resources): String? = w }.takeUnless { it.isNullOrBlank() } @DrawableRes -fun Throwable.getDisplayIcon() = when (this) { +fun Throwable.getDisplayIcon(): Int = when (this) { is AuthRequiredException -> R.drawable.ic_auth_key_large is CloudFlareProtectedException -> R.drawable.ic_bot_large is UnknownHostException, @@ -143,6 +145,7 @@ fun Throwable.getDisplayIcon() = when (this) { is CloudFlareBlockedException -> R.drawable.ic_denied_large + is InteractiveActionRequiredException -> R.drawable.ic_interaction_large else -> R.drawable.ic_error_large } @@ -155,6 +158,7 @@ fun Throwable.getCauseUrl(): String? = when (this) { is NoDataReceivedException -> url is CloudFlareBlockedException -> url is CloudFlareProtectedException -> url + is InteractiveActionRequiredException -> url is HttpStatusException -> url is HttpException -> (response.delegate as? Response)?.request?.url?.toString() else -> null diff --git a/app/src/main/res/drawable/ic_interaction_large.xml b/app/src/main/res/drawable/ic_interaction_large.xml new file mode 100644 index 000000000..809926178 --- /dev/null +++ b/app/src/main/res/drawable/ic_interaction_large.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b78baacfd..7993a5f96 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -836,4 +836,5 @@ Don\'t ask again This manga may contain adult content. Do you want to use incognito mode? Incognito mode for NSFW manga + Additional action is required diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 83ec8cc36..2512ff243 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ coroutines = "1.10.2" desugar = "2.1.5" diskLruCache = "1.5" fragment = "1.8.6" -gradle = "8.9.1" +gradle = "8.10.0" guava = "33.4.8-android" dagger = "2.56.2" hilt = "1.2.0" @@ -31,7 +31,7 @@ material = "1.13.0-alpha13" moshi = "1.15.2" okhttp = "4.12.0" okio = "3.11.0" -parsers = "cf0177364c" +parsers = "9408b9ba1b" preference = "1.2.1" recyclerview = "1.4.0" room = "2.7.1"