forked from recloudstream/cloudstream
optimized sflix
This commit is contained in:
parent
6cdf18ae8f
commit
8f9ac96de5
2 changed files with 123 additions and 14 deletions
|
@ -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,
|
||||||
|
@ -463,7 +520,7 @@ data class Actor(
|
||||||
data class ActorData(
|
data class ActorData(
|
||||||
val actor: Actor,
|
val actor: Actor,
|
||||||
val role: ActorRole? = null,
|
val role: ActorRole? = null,
|
||||||
val roleString : String? = null,
|
val roleString: String? = null,
|
||||||
val voiceActor: Actor? = null,
|
val voiceActor: Actor? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -553,7 +610,7 @@ interface LoadResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LoadResponse.setDuration(input: String?) {
|
fun LoadResponse.setDuration(input: String?) {
|
||||||
val cleanInput = input?.trim()?.replace(" ","") ?: return
|
val cleanInput = input?.trim()?.replace(" ", "") ?: return
|
||||||
Regex("([0-9]*)h.*?([0-9]*)m").find(cleanInput)?.groupValues?.let { values ->
|
Regex("([0-9]*)h.*?([0-9]*)m").find(cleanInput)?.groupValues?.let { values ->
|
||||||
if (values.size == 3) {
|
if (values.size == 3) {
|
||||||
val hours = values[1].toIntOrNull()
|
val hours = values[1].toIntOrNull()
|
||||||
|
|
|
@ -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 ->
|
||||||
|
|
Loading…
Reference in a new issue