* fix(pt/vizer): Fixed pt/Vizer videos empty (fix #256) * feat: Added new lib extractor: fireplayer-extractor
This commit is contained in:
parent
53856f9275
commit
8f7d9267d4
8 changed files with 177 additions and 72 deletions
10
lib/fireplayer-extractor/build.gradle.kts
Normal file
10
lib/fireplayer-extractor/build.gradle.kts
Normal 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"))
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -17,7 +17,12 @@ class MixDropExtractor(private val client: OkHttpClient) {
|
|||
externalSubs: List<Track> = emptyList(),
|
||||
referer: String = DEFAULT_REFERER,
|
||||
): 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 unpacked = doc.selectFirst("script:containsData(eval):containsData(MDCore)")
|
||||
?.data()
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
ext {
|
||||
extName = 'Vizer.tv'
|
||||
extClass = '.Vizer'
|
||||
extVersionCode = 16
|
||||
extVersionCode = 17
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib:fireplayer-extractor'))
|
||||
implementation(project(':lib:mixdrop-extractor'))
|
||||
implementation(project(':lib:playlist-utils'))
|
||||
implementation(project(':lib:streamtape-extractor'))
|
||||
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
|
||||
}
|
||||
|
|
|
@ -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.VideoDto
|
||||
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.model.AnimeFilterList
|
||||
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.Video
|
||||
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.streamtapeextractor.StreamTapeExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
|
@ -41,7 +42,7 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
|
||||
override val name = "Vizer.tv"
|
||||
|
||||
override val baseUrl = "https://vizertv.in"
|
||||
override val baseUrl = "https://novizer.com"
|
||||
private val apiUrl = "$baseUrl/includes/ajax"
|
||||
|
||||
override val lang = "pt-BR"
|
||||
|
@ -58,6 +59,8 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
private val webViewResolver by lazy { WebViewResolver(headers) }
|
||||
|
||||
// ============================== Popular ===============================
|
||||
override fun popularAnimeRequest(page: Int): Request {
|
||||
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 episodes = response.parseAs<EpisodeListDto>().episodes
|
||||
.values
|
||||
.filter { it.released }
|
||||
.filter { it.released === true }
|
||||
.map {
|
||||
SEpisode.create().apply {
|
||||
name = "$sname: Ep ${it.name}".run {
|
||||
|
@ -243,7 +246,7 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
|
||||
private val mixdropExtractor by lazy { MixDropExtractor(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> {
|
||||
val hosters = videoObj.hosters ?: return emptyList()
|
||||
|
@ -251,12 +254,16 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
val langPrefix = if (videoObj.lang == "1") "LEG" else "DUB"
|
||||
|
||||
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)
|
||||
if (url.isNullOrBlank()) {
|
||||
return emptyList()
|
||||
}
|
||||
when (name) {
|
||||
"mixdrop" -> mixdropExtractor.videosFromUrl(url, langPrefix)
|
||||
"streamtape" -> streamtapeExtractor.videosFromUrl(url, "StreamTape($langPrefix)")
|
||||
"warezcdn" -> warezExtractor.videosFromUrl(url, langPrefix)
|
||||
"warezcdn" -> fireplayerExtractor.videosFromUrl(url, videoNameGen = { "WarezCDN($langPrefix) - $it" })
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
|
@ -295,17 +302,8 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
// ============================= Utilities ==============================
|
||||
private val noRedirectClient = client.newBuilder().followRedirects(false).build()
|
||||
|
||||
private fun getPlayerUrl(id: String, name: String): String {
|
||||
val req = GET("$baseUrl/embed/getPlay.php?id=$id&sv=$name", headers)
|
||||
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 getPlayerUrl(id: String, name: String): String? {
|
||||
return webViewResolver.getUrl("$baseUrl/embed/getEmbed.php?id=$id&sv=$name", "$baseUrl/termos")
|
||||
}
|
||||
|
||||
private fun apiRequest(body: String): Request {
|
||||
|
|
|
@ -83,7 +83,7 @@ class HostersDto(
|
|||
object BooleanSerializer : JsonTransformingSerializer<Boolean>(Boolean.serializer()) {
|
||||
override fun transformDeserialize(element: JsonElement): JsonElement {
|
||||
require(element is JsonPrimitive)
|
||||
return if (element.jsonPrimitive.isString) {
|
||||
return if (element.jsonPrimitive.isString && element.jsonPrimitive.content == "true") {
|
||||
JsonPrimitive(true)
|
||||
} else {
|
||||
JsonPrimitive(element.jsonPrimitive.booleanOrNull ?: false)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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/") }
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue