forked from AlmightyHak/extensions-source
Initial commit
This commit is contained in:
commit
98ed7e8839
2263 changed files with 108711 additions and 0 deletions
23
src/pt/vizer/AndroidManifest.xml
Normal file
23
src/pt/vizer/AndroidManifest.xml
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<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>
|
13
src/pt/vizer/build.gradle
Normal file
13
src/pt/vizer/build.gradle
Normal file
|
@ -0,0 +1,13 @@
|
|||
ext {
|
||||
extName = 'Vizer.tv'
|
||||
extClass = '.Vizer'
|
||||
extVersionCode = 15
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib:mixdrop-extractor'))
|
||||
implementation(project(':lib:streamtape-extractor'))
|
||||
}
|
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,356 @@
|
|||
package eu.kanade.tachiyomi.animeextension.pt.vizer
|
||||
|
||||
import android.app.Application
|
||||
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.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.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
|
||||
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.mixdropextractor.MixDropExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
|
||||
import eu.kanade.tachiyomi.util.parseAs
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
override val name = "Vizer.tv"
|
||||
|
||||
override val baseUrl = "https://vizertv.in"
|
||||
private val apiUrl = "$baseUrl/includes/ajax"
|
||||
|
||||
override val lang = "pt-BR"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder().add("Referer", "$baseUrl/")
|
||||
|
||||
private val episodesClient by lazy {
|
||||
client.newBuilder().rateLimitHost(baseUrl.toHttpUrl(), 1, 1.5.seconds).build()
|
||||
}
|
||||
|
||||
private val preferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
// ============================== Popular ===============================
|
||||
override fun popularAnimeRequest(page: Int): Request {
|
||||
val pageType = preferences.getString(PREF_POPULAR_PAGE_KEY, PREF_POPULAR_PAGE_DEFAULT)!!
|
||||
val params = FilterSearchParams(
|
||||
orderBy = "vzViews",
|
||||
orderWay = "desc",
|
||||
type = pageType,
|
||||
)
|
||||
return searchAnimeRequest(page, "", params)
|
||||
}
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val result = response.parseAs<SearchResultDto>()
|
||||
val animes = result.items.values.map(::animeFromObject)
|
||||
val hasNext = result.quantity == 35
|
||||
return AnimesPage(animes, hasNext)
|
||||
}
|
||||
|
||||
private fun animeFromObject(item: SearchItemDto) = SAnime.create().apply {
|
||||
val (urlslug, imgslug) = when {
|
||||
item.status.isBlank() -> Pair("filme", "movies")
|
||||
else -> Pair("serie", "series")
|
||||
}
|
||||
url = "/$urlslug/online/${item.url}"
|
||||
title = item.title
|
||||
status = when (item.status) {
|
||||
"Retornando" -> SAnime.ONGOING
|
||||
else -> SAnime.COMPLETED
|
||||
}
|
||||
thumbnail_url = "$baseUrl/content/$imgslug/posterPt/342/${item.id}.webp"
|
||||
}
|
||||
|
||||
// =============================== Latest ===============================
|
||||
override fun latestUpdatesRequest(page: Int) = apiRequest("getHomeSliderSeries=1")
|
||||
|
||||
override fun latestUpdatesParse(response: Response): AnimesPage {
|
||||
val parsedData = response.parseAs<SearchResultDto>()
|
||||
val animes = parsedData.items.values.map(::animeFromObject)
|
||||
return AnimesPage(animes, false)
|
||||
}
|
||||
|
||||
// =============================== Search ===============================
|
||||
override fun getFilterList(): AnimeFilterList = VizerFilters.FILTER_LIST
|
||||
|
||||
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
|
||||
|
||||
override suspend fun getSearchAnime(page: Int, query: String, filters: AnimeFilterList): AnimesPage {
|
||||
return if (query.startsWith(PREFIX_SEARCH)) {
|
||||
val path = query.removePrefix(PREFIX_SEARCH).replace("/", "/online/")
|
||||
client.newCall(GET("$baseUrl/$path"))
|
||||
.awaitSuccess()
|
||||
.use(::searchAnimeByPathParse)
|
||||
} else {
|
||||
super.getSearchAnime(page, query, filters)
|
||||
}
|
||||
}
|
||||
|
||||
private fun searchAnimeByPathParse(response: Response): AnimesPage {
|
||||
val details = animeDetailsParse(response)
|
||||
return AnimesPage(listOf(details), false)
|
||||
}
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
val params = VizerFilters.getSearchParameters(filters)
|
||||
return searchAnimeRequest(page, query, params)
|
||||
}
|
||||
|
||||
private fun searchAnimeRequest(page: Int, query: String, params: FilterSearchParams): Request {
|
||||
val urlBuilder = "$apiUrl/ajaxPagination.php".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("page", "${page - 1}")
|
||||
.addQueryParameter("categoryFilterYearMin", params.minYear)
|
||||
.addQueryParameter("categoryFilterYearMax", params.maxYear)
|
||||
.addQueryParameter("categoryFilterOrderBy", params.orderBy)
|
||||
.addQueryParameter("categoryFilterOrderWay", params.orderWay)
|
||||
.apply {
|
||||
if (query.isNotBlank()) addQueryParameter("search", query)
|
||||
|
||||
when (params.type) {
|
||||
"Movies" -> {
|
||||
addQueryParameter("saga", "0")
|
||||
addQueryParameter("categoriesListMovies", params.genre)
|
||||
}
|
||||
else -> {
|
||||
addQueryParameter("categoriesListSeries", params.genre)
|
||||
val isAnime = params.type == "anime"
|
||||
addQueryParameter("anime", if (isAnime) "1" else "0")
|
||||
}
|
||||
}
|
||||
}
|
||||
return GET(urlBuilder.build(), headers)
|
||||
}
|
||||
|
||||
// =========================== Anime Details ============================
|
||||
override fun animeDetailsParse(response: Response) = SAnime.create().apply {
|
||||
val doc = response.asJsoup()
|
||||
setUrlWithoutDomain(doc.location())
|
||||
title = doc.selectFirst("section.ai > h2")!!.text()
|
||||
thumbnail_url = doc.selectFirst("meta[property=og:image]")!!.attr("content")
|
||||
|
||||
description = buildString {
|
||||
append(doc.selectFirst("span.desc")!!.text() + "\n")
|
||||
doc.selectFirst("div.year")?.also { append("\nAno: ", it.text()) }
|
||||
doc.selectFirst("div.tm")?.also { append("\nDuração: ", it.text()) }
|
||||
doc.selectFirst("a.rating")?.also { append("\nNota: ", it.text()) }
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
private fun getSeasonEps(seasonElement: Element): List<SEpisode> {
|
||||
val id = seasonElement.attr("data-season-id")
|
||||
val sname = seasonElement.text()
|
||||
val response = episodesClient.newCall(apiRequest("getEpisodes=$id")).execute()
|
||||
val episodes = response.parseAs<EpisodeListDto>().episodes
|
||||
.values
|
||||
.filter { it.released }
|
||||
.map {
|
||||
SEpisode.create().apply {
|
||||
name = "$sname: Ep ${it.name}".run {
|
||||
if (!it.title.contains("Episode ")) {
|
||||
this + " - ${it.title}"
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
episode_number = it.name.toFloatOrNull() ?: 0F
|
||||
url = it.id
|
||||
}
|
||||
}
|
||||
return episodes
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val doc = response.asJsoup()
|
||||
val seasons = doc.select("div.seasons div.list 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("getEpisodeData=$url")
|
||||
}
|
||||
}
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val body = response.body.string()
|
||||
val videoObjectList = if (body.startsWith("{")) {
|
||||
body.parseAs<VideoListDto>().videos.values.toList()
|
||||
} else {
|
||||
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.parallelCatchingFlatMapBlocking(::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 hosters = videoObj.hosters ?: return emptyList()
|
||||
|
||||
val langPrefix = if (videoObj.lang == "1") "LEG" else "DUB"
|
||||
|
||||
return hosters.iterator().flatMap { (name, status) ->
|
||||
if (status != 3) return@flatMap emptyList()
|
||||
val url = getPlayerUrl(videoObj.id, name)
|
||||
when (name) {
|
||||
"mixdrop" -> mixdropExtractor.videosFromUrl(url, langPrefix)
|
||||
"streamtape" -> streamtapeExtractor.videosFromUrl(url, "StreamTape($langPrefix)")
|
||||
"warezcdn" -> warezExtractor.videosFromUrl(url, langPrefix)
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Settings ==============================
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
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(PREF_POPULAR_PAGE_DEFAULT)
|
||||
summary = "%s"
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
key = PREF_PLAYER_KEY
|
||||
title = PREF_PLAYER_TITLE
|
||||
entries = PREF_PLAYER_ARRAY
|
||||
entryValues = PREF_PLAYER_ARRAY
|
||||
setDefaultValue(PREF_PLAYER_DEFAULT)
|
||||
summary = "%s"
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
key = PREF_LANGUAGE_KEY
|
||||
title = PREF_LANGUAGE_TITLE
|
||||
entries = PREF_LANGUAGE_ENTRIES
|
||||
entryValues = PREF_LANGUAGE_VALUES
|
||||
setDefaultValue(PREF_LANGUAGE_DEFAULT)
|
||||
summary = "%s"
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
// ============================= 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 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("$apiUrl/publicFunctions.php", newHeaders, body = reqBody)
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val player = preferences.getString(PREF_PLAYER_KEY, PREF_PLAYER_DEFAULT)!!
|
||||
val language = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!!
|
||||
return sortedWith(
|
||||
compareBy(
|
||||
{ it.quality.contains(player) },
|
||||
{ it.quality.contains(language) },
|
||||
),
|
||||
).reversed()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREF_POPULAR_PAGE_KEY = "pref_popular_page"
|
||||
private const val PREF_POPULAR_PAGE_DEFAULT = "movie"
|
||||
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",
|
||||
)
|
||||
|
||||
private const val PREF_PLAYER_KEY = "pref_player"
|
||||
private const val PREF_PLAYER_DEFAULT = "MixDrop"
|
||||
private const val PREF_PLAYER_TITLE = "Player/Server favorito"
|
||||
private val PREF_PLAYER_ARRAY = arrayOf(
|
||||
"MixDrop",
|
||||
"StreamTape",
|
||||
"WarezCDN",
|
||||
)
|
||||
|
||||
private const val PREF_LANGUAGE_KEY = "pref_language"
|
||||
private const val PREF_LANGUAGE_DEFAULT = "LEG"
|
||||
private const val PREF_LANGUAGE_TITLE = "Língua/tipo preferido"
|
||||
private val PREF_LANGUAGE_ENTRIES = arrayOf("Legendado", "Dublado")
|
||||
private val PREF_LANGUAGE_VALUES = arrayOf("LEG", "DUB")
|
||||
|
||||
const val PREFIX_SEARCH = "path:"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
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.asQueryPart(): String {
|
||||
return (first { it is R } as QueryPartFilter).toQueryPart()
|
||||
}
|
||||
|
||||
class TypeFilter : QueryPartFilter("Tipo", VizerFiltersData.TYPES)
|
||||
class MinYearFilter : QueryPartFilter("Ano (min)", VizerFiltersData.MIN_YEARS)
|
||||
class MaxYearFilter : QueryPartFilter("Ano (max)", VizerFiltersData.MAX_YEARS)
|
||||
class GenreFilter : QueryPartFilter("Categoria", VizerFiltersData.GENRES)
|
||||
|
||||
class SortFilter : AnimeFilter.Sort(
|
||||
"Ordernar por",
|
||||
VizerFiltersData.ORDERS.map { it.first }.toTypedArray(),
|
||||
Selection(0, false),
|
||||
)
|
||||
|
||||
val FILTER_LIST get() = 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",
|
||||
val orderBy: String = "rating",
|
||||
val orderWay: String = "desc",
|
||||
)
|
||||
|
||||
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
|
||||
if (filters.isEmpty()) return FilterSearchParams()
|
||||
|
||||
val sortFilter = filters.firstOrNull { it is SortFilter } as? SortFilter
|
||||
val (orderBy, ascending) = sortFilter?.state?.run {
|
||||
val order = VizerFiltersData.ORDERS[index].second
|
||||
val orderWay = if (ascending) "asc" else "desc"
|
||||
|
||||
Pair(order, orderWay)
|
||||
} ?: Pair("rating", "desc")
|
||||
|
||||
return FilterSearchParams(
|
||||
filters.asQueryPart<TypeFilter>(),
|
||||
filters.asQueryPart<MinYearFilter>(),
|
||||
filters.asQueryPart<MaxYearFilter>(),
|
||||
filters.asQueryPart<GenreFilter>(),
|
||||
orderBy,
|
||||
ascending,
|
||||
)
|
||||
}
|
||||
|
||||
private object VizerFiltersData {
|
||||
val TYPES = arrayOf(
|
||||
Pair("Animes", "anime"),
|
||||
Pair("Filmes", "Movies"),
|
||||
Pair("Series", "Series"),
|
||||
)
|
||||
val MAX_YEARS = (2022 downTo 1890).map {
|
||||
Pair(it.toString(), it.toString())
|
||||
}.toTypedArray()
|
||||
|
||||
val MIN_YEARS = MAX_YEARS.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,92 @@
|
|||
package eu.kanade.tachiyomi.animeextension.pt.vizer.dto
|
||||
|
||||
import eu.kanade.tachiyomi.util.parseAs
|
||||
import kotlinx.serialization.EncodeDefault
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.JsonTransformingSerializer
|
||||
import kotlinx.serialization.json.booleanOrNull
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
|
||||
typealias FakeList<T> = Map<String, T>
|
||||
|
||||
@Serializable
|
||||
class SearchResultDto(
|
||||
val quantity: Int = 0,
|
||||
@EncodeDefault
|
||||
@SerialName("list")
|
||||
val items: FakeList<SearchItemDto> = emptyMap(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class SearchItemDto(
|
||||
val id: String,
|
||||
val title: String,
|
||||
val url: String,
|
||||
@EncodeDefault
|
||||
val status: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class EpisodeListDto(
|
||||
@SerialName("list")
|
||||
val episodes: FakeList<EpisodeItemDto>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class EpisodeItemDto(
|
||||
val id: String,
|
||||
val name: String,
|
||||
@Serializable(with = BooleanSerializer::class)
|
||||
val released: Boolean,
|
||||
val title: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class VideoListDto(
|
||||
@SerialName("list")
|
||||
val videos: FakeList<VideoDto>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
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
|
||||
class HostersDto(
|
||||
val mixdrop: Int = 0,
|
||||
val streamtape: Int = 0,
|
||||
val warezcdn: Int = 0,
|
||||
) {
|
||||
operator fun iterator(): List<Pair<String, Int>> {
|
||||
return listOf(
|
||||
"mixdrop" to mixdrop,
|
||||
"streamtape" to streamtape,
|
||||
"warezcdn" to warezcdn,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object BooleanSerializer : JsonTransformingSerializer<Boolean>(Boolean.serializer()) {
|
||||
override fun transformDeserialize(element: JsonElement): JsonElement {
|
||||
require(element is JsonPrimitive)
|
||||
return if (element.jsonPrimitive.isString) {
|
||||
JsonPrimitive(true)
|
||||
} else {
|
||||
JsonPrimitive(element.jsonPrimitive.booleanOrNull ?: false)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue