Add universal captcha solver
This commit is contained in:
parent
852c1a423d
commit
ec4af65c36
@ -1,16 +1,19 @@
|
||||
package eu.kanade.tachiyomi.source.online
|
||||
|
||||
import android.app.Application
|
||||
import com.elvishew.xlog.XLog
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.network.newCallWithProgress
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.model.*
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import exh.ui.captcha.SolveCaptchaActivity
|
||||
import okhttp3.*
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.lang.Exception
|
||||
import java.net.URI
|
||||
@ -65,7 +68,37 @@ abstract class HttpSource : CatalogueSource {
|
||||
* Default network client for doing requests.
|
||||
*/
|
||||
open val client: OkHttpClient
|
||||
get() = network.client
|
||||
get() = network.client.newBuilder().addInterceptor { chain ->
|
||||
val response = chain.proceed(chain.request())
|
||||
val body = response.body()
|
||||
if(!response.isSuccessful && body != null) {
|
||||
if(body.contentType()?.type() == "text"
|
||||
&& body.contentType()?.subtype() == "html") {
|
||||
val bodyString = body.string()
|
||||
val rebuiltResponse = response.newBuilder()
|
||||
.body(ResponseBody.create(body.contentType(), bodyString))
|
||||
.build()
|
||||
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
|
||||
}
|
||||
}
|
||||
response
|
||||
}.build()
|
||||
|
||||
/**
|
||||
* Headers builder for requests. Implementations can override this method for custom headers.
|
||||
|
@ -290,7 +290,8 @@ class Tsumino(private val context: Context): ParsedHttpSource(), LewdSource<Tsum
|
||||
}
|
||||
|
||||
override val client: OkHttpClient
|
||||
get() = super.client.newBuilder()
|
||||
// Do not call super here as we don't want auto-captcha detection here
|
||||
get() = network.client.newBuilder()
|
||||
.cookieJar(CookieJar.NO_COOKIES)
|
||||
.addNetworkInterceptor {
|
||||
val cAspNetCookie = preferences.eh_ts_aspNetCookie().getOrDefault()
|
||||
|
@ -2,9 +2,7 @@ package exh.eh
|
||||
|
||||
import android.support.v4.util.AtomicFile
|
||||
import android.util.SparseArray
|
||||
import android.util.SparseIntArray
|
||||
import com.elvishew.xlog.XLog
|
||||
import exh.ui.captcha.SolveCaptchaActivity.Companion.launch
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
@ -14,7 +14,8 @@ import java.nio.charset.Charset
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
class AutoSolvingWebViewClient(activity: SolveCaptchaActivity,
|
||||
source: CaptchaCompletionVerifier,
|
||||
injectScript: String?)
|
||||
injectScript: String?,
|
||||
private val headers: Map<String, String>)
|
||||
: BasicWebViewClient(activity, source, injectScript) {
|
||||
|
||||
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
|
||||
@ -31,6 +32,24 @@ class AutoSolvingWebViewClient(activity: SolveCaptchaActivity,
|
||||
doc.toString().byteInputStream(Charset.forName("UTF-8")).buffered()
|
||||
)
|
||||
}
|
||||
if(headers.isNotEmpty()) {
|
||||
val response = activity.httpClient.newCall(request.toOkHttpRequest()
|
||||
.newBuilder()
|
||||
.apply {
|
||||
headers.forEach { (n, v) -> addHeader(n, v) }
|
||||
}
|
||||
.build())
|
||||
.execute()
|
||||
|
||||
return WebResourceResponse(
|
||||
response.body()?.contentType()?.let { "${it.type()}/${it.subtype()}" },
|
||||
response.body()?.contentType()?.charset()?.toString(),
|
||||
response.code(),
|
||||
response.message(),
|
||||
response.headers().toMultimap().mapValues { it.value.joinToString(",") },
|
||||
response.body()?.byteStream()
|
||||
)
|
||||
}
|
||||
return super.shouldInterceptRequest(view, request)
|
||||
}
|
||||
}
|
@ -26,17 +26,18 @@ import android.os.SystemClock
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import exh.log.maybeInjectEHLogger
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import exh.source.DelegatedHttpSource
|
||||
import exh.util.melt
|
||||
import rx.Observable
|
||||
|
||||
class SolveCaptchaActivity : AppCompatActivity() {
|
||||
private val sourceManager: SourceManager by injectLazy()
|
||||
private val preferencesHelper: PreferencesHelper by injectLazy()
|
||||
private val networkHelper: NetworkHelper by injectLazy()
|
||||
|
||||
val httpClient = OkHttpClient.Builder()
|
||||
.maybeInjectEHLogger()
|
||||
.build()
|
||||
val httpClient = networkHelper.client
|
||||
private val jsonParser = JsonParser()
|
||||
|
||||
private var currentLoopId: String? = null
|
||||
@ -51,9 +52,19 @@ class SolveCaptchaActivity : AppCompatActivity() {
|
||||
setContentView(eu.kanade.tachiyomi.R.layout.eh_activity_captcha)
|
||||
|
||||
val sourceId = intent.getLongExtra(SOURCE_ID_EXTRA, -1)
|
||||
val source = if(sourceId != -1L)
|
||||
sourceManager.get(sourceId) as? CaptchaCompletionVerifier
|
||||
else null
|
||||
val originalSource = if(sourceId != -1L) sourceManager.get(sourceId) else null
|
||||
val source = if(originalSource != null) {
|
||||
originalSource as? CaptchaCompletionVerifier
|
||||
?: run {
|
||||
(originalSource as? HttpSource)?.let {
|
||||
NoopCaptchaCompletionVerifier(it)
|
||||
}
|
||||
}
|
||||
} else null
|
||||
|
||||
val headers = (source as? HttpSource)?.headers?.toMultimap()?.mapValues {
|
||||
it.value.joinToString(",")
|
||||
} ?: emptyMap()
|
||||
|
||||
val cookies: HashMap<String, String>?
|
||||
= intent.getSerializableExtra(COOKIES_EXTRA) as? HashMap<String, String>
|
||||
@ -62,7 +73,7 @@ class SolveCaptchaActivity : AppCompatActivity() {
|
||||
|
||||
val url: String? = intent.getStringExtra(URL_EXTRA)
|
||||
|
||||
if(source == null || cookies == null || url == null) {
|
||||
if(source == null || url == null) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
@ -73,8 +84,7 @@ class SolveCaptchaActivity : AppCompatActivity() {
|
||||
|
||||
val cm = CookieManager.getInstance()
|
||||
|
||||
fun continueLoading() {
|
||||
cookies.forEach { (t, u) ->
|
||||
cookies?.forEach { (t, u) ->
|
||||
val cookieString = t + "=" + u + "; domain=" + parsedUrl.host
|
||||
cm.setCookie(url, cookieString)
|
||||
}
|
||||
@ -130,20 +140,12 @@ class SolveCaptchaActivity : AppCompatActivity() {
|
||||
}.melt()
|
||||
|
||||
webview.addJavascriptInterface(this@SolveCaptchaActivity, "exh")
|
||||
AutoSolvingWebViewClient(this, source, script)
|
||||
AutoSolvingWebViewClient(this, source, script, headers)
|
||||
} else {
|
||||
BasicWebViewClient(this, source, script)
|
||||
}
|
||||
|
||||
webview.loadUrl(url)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
cm.removeAllCookies { continueLoading() }
|
||||
} else {
|
||||
cm.removeAllCookie()
|
||||
continueLoading()
|
||||
}
|
||||
webview.loadUrl(url, headers)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
@ -611,7 +613,26 @@ class SolveCaptchaActivity : AppCompatActivity() {
|
||||
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
fun launchUniversal(context: Context,
|
||||
source: HttpSource,
|
||||
url: String) {
|
||||
val intent = Intent(context, SolveCaptchaActivity::class.java).apply {
|
||||
putExtra(SOURCE_ID_EXTRA, source.id)
|
||||
putExtra(URL_EXTRA, url)
|
||||
}
|
||||
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NoopCaptchaCompletionVerifier(private val source: HttpSource): DelegatedHttpSource(source),
|
||||
CaptchaCompletionVerifier {
|
||||
override val versionId get() = source.versionId
|
||||
override val lang: String get() = source.lang
|
||||
|
||||
override fun verifyNoCaptcha(url: String) = false
|
||||
}
|
||||
|
||||
interface CaptchaCompletionVerifier : Source {
|
||||
|
Loading…
x
Reference in New Issue
Block a user