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