diff --git a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt index faa08045..5fae55ac 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt @@ -5,6 +5,7 @@ import android.content.Context import android.widget.Toast import com.google.auto.service.AutoService import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.utils.AppUtils.openBrowser import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread import com.lagradost.cloudstream3.utils.DataStore.getKey @@ -12,6 +13,7 @@ import com.lagradost.cloudstream3.utils.DataStore.getKeys import com.lagradost.cloudstream3.utils.DataStore.removeKey import com.lagradost.cloudstream3.utils.DataStore.removeKeys import com.lagradost.cloudstream3.utils.DataStore.setKey +import kotlinx.coroutines.runBlocking import org.acra.ReportField import org.acra.config.CoreConfiguration import org.acra.data.CrashReportData @@ -33,9 +35,11 @@ class CustomReportSender : ReportSender { ) thread { // to not run it on main thread - normalSafeApiCall { - val post = app.post(url, data = data) - println("Report response: $post") + runBlocking { + suspendSafeApiCall { + val post = app.post(url, data = data) + println("Report response: $post") + } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt b/app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt index f3be00f6..c3a1bced 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt @@ -2,13 +2,9 @@ package com.lagradost.cloudstream3 import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking -import java.util.* -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit -import kotlin.collections.ArrayList //https://stackoverflow.com/questions/34697828/parallel-operations-on-kotlin-collections +/* fun Iterable.pmap( numThreads: Int = maxOf(Runtime.getRuntime().availableProcessors() - 2, 1), exec: ExecutorService = Executors.newFixedThreadPool(numThreads), @@ -27,14 +23,14 @@ fun Iterable.pmap( exec.awaitTermination(1, TimeUnit.DAYS) return ArrayList(destination) -} +}*/ fun List.apmap(f: suspend (A) -> B): List = runBlocking { map { async { f(it) } }.map { it.await() } } // run code in parallel -fun argpmap( +/*fun argpmap( vararg transforms: () -> R, numThreads: Int = maxOf(Runtime.getRuntime().availableProcessors() - 2, 1), exec: ExecutorService = Executors.newFixedThreadPool(numThreads) @@ -45,10 +41,10 @@ fun argpmap( exec.shutdown() exec.awaitTermination(1, TimeUnit.DAYS) -} +}*/ -//fun argamap( -// vararg transforms: () -> R, -//) = runBlocking { -// transforms.map { async { it.invoke() } }.map { it.await() } -//} \ No newline at end of file +fun argamap( + vararg transforms: suspend () -> R, +) = runBlocking { + transforms.map { async { it.invoke() } }.map { it.await() } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimePaheProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimePaheProvider.kt index 5ff8f56c..32fa9c59 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimePaheProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimePaheProvider.kt @@ -3,7 +3,7 @@ package com.lagradost.cloudstream3.animeproviders import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.module.kotlin.readValue import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.network.AppResponse import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getQualityFromName @@ -23,7 +23,7 @@ class AnimePaheProvider : MainAPI() { else TvType.Anime } - fun generateSession(): Boolean { + suspend fun generateSession(): Boolean { if (cookies.isNotEmpty()) return true return try { val response = app.get("$MAIN_URL/") @@ -124,7 +124,7 @@ class AnimePaheProvider : MainAPI() { @JsonProperty("data") val data: List ) - private fun getAnimeByIdAndTitle(title: String, animeId: Int): String? { + private suspend fun getAnimeByIdAndTitle(title: String, animeId: Int): String? { val url = "$mainUrl/api?m=search&l=8&q=$title" val headers = mapOf("referer" to "$mainUrl/") @@ -186,7 +186,7 @@ class AnimePaheProvider : MainAPI() { ) - private fun generateListOfEpisodes(link: String): ArrayList { + private suspend fun generateListOfEpisodes(link: String): ArrayList { try { val attrs = link.split('/') val id = attrs[attrs.size - 1].split("?")[0] @@ -243,8 +243,7 @@ class AnimePaheProvider : MainAPI() { } override suspend fun load(url: String): LoadResponse? { - return normalSafeApiCall { - + return suspendSafeApiCall { val regex = Regex("""a/(\d+)\?slug=(.+)""") val (animeId, animeTitle) = regex.find(url)!!.destructured val link = getAnimeByIdAndTitle(animeTitle, animeId.toInt())!! @@ -436,7 +435,7 @@ class AnimePaheProvider : MainAPI() { @JsonProperty("data") val data: List> ) - private fun bypassAdfly(adflyUri: String): String { + private suspend fun bypassAdfly(adflyUri: String): String { if (!generateSession()) { return bypassAdfly(adflyUri) } @@ -461,7 +460,7 @@ class AnimePaheProvider : MainAPI() { return decodeAdfly(YTSM.find(adflyContent?.text.toString())!!.destructured.component1()) } - private fun getStreamUrlFromKwik(adflyUri: String): String { + private suspend fun getStreamUrlFromKwik(adflyUri: String): String { val fContent = app.get( bypassAdfly(adflyUri), @@ -496,7 +495,7 @@ class AnimePaheProvider : MainAPI() { return content?.headers?.values("location").toString() } - private fun extractVideoLinks(episodeLink: String): List { + private suspend fun extractVideoLinks(episodeLink: String): List { var link = episodeLink val headers = mapOf("referer" to "$mainUrl/") @@ -507,7 +506,6 @@ class AnimePaheProvider : MainAPI() { val episodeNum = regex.find(link)?.destructured?.component1()?.toIntOrNull() link = link.replace(regex, "") - val req = app.get(link, headers = headers).text val jsonResponse = req.let { mapper.readValue(it) } val ep = ((jsonResponse.data.map { diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/DubbedAnimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/DubbedAnimeProvider.kt index 1662be5b..893d83b6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/DubbedAnimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/DubbedAnimeProvider.kt @@ -54,7 +54,7 @@ class DubbedAnimeProvider : MainAPI() { @JsonProperty("tags") val tags: String,*/ ) - private fun parseDocumentTrending(url: String): List { + private suspend fun parseDocumentTrending(url: String): List { val response = app.get(url).text val document = Jsoup.parse(response) return document.select("li > a").map { @@ -73,7 +73,7 @@ class DubbedAnimeProvider : MainAPI() { } } - private fun parseDocument(url: String, trimEpisode: Boolean = false): List { + private suspend fun parseDocument(url: String, trimEpisode: Boolean = false): List { val response = app.get(url).text val document = Jsoup.parse(response) return document.select("a.grid__link").map { @@ -109,7 +109,7 @@ class DubbedAnimeProvider : MainAPI() { } - private fun getAnimeEpisode(slug: String, isMovie: Boolean): EpisodeInfo { + private suspend fun getAnimeEpisode(slug: String, isMovie: Boolean): EpisodeInfo { val url = mainUrl + (if (isMovie) "/movies/jsonMovie" else "/xz/v3/jsonEpi") + ".php?slug=$slug&_=$unixTime" val response = app.get(url).text diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GogoanimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GogoanimeProvider.kt index 37582883..668f445f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GogoanimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GogoanimeProvider.kt @@ -232,17 +232,17 @@ class GogoanimeProvider : MainAPI() { val default: String? = null ) - private fun extractVideos(uri: String, callback: (ExtractorLink) -> Unit) { + private suspend fun extractVideos(uri: String, callback: (ExtractorLink) -> Unit) { val doc = app.get(uri).document val iframe = fixUrlNull(doc.selectFirst("div.play-video > iframe").attr("src")) ?: return - argpmap( + argamap( { val link = iframe.replace("streaming.php", "download") val page = app.get(link, headers = mapOf("Referer" to iframe)) - page.document.select(".dowload > a").pmap { + page.document.select(".dowload > a").apmap { if (it.hasAttr("download")) { val qual = if (it.text() .contains("HDP") @@ -266,7 +266,7 @@ class GogoanimeProvider : MainAPI() { }, { val streamingResponse = app.get(iframe, headers = mapOf("Referer" to iframe)) val streamingDocument = streamingResponse.document - argpmap({ + argamap({ streamingDocument.select(".list-server-items > .linkserver") ?.forEach { element -> val status = element.attr("data-status") ?: return@forEach @@ -302,7 +302,7 @@ class GogoanimeProvider : MainAPI() { sourceCallback.invoke( ExtractorLink( this.name, - "${this.name} ${source.label?.replace("0 P","0p") ?: ""}", + "${this.name} ${source.label?.replace("0 P", "0p") ?: ""}", source.file, "", getQualityFromName(source.label ?: ""), @@ -312,11 +312,9 @@ class GogoanimeProvider : MainAPI() { } sources.source?.forEach { - println("${this.name} ${it.label ?: ""}") invokeGogoSource(it, callback) } sources.sourceBk?.forEach { - println("${this.name} ${it.label ?: ""}") invokeGogoSource(it, callback) } }) 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 2a1cc405..0bb0e4f4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ZoroProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ZoroProvider.kt @@ -257,7 +257,7 @@ class ZoroProvider : MainAPI() { } } - private fun getM3u8FromRapidCloud(url: String): String { + private suspend fun getM3u8FromRapidCloud(url: String): String { return Regex("""/(embed-\d+)/(.*?)\?z=""").find(url)?.groupValues?.let { val jsonLink = "https://rapid-cloud.ru/ajax/${it[1]}/getSources?id=${it[2]}" app.get(jsonLink).text @@ -295,7 +295,7 @@ class ZoroProvider : MainAPI() { } // Prevent duplicates - servers.distinctBy { it.second }.pmap { + servers.distinctBy { it.second }.apmap { val link = "$mainUrl/ajax/v2/episode/sources?id=${it.second}" val extractorLink = app.get( @@ -316,7 +316,7 @@ class ZoroProvider : MainAPI() { extractorLink ) - if (response.contains("(response) mapped.tracks?.forEach { track -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt index 1a4b6e65..8b748b9d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt @@ -10,7 +10,7 @@ class AsianLoad : ExtractorApi() { override val requiresReferer = true private val sourceRegex = Regex("""sources:[\W\w]*?file:\s*?["'](.*?)["']""") - override fun getUrl(url: String, referer: String?): List { + override suspend fun getUrl(url: String, referer: String?): List { val extractedLinksList: MutableList = mutableListOf() with(app.get(url, referer = referer)) { sourceRegex.findAll(this.text).forEach { sourceMatch -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt index 4317a5a1..2d89631d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt @@ -4,7 +4,7 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities -import java.lang.Thread.sleep +import kotlinx.coroutines.delay class DoodToExtractor : DoodLaExtractor() { override val mainUrl = "https://dood.to" @@ -28,13 +28,13 @@ open class DoodLaExtractor : ExtractorApi() { return "$mainUrl/d/$id" } - override fun getUrl(url: String, referer: String?): List? { + override suspend fun getUrl(url: String, referer: String?): List? { val id = url.removePrefix("$mainUrl/e/").removePrefix("$mainUrl/d/") val trueUrl = getExtractorUrl(id) val response = app.get(trueUrl).text Regex("href=\".*/download/(.*?)\"").find(response)?.groupValues?.get(1)?.let { link -> if (link.isEmpty()) return null - sleep(5000) // might need this to not trigger anti bot + delay(5000) // might need this to not trigger anti bot val downloadLink = "$mainUrl/download/$link" val downloadResponse = app.get(downloadLink).text Regex("onclick=\"window\\.open\\((['\"])(.*?)(['\"])").find(downloadResponse)?.groupValues?.get(2) diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt index 27991b88..4bf2bac5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt @@ -15,7 +15,7 @@ open class Evoload : ExtractorApi() { - override fun getUrl(url: String, referer: String?): List { + override suspend fun getUrl(url: String, referer: String?): List { val id = url.replace("https://evoload.io/e/", "") // wanted media id val csrv_token = app.get("https://csrv.evosrv.com/captcha?m412548=").text // whatever that is val captchaPass = app.get("https://cd2.evosrv.com/html/jsx/e.jsx").text.take(300).split("captcha_pass = '")[1].split("\'")[0] //extract the captcha pass from the js response (located in the 300 first chars) diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/MixDrop.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/MixDrop.kt index 9bd2fb9f..c357b90e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/MixDrop.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/MixDrop.kt @@ -13,7 +13,7 @@ class MixDrop : ExtractorApi() { return "$mainUrl/e/$id" } - override fun getUrl(url: String, referer: String?): List? { + override suspend fun getUrl(url: String, referer: String?): List? { with(app.get(url)) { getAndUnpack(this.text).let { unpackedText -> srcRegex.find(unpackedText)?.groupValues?.get(1)?.let { link -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt index 30812550..9bdb57b7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt @@ -12,7 +12,7 @@ class Mp4Upload : ExtractorApi() { private val srcRegex = Regex("""player\.src\("(.*?)"""") override val requiresReferer = true - override fun getUrl(url: String, referer: String?): List? { + override suspend fun getUrl(url: String, referer: String?): List? { with(app.get(url)) { getAndUnpack(this.text).let { unpackedText -> srcRegex.find(unpackedText)?.groupValues?.get(1)?.let { link -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt index 162a2bc4..afd6da1e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt @@ -19,7 +19,7 @@ class MultiQuality : ExtractorApi() { return "$mainUrl/loadserver.php?id=$id" } - override fun getUrl(url: String, referer: String?): List { + override suspend fun getUrl(url: String, referer: String?): List { val extractedLinksList: MutableList = mutableListOf() with(app.get(url)) { sourceRegex.findAll(this.text).forEach { sourceMatch -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt index f8ccc6c7..1811826a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt @@ -1,8 +1,8 @@ package com.lagradost.cloudstream3.extractors +import com.lagradost.cloudstream3.apmap import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall -import com.lagradost.cloudstream3.pmap +import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.extractorApis import com.lagradost.cloudstream3.utils.getQualityFromName @@ -27,9 +27,9 @@ class Pelisplus(val mainUrl: String) { private val normalApis = arrayListOf(MultiQuality()) // https://gogo-stream.com/streaming.php?id=MTE3NDg5 - fun getUrl(id: String, isCasting: Boolean = false, callback: (ExtractorLink) -> Unit): Boolean { + suspend fun getUrl(id: String, isCasting: Boolean = false, callback: (ExtractorLink) -> Unit): Boolean { try { - normalApis.pmap { api -> + normalApis.apmap { api -> val url = api.getExtractorUrl(id) val source = api.getSafeUrl(url) source?.forEach { callback.invoke(it) } @@ -37,7 +37,7 @@ class Pelisplus(val mainUrl: String) { val extractorUrl = getExtractorUrl(id) /** Stolen from GogoanimeProvider.kt extractor */ - normalSafeApiCall { + suspendSafeApiCall { val link = getDownloadUrl(id) println("Generated vidstream download link: $link") val page = app.get(link, referer = extractorUrl) @@ -46,8 +46,8 @@ class Pelisplus(val mainUrl: String) { val qualityRegex = Regex("(\\d+)P") //a[download] - pageDoc.select(".dowload > a")?.pmap { element -> - val href = element.attr("href") ?: return@pmap + pageDoc.select(".dowload > a")?.apmap { element -> + val href = element.attr("href") ?: return@apmap val qual = if (element.text() .contains("HDP") ) "1080" else qualityRegex.find(element.text())?.destructured?.component1().toString() @@ -78,7 +78,7 @@ class Pelisplus(val mainUrl: String) { //val name = element.text() // Matches vidstream links with extractors - extractorApis.filter { !it.requiresReferer || !isCasting }.pmap { api -> + extractorApis.filter { !it.requiresReferer || !isCasting }.apmap { api -> if (link.startsWith(api.mainUrl)) { val extractedLinks = api.getSafeUrl(link, extractorUrl) if (extractedLinks?.isNotEmpty() == true) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/SBPlay.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/SBPlay.kt index d01ec99a..a6156a86 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/SBPlay.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/SBPlay.kt @@ -26,7 +26,7 @@ open class SBPlay : ExtractorApi() { override val name = "SBPlay" override val requiresReferer = false - override fun getUrl(url: String, referer: String?): List { + override suspend fun getUrl(url: String, referer: String?): List { val response = app.get(url, referer = referer).text val document = Jsoup.parse(response) diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt index 3259d45e..302eba53 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt @@ -19,7 +19,7 @@ class StreamSB : ExtractorApi() { override val requiresReferer = false // https://sbembed.com/embed-ns50b0cukf9j.html -> https://sbvideo.net/play/ns50b0cukf9j - override fun getUrl(url: String, referer: String?): List { + override suspend fun getUrl(url: String, referer: String?): List { val extractedLinksList: MutableList = mutableListOf() val newUrl = url.replace("sbplay.org/embed-", "sbplay.org/play/").removeSuffix(".html") with(app.get(newUrl, timeout = 10)) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt index fad652c9..b2ae1393 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt @@ -13,7 +13,7 @@ class StreamTape : ExtractorApi() { private val linkRegex = Regex("""'robotlink'\)\.innerHTML = '(.+?)'\+ \('(.+?)'\)""") - override fun getUrl(url: String, referer: String?): List? { + override suspend fun getUrl(url: String, referer: String?): List? { with(app.get(url)) { linkRegex.find(this.text)?.let { val extractedUrl = "https:${it.groups[1]!!.value + it.groups[2]!!.value.substring(3,)}" diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt index 72e8ae23..be0e9d6e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt @@ -16,7 +16,7 @@ class Streamhub : ExtractorApi() { return "$mainUrl/e/$id" } - override fun getUrl(url: String, referer: String?): List? { + override suspend fun getUrl(url: String, referer: String?): List? { val response = app.get(url).text Regex("eval((.|\\n)*?)").find(response)?.groupValues?.get(1)?.let { jsEval -> JsUnpacker("eval$jsEval").unpack()?.let { unPacked -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt index cc580f33..53a47387 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt @@ -10,7 +10,7 @@ class UpstreamExtractor: ExtractorApi() { override val mainUrl: String = "https://upstream.to" override val requiresReferer = true - override fun getUrl(url: String, referer: String?): List { + override suspend fun getUrl(url: String, referer: String?): List { // WIP: m3u8 link fetched but sometimes not playing //Log.i(this.name, "Result => (no extractor) ${url}") val sources: MutableList = mutableListOf() diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt index 469ec83b..643c80c8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt @@ -13,7 +13,7 @@ open class Uqload : ExtractorApi() { private val srcRegex = Regex("""sources:.\[(.*?)\]""") // would be possible to use the parse and find src attribute override val requiresReferer = true - override fun getUrl(url: String, referer: String?): List? { + override suspend fun getUrl(url: String, referer: String?): List? { with(app.get(url)) { // raised error ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED (3003) is due to the response: "error_nofile" srcRegex.find(this.text)?.groupValues?.get(1)?.replace("\"", "")?.let { link -> return listOf( diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt index 3cbdfdd7..b285b5d4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt @@ -1,8 +1,8 @@ package com.lagradost.cloudstream3.extractors +import com.lagradost.cloudstream3.apmap import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall -import com.lagradost.cloudstream3.pmap +import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.extractorApis import com.lagradost.cloudstream3.utils.getQualityFromName @@ -27,9 +27,9 @@ class Vidstream(val mainUrl: String) { private val normalApis = arrayListOf(MultiQuality()) // https://gogo-stream.com/streaming.php?id=MTE3NDg5 - fun getUrl(id: String, isCasting: Boolean = false, callback: (ExtractorLink) -> Unit): Boolean { + suspend fun getUrl(id: String, isCasting: Boolean = false, callback: (ExtractorLink) -> Unit): Boolean { try { - normalApis.pmap { api -> + normalApis.apmap { api -> val url = api.getExtractorUrl(id) val source = api.getSafeUrl(url) source?.forEach { callback.invoke(it) } @@ -37,7 +37,7 @@ class Vidstream(val mainUrl: String) { val extractorUrl = getExtractorUrl(id) /** Stolen from GogoanimeProvider.kt extractor */ - normalSafeApiCall { + suspendSafeApiCall { val link = getDownloadUrl(id) println("Generated vidstream download link: $link") val page = app.get(link, referer = extractorUrl) @@ -46,8 +46,8 @@ class Vidstream(val mainUrl: String) { val qualityRegex = Regex("(\\d+)P") //a[download] - pageDoc.select(".dowload > a")?.pmap { element -> - val href = element.attr("href") ?: return@pmap + pageDoc.select(".dowload > a")?.apmap { element -> + val href = element.attr("href") ?: return@apmap val qual = if (element.text() .contains("HDP") ) "1080" else qualityRegex.find(element.text())?.destructured?.component1().toString() @@ -78,7 +78,7 @@ class Vidstream(val mainUrl: String) { //val name = element.text() // Matches vidstream links with extractors - extractorApis.filter { !it.requiresReferer || !isCasting }.pmap { api -> + extractorApis.filter { !it.requiresReferer || !isCasting }.apmap { api -> if (link.startsWith(api.mainUrl)) { val extractedLinks = api.getSafeUrl(link, extractorUrl) if (extractedLinks?.isNotEmpty() == true) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/VoeExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/VoeExtractor.kt index 77e81326..c20fa28d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/VoeExtractor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/VoeExtractor.kt @@ -19,7 +19,7 @@ open class VoeExtractor : ExtractorApi() { //val type: String // Mp4 ) - override fun getUrl(url: String, referer: String?): List { + override suspend fun getUrl(url: String, referer: String?): List { val extractedLinksList: MutableList = mutableListOf() val doc = app.get(url).text if (doc.isNotEmpty()) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/WatchSB.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/WatchSB.kt index 720ccbd9..1c259d08 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/WatchSB.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/WatchSB.kt @@ -12,7 +12,7 @@ open class WatchSB : ExtractorApi() { override val mainUrl = "https://watchsb.com" override val requiresReferer = false - override fun getUrl(url: String, referer: String?): List { + override suspend fun getUrl(url: String, referer: String?): List { val response = app.get( url, interceptor = WebViewResolver( Regex("""master\.m3u8""") diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt index eaa3276c..1755066b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt @@ -12,7 +12,7 @@ class WcoStream : ExtractorApi() { override val requiresReferer = false private val hlsHelper = M3u8Helper() - override fun getUrl(url: String, referer: String?): List { + override suspend fun getUrl(url: String, referer: String?): List { val baseUrl = url.split("/e/")[0] val html = app.get(url, headers = mapOf("Referer" to "https://wcostream.cc/")).text diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt index 83c1dc5f..4a4cca56 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt @@ -29,7 +29,7 @@ open class XStreamCdn : ExtractorApi() { return "$domainUrl/api/source/$id" } - override fun getUrl(url: String, referer: String?): List { + override suspend fun getUrl(url: String, referer: String?): List { val headers = mapOf( "Referer" to url, "User-Agent" to "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0", diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper.kt index 14b921ed..8b0e36c4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor class AsianEmbedHelper { companion object { - fun getUrls(url: String, callback: (ExtractorLink) -> Unit) { + suspend fun getUrls(url: String, callback: (ExtractorLink) -> Unit) { if (url.startsWith("https://asianembed.io")) { // Fetch links val doc = app.get(url).document diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/VstreamhubHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/VstreamhubHelper.kt index 86995885..10554b8f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/VstreamhubHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/VstreamhubHelper.kt @@ -1,6 +1,5 @@ package com.lagradost.cloudstream3.extractors.helper -import android.util.Log import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities @@ -11,7 +10,7 @@ class VstreamhubHelper { private val baseUrl: String = "https://vstreamhub.com" private val baseName: String = "Vstreamhub" - fun getUrls(url: String, callback: (ExtractorLink) -> Unit) { + suspend fun getUrls(url: String, callback: (ExtractorLink) -> Unit) { if (url.startsWith(baseUrl)) { // Fetch links val doc = app.get(url).document.select("script") diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AkwamProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AkwamProvider.kt index 006b4b68..0f3cb0a3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AkwamProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AkwamProvider.kt @@ -41,7 +41,7 @@ class AkwamProvider : MainAPI() { "Series" to "$mainUrl/series", "Shows" to "$mainUrl/shows" ) - val pages = moviesUrl.pmap { + val pages = moviesUrl.apmap { val doc = app.get(it.second).document val list = doc.select("div.col-lg-auto.col-md-4.col-6.mb-12").mapNotNull { element -> element.toSearchResponse() @@ -150,7 +150,7 @@ class AkwamProvider : MainAPI() { // Maybe possible to not use the url shortener but cba investigating that. - private fun skipUrlShortener(url: String): AppResponse { + private suspend fun skipUrlShortener(url: String): AppResponse { return app.get(app.get(url).document.select("a.download-link").attr("href")) } @@ -180,7 +180,7 @@ class AkwamProvider : MainAPI() { }.filter { link -> link.first.contains("/link/") } }.flatten() - links.pmap { + links.map { val linkDoc = skipUrlShortener(it.first).document val button = linkDoc.select("div.btn-loader > a") val url = button.attr("href") diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/LookMovieProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/LookMovieProvider.kt index 7c17d91f..4278f2b5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/LookMovieProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/LookMovieProvider.kt @@ -110,7 +110,7 @@ class LookMovieProvider : MainAPI() { } override suspend fun search(query: String): List { - fun search(query: String, isMovie: Boolean): ArrayList { + suspend fun search(query: String, isMovie: Boolean): ArrayList { val url = "$mainUrl/${if (isMovie) "movies" else "shows"}/search/?q=$query" val response = app.get(url).text val document = Jsoup.parse(response) @@ -158,7 +158,7 @@ class LookMovieProvider : MainAPI() { } } - private fun loadCurrentLinks(url: String, callback: (ExtractorLink) -> Unit) { + private suspend fun loadCurrentLinks(url: String, callback: (ExtractorLink) -> Unit) { val response = app.get(url.replace("\$unixtime", unixTime.toString())).text M3u8Manifest.extractLinks(response).forEach { callback.invoke( diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisplusProviderTemplate.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisplusProviderTemplate.kt index 83606102..1825344c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisplusProviderTemplate.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisplusProviderTemplate.kt @@ -151,7 +151,7 @@ open class PelisplusProviderTemplate : MainAPI() { val urls = homePageUrlList val homePageList = ArrayList() // .pmap {} is used to fetch the different pages in parallel - urls.pmap { url -> + urls.apmap { url -> val response = app.get(url, timeout = 20).text val document = Jsoup.parse(response) document.select("div.main-inner")?.forEach { inner -> 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 c5964bb9..d65fcda9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt @@ -228,7 +228,7 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() { } } ?: tryParseJson>(data))?.distinct() - urls?.pmap { url -> + urls?.apmap { url -> val sources = app.get( url, interceptor = WebViewResolver( diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VfFilmProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VfFilmProvider.kt index c3dfaf4d..b5e0bd12 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VfFilmProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VfFilmProvider.kt @@ -59,7 +59,7 @@ class VfFilmProvider : MainAPI() { return true } - private fun getDirect(original: String): String { // original data, https://vf-film.org/?trembed=1&trid=55313&trtype=1 for example + private suspend fun getDirect(original: String): String { // original data, https://vf-film.org/?trembed=1&trid=55313&trtype=1 for example val response = app.get(original).text val url = "iframe .*src=\"(.*?)\"".toRegex().find(response)?.groupValues?.get(1) .toString() // https://vudeo.net/embed-uweno86lzx8f.html for example diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VfSerieProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VfSerieProvider.kt index 418984fc..be6f3105 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VfSerieProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VfSerieProvider.kt @@ -42,7 +42,7 @@ class VfSerieProvider : MainAPI() { return returnValue } - private fun getDirect(original: String): String { // original data, https://vf-serie.org/?trembed=1&trid=80467&trtype=2 for example + private suspend fun getDirect(original: String): String { // original data, https://vf-serie.org/?trembed=1&trid=80467&trtype=2 for example val response = app.get(original).text val url = "iframe .*src=\"(.*?)\"".toRegex().find(response)?.groupValues?.get(1) .toString() // https://vudeo.net/embed-7jdb1t5b2mvo.html for example diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VidstreamProviderTemplate.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VidstreamProviderTemplate.kt index c2567d71..b7e9a85e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VidstreamProviderTemplate.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/VidstreamProviderTemplate.kt @@ -148,7 +148,7 @@ open class VidstreamProviderTemplate : MainAPI() { val urls = homePageUrlList val homePageList = ArrayList() // .pmap {} is used to fetch the different pages in parallel - urls.pmap { url -> + urls.apmap { url -> val response = app.get(url, timeout = 20).text val document = Jsoup.parse(response) document.select("div.main-inner")?.forEach { inner -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/WatchAsianProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/WatchAsianProvider.kt index 4c6f9b37..0a27e4c8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/WatchAsianProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/WatchAsianProvider.kt @@ -191,7 +191,7 @@ class WatchAsianProvider : MainAPI() { return count > 0 } - private fun getServerLinks(url: String) : String { + private suspend fun getServerLinks(url: String) : String { val moviedoc = app.get(url, referer = mainUrl).document return moviedoc.select("div.anime_muti_link > ul > li") ?.mapNotNull { diff --git a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt index e15d7a16..28007b1c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt @@ -51,6 +51,15 @@ fun normalSafeApiCall(apiCall: () -> T): T? { } } +suspend fun suspendSafeApiCall(apiCall: suspend () -> T): T? { + return try { + apiCall.invoke() + } catch (throwable: Throwable) { + logError(throwable) + return null + } +} + fun safeFail(throwable: Throwable): Resource { val stackTraceMsg = (throwable.localizedMessage ?: "") + "\n\n" + throwable.stackTrace.joinToString( separator = "\n" diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/DdosGuardKiller.kt b/app/src/main/java/com/lagradost/cloudstream3/network/DdosGuardKiller.kt index 78e6f778..0d51f6cc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/DdosGuardKiller.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/DdosGuardKiller.kt @@ -2,6 +2,8 @@ package com.lagradost.cloudstream3.network import androidx.annotation.AnyThread import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.network.Requests.Companion.await +import kotlinx.coroutines.runBlocking import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response @@ -18,17 +20,17 @@ class DdosGuardKiller(private val alwaysBypass: Boolean) : Interceptor { private var ddosBypassPath: String? = null - override fun intercept(chain: Interceptor.Chain): Response { + override fun intercept(chain: Interceptor.Chain): Response = runBlocking { val request = chain.request() - if (alwaysBypass) return bypassDdosGuard(request) + if (alwaysBypass) return@runBlocking bypassDdosGuard(request) val response = chain.proceed(request) - return if (response.code == 403) { + return@runBlocking if (response.code == 403) { bypassDdosGuard(request) } else response } - private fun bypassDdosGuard(request: Request): Response { + private suspend fun bypassDdosGuard(request: Request): Response { ddosBypassPath = ddosBypassPath ?: Regex("'(.*?)'").find( app.get( "https://check.ddos-guard.net/check.js" @@ -49,6 +51,6 @@ class DdosGuardKiller(private val alwaysBypass: Boolean) : Interceptor { request.newBuilder() .headers(headers) .build() - ).execute() + ).await() } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/Requests.kt b/app/src/main/java/com/lagradost/cloudstream3/network/Requests.kt index 82941e9a..d3b56ac0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/Requests.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/Requests.kt @@ -7,13 +7,19 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mapper +import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.CompletionHandler +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.suspendCancellableCoroutine import okhttp3.* import okhttp3.Headers.Companion.toHeaders import org.jsoup.Jsoup import org.jsoup.nodes.Document import java.io.File +import java.io.IOException import java.net.URI import java.util.concurrent.TimeUnit +import kotlin.coroutines.resumeWithException class Session( @@ -234,7 +240,41 @@ open class Requests { return baseClient } - fun get( + class ContinuationCallback( + private val call: Call, + private val continuation: CancellableContinuation + ) : Callback, CompletionHandler { + + @ExperimentalCoroutinesApi + override fun onResponse(call: Call, response: Response) { + continuation.resume(response, null) + } + + override fun onFailure(call: Call, e: IOException) { + if (!call.isCanceled()) { + continuation.resumeWithException(e) + } + } + + override fun invoke(cause: Throwable?) { + try { + call.cancel() + } catch (_: Throwable) { + } + } + } + + companion object { + suspend inline fun Call.await(): Response { + return suspendCancellableCoroutine { continuation -> + val callback = ContinuationCallback(this, continuation) + enqueue(callback) + continuation.invokeOnCancellation(callback) + } + } + } + + suspend fun get( url: String, headers: Map = emptyMap(), referer: String? = null, @@ -255,15 +295,15 @@ open class Requests { if (interceptor != null) client.addInterceptor(interceptor) val request = getRequestCreator(url, headers, referer, params, cookies, cacheTime, cacheUnit) - val response = client.build().newCall(request).execute() + val response = client.build().newCall(request).await() return AppResponse(response) } - fun executeRequest(request : Request): AppResponse { + fun executeRequest(request: Request): AppResponse { return AppResponse(baseClient.newCall(request).execute()) } - fun post( + suspend fun post( url: String, headers: Map = mapOf(), referer: String? = null, @@ -283,11 +323,11 @@ open class Requests { .build() val request = postRequestCreator(url, headers, referer, params, cookies, data, cacheTime, cacheUnit) - val response = client.newCall(request).execute() + val response = client.newCall(request).await() return AppResponse(response) } - fun put( + suspend fun put( url: String, headers: Map = mapOf(), referer: String? = null, @@ -307,7 +347,7 @@ open class Requests { .build() val request = putRequestCreator(url, headers, referer, params, cookies, data, cacheTime, cacheUnit) - val response = client.newCall(request).execute() + val response = client.newCall(request).await() return AppResponse(response) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt b/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt index 329786b0..e0ede7c6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt @@ -76,7 +76,7 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List = override fun shouldInterceptRequest( view: WebView, request: WebResourceRequest - ): WebResourceResponse? { + ): WebResourceResponse? = runBlocking { val webViewUrl = request.url.toString() // println("Loading WebView URL: $webViewUrl") @@ -84,7 +84,7 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List = fixedRequest = request.toRequest().also(requestCallBack) println("Web-view request finished: $webViewUrl") destroyWebView() - return null + return@runBlocking null } if (additionalUrls.any { it.containsMatchIn(webViewUrl) }) { @@ -128,7 +128,13 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List = * Overriding with okhttp might fuck up otherwise working requests, * e.g the recaptcha request. * **/ - return try { + + /** NOTE! request.requestHeaders is not perfect! + * They don't contain all the headers the browser actually gives. + * Overriding with okhttp might fuck up otherwise working requests, + * e.g the recaptcha request. + * **/ + return@runBlocking try { when { blacklistedFiles.any { URI(webViewUrl).path.contains(it) } || webViewUrl.endsWith( "/favicon.ico" @@ -152,7 +158,7 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List = webViewUrl, headers = request.requestHeaders ).response.toWebResourceResponse() - else -> return super.shouldInterceptRequest(view, request) + else -> return@runBlocking super.shouldInterceptRequest(view, request) } } catch (e: Exception) { null diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt index f0fde233..3366384c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt @@ -66,7 +66,7 @@ interface SyncAPI : OAuth2API { val icon: Int val mainUrl: String - fun search(name: String): List? + suspend fun search(name: String): List? /** -1 -> None @@ -77,9 +77,9 @@ interface SyncAPI : OAuth2API { 4 -> PlanToWatch 5 -> ReWatching */ - fun score(id: String, status: SyncStatus): Boolean + suspend fun score(id: String, status: SyncStatus): Boolean - fun getStatus(id: String): SyncStatus? + suspend fun getStatus(id: String): SyncStatus? - fun getResult(id: String): SyncResult? + suspend fun getResult(id: String): SyncResult? } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt index 5765307b..3bde5c30 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt @@ -75,7 +75,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } } - override fun search(name: String): List? { + override suspend fun search(name: String): List? { val data = searchShows(name) ?: return null return data.data.Page.media.map { SyncAPI.SyncSearchResult( @@ -88,7 +88,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } } - override fun getResult(id: String): SyncAPI.SyncResult? { + override suspend fun getResult(id: String): SyncAPI.SyncResult? { val internalId = id.toIntOrNull() ?: return null val season = getSeason(internalId)?.data?.Media ?: return null @@ -104,7 +104,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { ) } - override fun getStatus(id: String): SyncAPI.SyncStatus? { + override suspend fun getStatus(id: String): SyncAPI.SyncStatus? { val internalId = id.toIntOrNull() ?: return null val data = getDataAboutId(internalId) ?: return null @@ -116,7 +116,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { ) } - override fun score(id: String, status: SyncAPI.SyncStatus): Boolean { + override suspend fun score(id: String, status: SyncAPI.SyncStatus): Boolean { return postDataAboutId( id.toIntOrNull() ?: return false, fromIntToAnimeStatus(status.status), @@ -143,7 +143,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { .replace("[^a-zA-Z0-9]".toRegex(), "") } - private fun searchShows(name: String): GetSearchRoot? { + private suspend fun searchShows(name: String): GetSearchRoot? { try { val query = """ query (${"$"}id: Int, ${"$"}page: Int, ${"$"}search: String, ${"$"}type: MediaType) { @@ -225,7 +225,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } // Should use https://gist.github.com/purplepinapples/5dc60f15f2837bf1cea71b089cfeaa0a - fun getShowId(malId: String?, name: String, year: Int?): GetSearchMedia? { + suspend fun getShowId(malId: String?, name: String, year: Int?): GetSearchMedia? { // Strips these from the name val blackList = listOf( "TV Dubbed", @@ -293,7 +293,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } - private fun getSeason(id: Int): SeasonResponse? { + private suspend fun getSeason(id: Int): SeasonResponse? { val q: String = """ query (${'$'}id: Int = $id) { Media (id: ${'$'}id, type: ANIME) { @@ -351,7 +351,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { )!! } - fun getDataAboutId(id: Int): AniListTitleHolder? { + suspend fun getDataAboutId(id: Int): AniListTitleHolder? { val q = """query (${'$'}id: Int = $id) { # Define which variables will be used in the query (id) Media (id: ${'$'}id, type: ANIME) { # Insert our variables into the query arguments (id) (type: ANIME is hard-coded in the query) @@ -410,7 +410,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } } - private fun postApi(url: String, q: String, cache: Boolean = false): String { + private suspend fun postApi(url: String, q: String, cache: Boolean = false): String { return try { if (!checkToken()) { // println("VARS_ " + vars) @@ -514,7 +514,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { return getKey(ANILIST_CACHED_LIST) as? Array } - fun getAnilistAnimeListSmart(): Array? { + suspend fun getAnilistAnimeListSmart(): Array? { if (getKey( accountId, ANILIST_TOKEN_KEY, @@ -535,7 +535,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } } - private fun getFullAnilistList(): FullAnilistList? { + private suspend fun getFullAnilistList(): FullAnilistList? { try { var userID: Int? = null /** WARNING ASSUMES ONE USER! **/ @@ -597,7 +597,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } } - fun toggleLike(id: Int): Boolean { + suspend fun toggleLike(id: Int): Boolean { val q = """mutation (${'$'}animeId: Int = $id) { ToggleFavourite (animeId: ${'$'}animeId) { anime { @@ -614,7 +614,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { return data != "" } - private fun postDataAboutId( + private suspend fun postDataAboutId( id: Int, type: AniListStatusType, score: Int?, @@ -643,7 +643,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } } - private fun getUser(setSettings: Boolean = true): AniListUser? { + private suspend fun getUser(setSettings: Boolean = true): AniListUser? { val q = """ { Viewer { @@ -686,9 +686,9 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } } - fun getAllSeasons(id: Int): List { + suspend fun getAllSeasons(id: Int): List { val seasons = mutableListOf() - fun getSeasonRecursive(id: Int) { + suspend fun getSeasonRecursive(id: Int) { val season = getSeason(id) if (season != null) { seasons.add(season) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt index a48b68ac..8629e0a8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt @@ -50,7 +50,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { return null } - override fun search(name: String): List { + override suspend fun search(name: String): List { val url = "https://api.myanimelist.net/v2/anime?q=$name&limit=$MAL_MAX_SEARCH_LIMIT" val auth = getKey( accountId, @@ -73,7 +73,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { } } - override fun score(id: String, status : SyncAPI.SyncStatus): Boolean { + override suspend fun score(id: String, status : SyncAPI.SyncStatus): Boolean { return setScoreRequest( id.toIntOrNull() ?: return false, fromIntToAnimeStatus(status.status), @@ -82,12 +82,12 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { ) } - override fun getResult(id: String): SyncAPI.SyncResult? { + override suspend fun getResult(id: String): SyncAPI.SyncResult? { val internalId = id.toIntOrNull() ?: return null TODO("Not yet implemented") } - override fun getStatus(id: String): SyncAPI.SyncStatus? { + override suspend fun getStatus(id: String): SyncAPI.SyncStatus? { val internalId = id.toIntOrNull() ?: return null val data = getDataAboutMalId(internalId)?.my_list_status ?: return null @@ -182,7 +182,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { } } - private fun refreshToken() { + private suspend fun refreshToken() { try { val res = app.post( "https://myanimelist.net/v1/oauth2/token", @@ -281,7 +281,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { return getKey(MAL_CACHED_LIST) as? Array } - fun getMalAnimeListSmart(): Array? { + suspend fun getMalAnimeListSmart(): Array? { if (getKey( accountId, MAL_TOKEN_KEY @@ -299,7 +299,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { } } - private fun getMalAnimeList(): Array? { + private suspend fun getMalAnimeList(): Array? { return try { checkMalToken() var offset = 0 @@ -321,7 +321,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { return fromIntToAnimeStatus(malStatusAsString.indexOf(string)) } - private fun getMalAnimeListSlice(offset: Int = 0): MalList? { + private suspend fun getMalAnimeListSlice(offset: Int = 0): MalList? { val user = "@me" val auth = getKey( accountId, @@ -344,7 +344,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { } } - private fun getDataAboutMalId(id: Int): MalAnime? { + private suspend fun getDataAboutMalId(id: Int): MalAnime? { return try { // https://myanimelist.net/apiconfig/references/api/v2#operation/anime_anime_id_get val url = "https://api.myanimelist.net/v2/anime/$id?fields=id,title,num_episodes,my_list_status" @@ -362,7 +362,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { } } - fun setAllMalData() { + suspend fun setAllMalData() { val user = "@me" var isDone = false var index = 0 @@ -426,7 +426,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { return null } - private fun checkMalToken() { + private suspend fun checkMalToken() { if (unixTime > getKey( accountId, MAL_UNIXTIME_KEY @@ -436,7 +436,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { } } - private fun getMalUser(setSettings: Boolean = true): MalUser? { + private suspend fun getMalUser(setSettings: Boolean = true): MalUser? { checkMalToken() return try { val res = app.get( @@ -483,7 +483,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { } } - fun setScoreRequest( + suspend fun setScoreRequest( id: Int, status: MalStatusType? = null, score: Int? = null, @@ -514,7 +514,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { } } - private fun setScoreRequest( + private suspend fun setScoreRequest( id: Int, status: String? = null, score: Int? = null, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt index 6debb5e6..f54a101c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt @@ -8,8 +8,6 @@ import com.lagradost.cloudstream3.utils.ExtractorLink class APIRepository(val api: MainAPI) { companion object { - var providersActive = HashSet() - var typesActive = HashSet() var dubStatusActive = HashSet() val noneApi = object : MainAPI() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 9db0ba1c..1baad3a5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -138,6 +138,22 @@ class HomeFragment : Fragment() { bottomSheetDialogBuilder.show() } + fun getPairList( + anime: MaterialButton?, + cartoons: MaterialButton?, + tvs: MaterialButton?, + docs: MaterialButton?, + movies: MaterialButton? + ): List>> { + return listOf( + Pair(anime, listOf(TvType.Anime, TvType.ONA, TvType.AnimeMovie)), + Pair(cartoons, listOf(TvType.Cartoon)), + Pair(tvs, listOf(TvType.TvSeries)), + Pair(docs, listOf(TvType.Documentary)), + Pair(movies, listOf(TvType.Movie, TvType.Torrent)) + ) + } + fun Context.selectHomepage(selectedApiName: String?, callback: (String) -> Unit) { val validAPIs = filterProviderByPreferredMedia().toMutableList() @@ -169,6 +185,8 @@ class HomeFragment : Fragment() { val cancelBtt = dialog.findViewById(R.id.cancel_btt) val applyBtt = dialog.findViewById(R.id.apply_btt) + val pairList = getPairList(anime, cartoons, tvs, docs, movies) + cancelBtt?.setOnClickListener { dialog.dismissSafe() } @@ -194,14 +212,6 @@ class HomeFragment : Fragment() { } } - val pairList = listOf( - Pair(anime, listOf(TvType.Anime, TvType.ONA, TvType.AnimeMovie)), - Pair(cartoons, listOf(TvType.Cartoon)), - Pair(tvs, listOf(TvType.TvSeries)), - Pair(docs, listOf(TvType.Documentary)), - Pair(movies, listOf(TvType.Movie, TvType.Torrent)) - ) - fun updateList() { this.setKey(HOME_PREF_HOMEPAGE, preSelectedTypes) @@ -210,12 +220,11 @@ class HomeFragment : Fragment() { api.hasMainPage && api.supportedTypes.any { preSelectedTypes.contains(it) } - }.toMutableList() + }.sortedBy { it.name }.toMutableList() currentValidApis.addAll(0, validAPIs.subList(0, 2)) val names = currentValidApis.map { it.name } val index = names.indexOf(currentApiName) - println("INDEX: $index") listView?.setItemChecked(index, true) arrayAdapter.notifyDataSetChanged() arrayAdapter.addAll(names) @@ -331,13 +340,13 @@ class HomeFragment : Fragment() { } }*/ - private fun focusCallback(card : SearchResponse) { + private fun focusCallback(card: SearchResponse) { home_focus_text?.text = card.name - home_blur_poster?.setImageBlur(card.posterUrl,50) + home_blur_poster?.setImageBlur(card.posterUrl, 50) } - private fun homeHandleSearch(callback : SearchClickCallback) { - if(callback.action == SEARCH_ACTION_FOCUSED) { + private fun homeHandleSearch(callback: SearchClickCallback) { + if (callback.action == SEARCH_ACTION_FOCUSED) { focusCallback(callback.card) } else { handleSearchClickCallback(activity, callback) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index 5b2f704e..e06aae76 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -6,8 +6,10 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.WindowManager -import android.widget.* -import androidx.appcompat.app.AlertDialog +import android.widget.AbsListView +import android.widget.ArrayAdapter +import android.widget.ImageView +import android.widget.ListView import androidx.appcompat.widget.SearchView import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -15,18 +17,15 @@ import androidx.fragment.app.activityViewModels import androidx.preference.PreferenceManager import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.google.android.material.switchmaterial.SwitchMaterial +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.button.MaterialButton import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.APIHolder.apis -import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings -import com.lagradost.cloudstream3.APIHolder.getApiSettings -import com.lagradost.cloudstream3.APIHolder.getApiTypeSettings +import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia +import com.lagradost.cloudstream3.APIHolder.getApiFromName import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.APIRepository -import com.lagradost.cloudstream3.ui.APIRepository.Companion.providersActive -import com.lagradost.cloudstream3.ui.APIRepository.Companion.typesActive import com.lagradost.cloudstream3.ui.home.HomeFragment import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.currentSpan import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.loadHomepageList @@ -34,7 +33,6 @@ import com.lagradost.cloudstream3.ui.home.ParentItemAdapter import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.setKey -import com.lagradost.cloudstream3.utils.SEARCH_PROVIDER_TOGGLE import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount @@ -42,6 +40,8 @@ import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import kotlinx.android.synthetic.main.fragment_search.* import java.util.concurrent.locks.ReentrantLock +const val SEARCH_PREF_TAGS = "search_pref_tags" +const val SEARCH_PREF_PROVIDERS = "search_pref_providers" class SearchFragment : Fragment() { companion object { @@ -90,6 +90,9 @@ class SearchFragment : Fragment() { super.onDestroyView() } + var selectedSearchTypes = mutableListOf() + var selectedApis = mutableSetOf() + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -108,188 +111,190 @@ class SearchFragment : Fragment() { search_autofit_results.adapter = adapter search_loading_bar.alpha = 0f - val searchExitIcon = main_search.findViewById(androidx.appcompat.R.id.search_close_btn) - val searchMagIcon = main_search.findViewById(androidx.appcompat.R.id.search_mag_icon) + val searchExitIcon = + main_search.findViewById(androidx.appcompat.R.id.search_close_btn) + val searchMagIcon = + main_search.findViewById(androidx.appcompat.R.id.search_mag_icon) searchMagIcon.scaleX = 0.65f searchMagIcon.scaleY = 0.65f + + context?.let { ctx -> + val validAPIs = ctx.filterProviderByPreferredMedia() + selectedApis = ctx.getKey( + SEARCH_PREF_PROVIDERS, + defVal = validAPIs.map { it.name } + )!!.toMutableSet() + } + search_filter.setOnClickListener { searchView -> - val apiNamesSetting = activity?.getApiSettings() - val langs = activity?.getApiProviderLangSettings() - if (apiNamesSetting != null && langs != null) { - val apiNames = apis.filter { langs.contains(it.lang) }.map { it.name } + searchView?.context?.let { ctx -> + val validAPIs = ctx.filterProviderByPreferredMedia() + var currentValidApis = listOf() + val currentSelectedApis = if (selectedApis.isEmpty()) validAPIs.map { it.name } + .toMutableSet() else selectedApis val builder = - AlertDialog.Builder(searchView.context).setView(R.layout.provider_list) + BottomSheetDialog(ctx) - val dialog = builder.create() - dialog.show() + builder.setContentView(R.layout.home_select_mainpage) + builder.show() + builder.let { dialog -> + val anime = dialog.findViewById(R.id.home_select_anime) + val cartoons = dialog.findViewById(R.id.home_select_cartoons) + val tvs = dialog.findViewById(R.id.home_select_tv_series) + val docs = dialog.findViewById(R.id.home_select_documentaries) + val movies = dialog.findViewById(R.id.home_select_movies) + val cancelBtt = dialog.findViewById(R.id.cancel_btt) + val applyBtt = dialog.findViewById(R.id.apply_btt) - val listView = dialog.findViewById(R.id.listview1)!! - val listView2 = dialog.findViewById(R.id.listview2)!! - val toggle = dialog.findViewById(R.id.toggle1)!! - val applyButton = dialog.findViewById(R.id.apply_btt)!! - val cancelButton = dialog.findViewById(R.id.cancel_btt)!! - // val applyHolder = dialog.findViewById(R.id.apply_btt_holder)!! + val pairList = HomeFragment.getPairList(anime, cartoons, tvs, docs, movies) - val arrayAdapter = ArrayAdapter(searchView.context, R.layout.sort_bottom_single_choice) - arrayAdapter.addAll(apiNames) - - listView.adapter = arrayAdapter - listView.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE - - val typeChoices = listOf( - Pair(R.string.movies, listOf(TvType.Movie)), - Pair(R.string.tv_series, listOf(TvType.TvSeries, TvType.Documentary)), - Pair(R.string.cartoons, listOf(TvType.Cartoon)), - Pair(R.string.anime, listOf(TvType.Anime, TvType.ONA, TvType.AnimeMovie)), - Pair(R.string.torrent, listOf(TvType.Torrent)), - ).filter { item -> apis.any { api -> api.supportedTypes.any { type -> item.second.contains(type) } } } - - val arrayAdapter2 = ArrayAdapter(searchView.context, R.layout.sort_bottom_single_choice) - arrayAdapter2.addAll(typeChoices.map { getString(it.first) }) - - listView2.adapter = arrayAdapter2 - listView2.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE - - for ((index, item) in apiNames.withIndex()) { - listView.setItemChecked(index, apiNamesSetting.contains(item)) - } - - for ((index, item) in typeChoices.withIndex()) { - listView2.setItemChecked(index, item.second.any { typesActive.contains(it) }) - } - - fun toggleSearch(isOn: Boolean) { - toggle.text = - getString(if (isOn) R.string.search_provider_text_types else R.string.search_provider_text_providers) - - if (isOn) { - listView2.visibility = View.VISIBLE - listView.visibility = View.GONE - } else { - listView.visibility = View.VISIBLE - listView2.visibility = View.GONE + cancelBtt?.setOnClickListener { + dialog.dismissSafe() } - } - val defVal = context?.getKey(SEARCH_PROVIDER_TOGGLE, true) ?: true - toggleSearch(defVal) + cancelBtt?.setOnClickListener { + dialog.dismissSafe() + } - toggle.isChecked = defVal - toggle.setOnCheckedChangeListener { _, isOn -> - toggleSearch(isOn) - } + applyBtt?.setOnClickListener { + //if (currentApiName != selectedApiName) { + // currentApiName?.let(callback) + //} + dialog.dismissSafe() + } - listView.setOnItemClickListener { _, _, _, _ -> - val types = HashSet() - for ((index, api) in apis.withIndex()) { - if (listView.checkedItemPositions[index]) { - types.addAll(api.supportedTypes) + dialog.setOnDismissListener { + context?.setKey(SEARCH_PREF_PROVIDERS, currentSelectedApis.toList()) + selectedApis = currentSelectedApis + } + + val selectedSearchTypes = context?.getKey>(SEARCH_PREF_TAGS) + ?.mapNotNull { listName -> + TvType.values().firstOrNull { it.name == listName } } - } - for ((typeIndex, type) in typeChoices.withIndex()) { - listView2.setItemChecked(typeIndex, type.second.any { types.contains(it) }) - } - } + ?.toMutableList() + ?: mutableListOf(TvType.Movie, TvType.TvSeries) - listView2.setOnItemClickListener { _, _, _, _ -> - for ((index, api) in apis.withIndex()) { - var isSupported = false + val listView = dialog.findViewById(R.id.listview1) + val arrayAdapter = ArrayAdapter(ctx, R.layout.sort_bottom_single_choice) + listView?.adapter = arrayAdapter + listView?.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE - for ((typeIndex, type) in typeChoices.withIndex()) { - if (listView2.checkedItemPositions[typeIndex]) { - if (api.supportedTypes.any { type.second.contains(it) }) { - isSupported = true - } + listView?.setOnItemClickListener { _, _, i, _ -> + if (!currentValidApis.isNullOrEmpty()) { + val api = currentValidApis[i].name + if (currentSelectedApis.contains(api)) { + listView.setItemChecked(i, false) + currentSelectedApis -= api + } else { + listView.setItemChecked(i, true) + currentSelectedApis += api } } - - listView.setItemChecked( - index, - isSupported - ) } - } - dialog.setOnDismissListener { - context?.setKey(SEARCH_PROVIDER_TOGGLE, toggle.isChecked) - } + fun updateList() { + arrayAdapter.clear() + currentValidApis = validAPIs.filter { api -> + api.hasMainPage && api.supportedTypes.any { + selectedSearchTypes.contains(it) + } + }.sortedBy { it.name } - applyButton.setOnClickListener { - val settingsManagerLocal = PreferenceManager.getDefaultSharedPreferences(activity) + val names = currentValidApis.map { it.name } - val activeTypes = HashSet() - for ((index, _) in typeChoices.withIndex()) { - if (listView2.checkedItemPositions[index]) { - activeTypes.addAll(typeChoices[index].second) + for ((index, api) in names.withIndex()) { + listView?.setItemChecked(index, currentSelectedApis.contains(api)) + } + + arrayAdapter.notifyDataSetChanged() + arrayAdapter.addAll(names) + arrayAdapter.notifyDataSetChanged() + } + + for ((button, validTypes) in pairList) { + val isValid = + validAPIs.any { api -> validTypes.any { api.supportedTypes.contains(it) } } + button?.isVisible = isValid + if (isValid) { + fun buttonContains(): Boolean { + return selectedSearchTypes.any { validTypes.contains(it) } + } + + button?.isSelected = buttonContains() + button?.setOnClickListener { + selectedSearchTypes.clear() + selectedSearchTypes.addAll(validTypes) + for ((otherButton, _) in pairList) { + otherButton?.isSelected = false + } + button.isSelected = true + updateList() + } + + button?.setOnLongClickListener { + if (!buttonContains()) { + button.isSelected = true + selectedSearchTypes.addAll(validTypes) + } else { + button.isSelected = false + selectedSearchTypes.removeAll(validTypes) + } + updateList() + return@setOnLongClickListener true + } } } + updateList() + } + } + } - if (activeTypes.size == 0) { - activeTypes.addAll(TvType.values()) + val pairList = HomeFragment.getPairList( + search_select_anime, + search_select_cartoons, + search_select_tv_series, + search_select_documentaries, + search_select_movies + ) + + selectedSearchTypes = context?.getKey>(SEARCH_PREF_TAGS) + ?.mapNotNull { listName -> TvType.values().firstOrNull { it.name == listName } } + ?.toMutableList() + ?: mutableListOf(TvType.Movie, TvType.TvSeries) + context?.filterProviderByPreferredMedia()?.let { validAPIs -> + for ((button, validTypes) in pairList) { + val isValid = + validAPIs.any { api -> validTypes.any { api.supportedTypes.contains(it) } } + button?.isVisible = isValid + if (isValid) { + fun buttonContains(): Boolean { + return selectedSearchTypes.any { validTypes.contains(it) } } - - val activeApis = HashSet() - for ((index, name) in apiNames.withIndex()) { - if (listView.checkedItemPositions[index]) { - activeApis.add(name) + button?.isSelected = buttonContains() + button?.setOnClickListener { + selectedSearchTypes.clear() + selectedSearchTypes.addAll(validTypes) + for ((otherButton, _) in pairList) { + otherButton?.isSelected = false } + it?.context?.setKey(SEARCH_PREF_TAGS, selectedSearchTypes) + it?.isSelected = true } - if (activeApis.size == 0) { - activeApis.addAll(apiNames) - } - - val edit = settingsManagerLocal.edit() - edit.putStringSet( - getString(R.string.search_providers_list_key), - activeApis - ) - edit.putStringSet( - getString(R.string.search_types_list_key), - activeTypes.map { it.name }.toSet() - ) - edit.apply() - providersActive = activeApis - typesActive = activeTypes - - dialog.dismissSafe(activity) - } - - cancelButton.setOnClickListener { - dialog.dismissSafe(activity) - } - - //listView.setSelection(selectedIndex) - // listView.setItemChecked(selectedIndex, true) - /* - val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext()) - - builder.setMultiChoiceItems( - apiNames.toTypedArray(), - apiNames.map { a -> apiNamesSetting.contains(a) }.toBooleanArray() - ) { _, position: Int, checked: Boolean -> - val apiNamesSettingLocal = activity?.getApiSettings() - if (apiNamesSettingLocal != null) { - val settingsManagerLocal = PreferenceManager.getDefaultSharedPreferences(activity) - if (checked) { - apiNamesSettingLocal.add(apiNames[position]) + button?.setOnLongClickListener { + if (!buttonContains()) { + it?.isSelected = true + selectedSearchTypes.addAll(validTypes) } else { - apiNamesSettingLocal.remove(apiNames[position]) + it?.isSelected = false + selectedSearchTypes.removeAll(validTypes) } - - val edit = settingsManagerLocal.edit() - edit.putStringSet( - getString(R.string.search_providers_list_key), - apiNames.filter { a -> apiNamesSettingLocal.contains(a) }.toSet() - ) - edit.apply() - providersActive = apiNamesSettingLocal + it?.context?.setKey(SEARCH_PREF_TAGS, selectedSearchTypes) + return@setOnLongClickListener true } } - builder.setTitle("Search Providers") - builder.setNegativeButton("Ok") { _, _ -> } - builder.show()*/ } } @@ -300,7 +305,12 @@ class SearchFragment : Fragment() { main_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String): Boolean { - searchViewModel.searchAndCancel(query = query) + searchViewModel.searchAndCancel( + query = query, + providersActive = selectedApis.filter { name -> + getApiFromName(name).supportedTypes.any { selectedSearchTypes.contains(it) } + }.toSet() + ) main_search?.let { hideKeyboard(it) @@ -363,10 +373,6 @@ class SearchFragment : Fragment() { } } - activity?.let { - providersActive = it.getApiSettings() - typesActive = it.getApiTypeSettings() - } /*main_search.setOnQueryTextFocusChangeListener { _, b -> if (b) { @@ -376,20 +382,21 @@ class SearchFragment : Fragment() { }*/ //main_search.onActionViewExpanded()*/ - val masterAdapter: RecyclerView.Adapter = ParentItemAdapter(listOf(), { callback -> - SearchHelper.handleSearchClickCallback(activity, callback) - }, { item -> - activity?.loadHomepageList(item) - }) + val masterAdapter: RecyclerView.Adapter = + ParentItemAdapter(listOf(), { callback -> + SearchHelper.handleSearchClickCallback(activity, callback) + }, { item -> + activity?.loadHomepageList(item) + }) - search_master_recycler.adapter = masterAdapter - search_master_recycler.layoutManager = GridLayoutManager(context, 1) + search_master_recycler?.adapter = masterAdapter + search_master_recycler?.layoutManager = GridLayoutManager(context, 1) val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) val isAdvancedSearch = settingsManager.getBoolean("advanced_search", true) - search_master_recycler.isVisible = isAdvancedSearch - search_autofit_results.isVisible = !isAdvancedSearch + search_master_recycler?.isVisible = isAdvancedSearch + search_autofit_results?.isVisible = !isAdvancedSearch // SubtitlesFragment.push(activity) //searchViewModel.search("iron man") diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt index df4b19cd..20fe0b08 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt @@ -14,7 +14,6 @@ import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.SyncApis import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.ui.APIRepository -import com.lagradost.cloudstream3.ui.APIRepository.Companion.providersActive import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -26,7 +25,8 @@ data class OnGoingSearch( ) class SearchViewModel : ViewModel() { - private val _searchResponse: MutableLiveData>> = MutableLiveData() + private val _searchResponse: MutableLiveData>> = + MutableLiveData() val searchResponse: LiveData>> get() = _searchResponse private val _currentSearch: MutableLiveData> = MutableLiveData() @@ -40,9 +40,14 @@ class SearchViewModel : ViewModel() { } var onGoingSearch: Job? = null - fun searchAndCancel(query: String, isMainApis: Boolean = true, ignoreSettings: Boolean = false) { + fun searchAndCancel( + query: String, + isMainApis: Boolean = true, + providersActive: Set = setOf(), + ignoreSettings: Boolean = false + ) { onGoingSearch?.cancel() - onGoingSearch = search(query, isMainApis, ignoreSettings) + onGoingSearch = search(query, isMainApis, providersActive, ignoreSettings) } data class SyncSearchResultSearchResponse( @@ -65,7 +70,12 @@ class SearchViewModel : ViewModel() { ) } - private fun search(query: String, isMainApis: Boolean = true, ignoreSettings: Boolean = false) = + private fun search( + query: String, + isMainApis: Boolean = true, + providersActive: Set, + ignoreSettings: Boolean = false + ) = viewModelScope.launch { if (query.length <= 1) { clearSearch() @@ -81,7 +91,7 @@ class SearchViewModel : ViewModel() { withContext(Dispatchers.IO) { // This interrupts UI otherwise if (isMainApis) { repos.filter { a -> - ignoreSettings || (providersActive.size == 0 || providersActive.contains(a.name)) + ignoreSettings || (providersActive.isEmpty() || providersActive.contains(a.name)) }.apmap { a -> // Parallel val search = a.search(query) currentList.add(OnGoingSearch(a.name, search)) @@ -102,7 +112,8 @@ class SearchViewModel : ViewModel() { val list = ArrayList() val nestedList = - currentList.map { it.data }.filterIsInstance>>().map { it.value } + currentList.map { it.data } + .filterIsInstance>>().map { it.value } // I do it this way to move the relevant search results to the top var index = 0 diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index 56aa5a0b..76f7b27d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -22,7 +22,6 @@ import com.hippo.unifile.UniFile import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings -import com.lagradost.cloudstream3.APIHolder.getApiSettings import com.lagradost.cloudstream3.APIHolder.restrictedApis import com.lagradost.cloudstream3.AcraApplication import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey @@ -294,7 +293,7 @@ class SettingsFragment : PreferenceFragmentCompat() { this.getString(R.string.provider_lang_key), selectedList.map { names[it].first }.toMutableSet() ).apply() - APIRepository.providersActive = it.context.getApiSettings() + //APIRepository.providersActive = it.context.getApiSettings() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt index d1eb1b54..51c82f92 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -5,7 +5,8 @@ import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.extractors.* -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall +import kotlinx.coroutines.delay import org.jsoup.Jsoup data class ExtractorLink( @@ -78,7 +79,7 @@ fun getAndUnpack(string: String): String { /** * Tries to load the appropriate extractor based on link, returns true if any extractor is loaded. * */ -fun loadExtractor(url: String, referer: String?, callback: (ExtractorLink) -> Unit) : Boolean { +suspend fun loadExtractor(url: String, referer: String?, callback: (ExtractorLink) -> Unit) : Boolean { for (extractor in extractorApis) { if (url.startsWith(extractor.mainUrl)) { extractor.getSafeUrl(url, referer)?.forEach(callback) @@ -138,7 +139,7 @@ fun httpsify(url: String): String { return if (url.startsWith("//")) "https:$url" else url } -fun getPostForm(requestUrl : String, html : String) : String? { +suspend fun getPostForm(requestUrl : String, html : String) : String? { val document = Jsoup.parse(html) val inputs = document.select("Form > input") if (inputs.size < 4) return null @@ -160,7 +161,7 @@ fun getPostForm(requestUrl : String, html : String) : String? { if (op == null || id == null || mode == null || hash == null) { return null } - Thread.sleep(5000) // ye this is needed, wont work with 0 delay + delay(5000) // ye this is needed, wont work with 0 delay val postResponse = app.post( requestUrl, @@ -181,14 +182,14 @@ abstract class ExtractorApi { abstract val mainUrl: String abstract val requiresReferer: Boolean - fun getSafeUrl(url: String, referer: String? = null): List? { - return normalSafeApiCall { getUrl(url, referer) } + suspend fun getSafeUrl(url: String, referer: String? = null): List? { + return suspendSafeApiCall { getUrl(url, referer) } } /** * Will throw errors, use getSafeUrl if you don't want to handle the exception yourself */ - abstract fun getUrl(url: String, referer: String? = null): List? + abstract suspend fun getUrl(url: String, referer: String? = null): List? open fun getExtractorUrl(id: String): String { return id diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/FillerEpisodeCheck.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/FillerEpisodeCheck.kt index a8aee7d4..d0fdd88b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/FillerEpisodeCheck.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/FillerEpisodeCheck.kt @@ -16,7 +16,7 @@ object FillerEpisodeCheck { return name.lowercase(Locale.ROOT)/*.replace(" ", "")*/.replace("-", " ").replace("[^a-zA-Z0-9 ]".toRegex(), "") } - private fun getFillerList(): Boolean { + private suspend fun getFillerList(): Boolean { if (list != null) return true try { val result = app.get("$MAIN_URL/shows").text @@ -59,7 +59,7 @@ object FillerEpisodeCheck { return q + "cache" + z } - fun getFillerEpisodes(query: String): HashMap? { + suspend fun getFillerEpisodes(query: String): HashMap? { try { if (!getFillerList()) return null val localList = list ?: return null diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt index 4996c1bf..ad879e73 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt @@ -24,6 +24,7 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import kotlinx.coroutines.runBlocking import java.io.File import kotlin.concurrent.thread @@ -83,7 +84,12 @@ class InAppUpdater { val url = "https://api.github.com/repos/LagradOst/CloudStream-3/releases" val headers = mapOf("Accept" to "application/vnd.github.v3+json") val response = - mapper.readValue>(app.get(url, headers = headers).text) + mapper.readValue>(runBlocking { + app.get( + url, + headers = headers + ).text + }) val versionRegex = Regex("""(.*?((\d+)\.(\d+)\.(\d+))\.apk)""") val versionRegexLocal = Regex("""(.*?((\d+)\.(\d+)\.(\d+)).*)""") @@ -139,7 +145,7 @@ class InAppUpdater { return Update(false, null, null, null) } - private fun Activity.getPreReleaseUpdate(): Update { + private fun Activity.getPreReleaseUpdate(): Update = runBlocking { val tagUrl = "https://api.github.com/repos/LagradOst/CloudStream-3/git/ref/tags/pre-release" val releaseUrl = "https://api.github.com/repos/LagradOst/CloudStream-3/releases" @@ -159,7 +165,7 @@ class InAppUpdater { val shouldUpdate = (getString(R.string.prerelease_commit_hash) != tagResponse.github_object.sha) - return if (foundAsset != null) { + return@runBlocking if (foundAsset != null) { Update( shouldUpdate, foundAsset.browser_download_url, diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/M3u8Helper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/M3u8Helper.kt index 9e6f1a19..d1d60f17 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/M3u8Helper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/M3u8Helper.kt @@ -2,7 +2,7 @@ package com.lagradost.cloudstream3.utils import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.network.text +import kotlinx.coroutines.runBlocking import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec @@ -82,7 +82,9 @@ class M3u8Helper { fun m3u8Generation(m3u8: M3u8Stream, returnThis: Boolean): List { val generate = sequence { val m3u8Parent = getParentLink(m3u8.streamUrl) - val response = app.get(m3u8.streamUrl, headers = m3u8.headers).text + val response = runBlocking { + app.get(m3u8.streamUrl, headers = m3u8.headers).text + } for (match in QUALITY_REGEX.findAll(response)) { var (quality, m3u8Link, m3u8Link2) = match.destructured @@ -146,7 +148,7 @@ class M3u8Helper { val secondSelection = selectBest(streams.ifEmpty { listOf(selected) }) if (secondSelection != null) { - val m3u8Response = app.get(secondSelection.streamUrl, headers = headers).text + val m3u8Response = runBlocking {app.get(secondSelection.streamUrl, headers = headers).text} var encryptionUri: String? var encryptionIv = byteArrayOf() @@ -164,7 +166,7 @@ class M3u8Helper { } encryptionIv = match.component3().toByteArray() - val encryptionKeyResponse = app.get(encryptionUri, headers = headers) + val encryptionKeyResponse = runBlocking { app.get(encryptionUri, headers = headers) } encryptionData = encryptionKeyResponse.body?.bytes() ?: byteArrayOf() } @@ -187,7 +189,7 @@ class M3u8Helper { while (lastYield != c) { try { - val tsResponse = app.get(url, headers = headers) + val tsResponse = runBlocking { app.get(url, headers = headers) } var tsData = tsResponse.body?.bytes() ?: byteArrayOf() if (encryptionState) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt index bb48e246..2e5a0340 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt @@ -5,13 +5,12 @@ import com.fasterxml.jackson.module.kotlin.readValue import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mapper import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.network.text import java.util.concurrent.TimeUnit object SyncUtil { /** first. Mal, second. Anilist, * valid sites are: Gogoanime, Twistmoe and 9anime*/ - fun getIdsFromSlug(slug: String, site : String = "Gogoanime"): Pair? { + suspend fun getIdsFromSlug(slug: String, site : String = "Gogoanime"): Pair? { try { //Gogoanime, Twistmoe and 9anime val url = diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 971a1614..5c7bf137 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -81,6 +81,59 @@ app:tint="?attr/textColor" android:contentDescription="@string/change_providers_img_des" /> + + + + + + + + + + + + + + + + ? = null @@ -144,7 +144,7 @@ class ProviderTests { @Test fun providerCorrectHomepage() { - getAllProviders().pmap { api -> + getAllProviders().apmap { api -> if (api.hasMainPage) { try { val homepage = api.getMainPage() @@ -175,10 +175,10 @@ class ProviderTests { // } @Test - fun providerCorrect() { + suspend fun providerCorrect() { val invalidProvider = ArrayList>() val providers = getAllProviders() - providers.pmap { api -> + providers.apmap { api -> try { println("Trying $api") if (testSingleProviderApi(api)) {