From db046fe123dbdae6b425ae1bf15bdf393cde1e2e Mon Sep 17 00:00:00 2001 From: hexated Date: Sat, 29 Apr 2023 00:25:17 +0700 Subject: [PATCH] sora: fixed sources --- SoraStream/build.gradle.kts | 2 +- .../main/kotlin/com/hexated/SoraExtractor.kt | 66 +++++++++++------ .../src/main/kotlin/com/hexated/SoraStream.kt | 23 +++--- .../src/main/kotlin/com/hexated/SoraUtils.kt | 70 +++++++++++++++++-- 4 files changed, 122 insertions(+), 39 deletions(-) diff --git a/SoraStream/build.gradle.kts b/SoraStream/build.gradle.kts index 436f34b6..2619a62f 100644 --- a/SoraStream/build.gradle.kts +++ b/SoraStream/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.konan.properties.Properties // use an integer for version numbers -version = 123 +version = 124 android { defaultConfig { diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt index b63cf1f3..f212342e 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraExtractor.kt @@ -732,8 +732,8 @@ object SoraExtractor : SoraStream() { ) { val query = title?.replace(Regex("[^\\w-\\s]"), "") val html = - app.get("https://fmovies.to/ajax/film/search?vrf=${encodeVrf("$query")}&keyword=$query") - .parsedSafe()?.html + app.get("$fmoviesAPI/ajax/film/search?vrf=${encodeVrf("$query")}&keyword=$query") + .parsedSafe()?.html val mediaId = Jsoup.parse(html ?: return).select("a.item").map { Triple( @@ -748,7 +748,7 @@ object SoraExtractor : SoraStream() { it.first.contains("/series/") } && (it.second.equals(title, true) || it.second.createSlug() .equals(title.createSlug())) && it.third.toInt() == year - }?.first ?: return + }?.first?.substringAfterLast("-") ?: return val episodeId = if (season == null) { "1-full" @@ -756,32 +756,32 @@ object SoraExtractor : SoraStream() { "$season-$episode" } - val sources = app.get( - "$consumetFmoviesAPI/watch?mediaId=${mediaId.removePrefix("/")}&episodeId=$episodeId" - ).parsedSafe() + val serversKname = + app.get("$fmoviesAPI/ajax/film/servers?id=$mediaId&vrf=${encodeVrf(mediaId)}") + .parsedSafe()?.html?.let { Jsoup.parse(it) } + ?.selectFirst("a[data-kname=$episodeId]")?.attr("data-ep") - sources?.sources?.map { - callback.invoke( - ExtractorLink( - "Vizcloud", - "Vizcloud", - it.url ?: return@map null, - sources.headers?.referer ?: "", - getQualityFromName(it.quality), - it.isM3U8 ?: true - ) - ) + val servers = tryParseJson>(serversKname) + + servers?.apmap { server -> + val decryptServer = app.get("$fmoviesAPI/ajax/episode/info?id=${server.value}") + .parsedSafe()?.url?.let { decodeVrf(it) } ?: return@apmap + if (server.key == "41") { + invokeVizcloud(decryptServer, callback) + } else { + loadExtractor(decryptServer, fmoviesAPI, subtitleCallback, callback) + } } - sources?.subtitles?.map { + val sub = app.get("$fmoviesAPI/ajax/episode/subtitles/${servers?.get("28") ?: return}").text + tryParseJson>(sub)?.map { subtitleCallback.invoke( SubtitleFile( - it.lang ?: "", it.url ?: return@map null + it.label ?: "", + it.file ?: return@map ) ) } - - } suspend fun invokeKisskh( @@ -3403,6 +3403,28 @@ data class DudetvSources( @JsonProperty("title") val title: String? = null, ) -data class FmoviesSearch( +data class FmoviesResponses( @JsonProperty("html") val html: String? = null, + @JsonProperty("url") val url: String? = null, +) + +data class FmoviesSubtitles( + @JsonProperty("label") val label: String? = null, + @JsonProperty("file") val file: String? = null, +) + +data class VizcloudSources( + @JsonProperty("file") val file: String? = null, +) + +data class VizcloudMedia( + @JsonProperty("sources") val sources: ArrayList? = arrayListOf(), +) + +data class VizcloudData( + @JsonProperty("media") val media: VizcloudMedia? = null, +) + +data class VizcloudResponses( + @JsonProperty("data") val data: VizcloudData? = 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 ca8c9908..df95c49e 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraStream.kt @@ -100,9 +100,7 @@ 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 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.co" const val lingAPI = "https://ling-online.net" @@ -121,21 +119,15 @@ open class SoraStream : TmdbProvider() { const val movie123NetAPI = "https://ww8.0123movie.net" const val smashyStreamAPI = "https://embed.smashystream.com" const val watchSomuchAPI = "https://watchsomuch.tv" // sub only - val gomoviesAPI = - base64DecodeAPI("bQ==Y28=ZS4=aW4=bmw=LW8=ZXM=dmk=bW8=Z28=Ly8=czo=dHA=aHQ=") + val gomoviesAPI = base64DecodeAPI("bQ==Y28=ZS4=aW4=bmw=LW8=ZXM=dmk=bW8=Z28=Ly8=czo=dHA=aHQ=") const val ask4MoviesAPI = "https://ask4movie.mx" const val biliBiliAPI = "https://api-vn.kaguya.app/server" const val watchOnlineAPI = "https://watchonline.ag" const val nineTvAPI = "https://api.9animetv.live" const val putlockerAPI = "https://ww7.putlocker.vip" + const val fmoviesAPI = "https://fmovies.to" // INDEX SITE - const val baymoviesAPI = "https://opengatewayindex.pages.dev" // dead - const val chillmovies0API = "https://chill.aicirou.workers.dev/0:" // dead - const val chillmovies1API = "https://chill.aicirou.workers.dev/1:" // dead - const val gamMoviesAPI = "https://drive.gamick.workers.dev/0:" // dead - const val jsMoviesAPI = "https://jsupload.jnsbot.workers.dev/0:" // dead - const val xtremeMoviesAPI = "https://kartik19.xtrememirror0.workers.dev/0:" // dead const val blackMoviesAPI = "https://dl.blacklistedbois.workers.dev/0:" const val rinzryMoviesAPI = "https://rinzry.stream/0:" const val codexMoviesAPI = "https://packs.codexcloudx.tech/0:" @@ -143,7 +135,6 @@ open class SoraStream : TmdbProvider() { const val papaonMovies1API = "https://m.papaonwork.workers.dev/0:" const val papaonMovies2API = "https://m.papaonwork.workers.dev/1:" const val dahmerMoviesAPI = "https://edytjedhgmdhm.abfhaqrhbnf.workers.dev" - const val tgarMovieAPI = "https://tgarchive.eu.org" // dead const val jmdkhMovieAPI = "https://tg.jmdkh.eu.org/0:" const val rubyMovieAPI = "https://upload.rubyshare111.workers.dev/0:" const val shinobiMovieAPI = "https://home.shinobicloud.cf/0:" @@ -151,6 +142,16 @@ open class SoraStream : TmdbProvider() { const val shivamhwAPI = "https://foogle.shivamhw.me" val cryMoviesAPI = base64DecodeAPI("ZXY=LmQ=cnM=a2U=b3I=Lnc=ZXI=ZGQ=bGE=cy0=b2I=YWM=Lmo=YWw=aW4=LWY=cm4=Ym8=cmU=Ly8=czo=dHA=aHQ=") + // DEAD SITE + const val consumetCrunchyrollAPI = "https://cronchy.consumet.stream" // dead + const val chillmovies0API = "https://chill.aicirou.workers.dev/0:" // dead + const val chillmovies1API = "https://chill.aicirou.workers.dev/1:" // dead + const val gamMoviesAPI = "https://drive.gamick.workers.dev/0:" // dead + const val jsMoviesAPI = "https://jsupload.jnsbot.workers.dev/0:" // dead + const val xtremeMoviesAPI = "https://kartik19.xtrememirror0.workers.dev/0:" // dead + const val tgarMovieAPI = "https://tgarchive.eu.org" // dead + const val baymoviesAPI = "https://opengatewayindex.pages.dev" // dead + fun getType(t: String?): TvType { return when (t) { "movie" -> TvType.Movie diff --git a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt index 4439f995..2d3c0952 100644 --- a/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt +++ b/SoraStream/src/main/kotlin/com/hexated/SoraUtils.kt @@ -29,6 +29,7 @@ import okhttp3.RequestBody.Companion.toRequestBody import org.jsoup.nodes.Document import java.net.URI import java.net.URL +import java.net.URLDecoder import java.net.URLEncoder import java.nio.charset.StandardCharsets import java.security.MessageDigest @@ -40,8 +41,9 @@ import javax.crypto.spec.SecretKeySpec import kotlin.collections.ArrayList import kotlin.math.min -val soraAPI = - base64DecodeAPI("cA==YXA=cy8=Y20=di8=LnQ=b2s=a2w=bG8=aS4=YXA=ZS0=aWw=b2I=LW0=Z2E=Ly8=czo=dHA=aHQ=") +val soraAPI = base64DecodeAPI("cA==YXA=cy8=Y20=di8=LnQ=b2s=a2w=bG8=aS4=YXA=ZS0=aWw=b2I=LW0=Z2E=Ly8=czo=dHA=aHQ=") +val bflixChipperKey = base64DecodeAPI("Yjc=ejM=TzA=YTk=WHE=WnU=bXU=RFo=") +val bflixKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" val soraHeaders = mapOf( "lang" to "en", @@ -397,6 +399,21 @@ suspend fun getDirectGdrive(url: String): String { } +suspend fun invokeVizcloud( + url: String, + callback: (ExtractorLink) -> Unit, +) { + val id = Regex("(?:embed-|/e/)([^?]*)").find(url)?.groupValues?.getOrNull(1) + app.get("https://api.consumet.org/anime/9anime/helper?query=${id ?: return}&action=vizcloud") + .parsedSafe()?.data?.media?.sources?.map { + M3u8Helper.generateM3u8( + "Vizcloud", + it.file ?: return@map, + "${getBaseUrl(url)}/" + ).forEach(callback) + } +} + suspend fun invokeSmashyOne( name: String, url: String, @@ -1154,13 +1171,18 @@ fun getDeviceId(length: Int = 16): String { fun encodeVrf(query: String): String { return encode( encryptVrf( - cipherVrf("DZmuZuXqa9O0z3b7", encode(query)), - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + cipherVrf(bflixChipperKey, encode(query)), + bflixKey ) ) } -fun encryptVrf(input: String, key: String): String { +fun decodeVrf(text: String): String { + return decode(cipherVrf(bflixChipperKey, decryptVrf(text, bflixKey))) +} + +@Suppress("SameParameterValue") +private 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) { @@ -1185,6 +1207,42 @@ fun encryptVrf(input: String, key: String): String { return output } +@Suppress("SameParameterValue") +private fun decryptVrf(input: String, key: String): String { + val t = if (input.replace("""[\t\n\f\r]""".toRegex(), "").length % 4 == 0) { + input.replace("""==?$""".toRegex(), "") + } else input + if (t.length % 4 == 1 || t.contains("""[^+/0-9A-Za-z]""".toRegex())) throw Exception("bad input") + var i: Int + var r = "" + var e = 0 + var u = 0 + for (o in t.indices) { + e = e shl 6 + i = key.indexOf(t[o]) + e = e or i + u += 6 + if (24 == u) { + r += ((16711680 and e) shr 16).toChar() + r += ((65280 and e) shr 8).toChar() + r += (255 and e).toChar() + e = 0 + u = 0 + } + } + return if (12 == u) { + e = e shr 4 + r + e.toChar() + } else { + if (18 == u) { + e = e shr 2 + r += ((65280 and e) shr 8).toChar() + r += (255 and e).toChar() + } + r + } +} + fun cipherVrf(key: String, text: String): String { val arr = IntArray(256) { it } @@ -1225,6 +1283,8 @@ fun String.decodeBase64(): String { return Base64.decode(this, Base64.DEFAULT).toString(Charsets.UTF_8) } +fun decode(input: String): String = URLDecoder.decode(input, "utf-8") + fun encode(input: String): String = URLEncoder.encode(input, "utf-8").replace("+", "%20") fun decryptStreamUrl(data: String): String {