Merged with dark25 (#636)

* merge

merged lib, lib-multisrc, all, ar, de, en, es, fr, hi, id, it, pt, tr src from dark25

* patch
This commit is contained in:
Hak 2025-02-10 15:41:59 +07:00 committed by GitHub
parent 9f385108fc
commit 1384df62f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
350 changed files with 12176 additions and 1064 deletions

View file

@ -0,0 +1,25 @@
ext {
extName = 'Animefenix'
extClass = '.Animefenix'
extVersionCode = 54
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:mp4upload-extractor'))
implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:yourupload-extractor'))
implementation(project(':lib:uqload-extractor'))
implementation(project(':lib:okru-extractor'))
implementation(project(':lib:burstcloud-extractor'))
implementation(project(':lib:streamwish-extractor'))
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'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -0,0 +1,161 @@
package eu.kanade.tachiyomi.animeextension.es.animefenix
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import java.util.Calendar
object AnimeFenixFilters {
open class QueryPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : AnimeFilter.Select<String>(
displayName,
vals.map { it.first }.toTypedArray(),
) {
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)
}
private inline fun <reified R> AnimeFilterList.getFirst(): R {
return this.filterIsInstance<R>().first()
}
private fun String.changePrefix() = this.takeIf { it.startsWith("&") }?.let { this.replaceFirst("&", "?") } ?: run { this }
data class FilterSearchParams(val filter: String = "") { fun getQuery() = filter.changePrefix() }
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"),
)
}
val FILTER_LIST get() = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenresFilter(),
YearsFilter(),
TypesFilter(),
StateFilter(),
SortFilter(),
)
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)
private object AnimeFenixFiltersData {
val YEARS = (1990..Calendar.getInstance().get(Calendar.YEAR)).map { Pair("$it", "$it") }.reversed().toTypedArray()
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"),
)
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"),
)
}
}

View file

@ -0,0 +1,307 @@
package eu.kanade.tachiyomi.animeextension.es.animefenix
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
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.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
import eu.kanade.tachiyomi.lib.streamhidevidextractor.StreamHideVidExtractor
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.universalextractor.UniversalExtractor
import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
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 okhttp3.Request
import okhttp3.Response
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 lang = "es"
override val supportsLatest = true
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 val SERVER_LIST = arrayOf(
"YourUpload", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape",
"Fastream", "Filemoon", "StreamWish", "Okru",
"Amazon", "AmazonES", "Fireload", "FileLions",
)
}
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/animes?order=likes&page=$page")
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 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")
}
}
return AnimesPage(animeList, nextPage)
}
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/animes?order=added&page=$page")
override fun latestUpdatesParse(response: Response) = popularAnimeParse(response)
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")
}
}
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
return document.select("div.container > div > ul > li").map { element ->
SEpisode.create().apply {
name = element.selectFirst("span > span")!!.ownText()
setUrlWithoutDomain(element.selectFirst("a")!!.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") }
}
private fun serverVideoResolver(url: String): List<Video> {
val videoList = mutableListOf<Video>()
val embedUrl = url.lowercase()
try {
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") -> {
val video = url.substringAfter("/stream/fl.php?v=")
if (client.newCall(GET(video)).execute().code == 200) {
videoList.add(Video(video, "FireLoad", video))
}
}
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) }
}
} catch (_: Exception) { }
return videoList
}
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)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).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 {
return when {
statusString.contains("Emisión") -> SAnime.ONGOING
statusString.contains("Finalizado") -> SAnime.COMPLETED
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) {
""
}
}
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"
entries = SERVER_LIST
entryValues = SERVER_LIST
setDefaultValue(PREF_SERVER_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)
}
suspend inline fun <A> Iterable<A>.parallelForEach(crossinline f: suspend (A) -> Unit) {
coroutineScope {
for (item in this@parallelForEach) {
launch(Dispatchers.IO) {
f(item)
}
}
}
}
inline fun <A> Iterable<A>.parallelForEachBlocking(crossinline f: suspend (A) -> Unit) {
runBlocking {
this@parallelForEachBlocking.parallelForEach(f)
}
}
}

View file

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