diff --git a/SoraStream/build.gradle.kts b/SoraStream/build.gradle.kts index 787ec88c..175bd10b 100644 --- a/SoraStream/build.gradle.kts +++ b/SoraStream/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 118 +version = 119 cloudstream { diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt index c8255dea..ab9c2d3f 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt @@ -811,71 +811,62 @@ object SoraExtractor : SoraStream() { } - suspend fun invokeFlixhq( + suspend fun invokeFmovies( title: String? = null, year: Int? = null, season: Int? = null, episode: Int? = null, - lastSeason: Int? = null, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val fixTitle = title?.replace("–", "-") - val id = app.get("$haikeiFlixhqAPI/$title") - .parsedSafe()?.results?.find { - if (season == null) { - it.title?.equals( - "$fixTitle", true - ) == true && it.releaseDate?.equals("$year") == true && it.type == "Movie" - } else { - it.title?.equals( - "$fixTitle", - true - ) == true && it.type == "TV Series" && it.seasons == lastSeason - } - }?.id ?: return + val html = + app.get("https://fmovies.to/ajax/film/search?vrf=${encodeVrf("$title")}&keyword=$title") + .parsedSafe()?.html - val episodeId = - app.get("$haikeiFlixhqAPI/info?id=$id").parsedSafe()?.let { - if (season == null) { - it.episodes?.first()?.id - } else { - it.episodes?.find { ep -> ep.number == episode && ep.season == season }?.id - } - } ?: return + val mediaId = Jsoup.parse(html ?: return).select("a.item").map { + Triple( + it.attr("href"), + it.select("div.title").text(), + it.selectFirst("i.dot")?.nextSibling().toString().trim(), + ) + }.find { + if (season == null) { + it.first.contains("/movie/") + } else { + it.first.contains("/series/") + } && (it.second.equals(title, true) || it.second.createSlug() + .equals(title.createSlug())) && it.third.toInt() == year + }?.first ?: return - listOf( - "vidcloud", "upcloud" - ).apmap { server -> - val sources = app.get( - if (server == "upcloud") { - "$haikeiFlixhqAPI/watch?episodeId=$episodeId&mediaId=$id" - } else { - "$haikeiFlixhqAPI/watch?episodeId=$episodeId&mediaId=$id&server=$server" - }, - ).parsedSafe() - val name = fixTitle(server) - sources?.sources?.map { - callback.invoke( - ExtractorLink( - name, - name, - it.url ?: return@map null, - sources.headers?.referer ?: "", - it.quality?.toIntOrNull() ?: Qualities.Unknown.value, - it.isM3U8 ?: true - ) + val episodeId = if (season == null) { + "1-full" + } else { + "$season-$episode" + } + + val sources = app.get( + "$consumetFmoviesAPI/watch?mediaId=${mediaId.removePrefix("/")}&episodeId=$episodeId" + ).parsedSafe() + + sources?.sources?.map { + callback.invoke( + ExtractorLink( + "Vizcloud", + "Vizcloud", + it.url ?: return@map null, + sources.headers?.referer ?: "", + getQualityFromName(it.quality), + it.isM3U8 ?: true ) - } + ) + } - sources?.subtitles?.map { - subtitleCallback.invoke( - SubtitleFile( - it.lang ?: "", it.url ?: return@map null - ) + sources?.subtitles?.map { + subtitleCallback.invoke( + SubtitleFile( + it.lang ?: "", it.url ?: return@map null ) - } - + ) } @@ -2019,6 +2010,9 @@ object SoraExtractor : SoraStream() { it.first.contains("/gtop") -> { invokeSmashyTwo(it.second, it.first, callback) } + it.first.contains("/dude_tv") -> { + invokeSmashyThree(it.second, it.first, callback) + } else -> return@apmap } } @@ -3492,4 +3486,13 @@ data class CryMoviesStream( data class CryMoviesResponse( @JsonProperty("streams") val streams: List? = null, +) + +data class DudetvSources( + @JsonProperty("file") val file: String? = null, + @JsonProperty("title") val title: String? = null, +) + +data class FmoviesSearch( + @JsonProperty("html") val html: 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 70dff40f..ebc8d3bd 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt @@ -28,6 +28,7 @@ import com.hexated.SoraExtractor.invokeDahmerMovies import com.hexated.SoraExtractor.invokeEdithxmovies import com.hexated.SoraExtractor.invokeFDMovies import com.hexated.SoraExtractor.invokeFlixon +import com.hexated.SoraExtractor.invokeFmovies import com.hexated.SoraExtractor.invokeFwatayako import com.hexated.SoraExtractor.invokeGMovies import com.hexated.SoraExtractor.invokeGdbotMovies @@ -100,11 +101,11 @@ open class SoraStream : TmdbProvider() { const val filmxyAPI = "https://www.filmxy.vip" const val kimcartoonAPI = "https://kimcartoon.li" const val xMovieAPI = "https://xemovies.to" - const val haikeiFlixhqAPI = "https://api.haikei.xyz/movies/flixhq" // disabled due to rate limit + const val consumetFmoviesAPI = "https://api.consumet.org/movies/fmovies" const val consumetZoroAPI = "https://api.consumet.org/anime/zoro" const val consumetCrunchyrollAPI = "https://cronchy.consumet.stream" // dead const val allanimeAPI = "https://api.allanime.to" - const val kissKhAPI = "https://kisskh.me" + const val kissKhAPI = "https://kisskh.co" const val lingAPI = "https://ling-online.net" const val uhdmoviesAPI = "https://uhdmovies.vip" const val fwatayakoAPI = "https://5100.svetacdn.in" @@ -481,17 +482,9 @@ open class SoraStream : TmdbProvider() { callback ) }, -// { -// invokeFlixhq( -// res.title, -// res.year, -// res.season, -// res.episode, -// res.lastSeason, -// subtitleCallback, -// callback -// ) -// }, + { + invokeFmovies(res.title, res.airedYear ?: res.year, res.season, res.episode, subtitleCallback, callback) + }, { invokeKisskh(res.title, res.season, res.episode, subtitleCallback, callback) }, diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt index 93194bec..a96dfd69 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt @@ -7,6 +7,7 @@ import com.hexated.SoraExtractor.invokeCrunchyroll import com.hexated.SoraExtractor.invokeDbgo import com.hexated.SoraExtractor.invokeFilmxy import com.hexated.SoraExtractor.invokeFlixon +import com.hexated.SoraExtractor.invokeFmovies import com.hexated.SoraExtractor.invokeFwatayako import com.hexated.SoraExtractor.invokeGomovies import com.hexated.SoraExtractor.invokeHDMovieBox @@ -187,17 +188,16 @@ class SoraStreamLite : SoraStream() { callback ) }, -// { -// invokeFlixhq( -// res.title, -// res.year, -// res.season, -// res.episode, -// res.lastSeason, -// subtitleCallback, -// callback -// ) -// }, + { + invokeFmovies( + res.title, + res.airedYear ?: res.year, + res.season, + res.episode, + subtitleCallback, + callback + ) + }, { invokeKisskh(res.title, 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 5d04c2db..5923c615 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt @@ -460,6 +460,26 @@ suspend fun invokeSmashyTwo( ) } +suspend fun invokeSmashyThree( + name: String, + url: String, + callback: (ExtractorLink) -> Unit +) { + val script = + app.get(url).document.selectFirst("script:containsData(player =)")?.data() ?: return + + val source = Regex("file:\\s*(\\[.*]),").find(script)?.groupValues?.get(1) ?: return + + tryParseJson>(source)?.filter { it.title == "English" }?.map { + M3u8Helper.generateM3u8( + "Smashy [Player 2]", + it.file ?: return@map , + "" + ).forEach(callback) + } + +} + suspend fun getSoraIdAndType(title: String?, year: Int?, season: Int?) : Pair? { val doc = app.get("${base64DecodeAPI("b20=LmM=b2s=a2w=bG8=Ly8=czo=dHA=aHQ=")}/search?keyword=$title").document val scriptData = doc.select("div.search-list div.search-video-card").map { @@ -1113,6 +1133,64 @@ fun getDeviceId(length: Int = 16): String { .joinToString("") } +fun encodeVrf(query: String): String { + return encode( + encryptVrf( + cipherVrf("DZmuZuXqa9O0z3b7", encode(query)), + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + ) + ) +} + +fun encryptVrf(input: String, key: String): String { + if (input.any { it.code > 255 }) throw Exception("illegal characters!") + var output = "" + for (i in input.indices step 3) { + val a = intArrayOf(-1, -1, -1, -1) + a[0] = input[i].code shr 2 + a[1] = (3 and input[i].code) shl 4 + if (input.length > i + 1) { + a[1] = a[1] or (input[i + 1].code shr 4) + a[2] = (15 and input[i + 1].code) shl 2 + } + if (input.length > i + 2) { + a[2] = a[2] or (input[i + 2].code shr 6) + a[3] = 63 and input[i + 2].code + } + for (n in a) { + if (n == -1) output += "=" + else { + if (n in 0..63) output += key[n] + } + } + } + return output +} + +fun cipherVrf(key: String, text: String): String { + val arr = IntArray(256) { it } + + var u = 0 + var r: Int + arr.indices.forEach { + u = (u + arr[it] + key[it % key.length].code) % 256 + r = arr[it] + arr[it] = arr[u] + arr[u] = r + } + u = 0 + var c = 0 + + return text.indices.map { j -> + c = (c + 1) % 256 + u = (u + arr[c]) % 256 + r = arr[c] + arr[c] = arr[u] + arr[u] = r + (text[j].code xor arr[(arr[c] + arr[u]) % 256]).toChar() + }.joinToString("") +} + fun String.encodeUrl(): String { val url = URL(this) val uri = URI(url.protocol, url.userInfo, url.host, url.port, url.path, url.query, url.ref) @@ -1129,7 +1207,7 @@ fun String.decodeBase64(): String { return Base64.decode(this, Base64.DEFAULT).toString(Charsets.UTF_8) } -fun encode(input: String): String? = URLEncoder.encode(input, "utf-8") +fun encode(input: String): String = URLEncoder.encode(input, "utf-8").replace("+", "%20") fun decryptStreamUrl(data: String): String {