src/pt dead sources (#756)

Co-authored-by: Your Name <you@example.com>
This commit is contained in:
krysanify 2025-03-07 04:37:48 +08:00 committed by GitHub
parent e054850545
commit c5aac9174a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 0 additions and 2686 deletions

View file

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".pt.anidong.AniDongUrlActivity"
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="anidong.net"
android:pathPattern="/anime/..*"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -1,7 +0,0 @@
ext {
extName = 'AniDong'
extClass = '.AniDong'
extVersionCode = 2
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

View file

@ -1,264 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.anidong
import eu.kanade.tachiyomi.animeextension.pt.anidong.dto.EpisodeDto
import eu.kanade.tachiyomi.animeextension.pt.anidong.dto.EpisodeListDto
import eu.kanade.tachiyomi.animeextension.pt.anidong.dto.SearchResultDto
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.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import okhttp3.FormBody
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
class AniDong : ParsedAnimeHttpSource() {
override val name = "AniDong"
override val baseUrl = "https://anidong.net"
override val lang = "pt-BR"
override val supportsLatest = true
private val json: Json by injectLazy()
private val apiHeaders by lazy {
headersBuilder() // sets user-agent
.add("Referer", baseUrl)
.add("x-requested-with", "XMLHttpRequest")
.build()
}
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = GET(baseUrl)
override fun popularAnimeSelector() = "article.top10_animes_item > a"
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href"))
title = element.attr("title")
thumbnail_url = element.selectFirst("img")?.attr("src")
}
override fun popularAnimeNextPageSelector() = null
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/lancamentos/page/$page/")
override fun latestUpdatesSelector() = "article.main_content_article > a"
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesNextPageSelector() = "div.paginacao > a.next"
// =============================== Search ===============================
override suspend fun getSearchAnime(page: Int, query: String, filters: AnimeFilterList): AnimesPage {
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
val id = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/anime/$id"))
.awaitSuccess()
.use(::searchAnimeByIdParse)
} else {
super.getSearchAnime(page, query, filters)
}
}
private fun searchAnimeByIdParse(response: Response): AnimesPage {
val details = animeDetailsParse(response.asJsoup()).apply {
setUrlWithoutDomain(response.request.url.toString())
initialized = true
}
return AnimesPage(listOf(details), false)
}
override fun getFilterList() = AniDongFilters.FILTER_LIST
private val nonce by lazy {
client.newCall(GET("$baseUrl/?js_global=1&ver=6.2.2")).execute()
.body.string()
.substringAfter("search_nonce")
.substringAfter("'")
.substringBefore("'")
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = AniDongFilters.getSearchParameters(filters)
val body = FormBody.Builder()
.add("letra", "")
.add("action", "show_animes_ajax")
.add("nome", query)
.add("status", params.status)
.add("formato", params.format)
.add("search_nonce", nonce)
.add("paged", page.toString())
.apply {
params.genres.forEach { add("generos[]", it) }
}.build()
return POST("$baseUrl/wp-admin/admin-ajax.php", headers = apiHeaders, body = body)
}
override fun searchAnimeParse(response: Response): AnimesPage {
val searchData: SearchResultDto = response.body.string()
.takeIf { it.trim() != "402" }
?.let(json::decodeFromString)
?: return AnimesPage(emptyList(), false)
val animes = searchData.animes.map {
SAnime.create().apply {
setUrlWithoutDomain(it.url)
title = it.title
thumbnail_url = it.thumbnail_url
}
}
val hasNextPage = searchData.pages > 1 && searchData.animes.size == 10
return AnimesPage(animes, hasNextPage)
}
override fun searchAnimeSelector(): String {
throw UnsupportedOperationException()
}
override fun searchAnimeFromElement(element: Element): SAnime {
throw UnsupportedOperationException()
}
override fun searchAnimeNextPageSelector(): String? {
throw UnsupportedOperationException()
}
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
val doc = getRealDoc(document)
val infos = doc.selectFirst("div.anime_infos")!!
setUrlWithoutDomain(doc.location())
title = infos.selectFirst("div > h3")!!.ownText()
thumbnail_url = infos.selectFirst("img")?.attr("src")
genre = infos.select("div[itemprop=genre] a").eachText().joinToString()
artist = infos.selectFirst("div[itemprop=productionCompany]")?.text()
status = doc.selectFirst("div:contains(Status) span")?.text().let {
when {
it == null -> SAnime.UNKNOWN
it == "Completo" -> SAnime.COMPLETED
it.contains("Lançamento") -> SAnime.ONGOING
else -> SAnime.UNKNOWN
}
}
description = buildString {
infos.selectFirst("div.anime_name + div.anime_info")?.text()?.also {
append("Nomes alternativos: $it\n")
}
doc.selectFirst("div[itemprop=description]")?.text()?.also {
append("\n$it")
}
}
}
// ============================== Episodes ==============================
override fun episodeListSelector(): String {
throw UnsupportedOperationException()
}
override fun episodeFromElement(element: Element): SEpisode {
throw UnsupportedOperationException()
}
override fun episodeListParse(response: Response): List<SEpisode> {
val doc = getRealDoc(response.asJsoup())
val id = doc.selectFirst("link[rel=shortlink]")!!.attr("href").substringAfter("=")
val body = FormBody.Builder()
.add("action", "show_videos")
.add("anime_id", id)
.build()
val res = client.newCall(POST("$baseUrl/api", apiHeaders, body)).execute()
.body.string()
val data = json.decodeFromString<EpisodeListDto>(res)
return buildList {
data.episodes.forEach { add(episodeFromObject(it, "Episódio")) }
data.movies.forEach { add(episodeFromObject(it, "Filme")) }
data.ovas.forEach { add(episodeFromObject(it, "OVA")) }
sortByDescending { it.episode_number }
}
}
private fun episodeFromObject(episode: EpisodeDto, prefix: String) = SEpisode.create().apply {
setUrlWithoutDomain(episode.epi_url)
episode_number = episode.epi_num.toFloatOrNull() ?: 0F
name = "$prefix ${episode.epi_num}"
}
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val doc = response.asJsoup()
return doc.select("div.player_option").flatMap {
val url = it.attr("data-playerlink")
val playerName = it.text().trim()
videosFromUrl(url, playerName)
}
}
private fun videosFromUrl(url: String, playerName: String): List<Video> {
val scriptData = client.newCall(GET(url, apiHeaders)).execute()
.asJsoup()
.selectFirst("script:containsData(sources)")
?.data() ?: return emptyList()
return scriptData.substringAfter("sources: [").substringBefore("]")
.split("{")
.drop(1)
.map {
val videoUrl = it.substringAfter("file: \"").substringBefore('"')
val label = it.substringAfter("label: \"", "Unknown").substringBefore('"')
val quality = "$playerName - $label"
Video(videoUrl, quality, videoUrl, headers = apiHeaders)
}
}
override fun videoFromElement(element: Element): Video {
throw UnsupportedOperationException()
}
override fun videoListSelector(): String {
throw UnsupportedOperationException()
}
override fun videoUrlParse(document: Document): String {
throw UnsupportedOperationException()
}
// ============================= Utilities ==============================
private fun getRealDoc(document: Document): Document {
if (!document.location().contains("/video/")) return document
return document.selectFirst(".episodioControleItem:has(i.ri-grid-fill)")?.let {
client.newCall(GET(it.attr("href"), headers)).execute()
.asJsoup()
} ?: document
}
companion object {
const val PREFIX_SEARCH = "id:"
}
}

