move to async get requests + remade search

This commit is contained in:
LagradOst 2022-01-29 22:25:12 +01:00
parent 06801ba6ab
commit 302185093c
53 changed files with 497 additions and 360 deletions

View File

@ -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,9 +35,11 @@ class CustomReportSender : ReportSender {
) )
thread { // to not run it on main thread thread { // to not run it on main thread
normalSafeApiCall { runBlocking {
val post = app.post(url, data = data) suspendSafeApiCall {
println("Report response: $post") val post = app.post(url, data = data)
println("Report response: $post")
}
} }
} }

View File

@ -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() }
//} }

View File

@ -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 {

View File

@ -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

View File

@ -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)
} }
}) })

View File

@ -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 ->

View File

@ -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 ->

View File

@ -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)

View File

@ -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)

View File

@ -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 ->

View File

@ -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 ->

View File

@ -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 ->

View File

@ -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) {

View File

@ -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)

View File

@ -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)) {

View File

@ -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,)}"

View File

@ -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 ->

View File

@ -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()

View File

@ -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(

View File

@ -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) {

View File

@ -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()) {

View File

@ -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""")

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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")

View File

@ -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")

View File

@ -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(

View File

@ -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 ->

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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 ->

View File

@ -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 {

View File

@ -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"

View File

@ -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()
} }
} }

View File

@ -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)
} }
} }

View File

@ -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

View File

@ -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?
} }

View File

@ -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)

View File

@ -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,

View File

@ -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() {

View File

@ -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)

View File

@ -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()) {
listView2.setItemChecked(index, item.second.any { typesActive.contains(it) })
}
fun toggleSearch(isOn: Boolean) {
toggle.text =
getString(if (isOn) R.string.search_provider_text_types else R.string.search_provider_text_providers)
if (isOn) {
listView2.visibility = View.VISIBLE
listView.visibility = View.GONE
} else {
listView.visibility = View.VISIBLE
listView2.visibility = View.GONE
} }
}
val defVal = context?.getKey(SEARCH_PROVIDER_TOGGLE, true) ?: true cancelBtt?.setOnClickListener {
toggleSearch(defVal) dialog.dismissSafe()
}
toggle.isChecked = defVal applyBtt?.setOnClickListener {
toggle.setOnCheckedChangeListener { _, isOn -> //if (currentApiName != selectedApiName) {
toggleSearch(isOn) // currentApiName?.let(callback)
} //}
dialog.dismissSafe()
}
listView.setOnItemClickListener { _, _, _, _ -> dialog.setOnDismissListener {
val types = HashSet<TvType>() context?.setKey(SEARCH_PREF_PROVIDERS, currentSelectedApis.toList())
for ((index, api) in apis.withIndex()) { selectedApis = currentSelectedApis
if (listView.checkedItemPositions[index]) { }
types.addAll(api.supportedTypes)
val selectedSearchTypes = context?.getKey<List<String>>(SEARCH_PREF_TAGS)
?.mapNotNull { listName ->
TvType.values().firstOrNull { it.name == listName }
} }
} ?.toMutableList()
for ((typeIndex, type) in typeChoices.withIndex()) { ?: mutableListOf(TvType.Movie, TvType.TvSeries)
listView2.setItemChecked(typeIndex, type.second.any { types.contains(it) })
}
}
listView2.setOnItemClickListener { _, _, _, _ -> val listView = dialog.findViewById<ListView>(R.id.listview1)
for ((index, api) in apis.withIndex()) { val arrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
var isSupported = false listView?.adapter = arrayAdapter
listView?.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
for ((typeIndex, type) in typeChoices.withIndex()) { listView?.setOnItemClickListener { _, _, i, _ ->
if (listView2.checkedItemPositions[typeIndex]) { if (!currentValidApis.isNullOrEmpty()) {
if (api.supportedTypes.any { type.second.contains(it) }) { val api = currentValidApis[i].name
isSupported = true if (currentSelectedApis.contains(api)) {
} listView.setItemChecked(i, false)
currentSelectedApis -= api
} else {
listView.setItemChecked(i, true)
currentSelectedApis += api
} }
} }
listView.setItemChecked(
index,
isSupported
)
} }
}
dialog.setOnDismissListener { fun updateList() {
context?.setKey(SEARCH_PROVIDER_TOGGLE, toggle.isChecked) arrayAdapter.clear()
} currentValidApis = validAPIs.filter { api ->
api.hasMainPage && api.supportedTypes.any {
selectedSearchTypes.contains(it)
}
}.sortedBy { it.name }
applyButton.setOnClickListener { val names = currentValidApis.map { it.name }
val settingsManagerLocal = PreferenceManager.getDefaultSharedPreferences(activity)
val activeTypes = HashSet<TvType>() for ((index, api) in names.withIndex()) {
for ((index, _) in typeChoices.withIndex()) { listView?.setItemChecked(index, currentSelectedApis.contains(api))
if (listView2.checkedItemPositions[index]) { }
activeTypes.addAll(typeChoices[index].second)
arrayAdapter.notifyDataSetChanged()
arrayAdapter.addAll(names)
arrayAdapter.notifyDataSetChanged()
}
for ((button, validTypes) in pairList) {
val isValid =
validAPIs.any { api -> validTypes.any { api.supportedTypes.contains(it) } }
button?.isVisible = isValid
if (isValid) {
fun buttonContains(): Boolean {
return selectedSearchTypes.any { validTypes.contains(it) }
}
button?.isSelected = buttonContains()
button?.setOnClickListener {
selectedSearchTypes.clear()
selectedSearchTypes.addAll(validTypes)
for ((otherButton, _) in pairList) {
otherButton?.isSelected = false
}
button.isSelected = true
updateList()
}
button?.setOnLongClickListener {
if (!buttonContains()) {
button.isSelected = true
selectedSearchTypes.addAll(validTypes)
} else {
button.isSelected = false
selectedSearchTypes.removeAll(validTypes)
}
updateList()
return@setOnLongClickListener true
}
} }
} }
updateList()
}
}
}
if (activeTypes.size == 0) { val pairList = HomeFragment.getPairList(
activeTypes.addAll(TvType.values()) search_select_anime,
search_select_cartoons,
search_select_tv_series,
search_select_documentaries,
search_select_movies
)
selectedSearchTypes = context?.getKey<List<String>>(SEARCH_PREF_TAGS)
?.mapNotNull { listName -> TvType.values().firstOrNull { it.name == listName } }
?.toMutableList()
?: mutableListOf(TvType.Movie, TvType.TvSeries)
context?.filterProviderByPreferredMedia()?.let { validAPIs ->
for ((button, validTypes) in pairList) {
val isValid =
validAPIs.any { api -> validTypes.any { api.supportedTypes.contains(it) } }
button?.isVisible = isValid
if (isValid) {
fun buttonContains(): Boolean {
return selectedSearchTypes.any { validTypes.contains(it) }
} }
button?.isSelected = buttonContains()
val activeApis = HashSet<String>() button?.setOnClickListener {
for ((index, name) in apiNames.withIndex()) { selectedSearchTypes.clear()
if (listView.checkedItemPositions[index]) { selectedSearchTypes.addAll(validTypes)
activeApis.add(name) for ((otherButton, _) in pairList) {
otherButton?.isSelected = false
} }
it?.context?.setKey(SEARCH_PREF_TAGS, selectedSearchTypes)
it?.isSelected = true
} }
if (activeApis.size == 0) { button?.setOnLongClickListener {
activeApis.addAll(apiNames) if (!buttonContains()) {
} it?.isSelected = true
selectedSearchTypes.addAll(validTypes)
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]) it?.isSelected = false
selectedSearchTypes.removeAll(validTypes)
} }
it?.context?.setKey(SEARCH_PREF_TAGS, selectedSearchTypes)
val edit = settingsManagerLocal.edit() return@setOnLongClickListener true
edit.putStringSet(
getString(R.string.search_providers_list_key),
apiNames.filter { a -> apiNamesSettingLocal.contains(a) }.toSet()
)
edit.apply()
providersActive = apiNamesSettingLocal
} }
} }
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> =
SearchHelper.handleSearchClickCallback(activity, callback) ParentItemAdapter(listOf(), { callback ->
}, { item -> SearchHelper.handleSearchClickCallback(activity, callback)
activity?.loadHomepageList(item) }, { 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")

View File

@ -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

View File

@ -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()
} }
} }

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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) {

View File

@ -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 =

View File

@ -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"

View File

@ -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)) {