diff --git a/IdlixProvider/build.gradle.kts b/IdlixProvider/build.gradle.kts index a2a176b5..6533b773 100644 --- a/IdlixProvider/build.gradle.kts +++ b/IdlixProvider/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 11 +version = 12 cloudstream { diff --git a/IdlixProvider/src/main/kotlin/com/hexated/IdlixProvider.kt b/IdlixProvider/src/main/kotlin/com/hexated/IdlixProvider.kt index 0674303d..064c18dd 100644 --- a/IdlixProvider/src/main/kotlin/com/hexated/IdlixProvider.kt +++ b/IdlixProvider/src/main/kotlin/com/hexated/IdlixProvider.kt @@ -6,6 +6,9 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.addActors import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson +import com.lagradost.nicehttp.Requests +import com.lagradost.nicehttp.Session import org.jsoup.nodes.Element import java.net.URI @@ -16,6 +19,7 @@ class IdlixProvider : MainAPI() { override val hasMainPage = true override var lang = "id" override val hasDownloadSupport = true + private val session = Session(Requests().baseClient) override val supportedTypes = setOf( TvType.Movie, TvType.TvSeries, @@ -47,9 +51,9 @@ class IdlixProvider : MainAPI() { val url = request.data.split("?") val nonPaged = request.name == "Featured" && page <= 1 val req = if (nonPaged) { - app.get(request.data) + session.get(request.data) } else { - app.get("${url.first()}$page/?${url.lastOrNull()}") + session.get("${url.first()}$page/?${url.lastOrNull()}") } mainUrl = getBaseUrl(req.url) val document = req.document @@ -94,7 +98,7 @@ class IdlixProvider : MainAPI() { } override suspend fun search(query: String): List { - val req = app.get("$mainUrl/search/$query") + val req = session.get("$mainUrl/search/$query") mainUrl = getBaseUrl(req.url) val document = req.document return document.select("div.result-item").map { @@ -109,7 +113,7 @@ class IdlixProvider : MainAPI() { } override suspend fun load(url: String): LoadResponse { - val request = app.get(url) + val request = session.get(url) directUrl = getBaseUrl(request.url) val document = request.document val title = @@ -189,7 +193,7 @@ class IdlixProvider : MainAPI() { callback: (ExtractorLink) -> Unit ): Boolean { - val document = app.get(data).document + val document = session.get(data).document val id = document.select("meta#dooplay-ajax-counter").attr("data-postid") val type = if (data.contains("/movie/")) "movie" else "tv" @@ -197,7 +201,7 @@ class IdlixProvider : MainAPI() { it.attr("data-nume") }.apmap { nume -> safeApiCall { - var source = app.post( + var source = session.post( url = "$directUrl/wp-admin/admin-ajax.php", data = mapOf( "action" to "doo_player_ajax", @@ -207,7 +211,7 @@ class IdlixProvider : MainAPI() { ), headers = mapOf("X-Requested-With" to "XMLHttpRequest"), referer = data - ).parsed().embed_url + ).let { tryParseJson(it.text) }?.embed_url ?: return@safeApiCall if (source.startsWith("https://uservideo.xyz")) { source = app.get(source).document.select("iframe").attr("src") diff --git a/SoraStream/build.gradle.kts b/SoraStream/build.gradle.kts index 1d33c459..4891d8da 100644 --- a/SoraStream/build.gradle.kts +++ b/SoraStream/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.konan.properties.Properties // use an integer for version numbers -version = 153 +version = 154 android { defaultConfig { diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt index aeb8d026..6041ab65 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt @@ -5,7 +5,6 @@ import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.nicehttp.Requests import com.lagradost.nicehttp.Session -import com.hexated.RabbitStream.extractRabbitStream import com.lagradost.cloudstream3.extractors.Filesim import com.lagradost.cloudstream3.extractors.StreamSB import com.lagradost.cloudstream3.extractors.Voe @@ -15,7 +14,6 @@ import com.lagradost.nicehttp.RequestBodyTypes import kotlinx.coroutines.delay import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody.Companion.toRequestBody -import okio.ByteString.Companion.encode import org.jsoup.Jsoup import org.jsoup.nodes.Document @@ -47,8 +45,9 @@ object SoraExtractor : SoraStream() { val titleMedia = ele.select("h3.movie-name").text() val titleSlug = title.createSlug() val yearMedia = ele.select("div.info-split > div:first-child").text().toIntOrNull() - val lastSeasonMedia = ele.select("div.info-split > div:nth-child(2)").text().substringAfter("SS") - .substringBefore("/").trim().toIntOrNull() + val lastSeasonMedia = + ele.select("div.info-split > div:nth-child(2)").text().substringAfter("SS") + .substringBefore("/").trim().toIntOrNull() (titleMedia.equals(title, true) || titleMedia.createSlug() .equals(titleSlug) || url?.contains("$titleSlug-") == true) && (if (season == null) { @@ -74,7 +73,8 @@ object SoraExtractor : SoraStream() { "$gokuAPI/ajax/movie/seasons/${ media.selectFirst("a.btn-wl")?.attr("data-id") ?: return }", headers = headers - ).document.select("a.ss-item").find { it.ownText().equals("Season $season", true) }?.attr("data-id") + ).document.select("a.ss-item").find { it.ownText().equals("Season $season", true) } + ?.attr("data-id") val episodeId = app.get( "$gokuAPI/ajax/movie/season/episodes/${seasonId ?: return}", @@ -93,15 +93,13 @@ object SoraExtractor : SoraStream() { val iframe = app.get("$gokuAPI/ajax/movie/episode/server/sources/$id", headers = headers) .parsedSafe()?.data?.link ?: return@apmap - extractRabbitStream( + loadCustomExtractor( if (iframe.contains("rabbitstream")) "Vidcloud" else "Upcloud", iframe, "$gokuAPI/", subtitleCallback, callback, - false, - decryptKey = RabbitStream.getKey() - ) { it } + ) } } @@ -417,7 +415,7 @@ object SoraExtractor : SoraStream() { } else { "$idlixAPI/episode/$fixTitle-season-$season-episode-$episode" } - invokeWpmovies(url,subtitleCallback, callback) + invokeWpmovies(url, subtitleCallback, callback) } suspend fun invokeMultimovies( @@ -433,7 +431,7 @@ object SoraExtractor : SoraStream() { } else { "$multimoviesAPI/episodes/$fixTitle-${season}x${episode}" } - invokeWpmovies(url,subtitleCallback, callback,true) + invokeWpmovies(url, subtitleCallback, callback, true) } suspend fun invokeNetmovies( @@ -450,7 +448,7 @@ object SoraExtractor : SoraStream() { } else { "$netmoviesAPI/episodes/$fixTitle-${season}x${episode}" } - invokeWpmovies(url,subtitleCallback, callback) + invokeWpmovies(url, subtitleCallback, callback) } private suspend fun invokeWpmovies( @@ -459,7 +457,7 @@ object SoraExtractor : SoraStream() { callback: (ExtractorLink) -> Unit, fixIframe: Boolean = false, ) { - val res = app.get(url ?: return) + val res = session.get(url ?: return) val referer = getBaseUrl(res.url) val document = res.document document.select("ul#playeroptionsul > li").map { @@ -469,11 +467,14 @@ object SoraExtractor : SoraStream() { it.attr("data-type") ) }.apmap { (id, nume, type) -> - val source = app.post( + val json = session.post( url = "$referer/wp-admin/admin-ajax.php", data = mapOf( "action" to "doo_player_ajax", "post" to id, "nume" to nume, "type" to type ), headers = mapOf("X-Requested-With" to "XMLHttpRequest"), referer = url - ).parsed().embed_url.let { if(fixIframe) Jsoup.parse(it).select("IFRAME").attr("SRC") else it } + ) + val source = tryParseJson(json.text)?.embed_url?.let { + if (fixIframe) Jsoup.parse(it).select("IFRAME").attr("SRC") else it + } ?: return@apmap if (!source.contains("youtube")) { loadExtractor(source, "$referer/", subtitleCallback, callback) } @@ -560,9 +561,11 @@ object SoraExtractor : SoraStream() { ) ) } + !source.contains("youtube") -> loadExtractor( source, "$uniqueStreamAPI/", subtitleCallback, callback ) + else -> { // pass } @@ -912,7 +915,10 @@ object SoraExtractor : SoraStream() { when { season == null -> slugTitle?.equals(slug) == true lastSeason == 1 -> slugTitle?.contains(slug) == true - else -> slugTitle?.contains(slug) == true && it.title?.contains("Season $season", true) == true + else -> slugTitle?.contains(slug) == true && it.title?.contains( + "Season $season", + true + ) == true } } data?.id to data?.title @@ -1066,16 +1072,23 @@ object SoraExtractor : SoraStream() { val headers = mapOf( "X-Requested-With" to "XMLHttpRequest", ) - val animeId = app.get("$malsyncAPI/mal/anime/${malId ?: return}").parsedSafe()?.sites?.zoro?.keys?.map { it } + val animeId = app.get("$malsyncAPI/mal/anime/${malId ?: return}") + .parsedSafe()?.sites?.zoro?.keys?.map { it } animeId?.apmap { id -> - val episodeId = app.get("$aniwatchAPI/ajax/v2/episode/list/${id ?: return@apmap}", headers = headers) + val episodeId = app.get( + "$aniwatchAPI/ajax/v2/episode/list/${id ?: return@apmap}", + headers = headers + ) .parsedSafe()?.html?.let { Jsoup.parse(it) }?.select("div.ss-list a")?.find { it.attr("data-number") == "${episode ?: 1}" } ?.attr("data-id") val servers = - app.get("$aniwatchAPI/ajax/v2/episode/servers?episodeId=${episodeId ?: return@apmap}", headers = headers) + app.get( + "$aniwatchAPI/ajax/v2/episode/servers?episodeId=${episodeId ?: return@apmap}", + headers = headers + ) .parsedSafe()?.html?.let { Jsoup.parse(it) } ?.select("div.item.server-item")?.map { Triple( @@ -1086,27 +1099,21 @@ object SoraExtractor : SoraStream() { } servers?.apmap servers@{ server -> - val iframe = app.get("$aniwatchAPI/ajax/v2/episode/sources?id=${server.second ?: return@servers}", headers = headers) - .parsedSafe()?.link ?: return@servers + val iframe = app.get( + "$aniwatchAPI/ajax/v2/episode/sources?id=${server.second ?: return@servers}", + headers = headers + ) + .parsedSafe()?.link ?: return@servers val audio = if (server.third == "sub") "Raw" else "English Dub" - if (server.first.contains(Regex("Vidstreaming|MegaCloud|Vidcloud"))) { - extractRabbitStream( - "${server.first} [$audio]", - iframe, - "$aniwatchAPI/", - subtitleCallback, - callback, - false, - decryptKey = RabbitStream.getZoroKey() - ) { it } - } else { - loadExtractor(iframe, "$aniwatchAPI/", subtitleCallback, callback) - } - + loadCustomExtractor( + "${server.first} [$audio]", + iframe, + "$aniwatchAPI/", + subtitleCallback, + callback, + ) } } - - } suspend fun invokeLing( @@ -1307,9 +1314,11 @@ object SoraExtractor : SoraStream() { val gdBotLink = extractGdbot(link) extractGdflix(gdBotLink ?: return@apmap) } + link.contains("gdflix") -> { extractGdflix(link) } + else -> { return@apmap } @@ -1476,15 +1485,17 @@ object SoraExtractor : SoraStream() { val gdBotLink = extractGdbot(fdLink ?: return@apmap null) extractGdflix(gdBotLink ?: return@apmap null) } + type.contains("oiya") -> { val oiyaLink = extractOiya(fdLink ?: return@apmap null, qualities) - if(oiyaLink?.contains("gdtot") == true) { + if (oiyaLink?.contains("gdtot") == true) { val gdBotLink = extractGdbot(oiyaLink) extractGdflix(gdBotLink ?: return@apmap null) } else { oiyaLink } } + else -> { return@apmap null } @@ -1634,7 +1645,7 @@ object SoraExtractor : SoraStream() { callback: (ExtractorLink) -> Unit ) { val id = getCrunchyrollId("${aniId ?: return}") - ?: getCrunchyrollIdFromMalSync("${malId ?: return}") ?: return + ?: getCrunchyrollIdFromMalSync("${malId ?: return}") val audioLocal = listOf( "ja-JP", "en-US", @@ -1946,24 +1957,33 @@ object SoraExtractor : SoraStream() { it.attr("data-id") to it.text() }.apmap { when { - (it.second.equals("Player F", true) || it.second.equals("Player N", true)) && !isAnime -> { + (it.second.equals("Player F", true) || it.second.equals( + "Player N", + true + )) && !isAnime -> { invokeSmashyFfix(it.second, it.first, url, callback) } + it.first.contains("/gtop") -> { invokeSmashyGtop(it.second, it.first, callback) } + it.first.contains("/dude_tv") -> { invokeSmashyDude(it.second, it.first, callback) } + it.first.contains("/rip") -> { invokeSmashyRip(it.second, it.first, subtitleCallback, callback) } + it.first.contains("/im.php") && !isAnime -> { invokeSmashyIm(it.second, it.first, subtitleCallback, callback) } + it.first.contains("/rw.php") && !isAnime -> { invokeSmashyRw(it.second, it.first, subtitleCallback, callback) } + else -> return@apmap } } @@ -2015,63 +2035,6 @@ object SoraExtractor : SoraStream() { } - } - - suspend fun invokeBaymovies( - title: String? = null, - year: Int? = null, - season: Int? = null, - episode: Int? = null, - callback: (ExtractorLink) -> Unit, - ) { - val api = "https://thebayindexpublicgroupapi.zindex.eu.org" - val key = base64DecodeAPI("ZW0=c3Q=c3k=b28=YWQ=Ymg=") - val headers = mapOf( - "Referer" to "$baymoviesAPI/", - "Origin" to baymoviesAPI, - "cf_cache_token" to "UKsVpQqBMxB56gBfhYKbfCVkRIXMh42pk6G4DdkXXoVh7j4BjV" - ) - val query = getIndexQuery(title, year, season, episode) - val search = app.get( - "$api/0:search?q=$query&page_token=&page_index=0", - headers = headers - ).text - val media = searchIndex(title, season, episode, year, search) ?: return - - media.apmap { file -> - val expiry = (System.currentTimeMillis() + 345600000).toString() - val hmacSign = "${file.id}@$expiry".encode() - .hmacSha256(key.encode()).base64().replace("+", "-") - val encryptedId = - base64Encode(CryptoAES.encrypt(key, file.id ?: return@apmap null).toByteArray()) - val encryptedExpiry = base64Encode(CryptoAES.encrypt(key, expiry).toByteArray()) - val worker = getConfig().workers.randomOrNull() ?: return@apmap null - - val link = - "https://api.$worker.workers.dev/download.aspx?file=$encryptedId&expiry=$encryptedExpiry&mac=$hmacSign" - val size = file.size?.toDouble() ?: return@apmap null - val sizeFile = "%.2f GB".format(bytesToGigaBytes(size)) - val tags = Regex("\\d{3,4}[pP]\\.?(.*?)\\.(mkv|mp4)").find( - file.name ?: return@apmap null - )?.groupValues?.getOrNull(1)?.replace(".", " ")?.trim() - ?: "" - val quality = - Regex("(\\d{3,4})[pP]").find(file.name)?.groupValues?.getOrNull(1)?.toIntOrNull() - ?: Qualities.P1080.value - - callback.invoke( - ExtractorLink( - "Baymovies", - "Baymovies $tags [$sizeFile]", - link, - "$baymoviesAPI/", - quality, - ) - ) - - } - - } suspend fun invokeBlackmovies( @@ -2094,26 +2057,6 @@ object SoraExtractor : SoraStream() { ) } - suspend fun invokeRinzrymovies( - apiUrl: String, - api: String, - title: String? = null, - year: Int? = null, - season: Int? = null, - episode: Int? = null, - callback: (ExtractorLink) -> Unit, - ) { - invokeIndex( - apiUrl, - api, - title, - year, - season, - episode, - callback, - ) - } - suspend fun invokeCodexmovies( apiUrl: String, api: String, @@ -2490,7 +2433,7 @@ object SoraExtractor : SoraStream() { episode: Int? = null, callback: (ExtractorLink) -> Unit, ) { - val (seasonSlug, episodeSlug) = getEpisodeSlug(season, episode) + val slug = getEpisodeSlug(season, episode) val query = if (season == null) { title } else { @@ -2530,7 +2473,7 @@ object SoraExtractor : SoraStream() { media.third, gomoviesAPI ) - ).document.selectFirst("div#g_MXOzFGouZrOAUioXjpddqkZK a:contains(Episode $episodeSlug)") + ).document.selectFirst("div#g_MXOzFGouZrOAUioXjpddqkZK a:contains(Episode ${slug.second})") ?.attr("href") } ?: return @@ -2576,7 +2519,6 @@ object SoraExtractor : SoraStream() { year: Int? = null, season: Int? = null, episode: Int? = null, - subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit, ) { val query = if (season == null) { @@ -2610,7 +2552,8 @@ object SoraExtractor : SoraStream() { } val iframeDoc = app.get(iframe, referer = "$ask4MoviesAPI/").text - val script = Regex("""eval\(function\(p,a,c,k,e,.*\)\)""").findAll(iframeDoc).lastOrNull()?.value + val script = + Regex("""eval\(function\(p,a,c,k,e,.*\)\)""").findAll(iframeDoc).lastOrNull()?.value val unpacked = getAndUnpack(script ?: return) val m3u8 = Regex("file:\\s*\"(.*?m3u8.*?)\"").find(unpacked)?.groupValues?.getOrNull(1) M3u8Helper.generateM3u8( @@ -2748,7 +2691,8 @@ object SoraExtractor : SoraStream() { "$nineTvAPI/tv/$tmdbId-$season-$episode" } - val iframe = app.get(url, referer = "https://pressplay.top/").document.selectFirst("iframe")?.attr("src") ?: return + val iframe = app.get(url, referer = "https://pressplay.top/").document.selectFirst("iframe") + ?.attr("src") ?: return loadExtractor(iframe, "$nineTvAPI/", subtitleCallback, callback) } @@ -2898,7 +2842,6 @@ object SoraExtractor : SoraStream() { imdbId: String? = null, title: String? = null, year: Int? = null, - season: Int? = null, episode: Int? = null, callback: (ExtractorLink) -> Unit ) { @@ -2909,7 +2852,7 @@ object SoraExtractor : SoraStream() { null, title, year, - season, + null, episode, false ) @@ -2919,7 +2862,11 @@ object SoraExtractor : SoraStream() { val size = getIndexSize(stream.title) val headers = stream.behaviorHints?.proxyHeaders?.request ?: mapOf() - if(!app.get(stream.url ?: return@apmap, headers = headers).isSuccessful) return@apmap + if (!app.get( + stream.url ?: return@apmap, + headers = headers + ).isSuccessful + ) return@apmap callback.invoke( ExtractorLink( @@ -2943,7 +2890,8 @@ object SoraExtractor : SoraStream() { ) { val referer = "https://2now.tv/" val (seasonSlug, episodeSlug) = getEpisodeSlug(season, episode) - val url = if(season == null) "$nowTvAPI/$tmdbId.mp4" else "$nowTvAPI/tv/$tmdbId/s${season}e${episodeSlug}.mp4" + val url = + if (season == null) "$nowTvAPI/$tmdbId.mp4" else "$nowTvAPI/tv/$tmdbId/s${season}e${episodeSlug}.mp4" if (!app.get(url, referer = referer).isSuccessful) return callback.invoke( ExtractorLink( @@ -3038,7 +2986,7 @@ object SoraExtractor : SoraStream() { ).text M3u8Helper.generateM3u8( - if(host == navyAPI) "Navy" else "Moment", + if (host == navyAPI) "Navy" else "Moment", path, "${referer}/" ).forEach(callback) @@ -3081,7 +3029,10 @@ object SoraExtractor : SoraStream() { ) ).parsedSafe()?.value - val script = app.get(server ?: return, referer = "$emoviesAPI/").document.selectFirst("script:containsData(sources:)")?.data() ?: return + val script = app.get( + server ?: return, + referer = "$emoviesAPI/" + ).document.selectFirst("script:containsData(sources:)")?.data() ?: return val sources = Regex("sources:\\s*\\[(.*)],").find(script)?.groupValues?.get(1)?.let { tryParseJson>("[$it]") } diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt index f46f41e2..14ee4366 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt @@ -159,7 +159,6 @@ open class SoraStream : TmdbProvider() { const val jsMoviesAPI = "https://jsupload.jnsbot.workers.dev/0:" const val xtremeMoviesAPI = "https://kartik19.xtrememirror0.workers.dev/0:" const val tgarMovieAPI = "https://tgarchive.eu.org" - const val baymoviesAPI = "https://opengatewayindex.pages.dev" const val papaonMovies1API = "https://m.papaonwork.workers.dev/0:" const val papaonMovies2API = "https://m.papaonwork.workers.dev/1:" const val xMovieAPI = "https://xemovies.to" @@ -789,7 +788,6 @@ open class SoraStream : TmdbProvider() { res.year, res.season, res.episode, - subtitleCallback, callback ) }, @@ -819,7 +817,6 @@ open class SoraStream : TmdbProvider() { res.imdbId, res.title, res.year, - res.season, res.episode, callback ) diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt index efd2a6d3..1558a2f7 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt @@ -276,7 +276,6 @@ class SoraStreamLite : SoraStream() { res.year, res.season, res.episode, - subtitleCallback, callback ) }, diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt index 40ed6859..35c490a5 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt @@ -1,11 +1,9 @@ package com.hexated import android.util.Base64 -import com.fasterxml.jackson.annotation.JsonProperty import com.hexated.DumpUtils.queryApi import com.hexated.SoraStream.Companion.anilistAPI import com.hexated.SoraStream.Companion.base64DecodeAPI -import com.hexated.SoraStream.Companion.baymoviesAPI import com.hexated.SoraStream.Companion.consumetHelper import com.hexated.SoraStream.Companion.crunchyrollAPI import com.hexated.SoraStream.Companion.filmxyAPI @@ -19,12 +17,9 @@ import com.hexated.SoraStream.Companion.watchOnlineAPI import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getCaptchaToken import com.lagradost.cloudstream3.APIHolder.unixTimeMS -import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.utils.* -import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson -import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.nicehttp.NiceResponse import com.lagradost.nicehttp.RequestBodyTypes import com.lagradost.nicehttp.requestCreator @@ -37,7 +32,6 @@ import okhttp3.RequestBody.Companion.toRequestBody import org.jsoup.nodes.Document import java.math.BigInteger import java.net.* -import java.nio.charset.StandardCharsets import java.security.* import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.X509EncodedKeySpec @@ -52,12 +46,6 @@ import kotlin.math.min val bflixChipperKey = base64DecodeAPI("Yjc=ejM=TzA=YTk=WHE=WnU=bXU=RFo=") const val bflixKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" const val otakuzBaseUrl = "https://otakuz.live/" -val soraHeaders = mapOf( - "lang" to "en", - "versioncode" to "33", - "clienttype" to "android_Official", - "deviceid" to getDeviceId(), -) val encodedIndex = arrayOf( "GamMovies", "JSMovies", @@ -1165,6 +1153,33 @@ suspend fun tmdbToAnimeId(title: String?, year: Int?, season: String?, type: TvT } +suspend fun loadCustomExtractor( + name: String, + url: String, + referer: String? = null, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + quality: Int? = null, +) { + loadExtractor(url, referer, subtitleCallback) { link -> + callback.invoke( + ExtractorLink( + name, + name, + link.url, + link.referer, + when { + link.isM3u8 -> link.quality + else -> quality ?: link.quality + }, + link.isM3u8, + link.headers, + link.extractorData + ) + ) + } +} + fun getSeason(month: Int?): String? { val seasons = arrayOf( "Winter", "Winter", "Spring", "Spring", "Spring", "Summer", @@ -1182,7 +1197,6 @@ fun getPutlockerQuality(quality: String): Int { } } - fun getEpisodeSlug( season: Int? = null, episode: Int? = null, @@ -1264,23 +1278,6 @@ fun matchingIndex( ) == true && ((mediaMimeType in mimeType) || mediaName.contains(Regex("\\.mkv|\\.mp4|\\.avi"))) } -suspend fun getConfig(): BaymoviesConfig { - val regex = """const country = "(.*?)"; -const downloadtime = "(.*?)"; -var arrayofworkers = (.*)""".toRegex() - val js = app.get( - "https://geolocation.zindex.eu.org/api.js", - referer = "$baymoviesAPI/", - ).text - val match = regex.find(js) ?: throw ErrorLoadingException() - val country = match.groupValues[1] - val downloadTime = match.groupValues[2] - val workers = tryParseJson>(match.groupValues[3]) - ?: throw ErrorLoadingException() - - return BaymoviesConfig(country, downloadTime, workers) -} - fun decodeIndexJson(json: String): String { val slug = json.reversed().substring(24) return base64Decode(slug.substring(0, slug.length - 20)) @@ -1791,312 +1788,6 @@ object CryptoAES { } -object RabbitStream { - - suspend fun MainAPI.extractRabbitStream( - server: String, - url: String, - ref: String, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit, - useSidAuthentication: Boolean, - /** Used for extractorLink name, input: Source name */ - extractorData: String? = null, - decryptKey: String? = null, - nameTransformer: (String) -> String, - ) = suspendSafeApiCall { - // https://rapid-cloud.ru/embed-6/dcPOVRE57YOT?z= -> https://rapid-cloud.ru/embed-6 - val mainIframeUrl = - url.substringBeforeLast("/") - val mainIframeId = url.substringAfterLast("/") - .substringBefore("?") // https://rapid-cloud.ru/embed-6/dcPOVRE57YOT?z= -> dcPOVRE57YOT - var sid: String? = null - if (useSidAuthentication && extractorData != null) { - negotiateNewSid(extractorData)?.also { pollingData -> - app.post( - "$extractorData&t=${generateTimeStamp()}&sid=${pollingData.sid}", - requestBody = "40".toRequestBody(), - timeout = 60 - ) - val text = app.get( - "$extractorData&t=${generateTimeStamp()}&sid=${pollingData.sid}", - timeout = 60 - ).text.replaceBefore("{", "") - - sid = AppUtils.parseJson(text).sid - ioSafe { app.get("$extractorData&t=${generateTimeStamp()}&sid=${pollingData.sid}") } - } - } - val mainIframeAjax = mainIframeUrl.let { - if(it.contains("/embed-2/e-1")) it.replace( - "/embed-2/e-1", - "/embed-2/ajax/e-1" - ) else it.replace( - "/embed", - "/ajax/embed" - ) - } - val getSourcesUrl = "$mainIframeAjax/getSources?id=$mainIframeId${sid?.let { "$&sId=$it" } ?: ""}" - val response = app.get( - getSourcesUrl, - referer = mainUrl, - headers = mapOf( - "X-Requested-With" to "XMLHttpRequest", - "Accept" to "*/*", - "Accept-Language" to "en-US,en;q=0.5", - "Connection" to "keep-alive", - "TE" to "trailers" - ) - ) - - val sourceObject = if (decryptKey != null) { - val encryptedMap = response.parsedSafe() - val sources = encryptedMap?.sources - if (sources == null || encryptedMap.encrypted == false) { - response.parsedSafe() - } else { - val (realKey, encData) = extractRealKey(sources, decryptKey) - val decrypted = decryptMapped>(encData, realKey) - SourceObject( - sources = decrypted, - tracks = encryptedMap.tracks - ) - } - } else { - response.parsedSafe() - } ?: return@suspendSafeApiCall - - sourceObject.tracks?.forEach { track -> - track?.toSubtitleFile()?.let { subtitleFile -> - subtitleCallback.invoke(subtitleFile) - } - } - - val list = listOf( - sourceObject.sources to "source 1", - sourceObject.sources1 to "source 2", - sourceObject.sources2 to "source 3", - sourceObject.sourcesBackup to "source backup" - ) - - list.forEach { subList -> - subList.first?.forEach { source -> - source?.toExtractorLink( - server, - ref, - extractorData, - ) - ?.forEach { - // Sets Zoro SID used for video loading -// (this as? ZoroProvider)?.sid?.set(it.url.hashCode(), sid) - callback(it) - } - } - } - } - - private suspend fun Sources.toExtractorLink( - name: String, - referer: String, - extractorData: String? = null, - ): List? { - return this.file?.let { file -> - //println("FILE::: $file") - val isM3u8 = URI(this.file).path.endsWith(".m3u8") || this.type.equals( - "hls", - ignoreCase = true - ) - return if (isM3u8) { - suspendSafeApiCall { - M3u8Helper().m3u8Generation( - M3u8Helper.M3u8Stream( - this.file, - null, - mapOf("Referer" to "https://mzzcloud.life/") - ), false - ) - .map { stream -> - ExtractorLink( - name, - name, - stream.streamUrl, - referer, - getQualityFromName(stream.quality?.toString()), - true, - extractorData = extractorData - ) - } - }.takeIf { !it.isNullOrEmpty() } ?: listOf( - // Fallback if m3u8 extractor fails - ExtractorLink( - name, - name, - this.file, - referer, - getQualityFromName(this.label), - isM3u8, - extractorData = extractorData - ) - ) - } else { - listOf( - ExtractorLink( - name, - name, - file, - referer, - getQualityFromName(this.label), - false, - extractorData = extractorData - ) - ) - } - } - } - - private fun Tracks.toSubtitleFile(): SubtitleFile? { - return this.file?.let { - SubtitleFile( - this.label ?: "Unknown", - it - ) - } - } - - /** - * Generates a session - * 1 Get request. - * */ - private suspend fun negotiateNewSid(baseUrl: String): PollingData? { - // Tries multiple times - for (i in 1..5) { - val jsonText = - app.get("$baseUrl&t=${generateTimeStamp()}").text.replaceBefore( - "{", - "" - ) -// println("Negotiated sid $jsonText") - AppUtils.parseJson(jsonText)?.let { return it } - delay(1000L * i) - } - return null - } - - private fun generateTimeStamp(): String { - val chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_" - var code = "" - var time = APIHolder.unixTimeMS - while (time > 0) { - code += chars[(time % (chars.length)).toInt()] - time /= chars.length - } - return code.reversed() - } - - suspend fun getKey(): String { - return app.get("https://raw.githubusercontent.com/enimax-anime/key/e4/key.txt") - .text - } - - suspend fun getZoroKey(): String { - return app.get("https://raw.githubusercontent.com/enimax-anime/key/e6/key.txt").text - } - - private fun extractRealKey(originalString: String?, stops: String) : Pair { - val table = parseJson>>(stops) - val decryptedKey = StringBuilder() - var offset = 0 - var encryptedString = originalString - - table.forEach { (start, end) -> - decryptedKey.append(encryptedString?.substring(start - offset, end - offset)) - encryptedString = encryptedString?.substring(0, start - offset) + encryptedString?.substring(end - offset) - offset += end - start - } - return decryptedKey.toString() to encryptedString.toString() - } - - private inline fun decryptMapped(input: String, key: String): T? { - return tryParseJson(decrypt(input, key)) - } - - private fun decrypt(input: String, key: String): String { - return decryptSourceUrl( - generateKey( - base64DecodeArray(input).copyOfRange(8, 16), - key.toByteArray() - ), input - ) - } - - private fun generateKey(salt: ByteArray, secret: ByteArray): ByteArray { - var key = md5(secret + salt) - var currentKey = key - while (currentKey.size < 48) { - key = md5(key + secret + salt) - currentKey += key - } - return currentKey - } - - private fun md5(input: ByteArray): ByteArray { - return MessageDigest.getInstance("MD5").digest(input) - } - - private fun decryptSourceUrl(decryptionKey: ByteArray, sourceUrl: String): String { - val cipherData = base64DecodeArray(sourceUrl) - val encrypted = cipherData.copyOfRange(16, cipherData.size) - val aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding") - - Objects.requireNonNull(aesCBC).init( - Cipher.DECRYPT_MODE, SecretKeySpec( - decryptionKey.copyOfRange(0, 32), - "AES" - ), - IvParameterSpec(decryptionKey.copyOfRange(32, decryptionKey.size)) - ) - val decryptedData = aesCBC!!.doFinal(encrypted) - return String(decryptedData, StandardCharsets.UTF_8) - } - - data class PollingData( - @JsonProperty("sid") val sid: String? = null, - @JsonProperty("upgrades") val upgrades: ArrayList = arrayListOf(), - @JsonProperty("pingInterval") val pingInterval: Int? = null, - @JsonProperty("pingTimeout") val pingTimeout: Int? = null - ) - - data class Tracks( - @JsonProperty("file") val file: String?, - @JsonProperty("label") val label: String?, - @JsonProperty("kind") val kind: String? - ) - - data class Sources( - @JsonProperty("file") val file: String?, - @JsonProperty("type") val type: String?, - @JsonProperty("label") val label: String? - ) - - data class SourceObject( - @JsonProperty("sources") val sources: List? = null, - @JsonProperty("sources_1") val sources1: List? = null, - @JsonProperty("sources_2") val sources2: List? = null, - @JsonProperty("sourcesBackup") val sourcesBackup: List? = null, - @JsonProperty("tracks") val tracks: List? = null - ) - - data class SourceObjectEncrypted( - @JsonProperty("sources") val sources: String?, - @JsonProperty("encrypted") val encrypted: Boolean?, - @JsonProperty("sources_1") val sources1: String?, - @JsonProperty("sources_2") val sources2: String?, - @JsonProperty("sourcesBackup") val sourcesBackup: String?, - @JsonProperty("tracks") val tracks: List? - ) - -} - object DumpUtils { private val deviceId = getDeviceId()