diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt index ee2a4729..d83955fb 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt @@ -12,6 +12,7 @@ import com.lagradost.cloudstream3.extractors.XStreamCdn import com.lagradost.cloudstream3.network.CloudflareKiller import com.lagradost.nicehttp.RequestBodyTypes import kotlinx.coroutines.delay +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody.Companion.toRequestBody @@ -792,25 +793,34 @@ object SoraExtractor : SoraStream() { } val script = if (scriptData.size == 1) { - scriptData.first() + scriptData.firstOrNull() } else { - scriptData.first { - if (season == null) { - it.first.equals( - title, - true - ) && it.second == year - } else { - it.first.contains( - "$title", - true - ) && (it.second == year || it.first.contains("Season $season", true)) + scriptData.find { + when (season) { + null -> { + it.first.equals( + title, + true + ) && it.second == year + } + 1 -> { + it.first.contains( + "$title", + true + ) && (it.second == year || it.first.contains("Season $season", true)) + } + else -> { + it.first.contains( + "$title", + true + ) && it.second == year && it.first.contains("Season $season", true) + } } } } - val id = script.third?.last() - val type = script.third?.get(2) + val id = script?.third?.last() ?: return + val type = script.third?.get(2) ?: return val jsonResponse = app.get( "$vipAPI/movieDrama/get?id=${id}&category=${type}", @@ -1491,6 +1501,81 @@ object SoraExtractor : SoraStream() { } + suspend fun invokeTvMovies( + title: String? = null, + season: Int? = null, + episode: Int? = null, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val fixTitle = title.fixTitle() + val url = if (season == null) { + "$tvMoviesAPI/show/$fixTitle" + } else { + "$tvMoviesAPI/show/index-of-$fixTitle" + } + + val server = getTvMoviesServer(url, season, episode) ?: return + + val request = session.get(server.second ?: return, referer = "$tvMoviesAPI/") + var filehosting = session.baseClient.cookieJar.loadForRequest(url.toHttpUrl()) + .find { it.name == "filehosting" }?.value + val iframe = request.document.findTvMoviesIframe() + delay(10000) + val request2 = session.get( + iframe ?: return, referer = url, headers = mapOf( + "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "Connection" to "keep-alive", + "Cookie" to "filehosting=$filehosting", + ) + ) + + filehosting = session.baseClient.cookieJar.loadForRequest(url.toHttpUrl()) + .find { it.name == "filehosting" }?.value + val iframe2 = request2.document.findTvMoviesIframe() + delay(11000) + val request3 = session.get( + iframe2 ?: return, referer = iframe, headers = mapOf( + "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "Connection" to "keep-alive", + "Cookie" to "filehosting=$filehosting", + ) + ) + + filehosting = session.baseClient.cookieJar.loadForRequest(url.toHttpUrl()) + .find { it.name == "filehosting" }?.value + val response = request3.document + val videoLink = + response.selectFirst("button.btn.btn--primary")?.attr("onclick") + ?.substringAfter("location = '")?.substringBefore("';")?.let { + app.get( + it, referer = iframe2, headers = mapOf( + "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "Connection" to "keep-alive", + "Cookie" to "filehosting=$filehosting", + ) + ).url + } + + val quality = + Regex("([0-9]{3,4})p").find(server.first)?.groupValues?.getOrNull(1)?.toIntOrNull() + val size = + response.selectFirst("ul.row--list li:contains(Filesize) span:last-child") + ?.text() + + callback.invoke( + ExtractorLink( + "TVMovies [$size]", + "TVMovies [$size]", + videoLink ?: return, + "", + quality ?: Qualities.Unknown.value + ) + ) + + + } + } class StreamM4u: XStreamCdn() { diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt index 55506d1b..a3607475 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt @@ -26,6 +26,7 @@ import com.hexated.SoraExtractor.invokeFwatayako import com.hexated.SoraExtractor.invokeGMovies import com.hexated.SoraExtractor.invokeLing import com.hexated.SoraExtractor.invokeM4uhd +import com.hexated.SoraExtractor.invokeTvMovies import com.hexated.SoraExtractor.invokeUhdmovies import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson @@ -78,6 +79,7 @@ open class SoraStream : TmdbProvider() { const val gMoviesAPI = "https://gdrivemovies.xyz" const val fdMoviesAPI = "https://freedrivemovie.com" const val m4uhdAPI = "https://m4uhd.tv" + const val tvMoviesAPI = "https://www.tvseriesnmovies.com" fun getType(t: String?): TvType { return when (t) { @@ -430,7 +432,10 @@ open class SoraStream : TmdbProvider() { // }, { invokeM4uhd(res.title, res.year, res.season, res.episode, subtitleCallback, callback) - } + }, + { + invokeTvMovies(res.title, 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 fe80bcf3..01226e95 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt @@ -17,6 +17,7 @@ import okhttp3.FormBody import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody.Companion.toRequestBody +import org.jsoup.nodes.Document import java.net.URI data class FilmxyCookies( @@ -222,6 +223,36 @@ suspend fun bypassFdAds(url: String): String? { return app.get(finalLink ?: return null, verify = false).url } +suspend fun getTvMoviesServer(url: String, season: Int?, episode: Int?): Pair? { + + val req = app.get(url) + if(!req.isSuccessful) return null + val doc = req.document + + return if (season == null) { + doc.select("table.wp-block-table tr:last-child td:first-child").text() to + doc.selectFirst("table.wp-block-table tr a")?.attr("href").let { link -> + app.get(link ?: return null).document.select("div#text-url a") + .mapIndexed { index, element -> + element.attr("href") to element.parent()?.textNodes()?.getOrNull(index) + ?.text() + }.filter { it.second?.contains("Subtitles", true) == false } + .map { it.first } + }.lastOrNull() + } else { + doc.select("div.vc_tta-panels div#Season-$season table.wp-block-table tr:last-child td:first-child") + .text() to + doc.select("div.vc_tta-panels div#Season-$season table.wp-block-table tr a") + .mapNotNull { ele -> + app.get(ele.attr("href")).document.select("div#text-url a") + .mapIndexed { index, element -> + element.attr("href") to element.parent()?.textNodes() + ?.getOrNull(index)?.text() + }.find { it.second?.contains("Episode $episode", true) == true }?.first + }.lastOrNull() + } +} + suspend fun getFilmxyCookies(imdbId: String? = null, season: Int? = null): FilmxyCookies? { val url = if (season == null) { @@ -269,6 +300,11 @@ suspend fun getFilmxyCookies(imdbId: String? = null, season: Int? = null): Filmx return FilmxyCookies(phpsessid, wLog, wSec) } +fun Document.findTvMoviesIframe(): String? { + return this.selectFirst("script:containsData(var seconds)")?.data()?.substringAfter("href='") + ?.substringBefore("'>") +} + fun String?.fixTitle(): String? { return this?.replace(Regex("[!%:]|( &)"), "")?.replace(" ", "-")?.lowercase() ?.replace("-–-", "-")