Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(pt/vizer): Update baseURL + fix episode list + add WarezCDN extractor #3036

Merged
merged 5 commits into from
Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/pt/vizer/build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
ext {
extName = 'Vizer.tv'
extClass = '.Vizer'
extVersionCode = 13
extVersionCode = 14
isNsfw = true
}

Expand All @@ -10,4 +10,4 @@ apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:mixdrop-extractor'))
implementation(project(':lib:streamtape-extractor'))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.pt.vizer.VizerFilters.FilterSearchParams
import eu.kanade.tachiyomi.animeextension.pt.vizer.dto.EpisodeListDto
import eu.kanade.tachiyomi.animeextension.pt.vizer.dto.PlayersDto
import eu.kanade.tachiyomi.animeextension.pt.vizer.dto.HostersDto
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.VideoLanguagesDto
import eu.kanade.tachiyomi.animeextension.pt.vizer.dto.VideoListDto
import eu.kanade.tachiyomi.animeextension.pt.vizer.extractors.WarezExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
Expand All @@ -24,7 +25,6 @@ import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parseAs
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
Expand All @@ -33,20 +33,19 @@ import okhttp3.Response
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy

class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {

override val name = "Vizer.tv"

override val baseUrl = "https://vizer.tv"
override val baseUrl = "https://vizertv.in"
private val apiUrl = "$baseUrl/includes/ajax"

override val lang = "pt-BR"

override val supportsLatest = true

private val json: Json by injectLazy()
override fun headersBuilder() = super.headersBuilder().add("Referer", "$baseUrl/")

private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
Expand All @@ -65,7 +64,7 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {

override fun popularAnimeParse(response: Response): AnimesPage {
val result = response.parseAs<SearchResultDto>()
val animes = result.list.map(::animeFromObject)
val animes = result.items.values.map(::animeFromObject)
val hasNext = result.quantity == 35
return AnimesPage(animes, hasNext)
}
Expand All @@ -89,7 +88,7 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {

override fun latestUpdatesParse(response: Response): AnimesPage {
val parsedData = response.parseAs<SearchResultDto>()
val animes = parsedData.list.map(::animeFromObject)
val animes = parsedData.items.values.map(::animeFromObject)
return AnimesPage(animes, false)
}

Expand Down Expand Up @@ -165,6 +164,7 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
val sname = seasonElement.text()
val response = client.newCall(apiRequest("getEpisodes=$id")).execute()
val episodes = response.parseAs<EpisodeListDto>().episodes
.values
.filter { it.released }
.map {
SEpisode.create().apply {
Expand All @@ -178,7 +178,7 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {

override fun episodeListParse(response: Response): List<SEpisode> {
val doc = response.asJsoup()
val seasons = doc.select("div#seasonsList div.item[data-season-id]")
val seasons = doc.select("div.seasons div.list div.item[data-season-id]")
return if (seasons.size > 0) {
seasons.flatMap(::getSeasonEps).reversed()
} else {
Expand All @@ -201,40 +201,48 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
} else {
// Fake url, its an ID that will be used to get episode languages
// (sub/dub) and then return the video link
apiRequest("getEpisodeLanguages=$url")
apiRequest("getEpisodeData=$url")
}
}

override fun videoListParse(response: Response): List<Video> {
val body = response.body.string()
val videoObjectList = if (body.startsWith("{")) {
json.decodeFromString<VideoLanguagesDto>(body).videos
body.parseAs<VideoListDto>().videos.values.toList()
} else {
val videoJson = body.substringAfterLast("videoPlayerBox(").substringBefore(");")
json.decodeFromString<VideoLanguagesDto>(videoJson).videos
val doc = response.asJsoup(body)
doc.select("div.audios div[data-load-player]").mapNotNull {
try {
val movieHosters = it.attr("data-players").parseAs<HostersDto>()
val movieId = it.attr("data-load-player")
val movieLang = if (it.hasClass("legendado")) "1" else "0"
VideoDto(movieId, movieLang).apply { hosters = movieHosters }
} catch (_: Throwable) { null }
}
}

return videoObjectList.flatMap(::getVideosFromObject)
}

private val mixdropExtractor by lazy { MixDropExtractor(client) }
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
private val warezExtractor by lazy { WarezExtractor(client, headers) }

private fun getVideosFromObject(videoObj: VideoDto): List<Video> {
val players = client.newCall(apiRequest("getVideoPlayers=${videoObj.id}"))
.execute()
.parseAs<PlayersDto>()
val hosters = videoObj.hosters ?: return emptyList()

val langPrefix = if (videoObj.lang == "1") "LEG" else "DUB"
val videoList = players.iterator().mapNotNull { (name, status) ->
if (status == "0") return@mapNotNull null

return hosters.iterator().flatMap { (name, status) ->
if (status != 3) return@flatMap emptyList()
val url = getPlayerUrl(videoObj.id, name)
when (name) {
"mixdrop" -> mixdropExtractor.videoFromUrl(url, langPrefix)
"mixdrop" -> mixdropExtractor.videosFromUrl(url, langPrefix)
"streamtape" -> streamtapeExtractor.videosFromUrl(url, "StreamTape($langPrefix)")
else -> null
"warezcdn" -> warezExtractor.videosFromUrl(url, langPrefix)
else -> emptyList()
}
}.flatten()
return videoList
}
}

// ============================== Settings ==============================
Expand Down Expand Up @@ -289,10 +297,19 @@ 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")
val body = client.newCall(req).execute().body.string()
return body.substringAfter("location.href=\"").substringBefore("\";")
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 apiRequest(body: String): Request {
Expand Down Expand Up @@ -333,6 +350,7 @@ class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
private val PREF_PLAYER_ARRAY = arrayOf(
"MixDrop",
"StreamTape",
"WarezCDN",
)

private const val PREF_LANGUAGE_KEY = "pref_language"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
package eu.kanade.tachiyomi.animeextension.pt.vizer.dto

import eu.kanade.tachiyomi.util.parseAs
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonTransformingSerializer

typealias FakeList<T> = Map<String, T>

@Serializable
data class SearchResultDto(
class SearchResultDto(
val quantity: Int = 0,
@Serializable(with = GenericListSerializer::class)
@EncodeDefault
val list: List<SearchItemDto> = emptyList(),
@SerialName("list")
val items: FakeList<SearchItemDto> = emptyMap(),
)

@Serializable
data class SearchItemDto(
class SearchItemDto(
val id: String,
val title: String,
val url: String,
Expand All @@ -28,51 +25,50 @@ data class SearchItemDto(
)

@Serializable
data class EpisodeListDto(
@Serializable(with = GenericListSerializer::class)
class EpisodeListDto(
@SerialName("list")
val episodes: List<EpisodeItemDto>,
val episodes: FakeList<EpisodeItemDto>,
)

@Serializable
data class EpisodeItemDto(
class EpisodeItemDto(
val id: String,
val name: String,
val released: Boolean,
val title: String,
)

@Serializable
data class VideoLanguagesDto(
class VideoListDto(
@SerialName("list")
@Serializable(with = GenericListSerializer::class)
val videos: List<VideoDto>,
val videos: FakeList<VideoDto>,
)

@Serializable
data class VideoDto(
class VideoDto(
val id: String,
val lang: String,
)
@SerialName("players")
private val players: String? = null,
) {
var hosters = try {
players?.parseAs<HostersDto>()
} catch (e: Throwable) {
null
}
}

@Serializable
data class PlayersDto(
val mixdrop: String = "0",
val streamtape: String = "0",
class HostersDto(
val mixdrop: Int = 0,
val streamtape: Int = 0,
val warezcdn: Int = 0,
) {
operator fun iterator(): List<Pair<String, String>> {
operator fun iterator(): List<Pair<String, Int>> {
return listOf(
"mixdrop" to mixdrop,
"streamtape" to streamtape,
"warezcdn" to warezcdn,
)
}
}

class GenericListSerializer<T>(
private val itemSerializer: KSerializer<T>,
) : JsonTransformingSerializer<List<T>>(ListSerializer(itemSerializer)) {
override fun transformDeserialize(element: JsonElement): JsonElement {
val jsonObj = element as JsonObject
return JsonArray(jsonObj.values.toList())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
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
}
}