2021-10-04 17:54:15 +00:00
|
|
|
package com.lagradost.cloudstream3.network
|
|
|
|
|
|
|
|
import android.annotation.SuppressLint
|
2021-10-07 17:40:31 +00:00
|
|
|
import android.net.http.SslError
|
|
|
|
import android.webkit.*
|
2021-10-04 17:54:15 +00:00
|
|
|
import com.lagradost.cloudstream3.AcraApplication
|
2021-10-28 11:28:19 +00:00
|
|
|
import com.lagradost.cloudstream3.USER_AGENT
|
2021-12-05 16:22:30 +00:00
|
|
|
import com.lagradost.cloudstream3.app
|
2021-10-04 17:54:15 +00:00
|
|
|
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
|
2021-10-04 17:54:15 +00:00
|
|
|
import java.util.concurrent.TimeUnit
|
2022-01-16 15:35:06 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* When used as Interceptor additionalUrls cannot be returned, use WebViewResolver(...).resolveUsingWebView(...)
|
|
|
|
* @param interceptUrl will stop the WebView when reaching this url.
|
|
|
|
* @param additionalUrls this will make resolveUsingWebView also return all other requests matching the list of Regex.
|
|
|
|
* */
|
|
|
|
class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List<Regex> = emptyList()) :
|
|
|
|
Interceptor {
|
2021-10-04 17:54:15 +00:00
|
|
|
|
|
|
|
override fun intercept(chain: Interceptor.Chain): Response {
|
|
|
|
val request = chain.request()
|
|
|
|
return runBlocking {
|
2022-01-16 15:35:06 +00:00
|
|
|
val fixedRequest = resolveUsingWebView(request).first
|
2021-10-04 17:54:15 +00:00
|
|
|
return@runBlocking chain.proceed(fixedRequest ?: request)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-16 15:35:06 +00:00
|
|
|
/**
|
|
|
|
* @param requestCallBack asynchronously return matched requests by either interceptUrl or additionalUrls.
|
|
|
|
* @return the final request (by interceptUrl) and all the collected urls (by additionalUrls).
|
|
|
|
* */
|
2021-10-04 17:54:15 +00:00
|
|
|
@SuppressLint("SetJavaScriptEnabled")
|
2022-01-16 15:35:06 +00:00
|
|
|
suspend fun resolveUsingWebView(
|
|
|
|
request: Request,
|
|
|
|
requestCallBack: (Request) -> Unit = {}
|
|
|
|
): Pair<Request?, List<Request>> {
|
2021-10-04 17:54:15 +00:00
|
|
|
val url = request.url.toString()
|
2021-10-10 20:01:02 +00:00
|
|
|
val headers = request.headers
|
2021-10-07 17:40:31 +00:00
|
|
|
println("Initial web-view request: $url")
|
2021-10-04 17:54:15 +00:00
|
|
|
var webView: WebView? = null
|
|
|
|
|
|
|
|
fun destroyWebView() {
|
|
|
|
main {
|
|
|
|
webView?.stopLoading()
|
|
|
|
webView?.destroy()
|
|
|
|
webView = null
|
|
|
|
println("Destroyed webview")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var fixedRequest: Request? = null
|
2022-01-16 15:35:06 +00:00
|
|
|
val extraRequestList = mutableListOf<Request>()
|
2021-10-04 17:54:15 +00:00
|
|
|
|
|
|
|
main {
|
2021-10-09 12:19:26 +00:00
|
|
|
// Useful for debugging
|
|
|
|
// WebView.setWebContentsDebuggingEnabled(true)
|
2021-10-04 17:54:15 +00:00
|
|
|
webView = WebView(
|
2022-01-16 15:35:06 +00:00
|
|
|
AcraApplication.context
|
|
|
|
?: throw RuntimeException("No base context in WebViewResolver")
|
2021-10-04 17:54:15 +00:00
|
|
|
).apply {
|
2021-10-09 12:19:26 +00:00
|
|
|
// Bare minimum to bypass captcha
|
2021-10-04 17:54:15 +00:00
|
|
|
settings.javaScriptEnabled = true
|
2021-10-09 12:19:26 +00:00
|
|
|
settings.domStorageEnabled = true
|
2021-10-28 11:28:19 +00:00
|
|
|
settings.userAgentString = USER_AGENT
|
2022-01-16 15:35:06 +00:00
|
|
|
// Blocks unnecessary images, remove if captcha fucks.
|
|
|
|
settings.blockNetworkImage = true
|
2021-10-04 17:54:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
webView?.webViewClient = object : WebViewClient() {
|
|
|
|
override fun shouldInterceptRequest(
|
|
|
|
view: WebView,
|
|
|
|
request: WebResourceRequest
|
2022-01-29 21:25:12 +00:00
|
|
|
): WebResourceResponse? = runBlocking {
|
2021-10-04 17:54:15 +00:00
|
|
|
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
|
|
|
|
2021-10-04 17:54:15 +00:00
|
|
|
if (interceptUrl.containsMatchIn(webViewUrl)) {
|
2022-01-16 15:35:06 +00:00
|
|
|
fixedRequest = request.toRequest().also(requestCallBack)
|
2021-10-07 17:40:31 +00:00
|
|
|
println("Web-view request finished: $webViewUrl")
|
2021-10-04 17:54:15 +00:00
|
|
|
destroyWebView()
|
2022-01-29 21:25:12 +00:00
|
|
|
return@runBlocking null
|
2021-10-04 17:54:15 +00:00
|
|
|
}
|
2021-10-18 16:04:05 +00:00
|
|
|
|
2022-01-16 15:35:06 +00:00
|
|
|
if (additionalUrls.any { it.containsMatchIn(webViewUrl) }) {
|
|
|
|
extraRequestList.add(request.toRequest().also(requestCallBack))
|
|
|
|
}
|
|
|
|
|
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.
|
2022-01-16 15:35:06 +00:00
|
|
|
// blockNetworkImage also does this job but i will keep it for the future.
|
2022-01-16 22:31:42 +00:00
|
|
|
val blacklistedFiles = listOf(
|
|
|
|
".jpg",
|
|
|
|
".png",
|
|
|
|
".webp",
|
|
|
|
".mpg",
|
|
|
|
".mpeg",
|
|
|
|
".jpeg",
|
|
|
|
".webm",
|
|
|
|
".mp4",
|
|
|
|
".mp3",
|
|
|
|
".gifv",
|
|
|
|
".flv",
|
|
|
|
".asf",
|
|
|
|
".mov",
|
|
|
|
".mng",
|
|
|
|
".mkv",
|
|
|
|
".ogg",
|
|
|
|
".avi",
|
|
|
|
".wav",
|
|
|
|
".woff2",
|
|
|
|
".woff",
|
|
|
|
".ttf",
|
|
|
|
".css",
|
|
|
|
".vtt",
|
|
|
|
".srt",
|
|
|
|
".ts",
|
|
|
|
".gif",
|
|
|
|
)
|
2021-10-28 11:28:19 +00:00
|
|
|
|
|
|
|
/** 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.
|
|
|
|
* **/
|
2022-01-29 21:25:12 +00:00
|
|
|
|
|
|
|
/** 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.
|
|
|
|
* **/
|
|
|
|
return@runBlocking try {
|
2021-10-18 16:04:05 +00:00
|
|
|
when {
|
2022-01-16 22:31:42 +00:00
|
|
|
blacklistedFiles.any { URI(webViewUrl).path.contains(it) } || webViewUrl.endsWith(
|
2021-10-28 11:28:19 +00:00
|
|
|
"/favicon.ico"
|
|
|
|
) -> WebResourceResponse(
|
|
|
|
"image/png",
|
|
|
|
null,
|
|
|
|
null
|
|
|
|
)
|
|
|
|
|
2022-01-16 15:35:06 +00:00
|
|
|
webViewUrl.contains("recaptcha") -> super.shouldInterceptRequest(
|
|
|
|
view,
|
|
|
|
request
|
|
|
|
)
|
2021-10-18 16:04:05 +00:00
|
|
|
|
2021-12-05 16:22:30 +00:00
|
|
|
request.method == "GET" -> app.get(
|
2021-10-18 16:04:05 +00:00
|
|
|
webViewUrl,
|
|
|
|
headers = request.requestHeaders
|
2021-12-05 16:22:30 +00:00
|
|
|
).response.toWebResourceResponse()
|
2021-10-18 16:04:05 +00:00
|
|
|
|
2021-12-05 16:22:30 +00:00
|
|
|
request.method == "POST" -> app.post(
|
2021-10-18 16:04:05 +00:00
|
|
|
webViewUrl,
|
|
|
|
headers = request.requestHeaders
|
2021-12-05 16:22:30 +00:00
|
|
|
).response.toWebResourceResponse()
|
2022-01-29 21:25:12 +00:00
|
|
|
else -> return@runBlocking super.shouldInterceptRequest(view, request)
|
2021-10-18 16:04:05 +00:00
|
|
|
}
|
|
|
|
} catch (e: Exception) {
|
|
|
|
null
|
|
|
|
}
|
2021-10-04 17:54:15 +00:00
|
|
|
}
|
2021-10-07 17:40:31 +00:00
|
|
|
|
2022-01-16 15:35:06 +00:00
|
|
|
override fun onReceivedSslError(
|
|
|
|
view: WebView?,
|
|
|
|
handler: SslErrorHandler?,
|
|
|
|
error: SslError?
|
|
|
|
) {
|
2021-10-07 17:40:31 +00:00
|
|
|
handler?.proceed() // Ignore ssl issues
|
|
|
|
}
|
2021-10-04 17:54:15 +00:00
|
|
|
}
|
2021-10-10 20:01:02 +00:00
|
|
|
webView?.loadUrl(url, headers.toMap())
|
2021-10-04 17:54:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var loop = 0
|
2021-10-09 12:19:26 +00:00
|
|
|
// Timeouts after this amount, 60s
|
|
|
|
val totalTime = 60000L
|
|
|
|
|
2021-10-04 17:54:15 +00:00
|
|
|
val delayTime = 100L
|
|
|
|
|
|
|
|
// A bit sloppy, but couldn't find a better way
|
|
|
|
while (loop < totalTime / delayTime) {
|
2022-01-16 15:35:06 +00:00
|
|
|
if (fixedRequest != null) return fixedRequest to extraRequestList
|
2021-10-04 17:54:15 +00:00
|
|
|
delay(delayTime)
|
|
|
|
loop += 1
|
|
|
|
}
|
|
|
|
|
2021-10-07 17:40:31 +00:00
|
|
|
println("Web-view timeout after ${totalTime / 1000}s")
|
2021-10-04 17:54:15 +00:00
|
|
|
destroyWebView()
|
2022-01-16 15:35:06 +00:00
|
|
|
return null to extraRequestList
|
|
|
|
}
|
|
|
|
|
|
|
|
fun WebResourceRequest.toRequest(): Request {
|
|
|
|
val webViewUrl = this.url.toString()
|
|
|
|
|
|
|
|
return when (this.method) {
|
|
|
|
"POST" -> postRequestCreator(
|
|
|
|
webViewUrl,
|
|
|
|
this.requestHeaders,
|
|
|
|
null,
|
|
|
|
emptyMap(),
|
|
|
|
emptyMap(),
|
|
|
|
emptyMap(),
|
|
|
|
10,
|
|
|
|
TimeUnit.MINUTES
|
|
|
|
)
|
|
|
|
// "GET",
|
|
|
|
else -> getRequestCreator(
|
|
|
|
webViewUrl,
|
|
|
|
this.requestHeaders,
|
|
|
|
null,
|
|
|
|
emptyMap(),
|
|
|
|
emptyMap(),
|
|
|
|
10,
|
|
|
|
TimeUnit.MINUTES
|
|
|
|
)
|
|
|
|
}
|
2021-10-04 17:54:15 +00:00
|
|
|
}
|
|
|
|
|
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) {
|
2021-12-05 16:22:30 +00:00
|
|
|
val found = typeRegex.find(contentTypeValue)
|
2021-10-18 16:04:05 +00:00
|
|
|
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())
|
|
|
|
}
|
|
|
|
}
|
2021-10-04 17:54:15 +00:00
|
|
|
}
|