View file

@ -1,124 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.anidong
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object AniDongFilters {
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
}
open class CheckBoxFilterList(name: String, val pairs: Array<Pair<String, String>>) :
AnimeFilter.Group<AnimeFilter.CheckBox>(name, pairs.map { CheckBoxVal(it.first, false) })
private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return (getFirst<R>() as QueryPartFilter).toQueryPart()
}
private inline fun <reified R> AnimeFilterList.getFirst(): R {
return first { it is R } as R
}
private inline fun <reified R> AnimeFilterList.parseCheckbox(
options: Array<Pair<String, String>>,
): List<String> {
return (getFirst<R>() as CheckBoxFilterList).state
.asSequence()
.filter { it.state }
.map { checkbox -> options.find { it.first == checkbox.name }!!.second }
.filter(String::isNotBlank)
.toList()
}
class StatusFilter : QueryPartFilter("Status", AniDongFiltersData.STATUS_LIST)
class FormatFilter : QueryPartFilter("Formato", AniDongFiltersData.FORMAT_LIST)
class GenresFilter : CheckBoxFilterList("Gêneros", AniDongFiltersData.GENRES_LIST)
val FILTER_LIST get() = AnimeFilterList(
StatusFilter(),
FormatFilter(),
GenresFilter(),
)
data class FilterSearchParams(
val status: String = "",
val format: String = "",
val genres: List<String> = emptyList(),
)
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
return FilterSearchParams(
filters.asQueryPart<StatusFilter>(),
filters.asQueryPart<FormatFilter>(),
filters.parseCheckbox<GenresFilter>(AniDongFiltersData.GENRES_LIST),
)
}
private object AniDongFiltersData {
private val SELECT = Pair("<Selecione>", "")
val STATUS_LIST = arrayOf(
SELECT,
Pair("Lançamento", "Lançamento"),
Pair("Completo", "Completo"),
)
val FORMAT_LIST = arrayOf(
SELECT,
Pair("Donghua", "Anime"),
Pair("Filme", "Filme"),
)
val GENRES_LIST = arrayOf(
Pair("Artes Marciais", "9"),
Pair("Aventura", "6"),
Pair("Ação", "2"),
Pair("Boys Love", "43"),
Pair("Comédia", "15"),
Pair("Corrida", "94"),
Pair("Cultivo", "12"),
Pair("Demônios", "18"),
Pair("Detetive", "24"),
Pair("Drama", "16"),
Pair("Escolar", "77"),
Pair("Espaço", "54"),
Pair("Esporte", "95"),
Pair("Fantasia", "7"),
Pair("Guerra", "26"),
Pair("Harém", "17"),
Pair("Histórico", "8"),
Pair("Horror", "44"),
Pair("Isekai", "72"),
Pair("Jogo", "25"),
Pair("Mecha", "40"),
Pair("Militar", "21"),
Pair("Mistério", "3"),
Pair("Mitolgia", "96"),
Pair("Mitologia", "19"),
Pair("O Melhor Donghua", "91"),
Pair("Polícia", "57"),
Pair("Política", "63"),
Pair("Psicológico", "33"),
Pair("Reencarnação", "30"),
Pair("Romance", "11"),
Pair("Sci-Fi", "39"),
Pair("Slice of Life", "84"),
Pair("Sobrenatural", "4"),
Pair("Super Poder", "67"),
Pair("Suspense", "32"),
Pair("Tragédia", "58"),
Pair("Vampiro", "82"),
)
}
}

View file

@ -1,41 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.anidong
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://anidong.net/anime/<item> intents
* and redirects them to the main Aniyomi process.
*/
class AniDongUrlActivity : Activity() {
private val tag = javaClass.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments
if (pathSegments != null && pathSegments.size > 1) {
val item = pathSegments[1]
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.ANIMESEARCH"
putExtra("query", "${AniDong.PREFIX_SEARCH}$item")
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)
}
}

View file

@ -1,53 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.anidong.dto
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 animes: List<AnimeDto>,
@SerialName("total_pages")
val pages: Int,
)
@Serializable
data class AnimeDto(
@SerialName("anime_capa")
val thumbnail_url: String,
@SerialName("anime_permalink")
val url: String,
@SerialName("anime_title")
val title: String,
)
@Serializable
data class EpisodeListDto(
@Serializable(with = EpisodeListSerializer::class)
@SerialName("episodios")
val episodes: List<EpisodeDto>,
@Serializable(with = EpisodeListSerializer::class)
@SerialName("filmes")
val movies: List<EpisodeDto>,
@Serializable(with = EpisodeListSerializer::class)
val ovas: List<EpisodeDto>,
)
@Serializable
data class EpisodeDto(
val epi_num: String,
val epi_url: String,
)
object EpisodeListSerializer :
JsonTransformingSerializer<List<EpisodeDto>>(ListSerializer(EpisodeDto.serializer())) {
override fun transformDeserialize(element: JsonElement): JsonElement =
when (element) {
is JsonObject -> JsonArray(element.values.toList())
else -> JsonArray(emptyList())
}
}

View file

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".pt.flixei.FlixeiUrlActivity"
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="flixei.com"
android:pathPattern="/assistir/..*/..*"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -1,12 +0,0 @@
ext {
extName = 'Flixei'
extClass = '.Flixei'
extVersionCode = 8
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(":lib:streamtape-extractor"))
implementation(project(":lib:mixdrop-extractor"))
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

View file

@ -1,346 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.flixei
import android.app.Application
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.pt.flixei.dto.AnimeDto
import eu.kanade.tachiyomi.animeextension.pt.flixei.dto.ApiResultsDto
import eu.kanade.tachiyomi.animeextension.pt.flixei.dto.EpisodeDto
import eu.kanade.tachiyomi.animeextension.pt.flixei.dto.PlayersDto
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.ParsedAnimeHttpSource
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.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import eu.kanade.tachiyomi.util.parseAs
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "Flixei"
override val baseUrl = "https://flixei.com"
override val lang = "pt-BR"
override val supportsLatest = true
private val json: Json by injectLazy()
private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int): Request {
val body = "slider=3".toFormBody()
return POST("$baseUrl/includes/ajax/home.php", body = body)
}
override fun popularAnimeParse(response: Response): AnimesPage {
val results = response.parseAs<ApiResultsDto<AnimeDto>>()
val animes = results.items.values.map(::parseAnimeFromObject)
return AnimesPage(animes, false)
}
private fun parseAnimeFromObject(anime: AnimeDto) = SAnime.create().apply {
title = anime.title
setUrlWithoutDomain("/assistir/filme/${anime.url}/online/gratis")
thumbnail_url = "$baseUrl/content/movies/posterPt/185/${anime.id}.webp"
}
override fun popularAnimeFromElement(element: Element): SAnime {
throw UnsupportedOperationException()
}
override fun popularAnimeNextPageSelector(): String? {
throw UnsupportedOperationException()
}
override fun popularAnimeSelector(): String {
throw UnsupportedOperationException()
}
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/filmes/estreia/$page")
override fun latestUpdatesSelector() = "div.generalMoviesList > a.gPoster"
override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply {
title = element.selectFirst("div.i span")!!.text()
thumbnail_url = element.selectFirst("img")?.attr("src")
setUrlWithoutDomain(element.attr("abs:href"))
}
override fun latestUpdatesNextPageSelector() = "div.paginationSystem a.next"
// =============================== Search ===============================
override suspend fun getSearchAnime(page: Int, query: String, filters: AnimeFilterList): AnimesPage {
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
val path = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/assistir/$path/online/gratis"))
.awaitSuccess()
.use(::searchAnimeByPathParse)
} else {
super.getSearchAnime(page, query, filters)
}
}
private fun searchAnimeByPathParse(response: Response): AnimesPage {
val details = animeDetailsParse(response.asJsoup()).apply {
setUrlWithoutDomain(response.request.url.toString())
initialized = true
}
return AnimesPage(listOf(details), false)
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
return GET("$baseUrl/pesquisar/$query")
}
override fun searchAnimeSelector() = latestUpdatesSelector()
override fun searchAnimeFromElement(element: Element) = latestUpdatesFromElement(element)
override fun searchAnimeNextPageSelector() = null
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
setUrlWithoutDomain(document.location())
thumbnail_url = document.selectFirst("meta[property=og:image]")?.attr("content")
val container = document.selectFirst("div.moviePresent")!!
with(container) {
title = selectFirst("h2.tit")!!.text()
genre = select("div.genres > span").eachText().joinToString()
author = getInfo("Diretor")
artist = getInfo("Produtoras")
description = buildString {
selectFirst("p")?.text()?.also { append(it + "\n\n") }
getInfo("Título")?.also { append("Título original: $it\n") }
getInfo("Serie de")?.also { append("ano: $it\n") }
getInfo("Elenco")?.also { append("Elenco: $it\n") }
getInfo("Qualidade")?.also { append("Qualidade: $it\n") }
}
}
}
// ============================== Episodes ==============================
private fun getSeasonEps(seasonElement: Element): List<SEpisode> {
val id = seasonElement.attr("data-load-episodes")
val sname = seasonElement.text()
val body = "getEpisodes=$id".toFormBody()
val response = client.newCall(POST("$EMBED_WAREZ_URL/serieAjax.php", body = body)).execute()
val episodes = response.parseAs<ApiResultsDto<EpisodeDto>>().items.values.map {
SEpisode.create().apply {
name = "Temp $sname: Ep ${it.name}"
episode_number = it.name.toFloatOrNull() ?: 0F
url = it.id
}
}
return episodes
}
override fun episodeListParse(response: Response): List<SEpisode> {
val docUrl = response.asJsoup().selectFirst("div#playButton")!!
.attr("onclick")
.substringAfter("'")
.substringBefore("'")
return if (response.request.url.toString().contains("/serie/")) {
client.newCall(GET(docUrl)).execute()
.asJsoup()
.select("div#seasons div.item[data-load-episodes]")
.flatMap(::getSeasonEps)
.reversed()
} else {
listOf(
SEpisode.create().apply {
name = "Filme"
episode_number = 1F
url = "$EMBED_WAREZ_URL/filme/" + docUrl.substringAfter("=")
},
)
}
}
override fun episodeFromElement(element: Element): SEpisode {
throw UnsupportedOperationException()
}
override fun episodeListSelector(): String {
throw UnsupportedOperationException()
}
// ============================ 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 {
POST("$EMBED_WAREZ_URL/serieAjax.php", body = "getAudios=$url".toFormBody())
}
}
override fun videoListParse(response: Response): List<Video> {
val body = response.body.string()
// Pair<Language, Query>
val items = if (body.startsWith("{")) {
val data = json.decodeFromString<ApiResultsDto<PlayersDto>>(body)
data.items.values.flatMap {
val lang = if (it.audio == "1") "LEGENDADO" else "DUBLADO"
it.iterator().mapNotNull { (server, status) ->
if (status == "3") {
Pair(lang, "?id=${it.id}&sv=$server")
} else {
null
}
}
}
} else {
val doc = response.asJsoup(body)
doc.select("div.selectAudioButton").flatMap {
val lang = it.text()
val id = it.attr("data-load-hosts")
doc.select("div[data-load-embed=$id]").map { element ->
lang to "?id=$id&sv=${element.attr("data-load-embed-host")}"
}
}.ifEmpty {
val lang = doc.selectFirst("div.selectAudio > b")!!.text()
.substringBefore("/")
.uppercase()
val id = doc.selectFirst("*[data-load-embed]")!!.attr("data-load-embed")
doc.select("div.buttonLoadHost").map {
lang to "?id=$id&sv=${it.attr("data-load-embed-host")}"
}
}
}
return items.parallelCatchingFlatMapBlocking(::getVideosFromItem)
}
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
private val mixdropExtractor by lazy { MixDropExtractor(client) }
private fun getVideosFromItem(item: Pair<String, String>): List<Video> {
val (lang, query) = item
val headers = headersBuilder().set("referer", WAREZ_URL).build()
val hostUrl = if ("warezcdn" in query) {
"$WAREZ_URL/player/player.php$query"
} else {
client.newCall(GET("$WAREZ_URL/embed/getPlay.php$query", headers))
.execute()
.body.string()
.substringAfter("location.href=\"")
.substringBefore("\";")
}
return when (query.substringAfter("sv=")) {
"streamtape" -> streamtapeExtractor.videosFromUrl(hostUrl, "Streamtape($lang)")
"mixdrop" -> mixdropExtractor.videoFromUrl(hostUrl, lang)
else -> null // TODO: Add warezcdn extractor
}.orEmpty()
}
override fun videoFromElement(element: Element): Video {
throw UnsupportedOperationException()
}
override fun videoListSelector(): String {
throw UnsupportedOperationException()
}
override fun videoUrlParse(document: Document): String {
throw UnsupportedOperationException()
}
// ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
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"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.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"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
}
// ============================= Utilities ==============================
private fun Element.getInfo(item: String) = selectFirst("*:containsOwn($item) b")?.text()
private fun String.toFormBody() = toRequestBody("application/x-www-form-urlencoded".toMediaType())
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 {
const val PREFIX_SEARCH = "path:"
private const val EMBED_WAREZ_URL = "https://embed.warezcdn.net"
private const val WAREZ_URL = "https://warezcdn.com"
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",
)
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")
}
}

View file

@ -1,42 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.flixei
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://flixei.com/assistir/<type>/<item> intents
* and redirects them to the main Aniyomi process.
*/
class FlixeiUrlActivity : Activity() {
private val tag = javaClass.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments
if (pathSegments != null && pathSegments.size > 2) {
val type = pathSegments[1]
val item = pathSegments[2]
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.ANIMESEARCH"
putExtra("query", "${Flixei.PREFIX_SEARCH}$type/$item")
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)
}
}

View file

@ -1,33 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.flixei.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ApiResultsDto<T>(
@SerialName("list")
val items: Map<String, T>,
)
@Serializable
data class AnimeDto(val id: String, val title: String, val url: String)
@Serializable
data class EpisodeDto(val id: String, val name: String)
@Serializable
data class PlayersDto(
val id: String,
val audio: String,
val mixdropStatus: String = "0",
val streamtapeStatus: String = "0",
val warezcdnStatus: String = "0",
) {
operator fun iterator(): List<Pair<String, String>> {
return listOf(
"streamtape" to streamtapeStatus,
"mixdrop" to mixdropStatus,
"warezcdn" to warezcdnStatus,
)
}
}

View file

@ -1,7 +0,0 @@
ext {
extName = 'Lista de Animes'
extClass = '.ListaDeAnimes'
extVersionCode = 2
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View file

@ -1,112 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.listadeanimes
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
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.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
class ListaDeAnimes : ParsedAnimeHttpSource() {
override val name = "Lista de Animes"
override val baseUrl = "https://www.listadeanimes.com"
override val lang = "pt-BR"
override val supportsLatest = false
override fun headersBuilder() = super.headersBuilder()
.add("Referer", baseUrl)
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/page/$page")
override fun popularAnimeSelector() = "article.post.excerpt > div.capa:not(:has(a[href=$baseUrl/anime-lista-online]))"
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
val img = element.selectFirst("img")!!
title = titleCase(img.attr("title").substringBefore(" todos os episódios"))
thumbnail_url = img.attr("data-src")
}
override fun popularAnimeNextPageSelector() = "a.next.page-numbers"
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException()
override fun latestUpdatesSelector(): String = throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element): SAnime = throw UnsupportedOperationException()
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
// =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList) = GET("$baseUrl/page/$page?s=$query")
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
override fun searchAnimeSelector() = popularAnimeSelector()
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector() = popularAnimeNextPageSelector()
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
setUrlWithoutDomain(document.location())
val titleText = document.selectFirst("h1.title.single-title")!!.text()
title = titleCase(titleText.substringBefore(" todos os episódios"))
thumbnail_url = document.selectFirst("img.aligncenter.size-full")?.attr("src")
val infos = document.selectFirst("div#content.post-single-content > center")
val infosText = infos?.run {
html()
.replace("<br>", "\n")
.replace("<b>", "")
.replace("</b>", "")
}?.let { "\n\n$it" }.orEmpty()
val sinopse = document.selectFirst("div#content > *:contains(Sinopse)")?.nextElementSibling()
description = (sinopse?.text() ?: "Sem sinopse.") + infosText
genre = document.select("a[rel=tag]").joinToString { it.text() }
}
// ============================== Episodes ==============================
override fun episodeListSelector() = "div.videos > ul"
override fun episodeListParse(response: Response): List<SEpisode> {
val doc = response.asJsoup()
return doc.select("div.videos > ul > li:gt(0)")
.map(::episodeFromElement)
.reversed()
}
override fun episodeFromElement(element: Element): SEpisode {
return SEpisode.create().apply {
episode_number = runCatching {
element.selectFirst("string")!!
.text()
.substringAfter(" ")
.toFloat()
}.getOrDefault(0F)
name = element.text().substringAfter("")
url = element.attr("id")
}
}
// ============================ Video Links =============================
override suspend fun getVideoList(episode: SEpisode): List<Video> {
return listOf(Video(episode.url, episode.name, episode.url))
}
override fun videoListSelector() = throw UnsupportedOperationException()
override fun videoListRequest(episode: SEpisode) = throw UnsupportedOperationException()
override fun videoListParse(response: Response) = throw UnsupportedOperationException()
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
// ============================= Utilities ==============================
private fun titleCase(str: String): String {
return str.split(' ')
.map { it.replaceFirstChar(Char::uppercase) }
.joinToString(" ")
}
}

View file

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".pt.megaflix.MegaflixUrlActivity"
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="megaflix.co"
android:pathPattern="/..*/..*/"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -1,20 +0,0 @@
ext {
extName = 'Megaflix'
extClass = '.Megaflix'
extVersionCode = 24
isNsfw = true
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:mixdrop-extractor'))
implementation(project(":lib:streamtape-extractor"))
implementation(project(":lib:voe-extractor"))
implementation(project(":lib:filemoon-extractor"))
implementation(project(":lib:vidhide-extractor"))
implementation(project(":lib:streamwish-extractor"))
implementation(project(":lib:playlist-utils"))
// for mixdrop and megaflix
implementation(libs.jsunpacker)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

View file

@ -1,317 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.megaflix
import android.app.Application
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.pt.megaflix.extractors.MegaflixExtractor
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.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.vidhideextractor.VidHideExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import eu.kanade.tachiyomi.util.parallelFlatMapBlocking
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class Megaflix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "Megaflix"
override val baseUrl = "https://megaflix.ac"
override val lang = "pt-BR"
override val supportsLatest = true
override fun headersBuilder() = super.headersBuilder().add("Referer", "$baseUrl/")
private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = GET(baseUrl)
override fun popularAnimeSelector() = "section#widget_list_movies_series-5 li > article"
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
title = element.selectFirst("h2.entry-title")!!.text()
setUrlWithoutDomain(element.selectFirst("a.lnk-blk")!!.attr("href"))
thumbnail_url = element.selectFirst("img")?.absUrl("src")
}
override fun popularAnimeNextPageSelector() = null
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request {
val pageType = preferences.getString(PREF_LATEST_PAGE_KEY, PREF_LATEST_PAGE_DEFAULT)!!
return GET("$baseUrl/$pageType/page/$page")
}
override fun latestUpdatesSelector() = "li > article"
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesNextPageSelector() = "div.nav-links > a:containsOwn(PRÓXIMO)"
// =============================== Search ===============================
override suspend fun getSearchAnime(
page: Int,
query: String,
filters: AnimeFilterList,
): AnimesPage {
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
val path = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/$path"))
.awaitSuccess()
.use(::searchAnimeByPathParse)
} else {
super.getSearchAnime(page, query, filters)
}
}
private fun searchAnimeByPathParse(response: Response): AnimesPage {
val details = animeDetailsParse(response.asJsoup()).apply {
setUrlWithoutDomain(response.request.url.toString())
initialized = true
}
return AnimesPage(listOf(details), false)
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
return if (query.isNotBlank()) {
GET("$baseUrl/page/$page/?s=$query")
} else {
val genre = MegaflixFilters.getGenre(filters)
GET("$baseUrl/categoria/$genre/page/$page")
}
}
override fun searchAnimeSelector() = latestUpdatesSelector()
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector() = latestUpdatesNextPageSelector()
override fun getFilterList() = MegaflixFilters.FILTER_LIST
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
setUrlWithoutDomain(document.location())
val infos = document.selectFirst("div.bd > article.post.single")!!
title = infos.selectFirst("h1.entry-title")!!.text()
thumbnail_url = infos.selectFirst("img")?.absUrl("src")
genre = infos.select("span.genres > a").eachText().joinToString()
description = infos.selectFirst("div.description")?.text()
}
// ============================== Episodes ==============================
override fun episodeListSelector() = "li > article.episodes"
private fun seasonListSelector() = "section.episodes div.choose-season > a"
override fun episodeListParse(response: Response): List<SEpisode> {
val doc = response.asJsoup()
val seasons = doc.select(seasonListSelector())
return when {
seasons.isEmpty() -> listOf(
SEpisode.create().apply {
name = "Filme"
setUrlWithoutDomain(doc.location())
episode_number = 1F
},
)
else -> seasons.parallelFlatMapBlocking(::episodesFromSeason).reversed()
}
}
private suspend fun episodesFromSeason(seasonElement: Element): List<SEpisode> {
return seasonElement.attr("href").let { url ->
client.newCall(GET(url, headers)).await()
.asJsoup()
.select(episodeListSelector())
.map(::episodeFromElement)
}
}
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
name = element.selectFirst("h2.entry-title")!!.text()
setUrlWithoutDomain(element.selectFirst("a.lnk-blk")!!.attr("href"))
episode_number = element.selectFirst("span.num-epi")?.run {
text().split("x").last().toFloatOrNull() ?: 0F
} ?: 0F
}
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val items = response.asJsoup().select(videoListSelector())
return items
.parallelCatchingFlatMapBlocking { element ->
val language = element.text().substringAfter("-")
val id = element.attr("href")
val url = element.closest("body")?.selectFirst("div$id iframe")
?.let {
val iframeUrl = it.attr("src")
client.newCall(GET(iframeUrl, headers))
.execute()
.asJsoup()
.selectFirst("iframe")
?.attr("src")
} ?: return@parallelCatchingFlatMapBlocking emptyList()
getVideoList(url, language)
}
}
/*--------------------------------Video extractors------------------------------------*/
private val webViewResolver by lazy { WebViewResolver() }
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
private val mixdropExtractor by lazy { MixDropExtractor(client) }
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
private val megaflixExtractor by lazy { MegaflixExtractor(client, headers) }
private val voeExtractor by lazy { VoeExtractor(client) }
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
private val vidHideExtractor by lazy { VidHideExtractor(client, headers) }
private val streamWishExtractor by lazy { StreamWishExtractor(client, headers) }
private fun getVideoList(url: String, language: String): List<Video> {
return when {
arrayOf("mixdrop", "mixdroop", "mix").any(url) -> mixdropExtractor.videoFromUrl(url, language)
arrayOf("streamtape", "stp", "stape").any(url) -> streamtapeExtractor.videosFromUrl(url, "StreamTape - $language")
arrayOf("mflix").any(url) -> megaflixExtractor.videosFromUrl(url, language)
arrayOf("voe").any(url) -> voeExtractor.videosFromUrl(url, "$language ")
arrayOf("filemoon", "moonplayer").any(url) -> filemoonExtractor.videosFromUrl(url, prefix = "$language Filemoon:")
arrayOf("vidhide", "vid.").any(url) -> vidHideExtractor.videosFromUrl(url, videoNameGen = { "$language VidHide:$it" })
arrayOf("wishembed", "streamwish", "strwish", "wish", "jwplayerhls").any(url) -> streamWishExtractor.videosFromUrl(url, videoNameGen = { "$language StreamWish:$it" })
arrayOf("fembedder").any(url) -> {
val webViewResult = webViewResolver.getUrl(url, headers)
if (webViewResult.isBlank()) {
return emptyList()
}
return if (webViewResult.contains("m3u8")) {
playlistUtils.extractFromHls(webViewResult)
} else {
listOf(Video(url, "Default", url))
}
}
else -> emptyList()
}
}
override fun videoListSelector() = "aside.video-options li a"
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
// ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = PREF_QUALITY_TITLE
entries = PREF_QUALITY_ENTRIES
entryValues = PREF_QUALITY_ENTRIES
setDefaultValue(PREF_QUALITY_DEFAULT)
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()
}
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_LANGUAGE_KEY
title = PREF_LANGUAGE_TITLE
entries = PREF_LANGUAGE_VALUES
entryValues = PREF_LANGUAGE_VALUES
setDefaultValue(PREF_LANGUAGE_DEFAULT)
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()
}
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_LATEST_PAGE_KEY
title = PREF_LATEST_PAGE_TITLE
entries = PREF_LATEST_PAGE_ENTRIES
entryValues = PREF_LATEST_PAGE_VALUES
setDefaultValue(PREF_LATEST_PAGE_DEFAULT)
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()
}
}.also(screen::addPreference)
}
// ============================= Utilities ==============================
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val lang = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!!
return sortedWith(
compareBy(
{ it.quality.contains(quality) },
{ it.quality.contains(lang) },
),
).reversed()
}
private fun Array<String>.any(url: String): Boolean = this.any { url.contains(it, ignoreCase = true) }
companion object {
const val PREFIX_SEARCH = "path:"
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_TITLE = "Qualidade preferida"
private const val PREF_QUALITY_DEFAULT = "720p"
private val PREF_QUALITY_ENTRIES = arrayOf("360p", "480p", "720p", "1080p")
private const val PREF_LANGUAGE_KEY = "pref_language"
private const val PREF_LANGUAGE_DEFAULT = "Legendado"
private const val PREF_LANGUAGE_TITLE = "Língua/tipo preferido"
private val PREF_LANGUAGE_VALUES = arrayOf("Legendado", "Dublado")
private const val PREF_LATEST_PAGE_KEY = "pref_latest_page"
private const val PREF_LATEST_PAGE_DEFAULT = "series"
private const val PREF_LATEST_PAGE_TITLE = "Página de últimos adicionados"
private val PREF_LATEST_PAGE_ENTRIES = arrayOf(
"Filmes",
"Séries",
)
private val PREF_LATEST_PAGE_VALUES = arrayOf(
"filmes",
"series",
)
}
}

