From 3be927b3c5a8c22031981e5ee2569ededdb54597 Mon Sep 17 00:00:00 2001 From: Samfun75 <38332931+Samfun75@users.noreply.github.com> Date: Fri, 19 Jan 2024 23:27:34 +0300 Subject: [PATCH] fix(en/fmovies): Update vrf & vidsrc extractor (#2789) --- src/en/fmovies/build.gradle | 2 +- .../animeextension/en/fmovies/FMovies.kt | 12 +- .../animeextension/en/fmovies/FMoviesDto.kt | 30 ++-- .../en/fmovies/FMoviesHelper.kt | 71 -------- .../animeextension/en/fmovies/FmoviesUtils.kt | 55 ++++++ .../en/fmovies/extractors/VidsrcExtractor.kt | 159 ++++++++++++------ 6 files changed, 186 insertions(+), 143 deletions(-) delete mode 100644 src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMoviesHelper.kt create mode 100644 src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FmoviesUtils.kt diff --git a/src/en/fmovies/build.gradle b/src/en/fmovies/build.gradle index d3b87e3cba..a7f64ae958 100644 --- a/src/en/fmovies/build.gradle +++ b/src/en/fmovies/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'FMovies' extClass = '.FMovies' - extVersionCode = 15 + extVersionCode = 16 } apply from: "$rootDir/common.gradle" diff --git a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMovies.kt b/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMovies.kt index 536e3fe7eb..12d311487a 100644 --- a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMovies.kt +++ b/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMovies.kt @@ -49,7 +49,7 @@ class FMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() { Injekt.get().getSharedPreferences("source_$id", 0x0000) } - private val vrfHelper by lazy { FMoviesHelper(client, headers) } + private val utils by lazy { FmoviesUtils() } // ============================== Popular =============================== @@ -126,8 +126,7 @@ class FMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() { val id = client.newCall(GET(baseUrl + anime.url)).execute().asJsoup() .selectFirst("div[data-id]")!!.attr("data-id") - val vrf = vrfHelper.getVrf(id) - + val vrf = utils.vrfEncrypt(id) val vrfHeaders = headers.newBuilder().apply { add("Accept", "application/json, text/javascript, */*; q=0.01") add("Host", baseUrl.toHttpUrl().host) @@ -189,7 +188,7 @@ class FMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun videoListRequest(episode: SEpisode): Request { val data = json.decodeFromString(episode.url) - val vrf = vrfHelper.getVrf(data.id) + val vrf = utils.vrfEncrypt(data.id) val vrfHeaders = headers.newBuilder() .add("Accept", "application/json, text/javascript, */*; q=0.01") @@ -217,7 +216,7 @@ class FMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() { if (!hosterSelection.contains(name)) return@parallelCatchingFlatMap emptyList() // Get decrypted url - val vrf = vrfHelper.getVrf(server.attr("data-link-id")) + val vrf = utils.vrfEncrypt(server.attr("data-link-id")) val vrfHeaders = headers.newBuilder() .add("Accept", "application/json, text/javascript, */*; q=0.01") @@ -229,8 +228,7 @@ class FMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() { GET("$baseUrl/ajax/server/${server.attr("data-link-id")}?vrf=$vrf", headers = vrfHeaders), ).await().parseAs().result.url - val decrypted = vrfHelper.decrypt(encrypted) - + val decrypted = utils.vrfDecrypt(encrypted) when (name) { "Vidplay", "MyCloud" -> vidsrcExtractor.videosFromUrl(decrypted, name) "Filemoon" -> filemoonExtractor.videosFromUrl(decrypted, headers = headers) diff --git a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMoviesDto.kt b/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMoviesDto.kt index 52c558c69d..f6f964a8ec 100644 --- a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMoviesDto.kt +++ b/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMoviesDto.kt @@ -2,12 +2,6 @@ package eu.kanade.tachiyomi.animeextension.en.fmovies import kotlinx.serialization.Serializable -@Serializable -data class VrfResponse( - val url: String, - val vrfQuery: String? = null, -) - @Serializable data class AjaxResponse( val result: String, @@ -36,21 +30,25 @@ data class FMoviesSubs( ) @Serializable -data class RawResponse( - val rawURL: String, -) - -@Serializable -data class VidsrcResponse( - val result: ResultObject, +data class MediaResponseBody( + val status: Int, + val result: Result, ) { @Serializable - data class ResultObject( - val sources: List, + data class Result( + val sources: ArrayList, + val tracks: ArrayList = ArrayList(), ) { @Serializable - data class SourceObject( + data class Source( + val file: String, + ) + + @Serializable + data class SubTrack( val file: String, + val label: String = "", + val kind: String, ) } } diff --git a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMoviesHelper.kt b/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMoviesHelper.kt deleted file mode 100644 index 0873a4541b..0000000000 --- a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMoviesHelper.kt +++ /dev/null @@ -1,71 +0,0 @@ -package eu.kanade.tachiyomi.animeextension.en.fmovies - -import eu.kanade.tachiyomi.AppInfo -import eu.kanade.tachiyomi.animeextension.BuildConfig -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.util.parseAs -import kotlinx.serialization.json.Json -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.OkHttpClient -import uy.kohesive.injekt.injectLazy -import java.net.URLEncoder - -class FMoviesHelper(private val client: OkHttpClient, private val headers: Headers) { - - private val json: Json by injectLazy() - - private val userAgent = Headers.headersOf( - "User-Agent", - "Aniyomi/${AppInfo.getVersionName()} (FMovies; ${BuildConfig.VERSION_CODE})", - ) - - fun getVrf(id: String): String { - val url = API_URL.newBuilder().apply { - addPathSegment("fmovies-vrf") - addQueryParameter("query", id) - addQueryParameter("apikey", API_KEY) - }.build().toString() - - return client.newCall(GET(url, userAgent)).execute().parseAs().let { - URLEncoder.encode(it.url, "utf-8") - } - } - - fun decrypt(encrypted: String): String { - val url = API_URL.newBuilder().apply { - addPathSegment("fmovies-decrypt") - addQueryParameter("query", encrypted) - addQueryParameter("apikey", API_KEY) - }.build().toString() - - return client.newCall(GET(url, userAgent)).execute().parseAs().url - } - - fun getVidSrc(query: String, host: String): String { - val url = API_URL.newBuilder().apply { - addPathSegment(if (host.contains("mcloud", true)) "rawMcloud" else "rawVizcloud") - addQueryParameter("apikey", API_KEY) - }.build().toString() - - val futoken = client.newCall( - GET("https://$host/futoken", headers), - ).execute().use { it.body.string() } - - val body = FormBody.Builder().apply { - add("query", query) - add("futoken", futoken) - }.build() - - return client.newCall( - POST(url, body = body, headers = userAgent), - ).execute().parseAs().rawURL - } - - companion object { - const val API_KEY = "aniyomi" - val API_URL = "https://9anime.eltik.net".toHttpUrl() - } -} diff --git a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FmoviesUtils.kt b/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FmoviesUtils.kt new file mode 100644 index 0000000000..97d5976040 --- /dev/null +++ b/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FmoviesUtils.kt @@ -0,0 +1,55 @@ +package eu.kanade.tachiyomi.animeextension.en.fmovies + +import android.util.Base64 +import java.net.URLDecoder +import javax.crypto.Cipher +import javax.crypto.spec.SecretKeySpec + +class FmoviesUtils { + + fun vrfEncrypt(input: String): String { + val rc4Key = SecretKeySpec("FWsfu0KQd9vxYGNB".toByteArray(), "RC4") + val cipher = Cipher.getInstance("RC4") + cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters) + + var vrf = cipher.doFinal(input.toByteArray()) + vrf = Base64.encode(vrf, Base64.URL_SAFE or Base64.NO_WRAP) + vrf = rot13(vrf) + vrf = Base64.encode(vrf, Base64.URL_SAFE or Base64.NO_WRAP) + vrf = vrfShift(vrf) + val stringVrf = vrf.toString(Charsets.UTF_8) + return java.net.URLEncoder.encode(stringVrf, "utf-8") + } + + fun vrfDecrypt(input: String): String { + var vrf = input.toByteArray() + vrf = Base64.decode(vrf, Base64.URL_SAFE) + + val rc4Key = SecretKeySpec("8z5Ag5wgagfsOuhz".toByteArray(), "RC4") + val cipher = Cipher.getInstance("RC4") + cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters) + vrf = cipher.doFinal(vrf) + + return URLDecoder.decode(vrf.toString(Charsets.UTF_8), "utf-8") + } + + private fun rot13(vrf: ByteArray): ByteArray { + for (i in vrf.indices) { + val byte = vrf[i] + if (byte in 'A'.code..'Z'.code) { + vrf[i] = ((byte - 'A'.code + 13) % 26 + 'A'.code).toByte() + } else if (byte in 'a'.code..'z'.code) { + vrf[i] = ((byte - 'a'.code + 13) % 26 + 'a'.code).toByte() + } + } + return vrf + } + + private fun vrfShift(vrf: ByteArray): ByteArray { + for (i in vrf.indices) { + val shift = arrayOf(-4, -2, -6, 5, -2)[i % 5] + vrf[i] = vrf[i].plus(shift).toByte() + } + return vrf + } +} diff --git a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/extractors/VidsrcExtractor.kt b/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/extractors/VidsrcExtractor.kt index 7f3821dc96..26e74f7742 100644 --- a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/extractors/VidsrcExtractor.kt +++ b/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/extractors/VidsrcExtractor.kt @@ -1,81 +1,144 @@ package eu.kanade.tachiyomi.animeextension.en.fmovies.extractors -import eu.kanade.tachiyomi.animeextension.en.fmovies.FMoviesHelper -import eu.kanade.tachiyomi.animeextension.en.fmovies.FMoviesSubs -import eu.kanade.tachiyomi.animeextension.en.fmovies.VidsrcResponse +import android.util.Base64 +import app.cash.quickjs.QuickJs +import eu.kanade.tachiyomi.animeextension.en.fmovies.MediaResponseBody import eu.kanade.tachiyomi.animesource.model.Track import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.util.parseAs -import kotlinx.serialization.json.Json +import okhttp3.CacheControl import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient -import uy.kohesive.injekt.injectLazy +import java.net.URLDecoder +import javax.crypto.Cipher +import javax.crypto.spec.SecretKeySpec class VidsrcExtractor(private val client: OkHttpClient, private val headers: Headers) { - private val json: Json by injectLazy() - private val vrfHelper by lazy { FMoviesHelper(client, headers) } private val playlistUtils by lazy { PlaylistUtils(client, headers) } - fun videosFromUrl(url: String, name: String): List