optimized sflix

This commit is contained in:
Blatzar 2022-02-19 16:15:48 +01:00
parent 6cdf18ae8f
commit 8f9ac96de5
2 changed files with 123 additions and 14 deletions

View file

@ -2,6 +2,8 @@ package com.lagradost.cloudstream3
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.net.Uri
import android.util.Base64.encodeToString
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.DeserializationFeature
@ -121,6 +123,58 @@ object APIHolder {
return url.replace(getApiFromName(apiName).mainUrl, "").replace("/", "").hashCode() return url.replace(getApiFromName(apiName).mainUrl, "").replace("/", "").hashCode()
} }
/**
* Gets the website captcha token
* discovered originally by https://github.com/ahmedgamal17
* optimized by https://github.com/justfoolingaround
*
* @param url the main url, likely the same website you found the key from.
* @param key used to fill https://www.google.com/recaptcha/api.js?render=....
*
* @param referer the referer for the google.com/recaptcha/api.js... request, optional.
* */
// Try document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]").attr("src").substringAfter("render=")
// To get the key
suspend fun getCaptchaToken(url: String, key: String, referer: String? = null): String? {
val uri = Uri.parse(url)
val domain = encodeToString(
(uri.scheme + "://" + uri.host + ":443").encodeToByteArray(),
0
).replace("\n", "").replace("=",".")
val vToken =
app.get(
"https://www.google.com/recaptcha/api.js?render=$key",
referer = referer,
cacheTime = 0
)
.text
.substringAfter("releases/")
.substringBefore("/")
val recapToken =
app.get("https://www.google.com/recaptcha/api2/anchor?ar=1&hl=en&size=invisible&cb=cs3&k=$key&co=$domain&v=$vToken")
.document
.selectFirst("#recaptcha-token")?.attr("value")
if (recapToken != null) {
return app.post(
"https://www.google.com/recaptcha/api2/reload?k=$key",
data = mapOf(
"v" to vToken,
"k" to key,
"c" to recapToken,
"co" to domain,
"sa" to "",
"reason" to "q"
), cacheTime = 0
).text
.substringAfter("rresp\",\"")
.substringBefore("\"")
}
return null
}
fun Context.getApiSettings(): HashSet<String> { fun Context.getApiSettings(): HashSet<String> {
//val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) //val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
@ -250,14 +304,17 @@ abstract class MainAPI {
suspend open fun getMainPage(): HomePageResponse? { suspend open fun getMainPage(): HomePageResponse? {
throw NotImplementedError() throw NotImplementedError()
} }
@WorkerThread @WorkerThread
suspend open fun search(query: String): List<SearchResponse>? { suspend open fun search(query: String): List<SearchResponse>? {
throw NotImplementedError() throw NotImplementedError()
} }
@WorkerThread @WorkerThread
suspend open fun quickSearch(query: String): List<SearchResponse>? { suspend open fun quickSearch(query: String): List<SearchResponse>? {
throw NotImplementedError() throw NotImplementedError()
} }
@WorkerThread @WorkerThread
/** /**
* Based on data from search() or getMainPage() it generates a LoadResponse, * Based on data from search() or getMainPage() it generates a LoadResponse,

View file

@ -3,14 +3,12 @@ package com.lagradost.cloudstream3.movieproviders
import android.util.Log import android.util.Log
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getCaptchaToken
import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.APIHolder.unixTimeMS
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.network.AppResponse import com.lagradost.cloudstream3.network.AppResponse
import com.lagradost.cloudstream3.network.WebViewResolver
import com.lagradost.cloudstream3.network.getRequestCreator
import com.lagradost.cloudstream3.network.text
import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
@ -277,6 +275,14 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() {
@JsonProperty("tracks") val tracks: List<Tracks?>? @JsonProperty("tracks") val tracks: List<Tracks?>?
) )
data class IframeJson(
// @JsonProperty("type") val type: String? = null,
@JsonProperty("link") val link: String? = null,
// @JsonProperty("sources") val sources: ArrayList<String> = arrayListOf(),
// @JsonProperty("tracks") val tracks: ArrayList<String> = arrayListOf(),
// @JsonProperty("title") val title: String? = null
)
override suspend fun loadLinks( override suspend fun loadLinks(
data: String, data: String,
isCasting: Boolean, isCasting: Boolean,
@ -299,21 +305,67 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() {
urls?.apmap { url -> urls?.apmap { url ->
suspendSafeApiCall { suspendSafeApiCall {
val resolved = WebViewResolver( // val resolved = WebViewResolver(
Regex("""/getSources"""), // Regex("""/getSources"""),
// This is unreliable, generating my own link instead // // This is unreliable, generating my own link instead
// additionalUrls = listOf(Regex("""^.*transport=polling(?!.*sid=).*$""")) //// additionalUrls = listOf(Regex("""^.*transport=polling(?!.*sid=).*$"""))
).resolveUsingWebView(getRequestCreator(url)) // ).resolveUsingWebView(getRequestCreator(url))
// val extractorData = resolved.second.getOrNull(0)?.url?.toString() //// val extractorData = resolved.second.getOrNull(0)?.url?.toString()
// ------- Main site -------
// Possible without token
// val response = app.get(url)
// val key =
// response.document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]")
// .attr("src").substringAfter("render=")
// val token = getCaptchaToken(mainUrl, key) ?: return@suspendSafeApiCall
val serverId = url.substringAfterLast(".")
val iframeLink =
app.get("https://sflix.to/ajax/get_link/$serverId").mapped<IframeJson>().link
?: return@suspendSafeApiCall
// ------- Iframe -------
val mainIframeUrl =
iframeLink.substringBeforeLast("/") // "https://rabbitstream.net/embed-4/6sBcv1i8vUF6?z=" -> "https://rabbitstream.net/embed-4"
val mainIframeId = iframeLink.substringAfterLast("/")
.substringBefore("?") // "https://rabbitstream.net/embed-4/6sBcv1i8vUF6?z=" -> "6sBcv1i8vUF6"
val iframe = app.get(iframeLink, referer = mainUrl)
val iframeKey =
iframe.document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]")
.attr("src").substringAfter("render=")
val iframeToken = getCaptchaToken(iframeLink, iframeKey)
val number = Regex("""recaptchaNumber = '(.*?)'""").find(iframe.text)?.groupValues?.get(1)
val mapped = app.get(
"${mainIframeUrl.replace("/embed", "/ajax/embed")}/getSources?id=$mainIframeId&_token=$iframeToken&_number=$number",
referer = "https://rabbitstream.net/",
headers = mapOf(
"X-Requested-With" to "XMLHttpRequest",
"Accept" to "*/*",
"Accept-Language" to "en-US,en;q=0.5",
// "Cache-Control" to "no-cache",
"Connection" to "keep-alive",
// "Sec-Fetch-Dest" to "empty",
// "Sec-Fetch-Mode" to "no-cors",
// "Sec-Fetch-Site" to "cross-site",
// "Pragma" to "no-cache",
// "Cache-Control" to "no-cache",
"TE" to "trailers"
)
).mapped<SourceObject>()
// Some smarter ws11 or w10 selection might be required in the future. // Some smarter ws11 or w10 selection might be required in the future.
val extractorData = val extractorData =
"https://ws11.rabbitstream.net/socket.io/?EIO=4&transport=polling" "https://ws11.rabbitstream.net/socket.io/?EIO=4&transport=polling"
val sources = resolved.first?.let { app.baseClient.newCall(it).execute().text } // val sources = resolved.first?.let { app.baseClient.newCall(it).execute().text }
?: return@suspendSafeApiCall // ?: return@suspendSafeApiCall
val mapped = parseJson<SourceObject>(sources) // val mapped = parseJson<SourceObject>(sources)
mapped.tracks?.forEach { mapped.tracks?.forEach {
it?.toSubtitleFile()?.let { subtitleFile -> it?.toSubtitleFile()?.let { subtitleFile ->