forked from recloudstream/cloudstream
		
	move to async get requests + remade search
This commit is contained in:
		
							parent
							
								
									06801ba6ab
								
							
						
					
					
						commit
						302185093c
					
				
					 53 changed files with 497 additions and 360 deletions
				
			
		|  | @ -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") | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 <T, R> Iterable<T>.pmap( | ||||
|     numThreads: Int = maxOf(Runtime.getRuntime().availableProcessors() - 2, 1), | ||||
|     exec: ExecutorService = Executors.newFixedThreadPool(numThreads), | ||||
|  | @ -27,14 +23,14 @@ fun <T, R> Iterable<T>.pmap( | |||
|     exec.awaitTermination(1, TimeUnit.DAYS) | ||||
| 
 | ||||
|     return ArrayList<R>(destination) | ||||
| } | ||||
| }*/ | ||||
| 
 | ||||
| fun <A, B> List<A>.apmap(f: suspend (A) -> B): List<B> = runBlocking { | ||||
|     map { async { f(it) } }.map { it.await() } | ||||
| } | ||||
| 
 | ||||
| // run code in parallel | ||||
| fun <R> argpmap( | ||||
| /*fun <R> argpmap( | ||||
|     vararg transforms: () -> R, | ||||
|     numThreads: Int = maxOf(Runtime.getRuntime().availableProcessors() - 2, 1), | ||||
|     exec: ExecutorService = Executors.newFixedThreadPool(numThreads) | ||||
|  | @ -45,10 +41,10 @@ fun <R> argpmap( | |||
| 
 | ||||
|     exec.shutdown() | ||||
|     exec.awaitTermination(1, TimeUnit.DAYS) | ||||
| } | ||||
| }*/ | ||||
| 
 | ||||
| //fun <R> argamap( | ||||
| //    vararg transforms: () -> R, | ||||
| //) = runBlocking { | ||||
| //    transforms.map { async { it.invoke() } }.map { it.await() } | ||||
| //} | ||||
| fun <R> argamap( | ||||
|     vararg transforms: suspend () -> R, | ||||
| ) = runBlocking { | ||||
|     transforms.map { async { it.invoke() } }.map { it.await() } | ||||
| } | ||||
|  | @ -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<AnimePaheSearchData> | ||||
|     ) | ||||
| 
 | ||||
