diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt index e14f435f..cb107908 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt @@ -13,6 +13,7 @@ import com.lagradost.nicehttp.RequestBodyTypes import kotlinx.coroutines.delay import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody.Companion.toRequestBody +import org.jsoup.Jsoup val session = Session(Requests().baseClient) @@ -522,7 +523,8 @@ object SoraExtractor : SoraStream() { "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8" ) ).document - val srcm3u8 = resDoc.selectFirst("script:containsData(let url =)")?.data()?.let { + val srcm3u8 = + resDoc.selectFirst("script:containsData(let url =)")?.data()?.let { Regex("['|\"](.*?.m3u8)['|\"]").find(it)?.groupValues?.getOrNull(1) } callback.invoke( @@ -803,7 +805,8 @@ object SoraExtractor : SoraStream() { "versioncode" to "11", "clienttype" to "ios_jike_default" ) - val vipAPI = base64DecodeAPI("cA==YXA=cy8=Y20=di8=LnQ=b2s=a2w=bG8=aS4=YXA=ZS0=aWw=b2I=LW0=Z2E=Ly8=czo=dHA=aHQ=") + val vipAPI = + base64DecodeAPI("cA==YXA=cy8=Y20=di8=LnQ=b2s=a2w=bG8=aS4=YXA=ZS0=aWw=b2I=LW0=Z2E=Ly8=czo=dHA=aHQ=") val searchUrl = base64DecodeAPI("b20=LmM=b2s=a2w=bG8=Ly8=czo=dHA=aHQ=") val doc = app.get( "$searchUrl/search?keyword=$title", @@ -1088,14 +1091,32 @@ object SoraExtractor : SoraStream() { } - suspend fun invokeZoro( + suspend fun invokeAnimes( id: Int? = null, + title: String? = null, season: Int? = null, episode: Int? = null, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val malId = app.get("$tmdb2mal/?id=$id&s=$season").text.trim() + val malId = if (season != null) app.get("$tmdb2mal/?id=$id&s=$season").text.trim() else null + + argamap( + { + if (season != null) invokeZoro(malId, episode, subtitleCallback, callback) + }, + { + invokeAnimeKaizoku(title, malId, season, episode, callback) + } + ) + } + + private suspend fun invokeZoro( + malId: String? = null, + episode: Int? = null, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { val episodeId = app.get("$consumetMalAPI/info/$malId?provider=zoro") .parsedSafe()?.episodes?.find { it.number == episode @@ -1131,7 +1152,74 @@ object SoraExtractor : SoraStream() { } } + } + private suspend fun invokeAnimeKaizoku( + title: String? = null, + malId: String? = null, + season: Int? = null, + episode: Int? = null, + callback: (ExtractorLink) -> Unit + ) { + val fixTitle = title.fixTitle() + val search = app.get("$animeKaizokuAPI/?s=${fixTitle?.replace("-", " ")}").document + val detailHref = + search.select("ul#posts-container li").map { it.selectFirst("a")?.attr("href") } + .find { + if (season == null) it?.contains( + fixTitle ?: return, + true + ) == true else it?.contains(malId ?: return) == true + }?.let { fixUrl(it, animeKaizokuAPI) } + + val detail = app.get(detailHref ?: return).document + val postId = + detail.selectFirst("link[rel=shortlink]")?.attr("href")?.substringAfter("?p=") ?: return + val script = detail.selectFirst("script:containsData(DDL)")?.data()?.splitData() ?: return + + val media = fetchingKaizoku(animeKaizokuAPI, postId, script, detailHref).document + val iframe = media.select("tbody td[colspan=2]").map { it.attr("onclick") to it.text() } + .filter { it.second.contains("1080p", true) } + + val eps = if (season == null) { + null + } else { + if (episode!! < 10) "0$episode" else episode + } + + iframe.apmap { (data, name) -> + val worker = + fetchingKaizoku(animeKaizokuAPI, postId, data.splitData(), detailHref).document + .select("tbody td") + .map { Triple(it.attr("onclick"), it.text(), it.nextElementSibling()?.text()) } + + val episodeData = worker.let { list -> + if (season == null) list.firstOrNull() else list.find { + it.second.contains( + Regex("($eps\\.)|(-\\s$eps)") + ) + } + } ?: return@apmap null + + val ouo = fetchingKaizoku( + animeKaizokuAPI, + postId, + episodeData.first.splitData(), + detailHref + ).text.substringAfter("openInNewTab(\"") + .substringBefore("\")").let { base64Decode(it) } + + if(!ouo.startsWith("https://ouo")) return@apmap null + callback.invoke( + ExtractorLink( + "AnimeKaizoku [${episodeData.third}]", + "AnimeKaizoku [${episodeData.third}]", + bypassOuo(ouo) ?: return@apmap null, + "$animeKaizokuAPI/", + Qualities.P1080.value, + ) + ) + } } suspend fun invokeLing( @@ -1760,13 +1848,13 @@ object SoraExtractor : SoraStream() { episode: Int? = null, callback: (ExtractorLink) -> Unit ) { - val url = if(season == null) { + val url = if (season == null) { "$rStreamAPI/Movies/$id/$id.mp4" } else { "$rStreamAPI/Shows/$id/$season/$episode.mp4" } - if(!app.get(url).isSuccessful) return + if (!app.get(url).isSuccessful) return delay(4000) callback.invoke( @@ -1780,6 +1868,58 @@ object SoraExtractor : SoraStream() { ) } + suspend fun invokeFlixon( + tmdbId: Int? = null, + imdbId: String? = null, + season: Int? = null, + episode: Int? = null, + callback: (ExtractorLink) -> Unit + ) { + val onionUrl = "https://onionplay.se/" + val request = if (season == null) { + val res = app.get("$flixonAPI/$imdbId", referer = onionUrl) + if (res.text.contains("BEGIN PGP SIGNED MESSAGE") + ) app.get("$flixonAPI/$imdbId-1", referer = onionUrl) else res + } else { + app.get("$flixonAPI/$tmdbId-$season-$episode", referer = onionUrl) + } + + val script = request.document.selectFirst("script:containsData(= \"\";)")?.data() + val collection = script?.substringAfter("= [")?.substringBefore("];") + val num = script?.substringAfterLast("(value) -")?.substringBefore(");")?.trim()?.toInt() + ?: return + + val iframe = collection?.split(",")?.map { it.trim().toInt() }?.map { nums -> + nums.minus(num).toChar() + }?.joinToString("")?.let { Jsoup.parse(it) }?.selectFirst("button.redirect") + ?.attr("onclick") + ?.substringAfter("('")?.substringBefore("')") + + val unPacker = + app.get(iframe ?: return).document.selectFirst("script:containsData(JuicyCodes.Run)") + ?.data() + ?.substringAfter("JuicyCodes.Run(")?.substringBefore(");")?.split("+") + ?.joinToString("") { it.replace("\"", "").trim() } + ?.let { getAndUnpack(base64Decode(it)) } + + val ref = "https://onionflix.ru/" + val link = Regex("[\"']file[\"']:[\"'](.+?)[\"'],").find( + unPacker ?: return + )?.groupValues?.getOrNull(1)?.let { app.get(it, referer = ref).url } + + callback.invoke( + ExtractorLink( + "Flixon", + "Flixon", + link ?: return, + ref, + Qualities.P720.value, + link.contains(".m3u8") + ) + ) + + } + } class StreamM4u : XStreamCdn() { diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt index 8e3ea651..a98ea17f 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt @@ -2,6 +2,7 @@ package com.hexated import com.fasterxml.jackson.annotation.JsonProperty import com.hexated.SoraExtractor.invoke123Movie +import com.hexated.SoraExtractor.invokeAnimes import com.hexated.SoraExtractor.invokeBollyMaza import com.hexated.SoraExtractor.invokeDbgo import com.hexated.SoraExtractor.invokeFilmxy @@ -21,6 +22,7 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.metaproviders.TmdbProvider import com.hexated.SoraExtractor.invokeCrunchyroll import com.hexated.SoraExtractor.invokeFDMovies +import com.hexated.SoraExtractor.invokeFlixon import com.hexated.SoraExtractor.invokeFwatayako import com.hexated.SoraExtractor.invokeGMovies import com.hexated.SoraExtractor.invokeKisskh @@ -32,7 +34,6 @@ import com.hexated.SoraExtractor.invokeRStream import com.hexated.SoraExtractor.invokeSoraStream import com.hexated.SoraExtractor.invokeTvMovies import com.hexated.SoraExtractor.invokeUhdmovies -import com.hexated.SoraExtractor.invokeZoro import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.ExtractorLink @@ -64,7 +65,8 @@ open class SoraStream : TmdbProvider() { base64DecodeAPI("cHA=LmE=ZWw=cmM=dmU=aC4=dGM=d2E=eHA=Ly8=czo=dHA=aHQ=") // private var mainServerAPI = base64DecodeAPI("cA==YXA=bC4=Y2U=ZXI=LnY=aWU=b3Y=LW0=cmE=c28=Ly8=czo=dHA=aHQ=") - var netMoviesAPI = base64DecodeAPI("aQ==YXA=cC8=YXA=bC4=Y2U=ZXI=LnY=bG0=Zmk=dC0=bmU=Ly8=czo=dHA=aHQ=") + var netMoviesAPI = + base64DecodeAPI("aQ==YXA=cC8=YXA=bC4=Y2U=ZXI=LnY=bG0=Zmk=dC0=bmU=Ly8=czo=dHA=aHQ=") const val twoEmbedAPI = "https://www.2embed.to" const val vidSrcAPI = "https://v2.vidsrc.me" const val dbgoAPI = "https://dbgo.fun" @@ -95,6 +97,8 @@ open class SoraStream : TmdbProvider() { const val bollyMazaAPI = "https://b.bloginguru.info" const val moviesbayAPI = "https://moviesbay.live" const val rStreamAPI = "https://fsa.remotestre.am" + const val flixonAPI = "https://flixon.ru" + const val animeKaizokuAPI = "https://animekaizoku.com" fun getType(t: String?): TvType { return when (t) { @@ -350,13 +354,7 @@ open class SoraStream : TmdbProvider() { // ) // }, { - if (res.season != null && res.isAnime) invokeZoro( - res.id, - res.season, - res.episode, - subtitleCallback, - callback - ) + if (res.isAnime) invokeAnimes(res.id, res.title, res.season, res.episode, subtitleCallback, callback) }, { if (res.season != null && res.isAnime) invokeCrunchyroll( @@ -516,7 +514,10 @@ open class SoraStream : TmdbProvider() { }, { invokeRStream(res.id, res.season, res.episode, callback) - } + }, + { + invokeFlixon(res.id, res.imdbId, res.season, res.episode, callback) + }, ) return true diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt index 3f81ec01..82449e01 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt @@ -3,6 +3,7 @@ package com.hexated import com.hexated.SoraStream.Companion.filmxyAPI import com.hexated.SoraStream.Companion.gdbot import com.hexated.SoraStream.Companion.tvMoviesAPI +import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.base64Decode import com.lagradost.cloudstream3.base64Encode @@ -12,6 +13,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.SubtitleHelper import com.lagradost.cloudstream3.utils.getQualityFromName +import com.lagradost.nicehttp.NiceResponse import com.lagradost.nicehttp.RequestBodyTypes import com.lagradost.nicehttp.requestCreator import kotlinx.coroutines.delay @@ -256,13 +258,64 @@ suspend fun extractCovyn(url: String?): Pair? { } fun getDirectGdrive(url: String): String { - return if(url.endsWith("share_link")) { - "https://drive.google.com/uc?id=${url.substringAfter("/d/").substringBefore("/")}&export=download" - } else { + return if (url.contains("&export=download")) { url + } else { + "https://drive.google.com/uc?id=${ + url.substringAfter("/d/").substringBefore("/") + }&export=download" } } +suspend fun bypassOuo(url: String?) : String? { + var res = session.get(url ?: return null) + (1..2).forEach { _ -> + val document = res.document + val nextUrl = document.select("form").attr("action") + val data = document.select("form input").mapNotNull { + it.attr("name") to it.attr("value") + }.toMap().toMutableMap() + val captchaKey = document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]") + .attr("src").substringAfter("render=") + val token = APIHolder.getCaptchaToken(url, captchaKey) + data["x-token"] = token ?: "" + res = session.post( + nextUrl, + data = data, + headers = mapOf("content-type" to "application/x-www-form-urlencoded"), + allowRedirects = false + ) + } + + return res.headers["location"] +} + +suspend fun fetchingKaizoku( + domain: String, + postId: String, + data: List, + ref: String +): NiceResponse { + return app.post( + "$domain/wp-admin/admin-ajax.php", + data = mapOf( + "action" to "DDL", + "post_id" to postId, + "div_id" to data.first(), + "tab_id" to data[1], + "num" to data[2], + "folder" to data.last() + ), + headers = mapOf("X-Requested-With" to "XMLHttpRequest"), + referer = ref + ) +} + +fun String.splitData(): List { + return this.substringAfterLast("DDL(").substringBefore(")").split(",") + .map { it.replace("'", "").trim() } +} + suspend fun bypassFdAds(url: String?): String? { val directUrl = app.get(url ?: return null, verify = false).document.select("a#link").attr("href") .substringAfter("/go/") @@ -399,7 +452,7 @@ fun Document.findTvMoviesIframe(): String? { } fun String?.fixTitle(): String? { - return this?.replace(Regex("[!%:']|( &)"), "")?.replace(" ", "-")?.lowercase() + return this?.replace(Regex("[!%:'?]|( &)"), "")?.replace(" ", "-")?.lowercase() ?.replace("-–-", "-") }