diff --git a/SoraStream/build.gradle.kts b/SoraStream/build.gradle.kts index 2f6a9730..75fa861d 100644 --- a/SoraStream/build.gradle.kts +++ b/SoraStream/build.gradle.kts @@ -1,5 +1,5 @@ // use an integer for version numbers -version = 83 +version = 84 cloudstream { diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt index 37946ef5..4eb13bdb 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt @@ -22,46 +22,6 @@ val session = Session(Requests().baseClient) object SoraExtractor : SoraStream() { - /* - suspend fun invokeLocalSources( - url: String, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit - ) { - val doc = app.get( - url, - headers = mapOf("User-Agent" to RandomUserAgent.getRandomUserAgent()) - ).document - val script = doc.select("script").find { it.data().contains("\"sources\":[") }?.data() - val sourcesData = script?.substringAfter("\"sources\":[")?.substringBefore("],") - val subData = script?.substringAfter("\"subtitles\":[")?.substringBefore("],") - - tryParseJson>("[$sourcesData]")?.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) - ) - ) - } - - tryParseJson>("[$subData]")?.map { sub -> - subtitleCallback.invoke( - SubtitleFile( - sub.lang.toString(), - sub.url ?: return@map null - ) - ) - } - - } - */ - suspend fun invokeTwoEmbed( id: Int? = null, season: Int? = null, @@ -348,7 +308,7 @@ object SoraExtractor : SoraStream() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val fixTitle = title.fixTitle() + val fixTitle = title.createSlug() val request = app.get("$hdMovieBoxAPI/watch/$fixTitle") if (!request.isSuccessful) return val doc = request.document @@ -416,7 +376,7 @@ object SoraExtractor : SoraStream() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val fixTitle = title.fixTitle() + val fixTitle = title.createSlug() val url = if (season == null) { "$series9API/film/$fixTitle/watching.html" } else { @@ -461,7 +421,7 @@ object SoraExtractor : SoraStream() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val fixTitle = title.fixTitle() + val fixTitle = title.createSlug() val url = if (season == null) { "$idlixAPI/movie/$fixTitle-$year" } else { @@ -498,7 +458,7 @@ object SoraExtractor : SoraStream() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val fixTitle = title.fixTitle() + val fixTitle = title.createSlug() val url = if (season == null) { "$uniqueStreamAPI/movies/$fixTitle-$year" } else { @@ -558,7 +518,7 @@ object SoraExtractor : SoraStream() { episode: Int? = null, callback: (ExtractorLink) -> Unit ) { - val fixTitle = title.fixTitle() + val fixTitle = title.createSlug() val url = if (season == null) { "$noverseAPI/movie/$fixTitle/download/" } else { @@ -721,7 +681,7 @@ object SoraExtractor : SoraStream() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val fixTitle = title.fixTitle() + val fixTitle = title.createSlug() val doc = if (season == null || season == 1) { app.get("$kimcartoonAPI/Cartoon/$fixTitle").document } else { @@ -905,7 +865,7 @@ object SoraExtractor : SoraStream() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val fixTitle = title.fixTitle() + val fixTitle = title.createSlug() val doc = if (season == null) { val res = app.get("$xMovieAPI/movies/$fixTitle/watch") if (res.url == "$xMovieAPI/") app.get("$xMovieAPI/movies/$fixTitle-$year/watch").document else res.document @@ -1300,7 +1260,7 @@ object SoraExtractor : SoraStream() { callback: (ExtractorLink) -> Unit ) { val url = if (season == null) { - "$uhdmoviesAPI/download-${title.fixTitle()}-$year" + "$uhdmoviesAPI/download-${title.createSlug()}-$year" } else { val url = "$uhdmoviesAPI/?s=$title" var doc = app.get(url).document @@ -1440,7 +1400,7 @@ object SoraExtractor : SoraStream() { episode: Int? = null, callback: (ExtractorLink) -> Unit ) { - val fixTitle = title.fixTitle() + val fixTitle = title.createSlug() val url = if (season == null || season == 1) { "$gMoviesAPI/$fixTitle-$year" } else { @@ -1489,7 +1449,7 @@ object SoraExtractor : SoraStream() { episode: Int? = null, callback: (ExtractorLink) -> Unit ) { - val fixTitle = title.fixTitle() + val fixTitle = title.createSlug() val url = if (season == null) { "$fdMoviesAPI/movies/$fixTitle" } else { @@ -1548,7 +1508,7 @@ object SoraExtractor : SoraStream() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val res = app.get("$m4uhdAPI/search/${title.fixTitle()}.html").document + val res = app.get("$m4uhdAPI/search/${title.createSlug()}.html").document val scriptData = res.select("div.row div.item").map { Triple( it.selectFirst("img.imagecover")?.attr("title"), @@ -1634,7 +1594,7 @@ object SoraExtractor : SoraStream() { episode: Int? = null, callback: (ExtractorLink) -> Unit ) { - val fixTitle = title.fixTitle() + val fixTitle = title.createSlug() val url = if (season == null) { "$tvMoviesAPI/show/$fixTitle" } else { @@ -1667,7 +1627,7 @@ object SoraExtractor : SoraStream() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val id = searchCrunchyrollAnimeId(title ?: return) ?: searchKamyrollAnimeId(title) ?: return + val id = searchCrunchyrollAnimeId(title ?: return) ?: return val detail = app.get("$consumetCrunchyrollAPI/info?id=$id&mediaType=series").text val epsId = tryParseJson(detail)?.findCrunchyrollId( title, @@ -1715,7 +1675,7 @@ object SoraExtractor : SoraStream() { val json = app.get(url, referer = "$moviesbayAPI/") .parsedSafe()?.values - val media = json?.find { it.first() == "${title.fixTitle()}-$year" } + val media = json?.find { it.first() == "${title.createSlug()}-$year" } media?.filter { it.startsWith("https://drive.google.com") }?.apmap { val index = media.indexOf(it) @@ -1779,7 +1739,7 @@ object SoraExtractor : SoraStream() { episode: Int? = null, callback: (ExtractorLink) -> Unit ) { - val fixTitle = title?.fixTitle()?.replace("-", " ") + val fixTitle = title?.createSlug()?.replace("-", " ") val doc = app.get("$apiUrl/?s=$fixTitle").document val matchMedia = doc.select("article.mh-loop-item").map { @@ -1926,7 +1886,7 @@ object SoraExtractor : SoraStream() { } - suspend fun invokeMovie123( + suspend fun invokeMovie123Net( title: String? = null, season: Int? = null, episode: Int? = null, @@ -1934,11 +1894,11 @@ object SoraExtractor : SoraStream() { callback: (ExtractorLink) -> Unit, ) { val server = "https://vidcloud9.org" - val fixTitle = title.fixTitle() + val fixTitle = title.createSlug() val m = app.get("$movie123NetAPI/searching?q=$title&limit=40") .parsedSafe()?.data?.find { if (season == null) { - (it.t.equals(title, true) || it.t.fixTitle() + (it.t.equals(title, true) || it.t.createSlug() .equals(fixTitle)) && it.t?.contains("season", true) == false } else { it.t?.equals( @@ -2002,26 +1962,43 @@ object SoraExtractor : SoraStream() { } suspend fun invokeSmashyStream( - id: Int? = null, + imdbId: String? = null, season: Int? = null, episode: Int? = null, - subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit, ) { val url = if (season == null) { - "$smashyStreamAPI/playere.php?tmdb=$id" + "$smashyStreamAPI/gtop/tv.php?imdb=$imdbId" } else { - "$smashyStreamAPI/playere.php?tmdb=$id&season=$season&episode=$episode" - } - app.get(url).document.select("div.dropdown.servers a").map { - it.text() to it.attr("data-id") - }.apmap { (server, link) -> - when (val player = server.replace("Server", "Player").trim()) { - "Player 1" -> invokeSmashy1(player, link, subtitleCallback, callback) - "Player 2" -> invokeSmashy2(player, link, callback) - else -> return@apmap null - } + "$smashyStreamAPI/gtop/tv.php?imdb=$imdbId&s=$season&e=$episode" } + + val doc = app.get(url).document + val script = doc.selectFirst("script:containsData(var secret)")?.data() ?: return + val secret = + script.substringAfter("secret = \"").substringBefore("\";").let { base64Decode(it) } + val key = script.substringAfter("token = \"").substringBefore("\";") + val source = app.get( + "$secret$key", + headers = mapOf( + "X-Requested-With" to "XMLHttpRequest" + ) + ).parsedSafe() ?: return + + val videoUrl = base64Decode(source.file ?: return) + val quality = Regex("(\\d{3,4})[Pp]").find(videoUrl)?.groupValues?.getOrNull(1)?.toIntOrNull() + ?: Qualities.P720.value + callback.invoke( + ExtractorLink( + "SmashyStream", + "SmashyStream", + videoUrl, + "", + quality, + videoUrl.contains(".m3u8") + ) + ) + } //TODO only subs @@ -2128,6 +2105,46 @@ object SoraExtractor : SoraStream() { } + suspend fun invokeJsmovies( + apiUrl: String, + api: String, + title: String? = null, + year: Int? = null, + season: Int? = null, + episode: Int? = null, + callback: (ExtractorLink) -> Unit, + ) { + invokeChillmovies( + apiUrl, + api, + title, + year, + season, + episode, + callback, + ) + } + + suspend fun invokeBlackmovies( + apiUrl: String, + api: String, + title: String? = null, + year: Int? = null, + season: Int? = null, + episode: Int? = null, + callback: (ExtractorLink) -> Unit, + ) { + invokeChillmovies( + apiUrl, + api, + title, + year, + season, + episode, + callback, + ) + } + suspend fun invokeGammovies( apiUrl: String, api: String, @@ -2197,6 +2214,12 @@ object SoraExtractor : SoraStream() { episode: Int? = null, callback: (ExtractorLink) -> Unit, ) { + val encodedIndex = arrayOf( + "Gammovies", + "JSMovies", + "Blackmovies" + ) + val query = getIndexQuery(title, year, season, episode) val body = """{"q":"$query","password":null,"page_token":null,"page_index":0}""".toRequestBody( @@ -2207,7 +2230,7 @@ object SoraExtractor : SoraStream() { "page_token" to "", "page_index" to "0" ) - val search = if (api == "Gammovies") { + val search = if (api in encodedIndex) { decodeIndexJson(app.post("${apiUrl}search", data = data).text) } else { app.post("${apiUrl}search", requestBody = body).text @@ -2220,7 +2243,7 @@ object SoraExtractor : SoraStream() { val pathData = mapOf( "id" to file.id, ) - val path = (if (api == "Gammovies") { + val path = (if (api in encodedIndex) { app.post( "${apiUrl}id2path", data = pathData ) @@ -2512,27 +2535,6 @@ data class DataAni( @JsonProperty("data") val data: MediaAni? = null, ) -data class KamyrollSearch( - @JsonProperty("items") var items: ArrayList = arrayListOf() -) - -data class KamyrollItems( - @JsonProperty("type") var type: String? = null, - @JsonProperty("items") var items: ArrayList = arrayListOf() -) - -data class KamyrollAnimes( - @JsonProperty("id") var id: String? = null, - @JsonProperty("slug_title") var slugTitle: String? = null, - @JsonProperty("title") var title: String? = null, - @JsonProperty("media_type") var mediaType: String? = null -) - -data class KamyrollToken( - @JsonProperty("access_token") val access_token: String? = null, - @JsonProperty("token_type") val token_type: String? = null, -) - data class Smashy1Tracks( @JsonProperty("file") val file: String? = null, @JsonProperty("label") val label: String? = null, diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt index d8e2b7a0..6d48338a 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.hexated.SoraExtractor.invoke123Movie import com.hexated.SoraExtractor.invokeAnimes import com.hexated.SoraExtractor.invokeBaymovies +import com.hexated.SoraExtractor.invokeBlackmovies import com.hexated.SoraExtractor.invokeBollyMaza import com.hexated.SoraExtractor.invokeChillmovies0 import com.hexated.SoraExtractor.invokeChillmovies1 @@ -29,10 +30,11 @@ import com.hexated.SoraExtractor.invokeFlixon import com.hexated.SoraExtractor.invokeFwatayako import com.hexated.SoraExtractor.invokeGMovies import com.hexated.SoraExtractor.invokeGammovies +import com.hexated.SoraExtractor.invokeJsmovies import com.hexated.SoraExtractor.invokeKisskh import com.hexated.SoraExtractor.invokeLing import com.hexated.SoraExtractor.invokeM4uhd -import com.hexated.SoraExtractor.invokeMovie123 +import com.hexated.SoraExtractor.invokeMovie123Net import com.hexated.SoraExtractor.invokeMoviesbay import com.hexated.SoraExtractor.invokeMoviezAdd import com.hexated.SoraExtractor.invokeRStream @@ -65,7 +67,6 @@ open class SoraStream : TmdbProvider() { const val jikanAPI = "https://api.jikan.moe/v4" const val gdbot = "https://gdbot.xyz" const val consumetAnilistAPI = "https://api.consumet.org/meta/anilist" - const val kamyrollAPI = "https://api.kamyroll.tech" const val baymovies = "https://opengatewayindex.pages.dev" private val apiKey = @@ -112,11 +113,13 @@ open class SoraStream : TmdbProvider() { const val animeKaizokuAPI = "https://animekaizoku.com" const val movie123NetAPI = "https://ww7.0123movie.net" const val smashyStreamAPI = "https://embed.smashystream.com" + const val watchSomuchAPI = "https://watchsomuch.tv" // sub only const val baymoviesAPI = "https://thebayindexpublicgroupapi.zindex.eu.org" const val chillmovies0API = "https://chill.aicirou.workers.dev/0:" const val chillmovies1API = "https://chill.aicirou.workers.dev/1:" const val gamMoviesAPI = "https://drive.gamick.workers.dev/0:" - const val watchSomuchAPI = "https://watchsomuch.tv" // sub only + const val jsMoviesAPI = "https://jsupload.jnsbot.workers.dev/0:" + const val blackMoviesAPI = "https://dl.blacklistedbois.workers.dev/0:" fun getType(t: String?): TvType { return when (t) { @@ -549,10 +552,10 @@ open class SoraStream : TmdbProvider() { invokeFlixon(res.id, res.imdbId, res.season, res.episode, callback) }, { - invokeMovie123(res.title, res.season, res.episode, subtitleCallback, callback) + invokeMovie123Net(res.title, res.season, res.episode, subtitleCallback, callback) }, { - invokeSmashyStream(res.id, res.season, res.episode, subtitleCallback, callback) + invokeSmashyStream(res.imdbId, res.season, res.episode, callback) }, { if (!res.isAnime) invokeBaymovies( @@ -586,7 +589,7 @@ open class SoraStream : TmdbProvider() { ) }, { - invokeGammovies( + if (!res.isAnime) invokeGammovies( gamMoviesAPI, "Gammovies", res.title, @@ -604,6 +607,28 @@ open class SoraStream : TmdbProvider() { subtitleCallback ) }, + { + if (!res.isAnime) invokeBlackmovies( + blackMoviesAPI, + "Blackmovies", + res.title, + res.year, + res.season, + res.episode, + callback + ) + }, + { + if (!res.isAnime) invokeJsmovies( + jsMoviesAPI, + "JSMovies", + res.title, + res.year, + res.season, + res.episode, + callback + ) + }, ) return true diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt b/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt index a03a29ec..906aa683 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStreamLite.kt @@ -14,9 +14,11 @@ import com.hexated.SoraExtractor.invokeKimcartoon import com.hexated.SoraExtractor.invokeKisskh import com.hexated.SoraExtractor.invokeLing import com.hexated.SoraExtractor.invokeM4uhd +import com.hexated.SoraExtractor.invokeMovie123Net import com.hexated.SoraExtractor.invokeMovieHab import com.hexated.SoraExtractor.invokeRStream import com.hexated.SoraExtractor.invokeSeries9 +import com.hexated.SoraExtractor.invokeSmashyStream import com.hexated.SoraExtractor.invokeSoraStream import com.hexated.SoraExtractor.invokeTwoEmbed import com.hexated.SoraExtractor.invokeUniqueStream @@ -78,6 +80,15 @@ class SoraStreamLite : SoraStream() { callback ) }, + { + invokeMovie123Net( + res.title, + res.season, + res.episode, + subtitleCallback, + callback + ) + }, { invokeMovieHab(res.imdbId, res.season, res.episode, subtitleCallback, callback) }, @@ -141,6 +152,9 @@ class SoraStreamLite : SoraStream() { { invokeKimcartoon(res.title, res.season, res.episode, subtitleCallback, callback) }, + { + invokeSmashyStream(res.imdbId, res.season, res.episode, callback) + }, { invokeXmovies( res.title, diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt index 3e34b7e5..911767ea 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt @@ -5,7 +5,6 @@ import com.hexated.SoraStream.Companion.baymovies import com.hexated.SoraStream.Companion.consumetCrunchyrollAPI import com.hexated.SoraStream.Companion.filmxyAPI import com.hexated.SoraStream.Companion.gdbot -import com.hexated.SoraStream.Companion.kamyrollAPI import com.hexated.SoraStream.Companion.tvMoviesAPI import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getCaptchaToken @@ -285,68 +284,6 @@ suspend fun extractCovyn(url: String?): Pair? { return Pair(videoLink, size) } -suspend fun invokeSmashy1( - player: String, - url: String?, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit, -) { - val doc = app.get(url ?: return).document - val script = doc.selectFirst("script:containsData(secret)")?.data() ?: return - val secret = script.substringAfter("secret = \"").substringBefore("\";").let { base64Decode(it) } - val key = script.substringAfter("token = \"").substringBefore("\";") - val source = app.get( - "$secret$key", - headers = mapOf( - "Accept" to "application/json, text/javascript, */*; q=0.01", - "X-Requested-With" to "XMLHttpRequest" - ) - ).parsedSafe() ?: return - - val videoUrl = base64Decode(source.file ?: return) - if(videoUrl.contains(".m3u8")) { - M3u8Helper.generateM3u8( - "Smashy ($player)", - videoUrl, - "" - ).forEach(callback) - source.tracks?.map { sub -> - subtitleCallback.invoke( - SubtitleFile( - sub.label ?: return@map null, - sub.file ?: return@map null - ) - ) - } - } else { - return - } -} - -suspend fun invokeSmashy2( - player: String, - url: String?, - callback: (ExtractorLink) -> Unit, -) { - val base = getBaseUrl(url ?: return) - val doc = app.get(url).document - val script = doc.selectFirst("script:containsData(playlist:)")?.data() ?: return - Regex("""file:[\n\s]+?"(\S+?.m3u8)",[\n\s]+label:"(\S+)",""").findAll(script).map { - it.groupValues[1] to it.groupValues[2] - }.toList().map { (link, quality) -> - callback.invoke( - ExtractorLink( - "Smashy ($player)", - "Smashy ($player)", - link, - base, - quality.toIntOrNull() ?: Qualities.Unknown.value, - isM3u8 = link.contains(".m3u8"), - ) - ) - } -} - fun getDirectGdrive(url: String): String { return if (url.contains("&export=download")) { url @@ -538,25 +475,6 @@ fun Document.findTvMoviesIframe(): String? { ?.substringBefore("'>") } -suspend fun searchKamyrollAnimeId(title: String): String? { - return app.get( - "$kamyrollAPI/content/v1/search", - headers = getCrunchyrollToken(), - params = mapOf( - "query" to title, - "channel_id" to "crunchyroll", - "limit" to "10", - ) - ).parsedSafe()?.items?.find { item -> - item.items.any { - (it.title?.contains(title, true) == true || it.slugTitle?.contains( - "${title.fixTitle()}", - true - ) == true) && it.mediaType == "series" - } - }?.items?.firstOrNull()?.id -} - suspend fun searchCrunchyrollAnimeId(title: String): String? { val res = app.get("${consumetCrunchyrollAPI}/$title") .parsedSafe()?.results @@ -567,26 +485,12 @@ suspend fun searchCrunchyrollAnimeId(title: String): String? { (it.title?.contains( title, true - ) == true || it.title.fixTitle() - ?.contains("${title.fixTitle()}", true) == true) && it.type.equals("series") + ) == true || it.title.createSlug() + ?.contains("${title.createSlug()}", true) == true) && it.type.equals("series") } })?.id } -suspend fun getCrunchyrollToken(): Map { - val res = app.get( - "$kamyrollAPI/auth/v1/token", - params = mapOf( - "device_id" to "com.service.data", - "device_type" to "sorastream", - "access_token" to "HMbQeThWmZq4t7w", - ) - ).parsedSafe() - return mapOf( - "Authorization" to "${res?.token_type} ${res?.access_token}" - ) -} - fun CrunchyrollDetails.findCrunchyrollId( title: String?, season: Int?, @@ -621,7 +525,7 @@ fun getEpisodeSlug( } fun getTitleSlug(title: String? = null): Pair { - return title.fixTitle()?.replace("-", ".") to title.fixTitle()?.replace("-", " ") + return title.createSlug()?.replace("-", ".") to title.createSlug()?.replace("-", " ") } fun getIndexQuery( @@ -701,7 +605,7 @@ fun decodeIndexJson(json: String): String { val slug = json.reversed().substring(24) return base64Decode(slug.substring(0, slug.length - 20)) } -fun String?.fixTitle(): String? { +fun String?.createSlug(): String? { return this?.replace(Regex("[!%:'?,]|( &)"), "")?.replace(" ", "-")?.lowercase() ?.replace("-–-", "-") }