diff --git a/SoraStream/build.gradle.kts b/SoraStream/build.gradle.kts index 66f39c81..4c8a78d1 100644 --- a/SoraStream/build.gradle.kts +++ b/SoraStream/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 45 +version = 46 cloudstream { diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt index 08e15dd1..640da299 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt @@ -1,5 +1,6 @@ package com.hexated +import android.util.Log import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.utils.* @@ -1354,47 +1355,64 @@ object SoraExtractor : SoraStream() { })?.filter { it.first.contains("gdtot") } ?: return iframe.apmap { (iframeLink, title) -> + val size = Regex("(?i)\\s(\\S+gb|mb)").find(title)?.groupValues?.getOrNull(1) val gdBotLink = extractGdbot(iframeLink) - val iframeGdbot = app.get( - gdBotLink ?: return@apmap null - ).document.selectFirst("ul.divide-y li.flex.flex-col a:contains(Drivebot)") - ?.attr("href") - val driveDoc = app.get(iframeGdbot ?: return@apmap null) + val videoLink = extractDrivebot(gdBotLink ?: return@apmap null) - val ssid = driveDoc.cookies["PHPSESSID"] - val script = driveDoc.document.selectFirst("script:containsData(var formData)")?.data() + callback.invoke( + ExtractorLink( + "GMovies [$size]", + "GMovies [$size]", + videoLink ?: return@apmap null, + "", + getGMoviesQuality(title) + ) + ) + } + } - val baseUrl = getBaseUrl(iframeGdbot) - val token = script?.substringAfter("'token', '")?.substringBefore("');") - val link = script?.substringAfter("fetch('")?.substringBefore("',").let { "$baseUrl$it" } + suspend fun invokeFDMovies( + title: String? = null, + season: Int? = null, + episode: Int? = null, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val fixTitle = title.fixTitle() + val url = if (season == null) { + "$fdMoviesAPI/movies/$fixTitle" + } else { + "$fdMoviesAPI/episodes/$fixTitle-s${season}xe${episode}/" + } - val body = FormBody.Builder() - .addEncoded("token", "$token") - .build() - val cookies = mapOf("PHPSESSID" to "$ssid") + val request = app.get(url) + if(!request.isSuccessful) return - val size = Regex("(?i)\\s(\\S+gb|mb)").find(title)?.groupValues?.getOrNull(1)?.let { "[$it]" } ?: "" - app.post( - link, - requestBody = body, - headers = mapOf( - "Accept" to "*/*", - "Origin" to baseUrl, - "Sec-Fetch-Site" to "same-origin" - ), - cookies = cookies - ).parsedSafe()?.url?.let { videoLink -> - callback.invoke( - ExtractorLink( - "GMovies $size", - "GMovies $size", - videoLink, - "", - getGMoviesQuality(title) - ) + val iframe = request.document.select("div#download tbody tr").map { it } + .filter { it.select("img").attr("src").contains("gdtot") }.map { + Triple( + it.select("a").attr("href"), + it.select("strong.quality").text(), + it.select("td:nth-child(4)").text() ) } + Log.i("fdMoviesAPI", "$iframe") + iframe.apmap { (link, quality, size) -> + val fdLink = bypassFdAds(link) + val gdBotLink = extractGdbot(fdLink ?: return@apmap null) + val videoLink = extractDrivebot(gdBotLink ?: return@apmap null) + + callback.invoke( + ExtractorLink( + "FDMovies [$size]", + "FDMovies [$size]", + videoLink ?: return@apmap null, + "", + getGMoviesQuality(quality) + ) + ) } + } @@ -1548,4 +1566,8 @@ data class SeasonFwatayako( data class SourcesFwatayako( @JsonProperty("movie") val sourcesMovie: String? = null, @JsonProperty("tv") val sourcesTv: ArrayList? = arrayListOf(), +) + +data class DriveBotLink( + @JsonProperty("url") val url: String? = null, ) \ No newline at end of file diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt index 488b9250..f3a32789 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt @@ -11,7 +11,6 @@ import com.hexated.SoraExtractor.invokeIdlix import com.hexated.SoraExtractor.invokeKimcartoon import com.hexated.SoraExtractor.invokeMovieHab import com.hexated.SoraExtractor.invokeNoverse -import com.hexated.SoraExtractor.invokeOlgply import com.hexated.SoraExtractor.invokeSeries9 import com.hexated.SoraExtractor.invokeSoraVIP import com.hexated.SoraExtractor.invokeTwoEmbed @@ -22,6 +21,7 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.metaproviders.TmdbProvider import com.hexated.SoraExtractor.invoZoro +import com.hexated.SoraExtractor.invokeFDMovies import com.hexated.SoraExtractor.invokeFwatayako import com.hexated.SoraExtractor.invokeGMovies import com.hexated.SoraExtractor.invokeLing @@ -52,7 +52,7 @@ open class SoraStream : TmdbProvider() { const val gdbot = "https://gdbot.xyz" private val mainAPI = 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=") +// private var mainServerAPI = base64DecodeAPI("cA==YXA=bC4=Y2U=ZXI=LnY=aWU=b3Y=LW0=cmE=c28=Ly8=czo=dHA=aHQ=") const val twoEmbedAPI = "https://www.2embed.to" const val vidSrcAPI = "https://v2.vidsrc.me" const val dbgoAPI = "https://dbgo.fun" @@ -75,6 +75,7 @@ open class SoraStream : TmdbProvider() { const val uhdmoviesAPI = "https://uhdmovies.site" const val fwatayakoAPI = "https://5100.svetacdn.in" const val gMoviesAPI = "https://gdrivemovies.xyz" + const val fdMoviesAPI = "https://freedrivemovie.com" fun getType(t: String?): TvType { return when (t) { @@ -166,12 +167,13 @@ open class SoraStream : TmdbProvider() { override suspend fun load(url: String): LoadResponse? { val data = parseJson(url) val type = getType(data.type) - val resUrl = if(type == TvType.Movie) { + val resUrl = if (type == TvType.Movie) { "$tmdbAPI/movie/${data.id}?api_key=$apiKey&append_to_response=credits,external_ids,videos,recommendations" } else { "$tmdbAPI/tv/${data.id}?api_key=$apiKey&append_to_response=credits,external_ids,videos,recommendations" } - val res = app.get(resUrl).parsedSafe() ?: throw ErrorLoadingException("Invalid Json Response") + val res = app.get(resUrl).parsedSafe() + ?: throw ErrorLoadingException("Invalid Json Response") val title = res.title ?: res.name ?: return null val poster = getOriImageUrl(res.posterPath) @@ -180,7 +182,7 @@ open class SoraStream : TmdbProvider() { val year = (res.releaseDate ?: res.firstAirDate)?.split("-")?.first()?.toIntOrNull() val rating = res.vote_average.toString().toRatingInt() val genres = res.genres?.mapNotNull { it.name } - val show = if (genres?.contains("Animation") == true && res.original_language == "ja") "Anime" else "Series/Movies" + val isAnime = genres?.contains("Animation") == true && res.original_language == "ja" val actors = res.credits?.cast?.mapNotNull { cast -> ActorData( @@ -191,7 +193,8 @@ open class SoraStream : TmdbProvider() { roleString = cast.character ) } ?: return null - val recommendations = res.recommendations?.results?.mapNotNull { media -> media.toSearchResponse() } + val recommendations = + res.recommendations?.results?.mapNotNull { media -> media.toSearchResponse() } val trailer = res.videos?.results?.map { "https://www.youtube.com/watch?v=${it.key}" } ?.randomOrNull() @@ -212,7 +215,7 @@ open class SoraStream : TmdbProvider() { title = title, year = season.airDate?.split("-")?.first()?.toIntOrNull(), orgTitle = orgTitle, - show = show, + isAnime = isAnime, airedYear = year, lastSeason = lastSeason ).toJson(), @@ -256,7 +259,7 @@ open class SoraStream : TmdbProvider() { title = title, year = year, orgTitle = orgTitle, - show = show, + isAnime = isAnime, ).toJson(), ) { this.posterUrl = poster @@ -280,16 +283,6 @@ open class SoraStream : TmdbProvider() { ): Boolean { val res = parseJson(data) -// val query = if (res.type == "tv") { -// "$mainServerAPI/tv-shows/${res.id}/season/${res.season}/episode/${res.episode}?_data=routes/tv-shows/\$tvId.season.\$seasonId.episode.\$episodeId" -// } else { -// "$mainServerAPI/movies/${res.id}/watch?_data=routes/movies/\$movieId.watch" -// } -// val referer = if (res.type == "tv") { -// "$mainServerAPI/tv-shows/${res.id}/season/${res.season}/episode/${res.episode}" -// } else { -// "$mainServerAPI/movies/${res.id}/watch" -// } argamap( { @@ -302,35 +295,6 @@ open class SoraStream : TmdbProvider() { callback ) }, -// { -// val json = app.get( -// query, -// referer = referer, -// headers = mapOf("User-Agent" to getRandomUserAgent()) -// ).parsedSafe() -// -// json?.sources?.map { source -> -// callback.invoke( -// ExtractorLink( -// this.name, -// this.name, -// source.url ?: return@map null, -// "$mainServerAPI/", -// source.quality?.toIntOrNull() ?: Qualities.Unknown.value, -// isM3u8 = source.isM3U8, -// headers = mapOf("Origin" to mainServerAPI) -// ) -// ) -// } -// json?.subtitles?.map { sub -> -// subtitleCallback.invoke( -// SubtitleFile( -// getLanguage(sub.lang.toString()), -// sub.url ?: return@map null -// ) -// ) -// } -// }, { invokeTwoEmbed(res.id, res.season, res.episode, subtitleCallback, callback) }, @@ -366,7 +330,7 @@ open class SoraStream : TmdbProvider() { // ) // }, { - if (res.season != null && res.show == "Anime") invoZoro( + if (res.season != null && res.isAnime) invoZoro( res.id, res.season, res.episode, @@ -375,7 +339,7 @@ open class SoraStream : TmdbProvider() { ) }, { - invokeHDMovieBox(res.title, res.season, res.episode, callback) + if(!res.isAnime) invokeHDMovieBox(res.title, res.season, res.episode, callback) }, { invokeSeries9(res.title, res.season, res.episode, subtitleCallback, callback) @@ -443,7 +407,7 @@ open class SoraStream : TmdbProvider() { ) }, { - invokeUhdmovies( + if(!res.isAnime) invokeUhdmovies( res.title, res.year, res.season, @@ -457,7 +421,10 @@ open class SoraStream : TmdbProvider() { invokeFwatayako(res.imdbId, res.season, res.episode, subtitleCallback, callback) }, { - invokeGMovies(res.title, res.year, res.season, res.episode, subtitleCallback, callback) + if(!res.isAnime) invokeGMovies(res.title, res.year, res.season, res.episode, subtitleCallback, callback) + }, + { + if(!res.isAnime) invokeFDMovies(res.title, res.season, res.episode, subtitleCallback, callback) }, ) @@ -475,7 +442,7 @@ open class SoraStream : TmdbProvider() { val title: String? = null, val year: Int? = null, val orgTitle: String? = null, - val show: String? = null, + val isAnime: Boolean = false, val airedYear: Int? = null, val lastSeason: Int? = null, ) @@ -487,23 +454,6 @@ open class SoraStream : TmdbProvider() { val malId: Int? = null, ) - data class Subtitles( - @JsonProperty("url") val url: String? = null, - @JsonProperty("lang") val lang: String? = null, - @JsonProperty("language") val language: String? = null, - ) - - data class Sources( - @JsonProperty("url") val url: String? = null, - @JsonProperty("quality") val quality: String? = null, - @JsonProperty("isM3U8") val isM3U8: Boolean = true, - ) - - data class LoadLinks( - @JsonProperty("sources") val sources: ArrayList? = arrayListOf(), - @JsonProperty("subtitles") val subtitles: ArrayList? = arrayListOf(), - ) - data class Results( @JsonProperty("results") val results: ArrayList? = arrayListOf(), ) diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt index 796b3161..a7fe6228 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt @@ -5,10 +5,12 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.base64Decode import com.lagradost.cloudstream3.base64Encode import com.lagradost.cloudstream3.network.WebViewResolver +import com.lagradost.cloudstream3.utils.AppUtils import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.nicehttp.requestCreator +import okhttp3.FormBody import okhttp3.HttpUrl.Companion.toHttpUrl import java.net.URI @@ -62,10 +64,12 @@ suspend fun extractGdbot(url: String): String? { ) val token = res.document.selectFirst("input[name=_token]")?.attr("value") val cookiesSet = res.headers.filter { it.first == "set-cookie" } - val xsrf = cookiesSet.find { it.second.contains("XSRF-TOKEN") }?.second?.substringAfter("XSRF-TOKEN=") - ?.substringBefore(";") - val session = cookiesSet.find { it.second.contains("gdtot_proxy_session") }?.second?.substringAfter("gdtot_proxy_session=") - ?.substringBefore(";") + val xsrf = + cookiesSet.find { it.second.contains("XSRF-TOKEN") }?.second?.substringAfter("XSRF-TOKEN=") + ?.substringBefore(";") + val session = + cookiesSet.find { it.second.contains("gdtot_proxy_session") }?.second?.substringAfter("gdtot_proxy_session=") + ?.substringBefore(";") val cookies = mapOf( "gdtot_proxy_session" to "$session", @@ -81,6 +85,67 @@ suspend fun extractGdbot(url: String): String? { return requestFile.selectFirst("div.mt-8 a.float-right")?.attr("href") } +suspend fun extractDrivebot(url: String): String? { + val iframeGdbot = + app.get(url).document.selectFirst("li.flex.flex-col.py-6 a:contains(Drivebot)") + ?.attr("href") + val driveDoc = app.get(iframeGdbot ?: return null) + + val ssid = driveDoc.cookies["PHPSESSID"] + val script = driveDoc.document.selectFirst("script:containsData(var formData)")?.data() + + val baseUrl = getBaseUrl(iframeGdbot) + val token = script?.substringAfter("'token', '")?.substringBefore("');") + val link = + script?.substringAfter("fetch('")?.substringBefore("',").let { "$baseUrl$it" } + + val body = FormBody.Builder() + .addEncoded("token", "$token") + .build() + val cookies = mapOf("PHPSESSID" to "$ssid") + + val result = app.post( + link, + requestBody = body, + headers = mapOf( + "Accept" to "*/*", + "Origin" to baseUrl, + "Sec-Fetch-Site" to "same-origin" + ), + cookies = cookies, + referer = iframeGdbot + ).text + return AppUtils.tryParseJson(result)?.url +} + +suspend fun bypassFdAds(url: String): String? { + val res = app.get(url).document + val freeRedirect = res.selectFirst("a#link")?.attr("href") + val res2 = app.get(freeRedirect ?: return null , verify = false).document + val formLink = res2.select("form#landing").attr("action") + val value = res2.select("form#landing input").attr("value") + val res3 = app.post(formLink, data = mapOf("go" to value), verify = false).document + val formLink2 = res3.select("form#landing").attr("action") + val humanVer = res3.select("form#landing input[name=humanverification]").attr("value") + val newwp = res3.select("form#landing input[name=newwpsafelink]").attr("value") + val res4 = app.post( + formLink2, + data = mapOf("humanverification" to humanVer, "newwpsafelink" to newwp), + verify = false + ).document + val formLink3 = res4.select("form#wpsafelink-landing").attr("action") + val newwpsafelink = + res4.select("form#wpsafelink-landing input[name=newwpsafelink]").attr("value") + val res5 = app.post( + formLink3, + data = mapOf("newwpsafelink" to newwpsafelink), + verify = false + ).document + val finalLink = res5.selectFirst("div#wpsafe-link a")?.attr("onclick")?.substringAfter("open('") + ?.substringBefore("',") + return app.get(finalLink ?: return null, verify = false).url +} + suspend fun getFilmxyCookies(imdbId: String? = null, season: Int? = null): FilmxyCookies? { val url = if (season == null) {