Update parsers

master
Koitharu 12 months ago
parent 3aaddfd513
commit 1bbe1204e6
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -1,13 +1,17 @@
package org.koitharu.kotatsu.browser package org.koitharu.kotatsu.browser
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContract
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R 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.AppRouter
import org.koitharu.kotatsu.core.nav.router import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.parser.ParserMangaRepository import org.koitharu.kotatsu.core.parser.ParserMangaRepository
@ -65,4 +69,23 @@ class BrowserActivity : BaseBrowserActivity() {
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
class Contract : ActivityResultContract<InteractiveActionRequiredException, Unit>() {
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"
}
} }

@ -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}")

@ -12,8 +12,10 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.browser.BrowserActivity
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity import org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException 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.ProxyConfigException
import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException
import org.koitharu.kotatsu.core.nav.AppRouter import org.koitharu.kotatsu.core.nav.AppRouter
@ -43,6 +45,9 @@ class ExceptionResolver @AssistedInject constructor(
) { ) {
private val continuations = MutableScatterMap<String, Continuation<Boolean>>(1) private val continuations = MutableScatterMap<String, Continuation<Boolean>>(1)
private val browserActionContract = host.registerForActivityResult(BrowserActivity.Contract()) {
handleActivityResult(BrowserActivity.TAG, true)
}
private val sourceAuthContract = host.registerForActivityResult(SourceAuthActivity.Contract()) { private val sourceAuthContract = host.registerForActivityResult(SourceAuthActivity.Contract()) {
handleActivityResult(SourceAuthActivity.TAG, it) handleActivityResult(SourceAuthActivity.TAG, it)
} }
@ -63,6 +68,8 @@ class ExceptionResolver @AssistedInject constructor(
false false
} }
is InteractiveActionRequiredException -> resolveBrowserAction(e)
is ProxyConfigException -> { is ProxyConfigException -> {
host.router()?.openProxySettings() host.router()?.openProxySettings()
false false
@ -93,6 +100,13 @@ class ExceptionResolver @AssistedInject constructor(
else -> false 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 -> private suspend fun resolveCF(e: CloudFlareProtectedException): Boolean = suspendCoroutine { cont ->
continuations[CloudFlareActivity.TAG] = cont continuations[CloudFlareActivity.TAG] = cont
cloudflareContract.launch(e) cloudflareContract.launch(e)
@ -171,6 +185,8 @@ class ExceptionResolver @AssistedInject constructor(
is ProxyConfigException -> R.string.settings is ProxyConfigException -> R.string.settings
is InteractiveActionRequiredException -> R.string._continue
else -> 0 else -> 0
} }

@ -207,12 +207,7 @@ class AppRouter private constructor(
fun openDirectoriesSettings() = startActivity(MangaDirectoriesActivity::class.java) fun openDirectoriesSettings() = startActivity(MangaDirectoriesActivity::class.java)
fun openBrowser(url: String, source: MangaSource?, title: String?) { fun openBrowser(url: String, source: MangaSource?, title: String?) {
startActivity( startActivity(browserIntent(contextOrNull() ?: return, url, source, title))
Intent(contextOrNull() ?: return, BrowserActivity::class.java)
.setData(url.toUri())
.putExtra(KEY_TITLE, title)
.putExtra(KEY_SOURCE, source?.name),
)
} }
fun openColorFilterConfig(manga: Manga, page: MangaPage) { 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 suggestionsIntent(context: Context) = Intent(context, SuggestionsActivity::class.java)
fun homeIntent(context: Context) = Intent(context, MainActivity::class.java) fun homeIntent(context: Context) = Intent(context, MainActivity::class.java)

@ -15,6 +15,7 @@ import okhttp3.OkHttpClient
import okhttp3.Response import okhttp3.Response
import okhttp3.ResponseBody.Companion.asResponseBody import okhttp3.ResponseBody.Companion.asResponseBody
import okio.Buffer import okio.Buffer
import org.koitharu.kotatsu.core.exceptions.InteractiveActionRequiredException
import org.koitharu.kotatsu.core.image.BitmapDecoderCompat import org.koitharu.kotatsu.core.image.BitmapDecoderCompat
import org.koitharu.kotatsu.core.network.MangaHttpClient import org.koitharu.kotatsu.core.network.MangaHttpClient
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar 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.toMimeType
import org.koitharu.kotatsu.core.util.ext.use import org.koitharu.kotatsu.core.util.ext.use
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.bitmap.Bitmap import org.koitharu.kotatsu.parsers.bitmap.Bitmap
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
@ -77,6 +79,11 @@ class MangaLoaderContextImpl @Inject constructor(
return LocaleListCompat.getAdjustedDefault().toList() 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 { override fun redrawImageResponse(response: Response, redraw: (image: Bitmap) -> Bitmap): Response {
return response.map { body -> return response.map { body ->
BitmapDecoderCompat.decode(body.byteStream(), body.contentType()?.toMimeType(), isMutable = true) BitmapDecoderCompat.decode(body.byteStream(), body.contentType()?.toMimeType(), isMutable = true)

@ -21,6 +21,7 @@ import org.koitharu.kotatsu.core.exceptions.CloudFlareBlockedException
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
import org.koitharu.kotatsu.core.exceptions.IncompatiblePluginException 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.NoDataReceivedException
import org.koitharu.kotatsu.core.exceptions.NonFileUriException import org.koitharu.kotatsu.core.exceptions.NonFileUriException
import org.koitharu.kotatsu.core.exceptions.ProxyConfigException 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 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 CloudFlareProtectedException -> resources.getString(R.string.captcha_required_message)
is CloudFlareBlockedException -> resources.getString(R.string.blocked_by_server_message) is CloudFlareBlockedException -> resources.getString(R.string.blocked_by_server_message)
is ActivityNotFoundException, is ActivityNotFoundException,
@ -132,7 +134,7 @@ private fun Throwable.getDisplayMessageOrNull(resources: Resources): String? = w
}.takeUnless { it.isNullOrBlank() } }.takeUnless { it.isNullOrBlank() }
@DrawableRes @DrawableRes
fun Throwable.getDisplayIcon() = when (this) { fun Throwable.getDisplayIcon(): Int = when (this) {
is AuthRequiredException -> R.drawable.ic_auth_key_large is AuthRequiredException -> R.drawable.ic_auth_key_large
is CloudFlareProtectedException -> R.drawable.ic_bot_large is CloudFlareProtectedException -> R.drawable.ic_bot_large
is UnknownHostException, is UnknownHostException,
@ -143,6 +145,7 @@ fun Throwable.getDisplayIcon() = when (this) {
is CloudFlareBlockedException -> R.drawable.ic_denied_large is CloudFlareBlockedException -> R.drawable.ic_denied_large
is InteractiveActionRequiredException -> R.drawable.ic_interaction_large
else -> R.drawable.ic_error_large else -> R.drawable.ic_error_large
} }
@ -155,6 +158,7 @@ fun Throwable.getCauseUrl(): String? = when (this) {
is NoDataReceivedException -> url is NoDataReceivedException -> url
is CloudFlareBlockedException -> url is CloudFlareBlockedException -> url
is CloudFlareProtectedException -> url is CloudFlareProtectedException -> url
is InteractiveActionRequiredException -> url
is HttpStatusException -> url is HttpStatusException -> url
is HttpException -> (response.delegate as? Response)?.request?.url?.toString() is HttpException -> (response.delegate as? Response)?.request?.url?.toString()
else -> null else -> null

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M13 5C15.21 5 17 6.79 17 9C17 10.5 16.2 11.77 15 12.46V11.24C15.61 10.69 16 9.89 16 9C16 7.34 14.66 6 13 6S10 7.34 10 9C10 9.89 10.39 10.69 11 11.24V12.46C9.8 11.77 9 10.5 9 9C9 6.79 10.79 5 13 5M20 20.5C19.97 21.32 19.32 21.97 18.5 22H13C12.62 22 12.26 21.85 12 21.57L8 17.37L8.74 16.6C8.93 16.39 9.2 16.28 9.5 16.28H9.7L12 18V9C12 8.45 12.45 8 13 8S14 8.45 14 9V13.47L15.21 13.6L19.15 15.79C19.68 16.03 20 16.56 20 17.14V20.5M20 2H4C2.9 2 2 2.9 2 4V12C2 13.11 2.9 14 4 14H8V12L4 12L4 4H20L20 12H18V14H20V13.96L20.04 14C21.13 14 22 13.09 22 12V4C22 2.9 21.11 2 20 2Z" />
</vector>

@ -836,4 +836,5 @@
<string name="dont_ask_again">Don\'t ask again</string> <string name="dont_ask_again">Don\'t ask again</string>
<string name="incognito_mode_hint_nsfw">This manga may contain adult content. Do you want to use incognito mode?</string> <string name="incognito_mode_hint_nsfw">This manga may contain adult content. Do you want to use incognito mode?</string>
<string name="incognito_for_nsfw">Incognito mode for NSFW manga</string> <string name="incognito_for_nsfw">Incognito mode for NSFW manga</string>
<string name="additional_action_required">Additional action is required</string>
</resources> </resources>

@ -15,7 +15,7 @@ coroutines = "1.10.2"
desugar = "2.1.5" desugar = "2.1.5"
diskLruCache = "1.5" diskLruCache = "1.5"
fragment = "1.8.6" fragment = "1.8.6"
gradle = "8.9.1" gradle = "8.10.0"
guava = "33.4.8-android" guava = "33.4.8-android"
dagger = "2.56.2" dagger = "2.56.2"
hilt = "1.2.0" hilt = "1.2.0"
@ -31,7 +31,7 @@ material = "1.13.0-alpha13"
moshi = "1.15.2" moshi = "1.15.2"
okhttp = "4.12.0" okhttp = "4.12.0"
okio = "3.11.0" okio = "3.11.0"
parsers = "cf0177364c" parsers = "9408b9ba1b"
preference = "1.2.1" preference = "1.2.1"
recyclerview = "1.4.0" recyclerview = "1.4.0"
room = "2.7.1" room = "2.7.1"

Loading…
Cancel
Save