diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt
index cb76f1162..2c503b5f5 100755
--- a/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt
@@ -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.
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt
index 84e080f6c..960bd0871 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt
@@ -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()
diff --git a/app/src/main/java/exh/eh/MemAutoFlushingLookupTable.kt b/app/src/main/java/exh/eh/MemAutoFlushingLookupTable.kt
index 46b21306d..dd053ce33 100644
--- a/app/src/main/java/exh/eh/MemAutoFlushingLookupTable.kt
+++ b/app/src/main/java/exh/eh/MemAutoFlushingLookupTable.kt
@@ -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
diff --git a/app/src/main/java/exh/ui/captcha/AutoSolvingWebViewClient.kt b/app/src/main/java/exh/ui/captcha/AutoSolvingWebViewClient.kt
index c492df15a..08aa7cee7 100644
--- a/app/src/main/java/exh/ui/captcha/AutoSolvingWebViewClient.kt
+++ b/app/src/main/java/exh/ui/captcha/AutoSolvingWebViewClient.kt
@@ -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)
     }
 }
\ No newline at end of file
diff --git a/app/src/main/java/exh/ui/captcha/SolveCaptchaActivity.kt b/app/src/main/java/exh/ui/captcha/SolveCaptchaActivity.kt
index 4ab91e0c7..59a614153 100644
--- a/app/src/main/java/exh/ui/captcha/SolveCaptchaActivity.kt
+++ b/app/src/main/java/exh/ui/captcha/SolveCaptchaActivity.kt
@@ -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,80 +84,71 @@ class SolveCaptchaActivity : AppCompatActivity() {
 
         val cm = CookieManager.getInstance()
 
-        fun continueLoading() {
-            cookies.forEach { (t, u) ->
-                val cookieString = t + "=" + u + "; domain=" + parsedUrl.host
-                cm.setCookie(url, cookieString)
-            }
+        cookies?.forEach { (t, u) ->
+            val cookieString = t + "=" + u + "; domain=" + parsedUrl.host
+            cm.setCookie(url, cookieString)
+        }
 
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
-                CookieSyncManager.createInstance(this).sync()
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
+            CookieSyncManager.createInstance(this).sync()
 
-            webview.settings.javaScriptEnabled = true
-            webview.settings.domStorageEnabled = true
+        webview.settings.javaScriptEnabled = true
+        webview.settings.domStorageEnabled = true
 
-            var loadedInners = 0
+        var loadedInners = 0
 
-            webview.webChromeClient = object : WebChromeClient() {
-                override fun onJsAlert(view: WebView?, url: String?, message: String, result: JsResult): Boolean {
-                    if(message.startsWith("exh-")) {
-                        loadedInners++
-                        // Wait for both inner scripts to be loaded
-                        if(loadedInners >= 2) {
-                            // Attempt to autosolve captcha
-                            if(preferencesHelper.eh_autoSolveCaptchas().getOrDefault()
-                                    && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-                                webview.post {
-                                    // 10 seconds to auto-solve captcha
-                                    strictValidationStartTime = System.currentTimeMillis() + 1000 * 10
-                                    beginSolveLoop()
-                                    beginValidateCaptchaLoop()
-                                    webview.evaluateJavascript(SOLVE_UI_SCRIPT_HIDE) {
-                                        webview.evaluateJavascript(SOLVE_UI_SCRIPT_SHOW, null)
-                                    }
+        webview.webChromeClient = object : WebChromeClient() {
+            override fun onJsAlert(view: WebView?, url: String?, message: String, result: JsResult): Boolean {
+                if(message.startsWith("exh-")) {
+                    loadedInners++
+                    // Wait for both inner scripts to be loaded
+                    if(loadedInners >= 2) {
+                        // Attempt to autosolve captcha
+                        if(preferencesHelper.eh_autoSolveCaptchas().getOrDefault()
+                                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                            webview.post {
+                                // 10 seconds to auto-solve captcha
+                                strictValidationStartTime = System.currentTimeMillis() + 1000 * 10
+                                beginSolveLoop()
+                                beginValidateCaptchaLoop()
+                                webview.evaluateJavascript(SOLVE_UI_SCRIPT_HIDE) {
+                                    webview.evaluateJavascript(SOLVE_UI_SCRIPT_SHOW, null)
                                 }
                             }
                         }
-                        result.confirm()
-                        return true
                     }
-                    return false
+                    result.confirm()
+                    return true
                 }
+                return false
             }
-
-            webview.webViewClient = if (preferencesHelper.eh_autoSolveCaptchas().getOrDefault()
-                    && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-                // Fetch auto-solve credentials early for speed
-                credentialsObservable = httpClient.newCall(Request.Builder()
-                        // Rob demo credentials
-                        .url("https://speech-to-text-demo.ng.bluemix.net/api/v1/credentials")
-                        .build())
-                        .asObservableSuccess()
-                        .subscribeOn(Schedulers.io())
-                        .map {
-                            val json = jsonParser.parse(it.body()!!.string())
-                            it.close()
-                            json["token"].string
-                        }.melt()
-
-                webview.addJavascriptInterface(this@SolveCaptchaActivity, "exh")
-                AutoSolvingWebViewClient(this, source, script)
-            } else {
-                BasicWebViewClient(this, source, script)
-            }
-
-            webview.loadUrl(url)
         }
 
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            cm.removeAllCookies { continueLoading() }
+        webview.webViewClient = if (preferencesHelper.eh_autoSolveCaptchas().getOrDefault()
+                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            // Fetch auto-solve credentials early for speed
+            credentialsObservable = httpClient.newCall(Request.Builder()
+                    // Rob demo credentials
+                    .url("https://speech-to-text-demo.ng.bluemix.net/api/v1/credentials")
+                    .build())
+                    .asObservableSuccess()
+                    .subscribeOn(Schedulers.io())
+                    .map {
+                        val json = jsonParser.parse(it.body()!!.string())
+                        it.close()
+                        json["token"].string
+                    }.melt()
+
+            webview.addJavascriptInterface(this@SolveCaptchaActivity, "exh")
+            AutoSolvingWebViewClient(this, source, script, headers)
         } else {
-            cm.removeAllCookie()
-            continueLoading()
+            BasicWebViewClient(this, source, script)
         }
 
+        webview.loadUrl(url, headers)
+
         setSupportActionBar(toolbar)
-        
+
         supportActionBar?.setDisplayHomeAsUpEnabled(true)
     }
 
@@ -611,9 +613,28 @@ 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 {
     fun verifyNoCaptcha(url: String): Boolean
 }