cloudstream/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt

155 lines
5.9 KiB
Kotlin
Raw Normal View History

package com.lagradost.cloudstream3.network
import android.annotation.SuppressLint
import android.net.http.SslError
import android.webkit.*
import com.lagradost.cloudstream3.AcraApplication
2021-10-28 11:28:19 +00:00
import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.cloudstream3.utils.Coroutines.main
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
2021-10-28 11:28:19 +00:00
import java.net.URI
import java.util.concurrent.TimeUnit
class WebViewResolver(val interceptUrl: Regex) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
return runBlocking {
val fixedRequest = resolveUsingWebView(request)
return@runBlocking chain.proceed(fixedRequest ?: request)
}
}
@SuppressLint("SetJavaScriptEnabled")
suspend fun resolveUsingWebView(request: Request): Request? {
val url = request.url.toString()
2021-10-10 20:01:02 +00:00
val headers = request.headers
println("Initial web-view request: $url")
var webView: WebView? = null
fun destroyWebView() {
main {
webView?.stopLoading()
webView?.destroy()
webView = null
println("Destroyed webview")
}
}
var fixedRequest: Request? = null
main {
// Useful for debugging
// WebView.setWebContentsDebuggingEnabled(true)
webView = WebView(
AcraApplication.context ?: throw RuntimeException("No base context in WebViewResolver")
).apply {
// Bare minimum to bypass captcha
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
2021-10-28 11:28:19 +00:00
settings.userAgentString = USER_AGENT
}
webView?.webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest
): WebResourceResponse? {
val webViewUrl = request.url.toString()
2021-10-28 11:28:19 +00:00
// println("Loading WebView URL: $webViewUrl")
2021-10-18 16:04:05 +00:00
if (interceptUrl.containsMatchIn(webViewUrl)) {
fixedRequest = getRequestCreator(
webViewUrl,
request.requestHeaders,
null,
mapOf(),
mapOf(),
10,
TimeUnit.MINUTES
)
println("Web-view request finished: $webViewUrl")
destroyWebView()
2021-10-28 11:28:19 +00:00
return null
}
2021-10-18 16:04:05 +00:00
2021-10-28 11:28:19 +00:00
// Suppress image requests as we don't display them anywhere
// Less data, low chance of causing issues.
val blacklistedFiles = listOf(".jpg", ".png", ".webp", ".jpeg", ".webm", ".mp4")
/** NOTE! request.requestHeaders is not perfect!
* They don't contain all the headers the browser actually gives.
* Overriding with okhttp might fuck up otherwise working requests,
* e.g the recaptcha request.
* **/
2021-10-18 16:04:05 +00:00
return try {
when {
2021-10-28 11:28:19 +00:00
blacklistedFiles.any { URI(webViewUrl).path.endsWith(it) } || webViewUrl.endsWith(
"/favicon.ico"
) -> WebResourceResponse(
"image/png",
null,
null
)
2021-10-18 16:04:05 +00:00
webViewUrl.contains("recaptcha") -> super.shouldInterceptRequest(view, request)
request.method == "GET" -> get(
webViewUrl,
headers = request.requestHeaders
).toWebResourceResponse()
request.method == "POST" -> post(
webViewUrl,
headers = request.requestHeaders
).toWebResourceResponse()
else -> return super.shouldInterceptRequest(view, request)
}
} catch (e: Exception) {
null
}
}
override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
handler?.proceed() // Ignore ssl issues
}
}
2021-10-10 20:01:02 +00:00
webView?.loadUrl(url, headers.toMap())
}
var loop = 0
// Timeouts after this amount, 60s
val totalTime = 60000L
val delayTime = 100L
// A bit sloppy, but couldn't find a better way
while (loop < totalTime / delayTime) {
if (fixedRequest != null) return fixedRequest
delay(delayTime)
loop += 1
}
println("Web-view timeout after ${totalTime / 1000}s")
destroyWebView()
return null
}
2021-10-18 16:04:05 +00:00
fun Response.toWebResourceResponse(): WebResourceResponse {
val contentTypeValue = this.header("Content-Type")
// 1. contentType. 2. charset
val typeRegex = Regex("""(.*);(?:.*charset=(.*)(?:|;)|)""")
return if (contentTypeValue != null) {
val found = typeRegex.find(contentTypeValue ?: "")
val contentType = found?.groupValues?.getOrNull(1)?.ifBlank { null } ?: contentTypeValue
val charset = found?.groupValues?.getOrNull(2)?.ifBlank { null }
WebResourceResponse(contentType, charset, this.body?.byteStream())
} else {
WebResourceResponse("application/octet-stream", null, this.body?.byteStream())
}
}
}