Add MangaDex login

This commit is contained in:
NerdNumber9 2019-04-19 02:46:34 -04:00
parent 5c2fbec80a
commit ea7ff432b2
14 changed files with 238 additions and 95 deletions

View File

@ -219,7 +219,7 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name="exh.ui.captcha.SolveCaptchaActivity" android:name="exh.ui.captcha.BrowserActionActivity"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:theme="@style/Theme.EHActivity" /> android:theme="@style/Theme.EHActivity" />
<activity android:name="exh.ui.webview.WebViewActivity" /> <activity android:name="exh.ui.webview.WebViewActivity" />

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.network
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import exh.log.maybeInjectEHLogger import exh.log.maybeInjectEHLogger
import exh.patch.attachMangaDexLogin
import okhttp3.* import okhttp3.*
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@ -14,23 +15,24 @@ import java.security.KeyStore
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import javax.net.ssl.* import javax.net.ssl.*
class NetworkHelper(context: Context) { open class NetworkHelper(context: Context) {
private val cacheDir = File(context.cacheDir, "network_cache") private val cacheDir = File(context.cacheDir, "network_cache")
private val cacheSize = 5L * 1024 * 1024 // 5 MiB private val cacheSize = 5L * 1024 * 1024 // 5 MiB
val cookieManager = AndroidCookieJar(context) open val cookieManager = AndroidCookieJar(context)
val client = OkHttpClient.Builder() open val client = OkHttpClient.Builder()
.cookieJar(cookieManager) .cookieJar(cookieManager)
.cache(Cache(cacheDir, cacheSize)) .cache(Cache(cacheDir, cacheSize))
.enableTLS12() .enableTLS12()
.maybeInjectEHLogger() .maybeInjectEHLogger()
.build() .build()
val cloudflareClient = client.newBuilder() open val cloudflareClient = client.newBuilder()
.addInterceptor(CloudflareInterceptor(context)) .addInterceptor(CloudflareInterceptor(context))
.attachMangaDexLogin()
.build() .build()
private fun OkHttpClient.Builder.enableTLS12(): OkHttpClient.Builder { private fun OkHttpClient.Builder.enableTLS12(): OkHttpClient.Builder {

View File

@ -24,7 +24,6 @@ import exh.metadata.metadata.PervEdenLang
import exh.source.BlacklistedSources import exh.source.BlacklistedSources
import exh.source.DelegatedHttpSource import exh.source.DelegatedHttpSource
import exh.source.EnhancedHttpSource import exh.source.EnhancedHttpSource
import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import kotlin.reflect.KClass import kotlin.reflect.KClass

View File

@ -1,15 +1,14 @@
package eu.kanade.tachiyomi.source.online package eu.kanade.tachiyomi.source.online
import android.app.Application import android.app.Application
import com.elvishew.xlog.XLog import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.*
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.newCallWithProgress
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.* import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.util.asJsoup import exh.source.DelegatedHttpSource
import exh.ui.captcha.SolveCaptchaActivity import exh.ui.captcha.BrowserActionActivity
import exh.util.interceptAsHtml
import okhttp3.* import okhttp3.*
import rx.Observable import rx.Observable
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -28,7 +27,19 @@ abstract class HttpSource : CatalogueSource {
/** /**
* Network service. * Network service.
*/ */
protected val network: NetworkHelper by injectLazy() protected val network: NetworkHelper by lazy {
val original = Injekt.get<NetworkHelper>()
object : NetworkHelper(Injekt.get<Application>()) {
override val client: OkHttpClient?
get() = delegate?.networkHttpClient ?: original.client
override val cloudflareClient: OkHttpClient?
get() = delegate?.networkCloudflareClient ?: original.cloudflareClient
override val cookieManager: AndroidCookieJar
get() = original.cookieManager
}
}
// /** // /**
// * Preferences that a source may need. // * Preferences that a source may need.
@ -68,36 +79,21 @@ abstract class HttpSource : CatalogueSource {
* Default network client for doing requests. * Default network client for doing requests.
*/ */
open val client: OkHttpClient open val client: OkHttpClient
get() = network.client.newBuilder().addInterceptor { chain -> get() = delegate?.baseHttpClient ?: network.client.newBuilder().addInterceptor { chain ->
// Automatic captcha detection
val response = chain.proceed(chain.request()) val response = chain.proceed(chain.request())
val body = response.body() if(!response.isSuccessful) {
if(!response.isSuccessful && body != null) { response.interceptAsHtml { doc ->
if(body.contentType()?.type() == "text" if (doc.getElementsByClass("g-recaptcha").isNotEmpty()) {
&& body.contentType()?.subtype() == "html") { // Found it, allow the user to solve this thing
val bodyString = body.string() BrowserActionActivity.launchUniversal(
val rebuiltResponse = response.newBuilder() Injekt.get<Application>(),
.body(ResponseBody.create(body.contentType(), bodyString)) this,
.build() chain.request().url().toString()
try { )
// Search for captcha
val parsed = response.asJsoup(html = bodyString)
if(parsed.getElementsByClass("g-recaptcha").isNotEmpty()) {
// Found it, allow the user to solve this thing
SolveCaptchaActivity.launchUniversal(
Injekt.get<Application>(),
this,
chain.request().url().toString()
)
}
} catch(t: Throwable) {
// Ignore all errors
XLog.w("Captcha detection error!", t)
} }
return@addInterceptor rebuiltResponse
} }
} } else response
response
}.build() }.build()
/** /**
@ -397,4 +393,14 @@ abstract class HttpSource : CatalogueSource {
* Returns the list of filters for the source. * Returns the list of filters for the source.
*/ */
override fun getFilterList() = FilterList() override fun getFilterList() = FilterList()
// EXH -->
private var delegate: DelegatedHttpSource? = null
get() = if(Injekt.get<PreferencesHelper>().eh_delegateSources().getOrDefault())
field
else null
fun bindDelegate(delegate: DelegatedHttpSource) {
this.delegate = delegate
}
// EXH <--
} }

View File

@ -16,8 +16,8 @@ import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.util.toast
import exh.TSUMINO_SOURCE_ID import exh.TSUMINO_SOURCE_ID
import exh.ui.captcha.CaptchaCompletionVerifier import exh.ui.captcha.ActionCompletionVerifier
import exh.ui.captcha.SolveCaptchaActivity import exh.ui.captcha.BrowserActionActivity
import exh.metadata.metadata.TsuminoSearchMetadata import exh.metadata.metadata.TsuminoSearchMetadata
import exh.metadata.metadata.TsuminoSearchMetadata.Companion.BASE_URL import exh.metadata.metadata.TsuminoSearchMetadata.Companion.BASE_URL
import exh.metadata.metadata.TsuminoSearchMetadata.Companion.TAG_TYPE_DEFAULT import exh.metadata.metadata.TsuminoSearchMetadata.Companion.TAG_TYPE_DEFAULT
@ -33,7 +33,7 @@ import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
class Tsumino(private val context: Context): ParsedHttpSource(), LewdSource<TsuminoSearchMetadata, Document>, CaptchaCompletionVerifier { class Tsumino(private val context: Context): ParsedHttpSource(), LewdSource<TsuminoSearchMetadata, Document>, ActionCompletionVerifier {
override val metaClass = TsuminoSearchMetadata::class override val metaClass = TsuminoSearchMetadata::class
private val preferences: PreferencesHelper by injectLazy() private val preferences: PreferencesHelper by injectLazy()
@ -343,7 +343,7 @@ class Tsumino(private val context: Context): ParsedHttpSource(), LewdSource<Tsum
else else
emptyMap() emptyMap()
SolveCaptchaActivity.launch(context, BrowserActionActivity.launchCaptcha(context,
this, this,
cookiesMap, cookiesMap,
CAPTCHA_SCRIPT, CAPTCHA_SCRIPT,
@ -356,7 +356,7 @@ class Tsumino(private val context: Context): ParsedHttpSource(), LewdSource<Tsum
} }
} }
override fun verifyNoCaptcha(url: String): Boolean { override fun verifyComplete(url: String): Boolean {
return Uri.parse(url).pathSegments.getOrNull(1) == "View" return Uri.parse(url).pathSegments.getOrNull(1) == "View"
} }

View File

@ -121,7 +121,7 @@ class SettingsAdvancedController : SettingsController() {
title = "Enable delegated sources" title = "Enable delegated sources"
key = PreferenceKeys.eh_delegateSources key = PreferenceKeys.eh_delegateSources
defaultValue = true defaultValue = true
summary = "Apply ${context.getString(R.string.app_name)} enhancements to the following sources if they are installed: ${DELEGATED_SOURCES.values.joinToString { it.sourceName }}" summary = "Apply ${context.getString(R.string.app_name)} enhancements to the following sources if they are installed: ${DELEGATED_SOURCES.values.map { it.sourceName }.distinct().joinToString()}"
} }
intListPreference { intListPreference {

View File

@ -0,0 +1,46 @@
package exh.patch
import android.app.Application
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource
import exh.ui.captcha.BrowserActionActivity
import exh.util.interceptAsHtml
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
private val HIDE_SCRIPT = """
document.querySelector("#forgot_button").style.visibility = "hidden";
document.querySelector("#signup_button").style.visibility = "hidden";
document.querySelector("#announcement").style.visibility = "hidden";
document.querySelector("nav").style.visibility = "hidden";
document.querySelector("footer").style.visibility = "hidden";
""".trimIndent()
private fun verifyComplete(url: String): Boolean {
return HttpUrl.parse(url)?.let { parsed ->
parsed.host() == "mangadex.org" && parsed.pathSegments().none { it.isNotBlank() }
} ?: false
}
fun OkHttpClient.Builder.attachMangaDexLogin() =
addInterceptor { chain ->
val response = chain.proceed(chain.request())
if(response.request().url().host() == "mangadex.org") {
response.interceptAsHtml { doc ->
if (doc.title().trim().equals("Login - MangaDex", true)) {
BrowserActionActivity.launchAction(
Injekt.get<Application>(),
::verifyComplete,
HIDE_SCRIPT,
"https://mangadex.org/login",
"Login",
(Injekt.get<SourceManager>().get(2499283573021220255) as? HttpSource)?.headers?.toMultimap()?.mapValues {
it.value.joinToString(",")
} ?: emptyMap()
)
}
}
} else response
}

View File

@ -2,6 +2,7 @@ package exh.source
import eu.kanade.tachiyomi.source.model.* import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
@ -93,16 +94,16 @@ abstract class DelegatedHttpSource(val delegate: HttpSource): HttpSource() {
/** /**
* Base url of the website without the trailing slash, like: http://mysite.com * Base url of the website without the trailing slash, like: http://mysite.com
*/ */
override val baseUrl = delegate.baseUrl override val baseUrl get() = delegate.baseUrl
/** /**
* Whether the source has support for latest updates. * Whether the source has support for latest updates.
*/ */
override val supportsLatest = delegate.supportsLatest override val supportsLatest get() = delegate.supportsLatest
/** /**
* Name of the source. * Name of the source.
*/ */
final override val name = delegate.name final override val name get() = delegate.name
// ===> OPTIONAL FIELDS // ===> OPTIONAL FIELDS
@ -111,11 +112,18 @@ abstract class DelegatedHttpSource(val delegate: HttpSource): HttpSource() {
* of the MD5 of the string: sourcename/language/versionId * of the MD5 of the string: sourcename/language/versionId
* Note the generated id sets the sign bit to 0. * Note the generated id sets the sign bit to 0.
*/ */
override val id = delegate.id override val id get() = delegate.id
/** /**
* Default network client for doing requests. * Default network client for doing requests.
*/ */
override val client = delegate.client final override val client get() = delegate.client
/**
* You must NEVER call super.client if you override this!
*/
open val baseHttpClient: OkHttpClient? = null
open val networkHttpClient: OkHttpClient get() = network.client
open val networkCloudflareClient: OkHttpClient get() = network.cloudflareClient
/** /**
* Visible name of the source. * Visible name of the source.
@ -235,4 +243,8 @@ abstract class DelegatedHttpSource(val delegate: HttpSource): HttpSource() {
} }
class IncompatibleDelegateException(message: String) : RuntimeException(message) class IncompatibleDelegateException(message: String) : RuntimeException(message)
init {
delegate.bindDelegate(this)
}
} }

View File

@ -96,21 +96,21 @@ class EnhancedHttpSource(val originalSource: HttpSource,
/** /**
* Base url of the website without the trailing slash, like: http://mysite.com * Base url of the website without the trailing slash, like: http://mysite.com
*/ */
override val baseUrl = source().baseUrl override val baseUrl get() = source().baseUrl
/** /**
* Whether the source has support for latest updates. * Whether the source has support for latest updates.
*/ */
override val supportsLatest = source().supportsLatest override val supportsLatest get() = source().supportsLatest
/** /**
* Name of the source. * Name of the source.
*/ */
override val name = source().name override val name get() = source().name
/** /**
* An ISO 639-1 compliant language code (two letters in lower case). * An ISO 639-1 compliant language code (two letters in lower case).
*/ */
override val lang = source().lang override val lang get() = source().lang
// ===> OPTIONAL FIELDS // ===> OPTIONAL FIELDS
@ -119,11 +119,11 @@ class EnhancedHttpSource(val originalSource: HttpSource,
* of the MD5 of the string: sourcename/language/versionId * of the MD5 of the string: sourcename/language/versionId
* Note the generated id sets the sign bit to 0. * Note the generated id sets the sign bit to 0.
*/ */
override val id = source().id override val id get() = source().id
/** /**
* Default network client for doing requests. * Default network client for doing requests.
*/ */
override val client = source().client override val client get() = source().client
/** /**
* Visible name of the source. * Visible name of the source.

View File

@ -6,17 +6,17 @@ import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse import android.webkit.WebResourceResponse
import android.webkit.WebView import android.webkit.WebView
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import exh.ui.captcha.SolveCaptchaActivity.Companion.CROSS_WINDOW_SCRIPT_INNER import exh.ui.captcha.BrowserActionActivity.Companion.CROSS_WINDOW_SCRIPT_INNER
import org.jsoup.nodes.DataNode import org.jsoup.nodes.DataNode
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.nio.charset.Charset import java.nio.charset.Charset
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class AutoSolvingWebViewClient(activity: SolveCaptchaActivity, class AutoSolvingWebViewClient(activity: BrowserActionActivity,
source: CaptchaCompletionVerifier, verifyComplete: (String) -> Boolean,
injectScript: String?, injectScript: String?,
headers: Map<String, String>) headers: Map<String, String>)
: HeadersInjectingWebViewClient(activity, source, injectScript, headers) { : HeadersInjectingWebViewClient(activity, verifyComplete, injectScript, headers) {
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
// Inject our custom script into the recaptcha iframes // Inject our custom script into the recaptcha iframes

View File

@ -3,13 +3,13 @@ package exh.ui.captcha
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
open class BasicWebViewClient(protected val activity: SolveCaptchaActivity, open class BasicWebViewClient(protected val activity: BrowserActionActivity,
protected val source: CaptchaCompletionVerifier, protected val verifyComplete: (String) -> Boolean,
private val injectScript: String?) : WebViewClient() { private val injectScript: String?) : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) { override fun onPageFinished(view: WebView, url: String) {
super.onPageFinished(view, url) super.onPageFinished(view, url)
if(source.verifyNoCaptcha(url)) { if(verifyComplete(url)) {
activity.finish() activity.finish()
} else { } else {
if(injectScript != null) view.loadUrl("javascript:(function() {$injectScript})();") if(injectScript != null) view.loadUrl("javascript:(function() {$injectScript})();")

View File

@ -31,8 +31,10 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import exh.source.DelegatedHttpSource import exh.source.DelegatedHttpSource
import exh.util.melt import exh.util.melt
import rx.Observable import rx.Observable
import java.io.Serializable
import kotlin.collections.HashMap
class SolveCaptchaActivity : AppCompatActivity() { class BrowserActionActivity : AppCompatActivity() {
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
private val preferencesHelper: PreferencesHelper by injectLazy() private val preferencesHelper: PreferencesHelper by injectLazy()
private val networkHelper: NetworkHelper by injectLazy() private val networkHelper: NetworkHelper by injectLazy()
@ -54,31 +56,38 @@ class SolveCaptchaActivity : AppCompatActivity() {
val sourceId = intent.getLongExtra(SOURCE_ID_EXTRA, -1) val sourceId = intent.getLongExtra(SOURCE_ID_EXTRA, -1)
val originalSource = if(sourceId != -1L) sourceManager.get(sourceId) else null val originalSource = if(sourceId != -1L) sourceManager.get(sourceId) else null
val source = if(originalSource != null) { val source = if(originalSource != null) {
originalSource as? CaptchaCompletionVerifier originalSource as? ActionCompletionVerifier
?: run { ?: run {
(originalSource as? HttpSource)?.let { (originalSource as? HttpSource)?.let {
NoopCaptchaCompletionVerifier(it) NoopActionCompletionVerifier(it)
} }
} }
} else null } else null
val headers = (source as? HttpSource)?.headers?.toMultimap()?.mapValues { val headers = ((source as? HttpSource)?.headers?.toMultimap()?.mapValues {
it.value.joinToString(",") it.value.joinToString(",")
} ?: emptyMap() } ?: emptyMap()) + (intent.getSerializableExtra(HEADERS_EXTRA) as? HashMap<String, String> ?: emptyMap())
val cookies: HashMap<String, String>? val cookies: HashMap<String, String>?
= intent.getSerializableExtra(COOKIES_EXTRA) as? HashMap<String, String> = intent.getSerializableExtra(COOKIES_EXTRA) as? HashMap<String, String>
val script: String? = intent.getStringExtra(SCRIPT_EXTRA) val script: String? = intent.getStringExtra(SCRIPT_EXTRA)
val url: String? = intent.getStringExtra(URL_EXTRA) val url: String? = intent.getStringExtra(URL_EXTRA)
val actionName = intent.getStringExtra(ACTION_NAME_EXTRA)
if(source == null || url == null) { val verifyComplete = if(source != null) {
source::verifyComplete!!
} else intent.getSerializableExtra(VERIFY_LAMBDA_EXTRA) as? (String) -> Boolean
if(verifyComplete == null || url == null) {
finish() finish()
return return
} }
toolbar.title = source.name + ": Solve captcha" val actionStr = actionName ?: "Solve captcha"
toolbar.title = if(source != null) {
"${source.name}: $actionStr"
} else actionStr
val parsedUrl = URL(url) val parsedUrl = URL(url)
@ -94,6 +103,9 @@ class SolveCaptchaActivity : AppCompatActivity() {
webview.settings.javaScriptEnabled = true webview.settings.javaScriptEnabled = true
webview.settings.domStorageEnabled = true webview.settings.domStorageEnabled = true
headers.entries.find { it.key.equals("user-agent", true) }?.let {
webview.settings.userAgentString = it.value
}
var loadedInners = 0 var loadedInners = 0
@ -125,7 +137,7 @@ class SolveCaptchaActivity : AppCompatActivity() {
} }
webview.webViewClient = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { webview.webViewClient = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if(preferencesHelper.eh_autoSolveCaptchas().getOrDefault()) { if(actionName == null && preferencesHelper.eh_autoSolveCaptchas().getOrDefault()) {
// Fetch auto-solve credentials early for speed // Fetch auto-solve credentials early for speed
credentialsObservable = httpClient.newCall(Request.Builder() credentialsObservable = httpClient.newCall(Request.Builder()
// Rob demo credentials // Rob demo credentials
@ -139,13 +151,13 @@ class SolveCaptchaActivity : AppCompatActivity() {
json["token"].string json["token"].string
}.melt() }.melt()
webview.addJavascriptInterface(this@SolveCaptchaActivity, "exh") webview.addJavascriptInterface(this@BrowserActionActivity, "exh")
AutoSolvingWebViewClient(this, source, script, headers) AutoSolvingWebViewClient(this, verifyComplete, script, headers)
} else { } else {
HeadersInjectingWebViewClient(this, source, script, headers) HeadersInjectingWebViewClient(this, verifyComplete, script, headers)
} }
} else { } else {
BasicWebViewClient(this, source, script) BasicWebViewClient(this, verifyComplete, script)
} }
webview.loadUrl(url, headers) webview.loadUrl(url, headers)
@ -490,11 +502,14 @@ class SolveCaptchaActivity : AppCompatActivity() {
} }
companion object { companion object {
const val VERIFY_LAMBDA_EXTRA = "verify_lambda_extra"
const val SOURCE_ID_EXTRA = "source_id_extra" const val SOURCE_ID_EXTRA = "source_id_extra"
const val COOKIES_EXTRA = "cookies_extra" const val COOKIES_EXTRA = "cookies_extra"
const val SCRIPT_EXTRA = "script_extra" const val SCRIPT_EXTRA = "script_extra"
const val URL_EXTRA = "url_extra" const val URL_EXTRA = "url_extra"
const val ASBTN_EXTRA = "asbtn_extra" const val ASBTN_EXTRA = "asbtn_extra"
const val ACTION_NAME_EXTRA = "action_name_extra"
const val HEADERS_EXTRA = "headers_extra"
const val STAGE_CHECKBOX = 0 const val STAGE_CHECKBOX = 0
const val STAGE_GET_AUDIO_BTN_LOCATION = 1 const val STAGE_GET_AUDIO_BTN_LOCATION = 1
@ -600,13 +615,13 @@ class SolveCaptchaActivity : AppCompatActivity() {
val TRANSCRIPT_CLEANER_REGEX = Regex("[^0-9a-zA-Z_ -]") val TRANSCRIPT_CLEANER_REGEX = Regex("[^0-9a-zA-Z_ -]")
val SPACE_DEDUPE_REGEX = Regex(" +") val SPACE_DEDUPE_REGEX = Regex(" +")
fun launch(context: Context, fun launchCaptcha(context: Context,
source: CaptchaCompletionVerifier, source: ActionCompletionVerifier,
cookies: Map<String, String>, cookies: Map<String, String>,
script: String, script: String?,
url: String, url: String,
autoSolveSubmitBtnSelector: String? = null) { autoSolveSubmitBtnSelector: String? = null) {
val intent = Intent(context, SolveCaptchaActivity::class.java).apply { val intent = Intent(context, BrowserActionActivity::class.java).apply {
putExtra(SOURCE_ID_EXTRA, source.id) putExtra(SOURCE_ID_EXTRA, source.id)
putExtra(COOKIES_EXTRA, HashMap(cookies)) putExtra(COOKIES_EXTRA, HashMap(cookies))
putExtra(SCRIPT_EXTRA, script) putExtra(SCRIPT_EXTRA, script)
@ -620,25 +635,57 @@ class SolveCaptchaActivity : AppCompatActivity() {
fun launchUniversal(context: Context, fun launchUniversal(context: Context,
source: HttpSource, source: HttpSource,
url: String) { url: String) {
val intent = Intent(context, SolveCaptchaActivity::class.java).apply { val intent = Intent(context, BrowserActionActivity::class.java).apply {
putExtra(SOURCE_ID_EXTRA, source.id) putExtra(SOURCE_ID_EXTRA, source.id)
putExtra(URL_EXTRA, url) putExtra(URL_EXTRA, url)
} }
context.startActivity(intent) context.startActivity(intent)
} }
fun launchAction(context: Context,
completionVerifier: ActionCompletionVerifier,
script: String?,
url: String,
actionName: String) {
val intent = Intent(context, BrowserActionActivity::class.java).apply {
putExtra(SOURCE_ID_EXTRA, completionVerifier.id)
putExtra(SCRIPT_EXTRA, script)
putExtra(URL_EXTRA, url)
putExtra(ACTION_NAME_EXTRA, actionName)
}
context.startActivity(intent)
}
fun launchAction(context: Context,
completionVerifier: (String) -> Boolean,
script: String?,
url: String,
actionName: String,
headers: Map<String, String>? = emptyMap()) {
val intent = Intent(context, BrowserActionActivity::class.java).apply {
putExtra(HEADERS_EXTRA, HashMap(headers))
putExtra(VERIFY_LAMBDA_EXTRA, completionVerifier as Serializable)
putExtra(SCRIPT_EXTRA, script)
putExtra(URL_EXTRA, url)
putExtra(ACTION_NAME_EXTRA, actionName)
}
context.startActivity(intent)
}
} }
} }
class NoopCaptchaCompletionVerifier(private val source: HttpSource): DelegatedHttpSource(source), class NoopActionCompletionVerifier(private val source: HttpSource): DelegatedHttpSource(source),
CaptchaCompletionVerifier { ActionCompletionVerifier {
override val versionId get() = source.versionId override val versionId get() = source.versionId
override val lang: String get() = source.lang override val lang: String get() = source.lang
override fun verifyNoCaptcha(url: String) = false override fun verifyComplete(url: String) = false
} }
interface CaptchaCompletionVerifier : Source { interface ActionCompletionVerifier : Source {
fun verifyNoCaptcha(url: String): Boolean fun verifyComplete(url: String): Boolean
} }

View File

@ -7,11 +7,11 @@ import android.webkit.WebResourceResponse
import android.webkit.WebView import android.webkit.WebView
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
open class HeadersInjectingWebViewClient(activity: SolveCaptchaActivity, open class HeadersInjectingWebViewClient(activity: BrowserActionActivity,
source: CaptchaCompletionVerifier, verifyComplete: (String) -> Boolean,
injectScript: String?, injectScript: String?,
private val headers: Map<String, String>) private val headers: Map<String, String>)
: BasicWebViewClient(activity, source, injectScript) { : BasicWebViewClient(activity, verifyComplete, injectScript) {
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
// Temp disabled as it's unreliable // Temp disabled as it's unreliable

View File

@ -0,0 +1,31 @@
package exh.util
import com.elvishew.xlog.XLog
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Response
import okhttp3.ResponseBody
import org.jsoup.nodes.Document
fun Response.interceptAsHtml(block: (Document) -> Unit): Response {
val body = body()
if(body != null) {
if (body.contentType()?.type() == "text"
&& body.contentType()?.subtype() == "html") {
val bodyString = body.string()
val rebuiltResponse = newBuilder()
.body(ResponseBody.create(body.contentType(), bodyString))
.build()
try {
// Search for captcha
val parsed = asJsoup(html = bodyString)
block(parsed)
} catch (t: Throwable) {
// Ignore all errors
XLog.w("Interception error!", t)
}
return rebuiltResponse
}
}
return this
}