fix(src/es): AnimeFenix fixes and updates #729

Merged
imper1aldev merged 1 commit from animefenix into main 2025-03-02 08:47:44 -06:00
6 changed files with 261 additions and 309 deletions

View file

@ -0,0 +1,7 @@
plugins {
id("lib-android")
}
dependencies {
implementation(project(":lib:playlist-utils"))
}

View file

@ -0,0 +1,41 @@
package eu.kanade.tachiyomi.lib.amazonextractor
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
class AmazonExtractor(private val client: OkHttpClient) {
private val playlistUtils by lazy { PlaylistUtils(client) }
fun videosFromUrl(url: String, prefix: String = ""): List<Video> {
if (url.contains("disable", true)) return emptyList()
val document = client.newCall(GET(url)).execute().asJsoup()
val shareIdScript = document.select("script:containsData(var shareId)").firstOrNull()?.data()
if (shareIdScript.isNullOrBlank()) return emptyList()
val shareId = shareIdScript.substringAfter("shareId = \"").substringBefore("\"")
val amazonApiJsonUrl = "https://www.amazon.com/drive/v1/shares/$shareId?resourceVersion=V2&ContentType=JSON&asset=ALL"
val amazonApiJson = client.newCall(GET(amazonApiJsonUrl)).execute().asJsoup()
val epId = amazonApiJson.toString().substringAfter("\"id\":\"").substringBefore("\"")
val amazonApiUrl = "https://www.amazon.com/drive/v1/nodes/$epId/children?resourceVersion=V2&ContentType=JSON&limit=200&sort=%5B%22kind+DESC%22%2C+%22modifiedDate+DESC%22%5D&asset=ALL&tempLink=true&shareId=$shareId"
val amazonApi = client.newCall(GET(amazonApiUrl)).execute().asJsoup()
val videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":").substringAfter("tempLink\":\"").substringBefore("\"")
val serverName = if (videoUrl.contains("&ext=es")) "AmazonES" else "Amazon"
return if (videoUrl.contains(".m3u8")) {
playlistUtils.extractFromHls(videoUrl, videoNameGen = { "${prefix}$serverName:$it" })
} else {
listOf(Video(videoUrl, "${prefix}$serverName", videoUrl))
}
}
}

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Animefenix' extName = 'Animefenix'
extClass = '.Animefenix' extClass = '.Animefenix'
extVersionCode = 55 extVersionCode = 56
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"
@ -17,9 +17,9 @@ dependencies {
implementation(project(':lib:filemoon-extractor')) implementation(project(':lib:filemoon-extractor'))
implementation(project(':lib:voe-extractor')) implementation(project(':lib:voe-extractor'))
implementation(project(':lib:streamlare-extractor')) implementation(project(':lib:streamlare-extractor'))
implementation(project(':lib:fastream-extractor'))
implementation(project(':lib:dood-extractor')) implementation(project(':lib:dood-extractor'))
implementation(project(':lib:upstream-extractor')) implementation(project(':lib:upstream-extractor'))
implementation(project(':lib:streamhidevid-extractor')) implementation(project(':lib:streamhidevid-extractor'))
implementation(project(':lib:universal-extractor')) implementation(project(':lib:universal-extractor'))
implementation(project(':lib:amazon-extractor'))
} }

View file

