zoro: fix vidcloud extractor
This commit is contained in:
@ -5,7 +5,7 @@ ext {
|
||||
extName = 'zoro.to (experimental)'
|
||||
pkgNameSuffix = 'en.zoro'
|
||||
extClass = '.Zoro'
|
||||
extVersionCode = 2
|
||||
extVersionCode = 3
|
||||
libVersion = '12'
|
||||
}
|
||||
|
||||
|
@ -1,99 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.en.zoro
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import okhttp3.Headers.Companion.toHeaders
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class GetSourcesInterceptor(private val getSources: String, private val client: OkHttpClient) : Interceptor {
|
||||
private val context = Injekt.get<Application>()
|
||||
private val handler by lazy { Handler(Looper.getMainLooper()) }
|
||||
|
||||
private val initWebView by lazy {
|
||||
WebSettings.getDefaultUserAgent(context)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
initWebView
|
||||
|
||||
val request = chain.request()
|
||||
|
||||
try {
|
||||
val newRequest = resolveWithWebView(request)
|
||||
|
||||
return chain.proceed(newRequest ?: request)
|
||||
} catch (e: Exception) {
|
||||
throw IOException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private fun resolveWithWebView(request: Request): Request? {
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
var webView: WebView? = null
|
||||
|
||||
val origRequestUrl = request.url.toString()
|
||||
val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
|
||||
var newRequest: Request? = null
|
||||
|
||||
handler.post {
|
||||
val webview = WebView(context)
|
||||
webView = webview
|
||||
with(webview.settings) {
|
||||
javaScriptEnabled = true
|
||||
domStorageEnabled = true
|
||||
databaseEnabled = true
|
||||
useWideViewPort = false
|
||||
loadWithOverviewMode = false
|
||||
userAgentString = "Mozilla/5.0 (Linux; Android) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.109 Safari/537.36 CrKey/1.54.248666"
|
||||
}
|
||||
webview.webViewClient = object : WebViewClient() {
|
||||
override fun shouldInterceptRequest(
|
||||
view: WebView,
|
||||
request: WebResourceRequest
|
||||
): WebResourceResponse? {
|
||||
val url = request.url.toString()
|
||||
if (url.contains(getSources)) {
|
||||
val newHeaders = request.requestHeaders.toHeaders()
|
||||
newRequest = GET(url, newHeaders)
|
||||
latch.countDown()
|
||||
}
|
||||
return super.shouldInterceptRequest(view, request)
|
||||
}
|
||||
}
|
||||
|
||||
webView?.loadUrl(origRequestUrl, headers)
|
||||
}
|
||||
|
||||
latch.await(TIMEOUT_SEC, TimeUnit.SECONDS)
|
||||
|
||||
handler.post {
|
||||
webView?.stopLoading()
|
||||
webView?.destroy()
|
||||
webView = null
|
||||
}
|
||||
return newRequest
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TIMEOUT_SEC: Long = 10
|
||||
}
|
||||
}
|
@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@ -94,9 +93,10 @@ class Zoro : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
val unescapedData = JSONUtil.unescape(data)
|
||||
val serversHtml = Jsoup.parse(unescapedData)
|
||||
val videoList = mutableListOf<Video>()
|
||||
serversHtml.select("div.server-item").forEach {
|
||||
val id = it.attr("data-id")
|
||||
val subDub = it.attr("data-type")
|
||||
for (server in serversHtml.select("div.server-item")) {
|
||||
if (server.text() == "StreamSB" || server.text() == "Streamtape") continue
|
||||
val id = server.attr("data-id")
|
||||
val subDub = server.attr("data-type")
|
||||
val videos = getVideosFromServer(
|
||||
client.newCall(GET("$baseUrl/ajax/v2/episode/sources?id=$id", episodeReferer)).execute(),
|
||||
subDub
|
||||
@ -108,16 +108,12 @@ class Zoro : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
private fun getVideosFromServer(response: Response, subDub: String): List<Video>? {
|
||||
val body = response.body!!.string()
|
||||
val url = body.substringAfter("\"link\":\"").substringBefore("\"").toHttpUrl()
|
||||
val id = url.pathSegments.last()
|
||||
val getSources = url.toString().substringBefore("embed-6") + "ajax/embed-6/getSources?id=$id&_token="
|
||||
val url = body.substringAfter("\"link\":\"").substringBefore("\"") + "&autoPlay=1&oa=0"
|
||||
val getSourcesLink = ZoroExtractor(client).getSourcesLink(url) ?: return null
|
||||
|
||||
val referer = Headers.headersOf("Referer", baseUrl)
|
||||
val recaptchaClient = client.newBuilder().addInterceptor(GetSourcesInterceptor(getSources, client)).build()
|
||||
|
||||
val lol = recaptchaClient.newCall(GET("$url&autoPlay=1", referer)).execute().body!!.string()
|
||||
if (!lol.contains("{\"sources\":[{\"file\":\"")) return null
|
||||
val masterUrl = lol.substringAfter("{\"sources\":[{\"file\":\"").substringBefore("\"")
|
||||
val source = client.newCall(GET(getSourcesLink)).execute().body!!.string()
|
||||
if (!source.contains("{\"sources\":[{\"file\":\"")) return null
|
||||
val masterUrl = source.substringAfter("{\"sources\":[{\"file\":\"").substringBefore("\"")
|
||||
val masterPlaylist = client.newCall(GET(masterUrl)).execute().body!!.string()
|
||||
val videoList = mutableListOf<Video>()
|
||||
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:").forEach {
|
||||
|
@ -0,0 +1,79 @@
|
||||
package eu.kanade.tachiyomi.animeextension.en.zoro
|
||||
|
||||
import android.net.Uri
|
||||
import android.util.Base64
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Response
|
||||
import okhttp3.WebSocket
|
||||
import okhttp3.WebSocketListener
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class ZoroExtractor(private val client: OkHttpClient) {
|
||||
fun getSourcesLink(url: String): String? {
|
||||
val html = client.newCall(GET(url, Headers.headersOf("referer", "https://zoro.to/"))).execute().body!!.string()
|
||||
val key = html.substringAfter("var recaptchaSiteKey = '", "")
|
||||
.substringBefore("',", "").ifEmpty { return null }
|
||||
val number = html.substringAfter("recaptchaNumber = '", "")
|
||||
.substringBefore("';", "").ifEmpty { return null }
|
||||
val captcha = captcha(url, key) ?: return null
|
||||
val id = url.substringAfter("/embed-6/", "")
|
||||
.substringBefore("?z=", "").ifEmpty { return null }
|
||||
val sId = sId() ?: return null
|
||||
return "https://rapid-cloud.ru/ajax/embed-6/getSources?id=$id&_token=$captcha&_number=$number&sId=$sId"
|
||||
}
|
||||
|
||||
private fun sId(): String? {
|
||||
val latch = CountDownLatch(1)
|
||||
var sId: String? = null
|
||||
val listener = object : WebSocketListener() {
|
||||
override fun onOpen(webSocket: WebSocket, response: Response) {
|
||||
webSocket.send("40")
|
||||
}
|
||||
|
||||
override fun onMessage(webSocket: WebSocket, text: String) {
|
||||
if (text.startsWith("40")) {
|
||||
sId = text
|
||||
webSocket.close(1000, null)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
client.newWebSocket(GET("wss://ws1.rapid-cloud.ru/socket.io/?EIO=4&transport=websocket"), listener)
|
||||
latch.await(30, TimeUnit.SECONDS)
|
||||
return sId?.substringAfter("40{\"sid\":\"", "")
|
||||
?.substringBefore("\"", "")
|
||||
}
|
||||
|
||||
private fun captcha(url: String, key: String): String? {
|
||||
val uri = Uri.parse(url)
|
||||
val domain = (Base64.encodeToString((uri.scheme + "://" + uri.host + ":443").encodeToByteArray(), Base64.NO_PADDING) + ".")
|
||||
.replace("\n", "")
|
||||
val headers = Headers.headersOf("referer", uri.scheme + "://" + uri.host)
|
||||
val vToken = client.newCall(GET("https://www.google.com/recaptcha/api.js?render=$key", headers)).execute().body!!.string()
|
||||
.replace("\n", "").substringAfter("/releases/", "")
|
||||
.substringBefore("/recaptcha", "").ifEmpty { return null }
|
||||
if (vToken.isEmpty()) return null
|
||||
val anchorUrl = "https://www.google.com/recaptcha/api2/anchor?ar=1&hl=en&size=invisible&cb=kr60249sk&k=$key&co=$domain&v=$vToken"
|
||||
val recapToken = client.newCall(GET(anchorUrl)).execute().asJsoup()
|
||||
.selectFirst("#recaptcha-token")?.attr("value") ?: return null
|
||||
val body = FormBody.Builder()
|
||||
.add("v", vToken)
|
||||
.add("k", key)
|
||||
.add("c", recapToken)
|
||||
.add("co", domain)
|
||||
.add("sa", "")
|
||||
.add("reason", "q")
|
||||
.build()
|
||||
return client.newCall(POST("https://www.google.com/recaptcha/api2/reload?k=$key", body = body)).execute().body!!.string()
|
||||
.replace("\n", "")
|
||||
.substringAfter("rresp\",\"", "")
|
||||
.substringBefore("\",null", "")
|
||||
.ifEmpty { null }
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user