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 com.google.auto.service.AutoService
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
import com.lagradost.cloudstream3.utils.DataStore.getKey
@ -12,6 +13,7 @@ import com.lagradost.cloudstream3.utils.DataStore.getKeys
import com.lagradost.cloudstream3.utils.DataStore.removeKey
import com.lagradost.cloudstream3.utils.DataStore.removeKeys
import com.lagradost.cloudstream3.utils.DataStore.setKey
import kotlinx.coroutines.runBlocking
import org.acra.ReportField
import org.acra.config.CoreConfiguration
import org.acra.data.CrashReportData
@ -33,11 +35,13 @@ class CustomReportSender : ReportSender {
)
thread { // to not run it on main thread
normalSafeApiCall {
runBlocking {
suspendSafeApiCall {
val post = app.post(url, data = data)
println("Report response: $post")
}
}
}
runOnMainThread { // to run it on main looper
normalSafeApiCall {

View file

@ -2,13 +2,9 @@ package com.lagradost.cloudstream3
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import kotlin.collections.ArrayList
//https://stackoverflow.com/questions/34697828/parallel-operations-on-kotlin-collections
/*
fun <T, R> Iterable<T>.pmap(
numThreads: Int = maxOf(Runtime.getRuntime().availableProcessors() - 2, 1),
exec: ExecutorService = Executors.newFixedThreadPool(numThreads),
@ -27,14 +23,14 @@ fun <T, R> Iterable<T>.pmap(
exec.awaitTermination(1, TimeUnit.DAYS)
return ArrayList<R>(destination)
}
}*/
fun <A, B> List<A>.apmap(f: suspend (A) -> B): List<B> = runBlocking {
map { async { f(it) } }.map { it.await() }
}
// run code in parallel
fun <R> argpmap(
/*fun <R> argpmap(
vararg transforms: () -> R,
numThreads: Int = maxOf(Runtime.getRuntime().availableProcessors() - 2, 1),
exec: ExecutorService = Executors.newFixedThreadPool(numThreads)
@ -45,10 +41,10 @@ fun <R> argpmap(
exec.shutdown()
exec.awaitTermination(1, TimeUnit.DAYS)
}
}*/
//fun <R> argamap(
// vararg transforms: () -> R,
//) = runBlocking {
// transforms.map { async { it.invoke() } }.map { it.await() }
//}
fun <R> argamap(
vararg transforms: suspend () -> R,
) = runBlocking {
transforms.map { async { it.invoke() } }.map { it.await() }
}

View file

@ -3,7 +3,7 @@ package com.lagradost.cloudstream3.animeproviders
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.readValue
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.network.AppResponse
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getQualityFromName
@ -23,7 +23,7 @@ class AnimePaheProvider : MainAPI() {
else TvType.Anime
}
fun generateSession(): Boolean {
suspend fun generateSession(): Boolean {
if (cookies.isNotEmpty()) return true
return try {
val response = app.get("$MAIN_URL/")
@ -124,7 +124,7 @@ class AnimePaheProvider : MainAPI() {
@JsonProperty("data") val data: List<AnimePaheSearchData>
)
private fun getAnimeByIdAndTitle(title: String, animeId: Int): String? {
private suspend fun getAnimeByIdAndTitle(title: String, animeId: Int): String? {
val url = "$mainUrl/api?m=search&l=8&q=$title"
val headers = mapOf("referer" to "$mainUrl/")
@ -186,7 +186,7 @@ class AnimePaheProvider : MainAPI() {
)
private fun generateListOfEpisodes(link: String): ArrayList<AnimeEpisode> {
private suspend fun generateListOfEpisodes(link: String): ArrayList<AnimeEpisode> {
try {
val attrs = link.split('/')
val id = attrs[attrs.size - 1].split("?")[0]
@ -243,8 +243,7 @@ class AnimePaheProvider : MainAPI() {
}
override suspend fun load(url: String): LoadResponse? {
return normalSafeApiCall {
return suspendSafeApiCall {
val regex = Regex("""a/(\d+)\?slug=(.+)""")
val (animeId, animeTitle) = regex.find(url)!!.destructured
val link = getAnimeByIdAndTitle(animeTitle, animeId.toInt())!!
@ -436,7 +435,7 @@ class AnimePaheProvider : MainAPI() {
@JsonProperty("data") val data: List<Map<String, VideoQuality>>
)
private fun bypassAdfly(adflyUri: String): String {
private suspend fun bypassAdfly(adflyUri: String): String {
if (!generateSession()) {
return bypassAdfly(adflyUri)
}
@ -461,7 +460,7 @@ class AnimePaheProvider : MainAPI() {
return decodeAdfly(YTSM.find(adflyContent?.text.toString())!!.destructured.component1())
}
private fun getStreamUrlFromKwik(adflyUri: String): String {
private suspend fun getStreamUrlFromKwik(adflyUri: String): String {
val fContent =
app.get(
bypassAdfly(adflyUri),
@ -496,7 +495,7 @@ class AnimePaheProvider : MainAPI() {
return content?.headers?.values("location").toString()
}
private fun extractVideoLinks(episodeLink: String): List<ExtractorLink> {
private suspend fun extractVideoLinks(episodeLink: String): List<ExtractorLink> {
var link = episodeLink
val headers = mapOf("referer" to "$mainUrl/")
@ -507,7 +506,6 @@ class AnimePaheProvider : MainAPI() {
val episodeNum = regex.find(link)?.destructured?.component1()?.toIntOrNull()
link = link.replace(regex, "")
val req = app.get(link, headers = headers).text
val jsonResponse = req.let { mapper.readValue<AnimePaheAnimeData>(it) }
val ep = ((jsonResponse.data.map {

View file

@ -54,7 +54,7 @@ class DubbedAnimeProvider : MainAPI() {
@JsonProperty("tags") val tags: String,*/
)
private fun parseDocumentTrending(url: String): List<SearchResponse> {
private suspend fun parseDocumentTrending(url: String): List<SearchResponse> {
val response = app.get(url).text
val document = Jsoup.parse(response)
return document.select("li > a").map {
@ -73,7 +73,7 @@ class DubbedAnimeProvider : MainAPI() {
}
}
private fun parseDocument(url: String, trimEpisode: Boolean = false): List<SearchResponse> {
private suspend fun parseDocument(url: String, trimEpisode: Boolean = false): List<SearchResponse> {
val response = app.get(url).text
val document = Jsoup.parse(response)
return document.select("a.grid__link").map {
@ -109,7 +109,7 @@ class DubbedAnimeProvider : MainAPI() {
}
private fun getAnimeEpisode(slug: String, isMovie: Boolean): EpisodeInfo {
private suspend fun getAnimeEpisode(slug: String, isMovie: Boolean): EpisodeInfo {
val url =
mainUrl + (if (isMovie) "/movies/jsonMovie" else "/xz/v3/jsonEpi") + ".php?slug=$slug&_=$unixTime"
val response = app.get(url).text

View file

@ -232,17 +232,17 @@ class GogoanimeProvider : MainAPI() {
val default: String? = null
)
private fun extractVideos(uri: String, callback: (ExtractorLink) -> Unit) {
private suspend fun extractVideos(uri: String, callback: (ExtractorLink) -> Unit) {
val doc = app.get(uri).document
val iframe = fixUrlNull(doc.selectFirst("div.play-video > iframe").attr("src")) ?: return
argpmap(
argamap(
{
val link = iframe.replace("streaming.php", "download")
val page = app.get(link, headers = mapOf("Referer" to iframe))
page.document.select(".dowload > a").pmap {
page.document.select(".dowload > a").apmap {
if (it.hasAttr("download")) {
val qual = if (it.text()
.contains("HDP")
@ -266,7 +266,7 @@ class GogoanimeProvider : MainAPI() {
}, {
val streamingResponse = app.get(iframe, headers = mapOf("Referer" to iframe))
val streamingDocument = streamingResponse.document
argpmap({
argamap({
streamingDocument.select(".list-server-items > .linkserver")
?.forEach { element ->
val status = element.attr("data-status") ?: return@forEach
@ -302,7 +302,7 @@ class GogoanimeProvider : MainAPI() {
sourceCallback.invoke(
ExtractorLink(
this.name,
"${this.name} ${source.label?.replace("0 P","0p") ?: ""}",
"${this.name} ${source.label?.replace("0 P", "0p") ?: ""}",
source.file,
"",
getQualityFromName(source.label ?: ""),
@ -312,11 +312,9 @@ class GogoanimeProvider : MainAPI() {
}
sources.source?.forEach {
println("${this.name} ${it.label ?: ""}")
invokeGogoSource(it, callback)
}
sources.sourceBk?.forEach {
println("${this.name} ${it.label ?: ""}")
invokeGogoSource(it, callback)
}
})

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 {
val jsonLink = "https://rapid-cloud.ru/ajax/${it[1]}/getSources?id=${it[2]}"
app.get(jsonLink).text
@ -295,7 +295,7 @@ class ZoroProvider : MainAPI() {
}
// Prevent duplicates
servers.distinctBy { it.second }.pmap {
servers.distinctBy { it.second }.apmap {
val link =
"$mainUrl/ajax/v2/episode/sources?id=${it.second}"
val extractorLink = app.get(
@ -316,7 +316,7 @@ class ZoroProvider : MainAPI() {
extractorLink
)
if (response.contains("<html")) return@pmap
if (response.contains("<html")) return@apmap
val mapped = mapper.readValue<SflixProvider.SourceObject>(response)
mapped.tracks?.forEach { track ->

View file

@ -10,7 +10,7 @@ class AsianLoad : ExtractorApi() {
override val requiresReferer = true
private val sourceRegex = Regex("""sources:[\W\w]*?file:\s*?["'](.*?)["']""")
override fun getUrl(url: String, referer: String?): List<ExtractorLink> {
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
val extractedLinksList: MutableList<ExtractorLink> = mutableListOf()
with(app.get(url, referer = referer)) {
sourceRegex.findAll(this.text).forEach { sourceMatch ->

View file

@ -4,7 +4,7 @@ import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
import java.lang.Thread.sleep
import kotlinx.coroutines.delay
class DoodToExtractor : DoodLaExtractor() {
override val mainUrl = "https://dood.to"
@ -28,13 +28,13 @@ open class DoodLaExtractor : ExtractorApi() {
return "$mainUrl/d/$id"
}
override fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
val id = url.removePrefix("$mainUrl/e/").removePrefix("$mainUrl/d/")
val trueUrl = getExtractorUrl(id)
val response = app.get(trueUrl).text
Regex("href=\".*/download/(.*?)\"").find(response)?.groupValues?.get(1)?.let { link ->
if (link.isEmpty()) return null
sleep(5000) // might need this to not trigger anti bot
delay(5000) // might need this to not trigger anti bot
val downloadLink = "$mainUrl/download/$link"
val downloadResponse = app.get(downloadLink).text
Regex("onclick=\"window\\.open\\((['\"])(.*?)(['\"])").find(downloadResponse)?.groupValues?.get(2)

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

View file

@ -13,7 +13,7 @@ class MixDrop : ExtractorApi() {
return "$mainUrl/e/$id"
}
override fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
with(app.get(url)) {
getAndUnpack(this.text).let { unpackedText ->
srcRegex.find(unpackedText)?.groupValues?.get(1)?.let { link ->

View file

@ -12,7 +12,7 @@ class Mp4Upload : ExtractorApi() {
private val srcRegex = Regex("""player\.src\("(.*?)"""")
override val requiresReferer = true
override fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
with(app.get(url)) {
getAndUnpack(this.text).let { unpackedText ->
srcRegex.find(unpackedText)?.groupValues?.get(1)?.let { link ->

View file

@ -19,7 +19,7 @@ class MultiQuality : ExtractorApi() {
return "$mainUrl/loadserver.php?id=$id"
}
override fun getUrl(url: String, referer: String?): List<ExtractorLink> {
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
val extractedLinksList: MutableList<ExtractorLink> = mutableListOf()
with(app.get(url)) {
sourceRegex.findAll(this.text).forEach { sourceMatch ->

View file

@ -1,8 +1,8 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.apmap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.pmap
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.extractorApis
import com.lagradost.cloudstream3.utils.getQualityFromName
@ -27,9 +27,9 @@ class Pelisplus(val mainUrl: String) {
private val normalApis = arrayListOf(MultiQuality())
// https://gogo-stream.com/streaming.php?id=MTE3NDg5
fun getUrl(id: String, isCasting: Boolean = false, callback: (ExtractorLink) -> Unit): Boolean {
suspend fun getUrl(id: String, isCasting: Boolean = false, callback: (ExtractorLink) -> Unit): Boolean {
try {
normalApis.pmap { api ->
normalApis.apmap { api ->
val url = api.getExtractorUrl(id)
val source = api.getSafeUrl(url)
source?.forEach { callback.invoke(it) }
@ -37,7 +37,7 @@ class Pelisplus(val mainUrl: String) {
val extractorUrl = getExtractorUrl(id)
/** Stolen from GogoanimeProvider.kt extractor */
normalSafeApiCall {
suspendSafeApiCall {
val link = getDownloadUrl(id)
println("Generated vidstream download link: $link")
val page = app.get(link, referer = extractorUrl)
@ -46,8 +46,8 @@ class Pelisplus(val mainUrl: String) {
val qualityRegex = Regex("(\\d+)P")
//a[download]
pageDoc.select(".dowload > a")?.pmap { element ->
val href = element.attr("href") ?: return@pmap
pageDoc.select(".dowload > a")?.apmap { element ->
val href = element.attr("href") ?: return@apmap
val qual = if (element.text()
.contains("HDP")
) "1080" else qualityRegex.find(element.text())?.destructured?.component1().toString()
@ -78,7 +78,7 @@ class Pelisplus(val mainUrl: String) {
//val name = element.text()
// Matches vidstream links with extractors
extractorApis.filter { !it.requiresReferer || !isCasting }.pmap { api ->
extractorApis.filter { !it.requiresReferer || !isCasting }.apmap { api ->
if (link.startsWith(api.mainUrl)) {
val extractedLinks = api.getSafeUrl(link, extractorUrl)
if (extractedLinks?.isNotEmpty() == true) {

View file

@ -26,7 +26,7 @@ open class SBPlay : ExtractorApi() {
override val name = "SBPlay"
override val requiresReferer = false
override fun getUrl(url: String, referer: String?): List<ExtractorLink> {
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
val response = app.get(url, referer = referer).text
val document = Jsoup.parse(response)

View file

@ -19,7 +19,7 @@ class StreamSB : ExtractorApi() {
override val requiresReferer = false
// https://sbembed.com/embed-ns50b0cukf9j.html -> https://sbvideo.net/play/ns50b0cukf9j
override fun getUrl(url: String, referer: String?): List<ExtractorLink> {
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
val extractedLinksList: MutableList<ExtractorLink> = mutableListOf()
val newUrl = url.replace("sbplay.org/embed-", "sbplay.org/play/").removeSuffix(".html")
with(app.get(newUrl, timeout = 10)) {

View file

@ -13,7 +13,7 @@ class StreamTape : ExtractorApi() {
private val linkRegex =
Regex("""'robotlink'\)\.innerHTML = '(.+?)'\+ \('(.+?)'\)""")
override fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
with(app.get(url)) {
linkRegex.find(this.text)?.let {
val extractedUrl = "https:${it.groups[1]!!.value + it.groups[2]!!.value.substring(3,)}"

View file

@ -16,7 +16,7 @@ class Streamhub : ExtractorApi() {
return "$mainUrl/e/$id"
}
override fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
val response = app.get(url).text
Regex("eval((.|\\n)*?)</script>").find(response)?.groupValues?.get(1)?.let { jsEval ->
JsUnpacker("eval$jsEval").unpack()?.let { unPacked ->

View file

@ -10,7 +10,7 @@ class UpstreamExtractor: ExtractorApi() {
override val mainUrl: String = "https://upstream.to"
override val requiresReferer = true
override fun getUrl(url: String, referer: String?): List<ExtractorLink> {
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
// WIP: m3u8 link fetched but sometimes not playing
//Log.i(this.name, "Result => (no extractor) ${url}")
val sources: MutableList<ExtractorLink> = mutableListOf()

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
override val requiresReferer = true
override fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
with(app.get(url)) { // raised error ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED (3003) is due to the response: "error_nofile"
srcRegex.find(this.text)?.groupValues?.get(1)?.replace("\"", "")?.let { link ->
return listOf(

View file

@ -1,8 +1,8 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.apmap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.pmap
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.extractorApis
import com.lagradost.cloudstream3.utils.getQualityFromName
@ -27,9 +27,9 @@ class Vidstream(val mainUrl: String) {
private val normalApis = arrayListOf(MultiQuality())
// https://gogo-stream.com/streaming.php?id=MTE3NDg5
fun getUrl(id: String, isCasting: Boolean = false, callback: (ExtractorLink) -> Unit): Boolean {
suspend fun getUrl(id: String, isCasting: Boolean = false, callback: (ExtractorLink) -> Unit): Boolean {
try {
normalApis.pmap { api ->
normalApis.apmap { api ->
val url = api.getExtractorUrl(id)
val source = api.getSafeUrl(url)
source?.forEach { callback.invoke(it) }
@ -37,7 +37,7 @@ class Vidstream(val mainUrl: String) {
val extractorUrl = getExtractorUrl(id)
/** Stolen from GogoanimeProvider.kt extractor */
normalSafeApiCall {
suspendSafeApiCall {
val link = getDownloadUrl(id)
println("Generated vidstream download link: $link")
val page = app.get(link, referer = extractorUrl)
@ -46,8 +46,8 @@ class Vidstream(val mainUrl: String) {
val qualityRegex = Regex("(\\d+)P")
//a[download]
pageDoc.select(".dowload > a")?.pmap { element ->
val href = element.attr("href") ?: return@pmap
pageDoc.select(".dowload > a")?.apmap { element ->
val href = element.attr("href") ?: return@apmap
val qual = if (element.text()
.contains("HDP")
) "1080" else qualityRegex.find(element.text())?.destructured?.component1().toString()
@ -78,7 +78,7 @@ class Vidstream(val mainUrl: String) {
//val name = element.text()
// Matches vidstream links with extractors
extractorApis.filter { !it.requiresReferer || !isCasting }.pmap { api ->
extractorApis.filter { !it.requiresReferer || !isCasting }.apmap { api ->
if (link.startsWith(api.mainUrl)) {
val extractedLinks = api.getSafeUrl(link, extractorUrl)
if (extractedLinks?.isNotEmpty() == true) {

View file

@ -19,7 +19,7 @@ open class VoeExtractor : ExtractorApi() {
//val type: String // Mp4
)
override fun getUrl(url: String, referer: String?): List<ExtractorLink> {
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
val extractedLinksList: MutableList<ExtractorLink> = mutableListOf()
val doc = app.get(url).text
if (doc.isNotEmpty()) {

View file

@ -12,7 +12,7 @@ open class WatchSB : ExtractorApi() {
override val mainUrl = "https://watchsb.com"
override val requiresReferer = false
override fun getUrl(url: String, referer: String?): List<ExtractorLink> {
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
val response = app.get(
url, interceptor = WebViewResolver(
Regex("""master\.m3u8""")

View file

@ -12,7 +12,7 @@ class WcoStream : ExtractorApi() {
override val requiresReferer = false
private val hlsHelper = M3u8Helper()
override fun getUrl(url: String, referer: String?): List<ExtractorLink> {
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
val baseUrl = url.split("/e/")[0]
val html = app.get(url, headers = mapOf("Referer" to "https://wcostream.cc/")).text

View file

@ -29,7 +29,7 @@ open class XStreamCdn : ExtractorApi() {
return "$domainUrl/api/source/$id"
}
override fun getUrl(url: String, referer: String?): List<ExtractorLink> {
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
val headers = mapOf(
"Referer" to url,
"User-Agent" to "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0",

View file

@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor
class AsianEmbedHelper {
companion object {
fun getUrls(url: String, callback: (ExtractorLink) -> Unit) {
suspend fun getUrls(url: String, callback: (ExtractorLink) -> Unit) {
if (url.startsWith("https://asianembed.io")) {
// Fetch links
val doc = app.get(url).document

View file

@ -1,6 +1,5 @@
package com.lagradost.cloudstream3.extractors.helper
import android.util.Log
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
@ -11,7 +10,7 @@ class VstreamhubHelper {
private val baseUrl: String = "https://vstreamhub.com"
private val baseName: String = "Vstreamhub"
fun getUrls(url: String, callback: (ExtractorLink) -> Unit) {
suspend fun getUrls(url: String, callback: (ExtractorLink) -> Unit) {
if (url.startsWith(baseUrl)) {
// Fetch links
val doc = app.get(url).document.select("script")

View file

@ -41,7 +41,7 @@ class AkwamProvider : MainAPI() {
"Series" to "$mainUrl/series",
"Shows" to "$mainUrl/shows"
)
val pages = moviesUrl.pmap {
val pages = moviesUrl.apmap {
val doc = app.get(it.second).document
val list = doc.select("div.col-lg-auto.col-md-4.col-6.mb-12").mapNotNull { element ->
element.toSearchResponse()
@ -150,7 +150,7 @@ class AkwamProvider : MainAPI() {
// Maybe possible to not use the url shortener but cba investigating that.
private fun skipUrlShortener(url: String): AppResponse {
private suspend fun skipUrlShortener(url: String): AppResponse {
return app.get(app.get(url).document.select("a.download-link").attr("href"))
}
@ -180,7 +180,7 @@ class AkwamProvider : MainAPI() {
}.filter { link -> link.first.contains("/link/") }
}.flatten()
links.pmap {
links.map {
val linkDoc = skipUrlShortener(it.first).document
val button = linkDoc.select("div.btn-loader > a")
val url = button.attr("href")

View file

@ -110,7 +110,7 @@ class LookMovieProvider : MainAPI() {
}
override suspend fun search(query: String): List<SearchResponse> {
fun search(query: String, isMovie: Boolean): ArrayList<SearchResponse> {
suspend fun search(query: String, isMovie: Boolean): ArrayList<SearchResponse> {
val url = "$mainUrl/${if (isMovie) "movies" else "shows"}/search/?q=$query"
val response = app.get(url).text
val document = Jsoup.parse(response)
@ -158,7 +158,7 @@ class LookMovieProvider : MainAPI() {
}
}
private fun loadCurrentLinks(url: String, callback: (ExtractorLink) -> Unit) {
private suspend fun loadCurrentLinks(url: String, callback: (ExtractorLink) -> Unit) {
val response = app.get(url.replace("\$unixtime", unixTime.toString())).text
M3u8Manifest.extractLinks(response).forEach {
callback.invoke(

View file

@ -151,7 +151,7 @@ open class PelisplusProviderTemplate : MainAPI() {
val urls = homePageUrlList
val homePageList = ArrayList<HomePageList>()
// .pmap {} is used to fetch the different pages in parallel
urls.pmap { url ->
urls.apmap { url ->
val response = app.get(url, timeout = 20).text
val document = Jsoup.parse(response)
document.select("div.main-inner")?.forEach { inner ->

View file

@ -228,7 +228,7 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() {
}
} ?: tryParseJson<List<String>>(data))?.distinct()
urls?.pmap { url ->
urls?.apmap { url ->
val sources = app.get(
url,
interceptor = WebViewResolver(

View file

@ -59,7 +59,7 @@ class VfFilmProvider : MainAPI() {
return true
}
private fun getDirect(original: String): String { // original data, https://vf-film.org/?trembed=1&trid=55313&trtype=1 for example
private suspend fun getDirect(original: String): String { // original data, https://vf-film.org/?trembed=1&trid=55313&trtype=1 for example
val response = app.get(original).text
val url = "iframe .*src=\"(.*?)\"".toRegex().find(response)?.groupValues?.get(1)
.toString() // https://vudeo.net/embed-uweno86lzx8f.html for example

View file

@ -42,7 +42,7 @@ class VfSerieProvider : MainAPI() {
return returnValue
}
private fun getDirect(original: String): String { // original data, https://vf-serie.org/?trembed=1&trid=80467&trtype=2 for example
private suspend fun getDirect(original: String): String { // original data, https://vf-serie.org/?trembed=1&trid=80467&trtype=2 for example
val response = app.get(original).text
val url = "iframe .*src=\"(.*?)\"".toRegex().find(response)?.groupValues?.get(1)
.toString() // https://vudeo.net/embed-7jdb1t5b2mvo.html for example

View file

@ -148,7 +148,7 @@ open class VidstreamProviderTemplate : MainAPI() {
val urls = homePageUrlList
val homePageList = ArrayList<HomePageList>()
// .pmap {} is used to fetch the different pages in parallel
urls.pmap { url ->
urls.apmap { url ->
val response = app.get(url, timeout = 20).text
val document = Jsoup.parse(response)
document.select("div.main-inner")?.forEach { inner ->

View file

@ -191,7 +191,7 @@ class WatchAsianProvider : MainAPI() {
return count > 0
}
private fun getServerLinks(url: String) : String {
private suspend fun getServerLinks(url: String) : String {
val moviedoc = app.get(url, referer = mainUrl).document
return moviedoc.select("div.anime_muti_link > ul > li")
?.mapNotNull {

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> {
val stackTraceMsg = (throwable.localizedMessage ?: "") + "\n\n" + throwable.stackTrace.joinToString(
separator = "\n"

View file

@ -2,6 +2,8 @@ package com.lagradost.cloudstream3.network
import androidx.annotation.AnyThread
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.network.Requests.Companion.await
import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
@ -18,17 +20,17 @@ class DdosGuardKiller(private val alwaysBypass: Boolean) : Interceptor {
private var ddosBypassPath: String? = null
override fun intercept(chain: Interceptor.Chain): Response {
override fun intercept(chain: Interceptor.Chain): Response = runBlocking {
val request = chain.request()
if (alwaysBypass) return bypassDdosGuard(request)
if (alwaysBypass) return@runBlocking bypassDdosGuard(request)
val response = chain.proceed(request)
return if (response.code == 403) {
return@runBlocking if (response.code == 403) {
bypassDdosGuard(request)
} else response
}
private fun bypassDdosGuard(request: Request): Response {
private suspend fun bypassDdosGuard(request: Request): Response {
ddosBypassPath = ddosBypassPath ?: Regex("'(.*?)'").find(
app.get(
"https://check.ddos-guard.net/check.js"
@ -49,6 +51,6 @@ class DdosGuardKiller(private val alwaysBypass: Boolean) : Interceptor {
request.newBuilder()
.headers(headers)
.build()
).execute()
).await()
}
}

View file

@ -7,13 +7,19 @@ import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mapper
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CompletionHandler
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.*
import okhttp3.Headers.Companion.toHeaders
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import java.io.File
import java.io.IOException
import java.net.URI
import java.util.concurrent.TimeUnit
import kotlin.coroutines.resumeWithException
class Session(
@ -234,7 +240,41 @@ open class Requests {
return baseClient
}
fun get(
class ContinuationCallback(
private val call: Call,
private val continuation: CancellableContinuation<Response>
) : Callback, CompletionHandler {
@ExperimentalCoroutinesApi
override fun onResponse(call: Call, response: Response) {
continuation.resume(response, null)
}
override fun onFailure(call: Call, e: IOException) {
if (!call.isCanceled()) {
continuation.resumeWithException(e)
}
}
override fun invoke(cause: Throwable?) {
try {
call.cancel()
} catch (_: Throwable) {
}
}
}
companion object {
suspend inline fun Call.await(): Response {
return suspendCancellableCoroutine { continuation ->
val callback = ContinuationCallback(this, continuation)
enqueue(callback)
continuation.invokeOnCancellation(callback)
}
}
}
suspend fun get(
url: String,
headers: Map<String, String> = emptyMap(),
referer: String? = null,
@ -255,15 +295,15 @@ open class Requests {
if (interceptor != null) client.addInterceptor(interceptor)
val request =
getRequestCreator(url, headers, referer, params, cookies, cacheTime, cacheUnit)
val response = client.build().newCall(request).execute()
val response = client.build().newCall(request).await()
return AppResponse(response)
}
fun executeRequest(request : Request): AppResponse {
fun executeRequest(request: Request): AppResponse {
return AppResponse(baseClient.newCall(request).execute())
}
fun post(
suspend fun post(
url: String,
headers: Map<String, String> = mapOf(),
referer: String? = null,
@ -283,11 +323,11 @@ open class Requests {
.build()
val request =
postRequestCreator(url, headers, referer, params, cookies, data, cacheTime, cacheUnit)
val response = client.newCall(request).execute()
val response = client.newCall(request).await()
return AppResponse(response)
}
fun put(
suspend fun put(
url: String,
headers: Map<String, String> = mapOf(),
referer: String? = null,
@ -307,7 +347,7 @@ open class Requests {
.build()
val request =
putRequestCreator(url, headers, referer, params, cookies, data, cacheTime, cacheUnit)
val response = client.newCall(request).execute()
val response = client.newCall(request).await()
return AppResponse(response)
}
}

View file

@ -76,7 +76,7 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List<Regex> =
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest
): WebResourceResponse? {
): WebResourceResponse? = runBlocking {
val webViewUrl = request.url.toString()
// println("Loading WebView URL: $webViewUrl")
@ -84,7 +84,7 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List<Regex> =
fixedRequest = request.toRequest().also(requestCallBack)
println("Web-view request finished: $webViewUrl")
destroyWebView()
return null
return@runBlocking null
}
if (additionalUrls.any { it.containsMatchIn(webViewUrl) }) {
@ -128,7 +128,13 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List<Regex> =
* Overriding with okhttp might fuck up otherwise working requests,
* e.g the recaptcha request.
* **/
return try {
/** NOTE! request.requestHeaders is not perfect!
* They don't contain all the headers the browser actually gives.
* Overriding with okhttp might fuck up otherwise working requests,
* e.g the recaptcha request.
* **/
return@runBlocking try {
when {
blacklistedFiles.any { URI(webViewUrl).path.contains(it) } || webViewUrl.endsWith(
"/favicon.ico"
@ -152,7 +158,7 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List<Regex> =
webViewUrl,
headers = request.requestHeaders
).response.toWebResourceResponse()
else -> return super.shouldInterceptRequest(view, request)
else -> return@runBlocking super.shouldInterceptRequest(view, request)
}
} catch (e: Exception) {
null

View file

@ -66,7 +66,7 @@ interface SyncAPI : OAuth2API {
val icon: Int
val mainUrl: String
fun search(name: String): List<SyncSearchResult>?
suspend fun search(name: String): List<SyncSearchResult>?
/**
-1 -> None
@ -77,9 +77,9 @@ interface SyncAPI : OAuth2API {
4 -> PlanToWatch
5 -> ReWatching
*/
fun score(id: String, status: SyncStatus): Boolean
suspend fun score(id: String, status: SyncStatus): Boolean
fun getStatus(id: String): SyncStatus?
suspend fun getStatus(id: String): SyncStatus?
fun getResult(id: String): SyncResult?
suspend fun getResult(id: String): SyncResult?
}

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
return data.data.Page.media.map {
SyncAPI.SyncSearchResult(
@ -88,7 +88,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
}
}
override fun getResult(id: String): SyncAPI.SyncResult? {
override suspend fun getResult(id: String): SyncAPI.SyncResult? {
val internalId = id.toIntOrNull() ?: return null
val season = getSeason(internalId)?.data?.Media ?: return null
@ -104,7 +104,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
)
}
override fun getStatus(id: String): SyncAPI.SyncStatus? {
override suspend fun getStatus(id: String): SyncAPI.SyncStatus? {
val internalId = id.toIntOrNull() ?: return null
val data = getDataAboutId(internalId) ?: return null
@ -116,7 +116,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
)
}
override fun score(id: String, status: SyncAPI.SyncStatus): Boolean {
override suspend fun score(id: String, status: SyncAPI.SyncStatus): Boolean {
return postDataAboutId(
id.toIntOrNull() ?: return false,
fromIntToAnimeStatus(status.status),
@ -143,7 +143,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
.replace("[^a-zA-Z0-9]".toRegex(), "")
}
private fun searchShows(name: String): GetSearchRoot? {
private suspend fun searchShows(name: String): GetSearchRoot? {
try {
val query = """
query (${"$"}id: Int, ${"$"}page: Int, ${"$"}search: String, ${"$"}type: MediaType) {
@ -225,7 +225,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
}
// Should use https://gist.github.com/purplepinapples/5dc60f15f2837bf1cea71b089cfeaa0a
fun getShowId(malId: String?, name: String, year: Int?): GetSearchMedia? {
suspend fun getShowId(malId: String?, name: String, year: Int?): GetSearchMedia? {
// Strips these from the name
val blackList = listOf(
"TV Dubbed",
@ -293,7 +293,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
}
private fun getSeason(id: Int): SeasonResponse? {
private suspend fun getSeason(id: Int): SeasonResponse? {
val q: String = """
query (${'$'}id: Int = $id) {
Media (id: ${'$'}id, type: ANIME) {
@ -351,7 +351,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
)!!
}
fun getDataAboutId(id: Int): AniListTitleHolder? {
suspend fun getDataAboutId(id: Int): AniListTitleHolder? {
val q =
"""query (${'$'}id: Int = $id) { # Define which variables will be used in the query (id)
Media (id: ${'$'}id, type: ANIME) { # Insert our variables into the query arguments (id) (type: ANIME is hard-coded in the query)
@ -410,7 +410,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
}
}
private fun postApi(url: String, q: String, cache: Boolean = false): String {
private suspend fun postApi(url: String, q: String, cache: Boolean = false): String {
return try {
if (!checkToken()) {
// println("VARS_ " + vars)
@ -514,7 +514,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
return getKey(ANILIST_CACHED_LIST) as? Array<Lists>
}
fun getAnilistAnimeListSmart(): Array<Lists>? {
suspend fun getAnilistAnimeListSmart(): Array<Lists>? {
if (getKey<String>(
accountId,
ANILIST_TOKEN_KEY,
@ -535,7 +535,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
}
}
private fun getFullAnilistList(): FullAnilistList? {
private suspend fun getFullAnilistList(): FullAnilistList? {
try {
var userID: Int? = null
/** WARNING ASSUMES ONE USER! **/
@ -597,7 +597,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
}
}
fun toggleLike(id: Int): Boolean {
suspend fun toggleLike(id: Int): Boolean {
val q = """mutation (${'$'}animeId: Int = $id) {
ToggleFavourite (animeId: ${'$'}animeId) {
anime {
@ -614,7 +614,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
return data != ""
}
private fun postDataAboutId(
private suspend fun postDataAboutId(
id: Int,
type: AniListStatusType,
score: Int?,
@ -643,7 +643,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
}
}
private fun getUser(setSettings: Boolean = true): AniListUser? {
private suspend fun getUser(setSettings: Boolean = true): AniListUser? {
val q = """
{
Viewer {
@ -686,9 +686,9 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
}
}
fun getAllSeasons(id: Int): List<SeasonResponse?> {
suspend fun getAllSeasons(id: Int): List<SeasonResponse?> {
val seasons = mutableListOf<SeasonResponse?>()
fun getSeasonRecursive(id: Int) {
suspend fun getSeasonRecursive(id: Int) {
val season = getSeason(id)
if (season != null) {
seasons.add(season)

View file

@ -50,7 +50,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
return null
}
override fun search(name: String): List<SyncAPI.SyncSearchResult> {
override suspend fun search(name: String): List<SyncAPI.SyncSearchResult> {
val url = "https://api.myanimelist.net/v2/anime?q=$name&limit=$MAL_MAX_SEARCH_LIMIT"
val auth = getKey<String>(
accountId,
@ -73,7 +73,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
}
}
override fun score(id: String, status : SyncAPI.SyncStatus): Boolean {
override suspend fun score(id: String, status : SyncAPI.SyncStatus): Boolean {
return setScoreRequest(
id.toIntOrNull() ?: return false,
fromIntToAnimeStatus(status.status),
@ -82,12 +82,12 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
)
}
override fun getResult(id: String): SyncAPI.SyncResult? {
override suspend fun getResult(id: String): SyncAPI.SyncResult? {
val internalId = id.toIntOrNull() ?: return null
TODO("Not yet implemented")
}
override fun getStatus(id: String): SyncAPI.SyncStatus? {
override suspend fun getStatus(id: String): SyncAPI.SyncStatus? {
val internalId = id.toIntOrNull() ?: return null
val data = getDataAboutMalId(internalId)?.my_list_status ?: return null
@ -182,7 +182,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
}
}
private fun refreshToken() {
private suspend fun refreshToken() {
try {
val res = app.post(
"https://myanimelist.net/v1/oauth2/token",
@ -281,7 +281,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
return getKey(MAL_CACHED_LIST) as? Array<Data>
}
fun getMalAnimeListSmart(): Array<Data>? {
suspend fun getMalAnimeListSmart(): Array<Data>? {
if (getKey<String>(
accountId,
MAL_TOKEN_KEY
@ -299,7 +299,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
}
}
private fun getMalAnimeList(): Array<Data>? {
private suspend fun getMalAnimeList(): Array<Data>? {
return try {
checkMalToken()
var offset = 0
@ -321,7 +321,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
return fromIntToAnimeStatus(malStatusAsString.indexOf(string))
}
private fun getMalAnimeListSlice(offset: Int = 0): MalList? {
private suspend fun getMalAnimeListSlice(offset: Int = 0): MalList? {
val user = "@me"
val auth = getKey<String>(
accountId,
@ -344,7 +344,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
}
}
private fun getDataAboutMalId(id: Int): MalAnime? {
private suspend fun getDataAboutMalId(id: Int): MalAnime? {
return try {
// https://myanimelist.net/apiconfig/references/api/v2#operation/anime_anime_id_get
val url = "https://api.myanimelist.net/v2/anime/$id?fields=id,title,num_episodes,my_list_status"
@ -362,7 +362,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
}
}
fun setAllMalData() {
suspend fun setAllMalData() {
val user = "@me"
var isDone = false
var index = 0
@ -426,7 +426,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
return null
}
private fun checkMalToken() {
private suspend fun checkMalToken() {
if (unixTime > getKey(
accountId,
MAL_UNIXTIME_KEY
@ -436,7 +436,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
}
}
private fun getMalUser(setSettings: Boolean = true): MalUser? {
private suspend fun getMalUser(setSettings: Boolean = true): MalUser? {
checkMalToken()
return try {
val res = app.get(
@ -483,7 +483,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
}
}
fun setScoreRequest(
suspend fun setScoreRequest(
id: Int,
status: MalStatusType? = null,
score: Int? = null,
@ -514,7 +514,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
}
}
private fun setScoreRequest(
private suspend fun setScoreRequest(
id: Int,
status: String? = null,
score: Int? = null,

View file

@ -8,8 +8,6 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
class APIRepository(val api: MainAPI) {
companion object {
var providersActive = HashSet<String>()
var typesActive = HashSet<TvType>()
var dubStatusActive = HashSet<DubStatus>()
val noneApi = object : MainAPI() {

View file

@ -138,6 +138,22 @@ class HomeFragment : Fragment() {
bottomSheetDialogBuilder.show()
}
fun getPairList(
anime: MaterialButton?,
cartoons: MaterialButton?,
tvs: MaterialButton?,
docs: MaterialButton?,
movies: MaterialButton?
): List<Pair<MaterialButton?, List<TvType>>> {
return listOf(
Pair(anime, listOf(TvType.Anime, TvType.ONA, TvType.AnimeMovie)),
Pair(cartoons, listOf(TvType.Cartoon)),
Pair(tvs, listOf(TvType.TvSeries)),
Pair(docs, listOf(TvType.Documentary)),
Pair(movies, listOf(TvType.Movie, TvType.Torrent))
)
}
fun Context.selectHomepage(selectedApiName: String?, callback: (String) -> Unit) {
val validAPIs = filterProviderByPreferredMedia().toMutableList()
@ -169,6 +185,8 @@ class HomeFragment : Fragment() {
val cancelBtt = dialog.findViewById<MaterialButton>(R.id.cancel_btt)
val applyBtt = dialog.findViewById<MaterialButton>(R.id.apply_btt)
val pairList = getPairList(anime, cartoons, tvs, docs, movies)
cancelBtt?.setOnClickListener {
dialog.dismissSafe()
}
@ -194,14 +212,6 @@ class HomeFragment : Fragment() {
}
}
val pairList = listOf(
Pair(anime, listOf(TvType.Anime, TvType.ONA, TvType.AnimeMovie)),
Pair(cartoons, listOf(TvType.Cartoon)),
Pair(tvs, listOf(TvType.TvSeries)),
Pair(docs, listOf(TvType.Documentary)),
Pair(movies, listOf(TvType.Movie, TvType.Torrent))
)
fun updateList() {
this.setKey(HOME_PREF_HOMEPAGE, preSelectedTypes)
@ -210,12 +220,11 @@ class HomeFragment : Fragment() {
api.hasMainPage && api.supportedTypes.any {
preSelectedTypes.contains(it)
}
}.toMutableList()
}.sortedBy { it.name }.toMutableList()
currentValidApis.addAll(0, validAPIs.subList(0, 2))
val names = currentValidApis.map { it.name }
val index = names.indexOf(currentApiName)
println("INDEX: $index")
listView?.setItemChecked(index, true)
arrayAdapter.notifyDataSetChanged()
arrayAdapter.addAll(names)
@ -331,13 +340,13 @@ class HomeFragment : Fragment() {
}
}*/
private fun focusCallback(card : SearchResponse) {
private fun focusCallback(card: SearchResponse) {
home_focus_text?.text = card.name
home_blur_poster?.setImageBlur(card.posterUrl,50)
home_blur_poster?.setImageBlur(card.posterUrl, 50)
}
private fun homeHandleSearch(callback : SearchClickCallback) {
if(callback.action == SEARCH_ACTION_FOCUSED) {
private fun homeHandleSearch(callback: SearchClickCallback) {
if (callback.action == SEARCH_ACTION_FOCUSED) {
focusCallback(callback.card)
} else {
handleSearchClickCallback(activity, callback)

View file

@ -6,8 +6,10 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.*
import androidx.appcompat.app.AlertDialog
import android.widget.AbsListView
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.ListView
import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
@ -15,18 +17,15 @@ import androidx.fragment.app.activityViewModels
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
import com.lagradost.cloudstream3.APIHolder.getApiSettings
import com.lagradost.cloudstream3.APIHolder.getApiTypeSettings
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
import com.lagradost.cloudstream3.APIHolder.getApiFromName
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.APIRepository.Companion.providersActive
import com.lagradost.cloudstream3.ui.APIRepository.Companion.typesActive
import com.lagradost.cloudstream3.ui.home.HomeFragment
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.currentSpan
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.loadHomepageList
@ -34,7 +33,6 @@ import com.lagradost.cloudstream3.ui.home.ParentItemAdapter
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.SEARCH_PROVIDER_TOGGLE
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
@ -42,6 +40,8 @@ import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import kotlinx.android.synthetic.main.fragment_search.*
import java.util.concurrent.locks.ReentrantLock
const val SEARCH_PREF_TAGS = "search_pref_tags"
const val SEARCH_PREF_PROVIDERS = "search_pref_providers"
class SearchFragment : Fragment() {
companion object {
@ -90,6 +90,9 @@ class SearchFragment : Fragment() {
super.onDestroyView()
}
var selectedSearchTypes = mutableListOf<TvType>()
var selectedApis = mutableSetOf<String>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -108,188 +111,190 @@ class SearchFragment : Fragment() {
search_autofit_results.adapter = adapter
search_loading_bar.alpha = 0f
val searchExitIcon = main_search.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
val searchMagIcon = main_search.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon)
val searchExitIcon =
main_search.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
val searchMagIcon =
main_search.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon)
searchMagIcon.scaleX = 0.65f
searchMagIcon.scaleY = 0.65f
context?.let { ctx ->
val validAPIs = ctx.filterProviderByPreferredMedia()
selectedApis = ctx.getKey(
SEARCH_PREF_PROVIDERS,
defVal = validAPIs.map { it.name }
)!!.toMutableSet()
}
search_filter.setOnClickListener { searchView ->
val apiNamesSetting = activity?.getApiSettings()
val langs = activity?.getApiProviderLangSettings()
if (apiNamesSetting != null && langs != null) {
val apiNames = apis.filter { langs.contains(it.lang) }.map { it.name }
searchView?.context?.let { ctx ->
val validAPIs = ctx.filterProviderByPreferredMedia()
var currentValidApis = listOf<MainAPI>()
val currentSelectedApis = if (selectedApis.isEmpty()) validAPIs.map { it.name }
.toMutableSet() else selectedApis
val builder =
AlertDialog.Builder(searchView.context).setView(R.layout.provider_list)
BottomSheetDialog(ctx)
val dialog = builder.create()
dialog.show()
builder.setContentView(R.layout.home_select_mainpage)
builder.show()
builder.let { dialog ->
val anime = dialog.findViewById<MaterialButton>(R.id.home_select_anime)
val cartoons = dialog.findViewById<MaterialButton>(R.id.home_select_cartoons)
val tvs = dialog.findViewById<MaterialButton>(R.id.home_select_tv_series)
val docs = dialog.findViewById<MaterialButton>(R.id.home_select_documentaries)
val movies = dialog.findViewById<MaterialButton>(R.id.home_select_movies)
val cancelBtt = dialog.findViewById<MaterialButton>(R.id.cancel_btt)
val applyBtt = dialog.findViewById<MaterialButton>(R.id.apply_btt)
val listView = dialog.findViewById<ListView>(R.id.listview1)!!
val listView2 = dialog.findViewById<ListView>(R.id.listview2)!!
val toggle = dialog.findViewById<SwitchMaterial>(R.id.toggle1)!!
val applyButton = dialog.findViewById<TextView>(R.id.apply_btt)!!
val cancelButton = dialog.findViewById<TextView>(R.id.cancel_btt)!!
// val applyHolder = dialog.findViewById<LinearLayout>(R.id.apply_btt_holder)!!
val pairList = HomeFragment.getPairList(anime, cartoons, tvs, docs, movies)
val arrayAdapter = ArrayAdapter<String>(searchView.context, R.layout.sort_bottom_single_choice)
arrayAdapter.addAll(apiNames)
listView.adapter = arrayAdapter
listView.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
val typeChoices = listOf(
Pair(R.string.movies, listOf(TvType.Movie)),
Pair(R.string.tv_series, listOf(TvType.TvSeries, TvType.Documentary)),
Pair(R.string.cartoons, listOf(TvType.Cartoon)),
Pair(R.string.anime, listOf(TvType.Anime, TvType.ONA, TvType.AnimeMovie)),
Pair(R.string.torrent, listOf(TvType.Torrent)),
).filter { item -> apis.any { api -> api.supportedTypes.any { type -> item.second.contains(type) } } }
val arrayAdapter2 = ArrayAdapter<String>(searchView.context, R.layout.sort_bottom_single_choice)
arrayAdapter2.addAll(typeChoices.map { getString(it.first) })
listView2.adapter = arrayAdapter2
listView2.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
for ((index, item) in apiNames.withIndex()) {
listView.setItemChecked(index, apiNamesSetting.contains(item))
cancelBtt?.setOnClickListener {
dialog.dismissSafe()
}
for ((index, item) in typeChoices.withIndex()) {
listView2.setItemChecked(index, item.second.any { typesActive.contains(it) })
cancelBtt?.setOnClickListener {
dialog.dismissSafe()
}
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
toggleSearch(defVal)
toggle.isChecked = defVal
toggle.setOnCheckedChangeListener { _, isOn ->
toggleSearch(isOn)
}
listView.setOnItemClickListener { _, _, _, _ ->
val types = HashSet<TvType>()
for ((index, api) in apis.withIndex()) {
if (listView.checkedItemPositions[index]) {
types.addAll(api.supportedTypes)
}
}
for ((typeIndex, type) in typeChoices.withIndex()) {
listView2.setItemChecked(typeIndex, type.second.any { types.contains(it) })
}
}
listView2.setOnItemClickListener { _, _, _, _ ->
for ((index, api) in apis.withIndex()) {
var isSupported = false
for ((typeIndex, type) in typeChoices.withIndex()) {
if (listView2.checkedItemPositions[typeIndex]) {
if (api.supportedTypes.any { type.second.contains(it) }) {
isSupported = true
}
}
}
listView.setItemChecked(
index,
isSupported
)
}
applyBtt?.setOnClickListener {
//if (currentApiName != selectedApiName) {
// currentApiName?.let(callback)
//}
dialog.dismissSafe()
}
dialog.setOnDismissListener {
context?.setKey(SEARCH_PROVIDER_TOGGLE, toggle.isChecked)
context?.setKey(SEARCH_PREF_PROVIDERS, currentSelectedApis.toList())
selectedApis = currentSelectedApis
}
applyButton.setOnClickListener {
val settingsManagerLocal = PreferenceManager.getDefaultSharedPreferences(activity)
val activeTypes = HashSet<TvType>()
for ((index, _) in typeChoices.withIndex()) {
if (listView2.checkedItemPositions[index]) {
activeTypes.addAll(typeChoices[index].second)
}
val selectedSearchTypes = context?.getKey<List<String>>(SEARCH_PREF_TAGS)
?.mapNotNull { listName ->
TvType.values().firstOrNull { it.name == listName }
}
?.toMutableList()
?: mutableListOf(TvType.Movie, TvType.TvSeries)
if (activeTypes.size == 0) {
activeTypes.addAll(TvType.values())
}
val listView = dialog.findViewById<ListView>(R.id.listview1)
val arrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
listView?.adapter = arrayAdapter
listView?.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
val activeApis = HashSet<String>()
for ((index, name) in apiNames.withIndex()) {
if (listView.checkedItemPositions[index]) {
activeApis.add(name)
}
}
if (activeApis.size == 0) {
activeApis.addAll(apiNames)
}
val edit = settingsManagerLocal.edit()
edit.putStringSet(
getString(R.string.search_providers_list_key),
activeApis
)
edit.putStringSet(
getString(R.string.search_types_list_key),
activeTypes.map { it.name }.toSet()
)
edit.apply()
providersActive = activeApis
typesActive = activeTypes
dialog.dismissSafe(activity)
}
cancelButton.setOnClickListener {
dialog.dismissSafe(activity)
}
//listView.setSelection(selectedIndex)
// listView.setItemChecked(selectedIndex, true)
/*
val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext())
builder.setMultiChoiceItems(
apiNames.toTypedArray(),
apiNames.map { a -> apiNamesSetting.contains(a) }.toBooleanArray()
) { _, position: Int, checked: Boolean ->
val apiNamesSettingLocal = activity?.getApiSettings()
if (apiNamesSettingLocal != null) {
val settingsManagerLocal = PreferenceManager.getDefaultSharedPreferences(activity)
if (checked) {
apiNamesSettingLocal.add(apiNames[position])
listView?.setOnItemClickListener { _, _, i, _ ->
if (!currentValidApis.isNullOrEmpty()) {
val api = currentValidApis[i].name
if (currentSelectedApis.contains(api)) {
listView.setItemChecked(i, false)
currentSelectedApis -= api
} else {
apiNamesSettingLocal.remove(apiNames[position])
listView.setItemChecked(i, true)
currentSelectedApis += api
}
}
}
val edit = settingsManagerLocal.edit()
edit.putStringSet(
getString(R.string.search_providers_list_key),
apiNames.filter { a -> apiNamesSettingLocal.contains(a) }.toSet()
fun updateList() {
arrayAdapter.clear()
currentValidApis = validAPIs.filter { api ->
api.hasMainPage && api.supportedTypes.any {
selectedSearchTypes.contains(it)
}
}.sortedBy { it.name }
val names = currentValidApis.map { it.name }
for ((index, api) in names.withIndex()) {
listView?.setItemChecked(index, currentSelectedApis.contains(api))
}
arrayAdapter.notifyDataSetChanged()
arrayAdapter.addAll(names)
arrayAdapter.notifyDataSetChanged()
}
for ((button, validTypes) in pairList) {
val isValid =
validAPIs.any { api -> validTypes.any { api.supportedTypes.contains(it) } }
button?.isVisible = isValid
if (isValid) {
fun buttonContains(): Boolean {
return selectedSearchTypes.any { validTypes.contains(it) }
}
button?.isSelected = buttonContains()
button?.setOnClickListener {
selectedSearchTypes.clear()
selectedSearchTypes.addAll(validTypes)
for ((otherButton, _) in pairList) {
otherButton?.isSelected = false
}
button.isSelected = true
updateList()
}
button?.setOnLongClickListener {
if (!buttonContains()) {
button.isSelected = true
selectedSearchTypes.addAll(validTypes)
} else {
button.isSelected = false
selectedSearchTypes.removeAll(validTypes)
}
updateList()
return@setOnLongClickListener true
}
}
}
updateList()
}
}
}
val pairList = HomeFragment.getPairList(
search_select_anime,
search_select_cartoons,
search_select_tv_series,
search_select_documentaries,
search_select_movies
)
edit.apply()
providersActive = apiNamesSettingLocal
selectedSearchTypes = context?.getKey<List<String>>(SEARCH_PREF_TAGS)
?.mapNotNull { listName -> TvType.values().firstOrNull { it.name == listName } }
?.toMutableList()
?: mutableListOf(TvType.Movie, TvType.TvSeries)
context?.filterProviderByPreferredMedia()?.let { validAPIs ->
for ((button, validTypes) in pairList) {
val isValid =
validAPIs.any { api -> validTypes.any { api.supportedTypes.contains(it) } }
button?.isVisible = isValid
if (isValid) {
fun buttonContains(): Boolean {
return selectedSearchTypes.any { validTypes.contains(it) }
}
button?.isSelected = buttonContains()
button?.setOnClickListener {
selectedSearchTypes.clear()
selectedSearchTypes.addAll(validTypes)
for ((otherButton, _) in pairList) {
otherButton?.isSelected = false
}
it?.context?.setKey(SEARCH_PREF_TAGS, selectedSearchTypes)
it?.isSelected = true
}
button?.setOnLongClickListener {
if (!buttonContains()) {
it?.isSelected = true
selectedSearchTypes.addAll(validTypes)
} else {
it?.isSelected = false
selectedSearchTypes.removeAll(validTypes)
}
it?.context?.setKey(SEARCH_PREF_TAGS, selectedSearchTypes)
return@setOnLongClickListener true
}
}
builder.setTitle("Search Providers")
builder.setNegativeButton("Ok") { _, _ -> }
builder.show()*/
}
}
@ -300,7 +305,12 @@ class SearchFragment : Fragment() {
main_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
searchViewModel.searchAndCancel(query = query)
searchViewModel.searchAndCancel(
query = query,
providersActive = selectedApis.filter { name ->
getApiFromName(name).supportedTypes.any { selectedSearchTypes.contains(it) }
}.toSet()
)
main_search?.let {
hideKeyboard(it)
@ -363,10 +373,6 @@ class SearchFragment : Fragment() {
}
}
activity?.let {
providersActive = it.getApiSettings()
typesActive = it.getApiTypeSettings()
}
/*main_search.setOnQueryTextFocusChangeListener { _, b ->
if (b) {
@ -376,20 +382,21 @@ class SearchFragment : Fragment() {
}*/
//main_search.onActionViewExpanded()*/
val masterAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = ParentItemAdapter(listOf(), { callback ->
val masterAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
ParentItemAdapter(listOf(), { callback ->
SearchHelper.handleSearchClickCallback(activity, callback)
}, { item ->
activity?.loadHomepageList(item)
})
search_master_recycler.adapter = masterAdapter
search_master_recycler.layoutManager = GridLayoutManager(context, 1)
search_master_recycler?.adapter = masterAdapter
search_master_recycler?.layoutManager = GridLayoutManager(context, 1)
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
val isAdvancedSearch = settingsManager.getBoolean("advanced_search", true)
search_master_recycler.isVisible = isAdvancedSearch
search_autofit_results.isVisible = !isAdvancedSearch
search_master_recycler?.isVisible = isAdvancedSearch
search_autofit_results?.isVisible = !isAdvancedSearch
// SubtitlesFragment.push(activity)
//searchViewModel.search("iron man")

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.SyncAPI
import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.APIRepository.Companion.providersActive
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
@ -26,7 +25,8 @@ data class OnGoingSearch(
)
class SearchViewModel : ViewModel() {
private val _searchResponse: MutableLiveData<Resource<ArrayList<SearchResponse>>> = MutableLiveData()
private val _searchResponse: MutableLiveData<Resource<ArrayList<SearchResponse>>> =
MutableLiveData()
val searchResponse: LiveData<Resource<ArrayList<SearchResponse>>> get() = _searchResponse
private val _currentSearch: MutableLiveData<ArrayList<OnGoingSearch>> = MutableLiveData()
@ -40,9 +40,14 @@ class SearchViewModel : ViewModel() {
}
var onGoingSearch: Job? = null
fun searchAndCancel(query: String, isMainApis: Boolean = true, ignoreSettings: Boolean = false) {
fun searchAndCancel(
query: String,
isMainApis: Boolean = true,
providersActive: Set<String> = setOf(),
ignoreSettings: Boolean = false
) {
onGoingSearch?.cancel()
onGoingSearch = search(query, isMainApis, ignoreSettings)
onGoingSearch = search(query, isMainApis, providersActive, ignoreSettings)
}
data class SyncSearchResultSearchResponse(
@ -65,7 +70,12 @@ class SearchViewModel : ViewModel() {
)
}
private fun search(query: String, isMainApis: Boolean = true, ignoreSettings: Boolean = false) =
private fun search(
query: String,
isMainApis: Boolean = true,
providersActive: Set<String>,
ignoreSettings: Boolean = false
) =
viewModelScope.launch {
if (query.length <= 1) {
clearSearch()
@ -81,7 +91,7 @@ class SearchViewModel : ViewModel() {
withContext(Dispatchers.IO) { // This interrupts UI otherwise
if (isMainApis) {
repos.filter { a ->
ignoreSettings || (providersActive.size == 0 || providersActive.contains(a.name))
ignoreSettings || (providersActive.isEmpty() || providersActive.contains(a.name))
}.apmap { a -> // Parallel
val search = a.search(query)
currentList.add(OnGoingSearch(a.name, search))
@ -102,7 +112,8 @@ class SearchViewModel : ViewModel() {
val list = ArrayList<SearchResponse>()
val nestedList =
currentList.map { it.data }.filterIsInstance<Resource.Success<List<SearchResponse>>>().map { it.value }
currentList.map { it.data }
.filterIsInstance<Resource.Success<List<SearchResponse>>>().map { it.value }
// I do it this way to move the relevant search results to the top
var index = 0

View file

@ -22,7 +22,6 @@ import com.hippo.unifile.UniFile
import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
import com.lagradost.cloudstream3.APIHolder.getApiSettings
import com.lagradost.cloudstream3.APIHolder.restrictedApis
import com.lagradost.cloudstream3.AcraApplication
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
@ -294,7 +293,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
this.getString(R.string.provider_lang_key),
selectedList.map { names[it].first }.toMutableSet()
).apply()
APIRepository.providersActive = it.context.getApiSettings()
//APIRepository.providersActive = it.context.getApiSettings()
}
}

View file

@ -5,7 +5,8 @@ import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.extractors.*
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import kotlinx.coroutines.delay
import org.jsoup.Jsoup
data class ExtractorLink(
@ -78,7 +79,7 @@ fun getAndUnpack(string: String): String {
/**
* Tries to load the appropriate extractor based on link, returns true if any extractor is loaded.
* */
fun loadExtractor(url: String, referer: String?, callback: (ExtractorLink) -> Unit) : Boolean {
suspend fun loadExtractor(url: String, referer: String?, callback: (ExtractorLink) -> Unit) : Boolean {
for (extractor in extractorApis) {
if (url.startsWith(extractor.mainUrl)) {
extractor.getSafeUrl(url, referer)?.forEach(callback)
@ -138,7 +139,7 @@ fun httpsify(url: String): String {
return if (url.startsWith("//")) "https:$url" else url
}
fun getPostForm(requestUrl : String, html : String) : String? {
suspend fun getPostForm(requestUrl : String, html : String) : String? {
val document = Jsoup.parse(html)
val inputs = document.select("Form > input")
if (inputs.size < 4) return null
@ -160,7 +161,7 @@ fun getPostForm(requestUrl : String, html : String) : String? {
if (op == null || id == null || mode == null || hash == null) {
return null
}
Thread.sleep(5000) // ye this is needed, wont work with 0 delay
delay(5000) // ye this is needed, wont work with 0 delay
val postResponse = app.post(
requestUrl,
@ -181,14 +182,14 @@ abstract class ExtractorApi {
abstract val mainUrl: String
abstract val requiresReferer: Boolean
fun getSafeUrl(url: String, referer: String? = null): List<ExtractorLink>? {
return normalSafeApiCall { getUrl(url, referer) }
suspend fun getSafeUrl(url: String, referer: String? = null): List<ExtractorLink>? {
return suspendSafeApiCall { getUrl(url, referer) }
}
/**
* Will throw errors, use getSafeUrl if you don't want to handle the exception yourself
*/
abstract fun getUrl(url: String, referer: String? = null): List<ExtractorLink>?
abstract suspend fun getUrl(url: String, referer: String? = null): List<ExtractorLink>?
open fun getExtractorUrl(id: String): String {
return id

View file

@ -16,7 +16,7 @@ object FillerEpisodeCheck {
return name.lowercase(Locale.ROOT)/*.replace(" ", "")*/.replace("-", " ").replace("[^a-zA-Z0-9 ]".toRegex(), "")
}
private fun getFillerList(): Boolean {
private suspend fun getFillerList(): Boolean {
if (list != null) return true
try {
val result = app.get("$MAIN_URL/shows").text
@ -59,7 +59,7 @@ object FillerEpisodeCheck {
return q + "cache" + z
}
fun getFillerEpisodes(query: String): HashMap<Int, Boolean>? {
suspend fun getFillerEpisodes(query: String): HashMap<Int, Boolean>? {
try {
if (!getFillerList()) return null
val localList = list ?: return null

View file

@ -24,6 +24,7 @@ import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import kotlinx.coroutines.runBlocking
import java.io.File
import kotlin.concurrent.thread
@ -83,7 +84,12 @@ class InAppUpdater {
val url = "https://api.github.com/repos/LagradOst/CloudStream-3/releases"
val headers = mapOf("Accept" to "application/vnd.github.v3+json")
val response =
mapper.readValue<List<GithubRelease>>(app.get(url, headers = headers).text)
mapper.readValue<List<GithubRelease>>(runBlocking {
app.get(
url,
headers = headers
).text
})
val versionRegex = Regex("""(.*?((\d+)\.(\d+)\.(\d+))\.apk)""")
val versionRegexLocal = Regex("""(.*?((\d+)\.(\d+)\.(\d+)).*)""")
@ -139,7 +145,7 @@ class InAppUpdater {
return Update(false, null, null, null)
}
private fun Activity.getPreReleaseUpdate(): Update {
private fun Activity.getPreReleaseUpdate(): Update = runBlocking {
val tagUrl =
"https://api.github.com/repos/LagradOst/CloudStream-3/git/ref/tags/pre-release"
val releaseUrl = "https://api.github.com/repos/LagradOst/CloudStream-3/releases"
@ -159,7 +165,7 @@ class InAppUpdater {
val shouldUpdate =
(getString(R.string.prerelease_commit_hash) != tagResponse.github_object.sha)
return if (foundAsset != null) {
return@runBlocking if (foundAsset != null) {
Update(
shouldUpdate,
foundAsset.browser_download_url,

View file

@ -2,7 +2,7 @@ package com.lagradost.cloudstream3.utils
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.network.text
import kotlinx.coroutines.runBlocking
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
@ -82,7 +82,9 @@ class M3u8Helper {
fun m3u8Generation(m3u8: M3u8Stream, returnThis: Boolean): List<M3u8Stream> {
val generate = sequence {
val m3u8Parent = getParentLink(m3u8.streamUrl)
val response = app.get(m3u8.streamUrl, headers = m3u8.headers).text
val response = runBlocking {
app.get(m3u8.streamUrl, headers = m3u8.headers).text
}
for (match in QUALITY_REGEX.findAll(response)) {
var (quality, m3u8Link, m3u8Link2) = match.destructured
@ -146,7 +148,7 @@ class M3u8Helper {
val secondSelection = selectBest(streams.ifEmpty { listOf(selected) })
if (secondSelection != null) {
val m3u8Response = app.get(secondSelection.streamUrl, headers = headers).text
val m3u8Response = runBlocking {app.get(secondSelection.streamUrl, headers = headers).text}
var encryptionUri: String?
var encryptionIv = byteArrayOf()
@ -164,7 +166,7 @@ class M3u8Helper {
}
encryptionIv = match.component3().toByteArray()
val encryptionKeyResponse = app.get(encryptionUri, headers = headers)
val encryptionKeyResponse = runBlocking { app.get(encryptionUri, headers = headers) }
encryptionData = encryptionKeyResponse.body?.bytes() ?: byteArrayOf()
}
@ -187,7 +189,7 @@ class M3u8Helper {
while (lastYield != c) {
try {
val tsResponse = app.get(url, headers = headers)
val tsResponse = runBlocking { app.get(url, headers = headers) }
var tsData = tsResponse.body?.bytes() ?: byteArrayOf()
if (encryptionState) {

View file

@ -5,13 +5,12 @@ import com.fasterxml.jackson.module.kotlin.readValue
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mapper
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.network.text
import java.util.concurrent.TimeUnit
object SyncUtil {
/** first. Mal, second. Anilist,
* valid sites are: Gogoanime, Twistmoe and 9anime*/
fun getIdsFromSlug(slug: String, site : String = "Gogoanime"): Pair<String?, String?>? {
suspend fun getIdsFromSlug(slug: String, site : String = "Gogoanime"): Pair<String?, String?>? {
try {
//Gogoanime, Twistmoe and 9anime
val url =

View file

@ -81,6 +81,59 @@
app:tint="?attr/textColor"
android:contentDescription="@string/change_providers_img_des" />
</FrameLayout>
<HorizontalScrollView
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:fadingEdge="horizontal"
android:requiresFadingEdge="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:nextFocusRight="@id/search_select_tv_series"
android:id="@+id/search_select_movies"
android:text="@string/movies"
style="@style/RoundedSelectableButton" />
<com.google.android.material.button.MaterialButton
android:nextFocusLeft="@id/search_select_movies"
android:nextFocusRight="@id/search_select_anime"
android:id="@+id/search_select_tv_series"
android:text="@string/tv_series"
style="@style/RoundedSelectableButton" />
<com.google.android.material.button.MaterialButton
android:nextFocusLeft="@id/search_select_tv_series"
android:nextFocusRight="@id/search_select_cartoons"
android:id="@+id/search_select_anime"
android:text="@string/anime"
style="@style/RoundedSelectableButton" />
<com.google.android.material.button.MaterialButton
android:nextFocusLeft="@id/search_select_anime"
android:nextFocusRight="@id/search_select_documentaries"
android:id="@+id/search_select_cartoons"
android:text="@string/cartoons"
style="@style/RoundedSelectableButton" />
<com.google.android.material.button.MaterialButton
android:nextFocusLeft="@id/search_select_cartoons"
android:id="@+id/search_select_documentaries"
android:text="@string/documentaries"
style="@style/RoundedSelectableButton" />
</LinearLayout>
</HorizontalScrollView>
<com.lagradost.cloudstream3.ui.AutofitRecyclerView
android:nextFocusLeft="@id/nav_rail_view"

View file

@ -13,7 +13,7 @@ class ProviderTests {
return allApis.filter { !it.usesWebView }
}
private fun loadLinks(api: MainAPI, url: String?): Boolean {
private suspend fun loadLinks(api: MainAPI, url: String?): Boolean {
Assert.assertNotNull("Api ${api.name} has invalid url on episode", url)
if (url == null) return true
var linksLoaded = 0
@ -39,7 +39,7 @@ class ProviderTests {
return true
}
private fun testSingleProviderApi(api: MainAPI): Boolean {
private suspend fun testSingleProviderApi(api: MainAPI): Boolean {
val searchQueries = listOf("over", "iron", "guy")
var correctResponses = 0
var searchResult: List<SearchResponse>? = null
@ -144,7 +144,7 @@ class ProviderTests {
@Test
fun providerCorrectHomepage() {
getAllProviders().pmap { api ->
getAllProviders().apmap { api ->
if (api.hasMainPage) {
try {
val homepage = api.getMainPage()
@ -175,10 +175,10 @@ class ProviderTests {
// }
@Test
fun providerCorrect() {
suspend fun providerCorrect() {
val invalidProvider = ArrayList<Pair<MainAPI,Exception?>>()
val providers = getAllProviders()
providers.pmap { api ->
providers.apmap { api ->
try {
println("Trying $api")
if (testSingleProviderApi(api)) {