diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ZoroProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ZoroProvider.kt index b8bc23b4..5e362d62 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ZoroProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ZoroProvider.kt @@ -1,13 +1,15 @@ package com.lagradost.cloudstream3.animeproviders import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.util.NameTransformer import com.fasterxml.jackson.module.kotlin.readValue import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.APIHolder.getCaptchaToken import com.lagradost.cloudstream3.movieproviders.SflixProvider +import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.extractRabbitStream import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.toExtractorLink import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.toSubtitleFile import com.lagradost.cloudstream3.network.WebViewResolver -import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.loadExtractor import org.jsoup.Jsoup @@ -233,7 +235,7 @@ class ZoroProvider : MainAPI() { val actors = document.select("div.block-actors-content > div.bac-list-wrap > div.bac-item") ?.mapNotNull { head -> val subItems = head.select(".per-info") ?: return@mapNotNull null - if(subItems.isEmpty()) return@mapNotNull null + if (subItems.isEmpty()) return@mapNotNull null var role: ActorRole? = null val mainActor = subItems.first()?.let { role = when (it.selectFirst(".pi-detail > .pi-cast")?.text()?.trim()) { @@ -243,7 +245,7 @@ class ZoroProvider : MainAPI() { } it.getActor() } ?: return@mapNotNull null - val voiceActor = if(subItems.size >= 2) subItems[1]?.getActor() else null + val voiceActor = if (subItems.size >= 2) subItems[1]?.getActor() else null ActorData(actor = mainActor, role = role, voiceActor = voiceActor) } @@ -328,42 +330,11 @@ class ZoroProvider : MainAPI() { val extractorLink = app.get( link, ).mapped().link -//.also { println("AAAAAAAAA: ${it.text}") } - // Loads the links in the appropriate extractor. val hasLoadedExtractorLink = loadExtractor(extractorLink, mainUrl, callback) if (!hasLoadedExtractorLink) { - - // Not an extractor because: - // 1. No subtitle callback - // 2. Missing dub/sub status in parameter (might be substituted in the referer) - - val response = - getM3u8FromRapidCloud( - extractorLink - ) - - if (response.contains("(response) - - mapped.tracks?.forEach { track -> - track?.toSubtitleFile()?.let { subtitleFile -> - subtitleCallback.invoke(subtitleFile) - } - } - - val list = listOf( - mapped.sources to "source 1", - mapped.sources1 to "source 2", - mapped.sources2 to "source 3", - mapped.sourcesBackup to "source backup" - ) - - list.forEach { subList -> - subList.first?.forEach { a -> - a?.toExtractorLink(this, subList.second + " - ${it.first}", null) - ?.forEach(callback) - } + extractRabbitStream(extractorLink, subtitleCallback, callback) { sourceName -> + sourceName + " - ${it.first}" } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt index 78580905..06a3e600 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt @@ -327,62 +327,11 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() { app.get("https://sflix.to/ajax/get_link/$serverId").mapped().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() - // Some smarter ws11 or w10 selection might be required in the future. val extractorData = "https://ws11.rabbitstream.net/socket.io/?EIO=4&transport=polling" -// val sources = resolved.first?.let { app.baseClient.newCall(it).execute().text } -// ?: return@suspendSafeApiCall - -// val mapped = parseJson(sources) - - mapped.tracks?.forEach { - it?.toSubtitleFile()?.let { subtitleFile -> - subtitleCallback.invoke(subtitleFile) - } - } - - listOf( - mapped.sources to "", - mapped.sources1 to "source 2", - mapped.sources2 to "source 3", - mapped.sourcesBackup to "source backup" - ).forEach { (sources, sourceName) -> - sources?.forEach { - it?.toExtractorLink(this, sourceName, extractorData)?.forEach(callback) - } - } + extractRabbitStream(iframeLink, subtitleCallback, callback, extractorData) { it } } } @@ -510,7 +459,7 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() { } //.also { println("Sflix post job ${it.text}") } - Log.d(this.name, "Running Sflix job $url") + Log.d(this.name, "Running ${this.name} job $url") val time = measureTimeMillis { // This acts as a timeout @@ -620,6 +569,72 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() { ) } } + + + suspend fun MainAPI.extractRabbitStream( + url: String, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + /** Used for extractorLink name, input: Source name */ + extractorData: String? = null, + nameTransformer: (String) -> String + ) { + // https://rapid-cloud.ru/embed-6/dcPOVRE57YOT?z= -> https://rapid-cloud.ru/embed-6 + val mainIframeUrl = + url.substringBeforeLast("/") + val mainIframeId = url.substringAfterLast("/") + .substringBefore("?") // https://rapid-cloud.ru/embed-6/dcPOVRE57YOT?z= -> dcPOVRE57YOT + val iframe = app.get(url, referer = mainUrl) + val iframeKey = + iframe.document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]") + .attr("src").substringAfter("render=") + val iframeToken = getCaptchaToken(url, 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 = mainUrl, + 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() + + mapped.tracks?.forEach { track -> + track?.toSubtitleFile()?.let { subtitleFile -> + subtitleCallback.invoke(subtitleFile) + } + } + + val list = listOf( + mapped.sources to "source 1", + mapped.sources1 to "source 2", + mapped.sources2 to "source 3", + mapped.sourcesBackup to "source backup" + ) + + list.forEach { subList -> + subList.first?.forEach { source -> + source?.toExtractorLink(this, nameTransformer(subList.second), extractorData) + ?.forEach(callback) + } + } + } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index 4fa6754a..8ff0727e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -22,6 +22,7 @@ import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import com.fasterxml.jackson.annotation.JsonProperty import com.hippo.unifile.UniFile +import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.MainActivity @@ -29,11 +30,13 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.services.VideoDownloadService +import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.removeKey import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.withContext import okhttp3.internal.closeQuietly @@ -677,7 +680,7 @@ object VideoDownloadManager { extension: String, tryResume: Boolean, parentId: Int?, - createNotificationCallback: (CreateNotificationMetadata) -> Unit + createNotificationCallback: (CreateNotificationMetadata) -> Unit, ): Int { if (link.url.startsWith("magnet") || link.url.endsWith(".torrent")) { return ERROR_UNKNOWN @@ -1135,7 +1138,6 @@ object VideoDownloadManager { val displayName = getDisplayName(name, extension) - val fileStream = stream.fileStream!! val firstTs = tsIterator.next() @@ -1346,6 +1348,13 @@ object VideoDownloadManager { val name = sanitizeFilename(ep.name ?: "${context.getString(R.string.episode)} ${ep.episode}") + // Make sure this is cancelled when download is done or cancelled. + val extractorJob = ioSafe { + if (link.extractorData != null) { + getApiFromNameNull(link.source)?.extractorVerifierJob(link.extractorData) + } + } + if (link.isM3u8 || URI(link.url).path.endsWith(".m3u8")) { val startIndex = if (tryResume) { context.getKey( @@ -1369,7 +1378,7 @@ object VideoDownloadManager { meta.hlsTotal ) } - } + }.also { extractorJob.cancel() } } return normalSafeApiCall { @@ -1387,7 +1396,7 @@ object VideoDownloadManager { ) } } - } ?: ERROR_UNKNOWN + }.also { extractorJob.cancel() } ?: ERROR_UNKNOWN } fun downloadCheck(