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 android.widget.Toast | ||||||
| import com.google.auto.service.AutoService | import com.google.auto.service.AutoService | ||||||
| import com.lagradost.cloudstream3.mvvm.normalSafeApiCall | import com.lagradost.cloudstream3.mvvm.normalSafeApiCall | ||||||
|  | import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall | ||||||
| import com.lagradost.cloudstream3.utils.AppUtils.openBrowser | import com.lagradost.cloudstream3.utils.AppUtils.openBrowser | ||||||
| import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread | import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread | ||||||
| import com.lagradost.cloudstream3.utils.DataStore.getKey | 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.removeKey | ||||||
| import com.lagradost.cloudstream3.utils.DataStore.removeKeys | import com.lagradost.cloudstream3.utils.DataStore.removeKeys | ||||||
| import com.lagradost.cloudstream3.utils.DataStore.setKey | import com.lagradost.cloudstream3.utils.DataStore.setKey | ||||||
|  | import kotlinx.coroutines.runBlocking | ||||||
| import org.acra.ReportField | import org.acra.ReportField | ||||||
| import org.acra.config.CoreConfiguration | import org.acra.config.CoreConfiguration | ||||||
| import org.acra.data.CrashReportData | import org.acra.data.CrashReportData | ||||||
|  | @ -33,11 +35,13 @@ class CustomReportSender : ReportSender { | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         thread { // to not run it on main thread |         thread { // to not run it on main thread | ||||||
|             normalSafeApiCall { |             runBlocking { | ||||||
|  |                 suspendSafeApiCall { | ||||||
|                     val post = app.post(url, data = data) |                     val post = app.post(url, data = data) | ||||||
|                     println("Report response: $post") |                     println("Report response: $post") | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         runOnMainThread { // to run it on main looper |         runOnMainThread { // to run it on main looper | ||||||
|             normalSafeApiCall { |             normalSafeApiCall { | ||||||
|  |  | ||||||
|  | @ -2,13 +2,9 @@ package com.lagradost.cloudstream3 | ||||||
| 
 | 
 | ||||||
| import kotlinx.coroutines.async | import kotlinx.coroutines.async | ||||||
| import kotlinx.coroutines.runBlocking | 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 | //https://stackoverflow.com/questions/34697828/parallel-operations-on-kotlin-collections | ||||||
|  | /* | ||||||
| fun <T, R> Iterable<T>.pmap( | fun <T, R> Iterable<T>.pmap( | ||||||
|     numThreads: Int = maxOf(Runtime.getRuntime().availableProcessors() - 2, 1), |     numThreads: Int = maxOf(Runtime.getRuntime().availableProcessors() - 2, 1), | ||||||
|     exec: ExecutorService = Executors.newFixedThreadPool(numThreads), |     exec: ExecutorService = Executors.newFixedThreadPool(numThreads), | ||||||
|  | @ -27,14 +23,14 @@ fun <T, R> Iterable<T>.pmap( | ||||||
|     exec.awaitTermination(1, TimeUnit.DAYS) |     exec.awaitTermination(1, TimeUnit.DAYS) | ||||||
| 
 | 
 | ||||||
|     return ArrayList<R>(destination) |     return ArrayList<R>(destination) | ||||||
| } | }*/ | ||||||
| 
 | 
 | ||||||
| fun <A, B> List<A>.apmap(f: suspend (A) -> B): List<B> = runBlocking { | fun <A, B> List<A>.apmap(f: suspend (A) -> B): List<B> = runBlocking { | ||||||
|     map { async { f(it) } }.map { it.await() } |     map { async { f(it) } }.map { it.await() } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // run code in parallel | // run code in parallel | ||||||
| fun <R> argpmap( | /*fun <R> argpmap( | ||||||
|     vararg transforms: () -> R, |     vararg transforms: () -> R, | ||||||
|     numThreads: Int = maxOf(Runtime.getRuntime().availableProcessors() - 2, 1), |     numThreads: Int = maxOf(Runtime.getRuntime().availableProcessors() - 2, 1), | ||||||
|     exec: ExecutorService = Executors.newFixedThreadPool(numThreads) |     exec: ExecutorService = Executors.newFixedThreadPool(numThreads) | ||||||
|  | @ -45,10 +41,10 @@ fun <R> argpmap( | ||||||
| 
 | 
 | ||||||
|     exec.shutdown() |     exec.shutdown() | ||||||
|     exec.awaitTermination(1, TimeUnit.DAYS) |     exec.awaitTermination(1, TimeUnit.DAYS) | ||||||
| } | }*/ | ||||||
| 
 | 
 | ||||||
| //fun <R> argamap( | fun <R> argamap( | ||||||
| //    vararg transforms: () -> R, |     vararg transforms: suspend () -> R, | ||||||
| //) = runBlocking { | ) = runBlocking { | ||||||
| //    transforms.map { async { it.invoke() } }.map { it.await() } |     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.annotation.JsonProperty | ||||||
| import com.fasterxml.jackson.module.kotlin.readValue | import com.fasterxml.jackson.module.kotlin.readValue | ||||||
| import com.lagradost.cloudstream3.* | 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.network.AppResponse | ||||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
| import com.lagradost.cloudstream3.utils.getQualityFromName | import com.lagradost.cloudstream3.utils.getQualityFromName | ||||||
|  | @ -23,7 +23,7 @@ class AnimePaheProvider : MainAPI() { | ||||||
|             else TvType.Anime |             else TvType.Anime | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         fun generateSession(): Boolean { |         suspend fun generateSession(): Boolean { | ||||||
|             if (cookies.isNotEmpty()) return true |             if (cookies.isNotEmpty()) return true | ||||||
|             return try { |             return try { | ||||||
|                 val response = app.get("$MAIN_URL/") |                 val response = app.get("$MAIN_URL/") | ||||||
|  | @ -124,7 +124,7 @@ class AnimePaheProvider : MainAPI() { | ||||||
|         @JsonProperty("data") val data: List<AnimePaheSearchData> |         @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 url = "$mainUrl/api?m=search&l=8&q=$title" | ||||||
|         val headers = mapOf("referer" to "$mainUrl/") |         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 { |         try { | ||||||
|             val attrs = link.split('/') |             val attrs = link.split('/') | ||||||
|             val id = attrs[attrs.size - 1].split("?")[0] |             val id = attrs[attrs.size - 1].split("?")[0] | ||||||
|  | @ -243,8 +243,7 @@ class AnimePaheProvider : MainAPI() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override suspend fun load(url: String): LoadResponse? { |     override suspend fun load(url: String): LoadResponse? { | ||||||
|         return normalSafeApiCall { |         return suspendSafeApiCall { | ||||||
| 
 |  | ||||||
|             val regex = Regex("""a/(\d+)\?slug=(.+)""") |             val regex = Regex("""a/(\d+)\?slug=(.+)""") | ||||||
|             val (animeId, animeTitle) = regex.find(url)!!.destructured |             val (animeId, animeTitle) = regex.find(url)!!.destructured | ||||||
|             val link = getAnimeByIdAndTitle(animeTitle, animeId.toInt())!! |             val link = getAnimeByIdAndTitle(animeTitle, animeId.toInt())!! | ||||||
|  | @ -436,7 +435,7 @@ class AnimePaheProvider : MainAPI() { | ||||||
|         @JsonProperty("data") val data: List<Map<String, VideoQuality>> |         @JsonProperty("data") val data: List<Map<String, VideoQuality>> | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     private fun bypassAdfly(adflyUri: String): String { |     private suspend fun bypassAdfly(adflyUri: String): String { | ||||||
|         if (!generateSession()) { |         if (!generateSession()) { | ||||||
|             return bypassAdfly(adflyUri) |             return bypassAdfly(adflyUri) | ||||||
|         } |         } | ||||||
|  | @ -461,7 +460,7 @@ class AnimePaheProvider : MainAPI() { | ||||||
|         return decodeAdfly(YTSM.find(adflyContent?.text.toString())!!.destructured.component1()) |         return decodeAdfly(YTSM.find(adflyContent?.text.toString())!!.destructured.component1()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun getStreamUrlFromKwik(adflyUri: String): String { |     private suspend fun getStreamUrlFromKwik(adflyUri: String): String { | ||||||
|         val fContent = |         val fContent = | ||||||
|             app.get( |             app.get( | ||||||
|                 bypassAdfly(adflyUri), |                 bypassAdfly(adflyUri), | ||||||
|  | @ -496,7 +495,7 @@ class AnimePaheProvider : MainAPI() { | ||||||
|         return content?.headers?.values("location").toString() |         return content?.headers?.values("location").toString() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun extractVideoLinks(episodeLink: String): List<ExtractorLink> { |     private suspend fun extractVideoLinks(episodeLink: String): List<ExtractorLink> { | ||||||
|         var link = episodeLink |         var link = episodeLink | ||||||
|         val headers = mapOf("referer" to "$mainUrl/") |         val headers = mapOf("referer" to "$mainUrl/") | ||||||
| 
 | 
 | ||||||
|  | @ -507,7 +506,6 @@ class AnimePaheProvider : MainAPI() { | ||||||
|             val episodeNum = regex.find(link)?.destructured?.component1()?.toIntOrNull() |             val episodeNum = regex.find(link)?.destructured?.component1()?.toIntOrNull() | ||||||
|             link = link.replace(regex, "") |             link = link.replace(regex, "") | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|             val req = app.get(link, headers = headers).text |             val req = app.get(link, headers = headers).text | ||||||
|             val jsonResponse = req.let { mapper.readValue<AnimePaheAnimeData>(it) } |             val jsonResponse = req.let { mapper.readValue<AnimePaheAnimeData>(it) } | ||||||
|             val ep = ((jsonResponse.data.map { |             val ep = ((jsonResponse.data.map { | ||||||
|  |  | ||||||
|  | @ -54,7 +54,7 @@ class DubbedAnimeProvider : MainAPI() { | ||||||
|         @JsonProperty("tags") val tags: String,*/ |         @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 response = app.get(url).text | ||||||
|         val document = Jsoup.parse(response) |         val document = Jsoup.parse(response) | ||||||
|         return document.select("li > a").map { |         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 response = app.get(url).text | ||||||
|         val document = Jsoup.parse(response) |         val document = Jsoup.parse(response) | ||||||
|         return document.select("a.grid__link").map { |         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 = |         val url = | ||||||
|             mainUrl + (if (isMovie) "/movies/jsonMovie" else "/xz/v3/jsonEpi") + ".php?slug=$slug&_=$unixTime" |             mainUrl + (if (isMovie) "/movies/jsonMovie" else "/xz/v3/jsonEpi") + ".php?slug=$slug&_=$unixTime" | ||||||
|         val response = app.get(url).text |         val response = app.get(url).text | ||||||
|  |  | ||||||
|  | @ -232,17 +232,17 @@ class GogoanimeProvider : MainAPI() { | ||||||
|         val default: String? = null |         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 doc = app.get(uri).document | ||||||
| 
 | 
 | ||||||
|         val iframe = fixUrlNull(doc.selectFirst("div.play-video > iframe").attr("src")) ?: return |         val iframe = fixUrlNull(doc.selectFirst("div.play-video > iframe").attr("src")) ?: return | ||||||
| 
 | 
 | ||||||
|         argpmap( |         argamap( | ||||||
|             { |             { | ||||||
|                 val link = iframe.replace("streaming.php", "download") |                 val link = iframe.replace("streaming.php", "download") | ||||||
|                 val page = app.get(link, headers = mapOf("Referer" to iframe)) |                 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")) { |                     if (it.hasAttr("download")) { | ||||||
|                         val qual = if (it.text() |                         val qual = if (it.text() | ||||||
|                                 .contains("HDP") |                                 .contains("HDP") | ||||||
|  | @ -266,7 +266,7 @@ class GogoanimeProvider : MainAPI() { | ||||||
|             }, { |             }, { | ||||||
|                 val streamingResponse = app.get(iframe, headers = mapOf("Referer" to iframe)) |                 val streamingResponse = app.get(iframe, headers = mapOf("Referer" to iframe)) | ||||||
|                 val streamingDocument = streamingResponse.document |                 val streamingDocument = streamingResponse.document | ||||||
|                 argpmap({ |                 argamap({ | ||||||
|                     streamingDocument.select(".list-server-items > .linkserver") |                     streamingDocument.select(".list-server-items > .linkserver") | ||||||
|                         ?.forEach { element -> |                         ?.forEach { element -> | ||||||
|                             val status = element.attr("data-status") ?: return@forEach |                             val status = element.attr("data-status") ?: return@forEach | ||||||
|  | @ -302,7 +302,7 @@ class GogoanimeProvider : MainAPI() { | ||||||
|                         sourceCallback.invoke( |                         sourceCallback.invoke( | ||||||
|                             ExtractorLink( |                             ExtractorLink( | ||||||
|                                 this.name, |                                 this.name, | ||||||
|                                 "${this.name} ${source.label?.replace("0 P","0p") ?: ""}", |                                 "${this.name} ${source.label?.replace("0 P", "0p") ?: ""}", | ||||||
|                                 source.file, |                                 source.file, | ||||||
|                                 "", |                                 "", | ||||||
|                                 getQualityFromName(source.label ?: ""), |                                 getQualityFromName(source.label ?: ""), | ||||||
|  | @ -312,11 +312,9 @@ class GogoanimeProvider : MainAPI() { | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     sources.source?.forEach { |                     sources.source?.forEach { | ||||||
|                         println("${this.name} ${it.label ?: ""}") |  | ||||||
|                         invokeGogoSource(it, callback) |                         invokeGogoSource(it, callback) | ||||||
|                     } |                     } | ||||||
|                     sources.sourceBk?.forEach { |                     sources.sourceBk?.forEach { | ||||||
|                         println("${this.name} ${it.label ?: ""}") |  | ||||||
|                         invokeGogoSource(it, callback) |                         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 { |         return Regex("""/(embed-\d+)/(.*?)\?z=""").find(url)?.groupValues?.let { | ||||||
|             val jsonLink = "https://rapid-cloud.ru/ajax/${it[1]}/getSources?id=${it[2]}" |             val jsonLink = "https://rapid-cloud.ru/ajax/${it[1]}/getSources?id=${it[2]}" | ||||||
|             app.get(jsonLink).text |             app.get(jsonLink).text | ||||||
|  | @ -295,7 +295,7 @@ class ZoroProvider : MainAPI() { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Prevent duplicates |         // Prevent duplicates | ||||||
|         servers.distinctBy { it.second }.pmap { |         servers.distinctBy { it.second }.apmap { | ||||||
|             val link = |             val link = | ||||||
|                 "$mainUrl/ajax/v2/episode/sources?id=${it.second}" |                 "$mainUrl/ajax/v2/episode/sources?id=${it.second}" | ||||||
|             val extractorLink = app.get( |             val extractorLink = app.get( | ||||||
|  | @ -316,7 +316,7 @@ class ZoroProvider : MainAPI() { | ||||||
|                         extractorLink |                         extractorLink | ||||||
|                     ) |                     ) | ||||||
| 
 | 
 | ||||||
|                 if (response.contains("<html")) return@pmap |                 if (response.contains("<html")) return@apmap | ||||||
|                 val mapped = mapper.readValue<SflixProvider.SourceObject>(response) |                 val mapped = mapper.readValue<SflixProvider.SourceObject>(response) | ||||||
| 
 | 
 | ||||||
|                 mapped.tracks?.forEach { track -> |                 mapped.tracks?.forEach { track -> | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ class AsianLoad : ExtractorApi() { | ||||||
|     override val requiresReferer = true |     override val requiresReferer = true | ||||||
| 
 | 
 | ||||||
|     private val sourceRegex = Regex("""sources:[\W\w]*?file:\s*?["'](.*?)["']""") |     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() |         val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||||
|         with(app.get(url, referer = referer)) { |         with(app.get(url, referer = referer)) { | ||||||
|             sourceRegex.findAll(this.text).forEach { sourceMatch -> |             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.ExtractorApi | ||||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
| import com.lagradost.cloudstream3.utils.Qualities | import com.lagradost.cloudstream3.utils.Qualities | ||||||
| import java.lang.Thread.sleep | import kotlinx.coroutines.delay | ||||||
| 
 | 
 | ||||||
| class DoodToExtractor : DoodLaExtractor() { | class DoodToExtractor : DoodLaExtractor() { | ||||||
|     override val mainUrl = "https://dood.to" |     override val mainUrl = "https://dood.to" | ||||||
|  | @ -28,13 +28,13 @@ open class DoodLaExtractor : ExtractorApi() { | ||||||
|         return "$mainUrl/d/$id" |         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 id = url.removePrefix("$mainUrl/e/").removePrefix("$mainUrl/d/") | ||||||
|         val trueUrl = getExtractorUrl(id) |         val trueUrl = getExtractorUrl(id) | ||||||
|         val response = app.get(trueUrl).text |         val response = app.get(trueUrl).text | ||||||
|         Regex("href=\".*/download/(.*?)\"").find(response)?.groupValues?.get(1)?.let { link -> |         Regex("href=\".*/download/(.*?)\"").find(response)?.groupValues?.get(1)?.let { link -> | ||||||
|             if (link.isEmpty()) return null |             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 downloadLink = "$mainUrl/download/$link" | ||||||
|             val downloadResponse = app.get(downloadLink).text |             val downloadResponse = app.get(downloadLink).text | ||||||
|             Regex("onclick=\"window\\.open\\((['\"])(.*?)(['\"])").find(downloadResponse)?.groupValues?.get(2) |             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 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 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) |         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" |         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)) { |         with(app.get(url)) { | ||||||
|             getAndUnpack(this.text).let { unpackedText -> |             getAndUnpack(this.text).let { unpackedText -> | ||||||
|                 srcRegex.find(unpackedText)?.groupValues?.get(1)?.let { link -> |                 srcRegex.find(unpackedText)?.groupValues?.get(1)?.let { link -> | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ class Mp4Upload : ExtractorApi() { | ||||||
|     private val srcRegex = Regex("""player\.src\("(.*?)"""") |     private val srcRegex = Regex("""player\.src\("(.*?)"""") | ||||||
|     override val requiresReferer = true |     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)) { |         with(app.get(url)) { | ||||||
|             getAndUnpack(this.text).let { unpackedText -> |             getAndUnpack(this.text).let { unpackedText -> | ||||||
|                 srcRegex.find(unpackedText)?.groupValues?.get(1)?.let { link -> |                 srcRegex.find(unpackedText)?.groupValues?.get(1)?.let { link -> | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ class MultiQuality : ExtractorApi() { | ||||||
|         return "$mainUrl/loadserver.php?id=$id" |         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() |         val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||||
|         with(app.get(url)) { |         with(app.get(url)) { | ||||||
|             sourceRegex.findAll(this.text).forEach { sourceMatch -> |             sourceRegex.findAll(this.text).forEach { sourceMatch -> | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| package com.lagradost.cloudstream3.extractors | package com.lagradost.cloudstream3.extractors | ||||||
| 
 | 
 | ||||||
|  | import com.lagradost.cloudstream3.apmap | ||||||
| import com.lagradost.cloudstream3.app | import com.lagradost.cloudstream3.app | ||||||
| import com.lagradost.cloudstream3.mvvm.normalSafeApiCall | import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall | ||||||
| import com.lagradost.cloudstream3.pmap |  | ||||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
| import com.lagradost.cloudstream3.utils.extractorApis | import com.lagradost.cloudstream3.utils.extractorApis | ||||||
| import com.lagradost.cloudstream3.utils.getQualityFromName | import com.lagradost.cloudstream3.utils.getQualityFromName | ||||||
|  | @ -27,9 +27,9 @@ class Pelisplus(val mainUrl: String) { | ||||||
|     private val normalApis = arrayListOf(MultiQuality()) |     private val normalApis = arrayListOf(MultiQuality()) | ||||||
| 
 | 
 | ||||||
|     // https://gogo-stream.com/streaming.php?id=MTE3NDg5 |     // 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 { |         try { | ||||||
|             normalApis.pmap { api -> |             normalApis.apmap { api -> | ||||||
|                 val url = api.getExtractorUrl(id) |                 val url = api.getExtractorUrl(id) | ||||||
|                 val source = api.getSafeUrl(url) |                 val source = api.getSafeUrl(url) | ||||||
|                 source?.forEach { callback.invoke(it) } |                 source?.forEach { callback.invoke(it) } | ||||||
|  | @ -37,7 +37,7 @@ class Pelisplus(val mainUrl: String) { | ||||||
|             val extractorUrl = getExtractorUrl(id) |             val extractorUrl = getExtractorUrl(id) | ||||||
| 
 | 
 | ||||||
|             /** Stolen from GogoanimeProvider.kt extractor */ |             /** Stolen from GogoanimeProvider.kt extractor */ | ||||||
|             normalSafeApiCall { |             suspendSafeApiCall { | ||||||
|                 val link = getDownloadUrl(id) |                 val link = getDownloadUrl(id) | ||||||
|                 println("Generated vidstream download link: $link") |                 println("Generated vidstream download link: $link") | ||||||
|                 val page = app.get(link, referer = extractorUrl) |                 val page = app.get(link, referer = extractorUrl) | ||||||
|  | @ -46,8 +46,8 @@ class Pelisplus(val mainUrl: String) { | ||||||
|                 val qualityRegex = Regex("(\\d+)P") |                 val qualityRegex = Regex("(\\d+)P") | ||||||
| 
 | 
 | ||||||
|                 //a[download] |                 //a[download] | ||||||
|                 pageDoc.select(".dowload > a")?.pmap { element -> |                 pageDoc.select(".dowload > a")?.apmap { element -> | ||||||
|                     val href = element.attr("href") ?: return@pmap |                     val href = element.attr("href") ?: return@apmap | ||||||
|                     val qual = if (element.text() |                     val qual = if (element.text() | ||||||
|                             .contains("HDP") |                             .contains("HDP") | ||||||
|                     ) "1080" else qualityRegex.find(element.text())?.destructured?.component1().toString() |                     ) "1080" else qualityRegex.find(element.text())?.destructured?.component1().toString() | ||||||
|  | @ -78,7 +78,7 @@ class Pelisplus(val mainUrl: String) { | ||||||
|                     //val name = element.text() |                     //val name = element.text() | ||||||
| 
 | 
 | ||||||
|                     // Matches vidstream links with extractors |                     // Matches vidstream links with extractors | ||||||
|                     extractorApis.filter { !it.requiresReferer || !isCasting }.pmap { api -> |                     extractorApis.filter { !it.requiresReferer || !isCasting }.apmap { api -> | ||||||
|                         if (link.startsWith(api.mainUrl)) { |                         if (link.startsWith(api.mainUrl)) { | ||||||
|                             val extractedLinks = api.getSafeUrl(link, extractorUrl) |                             val extractedLinks = api.getSafeUrl(link, extractorUrl) | ||||||
|                             if (extractedLinks?.isNotEmpty() == true) { |                             if (extractedLinks?.isNotEmpty() == true) { | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ open class SBPlay : ExtractorApi() { | ||||||
|     override val name = "SBPlay" |     override val name = "SBPlay" | ||||||
|     override val requiresReferer = false |     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 response = app.get(url, referer = referer).text | ||||||
|         val document = Jsoup.parse(response) |         val document = Jsoup.parse(response) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ class StreamSB : ExtractorApi() { | ||||||
|     override val requiresReferer = false |     override val requiresReferer = false | ||||||
| 
 | 
 | ||||||
|     // 	https://sbembed.com/embed-ns50b0cukf9j.html   ->   https://sbvideo.net/play/ns50b0cukf9j |     // 	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 extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||||
|         val newUrl = url.replace("sbplay.org/embed-", "sbplay.org/play/").removeSuffix(".html") |         val newUrl = url.replace("sbplay.org/embed-", "sbplay.org/play/").removeSuffix(".html") | ||||||
|         with(app.get(newUrl, timeout = 10)) { |         with(app.get(newUrl, timeout = 10)) { | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ class StreamTape : ExtractorApi() { | ||||||
|     private val linkRegex = |     private val linkRegex = | ||||||
|         Regex("""'robotlink'\)\.innerHTML = '(.+?)'\+ \('(.+?)'\)""") |         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)) { |         with(app.get(url)) { | ||||||
|             linkRegex.find(this.text)?.let { |             linkRegex.find(this.text)?.let { | ||||||
|                 val extractedUrl = "https:${it.groups[1]!!.value + it.groups[2]!!.value.substring(3,)}" |                 val extractedUrl = "https:${it.groups[1]!!.value + it.groups[2]!!.value.substring(3,)}" | ||||||
|  |  | ||||||
|  | @ -16,7 +16,7 @@ class Streamhub : ExtractorApi() { | ||||||
|         return "$mainUrl/e/$id" |         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 |         val response = app.get(url).text | ||||||
|         Regex("eval((.|\\n)*?)</script>").find(response)?.groupValues?.get(1)?.let { jsEval -> |         Regex("eval((.|\\n)*?)</script>").find(response)?.groupValues?.get(1)?.let { jsEval -> | ||||||
|             JsUnpacker("eval$jsEval").unpack()?.let { unPacked -> |             JsUnpacker("eval$jsEval").unpack()?.let { unPacked -> | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ class UpstreamExtractor: ExtractorApi() { | ||||||
|     override val mainUrl: String = "https://upstream.to" |     override val mainUrl: String = "https://upstream.to" | ||||||
|     override val requiresReferer = true |     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 |         // WIP: m3u8 link fetched but sometimes not playing | ||||||
|         //Log.i(this.name, "Result => (no extractor) ${url}") |         //Log.i(this.name, "Result => (no extractor) ${url}") | ||||||
|         val sources: MutableList<ExtractorLink> = mutableListOf() |         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 |     private val srcRegex = Regex("""sources:.\[(.*?)\]""")  // would be possible to use the parse and find src attribute | ||||||
|     override val requiresReferer = true |     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" |         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 -> |             srcRegex.find(this.text)?.groupValues?.get(1)?.replace("\"", "")?.let { link -> | ||||||
|                 return listOf( |                 return listOf( | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| package com.lagradost.cloudstream3.extractors | package com.lagradost.cloudstream3.extractors | ||||||
| 
 | 
 | ||||||
|  | import com.lagradost.cloudstream3.apmap | ||||||
| import com.lagradost.cloudstream3.app | import com.lagradost.cloudstream3.app | ||||||
| import com.lagradost.cloudstream3.mvvm.normalSafeApiCall | import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall | ||||||
| import com.lagradost.cloudstream3.pmap |  | ||||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
| import com.lagradost.cloudstream3.utils.extractorApis | import com.lagradost.cloudstream3.utils.extractorApis | ||||||
| import com.lagradost.cloudstream3.utils.getQualityFromName | import com.lagradost.cloudstream3.utils.getQualityFromName | ||||||
|  | @ -27,9 +27,9 @@ class Vidstream(val mainUrl: String) { | ||||||
|     private val normalApis = arrayListOf(MultiQuality()) |     private val normalApis = arrayListOf(MultiQuality()) | ||||||
| 
 | 
 | ||||||
|     // https://gogo-stream.com/streaming.php?id=MTE3NDg5 |     // 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 { |         try { | ||||||
|             normalApis.pmap { api -> |             normalApis.apmap { api -> | ||||||
|                 val url = api.getExtractorUrl(id) |                 val url = api.getExtractorUrl(id) | ||||||
|                 val source = api.getSafeUrl(url) |                 val source = api.getSafeUrl(url) | ||||||
|                 source?.forEach { callback.invoke(it) } |                 source?.forEach { callback.invoke(it) } | ||||||
|  | @ -37,7 +37,7 @@ class Vidstream(val mainUrl: String) { | ||||||
|             val extractorUrl = getExtractorUrl(id) |             val extractorUrl = getExtractorUrl(id) | ||||||
| 
 | 
 | ||||||
|             /** Stolen from GogoanimeProvider.kt extractor */ |             /** Stolen from GogoanimeProvider.kt extractor */ | ||||||
|             normalSafeApiCall { |             suspendSafeApiCall { | ||||||
|                 val link = getDownloadUrl(id) |                 val link = getDownloadUrl(id) | ||||||
|                 println("Generated vidstream download link: $link") |                 println("Generated vidstream download link: $link") | ||||||
|                 val page = app.get(link, referer = extractorUrl) |                 val page = app.get(link, referer = extractorUrl) | ||||||
|  | @ -46,8 +46,8 @@ class Vidstream(val mainUrl: String) { | ||||||
|                 val qualityRegex = Regex("(\\d+)P") |                 val qualityRegex = Regex("(\\d+)P") | ||||||
| 
 | 
 | ||||||
|                 //a[download] |                 //a[download] | ||||||
|                 pageDoc.select(".dowload > a")?.pmap { element -> |                 pageDoc.select(".dowload > a")?.apmap { element -> | ||||||
|                     val href = element.attr("href") ?: return@pmap |                     val href = element.attr("href") ?: return@apmap | ||||||
|                     val qual = if (element.text() |                     val qual = if (element.text() | ||||||
|                             .contains("HDP") |                             .contains("HDP") | ||||||
|                     ) "1080" else qualityRegex.find(element.text())?.destructured?.component1().toString() |                     ) "1080" else qualityRegex.find(element.text())?.destructured?.component1().toString() | ||||||
|  | @ -78,7 +78,7 @@ class Vidstream(val mainUrl: String) { | ||||||
|                     //val name = element.text() |                     //val name = element.text() | ||||||
| 
 | 
 | ||||||
|                     // Matches vidstream links with extractors |                     // Matches vidstream links with extractors | ||||||
|                     extractorApis.filter { !it.requiresReferer || !isCasting }.pmap { api -> |                     extractorApis.filter { !it.requiresReferer || !isCasting }.apmap { api -> | ||||||
|                         if (link.startsWith(api.mainUrl)) { |                         if (link.startsWith(api.mainUrl)) { | ||||||
|                             val extractedLinks = api.getSafeUrl(link, extractorUrl) |                             val extractedLinks = api.getSafeUrl(link, extractorUrl) | ||||||
|                             if (extractedLinks?.isNotEmpty() == true) { |                             if (extractedLinks?.isNotEmpty() == true) { | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ open class VoeExtractor : ExtractorApi() { | ||||||
|         //val type: String // Mp4 |         //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 extractedLinksList: MutableList<ExtractorLink> = mutableListOf() | ||||||
|         val doc = app.get(url).text |         val doc = app.get(url).text | ||||||
|         if (doc.isNotEmpty()) { |         if (doc.isNotEmpty()) { | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ open class WatchSB : ExtractorApi() { | ||||||
|     override val mainUrl = "https://watchsb.com" |     override val mainUrl = "https://watchsb.com" | ||||||
|     override val requiresReferer = false |     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( |         val response = app.get( | ||||||
|             url, interceptor = WebViewResolver( |             url, interceptor = WebViewResolver( | ||||||
|                 Regex("""master\.m3u8""") |                 Regex("""master\.m3u8""") | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ class WcoStream : ExtractorApi() { | ||||||
|     override val requiresReferer = false |     override val requiresReferer = false | ||||||
|     private val hlsHelper = M3u8Helper() |     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 baseUrl = url.split("/e/")[0] | ||||||
| 
 | 
 | ||||||
|         val html = app.get(url, headers = mapOf("Referer" to "https://wcostream.cc/")).text |         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" |         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( |         val headers = mapOf( | ||||||
|             "Referer" to url, |             "Referer" to url, | ||||||
|             "User-Agent" to "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0", |             "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 { | class AsianEmbedHelper { | ||||||
|     companion object { |     companion object { | ||||||
|         fun getUrls(url: String, callback: (ExtractorLink) -> Unit) { |         suspend fun getUrls(url: String, callback: (ExtractorLink) -> Unit) { | ||||||
|             if (url.startsWith("https://asianembed.io")) { |             if (url.startsWith("https://asianembed.io")) { | ||||||
|                 // Fetch links |                 // Fetch links | ||||||
|                 val doc = app.get(url).document |                 val doc = app.get(url).document | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| package com.lagradost.cloudstream3.extractors.helper | package com.lagradost.cloudstream3.extractors.helper | ||||||
| 
 | 
 | ||||||
| import android.util.Log |  | ||||||
| import com.lagradost.cloudstream3.app | import com.lagradost.cloudstream3.app | ||||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
| import com.lagradost.cloudstream3.utils.Qualities | import com.lagradost.cloudstream3.utils.Qualities | ||||||
|  | @ -11,7 +10,7 @@ class VstreamhubHelper { | ||||||
|         private val baseUrl: String = "https://vstreamhub.com" |         private val baseUrl: String = "https://vstreamhub.com" | ||||||
|         private val baseName: String = "Vstreamhub" |         private val baseName: String = "Vstreamhub" | ||||||
| 
 | 
 | ||||||
|         fun getUrls(url: String, callback: (ExtractorLink) -> Unit) { |         suspend fun getUrls(url: String, callback: (ExtractorLink) -> Unit) { | ||||||
|             if (url.startsWith(baseUrl)) { |             if (url.startsWith(baseUrl)) { | ||||||
|                 // Fetch links |                 // Fetch links | ||||||
|                 val doc = app.get(url).document.select("script") |                 val doc = app.get(url).document.select("script") | ||||||
|  |  | ||||||
|  | @ -41,7 +41,7 @@ class AkwamProvider : MainAPI() { | ||||||
|             "Series" to "$mainUrl/series", |             "Series" to "$mainUrl/series", | ||||||
|             "Shows" to "$mainUrl/shows" |             "Shows" to "$mainUrl/shows" | ||||||
|         ) |         ) | ||||||
|         val pages = moviesUrl.pmap { |         val pages = moviesUrl.apmap { | ||||||
|             val doc = app.get(it.second).document |             val doc = app.get(it.second).document | ||||||
|             val list = doc.select("div.col-lg-auto.col-md-4.col-6.mb-12").mapNotNull { element -> |             val list = doc.select("div.col-lg-auto.col-md-4.col-6.mb-12").mapNotNull { element -> | ||||||
|                 element.toSearchResponse() |                 element.toSearchResponse() | ||||||
|  | @ -150,7 +150,7 @@ class AkwamProvider : MainAPI() { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     // Maybe possible to not use the url shortener but cba investigating that. |     // 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")) |         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/") } |             }.filter { link -> link.first.contains("/link/") } | ||||||
|         }.flatten() |         }.flatten() | ||||||
| 
 | 
 | ||||||
|         links.pmap { |         links.map { | ||||||
|             val linkDoc = skipUrlShortener(it.first).document |             val linkDoc = skipUrlShortener(it.first).document | ||||||
|             val button = linkDoc.select("div.btn-loader > a") |             val button = linkDoc.select("div.btn-loader > a") | ||||||
|             val url = button.attr("href") |             val url = button.attr("href") | ||||||
|  |  | ||||||
|  | @ -110,7 +110,7 @@ class LookMovieProvider : MainAPI() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override suspend fun search(query: String): List<SearchResponse> { |     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 url = "$mainUrl/${if (isMovie) "movies" else "shows"}/search/?q=$query" | ||||||
|             val response = app.get(url).text |             val response = app.get(url).text | ||||||
|             val document = Jsoup.parse(response) |             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 |         val response = app.get(url.replace("\$unixtime", unixTime.toString())).text | ||||||
|         M3u8Manifest.extractLinks(response).forEach { |         M3u8Manifest.extractLinks(response).forEach { | ||||||
|             callback.invoke( |             callback.invoke( | ||||||
|  |  | ||||||
|  | @ -151,7 +151,7 @@ open class PelisplusProviderTemplate : MainAPI() { | ||||||
|         val urls = homePageUrlList |         val urls = homePageUrlList | ||||||
|         val homePageList = ArrayList<HomePageList>() |         val homePageList = ArrayList<HomePageList>() | ||||||
|         // .pmap {} is used to fetch the different pages in parallel |         // .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 response = app.get(url, timeout = 20).text | ||||||
|             val document = Jsoup.parse(response) |             val document = Jsoup.parse(response) | ||||||
|             document.select("div.main-inner")?.forEach { inner -> |             document.select("div.main-inner")?.forEach { inner -> | ||||||
|  |  | ||||||
|  | @ -228,7 +228,7 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() { | ||||||
|             } |             } | ||||||
|         } ?: tryParseJson<List<String>>(data))?.distinct() |         } ?: tryParseJson<List<String>>(data))?.distinct() | ||||||
| 
 | 
 | ||||||
|         urls?.pmap { url -> |         urls?.apmap { url -> | ||||||
|             val sources = app.get( |             val sources = app.get( | ||||||
|                 url, |                 url, | ||||||
|                 interceptor = WebViewResolver( |                 interceptor = WebViewResolver( | ||||||
|  |  | ||||||
|  | @ -59,7 +59,7 @@ class VfFilmProvider : MainAPI() { | ||||||
|         return true |         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 response = app.get(original).text | ||||||
|         val url = "iframe .*src=\"(.*?)\"".toRegex().find(response)?.groupValues?.get(1) |         val url = "iframe .*src=\"(.*?)\"".toRegex().find(response)?.groupValues?.get(1) | ||||||
|             .toString()  // https://vudeo.net/embed-uweno86lzx8f.html for example |             .toString()  // https://vudeo.net/embed-uweno86lzx8f.html for example | ||||||
|  |  | ||||||
|  | @ -42,7 +42,7 @@ class VfSerieProvider : MainAPI() { | ||||||
|         return returnValue |         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 response = app.get(original).text | ||||||
|         val url = "iframe .*src=\"(.*?)\"".toRegex().find(response)?.groupValues?.get(1) |         val url = "iframe .*src=\"(.*?)\"".toRegex().find(response)?.groupValues?.get(1) | ||||||
|             .toString()  // https://vudeo.net/embed-7jdb1t5b2mvo.html for example |             .toString()  // https://vudeo.net/embed-7jdb1t5b2mvo.html for example | ||||||
|  |  | ||||||
|  | @ -148,7 +148,7 @@ open class VidstreamProviderTemplate : MainAPI() { | ||||||
|         val urls = homePageUrlList |         val urls = homePageUrlList | ||||||
|         val homePageList = ArrayList<HomePageList>() |         val homePageList = ArrayList<HomePageList>() | ||||||
|         // .pmap {} is used to fetch the different pages in parallel |         // .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 response = app.get(url, timeout = 20).text | ||||||
|             val document = Jsoup.parse(response) |             val document = Jsoup.parse(response) | ||||||
|             document.select("div.main-inner")?.forEach { inner -> |             document.select("div.main-inner")?.forEach { inner -> | ||||||
|  |  | ||||||
|  | @ -191,7 +191,7 @@ class WatchAsianProvider : MainAPI() { | ||||||
|         return count > 0 |         return count > 0 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun getServerLinks(url: String) : String { |     private suspend fun getServerLinks(url: String) : String { | ||||||
|         val moviedoc = app.get(url, referer = mainUrl).document |         val moviedoc = app.get(url, referer = mainUrl).document | ||||||
|         return moviedoc.select("div.anime_muti_link > ul > li") |         return moviedoc.select("div.anime_muti_link > ul > li") | ||||||
|             ?.mapNotNull { |             ?.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> { | fun <T> safeFail(throwable: Throwable): Resource<T> { | ||||||
|     val stackTraceMsg = (throwable.localizedMessage ?: "") + "\n\n" + throwable.stackTrace.joinToString( |     val stackTraceMsg = (throwable.localizedMessage ?: "") + "\n\n" + throwable.stackTrace.joinToString( | ||||||
|         separator = "\n" |         separator = "\n" | ||||||
|  |  | ||||||
|  | @ -2,6 +2,8 @@ package com.lagradost.cloudstream3.network | ||||||
| 
 | 
 | ||||||
| import androidx.annotation.AnyThread | import androidx.annotation.AnyThread | ||||||
| import com.lagradost.cloudstream3.app | import com.lagradost.cloudstream3.app | ||||||
|  | import com.lagradost.cloudstream3.network.Requests.Companion.await | ||||||
|  | import kotlinx.coroutines.runBlocking | ||||||
| import okhttp3.Interceptor | import okhttp3.Interceptor | ||||||
| import okhttp3.Request | import okhttp3.Request | ||||||
| import okhttp3.Response | import okhttp3.Response | ||||||
|  | @ -18,17 +20,17 @@ class DdosGuardKiller(private val alwaysBypass: Boolean) : Interceptor { | ||||||
| 
 | 
 | ||||||
|     private var ddosBypassPath: String? = null |     private var ddosBypassPath: String? = null | ||||||
| 
 | 
 | ||||||
|     override fun intercept(chain: Interceptor.Chain): Response { |     override fun intercept(chain: Interceptor.Chain): Response =  runBlocking { | ||||||
|         val request = chain.request() |         val request = chain.request() | ||||||
|         if (alwaysBypass) return bypassDdosGuard(request) |         if (alwaysBypass) return@runBlocking bypassDdosGuard(request) | ||||||
| 
 | 
 | ||||||
|         val response = chain.proceed(request) |         val response = chain.proceed(request) | ||||||
|         return if (response.code == 403) { |         return@runBlocking if (response.code == 403) { | ||||||
|             bypassDdosGuard(request) |             bypassDdosGuard(request) | ||||||
|         } else response |         } else response | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun bypassDdosGuard(request: Request): Response { |     private suspend fun bypassDdosGuard(request: Request): Response { | ||||||
|         ddosBypassPath = ddosBypassPath ?: Regex("'(.*?)'").find( |         ddosBypassPath = ddosBypassPath ?: Regex("'(.*?)'").find( | ||||||
|             app.get( |             app.get( | ||||||
|                 "https://check.ddos-guard.net/check.js" |                 "https://check.ddos-guard.net/check.js" | ||||||
|  | @ -49,6 +51,6 @@ class DdosGuardKiller(private val alwaysBypass: Boolean) : Interceptor { | ||||||
|             request.newBuilder() |             request.newBuilder() | ||||||
|                 .headers(headers) |                 .headers(headers) | ||||||
|                 .build() |                 .build() | ||||||
|         ).execute() |         ).await() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -7,13 +7,19 @@ import com.lagradost.cloudstream3.R | ||||||
| import com.lagradost.cloudstream3.USER_AGENT | import com.lagradost.cloudstream3.USER_AGENT | ||||||
| import com.lagradost.cloudstream3.app | import com.lagradost.cloudstream3.app | ||||||
| import com.lagradost.cloudstream3.mapper | 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.* | ||||||
| import okhttp3.Headers.Companion.toHeaders | import okhttp3.Headers.Companion.toHeaders | ||||||
| import org.jsoup.Jsoup | import org.jsoup.Jsoup | ||||||
| import org.jsoup.nodes.Document | import org.jsoup.nodes.Document | ||||||
| import java.io.File | import java.io.File | ||||||
|  | import java.io.IOException | ||||||
| import java.net.URI | import java.net.URI | ||||||
| import java.util.concurrent.TimeUnit | import java.util.concurrent.TimeUnit | ||||||
|  | import kotlin.coroutines.resumeWithException | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Session( | class Session( | ||||||
|  | @ -234,7 +240,41 @@ open class Requests { | ||||||
|         return baseClient |         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, |         url: String, | ||||||
|         headers: Map<String, String> = emptyMap(), |         headers: Map<String, String> = emptyMap(), | ||||||
|         referer: String? = null, |         referer: String? = null, | ||||||
|  | @ -255,15 +295,15 @@ open class Requests { | ||||||
|         if (interceptor != null) client.addInterceptor(interceptor) |         if (interceptor != null) client.addInterceptor(interceptor) | ||||||
|         val request = |         val request = | ||||||
|             getRequestCreator(url, headers, referer, params, cookies, cacheTime, cacheUnit) |             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) |         return AppResponse(response) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun executeRequest(request : Request): AppResponse { |     fun executeRequest(request: Request): AppResponse { | ||||||
|         return AppResponse(baseClient.newCall(request).execute()) |         return AppResponse(baseClient.newCall(request).execute()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun post( |     suspend fun post( | ||||||
|         url: String, |         url: String, | ||||||
|         headers: Map<String, String> = mapOf(), |         headers: Map<String, String> = mapOf(), | ||||||
|         referer: String? = null, |         referer: String? = null, | ||||||
|  | @ -283,11 +323,11 @@ open class Requests { | ||||||
|             .build() |             .build() | ||||||
|         val request = |         val request = | ||||||
|             postRequestCreator(url, headers, referer, params, cookies, data, cacheTime, cacheUnit) |             postRequestCreator(url, headers, referer, params, cookies, data, cacheTime, cacheUnit) | ||||||
|         val response = client.newCall(request).execute() |         val response = client.newCall(request).await() | ||||||
|         return AppResponse(response) |         return AppResponse(response) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun put( |     suspend fun put( | ||||||
|         url: String, |         url: String, | ||||||
|         headers: Map<String, String> = mapOf(), |         headers: Map<String, String> = mapOf(), | ||||||
|         referer: String? = null, |         referer: String? = null, | ||||||
|  | @ -307,7 +347,7 @@ open class Requests { | ||||||
|             .build() |             .build() | ||||||
|         val request = |         val request = | ||||||
|             putRequestCreator(url, headers, referer, params, cookies, data, cacheTime, cacheUnit) |             putRequestCreator(url, headers, referer, params, cookies, data, cacheTime, cacheUnit) | ||||||
|         val response = client.newCall(request).execute() |         val response = client.newCall(request).await() | ||||||
|         return AppResponse(response) |         return AppResponse(response) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -76,7 +76,7 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List<Regex> = | ||||||
|                 override fun shouldInterceptRequest( |                 override fun shouldInterceptRequest( | ||||||
|                     view: WebView, |                     view: WebView, | ||||||
|                     request: WebResourceRequest |                     request: WebResourceRequest | ||||||
|                 ): WebResourceResponse? { |                 ): WebResourceResponse? = runBlocking { | ||||||
|                     val webViewUrl = request.url.toString() |                     val webViewUrl = request.url.toString() | ||||||
| //                    println("Loading WebView URL: $webViewUrl") | //                    println("Loading WebView URL: $webViewUrl") | ||||||
| 
 | 
 | ||||||
|  | @ -84,7 +84,7 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List<Regex> = | ||||||
|                         fixedRequest = request.toRequest().also(requestCallBack) |                         fixedRequest = request.toRequest().also(requestCallBack) | ||||||
|                         println("Web-view request finished: $webViewUrl") |                         println("Web-view request finished: $webViewUrl") | ||||||
|                         destroyWebView() |                         destroyWebView() | ||||||
|                         return null |                         return@runBlocking null | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     if (additionalUrls.any { it.containsMatchIn(webViewUrl) }) { |                     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, |                      *  Overriding with okhttp might fuck up otherwise working requests, | ||||||
|                      *  e.g the recaptcha request. |                      *  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 { |                         when { | ||||||
|                             blacklistedFiles.any { URI(webViewUrl).path.contains(it) } || webViewUrl.endsWith( |                             blacklistedFiles.any { URI(webViewUrl).path.contains(it) } || webViewUrl.endsWith( | ||||||
|                                 "/favicon.ico" |                                 "/favicon.ico" | ||||||
|  | @ -152,7 +158,7 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List<Regex> = | ||||||
|                                 webViewUrl, |                                 webViewUrl, | ||||||
|                                 headers = request.requestHeaders |                                 headers = request.requestHeaders | ||||||
|                             ).response.toWebResourceResponse() |                             ).response.toWebResourceResponse() | ||||||
|                             else -> return super.shouldInterceptRequest(view, request) |                             else -> return@runBlocking super.shouldInterceptRequest(view, request) | ||||||
|                         } |                         } | ||||||
|                     } catch (e: Exception) { |                     } catch (e: Exception) { | ||||||
|                         null |                         null | ||||||
|  |  | ||||||
|  | @ -66,7 +66,7 @@ interface SyncAPI : OAuth2API { | ||||||
|     val icon: Int |     val icon: Int | ||||||
| 
 | 
 | ||||||
|     val mainUrl: String |     val mainUrl: String | ||||||
|     fun search(name: String): List<SyncSearchResult>? |     suspend fun search(name: String): List<SyncSearchResult>? | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|     -1 -> None |     -1 -> None | ||||||
|  | @ -77,9 +77,9 @@ interface SyncAPI : OAuth2API { | ||||||
|     4 -> PlanToWatch |     4 -> PlanToWatch | ||||||
|     5 -> ReWatching |     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 |         val data = searchShows(name) ?: return null | ||||||
|         return data.data.Page.media.map { |         return data.data.Page.media.map { | ||||||
|             SyncAPI.SyncSearchResult( |             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 internalId = id.toIntOrNull() ?: return null | ||||||
|         val season = getSeason(internalId)?.data?.Media ?: 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 internalId = id.toIntOrNull() ?: return null | ||||||
|         val data = getDataAboutId(internalId) ?: 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( |         return postDataAboutId( | ||||||
|             id.toIntOrNull() ?: return false, |             id.toIntOrNull() ?: return false, | ||||||
|             fromIntToAnimeStatus(status.status), |             fromIntToAnimeStatus(status.status), | ||||||
|  | @ -143,7 +143,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|                 .replace("[^a-zA-Z0-9]".toRegex(), "") |                 .replace("[^a-zA-Z0-9]".toRegex(), "") | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private fun searchShows(name: String): GetSearchRoot? { |         private suspend fun searchShows(name: String): GetSearchRoot? { | ||||||
|             try { |             try { | ||||||
|                 val query = """ |                 val query = """ | ||||||
|                 query (${"$"}id: Int, ${"$"}page: Int, ${"$"}search: String, ${"$"}type: MediaType) { |                 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 |         // 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 |             // Strips these from the name | ||||||
|             val blackList = listOf( |             val blackList = listOf( | ||||||
|                 "TV Dubbed", |                 "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 = """ |             val q: String = """ | ||||||
|                query (${'$'}id: Int = $id) { |                query (${'$'}id: Int = $id) { | ||||||
|                    Media (id: ${'$'}id, type: ANIME) { |                    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 = |         val q = | ||||||
|             """query (${'$'}id: Int = $id) { # Define which variables will be used in the query (id) |             """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) |                 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 { |         return try { | ||||||
|             if (!checkToken()) { |             if (!checkToken()) { | ||||||
|                 // println("VARS_ " + vars) |                 // println("VARS_ " + vars) | ||||||
|  | @ -514,7 +514,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|         return getKey(ANILIST_CACHED_LIST) as? Array<Lists> |         return getKey(ANILIST_CACHED_LIST) as? Array<Lists> | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun getAnilistAnimeListSmart(): Array<Lists>? { |     suspend fun getAnilistAnimeListSmart(): Array<Lists>? { | ||||||
|         if (getKey<String>( |         if (getKey<String>( | ||||||
|                 accountId, |                 accountId, | ||||||
|                 ANILIST_TOKEN_KEY, |                 ANILIST_TOKEN_KEY, | ||||||
|  | @ -535,7 +535,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun getFullAnilistList(): FullAnilistList? { |     private suspend fun getFullAnilistList(): FullAnilistList? { | ||||||
|         try { |         try { | ||||||
|             var userID: Int? = null |             var userID: Int? = null | ||||||
|             /** WARNING ASSUMES ONE USER! **/ |             /** 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) { |         val q = """mutation (${'$'}animeId: Int = $id) { | ||||||
| 				ToggleFavourite (animeId: ${'$'}animeId) { | 				ToggleFavourite (animeId: ${'$'}animeId) { | ||||||
| 					anime { | 					anime { | ||||||
|  | @ -614,7 +614,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|         return data != "" |         return data != "" | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun postDataAboutId( |     private suspend fun postDataAboutId( | ||||||
|         id: Int, |         id: Int, | ||||||
|         type: AniListStatusType, |         type: AniListStatusType, | ||||||
|         score: Int?, |         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 = """ |         val q = """ | ||||||
| 				{ | 				{ | ||||||
|   					Viewer { |   					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?>() |         val seasons = mutableListOf<SeasonResponse?>() | ||||||
|         fun getSeasonRecursive(id: Int) { |         suspend fun getSeasonRecursive(id: Int) { | ||||||
|             val season = getSeason(id) |             val season = getSeason(id) | ||||||
|             if (season != null) { |             if (season != null) { | ||||||
|                 seasons.add(season) |                 seasons.add(season) | ||||||
|  |  | ||||||
|  | @ -50,7 +50,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|         return null |         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 url = "https://api.myanimelist.net/v2/anime?q=$name&limit=$MAL_MAX_SEARCH_LIMIT" | ||||||
|         val auth = getKey<String>( |         val auth = getKey<String>( | ||||||
|             accountId, |             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( |         return setScoreRequest( | ||||||
|             id.toIntOrNull() ?: return false, |             id.toIntOrNull() ?: return false, | ||||||
|             fromIntToAnimeStatus(status.status), |             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 |         val internalId = id.toIntOrNull() ?: return null | ||||||
|         TODO("Not yet implemented") |         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 internalId = id.toIntOrNull() ?: return null | ||||||
| 
 | 
 | ||||||
|         val data = getDataAboutMalId(internalId)?.my_list_status ?: 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 { |         try { | ||||||
|             val res = app.post( |             val res = app.post( | ||||||
|                 "https://myanimelist.net/v1/oauth2/token", |                 "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> |         return getKey(MAL_CACHED_LIST) as? Array<Data> | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun getMalAnimeListSmart(): Array<Data>? { |     suspend fun getMalAnimeListSmart(): Array<Data>? { | ||||||
|         if (getKey<String>( |         if (getKey<String>( | ||||||
|                 accountId, |                 accountId, | ||||||
|                 MAL_TOKEN_KEY |                 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 { |         return try { | ||||||
|             checkMalToken() |             checkMalToken() | ||||||
|             var offset = 0 |             var offset = 0 | ||||||
|  | @ -321,7 +321,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|         return fromIntToAnimeStatus(malStatusAsString.indexOf(string)) |         return fromIntToAnimeStatus(malStatusAsString.indexOf(string)) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun getMalAnimeListSlice(offset: Int = 0): MalList? { |     private suspend fun getMalAnimeListSlice(offset: Int = 0): MalList? { | ||||||
|         val user = "@me" |         val user = "@me" | ||||||
|         val auth = getKey<String>( |         val auth = getKey<String>( | ||||||
|             accountId, |             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 { |         return try { | ||||||
|             // https://myanimelist.net/apiconfig/references/api/v2#operation/anime_anime_id_get |             // 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" |             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" |         val user = "@me" | ||||||
|         var isDone = false |         var isDone = false | ||||||
|         var index = 0 |         var index = 0 | ||||||
|  | @ -426,7 +426,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|         return null |         return null | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun checkMalToken() { |     private suspend fun checkMalToken() { | ||||||
|         if (unixTime > getKey( |         if (unixTime > getKey( | ||||||
|                 accountId, |                 accountId, | ||||||
|                 MAL_UNIXTIME_KEY |                 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() |         checkMalToken() | ||||||
|         return try { |         return try { | ||||||
|             val res = app.get( |             val res = app.get( | ||||||
|  | @ -483,7 +483,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun setScoreRequest( |     suspend fun setScoreRequest( | ||||||
|         id: Int, |         id: Int, | ||||||
|         status: MalStatusType? = null, |         status: MalStatusType? = null, | ||||||
|         score: Int? = null, |         score: Int? = null, | ||||||
|  | @ -514,7 +514,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun setScoreRequest( |     private suspend fun setScoreRequest( | ||||||
|         id: Int, |         id: Int, | ||||||
|         status: String? = null, |         status: String? = null, | ||||||
|         score: Int? = null, |         score: Int? = null, | ||||||
|  |  | ||||||
|  | @ -8,8 +8,6 @@ import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
| 
 | 
 | ||||||
| class APIRepository(val api: MainAPI) { | class APIRepository(val api: MainAPI) { | ||||||
|     companion object { |     companion object { | ||||||
|         var providersActive = HashSet<String>() |  | ||||||
|         var typesActive = HashSet<TvType>() |  | ||||||
|         var dubStatusActive = HashSet<DubStatus>() |         var dubStatusActive = HashSet<DubStatus>() | ||||||
| 
 | 
 | ||||||
|         val noneApi = object : MainAPI() { |         val noneApi = object : MainAPI() { | ||||||
|  |  | ||||||
|  | @ -138,6 +138,22 @@ class HomeFragment : Fragment() { | ||||||
|             bottomSheetDialogBuilder.show() |             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) { |         fun Context.selectHomepage(selectedApiName: String?, callback: (String) -> Unit) { | ||||||
|             val validAPIs = filterProviderByPreferredMedia().toMutableList() |             val validAPIs = filterProviderByPreferredMedia().toMutableList() | ||||||
| 
 | 
 | ||||||
|  | @ -169,6 +185,8 @@ class HomeFragment : Fragment() { | ||||||
|                 val cancelBtt = dialog.findViewById<MaterialButton>(R.id.cancel_btt) |                 val cancelBtt = dialog.findViewById<MaterialButton>(R.id.cancel_btt) | ||||||
|                 val applyBtt = dialog.findViewById<MaterialButton>(R.id.apply_btt) |                 val applyBtt = dialog.findViewById<MaterialButton>(R.id.apply_btt) | ||||||
| 
 | 
 | ||||||
|  |                 val pairList = getPairList(anime, cartoons, tvs, docs, movies) | ||||||
|  | 
 | ||||||
|                 cancelBtt?.setOnClickListener { |                 cancelBtt?.setOnClickListener { | ||||||
|                     dialog.dismissSafe() |                     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() { |                 fun updateList() { | ||||||
|                     this.setKey(HOME_PREF_HOMEPAGE, preSelectedTypes) |                     this.setKey(HOME_PREF_HOMEPAGE, preSelectedTypes) | ||||||
| 
 | 
 | ||||||
|  | @ -210,12 +220,11 @@ class HomeFragment : Fragment() { | ||||||
|                         api.hasMainPage && api.supportedTypes.any { |                         api.hasMainPage && api.supportedTypes.any { | ||||||
|                             preSelectedTypes.contains(it) |                             preSelectedTypes.contains(it) | ||||||
|                         } |                         } | ||||||
|                     }.toMutableList() |                     }.sortedBy { it.name }.toMutableList() | ||||||
|                     currentValidApis.addAll(0, validAPIs.subList(0, 2)) |                     currentValidApis.addAll(0, validAPIs.subList(0, 2)) | ||||||
| 
 | 
 | ||||||
|                     val names = currentValidApis.map { it.name } |                     val names = currentValidApis.map { it.name } | ||||||
|                     val index = names.indexOf(currentApiName) |                     val index = names.indexOf(currentApiName) | ||||||
|                     println("INDEX: $index") |  | ||||||
|                     listView?.setItemChecked(index, true) |                     listView?.setItemChecked(index, true) | ||||||
|                     arrayAdapter.notifyDataSetChanged() |                     arrayAdapter.notifyDataSetChanged() | ||||||
|                     arrayAdapter.addAll(names) |                     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_focus_text?.text = card.name | ||||||
|         home_blur_poster?.setImageBlur(card.posterUrl,50) |         home_blur_poster?.setImageBlur(card.posterUrl, 50) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun homeHandleSearch(callback : SearchClickCallback) { |     private fun homeHandleSearch(callback: SearchClickCallback) { | ||||||
|         if(callback.action == SEARCH_ACTION_FOCUSED) { |         if (callback.action == SEARCH_ACTION_FOCUSED) { | ||||||
|             focusCallback(callback.card) |             focusCallback(callback.card) | ||||||
|         } else { |         } else { | ||||||
|             handleSearchClickCallback(activity, callback) |             handleSearchClickCallback(activity, callback) | ||||||
|  |  | ||||||
|  | @ -6,8 +6,10 @@ import android.view.LayoutInflater | ||||||
| import android.view.View | import android.view.View | ||||||
| import android.view.ViewGroup | import android.view.ViewGroup | ||||||
| import android.view.WindowManager | import android.view.WindowManager | ||||||
| import android.widget.* | import android.widget.AbsListView | ||||||
| import androidx.appcompat.app.AlertDialog | import android.widget.ArrayAdapter | ||||||
|  | import android.widget.ImageView | ||||||
|  | import android.widget.ListView | ||||||
| import androidx.appcompat.widget.SearchView | import androidx.appcompat.widget.SearchView | ||||||
| import androidx.core.view.isVisible | import androidx.core.view.isVisible | ||||||
| import androidx.fragment.app.Fragment | import androidx.fragment.app.Fragment | ||||||
|  | @ -15,18 +17,15 @@ import androidx.fragment.app.activityViewModels | ||||||
| import androidx.preference.PreferenceManager | import androidx.preference.PreferenceManager | ||||||
| import androidx.recyclerview.widget.GridLayoutManager | import androidx.recyclerview.widget.GridLayoutManager | ||||||
| import androidx.recyclerview.widget.RecyclerView | 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.* | ||||||
| import com.lagradost.cloudstream3.APIHolder.apis | import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia | ||||||
| import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings | import com.lagradost.cloudstream3.APIHolder.getApiFromName | ||||||
| import com.lagradost.cloudstream3.APIHolder.getApiSettings |  | ||||||
| import com.lagradost.cloudstream3.APIHolder.getApiTypeSettings |  | ||||||
| import com.lagradost.cloudstream3.mvvm.Resource | import com.lagradost.cloudstream3.mvvm.Resource | ||||||
| import com.lagradost.cloudstream3.mvvm.logError | import com.lagradost.cloudstream3.mvvm.logError | ||||||
| import com.lagradost.cloudstream3.mvvm.observe | import com.lagradost.cloudstream3.mvvm.observe | ||||||
| import com.lagradost.cloudstream3.ui.APIRepository | 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 | ||||||
| import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.currentSpan | import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.currentSpan | ||||||
| import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.loadHomepageList | 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.ui.settings.SettingsFragment.Companion.isTvSettings | ||||||
| import com.lagradost.cloudstream3.utils.DataStore.getKey | import com.lagradost.cloudstream3.utils.DataStore.getKey | ||||||
| import com.lagradost.cloudstream3.utils.DataStore.setKey | 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.dismissSafe | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar | import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount | 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 kotlinx.android.synthetic.main.fragment_search.* | ||||||
| import java.util.concurrent.locks.ReentrantLock | 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() { | class SearchFragment : Fragment() { | ||||||
|     companion object { |     companion object { | ||||||
|  | @ -90,6 +90,9 @@ class SearchFragment : Fragment() { | ||||||
|         super.onDestroyView() |         super.onDestroyView() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     var selectedSearchTypes = mutableListOf<TvType>() | ||||||
|  |     var selectedApis = mutableSetOf<String>() | ||||||
|  | 
 | ||||||
|     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||||
|         super.onViewCreated(view, savedInstanceState) |         super.onViewCreated(view, savedInstanceState) | ||||||
| 
 | 
 | ||||||
|  | @ -108,188 +111,190 @@ class SearchFragment : Fragment() { | ||||||
|         search_autofit_results.adapter = adapter |         search_autofit_results.adapter = adapter | ||||||
|         search_loading_bar.alpha = 0f |         search_loading_bar.alpha = 0f | ||||||
| 
 | 
 | ||||||
|         val searchExitIcon = main_search.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn) |         val searchExitIcon = | ||||||
|         val searchMagIcon = main_search.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon) |             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.scaleX = 0.65f | ||||||
|         searchMagIcon.scaleY = 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 -> |         search_filter.setOnClickListener { searchView -> | ||||||
|             val apiNamesSetting = activity?.getApiSettings() |             searchView?.context?.let { ctx -> | ||||||
|             val langs = activity?.getApiProviderLangSettings() |                 val validAPIs = ctx.filterProviderByPreferredMedia() | ||||||
|             if (apiNamesSetting != null && langs != null) { |                 var currentValidApis = listOf<MainAPI>() | ||||||
|                 val apiNames = apis.filter { langs.contains(it.lang) }.map { it.name } |                 val currentSelectedApis = if (selectedApis.isEmpty()) validAPIs.map { it.name } | ||||||
|  |                     .toMutableSet() else selectedApis | ||||||
|                 val builder = |                 val builder = | ||||||
|                     AlertDialog.Builder(searchView.context).setView(R.layout.provider_list) |                     BottomSheetDialog(ctx) | ||||||
| 
 | 
 | ||||||
|                 val dialog = builder.create() |                 builder.setContentView(R.layout.home_select_mainpage) | ||||||
|                 dialog.show() |                 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 pairList = HomeFragment.getPairList(anime, cartoons, tvs, docs, movies) | ||||||
|                 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 arrayAdapter = ArrayAdapter<String>(searchView.context, R.layout.sort_bottom_single_choice) |                     cancelBtt?.setOnClickListener { | ||||||
|                 arrayAdapter.addAll(apiNames) |                         dialog.dismissSafe() | ||||||
| 
 |  | ||||||
|                 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()) { |                     cancelBtt?.setOnClickListener { | ||||||
|                     listView2.setItemChecked(index, item.second.any { typesActive.contains(it) }) |                         dialog.dismissSafe() | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                 fun toggleSearch(isOn: Boolean) { |                     applyBtt?.setOnClickListener { | ||||||
|                     toggle.text = |                         //if (currentApiName != selectedApiName) { | ||||||
|                         getString(if (isOn) R.string.search_provider_text_types else R.string.search_provider_text_providers) |                         //    currentApiName?.let(callback) | ||||||
| 
 |                         //} | ||||||
|                     if (isOn) { |                         dialog.dismissSafe() | ||||||
|                         listView2.visibility = View.VISIBLE |  | ||||||
|                         listView.visibility = View.GONE |  | ||||||
|                     } else { |  | ||||||
|                         listView.visibility = View.VISIBLE |  | ||||||
|                         listView2.visibility = View.GONE |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 val defVal = context?.getKey(SEARCH_PROVIDER_TOGGLE, true) ?: true |  | ||||||
|                 toggleSearch(defVal) |  | ||||||
| 
 |  | ||||||
|                 toggle.isChecked = defVal |  | ||||||
|                 toggle.setOnCheckedChangeListener { _, isOn -> |  | ||||||
|                     toggleSearch(isOn) |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 listView.setOnItemClickListener { _, _, _, _ -> |  | ||||||
|                     val types = HashSet<TvType>() |  | ||||||
|                     for ((index, api) in apis.withIndex()) { |  | ||||||
|                         if (listView.checkedItemPositions[index]) { |  | ||||||
|                             types.addAll(api.supportedTypes) |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     for ((typeIndex, type) in typeChoices.withIndex()) { |  | ||||||
|                         listView2.setItemChecked(typeIndex, type.second.any { types.contains(it) }) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 listView2.setOnItemClickListener { _, _, _, _ -> |  | ||||||
|                     for ((index, api) in apis.withIndex()) { |  | ||||||
|                         var isSupported = false |  | ||||||
| 
 |  | ||||||
|                         for ((typeIndex, type) in typeChoices.withIndex()) { |  | ||||||
|                             if (listView2.checkedItemPositions[typeIndex]) { |  | ||||||
|                                 if (api.supportedTypes.any { type.second.contains(it) }) { |  | ||||||
|                                     isSupported = true |  | ||||||
|                                 } |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         listView.setItemChecked( |  | ||||||
|                             index, |  | ||||||
|                             isSupported |  | ||||||
|                         ) |  | ||||||
|                     } |  | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     dialog.setOnDismissListener { |                     dialog.setOnDismissListener { | ||||||
|                     context?.setKey(SEARCH_PROVIDER_TOGGLE, toggle.isChecked) |                         context?.setKey(SEARCH_PREF_PROVIDERS, currentSelectedApis.toList()) | ||||||
|  |                         selectedApis = currentSelectedApis | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                 applyButton.setOnClickListener { |                     val selectedSearchTypes = context?.getKey<List<String>>(SEARCH_PREF_TAGS) | ||||||
|                     val settingsManagerLocal = PreferenceManager.getDefaultSharedPreferences(activity) |                         ?.mapNotNull { listName -> | ||||||
| 
 |                             TvType.values().firstOrNull { it.name == listName } | ||||||
|                     val activeTypes = HashSet<TvType>() |  | ||||||
|                     for ((index, _) in typeChoices.withIndex()) { |  | ||||||
|                         if (listView2.checkedItemPositions[index]) { |  | ||||||
|                             activeTypes.addAll(typeChoices[index].second) |  | ||||||
|                         } |  | ||||||
|                         } |                         } | ||||||
|  |                         ?.toMutableList() | ||||||
|  |                         ?: mutableListOf(TvType.Movie, TvType.TvSeries) | ||||||
| 
 | 
 | ||||||
|                     if (activeTypes.size == 0) { |                     val listView = dialog.findViewById<ListView>(R.id.listview1) | ||||||
|                         activeTypes.addAll(TvType.values()) |                     val arrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice) | ||||||
|                     } |                     listView?.adapter = arrayAdapter | ||||||
|  |                     listView?.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE | ||||||
| 
 | 
 | ||||||
| 
 |                     listView?.setOnItemClickListener { _, _, i, _ -> | ||||||
|                     val activeApis = HashSet<String>() |                         if (!currentValidApis.isNullOrEmpty()) { | ||||||
|                     for ((index, name) in apiNames.withIndex()) { |                             val api = currentValidApis[i].name | ||||||
|                         if (listView.checkedItemPositions[index]) { |                             if (currentSelectedApis.contains(api)) { | ||||||
|                             activeApis.add(name) |                                 listView.setItemChecked(i, false) | ||||||
|                         } |                                 currentSelectedApis -= api | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     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]) |  | ||||||
|                             } else { |                             } else { | ||||||
|                             apiNamesSettingLocal.remove(apiNames[position]) |                                 listView.setItemChecked(i, true) | ||||||
|  |                                 currentSelectedApis += api | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                         val edit = settingsManagerLocal.edit() |                     fun updateList() { | ||||||
|                         edit.putStringSet( |                         arrayAdapter.clear() | ||||||
|                             getString(R.string.search_providers_list_key), |                         currentValidApis = validAPIs.filter { api -> | ||||||
|                             apiNames.filter { a -> apiNamesSettingLocal.contains(a) }.toSet() |                             api.hasMainPage && api.supportedTypes.any { | ||||||
|  |                                 selectedSearchTypes.contains(it) | ||||||
|  |                             } | ||||||
|  |                         }.sortedBy { it.name } | ||||||
|  | 
 | ||||||
|  |                         val names = currentValidApis.map { it.name } | ||||||
|  | 
 | ||||||
|  |                         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() | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         val pairList = HomeFragment.getPairList( | ||||||
|  |             search_select_anime, | ||||||
|  |             search_select_cartoons, | ||||||
|  |             search_select_tv_series, | ||||||
|  |             search_select_documentaries, | ||||||
|  |             search_select_movies | ||||||
|         ) |         ) | ||||||
|                         edit.apply() | 
 | ||||||
|                         providersActive = apiNamesSettingLocal |         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) } | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     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 | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     button?.setOnLongClickListener { | ||||||
|  |                         if (!buttonContains()) { | ||||||
|  |                             it?.isSelected = true | ||||||
|  |                             selectedSearchTypes.addAll(validTypes) | ||||||
|  |                         } else { | ||||||
|  |                             it?.isSelected = false | ||||||
|  |                             selectedSearchTypes.removeAll(validTypes) | ||||||
|  |                         } | ||||||
|  |                         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 { |         main_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener { | ||||||
|             override fun onQueryTextSubmit(query: String): Boolean { |             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 { |                 main_search?.let { | ||||||
|                     hideKeyboard(it) |                     hideKeyboard(it) | ||||||
|  | @ -363,10 +373,6 @@ class SearchFragment : Fragment() { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         activity?.let { |  | ||||||
|             providersActive = it.getApiSettings() |  | ||||||
|             typesActive = it.getApiTypeSettings() |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         /*main_search.setOnQueryTextFocusChangeListener { _, b -> |         /*main_search.setOnQueryTextFocusChangeListener { _, b -> | ||||||
|             if (b) { |             if (b) { | ||||||
|  | @ -376,20 +382,21 @@ class SearchFragment : Fragment() { | ||||||
|         }*/ |         }*/ | ||||||
|         //main_search.onActionViewExpanded()*/ |         //main_search.onActionViewExpanded()*/ | ||||||
| 
 | 
 | ||||||
|         val masterAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = ParentItemAdapter(listOf(), { callback -> |         val masterAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = | ||||||
|  |             ParentItemAdapter(listOf(), { callback -> | ||||||
|                 SearchHelper.handleSearchClickCallback(activity, callback) |                 SearchHelper.handleSearchClickCallback(activity, callback) | ||||||
|             }, { item -> |             }, { item -> | ||||||
|                 activity?.loadHomepageList(item) |                 activity?.loadHomepageList(item) | ||||||
|             }) |             }) | ||||||
| 
 | 
 | ||||||
|         search_master_recycler.adapter = masterAdapter |         search_master_recycler?.adapter = masterAdapter | ||||||
|         search_master_recycler.layoutManager = GridLayoutManager(context, 1) |         search_master_recycler?.layoutManager = GridLayoutManager(context, 1) | ||||||
| 
 | 
 | ||||||
|         val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) |         val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) | ||||||
|         val isAdvancedSearch = settingsManager.getBoolean("advanced_search", true) |         val isAdvancedSearch = settingsManager.getBoolean("advanced_search", true) | ||||||
| 
 | 
 | ||||||
|         search_master_recycler.isVisible = isAdvancedSearch |         search_master_recycler?.isVisible = isAdvancedSearch | ||||||
|         search_autofit_results.isVisible = !isAdvancedSearch |         search_autofit_results?.isVisible = !isAdvancedSearch | ||||||
| 
 | 
 | ||||||
|         // SubtitlesFragment.push(activity) |         // SubtitlesFragment.push(activity) | ||||||
|         //searchViewModel.search("iron man") |         //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.OAuth2API.Companion.SyncApis | ||||||
| import com.lagradost.cloudstream3.syncproviders.SyncAPI | import com.lagradost.cloudstream3.syncproviders.SyncAPI | ||||||
| import com.lagradost.cloudstream3.ui.APIRepository | import com.lagradost.cloudstream3.ui.APIRepository | ||||||
| import com.lagradost.cloudstream3.ui.APIRepository.Companion.providersActive |  | ||||||
| import kotlinx.coroutines.Dispatchers | import kotlinx.coroutines.Dispatchers | ||||||
| import kotlinx.coroutines.Job | import kotlinx.coroutines.Job | ||||||
| import kotlinx.coroutines.launch | import kotlinx.coroutines.launch | ||||||
|  | @ -26,7 +25,8 @@ data class OnGoingSearch( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| class SearchViewModel : ViewModel() { | 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 |     val searchResponse: LiveData<Resource<ArrayList<SearchResponse>>> get() = _searchResponse | ||||||
| 
 | 
 | ||||||
|     private val _currentSearch: MutableLiveData<ArrayList<OnGoingSearch>> = MutableLiveData() |     private val _currentSearch: MutableLiveData<ArrayList<OnGoingSearch>> = MutableLiveData() | ||||||
|  | @ -40,9 +40,14 @@ class SearchViewModel : ViewModel() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     var onGoingSearch: Job? = null |     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?.cancel() | ||||||
|         onGoingSearch = search(query, isMainApis, ignoreSettings) |         onGoingSearch = search(query, isMainApis, providersActive, ignoreSettings) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     data class SyncSearchResultSearchResponse( |     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 { |         viewModelScope.launch { | ||||||
|             if (query.length <= 1) { |             if (query.length <= 1) { | ||||||
|                 clearSearch() |                 clearSearch() | ||||||
|  | @ -81,7 +91,7 @@ class SearchViewModel : ViewModel() { | ||||||
|             withContext(Dispatchers.IO) { // This interrupts UI otherwise |             withContext(Dispatchers.IO) { // This interrupts UI otherwise | ||||||
|                 if (isMainApis) { |                 if (isMainApis) { | ||||||
|                     repos.filter { a -> |                     repos.filter { a -> | ||||||
|                         ignoreSettings || (providersActive.size == 0 || providersActive.contains(a.name)) |                         ignoreSettings || (providersActive.isEmpty() || providersActive.contains(a.name)) | ||||||
|                     }.apmap { a -> // Parallel |                     }.apmap { a -> // Parallel | ||||||
|                         val search = a.search(query) |                         val search = a.search(query) | ||||||
|                         currentList.add(OnGoingSearch(a.name, search)) |                         currentList.add(OnGoingSearch(a.name, search)) | ||||||
|  | @ -102,7 +112,8 @@ class SearchViewModel : ViewModel() { | ||||||
| 
 | 
 | ||||||
|             val list = ArrayList<SearchResponse>() |             val list = ArrayList<SearchResponse>() | ||||||
|             val nestedList = |             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 |             // I do it this way to move the relevant search results to the top | ||||||
|             var index = 0 |             var index = 0 | ||||||
|  |  | ||||||
|  | @ -22,7 +22,6 @@ import com.hippo.unifile.UniFile | ||||||
| import com.lagradost.cloudstream3.APIHolder.apis | import com.lagradost.cloudstream3.APIHolder.apis | ||||||
| import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings | import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings | ||||||
| import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings | import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings | ||||||
| import com.lagradost.cloudstream3.APIHolder.getApiSettings |  | ||||||
| import com.lagradost.cloudstream3.APIHolder.restrictedApis | import com.lagradost.cloudstream3.APIHolder.restrictedApis | ||||||
| import com.lagradost.cloudstream3.AcraApplication | import com.lagradost.cloudstream3.AcraApplication | ||||||
| import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey | import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey | ||||||
|  | @ -294,7 +293,7 @@ class SettingsFragment : PreferenceFragmentCompat() { | ||||||
|                         this.getString(R.string.provider_lang_key), |                         this.getString(R.string.provider_lang_key), | ||||||
|                         selectedList.map { names[it].first }.toMutableSet() |                         selectedList.map { names[it].first }.toMutableSet() | ||||||
|                     ).apply() |                     ).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.USER_AGENT | ||||||
| import com.lagradost.cloudstream3.app | import com.lagradost.cloudstream3.app | ||||||
| import com.lagradost.cloudstream3.extractors.* | 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 | import org.jsoup.Jsoup | ||||||
| 
 | 
 | ||||||
| data class ExtractorLink( | 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. |  * 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) { |     for (extractor in extractorApis) { | ||||||
|         if (url.startsWith(extractor.mainUrl)) { |         if (url.startsWith(extractor.mainUrl)) { | ||||||
|             extractor.getSafeUrl(url, referer)?.forEach(callback) |             extractor.getSafeUrl(url, referer)?.forEach(callback) | ||||||
|  | @ -138,7 +139,7 @@ fun httpsify(url: String): String { | ||||||
|     return if (url.startsWith("//")) "https:$url" else url |     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 document = Jsoup.parse(html) | ||||||
|     val inputs = document.select("Form > input") |     val inputs = document.select("Form > input") | ||||||
|     if (inputs.size < 4) return null |     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) { |     if (op == null || id == null || mode == null || hash == null) { | ||||||
|         return 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( |     val postResponse = app.post( | ||||||
|         requestUrl, |         requestUrl, | ||||||
|  | @ -181,14 +182,14 @@ abstract class ExtractorApi { | ||||||
|     abstract val mainUrl: String |     abstract val mainUrl: String | ||||||
|     abstract val requiresReferer: Boolean |     abstract val requiresReferer: Boolean | ||||||
| 
 | 
 | ||||||
|     fun getSafeUrl(url: String, referer: String? = null): List<ExtractorLink>? { |     suspend fun getSafeUrl(url: String, referer: String? = null): List<ExtractorLink>? { | ||||||
|         return normalSafeApiCall { getUrl(url, referer) } |         return suspendSafeApiCall { getUrl(url, referer) } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Will throw errors, use getSafeUrl if you don't want to handle the exception yourself |      * 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 { |     open fun getExtractorUrl(id: String): String { | ||||||
|         return id |         return id | ||||||
|  |  | ||||||
|  | @ -16,7 +16,7 @@ object FillerEpisodeCheck { | ||||||
|         return name.lowercase(Locale.ROOT)/*.replace(" ", "")*/.replace("-", " ").replace("[^a-zA-Z0-9 ]".toRegex(), "") |         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 |         if (list != null) return true | ||||||
|         try { |         try { | ||||||
|             val result = app.get("$MAIN_URL/shows").text |             val result = app.get("$MAIN_URL/shows").text | ||||||
|  | @ -59,7 +59,7 @@ object FillerEpisodeCheck { | ||||||
|         return q + "cache" + z |         return q + "cache" + z | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun getFillerEpisodes(query: String): HashMap<Int, Boolean>? { |     suspend fun getFillerEpisodes(query: String): HashMap<Int, Boolean>? { | ||||||
|         try { |         try { | ||||||
|             if (!getFillerList()) return null |             if (!getFillerList()) return null | ||||||
|             val localList = list ?: 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.app | ||||||
| import com.lagradost.cloudstream3.mvvm.logError | import com.lagradost.cloudstream3.mvvm.logError | ||||||
| import com.lagradost.cloudstream3.mvvm.normalSafeApiCall | import com.lagradost.cloudstream3.mvvm.normalSafeApiCall | ||||||
|  | import kotlinx.coroutines.runBlocking | ||||||
| import java.io.File | import java.io.File | ||||||
| import kotlin.concurrent.thread | import kotlin.concurrent.thread | ||||||
| 
 | 
 | ||||||
|  | @ -83,7 +84,12 @@ class InAppUpdater { | ||||||
|             val url = "https://api.github.com/repos/LagradOst/CloudStream-3/releases" |             val url = "https://api.github.com/repos/LagradOst/CloudStream-3/releases" | ||||||
|             val headers = mapOf("Accept" to "application/vnd.github.v3+json") |             val headers = mapOf("Accept" to "application/vnd.github.v3+json") | ||||||
|             val response = |             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 versionRegex = Regex("""(.*?((\d+)\.(\d+)\.(\d+))\.apk)""") | ||||||
|             val versionRegexLocal = Regex("""(.*?((\d+)\.(\d+)\.(\d+)).*)""") |             val versionRegexLocal = Regex("""(.*?((\d+)\.(\d+)\.(\d+)).*)""") | ||||||
|  | @ -139,7 +145,7 @@ class InAppUpdater { | ||||||
|             return Update(false, null, null, null) |             return Update(false, null, null, null) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private fun Activity.getPreReleaseUpdate(): Update { |         private fun Activity.getPreReleaseUpdate(): Update = runBlocking { | ||||||
|             val tagUrl = |             val tagUrl = | ||||||
|                 "https://api.github.com/repos/LagradOst/CloudStream-3/git/ref/tags/pre-release" |                 "https://api.github.com/repos/LagradOst/CloudStream-3/git/ref/tags/pre-release" | ||||||
|             val releaseUrl = "https://api.github.com/repos/LagradOst/CloudStream-3/releases" |             val releaseUrl = "https://api.github.com/repos/LagradOst/CloudStream-3/releases" | ||||||
|  | @ -159,7 +165,7 @@ class InAppUpdater { | ||||||
|             val shouldUpdate = |             val shouldUpdate = | ||||||
|                 (getString(R.string.prerelease_commit_hash) != tagResponse.github_object.sha) |                 (getString(R.string.prerelease_commit_hash) != tagResponse.github_object.sha) | ||||||
| 
 | 
 | ||||||
|             return if (foundAsset != null) { |             return@runBlocking if (foundAsset != null) { | ||||||
|                 Update( |                 Update( | ||||||
|                     shouldUpdate, |                     shouldUpdate, | ||||||
|                     foundAsset.browser_download_url, |                     foundAsset.browser_download_url, | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ package com.lagradost.cloudstream3.utils | ||||||
| 
 | 
 | ||||||
| import com.lagradost.cloudstream3.app | import com.lagradost.cloudstream3.app | ||||||
| import com.lagradost.cloudstream3.mvvm.logError | import com.lagradost.cloudstream3.mvvm.logError | ||||||
| import com.lagradost.cloudstream3.network.text | import kotlinx.coroutines.runBlocking | ||||||
| import javax.crypto.Cipher | import javax.crypto.Cipher | ||||||
| import javax.crypto.spec.IvParameterSpec | import javax.crypto.spec.IvParameterSpec | ||||||
| import javax.crypto.spec.SecretKeySpec | import javax.crypto.spec.SecretKeySpec | ||||||
|  | @ -82,7 +82,9 @@ class M3u8Helper { | ||||||
|     fun m3u8Generation(m3u8: M3u8Stream, returnThis: Boolean): List<M3u8Stream> { |     fun m3u8Generation(m3u8: M3u8Stream, returnThis: Boolean): List<M3u8Stream> { | ||||||
|         val generate = sequence { |         val generate = sequence { | ||||||
|             val m3u8Parent = getParentLink(m3u8.streamUrl) |             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)) { |             for (match in QUALITY_REGEX.findAll(response)) { | ||||||
|                 var (quality, m3u8Link, m3u8Link2) = match.destructured |                 var (quality, m3u8Link, m3u8Link2) = match.destructured | ||||||
|  | @ -146,7 +148,7 @@ class M3u8Helper { | ||||||
| 
 | 
 | ||||||
|         val secondSelection = selectBest(streams.ifEmpty { listOf(selected) }) |         val secondSelection = selectBest(streams.ifEmpty { listOf(selected) }) | ||||||
|         if (secondSelection != null) { |         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 encryptionUri: String? | ||||||
|             var encryptionIv = byteArrayOf() |             var encryptionIv = byteArrayOf() | ||||||
|  | @ -164,7 +166,7 @@ class M3u8Helper { | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 encryptionIv = match.component3().toByteArray() |                 encryptionIv = match.component3().toByteArray() | ||||||
|                 val encryptionKeyResponse = app.get(encryptionUri, headers = headers) |                 val encryptionKeyResponse = runBlocking { app.get(encryptionUri, headers = headers) } | ||||||
|                 encryptionData = encryptionKeyResponse.body?.bytes() ?: byteArrayOf() |                 encryptionData = encryptionKeyResponse.body?.bytes() ?: byteArrayOf() | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | @ -187,7 +189,7 @@ class M3u8Helper { | ||||||
| 
 | 
 | ||||||
|                     while (lastYield != c) { |                     while (lastYield != c) { | ||||||
|                         try { |                         try { | ||||||
|                             val tsResponse = app.get(url, headers = headers) |                             val tsResponse = runBlocking { app.get(url, headers = headers) } | ||||||
|                             var tsData = tsResponse.body?.bytes() ?: byteArrayOf() |                             var tsData = tsResponse.body?.bytes() ?: byteArrayOf() | ||||||
| 
 | 
 | ||||||
|                             if (encryptionState) { |                             if (encryptionState) { | ||||||
|  |  | ||||||
|  | @ -5,13 +5,12 @@ import com.fasterxml.jackson.module.kotlin.readValue | ||||||
| import com.lagradost.cloudstream3.app | import com.lagradost.cloudstream3.app | ||||||
| import com.lagradost.cloudstream3.mapper | import com.lagradost.cloudstream3.mapper | ||||||
| import com.lagradost.cloudstream3.mvvm.logError | import com.lagradost.cloudstream3.mvvm.logError | ||||||
| import com.lagradost.cloudstream3.network.text |  | ||||||
| import java.util.concurrent.TimeUnit | import java.util.concurrent.TimeUnit | ||||||
| 
 | 
 | ||||||
| object SyncUtil { | object SyncUtil { | ||||||
|     /** first. Mal, second. Anilist, |     /** first. Mal, second. Anilist, | ||||||
|      * valid sites are: Gogoanime, Twistmoe and 9anime*/ |      * 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 { |         try { | ||||||
|             //Gogoanime, Twistmoe and 9anime |             //Gogoanime, Twistmoe and 9anime | ||||||
|             val url = |             val url = | ||||||
|  |  | ||||||
|  | @ -81,6 +81,59 @@ | ||||||
|                 app:tint="?attr/textColor" |                 app:tint="?attr/textColor" | ||||||
|                 android:contentDescription="@string/change_providers_img_des" /> |                 android:contentDescription="@string/change_providers_img_des" /> | ||||||
|     </FrameLayout> |     </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 |     <com.lagradost.cloudstream3.ui.AutofitRecyclerView | ||||||
|             android:nextFocusLeft="@id/nav_rail_view" |             android:nextFocusLeft="@id/nav_rail_view" | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ class ProviderTests { | ||||||
|         return allApis.filter { !it.usesWebView } |         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) |         Assert.assertNotNull("Api ${api.name} has invalid url on episode", url) | ||||||
|         if (url == null) return true |         if (url == null) return true | ||||||
|         var linksLoaded = 0 |         var linksLoaded = 0 | ||||||
|  | @ -39,7 +39,7 @@ class ProviderTests { | ||||||
|         return true |         return true | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun testSingleProviderApi(api: MainAPI): Boolean { |     private suspend fun testSingleProviderApi(api: MainAPI): Boolean { | ||||||
|         val searchQueries = listOf("over", "iron", "guy") |         val searchQueries = listOf("over", "iron", "guy") | ||||||
|         var correctResponses = 0 |         var correctResponses = 0 | ||||||
|         var searchResult: List<SearchResponse>? = null |         var searchResult: List<SearchResponse>? = null | ||||||
|  | @ -144,7 +144,7 @@ class ProviderTests { | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|     fun providerCorrectHomepage() { |     fun providerCorrectHomepage() { | ||||||
|         getAllProviders().pmap { api -> |         getAllProviders().apmap { api -> | ||||||
|             if (api.hasMainPage) { |             if (api.hasMainPage) { | ||||||
|                 try { |                 try { | ||||||
|                     val homepage = api.getMainPage() |                     val homepage = api.getMainPage() | ||||||
|  | @ -175,10 +175,10 @@ class ProviderTests { | ||||||
| //    } | //    } | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|     fun providerCorrect() { |     suspend fun providerCorrect() { | ||||||
|         val invalidProvider = ArrayList<Pair<MainAPI,Exception?>>() |         val invalidProvider = ArrayList<Pair<MainAPI,Exception?>>() | ||||||
|         val providers = getAllProviders() |         val providers = getAllProviders() | ||||||
|         providers.pmap { api -> |         providers.apmap { api -> | ||||||
|             try { |             try { | ||||||
|                 println("Trying $api") |                 println("Trying $api") | ||||||
|                 if (testSingleProviderApi(api)) { |                 if (testSingleProviderApi(api)) { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue