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