New source: Vizer.tv (#967)
* Add vizer base * Add MixDrop extractor * Use StreamTape extractor * Use Fembed extractor * Dont count with WarezCDN * Add containsNsfw flag to build.gradle
This commit is contained in:
24
src/pt/vizer/AndroidManifest.xml
Normal file
24
src/pt/vizer/AndroidManifest.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="eu.kanade.tachiyomi.animeextension">
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<activity
|
||||||
|
android:name=".pt.vizer.VizerUrlActivity"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="vizer.tv"
|
||||||
|
android:pathPattern="/..*/..*/..*"
|
||||||
|
android:scheme="https" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
20
src/pt/vizer/build.gradle
Normal file
20
src/pt/vizer/build.gradle
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlinx-serialization'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
extName = 'Vizer.tv'
|
||||||
|
pkgNameSuffix = 'pt.vizer'
|
||||||
|
extClass = '.Vizer'
|
||||||
|
extVersionCode = 1
|
||||||
|
libVersion = '13'
|
||||||
|
containsNsfw = true
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(':lib-fembed-extractor'))
|
||||||
|
implementation(project(':lib-streamtape-extractor'))
|
||||||
|
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
BIN
src/pt/vizer/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/pt/vizer/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
BIN
src/pt/vizer/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/pt/vizer/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
BIN
src/pt/vizer/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/pt/vizer/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.4 KiB |
BIN
src/pt/vizer/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/pt/vizer/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
src/pt/vizer/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/pt/vizer/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,311 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.pt.vizer
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.preference.ListPreference
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
|
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.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.extractors.MixDropExtractor
|
||||||
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||||
|
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.fembedextractor.FembedExtractor
|
||||||
|
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||||
|
|
||||||
|
override val name = "Vizer.tv"
|
||||||
|
|
||||||
|
override val baseUrl = "https://vizer.tv"
|
||||||
|
private val API_URL = "$baseUrl/includes/ajax"
|
||||||
|
|
||||||
|
override val lang = "pt-BR"
|
||||||
|
|
||||||
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
override val client: OkHttpClient = network.cloudflareClient
|
||||||
|
|
||||||
|
private val json = Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private val preferences: SharedPreferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================== Popular ===============================
|
||||||
|
|
||||||
|
override fun popularAnimeRequest(page: Int): Request {
|
||||||
|
val initialUrl = "$API_URL/ajaxPagination.php?categoryFilterOrderBy=vzViews&page=$page&categoryFilterOrderWay=desc&categoryFilterYearMin=1950&categoryFilterYearMax=2022"
|
||||||
|
val pageType = preferences.getString(PREF_POPULAR_PAGE_KEY, "movie")!!
|
||||||
|
val finalUrl = if ("movie" in pageType) {
|
||||||
|
initialUrl + "&saga=0&categoriesListMovies=all"
|
||||||
|
} else {
|
||||||
|
(initialUrl + "&categoriesListSeries=all").let {
|
||||||
|
if ("anime" in pageType) it + "&anime=1"
|
||||||
|
else it + "&anime=0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return GET(finalUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||||
|
val result = response.parseAs<SearchResultDto>()
|
||||||
|
val animes = result.list.map(::animeFromObject).toList()
|
||||||
|
val hasNext = result.quantity == 35
|
||||||
|
return AnimesPage(animes, hasNext)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun animeFromObject(item: SearchItemDto): SAnime =
|
||||||
|
SAnime.create().apply {
|
||||||
|
var slug = if (item.status.isBlank()) "filme" else "serie"
|
||||||
|
url = "/$slug/online/${item.url}"
|
||||||
|
slug = if (slug == "filme") "movies" else "series"
|
||||||
|
title = item.title
|
||||||
|
status = when (item.status) {
|
||||||
|
"Retornando" -> SAnime.ONGOING
|
||||||
|
else -> SAnime.COMPLETED
|
||||||
|
}
|
||||||
|
thumbnail_url = "$baseUrl/content/$slug/posterPt/342/${item.id}.webp"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================== Episodes ==============================
|
||||||
|
|
||||||
|
private fun getSeasonEps(seasonElement: Element): List<SEpisode> {
|
||||||
|
val id = seasonElement.attr("data-season-id")
|
||||||
|
val sname = seasonElement.text()
|
||||||
|
val response = client.newCall(apiRequest("getEpisodes=$id")).execute()
|
||||||
|
val episodes = response.parseAs<EpisodeListDto>().episodes.mapNotNull {
|
||||||
|
if (it.released)
|
||||||
|
SEpisode.create().apply {
|
||||||
|
name = "Temp $sname: Ep ${it.name} - ${it.title}"
|
||||||
|
episode_number = it.name.toFloatOrNull() ?: 0F
|
||||||
|
url = it.id
|
||||||
|
}
|
||||||
|
else null
|
||||||
|
}
|
||||||
|
return episodes
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||||
|
val doc = response.asJsoup()
|
||||||
|
val seasons = doc.select("div#seasonsList div.item[data-season-id]")
|
||||||
|
return if (seasons.size > 0) {
|
||||||
|
seasons.flatMap(::getSeasonEps).reversed()
|
||||||
|
} else {
|
||||||
|
listOf(
|
||||||
|
SEpisode.create().apply {
|
||||||
|
name = "Filme"
|
||||||
|
episode_number = 1F
|
||||||
|
url = response.request.url.toString()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ Video Links =============================
|
||||||
|
|
||||||
|
override fun videoListRequest(episode: SEpisode): Request {
|
||||||
|
val url = episode.url
|
||||||
|
return if (url.startsWith("https")) {
|
||||||
|
// Its an real url, maybe from a movie
|
||||||
|
GET(url, headers)
|
||||||
|
} 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun videoListParse(response: Response): List<Video> {
|
||||||
|
val body = response.body?.string().orEmpty()
|
||||||
|
val videoObjectList = if (body.startsWith("{")) {
|
||||||
|
json.decodeFromString<VideoLanguagesDto>(body).videos
|
||||||
|
} else {
|
||||||
|
val videoJson = body.substringAfterLast("videoPlayerBox(").substringBefore(");")
|
||||||
|
json.decodeFromString<VideoLanguagesDto>(videoJson).videos
|
||||||
|
}
|
||||||
|
|
||||||
|
return videoObjectList.flatMap(::getVideosFromObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getVideosFromObject(videoObj: VideoDto): List<Video> {
|
||||||
|
val players = client.newCall(apiRequest("getVideoPlayers=${videoObj.id}"))
|
||||||
|
.execute()
|
||||||
|
.parseAs<PlayersDto>()
|
||||||
|
val langPrefix = if (videoObj.lang == "1") "SUB" else "DUB"
|
||||||
|
val videoList = players.iterator().mapNotNull loop@{ (name, status) ->
|
||||||
|
if (status == "0") return@loop null
|
||||||
|
val url = getPlayerUrl(videoObj.id, name)
|
||||||
|
when {
|
||||||
|
name == "mixdrop" ->
|
||||||
|
MixDropExtractor(client)
|
||||||
|
.videoFromUrl(url, langPrefix)?.let(::listOf)
|
||||||
|
name == "streamtape" ->
|
||||||
|
StreamTapeExtractor(client)
|
||||||
|
.videoFromUrl(url, "StreamTape($langPrefix)")?.let(::listOf)
|
||||||
|
name == "fembed" ->
|
||||||
|
runCatching {
|
||||||
|
FembedExtractor(client)
|
||||||
|
.videosFromUrl(url, langPrefix)
|
||||||
|
}.getOrNull()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}.flatten()
|
||||||
|
return videoList
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================== Search ===============================
|
||||||
|
|
||||||
|
override fun getFilterList(): AnimeFilterList = VizerFilters.filterList
|
||||||
|
|
||||||
|
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
|
||||||
|
|
||||||
|
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
|
||||||
|
return if (query.startsWith(PREFIX_SEARCH)) {
|
||||||
|
val path = query.removePrefix(PREFIX_SEARCH).replace("/", "/online/")
|
||||||
|
client.newCall(GET("$baseUrl/$path"))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { response ->
|
||||||
|
searchAnimeByPathParse(response, path)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val params = VizerFilters.getSearchParameters(filters)
|
||||||
|
client.newCall(searchAnimeRequest(page, query, params))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { response ->
|
||||||
|
searchAnimeParse(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchAnimeByPathParse(response: Response, path: String): AnimesPage {
|
||||||
|
val details = animeDetailsParse(response)
|
||||||
|
details.url = "/" + path
|
||||||
|
return AnimesPage(listOf(details), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = throw Exception("not used")
|
||||||
|
|
||||||
|
private fun searchAnimeRequest(page: Int, query: String, filters: VizerFilters.FilterSearchParams): Request {
|
||||||
|
val urlBuilder = "$API_URL/ajaxPagination.php".toHttpUrlOrNull()!!.newBuilder()
|
||||||
|
.addQueryParameter("page", page.toString())
|
||||||
|
.addQueryParameter("search", query)
|
||||||
|
.addQueryParameter("saga", "0")
|
||||||
|
.addQueryParameter("categoryFilterYearMin", filters.minYear)
|
||||||
|
.addQueryParameter("categoryFilterYearMax", filters.maxYear)
|
||||||
|
.addQueryParameter("categoryFilterOrderBy", filters.orderBy)
|
||||||
|
.addQueryParameter("categoryFilterOrderWay", filters.orderWay)
|
||||||
|
|
||||||
|
if (filters.type == "Movies")
|
||||||
|
urlBuilder.addQueryParameter("categoriesListMovies", filters.genre)
|
||||||
|
else
|
||||||
|
urlBuilder.addQueryParameter("categoriesListSeries", filters.genre)
|
||||||
|
if (filters.type == "anime")
|
||||||
|
urlBuilder.addQueryParameter("anime", "1")
|
||||||
|
return GET(urlBuilder.build().toString(), headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================== Anime Details ============================
|
||||||
|
|
||||||
|
override fun animeDetailsParse(response: Response): SAnime {
|
||||||
|
val doc = response.asJsoup()
|
||||||
|
return SAnime.create().apply {
|
||||||
|
title = doc.selectFirst("section.ai > h2").text()
|
||||||
|
thumbnail_url = doc.selectFirst("meta[property=og:image]").attr("content")
|
||||||
|
var desc = doc.selectFirst("span.desc").text() + "\n"
|
||||||
|
doc.selectFirst("div.year")?.let { desc += "\nAno: ${it.text()}" }
|
||||||
|
doc.selectFirst("div.tm")?.let { desc += "\nDuração: ${it.text()}" }
|
||||||
|
doc.selectFirst("a.rating")?.let { desc += "\nNota: ${it.text()}" }
|
||||||
|
description = desc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================== Latest ===============================
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request = apiRequest("getHomeSliderSeries=1")
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response): AnimesPage {
|
||||||
|
val parsedData = response.parseAs<SearchResultDto>()
|
||||||
|
val animes = parsedData.list.map(::animeFromObject).toList()
|
||||||
|
return AnimesPage(animes, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================== Settings ==============================
|
||||||
|
|
||||||
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
|
val popularPage = ListPreference(screen.context).apply {
|
||||||
|
key = PREF_POPULAR_PAGE_KEY
|
||||||
|
title = PREF_POPULAR_PAGE_TITLE
|
||||||
|
entries = PREF_POPULAR_PAGE_ENTRIES
|
||||||
|
entryValues = PREF_POPULAR_PAGE_VALUES
|
||||||
|
setDefaultValue("anime")
|
||||||
|
summary = "%s"
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
val selected = newValue as String
|
||||||
|
val index = findIndexOfValue(selected)
|
||||||
|
val entry = entryValues[index] as String
|
||||||
|
preferences.edit().putString(key, entry).commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
screen.addPreference(popularPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================= Utilities ==============================
|
||||||
|
|
||||||
|
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().orEmpty()
|
||||||
|
return body.substringAfter("location.href=\"").substringBefore("\";")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun apiRequest(body: String): Request {
|
||||||
|
val reqBody = body.toRequestBody("application/x-www-form-urlencoded".toMediaType())
|
||||||
|
val newHeaders = headersBuilder().add("x-requested-with", "XMLHttpRequest")
|
||||||
|
.build()
|
||||||
|
return POST("$API_URL/publicFunctions.php", newHeaders, body = reqBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T> Response.parseAs(): T {
|
||||||
|
val responseBody = body?.string().orEmpty()
|
||||||
|
return json.decodeFromString(responseBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val PREF_POPULAR_PAGE_KEY = "pref_popular_page"
|
||||||
|
private const val PREF_POPULAR_PAGE_TITLE = "Página de Populares"
|
||||||
|
private val PREF_POPULAR_PAGE_ENTRIES = arrayOf(
|
||||||
|
"Animes", "Filmes", "Séries"
|
||||||
|
)
|
||||||
|
private val PREF_POPULAR_PAGE_VALUES = arrayOf(
|
||||||
|
"anime", "movie", "serie"
|
||||||
|
)
|
||||||
|
|
||||||
|
const val PREFIX_SEARCH = "path:"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,113 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.pt.vizer
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
|
|
||||||
|
object VizerFilters {
|
||||||
|
|
||||||
|
open class QueryPartFilter(
|
||||||
|
displayName: String,
|
||||||
|
val vals: Array<Pair<String, String>>
|
||||||
|
) : AnimeFilter.Select<String>(
|
||||||
|
displayName,
|
||||||
|
vals.map { it.first }.toTypedArray()
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun toQueryPart() = vals[state].second
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified R> AnimeFilterList.getFirst(): R {
|
||||||
|
return this.filterIsInstance<R>().first()
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
|
||||||
|
return this.getFirst<R>().let {
|
||||||
|
(it as QueryPartFilter).toQueryPart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TypeFilter : QueryPartFilter("Tipo", VizerFiltersData.types)
|
||||||
|
class MinYearFilter : QueryPartFilter("Ano (min)", VizerFiltersData.minYears)
|
||||||
|
class MaxYearFilter : QueryPartFilter("Ano (max)", VizerFiltersData.maxYears)
|
||||||
|
class GenreFilter : QueryPartFilter("Categoria", VizerFiltersData.genres)
|
||||||
|
|
||||||
|
class SortFilter : AnimeFilter.Sort(
|
||||||
|
"Ordernar por",
|
||||||
|
VizerFiltersData.orders.map { it.first }.toTypedArray(),
|
||||||
|
Selection(0, true)
|
||||||
|
)
|
||||||
|
|
||||||
|
val filterList = AnimeFilterList(
|
||||||
|
TypeFilter(),
|
||||||
|
MinYearFilter(),
|
||||||
|
MaxYearFilter(),
|
||||||
|
GenreFilter(),
|
||||||
|
SortFilter()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class FilterSearchParams(
|
||||||
|
val type: String = "anime",
|
||||||
|
val minYear: String = "1890",
|
||||||
|
val maxYear: String = "2022",
|
||||||
|
val genre: String = "all",
|
||||||
|
var orderBy: String = "rating",
|
||||||
|
var orderWay: String = "desc"
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
|
||||||
|
val searchParams = FilterSearchParams(
|
||||||
|
filters.asQueryPart<TypeFilter>(),
|
||||||
|
filters.asQueryPart<MinYearFilter>(),
|
||||||
|
filters.asQueryPart<MaxYearFilter>(),
|
||||||
|
filters.asQueryPart<GenreFilter>()
|
||||||
|
)
|
||||||
|
filters.getFirst<SortFilter>().state?.let {
|
||||||
|
val order = VizerFiltersData.orders[it.index].second
|
||||||
|
searchParams.orderBy = order
|
||||||
|
searchParams.orderWay = if (it.ascending) "asc" else "desc"
|
||||||
|
}
|
||||||
|
return searchParams
|
||||||
|
}
|
||||||
|
|
||||||
|
private object VizerFiltersData {
|
||||||
|
|
||||||
|
val types = arrayOf(
|
||||||
|
Pair("Animes", "anime"),
|
||||||
|
Pair("Filmes", "Movies"),
|
||||||
|
Pair("Series", "Series")
|
||||||
|
)
|
||||||
|
val maxYears = (2022 downTo 1890).map {
|
||||||
|
Pair(it.toString(), it.toString())
|
||||||
|
}.toTypedArray()
|
||||||
|
|
||||||
|
val minYears = maxYears.reversed().toTypedArray()
|
||||||
|
|
||||||
|
val orders = arrayOf(
|
||||||
|
Pair("Popularidade", "vzViews"),
|
||||||
|
Pair("Ano", "year"),
|
||||||
|
Pair("Título", "title"),
|
||||||
|
Pair("Rating", "rating")
|
||||||
|
)
|
||||||
|
|
||||||
|
val genres = arrayOf(
|
||||||
|
Pair("Todas", "all"),
|
||||||
|
Pair("Animação", "animacao"),
|
||||||
|
Pair("Aventura", "aventura"),
|
||||||
|
Pair("Ação", "acao"),
|
||||||
|
Pair("Comédia", "comedia"),
|
||||||
|
Pair("Crime", "crime"),
|
||||||
|
Pair("Documentário", "documentario"),
|
||||||
|
Pair("Drama", "drama"),
|
||||||
|
Pair("Família", "familia"),
|
||||||
|
Pair("Fantasia", "fantasia"),
|
||||||
|
Pair("Faroeste", "faroeste"),
|
||||||
|
Pair("Guerra", "guerra"),
|
||||||
|
Pair("LGBTQ+", "lgbt"),
|
||||||
|
Pair("Mistério", "misterio"),
|
||||||
|
Pair("Músical", "musical"),
|
||||||
|
Pair("Romance", "romance"),
|
||||||
|
Pair("Suspense", "suspense"),
|
||||||
|
Pair("Terror", "terror"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.pt.vizer
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Springboard that accepts https://vizer.tv/[anime|filme|serie]/online/<slug> intents
|
||||||
|
* and redirects them to the main Aniyomi process.
|
||||||
|
*/
|
||||||
|
class VizerUrlActivity : Activity() {
|
||||||
|
|
||||||
|
private val TAG = "VizerUrlActivity"
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
val pathSegments = intent?.data?.pathSegments
|
||||||
|
if (pathSegments != null && pathSegments.size > 1) {
|
||||||
|
val query = "${pathSegments[0]}/${pathSegments[2]}"
|
||||||
|
val searchQuery = Vizer.PREFIX_SEARCH + query
|
||||||
|
val mainIntent = Intent().apply {
|
||||||
|
action = "eu.kanade.tachiyomi.ANIMESEARCH"
|
||||||
|
putExtra("query", searchQuery)
|
||||||
|
putExtra("filter", packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
startActivity(mainIntent)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
Log.e(TAG, e.toString())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "could not parse uri from intent $intent")
|
||||||
|
}
|
||||||
|
|
||||||
|
finish()
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.pt.vizer.dto
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SearchResultDto(
|
||||||
|
val quantity: Int = 0,
|
||||||
|
@Serializable(with = GenericListSerializer::class)
|
||||||
|
@EncodeDefault
|
||||||
|
val list: List<SearchItemDto> = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SearchItemDto(
|
||||||
|
val id: String,
|
||||||
|
val title: String,
|
||||||
|
val url: String,
|
||||||
|
@EncodeDefault
|
||||||
|
val status: String = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class EpisodeListDto(
|
||||||
|
@Serializable(with = GenericListSerializer::class)
|
||||||
|
@SerialName("list")
|
||||||
|
val episodes: List<EpisodeItemDto>
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class EpisodeItemDto(
|
||||||
|
val id: String,
|
||||||
|
val img: String,
|
||||||
|
val name: String,
|
||||||
|
val released: Boolean,
|
||||||
|
val title: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class VideoLanguagesDto(
|
||||||
|
@SerialName("list")
|
||||||
|
@Serializable(with = GenericListSerializer::class)
|
||||||
|
val videos: List<VideoDto>
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class VideoDto(
|
||||||
|
val id: String,
|
||||||
|
val lang: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PlayersDto(
|
||||||
|
val mixdrop: String = "0",
|
||||||
|
val streamtape: String = "0",
|
||||||
|
val fembed: String = "0"
|
||||||
|
) {
|
||||||
|
operator fun iterator(): List<Pair<String, String>> {
|
||||||
|
return listOf(
|
||||||
|
"mixdrop" to mixdrop,
|
||||||
|
"streamtape" to streamtape,
|
||||||
|
"fembed" to fembed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.pt.vizer.extractors
|
||||||
|
|
||||||
|
import dev.datlag.jsunpacker.JsUnpacker
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
|
||||||
|
class MixDropExtractor(private val client: OkHttpClient) {
|
||||||
|
fun videoFromUrl(url: String, lang: String = ""): Video? {
|
||||||
|
val doc = client.newCall(GET(url)).execute().asJsoup()
|
||||||
|
val unpacked = doc.selectFirst("script:containsData(eval):containsData(MDCore)")
|
||||||
|
?.data()
|
||||||
|
?.let { JsUnpacker.unpackAndCombine(it) }
|
||||||
|
?: return null
|
||||||
|
val videoUrl = "https:" + unpacked.substringAfter("Core.wurl=\"")
|
||||||
|
.substringBefore("\"")
|
||||||
|
val quality = ("MixDrop").let {
|
||||||
|
if (lang.isNotBlank()) {
|
||||||
|
"$it($lang)"
|
||||||
|
} else it
|
||||||
|
}
|
||||||
|
return Video(url, quality, videoUrl)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user