View file

@ -1,59 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.megaflix
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object MegaflixFilters {
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 GenreFilter : QueryPartFilter("Gênero", MegaflixFiltersData.GENRES)
val FILTER_LIST get() = AnimeFilterList(
AnimeFilter.Header(MegaflixFiltersData.IGNORE_SEARCH_MSG),
GenreFilter(),
)
fun getGenre(filters: AnimeFilterList) = filters.asQueryPart<GenreFilter>()
private object MegaflixFiltersData {
const val IGNORE_SEARCH_MSG = "NOTA: O filtro é IGNORADO ao usar a pesquisa."
val GENRES = arrayOf(
Pair("Animação", "animacao"),
Pair("Aventura", "aventura"),
Pair("Ação", "acao"),
Pair("Biografia", "biografia"),
Pair("Comédia", "comedia"),
Pair("Crime", "crime"),
Pair("Documentário", "documentario"),
Pair("Drama", "drama"),
Pair("Esporte", "esporte"),
Pair("Família", "familia"),
Pair("Fantasia", "fantasia"),
Pair("Faroeste", "faroeste"),
Pair("Ficção científica", "ficcao-cientifica"),
Pair("Guerra", "guerra"),
Pair("História", "historia"),
Pair("Mistério", "misterio"),
Pair("Musical", "musical"),
Pair("Música", "musica"),
Pair("Romance", "romance"),
Pair("Show", "show"),
Pair("Terror", "terror"),
Pair("Thriller", "thriller"),
)
}
}

View file

@ -1,41 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.megaflix
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://megaflix.ac/<type>/<item> intents
* and redirects them to the main Aniyomi process.
*/
class MegaflixUrlActivity : Activity() {
private val tag = javaClass.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments
if (pathSegments != null && pathSegments.size > 1) {
val path = "${pathSegments[0]}/${pathSegments[1]}"
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.ANIMESEARCH"
putExtra("query", "${Megaflix.PREFIX_SEARCH}$path")
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)
}
}

View file

@ -1,69 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.megaflix
import android.annotation.SuppressLint
import android.app.Application
import android.os.Handler
import android.os.Looper
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 context: Application by injectLazy()
private val handler by lazy { Handler(Looper.getMainLooper()) }
@SuppressLint("SetJavaScriptEnabled")
fun getUrl(origRequestUrl: String, origRequestheader: Headers): String {
val latch = CountDownLatch(1)
var webView: WebView? = null
var resultUrl = ""
val headers = origRequestheader.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
handler.post {
val webview = WebView(context)
webView = webview
with(webview.settings) {
javaScriptEnabled = true
domStorageEnabled = true
databaseEnabled = true
useWideViewPort = false
loadWithOverviewMode = false
userAgentString = origRequestheader["User-Agent"]
}
webview.webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest,
): WebResourceResponse? {
val url = request.url.toString()
if (VIDEO_REGEX.containsMatchIn(url)) {
resultUrl = url
latch.countDown()
}
return super.shouldInterceptRequest(view, request)
}
}
webView?.loadUrl(origRequestUrl, headers)
}
latch.await(TIMEOUT_SEC, TimeUnit.SECONDS)
handler.post {
webView?.stopLoading()
webView?.destroy()
webView = null
}
return resultUrl
}
companion object {
const val TIMEOUT_SEC: Long = 20
private val VIDEO_REGEX by lazy { Regex("\\.(mp4|m3u8)") }
}
}

View file

@ -1,28 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.megaflix.extractors
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 okhttp3.Headers
import okhttp3.OkHttpClient
class MegaflixExtractor(private val client: OkHttpClient, private val headers: Headers) {
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
fun videosFromUrl(url: String, lang: String = ""): List<Video> {
val unpacked = client.newCall(GET(url, headers)).execute()
.body.string()
.let(JsUnpacker::unpackAndCombine)
?.replace("\\", "")
?: return emptyList()
val playlistUrl = unpacked.substringAfter("file':'").substringBefore("'")
return playlistUtils.extractFromHls(
playlistUrl,
"https://megaflix.ac",
videoNameGen = { "Megaflix($lang) - $it" },
)
}
}

View file

@ -1,19 +0,0 @@
ext {
extName = 'Pobreflix'
extClass = '.Pobreflix'
themePkg = 'dooplay'
baseUrl = 'https://pobreflix.global'
overrideVersionCode = 18
isNsfw = true
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(":lib:filemoon-extractor"))
implementation(project(':lib:fireplayer-extractor'))
implementation(project(":lib:streamwish-extractor"))
implementation(project(":lib:streamtape-extractor"))
implementation(project(":lib:playlist-utils"))
implementation(libs.jsunpacker)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

View file

@ -1,80 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.pobreflix
import android.util.Base64
import eu.kanade.tachiyomi.animeextension.pt.pobreflix.extractors.MyStreamExtractor
import eu.kanade.tachiyomi.animeextension.pt.pobreflix.extractors.PlayerFlixExtractor
import eu.kanade.tachiyomi.animeextension.pt.pobreflix.extractors.SuperFlixExtractor
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.fireplayerextractor.FireplayerExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Response
class Pobreflix : DooPlay(
"pt-BR",
"Pobreflix",
"https://pobreflix.global",
) {
// ============================== Popular ===============================
override fun popularAnimeSelector() = "div.featured div.poster"
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/series/page/$page/", headers)
// ============================ Video Links =============================
private val fireplayerExtractor by lazy { FireplayerExtractor(client) }
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
private val mystreamExtractor by lazy { MyStreamExtractor(client, headers) }
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
private val streamwishExtractor by lazy { StreamWishExtractor(client, headers) }
private val playerflixExtractor by lazy { PlayerFlixExtractor(client, headers, ::genericExtractor) }
private val superflixExtractor by lazy { SuperFlixExtractor(client, headers, ::genericExtractor) }
private val supercdnExtractor by lazy { SuperFlixExtractor(client, headers, ::genericExtractor, "https://supercdn.org") }
override fun videoListParse(response: Response): List<Video> {
val doc = response.asJsoup()
return doc.select("div.source-box > a").parallelCatchingFlatMapBlocking {
val data = it.attr("href").trim().toHttpUrl().queryParameter("auth")
?.let { Base64.decode(it, Base64.DEFAULT) }
?.let(::String)
?: return@parallelCatchingFlatMapBlocking emptyList()
val url = data.replace("\\", "").substringAfter("url\":\"").substringBefore('"')
genericExtractor(url)
}
}
private fun genericExtractor(url: String, language: String = ""): List<Video> {
val langSubstr = if (language.isBlank()) "" else "[$language] "
return when {
url.contains("superflix") ->
superflixExtractor.videosFromUrl(url)
url.contains("supercdn") ->
supercdnExtractor.videosFromUrl(url)
url.contains("filemoon") ->
filemoonExtractor.videosFromUrl(url, "${langSubstr}Filemoon - ", headers = headers)
url.contains("watch.brplayer") || url.contains("/watch?v=") ->
mystreamExtractor.videosFromUrl(url, language)
url.contains("brbeast") ->
fireplayerExtractor.videosFromUrl(url = url, videoNameGen = { "${langSubstr}BrBeast - $it" })
url.contains("embedplayer") ->
fireplayerExtractor.videosFromUrl(url = url, videoNameGen = { "${langSubstr}EmbedPlayer - $it" })
url.contains("superembeds") ->
fireplayerExtractor.videosFromUrl(url = url, videoNameGen = { "${langSubstr}SuperEmbeds - $it" })
url.contains("streamtape") ->
streamtapeExtractor.videosFromUrl(url, "${langSubstr}Streamtape")
url.contains("filelions") ->
streamwishExtractor.videosFromUrl(url, videoNameGen = { "${langSubstr}FileLions - $it" })
url.contains("streamwish") ->
streamwishExtractor.videosFromUrl(url, videoNameGen = { "${langSubstr}Streamwish - $it" })
url.contains("playerflix") ->
playerflixExtractor.videosFromUrl(url)
else -> emptyList()
}
}
}

View file

@ -1,48 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.pobreflix.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import eu.kanade.tachiyomi.network.GET
import okhttp3.Headers
import okhttp3.OkHttpClient
// From animeworldindia
class MyStreamExtractor(private val client: OkHttpClient, private val headers: Headers) {
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
fun videosFromUrl(url: String, language: String): List<Video> {
val host = url.substringBefore("/watch?")
val response = client.newCall(GET(url, headers)).execute()
val body = response.body.string()
val codePart = body
.substringAfter("sniff(") // Video function
.substringBefore(",[")
val streamCode = codePart
.substringAfterLast(",\"") // our beloved hash
.substringBefore('"')
val id = codePart.substringAfter(",\"").substringBefore('"') // required ID
val streamUrl = "$host/m3u8/$id/$streamCode/master.txt?s=1&cache=1"
val cookie = response.headers.firstOrNull {
it.first.startsWith("set-cookie", true) && it.second.startsWith("PHPSESSID", true)
}?.second?.substringBefore(";") ?: ""
val newHeaders = headers.newBuilder()
.set("cookie", cookie)
.set("accept", "*/*")
.build()
return playlistUtils.extractFromHls(
streamUrl,
masterHeaders = newHeaders,
videoHeaders = newHeaders,
videoNameGen = { "[$language] MyStream: $it" },
)
}
}

View file

@ -1,35 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.pobreflix.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import okhttp3.Headers
import okhttp3.OkHttpClient
class PlayerFlixExtractor(
private val client: OkHttpClient,
private val defaultHeaders: Headers,
private val genericExtractor: (String, String) -> List<Video>,
) {
fun videosFromUrl(url: String): List<Video> {
val doc = client.newCall(GET(url, defaultHeaders)).execute().asJsoup()
val items = doc.select("#hostList div.buttonLoadHost").mapNotNull {
val url = it.attr("onclick")
.substringAfter('"', "")
.substringBefore('"')
?: return@mapNotNull null
val language = if (it.hasClass("hostDub")) {
"Dublado"
} else {
"Legendado"
}
language to url // (Language, videoId)
}
return items.parallelCatchingFlatMapBlocking { genericExtractor(it.second, it.first) }
}
}

View file

@ -1,134 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.pobreflix.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import uy.kohesive.injekt.injectLazy
class SuperFlixExtractor(
private val client: OkHttpClient,
private val defaultHeaders: Headers,
private val genericExtractor: (String, String) -> List<Video>,
private val host: String = "https://superflixapi.dev",
) {
private val json: Json by injectLazy()
fun videosFromUrl(url: String): List<Video> {
val links = linksFromUrl(url)
val fixedLinks = links.parallelCatchingFlatMapBlocking {
val (language, linkUrl) = it
when {
linkUrl.contains("?vid=") -> linksFromPlayer(linkUrl, language)
else -> listOf(it)
}
}
return fixedLinks.parallelCatchingFlatMapBlocking { genericExtractor(it.second, it.first) }
}
private suspend fun linksFromPlayer(url: String, language: String): List<Pair<String, String>> {
val httpUrl = url.toHttpUrl()
val id = httpUrl.queryParameter("vid")!!
val headers = defaultHeaders.newBuilder()
.set("referer", "$host/")
.set("origin", host)
.build()
val doc = client.newCall(GET(url, headers)).await().asJsoup()
val baseUrl = "https://" + httpUrl.host
val apiUrl = "$baseUrl/ajax_sources.php"
val apiHeaders = headers.newBuilder()
.set("referer", url)
.set("origin", baseUrl)
.set("X-Requested-With", "XMLHttpRequest")
.build()
return doc.select("ul > li[data-order-value]").mapNotNull {
val name = it.attr("data-dropdown-value")
val order = it.attr("data-order-value")
val formBody = FormBody.Builder()
.add("vid", id)
.add("alternative", name)
.add("ord", order)
.build()
val req = client.newCall(POST(apiUrl, apiHeaders, formBody)).await()
.body.string()
runCatching {
val iframeUrl = json.decodeFromString<PlayerLinkDto>(req).iframe!!
val iframeServer = iframeUrl.toHttpUrl().queryParameter("sv")!!
language to when (name) {
"1xbet" -> "https://watch.brplayer.site/watch?v=${iframeServer.trim('/')}"
else -> iframeServer
}
}.getOrNull()
}
}
@Serializable
data class PlayerLinkDto(val iframe: String? = null)
private fun linksFromUrl(url: String): List<Pair<String, String>> {
val doc = client.newCall(GET(url, defaultHeaders)).execute().asJsoup()
val items = doc.select("div.select_languages").mapNotNull {
val id = it.nextElementSibling()
?.selectFirst("div[data-id]")
?.attr("data-id")
?: return@mapNotNull null
val language = it.text()
.replace("Disponível", "")
.replace("disponível", "")
.replace("Apenas", "")
.replace("em", "")
.trim()
language to id // (Language, videoId)
}
val headers = defaultHeaders.newBuilder()
.set("Origin", host)
.set("Referer", url)
.set("X-Requested-With", "XMLHttpRequest")
.build()
return items.mapNotNull {
runCatching {
it.first to getLink(it.second, headers)!!
}.getOrNull()
}
}
private fun getLink(id: String, headers: Headers): String? {
val body = FormBody.Builder()
.add("action", "getPlayer")
.add("video_id", id)
.build()
val res = client.newCall(POST("$host/api", headers, body)).execute()
.body.string()
return json.decodeFromString<ApiResponseDto>(res).data?.video_url
}
@Serializable
data class ApiResponseDto(val data: DataDto? = null)
@Serializable
data class DataDto(val video_url: String? = null)
}

View file

@ -1,23 +0,0 @@
<?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>

View file

@ -1,16 +0,0 @@
ext {
extName = 'Vizer.tv'
extClass = '.Vizer'
extVersionCode = 24
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(libs.jsunpacker)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View file

@ -1,359 +0,0 @@
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.interceptor.WebViewResolver
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.fireplayerextractor.FireplayerExtractor
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.parallelCatchingFlatMap
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://novizer.com"
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)
}
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)!!
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).apply {
setUrlWithoutDomain(response.request.url.toString())
initialized = true
}
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 === true }
.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 fireplayerExtractor by lazy { FireplayerExtractor(client) }
private suspend fun getVideosFromObject(videoObj: VideoDto): List<Video> {
val hosters = videoObj.hosters ?: return emptyList()
val langPrefix = if (videoObj.lang == "1") "LEG" else "DUB"
return hosters.iterator().parallelCatchingFlatMap { (name, status) ->
// Always try the warezcdn
if (status != 3 && name != "warezcdn") return@parallelCatchingFlatMap emptyList()
val url = getPlayerUrl(videoObj.id, name)
if (url.isNullOrBlank()) {
return@parallelCatchingFlatMap emptyList()
}
when (name) {
"mixdrop" -> mixdropExtractor.videosFromUrl(url, langPrefix)
"streamtape" -> streamtapeExtractor.videosFromUrl(url, "StreamTape($langPrefix)")
"warezcdn" -> fireplayerExtractor.videosFromUrl(url, videoNameGen = { "WarezCDN($langPrefix) - $it" })
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? {
return webViewResolver.getUrl("$baseUrl/embed/getEmbed.php?id=$id&sv=$name", "$baseUrl/termos")
}
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:"
}
}

View file

@ -1,116 +0,0 @@
package eu.kanade.tachiyomi.animeextension.pt.vizer
import eu.kanade.tachiyomi.animeextension.pt.vizer.VizerFilters.VizerFiltersData.CURRENT_YEAR
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import java.util.Calendar
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 = CURRENT_YEAR.toString(),
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 CURRENT_YEAR by lazy {
Calendar.getInstance()[Calendar.YEAR]
}
val MAX_YEARS = (CURRENT_YEAR 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"),
)
}
}

View file

@ -1,42 +0,0 @@
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)
}
}

View file

@ -1,92 +0,0 @@
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 && element.jsonPrimitive.content == "true") {
JsonPrimitive(true)
} else {
JsonPrimitive(element.jsonPrimitive.booleanOrNull ?: false)
}
}
}

View file

@ -1,78 +0,0 @@
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/") }
}
}