mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-10 01:55:06 +01:00
Add an UserAgent Interceptor to Cloudflare Client
This commit is contained in:
parent
329e8f1988
commit
4e54690229
@ -8,6 +8,7 @@ import android.webkit.WebSettings
|
|||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
|
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
|
||||||
import eu.kanade.tachiyomi.util.system.isOutdated
|
import eu.kanade.tachiyomi.util.system.isOutdated
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
@ -55,18 +56,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
|
|||||||
.firstOrNull { it.name == "cf_clearance" }
|
.firstOrNull { it.name == "cf_clearance" }
|
||||||
resolveWithWebView(originalRequest, oldCookie)
|
resolveWithWebView(originalRequest, oldCookie)
|
||||||
|
|
||||||
// Avoid use empty User-Agent
|
return chain.proceed(originalRequest)
|
||||||
return if (originalRequest.header("User-Agent").isNullOrEmpty()) {
|
|
||||||
val newRequest = originalRequest
|
|
||||||
.newBuilder()
|
|
||||||
.removeHeader("User-Agent")
|
|
||||||
.addHeader("User-Agent",
|
|
||||||
DEFAULT_USERAGENT)
|
|
||||||
.build()
|
|
||||||
chain.proceed(newRequest)
|
|
||||||
} else {
|
|
||||||
chain.proceed(originalRequest)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
|
// Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
|
||||||
// we don't crash the entire app
|
// we don't crash the entire app
|
||||||
@ -84,11 +74,10 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
|
|||||||
|
|
||||||
var challengeFound = false
|
var challengeFound = false
|
||||||
var cloudflareBypassed = false
|
var cloudflareBypassed = false
|
||||||
var isWebviewOutdated = false
|
var isWebViewOutdated = false
|
||||||
|
|
||||||
val origRequestUrl = request.url.toString()
|
val origRequestUrl = request.url.toString()
|
||||||
val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }
|
val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }
|
||||||
val withUserAgent = request.header("User-Agent").isNullOrEmpty()
|
|
||||||
|
|
||||||
handler.post {
|
handler.post {
|
||||||
val webview = WebView(context)
|
val webview = WebView(context)
|
||||||
@ -96,15 +85,15 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
|
|||||||
webview.settings.javaScriptEnabled = true
|
webview.settings.javaScriptEnabled = true
|
||||||
|
|
||||||
// Avoid set empty User-Agent, Chromium WebView will reset to default if empty
|
// Avoid set empty User-Agent, Chromium WebView will reset to default if empty
|
||||||
webview.settings.userAgentString = request.header("User-Agent")
|
webview.settings.userAgentString =
|
||||||
?: DEFAULT_USERAGENT
|
request.header("User-Agent") ?: HttpSource.DEFAULT_USERAGENT
|
||||||
|
|
||||||
webview.webViewClient = object : WebViewClientCompat() {
|
webview.webViewClient = object : WebViewClientCompat() {
|
||||||
override fun onPageFinished(view: WebView, url: String) {
|
override fun onPageFinished(view: WebView, url: String) {
|
||||||
fun isCloudFlareBypassed(): Boolean {
|
fun isCloudFlareBypassed(): Boolean {
|
||||||
return networkHelper.cookieManager.get(origRequestUrl.toHttpUrl())
|
return networkHelper.cookieManager.get(origRequestUrl.toHttpUrl())
|
||||||
.firstOrNull { it.name == "cf_clearance" }
|
.firstOrNull { it.name == "cf_clearance" }
|
||||||
.let { it != null && (it != oldCookie || withUserAgent) }
|
.let { it != null && it != oldCookie }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCloudFlareBypassed()) {
|
if (isCloudFlareBypassed()) {
|
||||||
@ -147,7 +136,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
|
|||||||
|
|
||||||
handler.post {
|
handler.post {
|
||||||
if (!cloudflareBypassed) {
|
if (!cloudflareBypassed) {
|
||||||
isWebviewOutdated = webView?.isOutdated() == true
|
isWebViewOutdated = webView?.isOutdated() == true
|
||||||
}
|
}
|
||||||
|
|
||||||
webView?.stopLoading()
|
webView?.stopLoading()
|
||||||
@ -157,7 +146,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
|
|||||||
// Throw exception if we failed to bypass Cloudflare
|
// Throw exception if we failed to bypass Cloudflare
|
||||||
if (!cloudflareBypassed) {
|
if (!cloudflareBypassed) {
|
||||||
// Prompt user to update WebView if it seems too outdated
|
// Prompt user to update WebView if it seems too outdated
|
||||||
if (isWebviewOutdated) {
|
if (isWebViewOutdated) {
|
||||||
context.toast(R.string.please_update_webview, Toast.LENGTH_LONG)
|
context.toast(R.string.please_update_webview, Toast.LENGTH_LONG)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,6 +157,5 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
|
|||||||
companion object {
|
companion object {
|
||||||
private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare")
|
private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare")
|
||||||
private val COOKIE_NAMES = listOf("__cfduid", "cf_clearance")
|
private val COOKIE_NAMES = listOf("__cfduid", "cf_clearance")
|
||||||
private const val DEFAULT_USERAGENT = "Mozilla/5.0 (Windows NT 6.3; WOW64)"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ class NetworkHelper(context: Context) {
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
val cloudflareClient = client.newBuilder()
|
val cloudflareClient = client.newBuilder()
|
||||||
|
.addInterceptor(UserAgentInterceptor())
|
||||||
.addInterceptor(CloudflareInterceptor(context))
|
.addInterceptor(CloudflareInterceptor(context))
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
package eu.kanade.tachiyomi.network
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
|
||||||
|
class UserAgentInterceptor : Interceptor {
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val originalRequest = chain.request()
|
||||||
|
|
||||||
|
return if (originalRequest.header("User-Agent").isNullOrEmpty()) {
|
||||||
|
val newRequest = originalRequest
|
||||||
|
.newBuilder()
|
||||||
|
.removeHeader("User-Agent")
|
||||||
|
.addHeader("User-Agent", HttpSource.DEFAULT_USERAGENT)
|
||||||
|
.build()
|
||||||
|
chain.proceed(newRequest)
|
||||||
|
} else {
|
||||||
|
chain.proceed(originalRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -74,7 +74,7 @@ abstract class HttpSource : CatalogueSource {
|
|||||||
* Headers builder for requests. Implementations can override this method for custom headers.
|
* Headers builder for requests. Implementations can override this method for custom headers.
|
||||||
*/
|
*/
|
||||||
protected open fun headersBuilder() = Headers.Builder().apply {
|
protected open fun headersBuilder() = Headers.Builder().apply {
|
||||||
add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)")
|
add("User-Agent", DEFAULT_USERAGENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -207,14 +207,14 @@ abstract class HttpSource : CatalogueSource {
|
|||||||
* @param manga the manga to look for chapters.
|
* @param manga the manga to look for chapters.
|
||||||
*/
|
*/
|
||||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
if (manga.status != SManga.LICENSED) {
|
return if (manga.status != SManga.LICENSED) {
|
||||||
return client.newCall(chapterListRequest(manga))
|
client.newCall(chapterListRequest(manga))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map { response ->
|
.map { response ->
|
||||||
chapterListParse(response)
|
chapterListParse(response)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Observable.error(Exception("Licensed - No chapters to show"))
|
Observable.error(Exception("Licensed - No chapters to show"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,16 +340,14 @@ abstract class HttpSource : CatalogueSource {
|
|||||||
* @param orig the full url.
|
* @param orig the full url.
|
||||||
*/
|
*/
|
||||||
private fun getUrlWithoutDomain(orig: String): String {
|
private fun getUrlWithoutDomain(orig: String): String {
|
||||||
try {
|
return try {
|
||||||
val uri = URI(orig)
|
val uri = URI(orig)
|
||||||
var out = uri.path
|
var out = uri.path
|
||||||
if (uri.query != null)
|
if (uri.query != null) out += "?" + uri.query
|
||||||
out += "?" + uri.query
|
if (uri.fragment != null) out += "#" + uri.fragment
|
||||||
if (uri.fragment != null)
|
out
|
||||||
out += "#" + uri.fragment
|
|
||||||
return out
|
|
||||||
} catch (e: URISyntaxException) {
|
} catch (e: URISyntaxException) {
|
||||||
return orig
|
orig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,4 +365,8 @@ 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()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val DEFAULT_USERAGENT = "Mozilla/5.0 (Windows NT 6.3; WOW64)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user