diff --git a/SoraStream/build.gradle.kts b/SoraStream/build.gradle.kts index 06f51e21..e407f624 100644 --- a/SoraStream/build.gradle.kts +++ b/SoraStream/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 41 +version = 42 cloudstream { diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt index c2c0514a..756d588a 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt @@ -9,6 +9,7 @@ import com.lagradost.nicehttp.Session import com.google.gson.JsonParser import com.lagradost.cloudstream3.network.CloudflareKiller import kotlinx.coroutines.delay +import okhttp3.FormBody import okhttp3.RequestBody.Companion.toRequestBody val session = Session(Requests().baseClient) @@ -1091,9 +1092,12 @@ object SoraExtractor : SoraStream() { val animeId = app.get("$consumetZoroAPI/$japTitle") .parsedSafe()?.results?.find { - val title = it.title ?: return - (title.equals(engTitle, true) || title.equals(japTitle, true)) && it.type == "TV" - }?.id ?: return + val title = it.title ?: return + (title.equals(engTitle, true) || title.equals( + japTitle, + true + )) && it.type == "TV" + }?.id ?: return val episodeId = app.get("$consumetZoroAPI/info?id=$animeId") .parsedSafe()?.episodes?.find { @@ -1334,8 +1338,86 @@ object SoraExtractor : SoraStream() { } } + suspend fun invokeGMovies( + title: String? = null, + year: Int? = null, + season: Int? = null, + episode: Int? = null, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val fixTitle = title.fixTitle() + val url = if (season == null) { + "$gMoviesAPI/$fixTitle-$year" + } else { + "$gMoviesAPI/$fixTitle-$year-season-$season" + } + + val doc = app.get(url).document + + val iframe = (if (season == null) { + doc.select("div.is-content-justification-center div.wp-block-button").map { + it.select("a").attr("href") to it.text() + } + } else { + doc.select("div.is-content-justification-center").find { + it.previousElementSibling()?.text()?.contains("episode $episode", true) == true + }?.select("div.wp-block-button")?.map { + it.select("a").attr("href") to it.text() + } + })?.filter { it.first.contains("gdtot") } ?: return + + iframe.apmap { (iframeLink, title) -> + 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 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 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) + ) + ) + } + } + } + + } +data class GdBotLink( + @JsonProperty("url") val url: String? = null, +) + data class HdMovieBoxSource( @JsonProperty("videoUrl") val videoUrl: String? = null, @JsonProperty("videoServer") val videoServer: String? = null, diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt index 35052ab5..c0aacd92 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt @@ -23,6 +23,7 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.metaproviders.TmdbProvider import com.hexated.SoraExtractor.invoZoro import com.hexated.SoraExtractor.invokeFwatayako +import com.hexated.SoraExtractor.invokeGMovies import com.hexated.SoraExtractor.invokeLing import com.hexated.SoraExtractor.invokeUhdmovies import com.lagradost.cloudstream3.utils.AppUtils.parseJson @@ -48,6 +49,7 @@ open class SoraStream : TmdbProvider() { private const val tmdbAPI = "https://api.themoviedb.org/3" private val apiKey = base64DecodeAPI("ZTM=NTg=MjM=MjM=ODc=MzI=OGQ=MmE=Nzk=Nzk=ZjI=NTA=NDY=NDA=MzA=YjA=") // PLEASE DON'T STEAL const val tmdb2mal = "https://tmdb2mal.slidemovies.org" + 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=") @@ -72,6 +74,7 @@ open class SoraStream : TmdbProvider() { const val lingAPI = "https://ling-online.net" const val uhdmoviesAPI = "https://uhdmovies.site" const val fwatayakoAPI = "https://5100.svetacdn.in" + const val gMoviesAPI = "https://gdrivemovies.xyz" fun getType(t: String?): TvType { return when (t) { @@ -451,6 +454,9 @@ open class SoraStream : TmdbProvider() { { invokeFwatayako(res.imdbId, res.season, res.episode, subtitleCallback, callback) }, + { + invokeGMovies(res.title, res.year, res.season, res.episode, subtitleCallback, callback) + }, ) return true diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt index 160185bd..420c7335 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt @@ -1,5 +1,7 @@ package com.hexated +import com.hexated.SoraStream.Companion.gdbot +import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.base64Decode import com.lagradost.cloudstream3.base64Encode import com.lagradost.cloudstream3.network.WebViewResolver @@ -51,6 +53,34 @@ fun String.filterMedia(title: String?, yearNum: Int?, seasonNum: Int?): Boolean } } +suspend fun extractGdbot(url: String): String? { + val headers = mapOf( + "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + ) + val res = app.get( + "$gdbot/", headers = headers + ) + 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 cookies = mapOf( + "gdtot_proxy_session" to "$session", + "XSRF-TOKEN" to "$xsrf" + ) + val requestFile = app.post( + "$gdbot/file", data = mapOf( + "link" to url, + "_token" to "$token" + ), headers = headers, referer = "$gdbot/", cookies = cookies + ).document + + return requestFile.selectFirst("div.mt-8 a.float-right")?.attr("href") +} + suspend fun getFilmxyCookies(imdbId: String? = null, season: Int? = null): FilmxyCookies? { val url = if (season == null) { @@ -122,6 +152,16 @@ fun getQuality(str: String): Int { } } +fun getGMoviesQuality(str: String): Int { + return when { + str.contains("480P", true) -> Qualities.P480.value + str.contains("720P", true) -> Qualities.P720.value + str.contains("1080", true) -> Qualities.P1080.value + str.contains("4K", true) -> Qualities.P2160.value + else -> Qualities.Unknown.value + } +} + fun getBaseUrl(url: String): String { return URI(url).let { "${it.scheme}://${it.host}"