@ -12,30 +12,6 @@ object AnimeFenixFilters {
fun toQueryPart(name: String) = vals[state].second.takeIf { it.isNotEmpty() }?.let { "&$name=${vals[state].second}" } ?: run { "" } fun toQueryPart(name: String) = vals[state].second.takeIf { it.isNotEmpty() }?.let { "&$name=${vals[state].second}" } ?: run { "" }
} }
open class CheckBoxFilterList(name: String, values: List<CheckBox>) : AnimeFilter.Group<AnimeFilter.CheckBox>(name, values)
private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
private inline fun <reified R> AnimeFilterList.parseCheckbox(
options: Array<Pair<String, String>>,
name: String,
): String {
return (this.getFirst<R>() as CheckBoxFilterList).state
.mapNotNull { checkbox ->
if (checkbox.state) {
options.find { it.first == checkbox.name }!!.second
} else {
null
}
}.joinToString("&$name[]=").let {
if (it.isBlank()) {
""
} else {
"&$name[]=$it"
}
}
}
private inline fun <reified R> AnimeFilterList.asQueryPart(name: String): String { private inline fun <reified R> AnimeFilterList.asQueryPart(name: String): String {
return (this.getFirst<R>() as QueryPartFilter).toQueryPart(name) return (this.getFirst<R>() as QueryPartFilter).toQueryPart(name)
} }
@ -51,111 +27,118 @@ object AnimeFenixFilters {
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams { internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams() if (filters.isEmpty()) return FilterSearchParams()
return FilterSearchParams( return FilterSearchParams(
filters.parseCheckbox<GenresFilter>(AnimeFenixFiltersData.GENRES, "genero") + filters.asQueryPart<StateFilter>("estado") +
filters.parseCheckbox<YearsFilter>(AnimeFenixFiltersData.YEARS, "year") + filters.asQueryPart<TypesFilter>("tipo") +
filters.parseCheckbox<TypesFilter>(AnimeFenixFiltersData.TYPES, "type") + filters.asQueryPart<GenresFilter>("genero") +
filters.parseCheckbox<StateFilter>(AnimeFenixFiltersData.STATE, "estado") + filters.asQueryPart<YearsFilter>("estreno") +
filters.asQueryPart<SortFilter>("order"), filters.asQueryPart<LangFilter>("idioma"),
) )
} }
val FILTER_LIST get() = AnimeFilterList( val FILTER_LIST get() = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"), AnimeFilter.Header("La busqueda por texto ignora el filtro"),
StateFilter(),
TypesFilter(),
GenresFilter(), GenresFilter(),
YearsFilter(), YearsFilter(),
TypesFilter(), LangFilter(),
StateFilter(),
SortFilter(),
) )
class GenresFilter : CheckBoxFilterList("Género", AnimeFenixFiltersData.GENRES.map { CheckBoxVal(it.first, false) }) class StateFilter : QueryPartFilter("Estado", AnimeFenixFiltersData.STATE)
class TypesFilter : QueryPartFilter("Tipo", AnimeFenixFiltersData.TYPES)
class YearsFilter : CheckBoxFilterList("Año", AnimeFenixFiltersData.YEARS.map { CheckBoxVal(it.first, false) }) class GenresFilter : QueryPartFilter("Género", AnimeFenixFiltersData.GENRES)
class YearsFilter : QueryPartFilter("Año", AnimeFenixFiltersData.YEARS)
class TypesFilter : CheckBoxFilterList("Tipo", AnimeFenixFiltersData.TYPES.map { CheckBoxVal(it.first, false) }) class LangFilter : QueryPartFilter("Idioma", AnimeFenixFiltersData.LANGUAGE)
class StateFilter : CheckBoxFilterList("Estado", AnimeFenixFiltersData.STATE.map { CheckBoxVal(it.first, false) })
class SortFilter : QueryPartFilter("Orden", AnimeFenixFiltersData.SORT)
private object AnimeFenixFiltersData { private object AnimeFenixFiltersData {
val YEARS = (1990..Calendar.getInstance().get(Calendar.YEAR)).map { Pair("$it", "$it") }.reversed().toTypedArray() val STATE = arrayOf(
Pair("Todos", ""),
Pair("Emisión", "2"),
Pair("Finalizado", "1"),
Pair("Próximamente", "3"),
)
val TYPES = arrayOf( val TYPES = arrayOf(
Pair("TV", "tv"), Pair("Todos", ""),
Pair("Película", "movie"), Pair("TV", "1"),
Pair("Especial", "special"), Pair("Película", "2"),
Pair("OVA", "ova"), Pair("OVA", "3"),
Pair("DONGHUA", "donghua"), Pair("Especial", "4"),
) Pair("Serie", "9"),
Pair("Dorama", "11"),
val STATE = arrayOf( Pair("Corto", "14"),
Pair("Emisión", "1"), Pair("Donghua", "15"),
Pair("Finalizado", "2"), Pair("ONA", "16"),
Pair("Próximamente", "3"), Pair("Live Action", "17"),
Pair("En Cuarentena", "4"), Pair("Manhwa", "18"),
) Pair("Teatral", "19"),
val SORT = arrayOf(
Pair("Por Defecto", "default"),
Pair("Recientemente Actualizados", "updated"),
Pair("Recientemente Agregados", "added"),
Pair("Nombre A-Z", "title"),
Pair("Calificación", "likes"),
Pair("Más Vistos", "visits"),
) )
val GENRES = arrayOf( val GENRES = arrayOf(
Pair("Acción", "accion"), Pair("Todos", ""),
Pair("Ángeles", "angeles"), Pair("Acción", "1"),
Pair("Artes Marciales", "artes-marciales"), Pair("Escolares", "2"),
Pair("Aventura", "aventura"), Pair("Romance", "3"),
Pair("Ciencia Ficción", "Ciencia Ficción"), Pair("Shoujo", "4"),
Pair("Comedia", "comedia"), Pair("Comedia", "5"),
Pair("Cyberpunk", "cyberpunk"), Pair("Drama", "6"),
Pair("Demonios", "demonios"), Pair("Seinen", "7"),
Pair("Deportes", "deportes"), Pair("Deportes", "8"),
Pair("Dragones", "dragones"), Pair("Shounen", "9"),
Pair("Drama", "drama"), Pair("Recuentos de la vida", "10"),
Pair("Ecchi", "ecchi"), Pair("Ecchi", "11"),
Pair("Escolares", "escolares"), Pair("Sobrenatural", "12"),
Pair("Fantasía", "fantasia"), Pair("Fantasía", "13"),
Pair("Gore", "gore"), Pair("Magia", "14"),
Pair("Harem", "harem"), Pair("Superpoderes", "15"),
Pair("Histórico", "historico"), Pair("Demencia", "16"),
Pair("Horror", "horror"), Pair("Misterio", "17"),
Pair("Infantil", "infantil"), Pair("Psicológico", "18"),
Pair("Isekai", "isekai"), Pair("Suspenso", "19"),
Pair("Josei", "josei"), Pair("Ciencia Ficción", "20"),
Pair("Juegos", "juegos"), Pair("Mecha", "21"),
Pair("Magia", "magia"), Pair("Militar", "22"),
Pair("Mecha", "mecha"), Pair("Aventuras", "23"),
Pair("Militar", "militar"), Pair("Historico", "24"),
Pair("Misterio", "misterio"), Pair("Infantil", "25"),
Pair("Música", "Musica"), Pair("Artes Marciales", "26"),
Pair("Ninjas", "ninjas"), Pair("Terror", "27"),
Pair("Parodia", "parodia"), Pair("Harem", "28"),
Pair("Policía", "policia"), Pair("Josei", "29"),
Pair("Psicológico", "psicologico"), Pair("Parodia", "30"),
Pair("Recuerdos de la vida", "Recuerdos de la vida"), Pair("Policía", "31"),
Pair("Romance", "romance"), Pair("Juegos", "32"),
Pair("Samurai", "samurai"), Pair("Carreras", "33"),
Pair("Sci-Fi", "sci-fi"), Pair("Samurai", "34"),
Pair("Seinen", "seinen"), Pair("Espacial", "35"),
Pair("Shoujo", "shoujo"), Pair("Música", "36"),
Pair("Shoujo Ai", "shoujo-ai"), Pair("Yuri", "37"),
Pair("Shounen", "shounen"), Pair("Demonios", "38"),
Pair("Slice of life", "slice-of-life"), Pair("Vampiros", "39"),
Pair("Sobrenatural", "sobrenatural"), Pair("Yaoi", "40"),
Pair("Space", "space"), Pair("Humor Negro", "41"),
Pair("Spokon", "spokon"), Pair("Crimen", "42"),
Pair("Steampunk", "steampunk"), Pair("Hentai", "43"),
Pair("Superpoder", "superpoder"), Pair("Youtuber", "44"),
Pair("Thriller", "thriller"), Pair("MaiNess Random", "45"),
Pair("Vampiro", "vampiro"), Pair("Donghua", "46"),
Pair("Yaoi", "yaoi"), Pair("Horror", "47"),
Pair("Yuri", "yuri"), Pair("Sin Censura", "48"),
Pair("Zombies", "zombies"), Pair("Gore", "49"),
Pair("Live Action", "50"),
Pair("Isekai", "51"),
Pair("Gourmet", "52"),
Pair("spokon", "53"),
Pair("Zombies", "54"),
Pair("Idols", "55"),
) )
val LANGUAGE = arrayOf(
Pair("Todos", ""),
Pair("Japonés", "1"),
Pair("Latino", "2"),
)
val YEARS = arrayOf(Pair("Todos", "")) + (1967..Calendar.getInstance().get(Calendar.YEAR)).map { Pair("$it", "$it") }.reversed().toTypedArray()
} }
} }

