fix(src/es): AnimeFenix fixes and updates

Closes #719
Closes #511
This commit is contained in:
imper1aldev 2025-03-01 18:06:42 -06:00
parent 6706bbf200
commit 60729c8078
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 {
extName = 'Animefenix'
extClass = '.Animefenix'
extVersionCode = 55
extVersionCode = 56
}
apply from: "$rootDir/common.gradle"
@ -17,9 +17,9 @@ dependencies {
implementation(project(':lib:filemoon-extractor'))
implementation(project(':lib:voe-extractor'))
implementation(project(':lib:streamlare-extractor'))
implementation(project(':lib:fastream-extractor'))
implementation(project(':lib:dood-extractor'))
implementation(project(':lib:upstream-extractor'))
implementation(project(':lib:streamhidevid-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 { "" }
}
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 {
return (this.getFirst<R>() as QueryPartFilter).toQueryPart(name)
}
@ -51,111 +27,118 @@ object AnimeFenixFilters {
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
return FilterSearchParams(
filters.parseCheckbox<GenresFilter>(AnimeFenixFiltersData.GENRES, "genero") +
filters.parseCheckbox<YearsFilter>(AnimeFenixFiltersData.YEARS, "year") +
filters.parseCheckbox<TypesFilter>(AnimeFenixFiltersData.TYPES, "type") +
filters.parseCheckbox<StateFilter>(AnimeFenixFiltersData.STATE, "estado") +
filters.asQueryPart<SortFilter>("order"),
filters.asQueryPart<StateFilter>("estado") +
filters.asQueryPart<TypesFilter>("tipo") +
filters.asQueryPart<GenresFilter>("genero") +
filters.asQueryPart<YearsFilter>("estreno") +
filters.asQueryPart<LangFilter>("idioma"),
)
}
val FILTER_LIST get() = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
StateFilter(),
TypesFilter(),
GenresFilter(),
YearsFilter(),
TypesFilter(),
StateFilter(),
SortFilter(),
LangFilter(),
)
class GenresFilter : CheckBoxFilterList("Género", AnimeFenixFiltersData.GENRES.map { CheckBoxVal(it.first, false) })
class YearsFilter : CheckBoxFilterList("Año", AnimeFenixFiltersData.YEARS.map { CheckBoxVal(it.first, false) })
class TypesFilter : CheckBoxFilterList("Tipo", AnimeFenixFiltersData.TYPES.map { CheckBoxVal(it.first, false) })
class StateFilter : CheckBoxFilterList("Estado", AnimeFenixFiltersData.STATE.map { CheckBoxVal(it.first, false) })
class SortFilter : QueryPartFilter("Orden", AnimeFenixFiltersData.SORT)
class StateFilter : QueryPartFilter("Estado", AnimeFenixFiltersData.STATE)
class TypesFilter : QueryPartFilter("Tipo", AnimeFenixFiltersData.TYPES)
class GenresFilter : QueryPartFilter("Género", AnimeFenixFiltersData.GENRES)
class YearsFilter : QueryPartFilter("Año", AnimeFenixFiltersData.YEARS)
class LangFilter : QueryPartFilter("Idioma", AnimeFenixFiltersData.LANGUAGE)
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(
Pair("TV", "tv"),
Pair("Película", "movie"),
Pair("Especial", "special"),
Pair("OVA", "ova"),
Pair("DONGHUA", "donghua"),
)
val STATE = arrayOf(
Pair("Emisión", "1"),
Pair("Finalizado", "2"),
Pair("Próximamente", "3"),
Pair("En Cuarentena", "4"),
)
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"),
Pair("Todos", ""),
Pair("TV", "1"),
Pair("Película", "2"),
Pair("OVA", "3"),
Pair("Especial", "4"),
Pair("Serie", "9"),
Pair("Dorama", "11"),
Pair("Corto", "14"),
Pair("Donghua", "15"),
Pair("ONA", "16"),
Pair("Live Action", "17"),
Pair("Manhwa", "18"),
Pair("Teatral", "19"),
)
val GENRES = arrayOf(
Pair("Acción", "accion"),
Pair("Ángeles", "angeles"),
Pair("Artes Marciales", "artes-marciales"),
Pair("Aventura", "aventura"),
Pair("Ciencia Ficción", "Ciencia Ficción"),
Pair("Comedia", "comedia"),
Pair("Cyberpunk", "cyberpunk"),
Pair("Demonios", "demonios"),
Pair("Deportes", "deportes"),
Pair("Dragones", "dragones"),
Pair("Drama", "drama"),
Pair("Ecchi", "ecchi"),
Pair("Escolares", "escolares"),
Pair("Fantasía", "fantasia"),
Pair("Gore", "gore"),
Pair("Harem", "harem"),
Pair("Histórico", "historico"),
Pair("Horror", "horror"),
Pair("Infantil", "infantil"),
Pair("Isekai", "isekai"),
Pair("Josei", "josei"),
Pair("Juegos", "juegos"),
Pair("Magia", "magia"),
Pair("Mecha", "mecha"),
Pair("Militar", "militar"),
Pair("Misterio", "misterio"),
Pair("Música", "Musica"),
Pair("Ninjas", "ninjas"),
Pair("Parodia", "parodia"),
Pair("Policía", "policia"),
Pair("Psicológico", "psicologico"),
Pair("Recuerdos de la vida", "Recuerdos de la vida"),
Pair("Romance", "romance"),
Pair("Samurai", "samurai"),
Pair("Sci-Fi", "sci-fi"),
Pair("Seinen", "seinen"),
Pair("Shoujo", "shoujo"),
Pair("Shoujo Ai", "shoujo-ai"),
Pair("Shounen", "shounen"),
Pair("Slice of life", "slice-of-life"),
Pair("Sobrenatural", "sobrenatural"),
Pair("Space", "space"),
Pair("Spokon", "spokon"),
Pair("Steampunk", "steampunk"),
Pair("Superpoder", "superpoder"),
Pair("Thriller", "thriller"),
Pair("Vampiro", "vampiro"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
Pair("Zombies", "zombies"),
Pair("Todos", ""),
Pair("Acción", "1"),
Pair("Escolares", "2"),
Pair("Romance", "3"),
Pair("Shoujo", "4"),
Pair("Comedia", "5"),
Pair("Drama", "6"),
Pair("Seinen", "7"),
Pair("Deportes", "8"),
Pair("Shounen", "9"),
Pair("Recuentos de la vida", "10"),
Pair("Ecchi", "11"),
Pair("Sobrenatural", "12"),
Pair("Fantasía", "13"),
Pair("Magia", "14"),
Pair("Superpoderes", "15"),
Pair("Demencia", "16"),
Pair("Misterio", "17"),
Pair("Psicológico", "18"),
Pair("Suspenso", "19"),
Pair("Ciencia Ficción", "20"),
Pair("Mecha", "21"),
Pair("Militar", "22"),
Pair("Aventuras", "23"),
Pair("Historico", "24"),
Pair("Infantil", "25"),
Pair("Artes Marciales", "26"),
Pair("Terror", "27"),
Pair("Harem", "28"),
Pair("Josei", "29"),
Pair("Parodia", "30"),
Pair("Policía", "31"),
Pair("Juegos", "32"),
Pair("Carreras", "33"),
Pair("Samurai", "34"),
Pair("Espacial", "35"),
Pair("Música", "36"),
Pair("Yuri", "37"),
Pair("Demonios", "38"),
Pair("Vampiros", "39"),
Pair("Yaoi", "40"),
Pair("Humor Negro", "41"),
Pair("Crimen", "42"),
Pair("Hentai", "43"),
Pair("Youtuber", "44"),
Pair("MaiNess Random", "45"),
Pair("Donghua", "46"),
Pair("Horror", "47"),
Pair("Sin Censura", "48"),
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.Video
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.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
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.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import okhttp3.HttpUrl.Companion.toHttpUrl
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.net.URLDecoder
class Animefenix : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "AnimeFenix"
override val baseUrl = "https://www3.animefenix.tv"
override val baseUrl = "https://animefenix2.tv"
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 {
private val SERVER_REGEX = """tabsArray\['?\d+'?]\s*=\s*['\"](https[^'\"]+)['\"]""".toRegex()
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
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(
"YourUpload", "Voe", "Mp4Upload", "Doodstream",
"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 {
val document = response.asJsoup()
val elements = document.select("div.container .grid.gap-4 a[href]")
val nextPage = document.select("nav[aria-label=Pagination] span:containsOwn(Next)").any()
val elements = document.select(".grid-animes li article a")
val nextPage = document.select(".right:not(.disabledd)").any()
val animeList = elements.map { element ->
SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href"))
title = element.selectFirst("div h3.text-primary")!!.ownText()
thumbnail_url = element.selectFirst("img.object-cover")?.attr("abs:src")
setUrlWithoutDomain(element.attr("abs:href"))
title = element.selectFirst("p:not(.gray)")?.text() ?: ""
thumbnail_url = element.selectFirst(".main-img img")?.getImageUrl()
}
}
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 {
val params = AnimeFenixFilters.getSearchParameters(filters)
return when {
query.isNotBlank() -> GET("$baseUrl/animes?q=$query&page=$page", headers)
params.filter.isNotBlank() -> GET("$baseUrl/animes${params.getQuery()}&page=$page", headers)
else -> GET("$baseUrl/animes?order=likes&page=$page")
query.isNotBlank() -> GET("$baseUrl/directorio/anime?q=$query&p=$page", headers)
params.filter.isNotBlank() -> GET("$baseUrl/directorio/anime${params.getQuery()}&page=$page", headers)
else -> popularAnimeRequest(page)
}
}
@ -101,115 +111,77 @@ class Animefenix : ConfigurableAnimeSource, AnimeHttpSource() {
override fun episodeListParse(response: Response): List<SEpisode> {
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 {
name = element.selectFirst("span > span")!!.ownText()
setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href"))
name = title
episode_number = title.substringAfter("Episodio").toFloatOrNull() ?: 0F
setUrlWithoutDomain(it.attr("abs:href"))
}
}
}
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
val serversData = document.selectFirst("script:containsData(var tabsArray)")?.data() ?: throw Exception("No se encontraron servidores")
val servers = SERVER_REGEX.findAll(serversData).map { it.groupValues[1] }.toList()
servers.parallelForEachBlocking { server ->
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") }
val script = document.selectFirst("script:containsData(var tabsArray)") ?: return emptyList()
return script.data().substringAfter("<iframe").split("src='")
.map { it.substringBefore("'").substringAfter("redirect.php?id=").trim() }
.parallelCatchingFlatMapBlocking { url ->
serverVideoResolver(url)
}
}
/*-------------------------------- 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> {
val videoList = mutableListOf<Video>()
val embedUrl = url.lowercase()
try {
return runCatching {
when {
embedUrl.contains("voe") -> {
VoeExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
(embedUrl.contains("amazon") || embedUrl.contains("amz")) && !embedUrl.contains("disable") -> {
val video = amazonExtractor(baseUrl + url.substringAfter(".."))
if (video.isNotBlank()) {
if (url.contains("&ext=es")) {
videoList.add(Video(video, "AmazonES", video))
} else {
videoList.add(Video(video, "Amazon", video))
}
}
}
embedUrl.contains("ok.ru") || embedUrl.contains("okru") -> {
OkruExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
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") -> {
arrayOf("voe", "robertordercharacter", "donaldlineelse").any(url) -> voeExtractor.videosFromUrl(url)
arrayOf("amazon", "amz").any(url) -> amazonExtractor.videosFromUrl(url)
arrayOf("ok.ru", "okru").any(url) -> okruExtractor.videosFromUrl(url)
arrayOf("moon").any(url) -> filemoonExtractor.videosFromUrl(url, prefix = "Filemoon:")
arrayOf("uqload").any(url) -> uqloadExtractor.videosFromUrl(url)
arrayOf("mp4upload").any(url) -> mp4uploadExtractor.videosFromUrl(url, headers)
arrayOf("wish").any(url) -> streamwishExtractor.videosFromUrl(url, videoNameGen = { "StreamWish:$it" })
arrayOf("doodstream", "dood.").any(url) -> doodExtractor.videosFromUrl(url, "DoodStream")
arrayOf("streamlare").any(url) -> streamlareExtractor.videosFromUrl(url)
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)
arrayOf("ahvsh", "streamhide").any(url) -> streamHideVidExtractor.videosFromUrl(url)
arrayOf("/stream/fl.php").any(url) -> {
val video = url.substringAfter("/stream/fl.php?v=")
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") -> {
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "FileLions:$it" }).also(videoList::addAll)
}
else ->
UniversalExtractor(client).videosFromUrl(url, headers).let { videoList.addAll(it) }
arrayOf("lion").any(url) -> filelionsExtractor.videosFromUrl(url, videoNameGen = { "FileLions:$it" })
else -> universalExtractor.videosFromUrl(url, headers)
}
} catch (_: Exception) { }
return videoList
}.getOrElse { emptyList() }
}
private fun Array<String>.any(url: String): Boolean = this.any { url.contains(it, ignoreCase = true) }
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
@ -222,56 +194,32 @@ class Animefenix : ConfigurableAnimeSource, AnimeHttpSource() {
).reversed()
}
override fun animeDetailsParse(response: Response) = SAnime.create().apply {
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 {
private fun Elements.getStatus(): Int {
return when {
statusString.contains("Emisión") -> SAnime.ONGOING
statusString.contains("Finalizado") -> SAnime.COMPLETED
text().contains("finalizado", true) -> SAnime.COMPLETED
text().contains("emision", true) -> SAnime.ONGOING
else -> SAnime.UNKNOWN
}
}
private fun amazonExtractor(url: String): String {
val document = client.newCall(GET(url)).execute().asJsoup()
val videoURl = document.selectFirst("script:containsData(sources: [)")!!.data()
.substringAfter("[{\"file\":\"")
.substringBefore("\",").replace("\\", "")
return try {
if (client.newCall(GET(videoURl)).execute().code == 200) videoURl else ""
} catch (e: Exception) {
""
private fun Element.getImageUrl(): String? {
return when {
isValidUrl("data-src") -> attr("abs:data-src")
isValidUrl("data-lazy-src") -> attr("abs:data-lazy-src")
isValidUrl("srcset") -> attr("abs:srcset").substringBefore(" ")
isValidUrl("src") -> attr("abs:src")
else -> ""
}
}
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 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 {
key = PREF_SERVER_KEY
title = "Preferred server"
@ -287,21 +235,21 @@ class Animefenix : ConfigurableAnimeSource, AnimeHttpSource() {
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
}
suspend inline fun <A> Iterable<A>.parallelForEach(crossinline f: suspend (A) -> Unit) {
coroutineScope {
for (item in this@parallelForEach) {
launch(Dispatchers.IO) {
f(item)
}
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()
}
}
}
inline fun <A> Iterable<A>.parallelForEachBlocking(crossinline f: suspend (A) -> Unit) {
runBlocking {
this@parallelForEachBlocking.parallelForEach(f)
}
}.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
}
}
}