fix(en/seez): Fix Seez and move vidsrc to shared lib (#2800)
This commit is contained in:
parent
255bf2d6a6
commit
cd5ab35f9d
20
lib/vidsrc-extractor/build.gradle.kts
Normal file
20
lib/vidsrc-extractor/build.gradle.kts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.library")
|
||||||
|
kotlin("android")
|
||||||
|
id("kotlinx-serialization")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdk = AndroidConfig.compileSdk
|
||||||
|
namespace = "eu.kanade.tachiyomi.lib.vidsrcextractor"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = AndroidConfig.minSdk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly(libs.bundles.common)
|
||||||
|
implementation(project(":lib-playlist-utils"))
|
||||||
|
}
|
||||||
|
// BUMPS: 0
|
@ -1,13 +1,14 @@
|
|||||||
package eu.kanade.tachiyomi.animeextension.en.fmovies.extractors
|
package eu.kanade.tachiyomi.lib.vidsrcextractor
|
||||||
|
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import app.cash.quickjs.QuickJs
|
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.Track
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
|
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.util.parseAs
|
import eu.kanade.tachiyomi.util.parseAs
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import okhttp3.CacheControl
|
import okhttp3.CacheControl
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
@ -16,6 +17,7 @@ import java.net.URLDecoder
|
|||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
class VidsrcExtractor(private val client: OkHttpClient, private val headers: Headers) {
|
class VidsrcExtractor(private val client: OkHttpClient, private val headers: Headers) {
|
||||||
|
|
||||||
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
|
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
|
||||||
@ -25,13 +27,14 @@ class VidsrcExtractor(private val client: OkHttpClient, private val headers: Hea
|
|||||||
.cache(null)
|
.cache(null)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
|
||||||
private val keys by lazy {
|
private val keys by lazy {
|
||||||
noCacheClient.newCall(
|
noCacheClient.newCall(
|
||||||
GET("https://raw.githubusercontent.com/KillerDogeEmpire/vidplay-keys/keys/keys.json", cache = cacheControl),
|
GET("https://raw.githubusercontent.com/KillerDogeEmpire/vidplay-keys/keys/keys.json", cache = cacheControl),
|
||||||
).execute().parseAs<List<String>>()
|
).execute().parseAs<List<String>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun videosFromUrl(embedLink: String, hosterName: String): List<Video> {
|
fun videosFromUrl(embedLink: String, hosterName: String, type: String = "" ): List<Video> {
|
||||||
val host = embedLink.toHttpUrl().host
|
val host = embedLink.toHttpUrl().host
|
||||||
val apiUrl = getApiUrl(embedLink, keys)
|
val apiUrl = getApiUrl(embedLink, keys)
|
||||||
|
|
||||||
@ -61,7 +64,7 @@ class VidsrcExtractor(private val client: OkHttpClient, private val headers: Hea
|
|||||||
return playlistUtils.extractFromHls(
|
return playlistUtils.extractFromHls(
|
||||||
data.result.sources.first().file,
|
data.result.sources.first().file,
|
||||||
referer = "https://$host/",
|
referer = "https://$host/",
|
||||||
videoNameGen = { q -> "$hosterName - $q" },
|
videoNameGen = { q -> hosterName + (if(type.isBlank()) "" else " - $type") + " - $q" },
|
||||||
subtitleList = data.result.tracks.toTracks(),
|
subtitleList = data.result.tracks.toTracks(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -142,3 +145,27 @@ class VidsrcExtractor(private val client: OkHttpClient, private val headers: Hea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MediaResponseBody(
|
||||||
|
val status: Int,
|
||||||
|
val result: Result,
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Result(
|
||||||
|
val sources: ArrayList<Source>,
|
||||||
|
val tracks: ArrayList<SubTrack> = ArrayList(),
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Source(
|
||||||
|
val file: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SubTrack(
|
||||||
|
val file: String,
|
||||||
|
val label: String = "",
|
||||||
|
val kind: String,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ ext {
|
|||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(project(':lib-vidsrc-extractor'))
|
||||||
implementation(project(':lib-filemoon-extractor'))
|
implementation(project(':lib-filemoon-extractor'))
|
||||||
implementation(project(':lib-streamtape-extractor'))
|
implementation(project(':lib-streamtape-extractor'))
|
||||||
implementation(project(':lib-playlist-utils'))
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import android.content.SharedPreferences
|
|||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.MultiSelectListPreference
|
import androidx.preference.MultiSelectListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.animeextension.en.fmovies.extractors.VidsrcExtractor
|
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
@ -15,6 +14,7 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
|||||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||||
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
|
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
|
||||||
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
||||||
|
import eu.kanade.tachiyomi.lib.vidsrcextractor.VidsrcExtractor
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.await
|
import eu.kanade.tachiyomi.network.await
|
||||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||||
|
@ -7,8 +7,8 @@ ext {
|
|||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(project(':lib-vidsrc-extractor'))
|
||||||
implementation(project(':lib-filemoon-extractor'))
|
implementation(project(':lib-filemoon-extractor'))
|
||||||
implementation(project(':lib-mp4upload-extractor'))
|
implementation(project(':lib-mp4upload-extractor'))
|
||||||
implementation(project(':lib-streamtape-extractor'))
|
implementation(project(':lib-streamtape-extractor'))
|
||||||
implementation(project(':lib-playlist-utils'))
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import androidx.preference.ListPreference
|
|||||||
import androidx.preference.MultiSelectListPreference
|
import androidx.preference.MultiSelectListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import eu.kanade.tachiyomi.animeextension.en.nineanime.extractors.VidsrcExtractor
|
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
@ -17,19 +16,18 @@ import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
|||||||
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
|
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
|
||||||
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
|
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
|
||||||
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
||||||
|
import eu.kanade.tachiyomi.lib.vidsrcextractor.VidsrcExtractor
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import eu.kanade.tachiyomi.util.parallelFlatMapBlocking
|
import eu.kanade.tachiyomi.util.parallelFlatMapBlocking
|
||||||
import eu.kanade.tachiyomi.util.parallelMapBlocking
|
import eu.kanade.tachiyomi.util.parallelMapBlocking
|
||||||
import eu.kanade.tachiyomi.util.parseAs
|
import eu.kanade.tachiyomi.util.parseAs
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@ -47,8 +45,6 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
|
||||||
|
|
||||||
private val utils by lazy { AniwaveUtils() }
|
private val utils by lazy { AniwaveUtils() }
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
private val preferences: SharedPreferences by lazy {
|
||||||
@ -269,7 +265,13 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
val parsed = response.parseAs<ServerResponse>()
|
val parsed = response.parseAs<ServerResponse>()
|
||||||
val embedLink = utils.vrfDecrypt(parsed.result.url)
|
val embedLink = utils.vrfDecrypt(parsed.result.url)
|
||||||
when (server.serverName) {
|
when (server.serverName) {
|
||||||
"vidplay", "mycloud" -> vidsrcExtractor.videosFromUrl(embedLink, server.serverName, server.type)
|
"vidplay", "mycloud" -> {
|
||||||
|
val hosterName = when (server.serverName) {
|
||||||
|
"vidplay" -> "VidPlay"
|
||||||
|
else -> "MyCloud"
|
||||||
|
}
|
||||||
|
vidsrcExtractor.videosFromUrl(embedLink, hosterName, server.type)
|
||||||
|
}
|
||||||
"filemoon" -> filemoonExtractor.videosFromUrl(embedLink, "Filemoon - ${server.type} - ")
|
"filemoon" -> filemoonExtractor.videosFromUrl(embedLink, "Filemoon - ${server.type} - ")
|
||||||
"streamtape" -> streamtapeExtractor.videoFromUrl(embedLink, "StreamTape - ${server.type}")?.let(::listOf) ?: emptyList()
|
"streamtape" -> streamtapeExtractor.videoFromUrl(embedLink, "StreamTape - ${server.type}")?.let(::listOf) ?: emptyList()
|
||||||
"mp4upload" -> mp4uploadExtractor.videosFromUrl(embedLink, headers, suffix = " - ${server.type}")
|
"mp4upload" -> mp4uploadExtractor.videosFromUrl(embedLink, headers, suffix = " - ${server.type}")
|
||||||
|
@ -1,152 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animeextension.en.nineanime.extractors
|
|
||||||
|
|
||||||
import android.util.Base64
|
|
||||||
import app.cash.quickjs.QuickJs
|
|
||||||
import eu.kanade.tachiyomi.animeextension.en.nineanime.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 playlistUtils by lazy { PlaylistUtils(client, headers) }
|
|
||||||
|
|
||||||
private val cacheControl = CacheControl.Builder().noStore().build()
|
|
||||||
private val noCacheClient = client.newBuilder()
|
|
||||||
.cache(null)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
private val keys by lazy {
|
|
||||||
noCacheClient.newCall(
|
|
||||||
GET("https://raw.githubusercontent.com/KillerDogeEmpire/vidplay-keys/keys/keys.json", cache = cacheControl),
|
|
||||||
).execute().parseAs<List<String>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun videosFromUrl(embedLink: String, name: String, type: String): List<Video> {
|
|
||||||
val hosterName = when (name) {
|
|
||||||
"vidplay" -> "VidPlay"
|
|
||||||
else -> "MyCloud"
|
|
||||||
}
|
|
||||||
val host = embedLink.toHttpUrl().host
|
|
||||||
val apiUrl = getApiUrl(embedLink, keys)
|
|
||||||
|
|
||||||
val apiHeaders = headers.newBuilder().apply {
|
|
||||||
add("Accept", "application/json, text/javascript, */*; q=0.01")
|
|
||||||
add("Host", host)
|
|
||||||
add("Referer", URLDecoder.decode(embedLink, "UTF-8"))
|
|
||||||
add("X-Requested-With", "XMLHttpRequest")
|
|
||||||
}.build()
|
|
||||||
|
|
||||||
val response = client.newCall(
|
|
||||||
GET(apiUrl, apiHeaders),
|
|
||||||
).execute()
|
|
||||||
|
|
||||||
val data = runCatching {
|
|
||||||
response.parseAs<MediaResponseBody>()
|
|
||||||
}.getOrElse { // Keys are out of date
|
|
||||||
val newKeys = noCacheClient.newCall(
|
|
||||||
GET("https://raw.githubusercontent.com/KillerDogeEmpire/vidplay-keys/keys/keys.json", cache = cacheControl),
|
|
||||||
).execute().parseAs<List<String>>()
|
|
||||||
val newApiUrL = getApiUrl(embedLink, newKeys)
|
|
||||||
client.newCall(
|
|
||||||
GET(newApiUrL, apiHeaders),
|
|
||||||
).execute().parseAs()
|
|
||||||
}
|
|
||||||
|
|
||||||
return playlistUtils.extractFromHls(
|
|
||||||
data.result.sources.first().file,
|
|
||||||
referer = "https://$host/",
|
|
||||||
videoNameGen = { q -> "$hosterName - $type - $q" },
|
|
||||||
subtitleList = data.result.tracks.toTracks(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getApiUrl(embedLink: String, keyList: List<String>): String {
|
|
||||||
val host = embedLink.toHttpUrl().host
|
|
||||||
val params = embedLink.toHttpUrl().let { url ->
|
|
||||||
url.queryParameterNames.map {
|
|
||||||
Pair(it, url.queryParameter(it) ?: "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val vidId = embedLink.substringAfterLast("/").substringBefore("?")
|
|
||||||
val encodedID = encodeID(vidId, keyList)
|
|
||||||
val apiSlug = callFromFuToken(host, encodedID)
|
|
||||||
|
|
||||||
return buildString {
|
|
||||||
append("https://")
|
|
||||||
append(host)
|
|
||||||
append("/")
|
|
||||||
append(apiSlug)
|
|
||||||
if (params.isNotEmpty()) {
|
|
||||||
append("?")
|
|
||||||
append(
|
|
||||||
params.joinToString("&") {
|
|
||||||
"${it.first}=${it.second}"
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun encodeID(videoID: String, keyList: List<String>): String {
|
|
||||||
val rc4Key1 = SecretKeySpec(keyList[0].toByteArray(), "RC4")
|
|
||||||
val rc4Key2 = SecretKeySpec(keyList[1].toByteArray(), "RC4")
|
|
||||||
val cipher1 = Cipher.getInstance("RC4")
|
|
||||||
val cipher2 = Cipher.getInstance("RC4")
|
|
||||||
cipher1.init(Cipher.DECRYPT_MODE, rc4Key1, cipher1.parameters)
|
|
||||||
cipher2.init(Cipher.DECRYPT_MODE, rc4Key2, cipher2.parameters)
|
|
||||||
var encoded = videoID.toByteArray()
|
|
||||||
|
|
||||||
encoded = cipher1.doFinal(encoded)
|
|
||||||
encoded = cipher2.doFinal(encoded)
|
|
||||||
encoded = Base64.encode(encoded, Base64.DEFAULT)
|
|
||||||
return encoded.toString(Charsets.UTF_8).replace("/", "_").trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun callFromFuToken(host: String, data: String): String {
|
|
||||||
val fuTokenScript = client.newCall(
|
|
||||||
GET("https://$host/futoken"),
|
|
||||||
).execute().use { it.body.string() }
|
|
||||||
|
|
||||||
val js = buildString {
|
|
||||||
append("(function")
|
|
||||||
append(
|
|
||||||
fuTokenScript.substringAfter("window")
|
|
||||||
.substringAfter("function")
|
|
||||||
.replace("jQuery.ajax(", "")
|
|
||||||
.substringBefore("+location.search"),
|
|
||||||
)
|
|
||||||
append("}(\"$data\"))")
|
|
||||||
}
|
|
||||||
|
|
||||||
return QuickJs.create().use {
|
|
||||||
it.evaluate(js)?.toString()!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun List<MediaResponseBody.Result.SubTrack>.toTracks(): List<Track> {
|
|
||||||
return filter {
|
|
||||||
it.kind == "captions"
|
|
||||||
}.mapNotNull {
|
|
||||||
runCatching {
|
|
||||||
Track(
|
|
||||||
it.file,
|
|
||||||
it.label,
|
|
||||||
)
|
|
||||||
}.getOrNull()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,7 +7,6 @@ ext {
|
|||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(project(':lib-vidsrc-extractor'))
|
||||||
implementation(project(':lib-filemoon-extractor'))
|
implementation(project(':lib-filemoon-extractor'))
|
||||||
implementation(project(':lib-streamtape-extractor'))
|
|
||||||
implementation(project(':lib-playlist-utils'))
|
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@ package eu.kanade.tachiyomi.animeextension.en.seez
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.util.Base64
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.animeextension.en.seez.extractors.VidsrcExtractor
|
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.animesource.model.SEpisode
|
|||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||||
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
|
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
|
||||||
|
import eu.kanade.tachiyomi.lib.vidsrcextractor.VidsrcExtractor
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
|
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
|
||||||
@ -27,8 +28,11 @@ import okhttp3.Response
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.net.URLDecoder
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
class Seez : ConfigurableAnimeSource, AnimeHttpSource() {
|
class Seez : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||||
|
|
||||||
@ -48,8 +52,6 @@ class Seez : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val vrfHelper by lazy { VrfHelper(client, headers) }
|
|
||||||
|
|
||||||
private val apiKey by lazy {
|
private val apiKey by lazy {
|
||||||
val jsUrl = client.newCall(GET(baseUrl, headers)).execute().asJsoup()
|
val jsUrl = client.newCall(GET(baseUrl, headers)).execute().asJsoup()
|
||||||
.select("script[defer][src]")[1].attr("abs:src")
|
.select("script[defer][src]")[1].attr("abs:src")
|
||||||
@ -301,14 +303,13 @@ class Seez : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
GET("$embedUrl/ajax/embed/source/${it.id}", headers = sourcesHeaders),
|
GET("$embedUrl/ajax/embed/source/${it.id}", headers = sourcesHeaders),
|
||||||
).execute().parseAs<EmbedUrlResponse>().result.url
|
).execute().parseAs<EmbedUrlResponse>().result.url
|
||||||
|
|
||||||
Pair(vrfHelper.decrypt(encrypted), it.title)
|
Pair(decrypt(encrypted), it.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
return urlList.parallelCatchingFlatMapBlocking {
|
return urlList.parallelCatchingFlatMapBlocking {
|
||||||
val url = it.first
|
val url = it.first
|
||||||
val name = it.second
|
|
||||||
|
|
||||||
when (name) {
|
when (val name = it.second) {
|
||||||
"Vidplay" -> vidsrcExtractor.videosFromUrl(url, name)
|
"Vidplay" -> vidsrcExtractor.videosFromUrl(url, name)
|
||||||
"Filemoon" -> filemoonExtractor.videosFromUrl(url)
|
"Filemoon" -> filemoonExtractor.videosFromUrl(url)
|
||||||
else -> emptyList()
|
else -> emptyList()
|
||||||
@ -344,10 +345,22 @@ class Seez : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||||||
.getOrNull() ?: 0L
|
.getOrNull() ?: 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun decrypt(encrypted: String): String {
|
||||||
|
var vrf = encrypted.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")
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val TMDB_URL = "https://api.themoviedb.org/3".toHttpUrl()
|
private val TMDB_URL = "https://api.themoviedb.org/3".toHttpUrl()
|
||||||
private val IMG_URL = "https://image.tmdb.org/t/p/w300/"
|
private const val IMG_URL = "https://image.tmdb.org/t/p/w300/"
|
||||||
private val FALLBACK_IMG = "https://seez.su/fallback.png"
|
private const val FALLBACK_IMG = "https://seez.su/fallback.png"
|
||||||
|
|
||||||
private val DATE_FORMATTER by lazy {
|
private val DATE_FORMATTER by lazy {
|
||||||
SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
|
SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animeextension.en.seez
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.network.POST
|
|
||||||
import eu.kanade.tachiyomi.util.parseAs
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import okhttp3.FormBody
|
|
||||||
import okhttp3.Headers
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class VrfHelper(private val client: OkHttpClient, private val headers: Headers) {
|
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
|
||||||
|
|
||||||
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)).execute().parseAs<VrfResponse>().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),
|
|
||||||
).execute().parseAs<RawResponse>().rawURL
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val API_KEY = "aniyomi"
|
|
||||||
val API_URL = "https://9anime.eltik.net".toHttpUrl()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class VrfResponse(
|
|
||||||
val url: String,
|
|
||||||
val vrfQuery: String? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class RawResponse(
|
|
||||||
val rawURL: String,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,101 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animeextension.en.seez.extractors
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.animeextension.en.seez.VrfHelper
|
|
||||||
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.Serializable
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import okhttp3.Headers
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
// Stolen from fmovies
|
|
||||||
class VidsrcExtractor(private val client: OkHttpClient, private val headers: Headers) {
|
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
|
||||||
private val vrfHelper by lazy { VrfHelper(client, headers) }
|
|
||||||
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
|
|
||||||
|
|
||||||
fun videosFromUrl(url: String, name: String): List<Video> {
|
|
||||||
val httpUrl = url.toHttpUrl()
|
|
||||||
val host = httpUrl.host
|
|
||||||
val referer = "https://$host/"
|
|
||||||
|
|
||||||
val query = buildString {
|
|
||||||
append(httpUrl.pathSegments.last())
|
|
||||||
append("?")
|
|
||||||
append(
|
|
||||||
httpUrl.queryParameterNames.joinToString("&") {
|
|
||||||
"$it=${httpUrl.queryParameter(it)}"
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val rawUrl = vrfHelper.getVidSrc(query, host).addAutoStart()
|
|
||||||
|
|
||||||
val refererHeaders = headers.newBuilder().apply {
|
|
||||||
add("Accept", "application/json, text/javascript, */*; q=0.01")
|
|
||||||
add("Host", host)
|
|
||||||
add("Referer", url.addAutoStart())
|
|
||||||
add("X-Requested-With", "XMLHttpRequest")
|
|
||||||
}.build()
|
|
||||||
|
|
||||||
val infoJson = client.newCall(
|
|
||||||
GET(rawUrl, headers = refererHeaders),
|
|
||||||
).execute().parseAs<VidsrcResponse>()
|
|
||||||
|
|
||||||
val subtitleList = httpUrl.queryParameter("sub.info")?.let {
|
|
||||||
val subtitlesHeaders = headers.newBuilder().apply {
|
|
||||||
add("Accept", "application/json, text/javascript, */*; q=0.01")
|
|
||||||
add("Host", it.toHttpUrl().host)
|
|
||||||
add("Origin", "https://$host")
|
|
||||||
add("Referer", referer)
|
|
||||||
}.build()
|
|
||||||
|
|
||||||
client.newCall(
|
|
||||||
GET(it, headers = subtitlesHeaders),
|
|
||||||
).execute().parseAs<List<FMoviesSubs>>().map {
|
|
||||||
Track(it.file, it.label)
|
|
||||||
}
|
|
||||||
} ?: emptyList()
|
|
||||||
|
|
||||||
return infoJson.result.sources.distinctBy { it.file }.flatMap {
|
|
||||||
val url = it.file
|
|
||||||
.toHttpUrl()
|
|
||||||
.newBuilder()
|
|
||||||
.fragment(null)
|
|
||||||
.build()
|
|
||||||
.toString()
|
|
||||||
|
|
||||||
playlistUtils.extractFromHls(url, subtitleList = subtitleList, referer = referer, videoNameGen = { q -> "$name - $q" })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String.addAutoStart(): String {
|
|
||||||
return this.toHttpUrl().newBuilder().setQueryParameter("autostart", "true").build().toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class VidsrcResponse(
|
|
||||||
val result: ResultObject,
|
|
||||||
) {
|
|
||||||
@Serializable
|
|
||||||
data class ResultObject(
|
|
||||||
val sources: List<SourceObject>,
|
|
||||||
) {
|
|
||||||
@Serializable
|
|
||||||
data class SourceObject(
|
|
||||||
val file: String,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class FMoviesSubs(
|
|
||||||
val file: String,
|
|
||||||
val label: String,
|
|
||||||
)
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user