Improvements to accuracy of universal captcha detection

This commit is contained in:
NerdNumber9
2019-04-21 23:01:41 -04:00
parent 59fcfbe9d2
commit c5ab79f618
5 changed files with 122 additions and 52 deletions

View File

@ -3,10 +3,6 @@ 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.MANGADEX_DOMAIN
import exh.patch.MANGADEX_SOURCE_ID
import exh.patch.attachMangaDexLogin
import exh.patch.detectCaptchas
import okhttp3.* import okhttp3.*
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@ -35,8 +31,7 @@ open class NetworkHelper(context: Context) {
open val cloudflareClient = client.newBuilder() open val cloudflareClient = client.newBuilder()
.addInterceptor(CloudflareInterceptor(context)) .addInterceptor(CloudflareInterceptor(context))
.attachMangaDexLogin() .maybeInjectEHLogger()
.detectCaptchas(context, MANGADEX_SOURCE_ID, listOf(MANGADEX_DOMAIN))
.build() .build()
private fun OkHttpClient.Builder.enableTLS12(): OkHttpClient.Builder { private fun OkHttpClient.Builder.enableTLS12(): OkHttpClient.Builder {

View File

@ -6,13 +6,12 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.network.* import eu.kanade.tachiyomi.network.*
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 exh.patch.detectCaptchas import exh.patch.injectPatches
import exh.source.DelegatedHttpSource import exh.source.DelegatedHttpSource
import okhttp3.* import okhttp3.*
import rx.Observable import rx.Observable
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.lang.Exception import java.lang.Exception
import java.net.URI import java.net.URI
import java.net.URISyntaxException import java.net.URISyntaxException
@ -31,9 +30,15 @@ abstract class HttpSource : CatalogueSource {
object : NetworkHelper(Injekt.get<Application>()) { object : NetworkHelper(Injekt.get<Application>()) {
override val client: OkHttpClient? override val client: OkHttpClient?
get() = delegate?.networkHttpClient ?: original.client get() = delegate?.networkHttpClient ?: original.client
.newBuilder()
.injectPatches { id }
.build()
override val cloudflareClient: OkHttpClient? override val cloudflareClient: OkHttpClient?
get() = delegate?.networkCloudflareClient ?: original.cloudflareClient get() = delegate?.networkCloudflareClient ?: original.cloudflareClient
.newBuilder()
.injectPatches { id }
.build()
override val cookieManager: AndroidCookieJar override val cookieManager: AndroidCookieJar
get() = original.cookieManager get() = original.cookieManager
@ -79,9 +84,6 @@ abstract class HttpSource : CatalogueSource {
*/ */
open val client: OkHttpClient open val client: OkHttpClient
get() = delegate?.baseHttpClient ?: network.client get() = delegate?.baseHttpClient ?: network.client
.newBuilder()
.detectCaptchas(Injekt.get<Application>(), id, null)
.build()
/** /**
* Headers builder for requests. Implementations can override this method for custom headers. * Headers builder for requests. Implementations can override this method for custom headers.

View File

@ -24,10 +24,8 @@ private fun verifyComplete(url: String): Boolean {
} ?: false } ?: false
} }
fun OkHttpClient.Builder.attachMangaDexLogin() = val MANGADEX_LOGIN_PATCH: EHInterceptor = { request, response, sourceId ->
addInterceptor { chain -> if(request.url().host() == MANGADEX_DOMAIN) {
val response = chain.proceed(chain.request())
if(response.request().url().host() == MANGADEX_DOMAIN) {
response.interceptAsHtml { doc -> response.interceptAsHtml { doc ->
if (doc.title().trim().equals("Login - MangaDex", true)) { if (doc.title().trim().equals("Login - MangaDex", true)) {
BrowserActionActivity.launchAction( BrowserActionActivity.launchAction(
@ -36,14 +34,53 @@ fun OkHttpClient.Builder.attachMangaDexLogin() =
HIDE_SCRIPT, HIDE_SCRIPT,
"https://mangadex.org/login", "https://mangadex.org/login",
"Login", "Login",
(Injekt.get<SourceManager>().get(MANGADEX_SOURCE_ID) as? HttpSource)?.headers?.toMultimap()?.mapValues { (Injekt.get<SourceManager>().get(sourceId) as? HttpSource)?.headers?.toMultimap()?.mapValues {
it.value.joinToString(",") it.value.joinToString(",")
} ?: emptyMap() } ?: emptyMap()
) )
} }
} }
} else response } else response
} }
const val MANGADEX_SOURCE_ID = 2499283573021220255 val MANGADEX_SOURCE_IDS = listOf(
2499283573021220255,
8033579885162383068,
1952071260038453057,
2098905203823335614,
5098537545549490547,
4505830566611664829,
9194073792736219759,
6400665728063187402,
4938773340256184018,
5860541308324630662,
5189216366882819742,
2655149515337070132,
1145824452519314725,
3846770256925560569,
3807502156582598786,
4284949320785450865,
5463447640980279236,
8578871918181236609,
6750440049024086587,
3339599426223341161,
5148895169070562838,
1493666528525752601,
1713554459881080228,
4150470519566206911,
1347402746269051958,
3578612018159256808,
425785191804166217,
8254121249433835847,
3260701926561129943,
1411768577036936240,
3285208643537017688,
737986167355114438,
1471784905273036181,
5967745367608513818,
3781216447842245147,
4774459486579224459,
4710920497926776490,
5779037855201976894
)
const val MANGADEX_DOMAIN = "mangadex.org" const val MANGADEX_DOMAIN = "mangadex.org"

View File

@ -0,0 +1,41 @@
package exh.patch
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
typealias EHInterceptor = (request: Request, response: Response, sourceId: Long) -> Response
fun OkHttpClient.Builder.injectPatches(sourceIdProducer: () -> Long): OkHttpClient.Builder {
return addInterceptor { chain ->
val req = chain.request()
val response = chain.proceed(req)
val sourceId = sourceIdProducer()
findAndApplyPatches(sourceId)(req, response, sourceId)
}
}
fun findAndApplyPatches(sourceId: Long): EHInterceptor {
return ((EH_INTERCEPTORS[sourceId] ?: emptyList()) +
(EH_INTERCEPTORS[EH_UNIVERSAL_INTERCEPTOR] ?: emptyList())).merge()
}
fun List<EHInterceptor>.merge(): EHInterceptor {
return { request, response, sourceId ->
fold(response) { acc, int ->
int(request, acc, sourceId)
}
}
}
private const val EH_UNIVERSAL_INTERCEPTOR = -1L
private val EH_INTERCEPTORS: Map<Long, List<EHInterceptor>> = mapOf(
EH_UNIVERSAL_INTERCEPTOR to listOf(
CAPTCHA_DETECTION_PATCH // Auto captcha detection
),
// MangaDex login support
*MANGADEX_SOURCE_IDS.map { id ->
id to listOf(MANGADEX_LOGIN_PATCH)
}.toTypedArray()
)

View File

@ -1,28 +1,23 @@
package exh.patch package exh.patch
import android.content.Context import android.app.Application
import exh.ui.captcha.BrowserActionActivity import exh.ui.captcha.BrowserActionActivity
import exh.util.interceptAsHtml import exh.util.interceptAsHtml
import okhttp3.OkHttpClient import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
fun OkHttpClient.Builder.detectCaptchas(context: Context, sourceId: Long, domains: List<String>? = null): OkHttpClient.Builder { val CAPTCHA_DETECTION_PATCH: EHInterceptor = { request, response, sourceId ->
return addInterceptor { chain ->
// Automatic captcha detection
val response = chain.proceed(chain.request())
if(!response.isSuccessful) { if(!response.isSuccessful) {
if(domains != null && response.request().url().host() !in domains)
return@addInterceptor response
response.interceptAsHtml { doc -> response.interceptAsHtml { doc ->
// Find captcha
if (doc.getElementsByClass("g-recaptcha").isNotEmpty()) { if (doc.getElementsByClass("g-recaptcha").isNotEmpty()) {
// Found it, allow the user to solve this thing // Found it, allow the user to solve this thing
BrowserActionActivity.launchUniversal( BrowserActionActivity.launchUniversal(
context, Injekt.get<Application>(),
sourceId, sourceId,
chain.request().url().toString() request.url().toString()
) )
} }
} }
} else response } else response
}
} }