fix(pt/vizer): Fixed pt/Vizer videos empty (#110)

This commit is contained in:
Dark25 2024-10-18 22:02:57 +02:00 committed by GitHub
parent 25e922a05c
commit 288a0c4947
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 177 additions and 72 deletions

View file

@ -0,0 +1,10 @@
plugins {
id("lib-android")
}
dependencies {
implementation("dev.datlag.jsunpacker:jsunpacker:1.0.1") {
exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk8")
}
implementation(project(":lib:playlist-utils"))
}

View file

@ -0,0 +1,63 @@
package eu.kanade.tachiyomi.lib.fireplayerextractor
import dev.datlag.jsunpacker.JsUnpacker
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
class FireplayerExtractor(
private val client: OkHttpClient,
private val defaultHost: String? = null,
) {
fun videosFromUrl(
url: String,
videoNameGen: (String) -> String = { quality -> quality },
videoHost: String? = null,
): List<Video> {
val host = videoHost ?: defaultHost ?: "https://${url.toHttpUrl().host}"
val headers = Headers.Builder()
.set("X-Requested-With", "XMLHttpRequest")
.set("Referer", host)
.set("Origin", "https://${host.toHttpUrl().host}")
.set("X-Requested-With", "XMLHttpRequest")
.build()
var id = url.substringAfterLast("/")
if (id.length < 32) {
val doc = client.newCall(GET(url, headers)).execute().asJsoup()
val script =
doc.selectFirst("script:containsData(eval):containsData(p,a,c,k,e,d)")?.data()
?.let(JsUnpacker::unpackAndCombine)
?: doc.selectFirst("script:containsData(FirePlayer)")?.data()
if (script?.contains("FirePlayer(") == true) {
id = script.substringAfter("FirePlayer(\"").substringBefore('"')
}
}
val postUrl = "$host/player/index.php?data=$id&do=getVideo"
val body = FormBody.Builder()
.add("hash", id)
.add("r", "")
.build()
val masterUrl = client.newCall(POST(postUrl, headers, body = body)).execute()
.body.string()
.substringAfter("securedLink\":\"")
.substringBefore('"')
.replace("\\", "")
val playlistUtils = PlaylistUtils(client, headers)
return playlistUtils.extractFromHls(masterUrl, videoNameGen = videoNameGen)
}
}

View file

@ -17,7 +17,12 @@ class MixDropExtractor(private val client: OkHttpClient) {
externalSubs: List<Track> = emptyList(), externalSubs: List<Track> = emptyList(),
referer: String = DEFAULT_REFERER, referer: String = DEFAULT_REFERER,
): List<Video> { ): List<Video> {
val headers = Headers.headersOf("Referer", referer) val headers = Headers.headersOf(
"Referer",
referer,
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
)
val doc = client.newCall(GET(url, headers)).execute().asJsoup() val doc = client.newCall(GET(url, headers)).execute().asJsoup()
val unpacked = doc.selectFirst("script:containsData(eval):containsData(MDCore)") val unpacked = doc.selectFirst("script:containsData(eval):containsData(MDCore)")
?.data() ?.data()

View file

@ -1,13 +1,16 @@
ext { ext {
extName = 'Vizer.tv' extName = 'Vizer.tv'
extClass = '.Vizer' extClass = '.Vizer'
extVersionCode = 16 extVersionCode = 17
isNsfw = true isNsfw = true
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"
dependencies { dependencies {
implementation(project(':lib:fireplayer-extractor'))
implementation(project(':lib:mixdrop-extractor')) implementation(project(':lib:mixdrop-extractor'))
implementation(project(':lib:playlist-utils'))
implementation(project(':lib:streamtape-extractor')) implementation(project(':lib:streamtape-extractor'))
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
} }

View file

@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.animeextension.pt.vizer.dto.SearchItemDto
import eu.kanade.tachiyomi.animeextension.pt.vizer.dto.SearchResultDto import eu.kanade.tachiyomi.animeextension.pt.vizer.dto.SearchResultDto
import eu.kanade.tachiyomi.animeextension.pt.vizer.dto.VideoDto import eu.kanade.tachiyomi.animeextension.pt.vizer.dto.VideoDto
import eu.kanade.tachiyomi.animeextension.pt.vizer.dto.VideoListDto import eu.kanade.tachiyomi.animeextension.pt.vizer.dto.VideoListDto
import eu.kanade.tachiyomi.animeextension.pt.vizer.extractors.WarezExtractor import eu.kanade.tachiyomi.animeextension.pt.vizer.interceptor.WebViewResolver
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.AnimesPage import eu.kanade.tachiyomi.animesource.model.AnimesPage
@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode 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.fireplayerextractor.FireplayerExtractor
import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
@ -41,7 +42,7 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "Vizer.tv" override val name = "Vizer.tv"
override val baseUrl = "https://vizertv.in" override val baseUrl = "https://novizer.com"
private val apiUrl = "$baseUrl/includes/ajax" private val apiUrl = "$baseUrl/includes/ajax"
override val lang = "pt-BR" override val lang = "pt-BR"
@ -58,6 +59,8 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
private val webViewResolver by lazy { WebViewResolver(headers) }
// ============================== Popular =============================== // ============================== Popular ===============================
override fun popularAnimeRequest(page: Int): Request { override fun popularAnimeRequest(page: Int): Request {
val pageType = preferences.getString(PREF_POPULAR_PAGE_KEY, PREF_POPULAR_PAGE_DEFAULT)!! val pageType = preferences.getString(PREF_POPULAR_PAGE_KEY, PREF_POPULAR_PAGE_DEFAULT)!!
@ -176,7 +179,7 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
val response = episodesClient.newCall(apiRequest("getEpisodes=$id")).execute() val response = episodesClient.newCall(apiRequest("getEpisodes=$id")).execute()
val episodes = response.parseAs<EpisodeListDto>().episodes val episodes = response.parseAs<EpisodeListDto>().episodes
.values .values
.filter { it.released } .filter { it.released === true }
.map { .map {
SEpisode.create().apply { SEpisode.create().apply {
name = "$sname: Ep ${it.name}".run { name = "$sname: Ep ${it.name}".run {
@ -243,7 +246,7 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
private val mixdropExtractor by lazy { MixDropExtractor(client) } private val mixdropExtractor by lazy { MixDropExtractor(client) }
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) } private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
private val warezExtractor by lazy { WarezExtractor(client, headers) } private val fireplayerExtractor by lazy { FireplayerExtractor(client) }
private fun getVideosFromObject(videoObj: VideoDto): List<Video> { private fun getVideosFromObject(videoObj: VideoDto): List<Video> {
val hosters = videoObj.hosters ?: return emptyList() val hosters = videoObj.hosters ?: return emptyList()
@ -251,12 +254,16 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
val langPrefix = if (videoObj.lang == "1") "LEG" else "DUB" val langPrefix = if (videoObj.lang == "1") "LEG" else "DUB"
return hosters.iterator().flatMap { (name, status) -> return hosters.iterator().flatMap { (name, status) ->
if (status != 3) return@flatMap emptyList() // Always try the warezcdn
if (status != 3 && name != "warezcdn") return@flatMap emptyList()
val url = getPlayerUrl(videoObj.id, name) val url = getPlayerUrl(videoObj.id, name)
if (url.isNullOrBlank()) {
return emptyList()
}
when (name) { when (name) {
"mixdrop" -> mixdropExtractor.videosFromUrl(url, langPrefix) "mixdrop" -> mixdropExtractor.videosFromUrl(url, langPrefix)
"streamtape" -> streamtapeExtractor.videosFromUrl(url, "StreamTape($langPrefix)") "streamtape" -> streamtapeExtractor.videosFromUrl(url, "StreamTape($langPrefix)")
"warezcdn" -> warezExtractor.videosFromUrl(url, langPrefix) "warezcdn" -> fireplayerExtractor.videosFromUrl(url, videoNameGen = { "WarezCDN($langPrefix) - $it" })
else -> emptyList() else -> emptyList()
} }
} }
@ -295,17 +302,8 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
// ============================= Utilities ============================== // ============================= Utilities ==============================
private val noRedirectClient = client.newBuilder().followRedirects(false).build() private val noRedirectClient = client.newBuilder().followRedirects(false).build()
private fun getPlayerUrl(id: String, name: String): String { private fun getPlayerUrl(id: String, name: String): String? {
val req = GET("$baseUrl/embed/getPlay.php?id=$id&sv=$name", headers) return webViewResolver.getUrl("$baseUrl/embed/getEmbed.php?id=$id&sv=$name", "$baseUrl/termos")
return if (name == "warezcdn") {
val res = noRedirectClient.newCall(req).execute()
res.close()
res.headers["location"]!!
} else {
val res = client.newCall(req).execute()
val body = res.body.string()
body.substringAfter("location.href=\"", "").substringBefore("\";", "")
}
} }
private fun apiRequest(body: String): Request { private fun apiRequest(body: String): Request {

View file

@ -83,7 +83,7 @@ class HostersDto(
object BooleanSerializer : JsonTransformingSerializer<Boolean>(Boolean.serializer()) { object BooleanSerializer : JsonTransformingSerializer<Boolean>(Boolean.serializer()) {
override fun transformDeserialize(element: JsonElement): JsonElement { override fun transformDeserialize(element: JsonElement): JsonElement {
require(element is JsonPrimitive) require(element is JsonPrimitive)
return if (element.jsonPrimitive.isString) { return if (element.jsonPrimitive.isString && element.jsonPrimitive.content == "true") {
JsonPrimitive(true) JsonPrimitive(true)
} else { } else {
JsonPrimitive(element.jsonPrimitive.booleanOrNull ?: false) JsonPrimitive(element.jsonPrimitive.booleanOrNull ?: false)

View file

@ -1,52 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.vizer.extractors
import android.util.Base64
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
class WarezExtractor(private val client: OkHttpClient, private val headers: Headers) {
fun videosFromUrl(url: String, lang: String): List<Video> {
val doc = client.newCall(GET(url, headers)).execute().asJsoup()
val httpUrl = doc.location().toHttpUrl()
val videoId = httpUrl.queryParameter("id") ?: return emptyList()
val script = doc.selectFirst("script:containsData(allowanceKey)")?.data()
?: return emptyList()
val key = script.substringAfter("allowanceKey").substringAfter('"').substringBefore('"')
val cdn = script.substringAfter("cdnListing").substringAfter('[').substringBefore(']')
.split(',')
.random()
val body = FormBody.Builder()
.add("getVideo", videoId)
.add("key", key)
.build()
val host = "https://" + httpUrl.host
val reqHeaders = headers.newBuilder()
.set("Origin", host)
.set("Referer", url)
.set("X-Requested-With", "XMLHttpRequest")
.build()
val req = client.newCall(POST("$host/player/functions.php", reqHeaders, body)).execute()
val id = req.body.string().substringAfter("id\":\"", "").substringBefore('"', "")
.ifBlank { return emptyList() }
val decrypted = decryptorium(id)
val videoUrl = "https://workerproxy.warezcdn.workers.dev/?url=https://cloclo$cdn.cloud.mail.ru/weblink/view/$decrypted"
return listOf(Video(videoUrl, "WarezCDN - $lang", videoUrl, headers))
}
private fun decryptorium(enc: String): String {
val b64dec = String(Base64.decode(enc, Base64.DEFAULT)).trim()
val start = b64dec.reversed().dropLast(5)
val end = b64dec.substring(0, 5)
return start + end
}
}

View file

@ -0,0 +1,78 @@
package eu.kanade.tachiyomi.animeextension.pt.vizer.interceptor
import android.annotation.SuppressLint
import android.app.Application
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
import android.webkit.WebViewClient
import okhttp3.Headers
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
class WebViewResolver(private val globalHeaders: Headers) {
private val context: Application by injectLazy()
private val handler by lazy { Handler(Looper.getMainLooper()) }
private val tag by lazy { javaClass.simpleName }
@SuppressLint("SetJavaScriptEnabled")
fun getUrl(origRequestUrl: String, baseUrl: String): String? {
val latch = CountDownLatch(1)
var webView: WebView? = null
var result: String? = null
handler.post {
val webview = WebView(context)
webView = webview
with(webview.settings) {
javaScriptEnabled = true
domStorageEnabled = true
databaseEnabled = true
useWideViewPort = false
loadWithOverviewMode = false
userAgentString = globalHeaders["User-Agent"]
}
webview.webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest,
): WebResourceResponse? {
val url = request.url.toString()
Log.d(tag, "Checking url $url")
if (VIDEO_REGEX.containsMatchIn(url)) {
result = url
latch.countDown()
}
return super.shouldInterceptRequest(view, request)
}
override fun onPageFinished(view: WebView?, url: String?) {
Log.d(tag, "onPageFinished $url")
super.onPageFinished(view, url)
view?.evaluateJavascript("document.body.innerHTML += '<iframe src=\"" + origRequestUrl + "\" scrolling=\"no\" frameborder=\"0\" allowfullscreen=\"\" webkitallowfullscreen=\"\" mozallowfullscreen=\"\"></iframe>'") {}
}
}
webView?.loadUrl(baseUrl)
}
latch.await(TIMEOUT_SEC, TimeUnit.SECONDS)
handler.post {
webView?.stopLoading()
webView?.destroy()
webView = null
}
return result
}
companion object {
const val TIMEOUT_SEC: Long = 25
private val VIDEO_REGEX by lazy { Regex("//(mixdrop|streamtape|warezcdn)|/video/") }
}
}