diff --git a/app/build.gradle b/app/build.gradle index 185a081a..49b881b7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -108,7 +108,7 @@ dependencies { //implementation "io.karn:khttp-android:0.1.2" //okhttp instead // implementation 'org.jsoup:jsoup:1.13.1' -// implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.12.3" + implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1" implementation "androidx.preference:preference-ktx:1.2.0" @@ -156,7 +156,7 @@ dependencies { // Networking // implementation "com.squareup.okhttp3:okhttp:4.9.2" // implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1" - implementation 'com.github.Blatzar:NiceHttp:0.2.0' + implementation 'com.github.Blatzar:NiceHttp:0.3.2' // Util to skip the URI file fuckery 🙏 implementation "com.github.tachiyomiorg:unifile:17bec43" diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 73c346d0..86a0aafe 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -21,6 +21,9 @@ import androidx.navigation.NavOptions import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.setupWithNavController import androidx.preference.PreferenceManager +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.google.android.gms.cast.framework.* import com.google.android.material.navigationrail.NavigationRailView import com.jaredrummler.android.colorpicker.ColorPickerDialogListener @@ -75,6 +78,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.requestRW import com.lagradost.cloudstream3.utils.USER_PROVIDER_API import com.lagradost.nicehttp.Requests +import com.lagradost.nicehttp.ResponseParser import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_result_swipe.* import kotlinx.coroutines.Dispatchers @@ -83,6 +87,7 @@ import kotlinx.coroutines.withContext import org.schabi.newpipe.extractor.NewPipe import java.io.File import kotlin.concurrent.thread +import kotlin.reflect.KClass const val VLC_PACKAGE = "org.videolan.vlc" @@ -98,7 +103,29 @@ const val VLC_EXTRA_DURATION_OUT = "extra_duration" const val VLC_LAST_ID_KEY = "vlc_last_open_id" // Short name for requests client to make it nicer to use -var app = Requests().apply { + +var app = Requests(responseParser = object : ResponseParser { + val mapper: ObjectMapper = jacksonObjectMapper().configure( + DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, + false + ) + + override fun parse(text: String, kClass: KClass): T { + return mapper.readValue(text, kClass.java) + } + + override fun parseSafe(text: String, kClass: KClass): T? { + return try { + mapper.readValue(text, kClass.java) + } catch (e: Exception) { + null + } + } + + override fun writeValueAsString(obj: Any): String { + return mapper.writeValueAsString(obj) + } +}).apply { defaultHeaders = mapOf("user-agent" to USER_AGENT) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AllAnimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AllAnimeProvider.kt index f3c91a33..7e3bd6ba 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AllAnimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AllAnimeProvider.kt @@ -312,7 +312,7 @@ class AllAnimeProvider : MainAPI() { @JsonProperty("episodeIframeHead") val episodeIframeHead: String ) - private fun getM3u8Qualities( + private suspend fun getM3u8Qualities( m3u8Link: String, referer: String, qualityName: String, diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KawaiifuProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KawaiifuProvider.kt index f1930306..61202385 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KawaiifuProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KawaiifuProvider.kt @@ -91,7 +91,7 @@ class KawaiifuProvider : MainAPI() { val title = soup.selectFirst(".title")!!.text() val tags = soup.select(".table a[href*=\"/tag/\"]").map { tag -> tag.text() } val description = soup.select(".sub-desc p") - .filter { it.select("strong").isEmpty() && it.select("iframe").isEmpty() } + .filter { it -> it.select("strong").isEmpty() && it.select("iframe").isEmpty() } .joinToString("\n") { it.text() } val year = url.split("/").filter { it.contains("-") }[0].split("-")[1].toIntOrNull() diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/WatchCartoonOnlineProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/WatchCartoonOnlineProvider.kt index ef393089..e3be650b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/WatchCartoonOnlineProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/WatchCartoonOnlineProvider.kt @@ -72,7 +72,7 @@ class WatchCartoonOnlineProvider : MainAPI() { ).text document = Jsoup.parse(response) items = document.select("#catlist-listview2 > ul > li") - .filter { it?.text() != null && !it.text().toString().contains("Episode") } + .filter { it -> it?.text() != null && !it.text().toString().contains("Episode") } for (item in items) { val titleHeader = item.selectFirst("a") diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AltadefinizioneProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AltadefinizioneProvider.kt index 719acc96..826576d8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AltadefinizioneProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AltadefinizioneProvider.kt @@ -148,7 +148,7 @@ class AltadefinizioneProvider : MainAPI() { val doc = app.get(data).document if (doc.select("div.guardahd-player").isNullOrEmpty()) { val videoUrl = - doc.select("input").filter { it.hasAttr("data-mirror") }.last().attr("value") + doc.select("input").last { it.hasAttr("data-mirror") }.attr("value") loadExtractor(videoUrl, data, subtitleCallback, callback) doc.select("#mirrors > li > a").forEach { loadExtractor(fixUrl(it.attr("data-target")), data, subtitleCallback, callback) diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CineblogProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CineblogProvider.kt index 77114ba4..b8ab057a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CineblogProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/CineblogProvider.kt @@ -126,7 +126,7 @@ class CineblogProvider : MainAPI() { val episodeList = ArrayList() document.select("#seasons > div").reversed().map { element -> val season = element.selectFirst("div.se-q > span.se-t")!!.text().toInt() - element.select("div.se-a > ul > li").filter { it.text()!="There are still no episodes this season" }.map{ episode -> + element.select("div.se-a > ul > li").filter { it -> it.text()!="There are still no episodes this season" }.map{ episode -> val href = episode.selectFirst("div.episodiotitle > a")!!.attr("href") val epNum =episode.selectFirst("div.numerando")!!.text().substringAfter("-").filter { it.isDigit() }.toIntOrNull() val epTitle = episode.selectFirst("div.episodiotitle > a")!!.text() @@ -160,7 +160,7 @@ class CineblogProvider : MainAPI() { ) } else { val actors: List = - document.select("div.person").filter{it.selectFirst("div.img > a > img")?.attr("src")!!.contains("/no/cast.png").not()}.map { actordata -> + document.select("div.person").filter{ it -> it.selectFirst("div.img > a > img")?.attr("src")!!.contains("/no/cast.png").not()}.map { actordata -> val actorName = actordata.selectFirst("div.data > div.name > a")!!.text() val actorImage : String? = actordata.selectFirst("div.img > a > img")?.attr("src") val roleActor = actordata.selectFirst("div.data > div.caracter")!!.text() diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/FilmpertuttiProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/FilmpertuttiProvider.kt index cda70a31..cf800d30 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/FilmpertuttiProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/FilmpertuttiProvider.kt @@ -111,7 +111,7 @@ class FilmpertuttiProvider : MainAPI() { if (type == TvType.TvSeries) { val episodeList = ArrayList() - document.select("div.accordion-item").filter{it.selectFirst("#season > ul > li.s_title > span")!!.text().isNotEmpty()}.map { element -> + document.select("div.accordion-item").filter{ it -> it.selectFirst("#season > ul > li.s_title > span")!!.text().isNotEmpty()}.map { element -> val season = element.selectFirst("#season > ul > li.s_title > span")!!.text().toInt() element.select("div.episode-wrap").map { episode -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt index 0543e42f..c55be64c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt @@ -9,6 +9,8 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.addActors import com.lagradost.cloudstream3.LoadResponse.Companion.addDuration import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.animeproviders.ZoroProvider +import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson @@ -345,7 +347,7 @@ open class SflixProvider : MainAPI() { "https://ws11.rabbitstream.net/socket.io/?EIO=4&transport=polling" if (iframeLink.contains("streamlare", ignoreCase = true)) { - loadExtractor(iframeLink, null,subtitleCallback,callback) + loadExtractor(iframeLink, null, subtitleCallback, callback) } else { extractRabbitStream(iframeLink, subtitleCallback, callback, false) { it } } @@ -584,7 +586,7 @@ open class SflixProvider : MainAPI() { } // For re-use in Zoro - private fun Sources.toExtractorLink( + private suspend fun Sources.toExtractorLink( caller: MainAPI, name: String, extractorData: String? = null, @@ -595,19 +597,38 @@ open class SflixProvider : MainAPI() { "hls", ignoreCase = true ) - if (isM3u8) { - M3u8Helper().m3u8Generation(M3u8Helper.M3u8Stream(this.file, null), null) - .map { stream -> - ExtractorLink( - caller.name, - "${caller.name} $name", - stream.streamUrl, - caller.mainUrl, - getQualityFromName(stream.quality?.toString()), - true, - extractorData = extractorData - ) - } + return if (isM3u8) { + suspendSafeApiCall { + M3u8Helper().m3u8Generation( + M3u8Helper.M3u8Stream( + this.file, + null, + mapOf("Referer" to "https://mzzcloud.life/") + ), false + ) + .map { stream -> + ExtractorLink( + caller.name, + "${caller.name} $name", + stream.streamUrl, + caller.mainUrl, + getQualityFromName(stream.quality?.toString()), + true, + extractorData = extractorData + ) + } + } ?: listOf( + // Fallback if m3u8 extractor fails + ExtractorLink( + caller.name, + "${caller.name} $name", + this.file, + caller.mainUrl, + getQualityFromName(this.label), + isM3u8, + extractorData = extractorData + ) + ) } else { listOf( ExtractorLink( diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/StreamingcommunityProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/StreamingcommunityProvider.kt index 49c6389b..33f66864 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/StreamingcommunityProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/StreamingcommunityProvider.kt @@ -398,7 +398,7 @@ class StreamingcommunityProvider : MainAPI() { } - private fun getM3u8Qualities( + private suspend fun getM3u8Qualities( m3u8Link: String, referer: String, qualityName: String, diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TantiFilmProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TantiFilmProvider.kt index 30b00ec0..95adef89 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TantiFilmProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TantiFilmProvider.kt @@ -167,7 +167,7 @@ class TantifilmProvider : MainAPI() { val actors: List? = if (Linkactor.isNotEmpty()) { val actorpage = app.get(Linkactor + "cast/").document actorpage.select("article.membro-cast").filter { - it.selectFirst("img") + it -> it.selectFirst("img") ?.attr("src") != "https://www.filmtv.it/imgbank/DUMMY/no_portrait.jpg" }.mapNotNull { val name = it.selectFirst("div.info > h3")!!.text() diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/M3u8Helper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/M3u8Helper.kt index d8065f0c..c309d36e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/M3u8Helper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/M3u8Helper.kt @@ -12,7 +12,7 @@ import kotlin.math.pow class M3u8Helper { companion object { private val generator = M3u8Helper() - fun generateM3u8( + suspend fun generateM3u8( source: String, streamUrl: String, referer: String, @@ -116,57 +116,52 @@ class M3u8Helper { return !url.contains("https://") && !url.contains("http://") } - fun m3u8Generation(m3u8: M3u8Stream, returnThis: Boolean?): List { - val generate = sequence { - val m3u8Parent = getParentLink(m3u8.streamUrl) - val response = runBlocking { - app.get(m3u8.streamUrl, headers = m3u8.headers).text - } + suspend fun m3u8Generation(m3u8: M3u8Stream, returnThis: Boolean?): List { + val list = mutableListOf() - var hasAnyContent = false - for (match in QUALITY_REGEX.findAll(response)) { - hasAnyContent = true + val m3u8Parent = getParentLink(m3u8.streamUrl) + val response = app.get(m3u8.streamUrl, headers = m3u8.headers, verify = false).text - var (quality, m3u8Link, m3u8Link2) = match.destructured - if (m3u8Link.isEmpty()) m3u8Link = m3u8Link2 - if (absoluteExtensionDetermination(m3u8Link) == "m3u8") { - if (isNotCompleteUrl(m3u8Link)) { - m3u8Link = "$m3u8Parent/$m3u8Link" - } - if (quality.isEmpty()) { - println(m3u8.streamUrl) - } - yieldAll( - m3u8Generation( - M3u8Stream( - m3u8Link, - quality.toIntOrNull(), - m3u8.headers - ), false - ) - ) + var hasAnyContent = false + for (match in QUALITY_REGEX.findAll(response)) { + hasAnyContent = true + var (quality, m3u8Link, m3u8Link2) = match.destructured + if (m3u8Link.isEmpty()) m3u8Link = m3u8Link2 + if (absoluteExtensionDetermination(m3u8Link) == "m3u8") { + if (isNotCompleteUrl(m3u8Link)) { + m3u8Link = "$m3u8Parent/$m3u8Link" } - yield( + if (quality.isEmpty()) { + println(m3u8.streamUrl) + } + list += m3u8Generation( M3u8Stream( m3u8Link, quality.toIntOrNull(), m3u8.headers - ) - ) - } - if (returnThis ?: !hasAnyContent) { - yield( - M3u8Stream( - m3u8.streamUrl, - Qualities.Unknown.value, - m3u8.headers - ) + ), false ) + } + list += M3u8Stream( + m3u8Link, + quality.toIntOrNull(), + m3u8.headers + ) + } - return generate.toList() + if (returnThis ?: !hasAnyContent) { + list += M3u8Stream( + m3u8.streamUrl, + Qualities.Unknown.value, + m3u8.headers + ) + } + + return list } + data class HlsDownloadData( val bytes: ByteArray, val currentIndex: Int, @@ -174,7 +169,7 @@ class M3u8Helper { val errored: Boolean = false ) - fun hlsYield(qualities: List, startIndex: Int = 0): Iterator { + suspend fun hlsYield(qualities: List, startIndex: Int = 0): Iterator { if (qualities.isEmpty()) return listOf( HlsDownloadData( byteArrayOf(), @@ -196,7 +191,13 @@ class M3u8Helper { val secondSelection = selectBest(streams.ifEmpty { listOf(selected) }) if (secondSelection != null) { val m3u8Response = - runBlocking { app.get(secondSelection.streamUrl, headers = headers).text } + runBlocking { + app.get( + secondSelection.streamUrl, + headers = headers, + verify = false + ).text + } var encryptionUri: String? var encryptionIv = byteArrayOf() @@ -215,7 +216,7 @@ class M3u8Helper { encryptionIv = match.component3().toByteArray() val encryptionKeyResponse = - runBlocking { app.get(encryptionUri, headers = headers) } + runBlocking { app.get(encryptionUri, headers = headers, verify = false) } encryptionData = encryptionKeyResponse.body?.bytes() ?: byteArrayOf() } @@ -238,7 +239,8 @@ class M3u8Helper { while (lastYield != c) { try { - val tsResponse = runBlocking { app.get(url, headers = headers) } + val tsResponse = + runBlocking { app.get(url, headers = headers, verify = false) } var tsData = tsResponse.body?.bytes() ?: byteArrayOf() if (encryptionState) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index 9450175b..c4d05cfe 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -38,6 +38,7 @@ import com.lagradost.cloudstream3.utils.DataStore.removeKey import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import okhttp3.internal.closeQuietly import java.io.BufferedInputStream @@ -1129,7 +1130,9 @@ object VideoDownloadManager { if (!stream.resume!!) realIndex = 0 val fileLengthAdd = stream.fileLength!! - val tsIterator = m3u8Helper.hlsYield(listOf(m3u8), realIndex) + val tsIterator = runBlocking { + m3u8Helper.hlsYield(listOf(m3u8), realIndex) + } val displayName = getDisplayName(name, extension)