zoro: fix vidcloud extractor
This commit is contained in:
@ -5,7 +5,7 @@ ext {
|
|||||||
extName = 'zoro.to (experimental)'
|
extName = 'zoro.to (experimental)'
|
||||||
pkgNameSuffix = 'en.zoro'
|
pkgNameSuffix = 'en.zoro'
|
||||||
extClass = '.Zoro'
|
extClass = '.Zoro'
|
||||||
extVersionCode = 2
|
extVersionCode = 3
|
||||||
libVersion = '12'
|
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.animesource.online.ParsedAnimeHttpSource
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
@ -94,9 +93,10 @@ class Zoro : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
val unescapedData = JSONUtil.unescape(data)
|
val unescapedData = JSONUtil.unescape(data)
|
||||||
val serversHtml = Jsoup.parse(unescapedData)
|
val serversHtml = Jsoup.parse(unescapedData)
|
||||||
val videoList = mutableListOf<Video>()
|
val videoList = mutableListOf<Video>()
|
||||||
serversHtml.select("div.server-item").forEach {
|
for (server in serversHtml.select("div.server-item")) {
|
||||||
val id = it.attr("data-id")
|
if (server.text() == "StreamSB" || server.text() == "Streamtape") continue
|
||||||
val subDub = it.attr("data-type")
|
val id = server.attr("data-id")
|
||||||
|
val subDub = server.attr("data-type")
|
||||||
val videos = getVideosFromServer(
|
val videos = getVideosFromServer(
|
||||||
client.newCall(GET("$baseUrl/ajax/v2/episode/sources?id=$id", episodeReferer)).execute(),
|
client.newCall(GET("$baseUrl/ajax/v2/episode/sources?id=$id", episodeReferer)).execute(),
|
||||||
subDub
|
subDub
|
||||||
@ -108,16 +108,12 @@ class Zoro : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
private fun getVideosFromServer(response: Response, subDub: String): List<Video>? {
|
private fun getVideosFromServer(response: Response, subDub: String): List<Video>? {
|
||||||
val body = response.body!!.string()
|
val body = response.body!!.string()
|
||||||
val url = body.substringAfter("\"link\":\"").substringBefore("\"").toHttpUrl()
|
val url = body.substringAfter("\"link\":\"").substringBefore("\"") + "&autoPlay=1&oa=0"
|
||||||
val id = url.pathSegments.last()
|
val getSourcesLink = ZoroExtractor(client).getSourcesLink(url) ?: return null
|
||||||
val getSources = url.toString().substringBefore("embed-6") + "ajax/embed-6/getSources?id=$id&_token="
|
|
||||||
|
|
||||||
val referer = Headers.headersOf("Referer", baseUrl)
|
val source = client.newCall(GET(getSourcesLink)).execute().body!!.string()
|
||||||
val recaptchaClient = client.newBuilder().addInterceptor(GetSourcesInterceptor(getSources, client)).build()
|
if (!source.contains("{\"sources\":[{\"file\":\"")) return null
|
||||||
|
val masterUrl = source.substringAfter("{\"sources\":[{\"file\":\"").substringBefore("\"")
|
||||||
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 masterPlaylist = client.newCall(GET(masterUrl)).execute().body!!.string()
|
val masterPlaylist = client.newCall(GET(masterUrl)).execute().body!!.string()
|
||||||
val videoList = mutableListOf<Video>()
|
val videoList = mutableListOf<Video>()
|
||||||
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:").forEach {
|
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