|     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<AnimeEpisode> { | ||||
|     private suspend fun generateListOfEpisodes(link: String): ArrayList<AnimeEpisode> { | ||||
|         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<Map<String, VideoQuality>> | ||||
|     ) | ||||
| 
 | ||||
|     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<ExtractorLink> { | ||||
|     private suspend fun extractVideoLinks(episodeLink: String): List<ExtractorLink> { | ||||
|         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<AnimePaheAnimeData>(it) } | ||||
|             val ep = ((jsonResponse.data.map { | ||||
|  |  | |||
|  | @ -54,7 +54,7 @@ class DubbedAnimeProvider : MainAPI() { | |||
|         @JsonProperty("tags") val tags: String,*/ | ||||
|     ) | ||||
| 
 | ||||
|     private fun parseDocumentTrending(url: String): List<SearchResponse> { | ||||
|     private suspend fun parseDocumentTrending(url: String): List<SearchResponse> { | ||||
|         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<SearchResponse> { | ||||
|     private suspend fun parseDocument(url: String, trimEpisode: Boolean = false): List<SearchResponse> { | ||||
|         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 | ||||
|  |  | |||
|  | @ -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) | ||||
|                     } | ||||
|                 }) | ||||
|  |  | |||
|  | @ -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("<html")) return@pmap | ||||
|                 if (response.contains("<html")) return@apmap | ||||
|                 val mapped = mapper.readValue<SflixProvider.SourceObject>(response) | ||||
| 
 | ||||
|                 mapped.tracks?.forEach { track -> | ||||
|  |  | |||
|  | @ -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<ExtractorLink> { | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||
|         with(app.get(url, referer = referer)) { | ||||
|             sourceRegex.findAll(this.text).forEach { sourceMatch -> | ||||
|  |  | |||
|  | @ -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<ExtractorLink>? { | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         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) | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ open class Evoload : ExtractorApi() { | |||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     override fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         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) | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ class MixDrop : ExtractorApi() { | |||
|         return "$mainUrl/e/$id" | ||||
|     } | ||||
| 
 | ||||
|     override fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         with(app.get(url)) { | ||||
|             getAndUnpack(this.text).let { unpackedText -> | ||||
|                 srcRegex.find(unpackedText)?.groupValues?.get(1)?.let { link -> | ||||
|  |  | |||
|  | @ -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<ExtractorLink>? { | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         with(app.get(url)) { | ||||
|             getAndUnpack(this.text).let { unpackedText -> | ||||
|                 srcRegex.find(unpackedText)?.groupValues?.get(1)?.let { link -> | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ class MultiQuality : ExtractorApi() { | |||
|         return "$mainUrl/loadserver.php?id=$id" | ||||
|     } | ||||
| 
 | ||||
|     override fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||
|         with(app.get(url)) { | ||||
|             sourceRegex.findAll(this.text).forEach { sourceMatch -> | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ open class SBPlay : ExtractorApi() { | |||
|     override val name = "SBPlay" | ||||
|     override val requiresReferer = false | ||||
| 
 | ||||
|     override fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val response = app.get(url, referer = referer).text | ||||
|         val document = Jsoup.parse(response) | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<ExtractorLink> { | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||
|         val newUrl = url.replace("sbplay.org/embed-", "sbplay.org/play/").removeSuffix(".html") | ||||
|         with(app.get(newUrl, timeout = 10)) { | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ class StreamTape : ExtractorApi() { | |||
|     private val linkRegex = | ||||
|         Regex("""'robotlink'\)\.innerHTML = '(.+?)'\+ \('(.+?)'\)""") | ||||
| 
 | ||||
|     override fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         with(app.get(url)) { | ||||
|             linkRegex.find(this.text)?.let { | ||||
|                 val extractedUrl = "https:${it.groups[1]!!.value + it.groups[2]!!.value.substring(3,)}" | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ class Streamhub : ExtractorApi() { | |||
|         return "$mainUrl/e/$id" | ||||
|     } | ||||
| 
 | ||||
|     override fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         val response = app.get(url).text | ||||
|         Regex("eval((.|\\n)*?)</script>").find(response)?.groupValues?.get(1)?.let { jsEval -> | ||||
|             JsUnpacker("eval$jsEval").unpack()?.let { unPacked -> | ||||
|  |  | |||
|  | @ -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<ExtractorLink> { | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         // WIP: m3u8 link fetched but sometimes not playing | ||||
|         //Log.i(this.name, "Result => (no extractor) ${url}") | ||||
|         val sources: MutableList<ExtractorLink> = mutableListOf() | ||||
|  |  | |||
|  | @ -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<ExtractorLink>? { | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { | ||||
|         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( | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ open class VoeExtractor : ExtractorApi() { | |||
|         //val type: String // Mp4 | ||||
|     ) | ||||
| 
 | ||||
|     override fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||
|         val doc = app.get(url).text | ||||
|         if (doc.isNotEmpty()) { | ||||
|  |  | |||
|  | @ -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<ExtractorLink> { | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val response = app.get( | ||||
|             url, interceptor = WebViewResolver( | ||||
|                 Regex("""master\.m3u8""") | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ class WcoStream : ExtractorApi() { | |||
|     override val requiresReferer = false | ||||
|     private val hlsHelper = M3u8Helper() | ||||
| 
 | ||||
|     override fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val baseUrl = url.split("/e/")[0] | ||||
| 
 | ||||
|         val html = app.get(url, headers = mapOf("Referer" to "https://wcostream.cc/")).text | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ open class XStreamCdn : ExtractorApi() { | |||
|         return "$domainUrl/api/source/$id" | ||||
|     } | ||||
| 
 | ||||
|     override fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|     override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> { | ||||
|         val headers = mapOf( | ||||
|             "Referer" to url, | ||||
|             "User-Agent" to "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0", | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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") | ||||
|  |  | |||
|  | @ -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") | ||||
|  |  | |||
|  | @ -110,7 +110,7 @@ class LookMovieProvider : MainAPI() { | |||
|     } | ||||
| 
 | ||||
|     override suspend fun search(query: String): List<SearchResponse> { | ||||
|         fun search(query: String, isMovie: Boolean): ArrayList<SearchResponse> { | ||||
|         suspend fun search(query: String, isMovie: Boolean): ArrayList<SearchResponse> { | ||||
|             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( | ||||
|  |  | |||
|  | @ -151,7 +151,7 @@ open class PelisplusProviderTemplate : MainAPI() { | |||
|         val urls = homePageUrlList | ||||
|         val homePageList = ArrayList<HomePageList>() | ||||
|         // .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 -> | ||||
|  |  | |||
|  | @ -228,7 +228,7 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() { | |||
|             } | ||||
|         } ?: tryParseJson<List<String>>(data))?.distinct() | ||||
| 
 | ||||
|         urls?.pmap { url -> | ||||
|         urls?.apmap { url -> | ||||
|             val sources = app.get( | ||||
|                 url, | ||||
|                 interceptor = WebViewResolver( | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -148,7 +148,7 @@ open class VidstreamProviderTemplate : MainAPI() { | |||
|         val urls = homePageUrlList | ||||
|         val homePageList = ArrayList<HomePageList>() | ||||
|         // .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 -> | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -51,6 +51,15 @@ fun <T> normalSafeApiCall(apiCall: () -> T): T? { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| suspend fun <T> suspendSafeApiCall(apiCall: suspend () -> T): T? { | ||||
|     return try { | ||||
|         apiCall.invoke() | ||||
|     } catch (throwable: Throwable) { | ||||
|         logError(throwable) | ||||
|         return null | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fun <T> safeFail(throwable: Throwable): Resource<T> { | ||||
|     val stackTraceMsg = (throwable.localizedMessage ?: "") + "\n\n" + throwable.stackTrace.joinToString( | ||||
|         separator = "\n" | ||||
|  |  | |||
|  | @ -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() | ||||
|     } | ||||
| } | ||||
|  | @ -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<Response> | ||||
|     ) : 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<String, String> = 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<String, String> = 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<String, String> = 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) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -76,7 +76,7 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List<Regex> = | |||
|                 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<Regex> = | |||
|                         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<Regex> = | |||
|                      *  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<Regex> = | |||
|                                 webViewUrl, | ||||
|                                 headers = request.requestHeaders | ||||
|                             ).response.toWebResourceResponse() | ||||
|                             else -> return super.shouldInterceptRequest(view, request) | ||||
|                             else -> return@runBlocking super.shouldInterceptRequest(view, request) | ||||
|                         } | ||||
|                     } catch (e: Exception) { | ||||
|                         null | ||||
|  |  | |||
|  | @ -66,7 +66,7 @@ interface SyncAPI : OAuth2API { | |||
|     val icon: Int | ||||
| 
 | ||||
|     val mainUrl: String | ||||
|     fun search(name: String): List<SyncSearchResult>? | ||||
|     suspend fun search(name: String): List<SyncSearchResult>? | ||||
| 
 | ||||
|     /** | ||||
|     -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? | ||||
| } | ||||
|  | @ -75,7 +75,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     override fun search(name: String): List<SyncAPI.SyncSearchResult>? { | ||||
|     override suspend fun search(name: String): List<SyncAPI.SyncSearchResult>? { | ||||
|         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<Lists> | ||||
|     } | ||||
| 
 | ||||
|     fun getAnilistAnimeListSmart(): Array<Lists>? { | ||||
|     suspend fun getAnilistAnimeListSmart(): Array<Lists>? { | ||||
|         if (getKey<String>( | ||||
|                 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<SeasonResponse?> { | ||||
|     suspend fun getAllSeasons(id: Int): List<SeasonResponse?> { | ||||
|         val seasons = mutableListOf<SeasonResponse?>() | ||||
|         fun getSeasonRecursive(id: Int) { | ||||
|         suspend fun getSeasonRecursive(id: Int) { | ||||
|             val season = getSeason(id) | ||||
|             if (season != null) { | ||||
|                 seasons.add(season) | ||||
|  |  | |||
|  | @ -50,7 +50,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { | |||
|         return null | ||||
|     } | ||||
| 
 | ||||
|     override fun search(name: String): List<SyncAPI.SyncSearchResult> { | ||||
|     override suspend fun search(name: String): List<SyncAPI.SyncSearchResult> { | ||||
|         val url = "https://api.myanimelist.net/v2/anime?q=$name&limit=$MAL_MAX_SEARCH_LIMIT" | ||||
|         val auth = getKey<String>( | ||||
|             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<Data> | ||||
|     } | ||||
| 
 | ||||
|     fun getMalAnimeListSmart(): Array<Data>? { | ||||
|     suspend fun getMalAnimeListSmart(): Array<Data>? { | ||||
|         if (getKey<String>( | ||||
|                 accountId, | ||||
|                 MAL_TOKEN_KEY | ||||
|  | @ -299,7 +299,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun getMalAnimeList(): Array<Data>? { | ||||
|     private suspend fun getMalAnimeList(): Array<Data>? { | ||||
|         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<String>( | ||||
|             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, | ||||
|  |  | |||
|  | @ -8,8 +8,6 @@ import com.lagradost.cloudstream3.utils.ExtractorLink | |||
| 
 | ||||
| class APIRepository(val api: MainAPI) { | ||||
|     companion object { | ||||
|         var providersActive = HashSet<String>() | ||||
|         var typesActive = HashSet<TvType>() | ||||
|         var dubStatusActive = HashSet<DubStatus>() | ||||
| 
 | ||||
|         val noneApi = object : MainAPI() { | ||||
|  |  | |||
|  | @ -138,6 +138,22 @@ class HomeFragment : Fragment() { | |||
|             bottomSheetDialogBuilder.show() | ||||
|         } | ||||
| 
 | ||||
|         fun getPairList( | ||||
|             anime: MaterialButton?, | ||||
|             cartoons: MaterialButton?, | ||||
|             tvs: MaterialButton?, | ||||
|             docs: MaterialButton?, | ||||
|             movies: MaterialButton? | ||||
|         ): List<Pair<MaterialButton?, List<TvType>>> { | ||||
|             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<MaterialButton>(R.id.cancel_btt) | ||||
|                 val applyBtt = dialog.findViewById<MaterialButton>(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) | ||||
|  |  | |||
|  | @ -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<TvType>() | ||||
|     var selectedApis = mutableSetOf<String>() | ||||
| 
 | ||||
|     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<ImageView>(androidx.appcompat.R.id.search_close_btn) | ||||
|         val searchMagIcon = main_search.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon) | ||||
|         val searchExitIcon = | ||||
|             main_search.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn) | ||||
|         val searchMagIcon = | ||||
|             main_search.findViewById<ImageView>(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<MainAPI>() | ||||
|                 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<MaterialButton>(R.id.home_select_anime) | ||||
|                     val cartoons = dialog.findViewById<MaterialButton>(R.id.home_select_cartoons) | ||||
|                     val tvs = dialog.findViewById<MaterialButton>(R.id.home_select_tv_series) | ||||
|                     val docs = dialog.findViewById<MaterialButton>(R.id.home_select_documentaries) | ||||
|                     val movies = dialog.findViewById<MaterialButton>(R.id.home_select_movies) | ||||
|                     val cancelBtt = dialog.findViewById<MaterialButton>(R.id.cancel_btt) | ||||
|                     val applyBtt = dialog.findViewById<MaterialButton>(R.id.apply_btt) | ||||
| 
 | ||||
|                 val listView = dialog.findViewById<ListView>(R.id.listview1)!! | ||||
|                 val listView2 = dialog.findViewById<ListView>(R.id.listview2)!! | ||||
|                 val toggle = dialog.findViewById<SwitchMaterial>(R.id.toggle1)!! | ||||
|                 val applyButton = dialog.findViewById<TextView>(R.id.apply_btt)!! | ||||
|                 val cancelButton = dialog.findViewById<TextView>(R.id.cancel_btt)!! | ||||
|                 // val applyHolder = dialog.findViewById<LinearLayout>(R.id.apply_btt_holder)!! | ||||
|                     val pairList = HomeFragment.getPairList(anime, cartoons, tvs, docs, movies) | ||||
| 
 | ||||
|                 val arrayAdapter = ArrayAdapter<String>(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<String>(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<TvType>() | ||||
|                     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<List<String>>(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<ListView>(R.id.listview1) | ||||
|                     val arrayAdapter = ArrayAdapter<String>(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<TvType>() | ||||
|                     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<List<String>>(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<String>() | ||||
|                     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<RecyclerView.ViewHolder> = ParentItemAdapter(listOf(), { callback -> | ||||
|             SearchHelper.handleSearchClickCallback(activity, callback) | ||||
|         }, { item -> | ||||
|             activity?.loadHomepageList(item) | ||||
|         }) | ||||
|         val masterAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = | ||||
|             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") | ||||
|  |  | |||
|  | @ -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<Resource<ArrayList<SearchResponse>>> = MutableLiveData() | ||||
|     private val _searchResponse: MutableLiveData<Resource<ArrayList<SearchResponse>>> = | ||||
|         MutableLiveData() | ||||
|     val searchResponse: LiveData<Resource<ArrayList<SearchResponse>>> get() = _searchResponse | ||||
| 
 | ||||
|     private val _currentSearch: MutableLiveData<ArrayList<OnGoingSearch>> = 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<String> = 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<String>, | ||||
|         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<SearchResponse>() | ||||
|             val nestedList = | ||||
|                 currentList.map { it.data }.filterIsInstance<Resource.Success<List<SearchResponse>>>().map { it.value } | ||||
|                 currentList.map { it.data } | ||||
|                     .filterIsInstance<Resource.Success<List<SearchResponse>>>().map { it.value } | ||||
| 
 | ||||
|             // I do it this way to move the relevant search results to the top | ||||
|             var index = 0 | ||||
|  |  | |||
|  | @ -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() | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<ExtractorLink>? { | ||||
|         return normalSafeApiCall { getUrl(url, referer) } | ||||
|     suspend fun getSafeUrl(url: String, referer: String? = null): List<ExtractorLink>? { | ||||
|         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<ExtractorLink>? | ||||
|     abstract suspend fun getUrl(url: String, referer: String? = null): List<ExtractorLink>? | ||||
| 
 | ||||
|     open fun getExtractorUrl(id: String): String { | ||||
|         return id | ||||
|  |  | |||
|  | @ -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<Int, Boolean>? { | ||||
|     suspend fun getFillerEpisodes(query: String): HashMap<Int, Boolean>? { | ||||
|         try { | ||||
|             if (!getFillerList()) return null | ||||
|             val localList = list ?: return null | ||||
|  |  | |||
|  | @ -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<List<GithubRelease>>(app.get(url, headers = headers).text) | ||||
|                 mapper.readValue<List<GithubRelease>>(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, | ||||
|  |  | |||
|  | @ -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<M3u8Stream> { | ||||
|         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) { | ||||
|  |  | |||
|  | @ -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<String?, String?>? { | ||||
|     suspend fun getIdsFromSlug(slug: String, site : String = "Gogoanime"): Pair<String?, String?>? { | ||||
|         try { | ||||
|             //Gogoanime, Twistmoe and 9anime | ||||
|             val url = | ||||
|  |  | |||
|  | @ -81,6 +81,59 @@ | |||
|                 app:tint="?attr/textColor" | ||||
|                 android:contentDescription="@string/change_providers_img_des" /> | ||||
|     </FrameLayout> | ||||
|     <HorizontalScrollView | ||||
|             android:paddingStart="10dp" | ||||
|             android:paddingEnd="10dp" | ||||
|             android:fadingEdge="horizontal" | ||||
|             android:requiresFadingEdge="horizontal" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content"> | ||||
| 
 | ||||
|         <LinearLayout | ||||
|                 android:orientation="horizontal" | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content"> | ||||
| 
 | ||||
|             <com.google.android.material.button.MaterialButton | ||||
|                     android:nextFocusRight="@id/search_select_tv_series" | ||||
| 
 | ||||
|                     android:id="@+id/search_select_movies" | ||||
|                     android:text="@string/movies" | ||||
|                     style="@style/RoundedSelectableButton" /> | ||||
| 
 | ||||
|             <com.google.android.material.button.MaterialButton | ||||
|                     android:nextFocusLeft="@id/search_select_movies" | ||||
|                     android:nextFocusRight="@id/search_select_anime" | ||||
| 
 | ||||
|                     android:id="@+id/search_select_tv_series" | ||||
|                     android:text="@string/tv_series" | ||||
|                     style="@style/RoundedSelectableButton" /> | ||||
| 
 | ||||
|             <com.google.android.material.button.MaterialButton | ||||
|                     android:nextFocusLeft="@id/search_select_tv_series" | ||||
|                     android:nextFocusRight="@id/search_select_cartoons" | ||||
| 
 | ||||
|                     android:id="@+id/search_select_anime" | ||||
|                     android:text="@string/anime" | ||||
|                     style="@style/RoundedSelectableButton" /> | ||||
| 
 | ||||
|             <com.google.android.material.button.MaterialButton | ||||
|                     android:nextFocusLeft="@id/search_select_anime" | ||||
|                     android:nextFocusRight="@id/search_select_documentaries" | ||||
| 
 | ||||
|                     android:id="@+id/search_select_cartoons" | ||||
|                     android:text="@string/cartoons" | ||||
|                     style="@style/RoundedSelectableButton" /> | ||||
| 
 | ||||
|             <com.google.android.material.button.MaterialButton | ||||
|                     android:nextFocusLeft="@id/search_select_cartoons" | ||||
| 
 | ||||
|                     android:id="@+id/search_select_documentaries" | ||||
|                     android:text="@string/documentaries" | ||||
|                     style="@style/RoundedSelectableButton" /> | ||||
|         </LinearLayout> | ||||
|     </HorizontalScrollView> | ||||
| 
 | ||||
| 
 | ||||
|     <com.lagradost.cloudstream3.ui.AutofitRecyclerView | ||||
|             android:nextFocusLeft="@id/nav_rail_view" | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ class ProviderTests { | |||
|         return allApis.filter { !it.usesWebView } | ||||
|     } | ||||
| 
 | ||||
|     private fun loadLinks(api: MainAPI, url: String?): Boolean { | ||||
|     private suspend fun loadLinks(api: MainAPI, url: String?): Boolean { | ||||
|         Assert.assertNotNull("Api ${api.name} has invalid url on episode", url) | ||||
|         if (url == null) return true | ||||
|         var linksLoaded = 0 | ||||
|  | @ -39,7 +39,7 @@ class ProviderTests { | |||
|         return true | ||||
|     } | ||||
| 
 | ||||
|     private fun testSingleProviderApi(api: MainAPI): Boolean { | ||||
|     private suspend fun testSingleProviderApi(api: MainAPI): Boolean { | ||||
|         val searchQueries = listOf("over", "iron", "guy") | ||||
|         var correctResponses = 0 | ||||
|         var searchResult: List<SearchResponse>? = 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<Pair<MainAPI,Exception?>>() | ||||
|         val providers = getAllProviders() | ||||
|         providers.pmap { api -> | ||||
|         providers.apmap { api -> | ||||
|             try { | ||||
|                 println("Trying $api") | ||||
|                 if (testSingleProviderApi(api)) { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue