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,33 @@
ext {
extName = 'FlixLatam'
extClass = '.FlixLatam'
themePkg = 'dooplay'
baseUrl = 'https://flixlatam.com'
overrideVersionCode = 3
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:vudeo-extractor'))
implementation(project(':lib:uqload-extractor'))
implementation(project(':lib:streamwish-extractor'))
implementation(project(':lib:filemoon-extractor'))
implementation(project(':lib:streamlare-extractor'))
implementation(project(':lib:yourupload-extractor'))
implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:dood-extractor'))
implementation(project(':lib:voe-extractor'))
implementation(project(':lib:okru-extractor'))
implementation(project(':lib:mp4upload-extractor'))
implementation(project(':lib:mixdrop-extractor'))
implementation(project(':lib:burstcloud-extractor'))
implementation(project(':lib:fastream-extractor'))
implementation(project(':lib:upstream-extractor'))
implementation(project(':lib:streamhidevid-extractor'))
implementation(project(':lib:streamsilk-extractor'))
implementation(project(':lib:vidguard-extractor'))
implementation(project(':lib:universal-extractor'))
implementation(project(':lib:cryptoaes'))
implementation(libs.jsunpacker)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -0,0 +1,298 @@
package eu.kanade.tachiyomi.animeextension.es.flixlatam
import android.util.Log
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.burstcloudextractor.BurstCloudExtractor
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
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.streamsilkextractor.StreamSilkExtractor
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.vidguardextractor.VidGuardExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelFlatMapBlocking
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import okhttp3.FormBody
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
class FlixLatam : DooPlay(
"es",
"FlixLatam",
"https://flixlatam.com",
) {
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/pelicula/page/$page")
override fun popularAnimeSelector() = latestUpdatesSelector()
override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector()
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/lanzamiento/2024/page/$page")
override val episodeMovieText = "Película"
override val episodeSeasonPrefix = "Temporada"
override val prefQualityTitle = "Calidad preferida"
override fun videoListSelector() = "li.dooplay_player_option" // ul#playeroptionsul
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoSet = mutableSetOf<Video>()
val players = document.select("ul#playeroptionsul li")
// Iterar sobre cada player
players.parallelFlatMapBlocking { player ->
val url = getPlayerUrl(player)
?: return@parallelFlatMapBlocking emptyList<Video>()
if (url.contains("embed69")) {
val htmlContent = client.newCall(GET(url)).execute().body.string()
val links = extractNewExtractorLinks(document, htmlContent) ?: return@parallelFlatMapBlocking emptyList<Video>()
links.forEach { (link, language) ->
videoSet.addAll(serverVideoResolver(link, " $language"))
}
}
return@parallelFlatMapBlocking emptyList<Video>()
}
return videoSet.toList()
}
private fun getPlayerUrl(player: Element): String? {
val body = FormBody.Builder()
.add("action", "doo_player_ajax")
.add("post", player.attr("data-post"))
.add("nume", player.attr("data-nume"))
.add("type", player.attr("data-type"))
.build()
return client.newCall(POST("$baseUrl/wp-admin/admin-ajax.php", headers, body))
.execute().body?.string()
?.substringAfter("\"embed_url\":\"")
?.substringBefore("\",")
?.replace("\\", "")
?.takeIf(String::isNotBlank)
}
/*-------------------------------- Video extractors ------------------------------------*/
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 fastreamExtractor by lazy { FastreamExtractor(client, headers) }
private val upstreamExtractor by lazy { UpstreamExtractor(client) }
private val streamTapeExtractor by lazy { StreamTapeExtractor(client) }
private val streamHideVidExtractor by lazy { StreamHideVidExtractor(client, headers) }
private val streamSilkExtractor by lazy { StreamSilkExtractor(client) }
private val vidGuardExtractor by lazy { VidGuardExtractor(client) }
private val universalExtractor by lazy { UniversalExtractor(client) }
private fun serverVideoResolver(url: String, prefix: String = ""): List<Video> {
return runCatching {
Log.d("SoloLatino", "URL: $url")
when {
"voe" in url -> voeExtractor.videosFromUrl(url, "$prefix ")
"ok.ru" in url || "okru" in url -> okruExtractor.videosFromUrl(url, prefix)
"filemoon" in url || "moonplayer" in url -> filemoonExtractor.videosFromUrl(url, "$prefix Filemoon:")
"amazon" in url || "amz" in url -> extractAmazonVideo(url, prefix)
"uqload" in url -> uqloadExtractor.videosFromUrl(url, prefix)
"mp4upload" in url -> mp4uploadExtractor.videosFromUrl(url, headers, "$prefix ")
"streamwish" in url || "wish" in url -> streamWishExtractor.videosFromUrl(url, "$prefix StreamWish:")
"doodstream" in url || "dood." in url -> doodExtractor.videosFromUrl(url.replace("https://doodstream.com/e/", "https://d0000d.com/e/"), "$prefix DoodStream")
"streamlare" in url -> streamlareExtractor.videosFromUrl(url, prefix)
"yourupload" in url -> yourUploadExtractor.videoFromUrl(url, headers, "$prefix ")
"burstcloud" in url -> burstCloudExtractor.videoFromUrl(url, headers, "$prefix ")
"fastream" in url -> fastreamExtractor.videosFromUrl(url, "$prefix Fastream:")
"upstream" in url -> upstreamExtractor.videosFromUrl(url, "$prefix ")
"streamsilk" in url -> streamSilkExtractor.videosFromUrl(url, "$prefix StreamSilk:")
"streamtape" in url || "stp" in url -> streamTapeExtractor.videosFromUrl(url, "$prefix StreamTape")
arrayOf("ahvsh", "streamhide", "guccihide", "streamvid", "vidhide").any(url) -> streamHideVidExtractor.videosFromUrl(url, videoNameGen = { "$prefix StreamHideVid:$it" })
arrayOf("vembed", "guard", "listeamed", "bembed", "vgfplay").any(url) -> vidGuardExtractor.videosFromUrl(url, prefix = "$prefix ")
else -> emptyList()
}
}.getOrElse { emptyList() }
}
private fun extractAmazonVideo(url: String, prefix: String): List<Video> {
val body = client.newCall(GET(url)).execute().asJsoup()
val shareId = body.selectFirst("script:containsData(var shareId)")
?.data()
?.substringAfter("shareId = \"")
?.substringBefore("\"") ?: return emptyList()
val amazonApiJson = client.newCall(GET("https://www.amazon.com/drive/v1/shares/$shareId?resourceVersion=V2&ContentType=JSON&asset=ALL"))
.execute().asJsoup()
val epId = amazonApiJson.toString().substringAfter("\"id\":\"").substringBefore("\"")
val amazonApi = client.newCall(GET("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"))
.execute().asJsoup()
val videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":").substringAfter("tempLink\":\"").substringBefore("\"")
return listOf(Video(videoUrl, "$prefix Amazon", videoUrl))
}
private fun extractNewExtractorLinks(doc: Document, htmlContent: String): List<Pair<String, String>>? {
val links = mutableListOf<Pair<String, String>>()
val jsLinksMatch = getFirstMatch("""dataLink = (\[.+?\]);""".toRegex(), htmlContent) ?: return null
val items = Json.decodeFromString<List<Item>>(jsLinksMatch)
val idiomas = mapOf("LAT" to "[LAT]", "ESP" to "[CAST]", "SUB" to "[SUB]")
items.forEach { item ->
val languageCode = idiomas[item.video_language] ?: "unknown"
item.sortedEmbeds.forEach { embed ->
val decryptedLink = CryptoAES.decrypt(embed.link, "Ak7qrvvH4WKYxV2OgaeHAEg2a5eh16vE")
links.add(Pair(decryptedLink, languageCode))
}
}
return links.ifEmpty { null }
}
private fun getFirstMatch(regex: Regex, input: String): String? {
return regex.find(input)?.groupValues?.get(1)
}
// ============================== Filters ===============================
override val fetchGenres = false
override fun getFilterList() = FlixLatamFilters.FILTER_LIST
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = FlixLatamFilters.getSearchParameters(filters)
val path = when {
params.genre.isNotBlank() -> {
if (params.genre in listOf("ratings", "tendencias", "pelicula")) {
"/${params.genre}"
} else {
"/genero/${params.genre}"
}
}
else -> buildString {
append(
when {
query.isNotBlank() -> "/?s=$query"
else -> "/"
},
)
if (params.isInverted) append("&orden=asc")
}
}
return if (path.startsWith("/?s=")) {
GET("$baseUrl/page/$page$path")
} else {
GET("$baseUrl$path/page/$page")
}
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
super.setupPreferenceScreen(screen) // Quality preference
val langPref = ListPreference(screen.context).apply {
key = PREF_LANG_KEY
title = PREF_LANG_TITLE
entries = PREF_LANG_ENTRIES
entryValues = PREF_LANG_VALUES
setDefaultValue(PREF_LANG_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()
}
}
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)
screen.addPreference(langPref)
}
// ============================= Utilities ==============================
override fun String.toDate() = 0L
@Serializable
data class Item(
val file_id: Int,
val video_language: String, // Campo nuevo para almacenar el idioma
val sortedEmbeds: List<Embed>,
)
@Serializable
data class Embed(
val servername: String,
val link: String,
val type: String,
)
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(prefQualityKey, prefQualityDefault)!!
val lang = preferences.getString(PREF_LANG_KEY, PREF_LANG_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
return sortedWith(
compareBy(
{ it.quality.contains(lang) },
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
),
).reversed()
}
override val prefQualityValues = arrayOf("480p", "720p", "1080p")
override val prefQualityEntries = prefQualityValues
companion object {
private const val PREF_LANG_KEY = "preferred_lang"
private const val PREF_LANG_TITLE = "Preferred language"
private const val PREF_LANG_DEFAULT = "[LAT]"
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Uqload"
private val PREF_LANG_ENTRIES = arrayOf("[LAT]", "[SUB]", "[CAST]")
private val PREF_LANG_VALUES = arrayOf("[LAT]", "[SUB]", "[CAST]")
private val SERVER_LIST = arrayOf("StreamWish", "Uqload", "VidGuard", "StreamHideVid", "Voe")
}
}

View file

@ -0,0 +1,108 @@
package eu.kanade.tachiyomi.animeextension.es.flixlatam
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object FlixLatamFilters {
open class UriPartFilter(
displayName: String,
private val vals: Array<Pair<String, String>>,
) : AnimeFilter.Select<String>(
displayName,
vals.map { it.first }.toTypedArray(),
) {
fun toUriPart() = vals[state].second
}
private inline fun <reified R> AnimeFilterList.getFirst(): R {
return first { it is R } as R
}
private inline fun <reified R> AnimeFilterList.asUriPart(): String {
return getFirst<R>().let {
(it as UriPartFilter).toUriPart()
}
}
class GenreFilter : UriPartFilter("Generos", FlixLatamFiltersData.GENRES)
val FILTER_LIST get() = AnimeFilterList(
GenreFilter(),
)
data class FilterSearchParams(
val isInverted: Boolean = false,
val genre: String = "",
val year: String = "",
)
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
return FilterSearchParams(
genre = filters.asUriPart<GenreFilter>(),
)
}
private object FlixLatamFiltersData {
val EVERY = Pair("Seleccionar", "")
val GENRES = arrayOf(
EVERY,
Pair("Acción", "accion"),
Pair("Action & Adventure", "action-adventure"),
Pair("Adolescencia", "adolescencia"),
Pair("Animación", "animacion"),
Pair("Anime", "anime"),
Pair("Artes Marciales", "artes-marciales"),
Pair("Aventura", "aventura"),
Pair("Aventuras", "aventuras"),
Pair("Bélica", "belica"),
Pair("Bélico", "belico"),
Pair("Ciencia Ficción", "ciencia-ficcion"),
Pair("Comedia", "comedia"),
Pair("Comedia de Situación", "comedia-de-situacion"),
Pair("Crimen", "crimen"),
Pair("Deporte", "deporte"),
Pair("Dibujo Animado", "dibujo-animado"),
Pair("Documental", "documental"),
Pair("Drama", "drama"),
Pair("Drama Adolescente", "drama-adolescente"),
Pair("Drama Médico", "drama-medico"),
Pair("Familia", "familia"),
Pair("Fantasía", "fantasia"),
Pair("Ficción Histórica", "ficcion-historica"),
Pair("Hentai", "hentai"),
Pair("Historia", "historia"),
Pair("Humor Negro", "humor-negro"),
Pair("Infantil", "infantil"),
Pair("Kids", "kids"),
Pair("Misterio", "misterio"),
Pair("Música", "musica"),
Pair("Película de TV", "pelicula-de-tv"),
Pair("Política", "politica"),
Pair("Psicológico", "psicologico"),
Pair("Reality", "reality"),
Pair("Romance", "romance"),
Pair("Sátira", "satira"),
Pair("Sci-Fi & Fantasy", "sci-fi-fantasy"),
Pair("Sexualidad y Pornografía", "sexualidad-y-pornografia"),
Pair("Soap", "soap"),
Pair("Sobrenatural", "sobrenatural"),
Pair("Superhéroes", "superheroes"),
Pair("Suspense", "suspense"),
Pair("Suspenso", "suspenso"),
Pair("Talk", "talk"),
Pair("Telecomedia", "telecomedia"),
Pair("Telenovela", "telenovela"),
Pair("Terror", "terror"),
Pair("TV Asiática", "tv-asiatica"),
Pair("TV Latina", "tv-latina"),
Pair("War & Politics", "war-politics"),
Pair("Western", "western"),
)
}
}