View file

@ -11,9 +11,9 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.lib.amazonextractor.AmazonExtractor
import eu.kanade.tachiyomi.lib.burstcloudextractor.BurstCloudExtractor import eu.kanade.tachiyomi.lib.burstcloudextractor.BurstCloudExtractor
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
@ -28,37 +28,35 @@ import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.Dispatchers import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.net.URLDecoder
class Animefenix : ConfigurableAnimeSource, AnimeHttpSource() { class Animefenix : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "AnimeFenix" override val name = "AnimeFenix"
override val baseUrl = "https://www3.animefenix.tv" override val baseUrl = "https://animefenix2.tv"
override val lang = "es" override val lang = "es"
override val supportsLatest = true override val supportsLatest = false
private val preferences: SharedPreferences by lazy { Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) } private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
companion object { companion object {
private val SERVER_REGEX = """tabsArray\['?\d+'?]\s*=\s*['\"](https[^'\"]+)['\"]""".toRegex()
private const val PREF_QUALITY_KEY = "preferred_quality" private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080" private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360") private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server" private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Amazon" private const val PREF_SERVER_DEFAULT = "Mp4Upload"
private val SERVER_LIST = arrayOf( private val SERVER_LIST = arrayOf(
"YourUpload", "Voe", "Mp4Upload", "Doodstream", "YourUpload", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Upload", "BurstCloud", "Upstream", "StreamTape",
@ -67,33 +65,45 @@ class Animefenix : ConfigurableAnimeSource, AnimeHttpSource() {
) )
} }
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/animes?order=likes&page=$page") override fun animeDetailsParse(response: Response): SAnime {
val document = response.asJsoup()
val animeDetails = SAnime.create().apply {
title = document.selectFirst("h1.text-4xl")?.ownText() ?: ""
status = document.select(".relative .rounded").getStatus()
description = document.selectFirst(".mb-6 p.text-gray-300")?.text()
genre = document.select(".flex-wrap a").joinToString { it.text().trim() }
thumbnail_url = document.selectFirst("#anime_image")?.getImageUrl()
}
return animeDetails
}
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/directorio/anime?p=$page&estado=2", headers)
override fun popularAnimeParse(response: Response): AnimesPage { override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup() val document = response.asJsoup()
val elements = document.select("div.container .grid.gap-4 a[href]") val elements = document.select(".grid-animes li article a")
val nextPage = document.select("nav[aria-label=Pagination] span:containsOwn(Next)").any() val nextPage = document.select(".right:not(.disabledd)").any()
val animeList = elements.map { element -> val animeList = elements.map { element ->
SAnime.create().apply { SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href")) setUrlWithoutDomain(element.attr("abs:href"))
title = element.selectFirst("div h3.text-primary")!!.ownText() title = element.selectFirst("p:not(.gray)")?.text() ?: ""
thumbnail_url = element.selectFirst("img.object-cover")?.attr("abs:src") thumbnail_url = element.selectFirst(".main-img img")?.getImageUrl()
} }
} }
return AnimesPage(animeList, nextPage) return AnimesPage(animeList, nextPage)
} }
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/animes?order=added&page=$page") override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
override fun latestUpdatesParse(response: Response) = popularAnimeParse(response) override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = AnimeFenixFilters.getSearchParameters(filters) val params = AnimeFenixFilters.getSearchParameters(filters)
return when { return when {
query.isNotBlank() -> GET("$baseUrl/animes?q=$query&page=$page", headers) query.isNotBlank() -> GET("$baseUrl/directorio/anime?q=$query&p=$page", headers)
params.filter.isNotBlank() -> GET("$baseUrl/animes${params.getQuery()}&page=$page", headers) params.filter.isNotBlank() -> GET("$baseUrl/directorio/anime${params.getQuery()}&page=$page", headers)
else -> GET("$baseUrl/animes?order=likes&page=$page") else -> popularAnimeRequest(page)
} }
} }
@ -101,115 +111,77 @@ class Animefenix : ConfigurableAnimeSource, AnimeHttpSource() {
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup() val document = response.asJsoup()
return document.select("div.container > div > ul > li").map { element -> return document.select(".divide-y li > a").map {
val title = it.select(".font-semibold").text()
SEpisode.create().apply { SEpisode.create().apply {
name = element.selectFirst("span > span")!!.ownText() name = title
setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href")) episode_number = title.substringAfter("Episodio").toFloatOrNull() ?: 0F
setUrlWithoutDomain(it.attr("abs:href"))
} }
} }
} }
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup() val document = response.asJsoup()
val videoList = mutableListOf<Video>() val script = document.selectFirst("script:containsData(var tabsArray)") ?: return emptyList()
val serversData = document.selectFirst("script:containsData(var tabsArray)")?.data() ?: throw Exception("No se encontraron servidores") return script.data().substringAfter("<iframe").split("src='")
val servers = SERVER_REGEX.findAll(serversData).map { it.groupValues[1] }.toList() .map { it.substringBefore("'").substringAfter("redirect.php?id=").trim() }
.parallelCatchingFlatMapBlocking { url ->
servers.parallelForEachBlocking { server -> serverVideoResolver(url)
val decodedUrl = URLDecoder.decode(server, "UTF-8")
val realUrl = try {
client.newCall(GET(decodedUrl)).execute().asJsoup().selectFirst("script")!!
.data().substringAfter("src=\"").substringBefore("\"")
} catch (e: Exception) { "" }
try {
serverVideoResolver(realUrl).let { videoList.addAll(it) }
} catch (_: Exception) { }
} }
return videoList.filter { it.url.contains("https") || it.url.contains("http") }
} }
/*-------------------------------- Video extractors ------------------------------------*/
private val universalExtractor by lazy { UniversalExtractor(client) }
private val voeExtractor by lazy { VoeExtractor(client) }
private val okruExtractor by lazy { OkruExtractor(client) }
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
private val uqloadExtractor by lazy { UqloadExtractor(client) }
private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) }
private val streamwishExtractor by lazy { StreamWishExtractor(client, headers) }
private val doodExtractor by lazy { DoodExtractor(client) }
private val streamlareExtractor by lazy { StreamlareExtractor(client) }
private val yourUploadExtractor by lazy { YourUploadExtractor(client) }
private val burstcloudExtractor by lazy { BurstCloudExtractor(client) }
private val upstreamExtractor by lazy { UpstreamExtractor(client) }
private val streamTapeExtractor by lazy { StreamTapeExtractor(client) }
private val streamHideVidExtractor by lazy { StreamHideVidExtractor(client, headers) }
private val filelionsExtractor by lazy { StreamWishExtractor(client, headers) }
private val amazonExtractor by lazy { AmazonExtractor(client) }
private fun serverVideoResolver(url: String): List<Video> { private fun serverVideoResolver(url: String): List<Video> {
val videoList = mutableListOf<Video>() return runCatching {
val embedUrl = url.lowercase()
try {
when { when {
embedUrl.contains("voe") -> { arrayOf("voe", "robertordercharacter", "donaldlineelse").any(url) -> voeExtractor.videosFromUrl(url)
VoeExtractor(client).videosFromUrl(url).also(videoList::addAll) arrayOf("amazon", "amz").any(url) -> amazonExtractor.videosFromUrl(url)
} arrayOf("ok.ru", "okru").any(url) -> okruExtractor.videosFromUrl(url)
(embedUrl.contains("amazon") || embedUrl.contains("amz")) && !embedUrl.contains("disable") -> { arrayOf("moon").any(url) -> filemoonExtractor.videosFromUrl(url, prefix = "Filemoon:")
val video = amazonExtractor(baseUrl + url.substringAfter("..")) arrayOf("uqload").any(url) -> uqloadExtractor.videosFromUrl(url)
if (video.isNotBlank()) { arrayOf("mp4upload").any(url) -> mp4uploadExtractor.videosFromUrl(url, headers)
if (url.contains("&ext=es")) { arrayOf("wish").any(url) -> streamwishExtractor.videosFromUrl(url, videoNameGen = { "StreamWish:$it" })
videoList.add(Video(video, "AmazonES", video)) arrayOf("doodstream", "dood.").any(url) -> doodExtractor.videosFromUrl(url, "DoodStream")
} else { arrayOf("streamlare").any(url) -> streamlareExtractor.videosFromUrl(url)
videoList.add(Video(video, "Amazon", video)) arrayOf("yourupload", "upload").any(url) -> yourUploadExtractor.videoFromUrl(url, headers = headers)
} arrayOf("burstcloud", "burst").any(url) -> burstcloudExtractor.videoFromUrl(url, headers = headers)
} arrayOf("upstream").any(url) -> upstreamExtractor.videosFromUrl(url)
} arrayOf("streamtape", "stp", "stape").any(url) -> streamTapeExtractor.videosFromUrl(url)
embedUrl.contains("ok.ru") || embedUrl.contains("okru") -> { arrayOf("ahvsh", "streamhide").any(url) -> streamHideVidExtractor.videosFromUrl(url)
OkruExtractor(client).videosFromUrl(url).also(videoList::addAll) arrayOf("/stream/fl.php").any(url) -> {
}
embedUrl.contains("filemoon") || embedUrl.contains("moonplayer") -> {
val vidHeaders = headers.newBuilder()
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
FilemoonExtractor(client).videosFromUrl(url, prefix = "Filemoon:", headers = vidHeaders).also(videoList::addAll)
}
embedUrl.contains("uqload") -> {
UqloadExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
embedUrl.contains("mp4upload") -> {
Mp4uploadExtractor(client).videosFromUrl(url, headers).let { videoList.addAll(it) }
}
embedUrl.contains("wishembed") || embedUrl.contains("embedwish") || embedUrl.contains("streamwish") || embedUrl.contains("strwish") || embedUrl.contains("wish") -> {
val docHeaders = headers.newBuilder()
.add("Origin", "https://streamwish.to")
.add("Referer", "https://streamwish.to/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" }).also(videoList::addAll)
}
embedUrl.contains("doodstream") || embedUrl.contains("dood.") -> {
DoodExtractor(client).videoFromUrl(url, "DoodStream")?.let { videoList.add(it) }
}
embedUrl.contains("streamlare") -> {
StreamlareExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
embedUrl.contains("yourupload") || embedUrl.contains("upload") -> {
YourUploadExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
}
embedUrl.contains("burstcloud") || embedUrl.contains("burst") -> {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
}
embedUrl.contains("fastream") -> {
FastreamExtractor(client, headers).videosFromUrl(url).also(videoList::addAll)
}
embedUrl.contains("upstream") -> {
UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape") -> {
StreamTapeExtractor(client).videoFromUrl(url)?.let { videoList.add(it) }
}
embedUrl.contains("ahvsh") || embedUrl.contains("streamhide") -> {
StreamHideVidExtractor(client, headers).videosFromUrl(url).let { videoList.addAll(it) }
}
embedUrl.contains("/stream/fl.php") -> {
val video = url.substringAfter("/stream/fl.php?v=") val video = url.substringAfter("/stream/fl.php?v=")
if (client.newCall(GET(video)).execute().code == 200) { if (client.newCall(GET(video)).execute().code == 200) {
videoList.add(Video(video, "FireLoad", video)) listOf(Video(video, "FireLoad", video))
} else {
emptyList()
} }
} }
embedUrl.contains("filelions") || embedUrl.contains("lion") -> { arrayOf("lion").any(url) -> filelionsExtractor.videosFromUrl(url, videoNameGen = { "FileLions:$it" })
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "FileLions:$it" }).also(videoList::addAll) else -> universalExtractor.videosFromUrl(url, headers)
} }
else -> }.getOrElse { emptyList() }
UniversalExtractor(client).videosFromUrl(url, headers).let { videoList.addAll(it) }
}
} catch (_: Exception) { }
return videoList
} }
private fun Array<String>.any(url: String): Boolean = this.any { url.contains(it, ignoreCase = true) }
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!! val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!! val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
@ -222,56 +194,32 @@ class Animefenix : ConfigurableAnimeSource, AnimeHttpSource() {
).reversed() ).reversed()
} }
override fun animeDetailsParse(response: Response) = SAnime.create().apply { private fun Elements.getStatus(): Int {
val document = response.asJsoup()
with(document.selectFirst("main > div.relative > div.container > div.flex")!!) {
title = selectFirst("h1.font-bold")!!.ownText()
genre = select("div:has(h2:containsOwn(Géneros)) > div.flex > a").joinToString { it.text() }
status = parseStatus(selectFirst("li:has(> span:containsOwn(Estado))")!!.ownText())
description = select("div:has(h2:containsOwn(Sinopsis)) > p").text()
}
}
private fun parseStatus(statusString: String): Int {
return when { return when {
statusString.contains("Emisión") -> SAnime.ONGOING text().contains("finalizado", true) -> SAnime.COMPLETED
statusString.contains("Finalizado") -> SAnime.COMPLETED text().contains("emision", true) -> SAnime.ONGOING
else -> SAnime.UNKNOWN else -> SAnime.UNKNOWN
} }
} }
private fun amazonExtractor(url: String): String { private fun Element.getImageUrl(): String? {
val document = client.newCall(GET(url)).execute().asJsoup() return when {
val videoURl = document.selectFirst("script:containsData(sources: [)")!!.data() isValidUrl("data-src") -> attr("abs:data-src")
.substringAfter("[{\"file\":\"") isValidUrl("data-lazy-src") -> attr("abs:data-lazy-src")
.substringBefore("\",").replace("\\", "") isValidUrl("srcset") -> attr("abs:srcset").substringBefore(" ")
isValidUrl("src") -> attr("abs:src")
return try { else -> ""
if (client.newCall(GET(videoURl)).execute().code == 200) videoURl else ""
} catch (e: Exception) {
""
} }
} }
private fun Element.isValidUrl(attrName: String): Boolean {
if (!hasAttr(attrName)) return false
return !attr(attrName).contains("data:image/")
}
override fun getFilterList(): AnimeFilterList = AnimeFenixFilters.FILTER_LIST override fun getFilterList(): AnimeFilterList = AnimeFenixFilters.FILTER_LIST
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
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 { ListPreference(screen.context).apply {
key = PREF_SERVER_KEY key = PREF_SERVER_KEY
title = "Preferred server" title = "Preferred server"
@ -287,21 +235,21 @@ class Animefenix : ConfigurableAnimeSource, AnimeHttpSource() {
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference) }.also(screen::addPreference)
}
suspend inline fun <A> Iterable<A>.parallelForEach(crossinline f: suspend (A) -> Unit) { ListPreference(screen.context).apply {
coroutineScope { key = PREF_QUALITY_KEY
for (item in this@parallelForEach) { title = "Preferred quality"
launch(Dispatchers.IO) { entries = QUALITY_LIST
f(item) entryValues = QUALITY_LIST
} setDefaultValue(PREF_QUALITY_DEFAULT)
} summary = "%s"
}
}
inline fun <A> Iterable<A>.parallelForEachBlocking(crossinline f: suspend (A) -> Unit) { setOnPreferenceChangeListener { _, newValue ->
runBlocking { val selected = newValue as String
this@parallelForEachBlocking.parallelForEach(f) val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference)
} }
} }

View file

@ -1,27 +0,0 @@
package eu.kanade.tachiyomi.animeextension.es.animefenix.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
class SolidFilesExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String, prefix: String = ""): List<Video> {
val videoList = mutableListOf<Video>()
return try {
val document = client.newCall(GET(url)).execute().asJsoup()
document.select("script").forEach { script ->
if (script.data().contains("\"downloadUrl\":")) {
val data = script.data().substringAfter("\"downloadUrl\":").substringBefore(",")
val url = data.replace("\"", "")
val videoUrl = url
val quality = prefix + "SolidFiles"
videoList.add(Video(videoUrl, quality, videoUrl))
}
}
videoList
} catch (e: Exception) {
videoList
}
}
}