Initial commit

This commit is contained in:
almightyhak 2024-06-20 11:54:12 +07:00
commit 98ed7e8839
2263 changed files with 108711 additions and 0 deletions

View file

@ -0,0 +1,24 @@
ext {
extName = 'Animefenix'
extClass = '.Animefenix'
extVersionCode = 38
}
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'))
}

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,414 @@
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.AnimeFilter
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.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 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://www.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 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("article.serie-card")
val nextPage = document.select("ul.pagination-list li a.pagination-link:contains(Siguiente)").any()
val animeList = elements.map { element ->
SAnime.create().apply {
setUrlWithoutDomain(element.select("figure.image a").attr("abs:href"))
title = element.select("div.title h3 a").text()
thumbnail_url = element.select("figure.image a img").attr("abs:src")
description = element.select("div.serie-card__information p").text()
}
}
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 yearFilter = filters.find { it is YearFilter } as YearFilter
val stateFilter = filters.find { it is StateFilter } as StateFilter
val typeFilter = filters.find { it is TypeFilter } as TypeFilter
val orderByFilter = filters.find { it is OrderByFilter } as OrderByFilter
val genreFilter = (filters.find { it is TagFilter } as TagFilter).state.filter { it.state }
var filterUrl = "$baseUrl/animes?"
if (query.isNotBlank()) {
filterUrl += "&q=$query"
} // search by name
if (genreFilter.isNotEmpty()) {
genreFilter.forEach {
filterUrl += "&genero[]=${it.name}"
}
} // search by genre
if (yearFilter.state.isNotBlank()) {
filterUrl += "&year[]=${yearFilter.state}"
} // search by year
if (stateFilter.state != 0) {
filterUrl += "&estado[]=${stateFilter.toUriPart()}"
} // search by state
if (typeFilter.state != 0) {
filterUrl += "&type[]=${typeFilter.toUriPart()}"
} // search by type
filterUrl += "&order=${orderByFilter.toUriPart()}"
filterUrl += "&page=$page" // add page
return when {
genreFilter.isEmpty() || yearFilter.state.isNotBlank() ||
stateFilter.state != 0 || typeFilter.state != 0 || query.isNotBlank() -> GET(filterUrl, 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("ul.anime-page__episode-list.is-size-6 li").map { it ->
val epNum = it.select("a span").text().replace("Episodio", "")
SEpisode.create().apply {
episode_number = epNum.toFloat()
name = "Episodio $epNum"
setUrlWithoutDomain(it.select("a").attr("abs:href"))
}
}
}
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
val servers = document.selectFirst("script:containsData(var tabsArray)")!!.data()
.split("tabsArray").map { it.substringAfter("src='").substringBefore("'").replace("amp;", "") }
.filter { it.contains("https") }
servers.forEach { 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 {
if (embedUrl.contains("voe")) {
VoeExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if ((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))
}
}
}
if (embedUrl.contains("ok.ru") || embedUrl.contains("okru")) {
OkruExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if (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)
}
if (embedUrl.contains("uqload")) {
UqloadExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("mp4upload")) {
Mp4uploadExtractor(client).videosFromUrl(url, headers).let { videoList.addAll(it) }
}
if (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)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
DoodExtractor(client).videoFromUrl(url, "DoodStream", false)?.let { videoList.add(it) }
}
if (embedUrl.contains("streamlare")) {
StreamlareExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
if (embedUrl.contains("yourupload") || embedUrl.contains("upload")) {
YourUploadExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("burstcloud") || embedUrl.contains("burst")) {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("fastream")) {
FastreamExtractor(client, headers).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
if (embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape")) {
StreamTapeExtractor(client).videoFromUrl(url)?.let { videoList.add(it) }
}
if (embedUrl.contains("ahvsh") || embedUrl.contains("streamhide")) {
StreamHideVidExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
if (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))
}
}
if (embedUrl.contains("filelions") || embedUrl.contains("lion")) {
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "FileLions:$it" }).also(videoList::addAll)
}
} 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 {
val document = response.asJsoup()
return SAnime.create().apply {
title = document.select("h1.title.has-text-orange").text()
genre = document.select("a.button.is-small.is-orange.is-outlined.is-roundedX").joinToString { it.text() }
status = parseStatus(document.select("div.column.is-12-mobile.xis-3-tablet.xis-3-desktop.xhas-background-danger.is-narrow-tablet.is-narrow-desktop a").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 = AnimeFilterList(
TagFilter("Generos", checkboxesFrom(genreList)),
StateFilter(),
TypeFilter(),
OrderByFilter(),
YearFilter(),
)
private val genreList = arrayOf(
Pair("Acción", "acción"),
Pair("Aventura", "aventura"),
Pair("Angeles", "angeles"),
Pair("Artes Marciales", "artes-marciales"),
Pair("Ciencia Ficcion", "ciencia-ficcion"),
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", "fantasía"),
Pair("Gore", "gore"),
Pair("Harem", "harem"),
Pair("Historico", "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", "música"),
Pair("Ninjas", "ninjas"),
Pair("Parodias", "parodias"),
Pair("Policia", "policia"),
Pair("Psicológico", "psicológico"),
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("Shonen", "shonen"),
Pair("Slice of life", "slice-of-life"),
Pair("Sobrenatural", "sobrenatural"),
Pair("Space", "space"),
Pair("Spokon", "spokon"),
Pair("SteamPunk", "steampunk"),
Pair("SuperPoder", "superpoder"),
Pair("Vampiros", "vampiros"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
)
private fun checkboxesFrom(tagArray: Array<Pair<String, String>>): List<TagCheckBox> = tagArray.map { TagCheckBox(it.second) }
class TagCheckBox(tag: String) : AnimeFilter.CheckBox(tag, false)
class TagFilter(name: String, checkBoxes: List<TagCheckBox>) : AnimeFilter.Group<TagCheckBox>(name, checkBoxes)
private class YearFilter : AnimeFilter.Text("Año")
private class StateFilter : UriPartFilter(
"Estado",
arrayOf(
Pair("<Seleccionar>", ""),
Pair("Emision", "1"),
Pair("Finalizado", "2"),
Pair("Proximamente", "3"),
Pair("En Cuarentena", "4"),
),
)
private class TypeFilter : UriPartFilter(
"Tipo",
arrayOf(
Pair("<Seleccionar>", ""),
Pair("TV", "tv"),
Pair("Pelicula", "movie"),
Pair("Especial", "special"),
Pair("OVA", "ova"),
),
)
private class OrderByFilter : UriPartFilter(
"Ordenar Por",
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"),
),
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
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)
}
}

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
}
}
}

View file

@ -0,0 +1,14 @@
ext {
extName = 'AnimeFLV'
extClass = '.AnimeFlv'
extVersionCode = 55
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:yourupload-extractor'))
implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:okru-extractor'))
implementation(project(':lib:streamwish-extractor'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View file

@ -0,0 +1,340 @@
package eu.kanade.tachiyomi.animeextension.es.animeflv
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.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import kotlin.Exception
class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "AnimeFLV"
override val baseUrl = "https://www3.animeflv.net"
override val lang = "es"
override val supportsLatest = true
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
companion object {
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "720"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "StreamWish"
private val SERVER_LIST = arrayOf("StreamWish", "YourUpload", "Okru", "Streamtape")
}
override fun popularAnimeSelector(): String = "div.Container ul.ListAnimes li article"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/browse?order=rating&page=$page")
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.select("div.Description a.Button").attr("abs:href"))
anime.title = element.select("a h3").text()
anime.thumbnail_url = try {
element.select("a div.Image figure img").attr("src")
} catch (e: Exception) {
element.select("a div.Image figure img").attr("data-cfsrc")
}
anime.description = element.select("div.Description p:eq(2)").text().removeSurrounding("\"")
return anime
}
override fun popularAnimeNextPageSelector(): String = "ul.pagination li a[rel=\"next\"]"
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val episodeList = mutableListOf<SEpisode>()
document.select("script").forEach { script ->
if (script.data().contains("var anime_info =")) {
val animeInfo = script.data().substringAfter("var anime_info = [").substringBefore("];")
val arrInfo = json.decodeFromString<List<String>>("[$animeInfo]")
val animeUri = arrInfo[2]!!.replace("\"", "")
val episodes = script.data().substringAfter("var episodes = [").substringBefore("];").trim()
val arrEpisodes = episodes.split("],[")
arrEpisodes!!.forEach { arrEp ->
val noEpisode = arrEp!!.replace("[", "")!!.replace("]", "")!!.split(",")!![0]
val ep = SEpisode.create()
val url = "$baseUrl/ver/$animeUri-$noEpisode"
ep.setUrlWithoutDomain(url)
ep.name = "Episodio $noEpisode"
ep.episode_number = noEpisode.toFloat()
episodeList.add(ep)
}
}
}
return episodeList
}
override fun episodeListSelector() = "uwu"
override fun episodeFromElement(element: Element) = throw UnsupportedOperationException()
/*--------------------------------Video extractors------------------------------------*/
private val streamTapeExtractor by lazy { StreamTapeExtractor(client) }
private val okruExtractor by lazy { OkruExtractor(client) }
private val yourUploadExtractor by lazy { YourUploadExtractor(client) }
private val streamWishExtractor by lazy { StreamWishExtractor(client, headers.newBuilder().add("Referer", "$baseUrl/").build()) }
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val jsonString = document.selectFirst("script:containsData(var videos = {)")?.data() ?: return emptyList()
val responseString = jsonString.substringAfter("var videos =").substringBefore(";").trim()
return json.decodeFromString<ServerModel>(responseString).sub.parallelCatchingFlatMapBlocking {
when (it.title) {
"Stape" -> listOf(streamTapeExtractor.videoFromUrl(it.url ?: it.code)!!)
"Okru" -> okruExtractor.videosFromUrl(it.url ?: it.code)
"YourUpload" -> yourUploadExtractor.videoFromUrl(it.url ?: it.code, headers = headers)
"SW" -> streamWishExtractor.videosFromUrl(it.url ?: it.code, videoNameGen = { "StreamWish:$it" })
else -> emptyList()
}
}
}
override fun videoListSelector() = throw UnsupportedOperationException()
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
val stateFilter = filterList.find { it is StateFilter } as StateFilter
val typeFilter = filterList.find { it is TypeFilter } as TypeFilter
val orderByFilter = filterList.find { it is OrderByFilter } as OrderByFilter
var uri = "$baseUrl/browse?"
uri += if (query.isNotBlank()) "&q=$query" else ""
uri += if (genreFilter.state != 0) "&genre[]=${genreFilter.toUriPart()}" else ""
uri += if (stateFilter.state != 0) "&status[]=${stateFilter.toUriPart()}" else ""
uri += if (typeFilter.state != 0) "&type[]=${typeFilter.toUriPart()}" else ""
uri += "&order=${orderByFilter.toUriPart()}"
uri += "&page=$page"
return when {
query.isNotBlank() || genreFilter.state != 0 || stateFilter.state != 0 || orderByFilter.state != 0 || typeFilter.state != 0 -> GET(uri)
else -> GET("$baseUrl/browse?page=$page&order=rating")
}
}
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenreFilter(),
StateFilter(),
TypeFilter(),
OrderByFilter(),
)
private class GenreFilter : UriPartFilter(
"Géneros",
arrayOf(
Pair("<Selecionar>", "all"),
Pair("Todo", "all"),
Pair("Acción", "accion"),
Pair("Artes Marciales", "artes_marciales"),
Pair("Aventuras", "aventura"),
Pair("Carreras", "carreras"),
Pair("Ciencia Ficción", "ciencia_ficcion"),
Pair("Comedia", "comedia"),
Pair("Demencia", "demencia"),
Pair("Demonios", "demonios"),
Pair("Deportes", "deportes"),
Pair("Drama", "drama"),
Pair("Ecchi", "ecchi"),
Pair("Escolares", "escolares"),
Pair("Espacial", "espacial"),
Pair("Fantasía", "fantasia"),
Pair("Harem", "harem"),
Pair("Historico", "historico"),
Pair("Infantil", "infantil"),
Pair("Josei", "josei"),
Pair("Juegos", "juegos"),
Pair("Magia", "magia"),
Pair("Mecha", "mecha"),
Pair("Militar", "militar"),
Pair("Misterio", "misterio"),
Pair("Música", "musica"),
Pair("Parodia", "parodia"),
Pair("Policía", "policia"),
Pair("Psicológico", "psicologico"),
Pair("Recuentos de la vida", "recuentos_de_la_vida"),
Pair("Romance", "romance"),
Pair("Samurai", "samurai"),
Pair("Seinen", "seinen"),
Pair("Shoujo", "shoujo"),
Pair("Shounen", "shounen"),
Pair("Sobrenatural", "sobrenatural"),
Pair("Superpoderes", "superpoderes"),
Pair("Suspenso", "suspenso"),
Pair("Terror", "terror"),
Pair("Vampiros", "vampiros"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
),
)
private class StateFilter : UriPartFilter(
"Estado",
arrayOf(
Pair("<Seleccionar>", ""),
Pair("En emisión", "1"),
Pair("Finalizado", "2"),
Pair("Próximamente", "3"),
),
)
private class TypeFilter : UriPartFilter(
"Tipo",
arrayOf(
Pair("<Seleccionar>", ""),
Pair("TV", "tv"),
Pair("Película", "movie"),
Pair("Especial", "special"),
Pair("OVA", "ova"),
),
)
private class OrderByFilter : UriPartFilter(
"Ordenar Por",
arrayOf(
Pair("Por defecto", "default"),
Pair("Recientemente Actualizados", "updated"),
Pair("Recientemente Agregados", "added"),
Pair("Nombre A-Z", "title"),
Pair("Calificación", "rating"),
),
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.thumbnail_url = document.selectFirst("div.AnimeCover div.Image figure img")!!.attr("abs:src")
anime.title = document.selectFirst("div.Ficha.fchlt div.Container .Title")!!.text()
anime.description = document.selectFirst("div.Description")!!.text().removeSurrounding("\"")
anime.genre = document.select("nav.Nvgnrs a").joinToString { it.text() }
anime.status = parseStatus(document.select("span.fa-tv").text())
return anime
}
private fun parseStatus(statusString: String): Int {
return when {
statusString.contains("En emision") -> SAnime.ONGOING
statusString.contains("Finalizado") -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
}
}
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/browse?order=added&page=$page")
override fun latestUpdatesSelector() = popularAnimeSelector()
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 setupPreferenceScreen(screen: PreferenceScreen) {
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)
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)
}
@Serializable
data class ServerModel(
@SerialName("SUB")
val sub: List<Sub> = emptyList(),
)
@Serializable
data class Sub(
val server: String? = "",
val title: String? = "",
val ads: Long? = null,
val url: String? = null,
val code: String = "",
@SerialName("allow_mobile")
val allowMobile: Boolean? = false,
)
}

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View file

@ -0,0 +1,365 @@
package eu.kanade.tachiyomi.animeextension.es.animeid
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.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Date
class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "AnimeID"
override val baseUrl = "https://www.animeid.tv/"
override val lang = "es"
override val supportsLatest = true
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override fun popularAnimeSelector(): String = "#result article.item"
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/series?sort=views&pag=$page")
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(baseUrl + element.select("a").attr("href"))
anime.title = element.select("a header").text()
anime.thumbnail_url = element.select("a figure img").attr("src")
anime.description = element.select("p div").text().removeSurrounding("\"")
return anime
}
override fun popularAnimeNextPageSelector(): String = "#paginas ul li:nth-last-child(2) a"
override fun episodeListSelector() = throw UnsupportedOperationException()
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val animeId = document.select("#ord").attr("data-id")
return episodeJsonParse(response.request.url.toString(), animeId)
}
private fun episodeJsonParse(url: String, animeId: String): MutableList<SEpisode> {
val capList = mutableListOf<SEpisode>()
var nextPage = 1
do {
val headers = headers.newBuilder()
.set("Referer", url)
.set("sec-fetch-site", "same-origin")
.set("x-requested-with", "XMLHttpRequest")
.set("User-Agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0")
.set("Accept-Language", "es-MX,es-419;q=0.9,es;q=0.8,en;q=0.7")
.build()
val responseString = client.newCall(GET("https://www.animeid.tv/ajax/caps?id=$animeId&ord=DESC&pag=$nextPage", headers))
.execute().asJsoup().body()!!.toString().substringAfter("<body>").substringBefore("</body>")
val jObject = json.decodeFromString<JsonObject>(responseString)
val listCaps = jObject["list"]!!.jsonArray
listCaps!!.forEach { cap ->
val capParsed = cap.jsonObject
val epNum = capParsed["numero"]!!.jsonPrimitive.content!!.toFloat()
val episode = SEpisode.create()
val dateUpload = manualDateParse(capParsed["date"]!!.jsonPrimitive.content!!.toString())
episode.episode_number = epNum
episode.name = "Episodio $epNum"
dateUpload!!.also { episode.date_upload = it }
episode.setUrlWithoutDomain(baseUrl + capParsed["href"]!!.jsonPrimitive.content!!.toString())
capList.add(episode)
}
if (listCaps!!.any()) nextPage += 1 else nextPage = -1
} while (nextPage != -1)
return capList
}
private fun manualDateParse(stringDate: String): Long? {
return try {
val format = SimpleDateFormat("dd MMM yyyy")
format.parse(stringDate!!.toString()).time
} catch (e: Exception) {
var dateParsed = stringDate.split(" ")
val arrMonths = arrayOf("Jun", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
val day = dateParsed[0]!!.trim().toInt()
val month = arrMonths.indexOf(dateParsed[1].trim()) + 1
val year = dateParsed[2]!!.trim().toInt()
Date(year, month, day).time
}
}
override fun episodeFromElement(element: Element) = throw UnsupportedOperationException()
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
document.select("#partes div.container li.subtab div.parte").forEach { script ->
val jsonString = script.attr("data")
val jsonUnescape = unescapeJava(jsonString)!!.replace("\\", "")
val url = jsonUnescape.substringAfter("src=\"").substringBefore("\"").replace("\\\\", "\\")
if (url.contains("streamtape")) {
StreamTapeExtractor(client).videoFromUrl(url)?.let { videoList.add(it) }
}
}
return videoList
}
private fun unescapeJava(escaped: String): String {
var escaped = escaped
if (escaped.indexOf("\\u") == -1) return escaped
var processed = ""
var position = escaped.indexOf("\\u")
while (position != -1) {
if (position != 0) processed += escaped.substring(0, position)
val token = escaped.substring(position + 2, position + 6)
escaped = escaped.substring(position + 6)
processed += token.toInt(16).toChar()
position = escaped.indexOf("\\u")
}
processed += escaped
return processed
}
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.thumbnail_url = document.selectFirst("#anime figure img.cover")!!.attr("abs:src")
anime.title = document.selectFirst("#anime section hgroup h1")!!.text()
anime.description = document.selectFirst("#anime section p.sinopsis")!!.text().removeSurrounding("\"")
anime.genre = document.select("#anime section ul.tags li a").joinToString { it.text() }
anime.status = parseStatus(document.select("div.main div section div.status-left div.cuerpo div:nth-child(2) span").text().trim())
return anime
}
override fun videoListSelector() = throw UnsupportedOperationException()
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
val orderByFilter = filters.find { it is OrderByFilter } as OrderByFilter
return when {
query.isNotBlank() -> GET("$baseUrl/buscar?q=$query&pag=$page&sort=${orderByFilter.toUriPart()}")
genreFilter.state != 0 -> GET("$baseUrl/genero/${genreFilter.toUriPart()}?pag=$page&sort=${orderByFilter.toUriPart()}")
orderByFilter.state != 0 -> GET("$baseUrl/series?sort=${orderByFilter.toUriPart()}&pag=$page")
else -> popularAnimeRequest(page)
}
}
override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenreFilter(),
OrderByFilter(),
)
private class GenreFilter : UriPartFilter(
"Géneros",
arrayOf(
Pair("<Selecionar>", ""),
Pair("+18", "18"),
Pair("Acción", "accion"),
Pair("Animación", "animacion"),
Pair("Arte", "arte"),
Pair("Artes Marciales", "artes-marciales"),
Pair("Aventura", "aventura"),
Pair("Bizarro", "bizarro"),
Pair("Carreras", "carreras"),
Pair("Ciencia Ficción", "ciencia-ficcion"),
Pair("Colegialas", "colegialas"),
Pair("Comedia", "comedia"),
Pair("Concert", "concert"),
Pair("Cyberpunk", "cyberpunk"),
Pair("Demonios", "demonios"),
Pair("Deportes", "deportes"),
Pair("Drama", "drama"),
Pair("Ecchi", "ecchi"),
Pair("Escolares", "escolares"),
Pair("Fantasía", "fantasia"),
Pair("Fútbol", "futbol"),
Pair("Game", "game"),
Pair("Gore", "gore"),
Pair("Guerra", "guerra"),
Pair("Harem", "harem"),
Pair("Histórico", "historico"),
Pair("Horror", "horror"),
Pair("Idol", "idol"),
Pair("Infantil", "infantil"),
Pair("invier", "invier"),
Pair("Invierno 2013", "invierno-2013"),
Pair("Invierno 2014", "invierno-2014"),
Pair("Invierno 2015", "invierno-2015"),
Pair("Invierno 2016", "invierno-2016"),
Pair("Invierno 2017", "invierno-2017"),
Pair("Invierno 2019", "invierno-2019"),
Pair("Invierno 2020", "invierno-2020"),
Pair("Invierno 2021", "invierno-2021"),
Pair("Invierno 2022", "invierno-2022"),
Pair("Invierno-2018", "invierno-2018"),
Pair("Josei", "josei"),
Pair("Juegos", "juegos"),
Pair("Juegos De Mesa", "juegos-de-mesa"),
Pair("Kids", "kids"),
Pair("Loli", "loli"),
Pair("Lucha", "lucha"),
Pair("Mafia", "mafia"),
Pair("Magia", "magia"),
Pair("Mahou Shōjo", "mahou-shojo"),
Pair("Mecha", "mecha"),
Pair("Militar", "militar"),
Pair("Misterio", "misterio"),
Pair("Música", "musica"),
Pair("Otoño 2012", "otono-2012"),
Pair("Otoño 2013", "otono-2013"),
Pair("Otoño 2014", "otono-2014"),
Pair("Otoño 2015", "otono-2015"),
Pair("Otoño 2016", "otono-2016"),
Pair("Otoño 2018", "otono-2018"),
Pair("Otoño 2019", "otono-2019"),
Pair("Otoño 2020", "otono-2020"),
Pair("Otoño 2021", "otono-2021"),
Pair("otono-2017", "otono-2017"),
Pair("Pantsu", "pantsu"),
Pair("Parodia", "parodia"),
Pair("Policía", "policia"),
Pair("Post Apocalitico", "post-apocalitico"),
Pair("prima", "prima"),
Pair("Primavera 2013", "primavera-2013"),
Pair("Primavera 2014", "primavera-2014"),
Pair("Primavera 2015", "primavera-2015"),
Pair("Primavera 2016", "primavera-2016"),
Pair("Primavera 2017", "primavera-2017"),
Pair("primavera 2018", "primavera-2018"),
Pair("Primavera 2019", "primavera-2019"),
Pair("Primavera 2020", "primavera-2020"),
Pair("Primavera 2021", "primavera-2021"),
Pair("Primavera 2022", "primavera-2022"),
Pair("Primvera 2018", "primvera-2018"),
Pair("Psicológico", "psicologico"),
Pair("Recuentos De La Vida", "recuentos-de-la-vida"),
Pair("Romance", "romance"),
Pair("Samurai", "samurai"),
Pair("Sci-Fi", "sci-fi"),
Pair("Seinen", "seinen"),
Pair("Shōjo", "shojo"),
Pair("Shōjo-ai", "shojo-ai"),
Pair("Shōnen", "shonen"),
Pair("Shōnen-ai", "shonen-ai"),
Pair("Shooter", "shooter"),
Pair("Shoujo", "shoujo"),
Pair("Shoujo Ai", "shoujo-ai"),
Pair("Shounen", "shounen"),
Pair("shounen ai", "shounen-ai"),
Pair("Slice Of Life", "slice-of-life"),
Pair("Sobrenatural", "sobrenatural"),
Pair("Super poder", "super-poder"),
Pair("Supernatural", "supernatural"),
Pair("Suspenso", "suspenso"),
Pair("Terror", "terror"),
Pair("Thriller", "thriller"),
Pair("Torneo", "torneo"),
Pair("Tragedia", "tragedia"),
Pair("Vampiros", "vampiros"),
Pair("ver", "ver"),
Pair("vera", "vera"),
Pair("Verano 2013", "verano-2013"),
Pair("Verano 2014", "verano-2014"),
Pair("Verano 2015", "verano-2015"),
Pair("Verano 2016", "verano-2016"),
Pair("Verano 2017", "verano-2017"),
Pair("Verano 2018", "verano-2018"),
Pair("Verano 2019", "verano-2019"),
Pair("Verano 2020", "verano-2020"),
Pair("Verano 2021", "verano-2021"),
Pair("Verano 2022", "verano-2022"),
Pair("Violencia", "violencia"),
Pair("Vocaloid", "vocaloid"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
),
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
private class OrderByFilter : UriPartFilter(
"Ordenar Por",
arrayOf(
Pair("<Seleccionar>", ""),
Pair("Recientes", "newest"),
Pair("A-Z", "asc"),
Pair("Más vistos", "views"),
),
)
private fun parseStatus(statusString: String): Int {
return when {
statusString.contains("En emisión") -> SAnime.ONGOING
statusString.contains("Finalizada") -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
}
}
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/series?sort=newest&pag=$page")
override fun latestUpdatesSelector() = popularAnimeSelector()
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred server"
entries = arrayOf("StreamTape")
entryValues = arrayOf("StreamTape")
setDefaultValue("StreamTape")
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)
}
}

View file

@ -0,0 +1,15 @@
ext {
extName = 'AnimeLatinoHD'
extClass = '.AnimeLatinoHD'
extVersionCode = 32
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:filemoon-extractor'))
implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:okru-extractor'))
implementation(project(':lib:dood-extractor'))
implementation(project(':lib:streamwish-extractor'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -0,0 +1,435 @@
package eu.kanade.tachiyomi.animeextension.es.animelatinohd
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.animelatinohd.extractors.SolidFilesExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
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.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class AnimeLatinoHD : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "AnimeLatinoHD"
override val baseUrl = "https://www.animelatinohd.com"
override val lang = "es"
override val supportsLatest = true
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
companion object {
const val PREF_QUALITY_KEY = "preferred_quality"
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 = "FileLions"
private val SERVER_LIST = arrayOf(
"YourUpload", "BurstCloud", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon",
"Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare",
"FileLions", "StreamHideVid", "SolidFiles", "Od.lk", "CldUp",
)
private const val PREF_LANGUAGE_KEY = "preferred_language"
private const val PREF_LANGUAGE_DEFAULT = "[LAT]"
private val LANGUAGE_LIST = arrayOf("[LAT]", "[SUB]")
}
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/animes/populares")
override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val animeList = mutableListOf<SAnime>()
val url = response.request.url.toString().lowercase()
val hasNextPage = document.select("#__next > main > div > div[class*=\"Animes_paginate\"] a:last-child svg").any()
document.select("script").forEach { script ->
if (script.data().contains("{\"props\":{\"pageProps\":")) {
val jObject = json.decodeFromString<JsonObject>(script.data())
val props = jObject["props"]!!.jsonObject
val pageProps = props["pageProps"]!!.jsonObject
val data = pageProps["data"]!!.jsonObject
if (url.contains("status=1")) {
val latestData = data["data"]!!.jsonArray
latestData.forEach { item ->
val animeItem = item!!.jsonObject
val anime = SAnime.create()
anime.setUrlWithoutDomain(externalOrInternalImg("anime/${animeItem["slug"]!!.jsonPrimitive!!.content}"))
anime.thumbnail_url = "https://image.tmdb.org/t/p/w200${animeItem["poster"]!!.jsonPrimitive!!.content}"
anime.title = animeItem["name"]!!.jsonPrimitive!!.content
animeList.add(anime)
}
} else {
val popularToday = data["popular_today"]!!.jsonArray
popularToday.forEach { item ->
val animeItem = item!!.jsonObject
val anime = SAnime.create()
anime.setUrlWithoutDomain(externalOrInternalImg("anime/${animeItem["slug"]!!.jsonPrimitive!!.content}"))
anime.thumbnail_url = "https://image.tmdb.org/t/p/w200${animeItem["poster"]!!.jsonPrimitive!!.content}"
anime.title = animeItem["name"]!!.jsonPrimitive!!.content
animeList.add(anime)
}
}
}
}
return AnimesPage(animeList, hasNextPage)
}
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/animes?page=$page&status=1")
override fun latestUpdatesParse(response: Response) = popularAnimeParse(response)
override fun animeDetailsParse(response: Response): SAnime {
val document = response.asJsoup()
val newAnime = SAnime.create()
document.select("script").forEach { script ->
if (script.data().contains("{\"props\":{\"pageProps\":")) {
val jObject = json.decodeFromString<JsonObject>(script.data())
val props = jObject["props"]!!.jsonObject
val pageProps = props["pageProps"]!!.jsonObject
val data = pageProps["data"]!!.jsonObject
newAnime.title = data["name"]!!.jsonPrimitive!!.content
newAnime.genre = data["genres"]!!.jsonPrimitive!!.content.split(",").joinToString()
newAnime.description = data["overview"]!!.jsonPrimitive!!.content
newAnime.status = parseStatus(data["status"]!!.jsonPrimitive!!.content)
newAnime.thumbnail_url = "https://image.tmdb.org/t/p/w600_and_h900_bestv2${data["poster"]!!.jsonPrimitive!!.content}"
newAnime.setUrlWithoutDomain(externalOrInternalImg("anime/${data["slug"]!!.jsonPrimitive!!.content}"))
}
}
return newAnime
}
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val episodeList = mutableListOf<SEpisode>()
document.select("script").forEach { script ->
if (script.data().contains("{\"props\":{\"pageProps\":")) {
val jObject = json.decodeFromString<JsonObject>(script.data())
val props = jObject["props"]!!.jsonObject
val pageProps = props["pageProps"]!!.jsonObject
val data = pageProps["data"]!!.jsonObject
val arrEpisode = data["episodes"]!!.jsonArray
arrEpisode.forEach { item ->
val animeItem = item!!.jsonObject
val episode = SEpisode.create()
episode.setUrlWithoutDomain(externalOrInternalImg("ver/${data["slug"]!!.jsonPrimitive!!.content}/${animeItem["number"]!!.jsonPrimitive!!.content!!.toFloat()}"))
episode.episode_number = animeItem["number"]!!.jsonPrimitive!!.content!!.toFloat()
episode.name = "Episodio ${animeItem["number"]!!.jsonPrimitive!!.content!!.toFloat()}"
episodeList.add(episode)
}
}
}
return episodeList
}
private fun parseJsonArray(json: JsonElement?): List<JsonElement> {
val list = mutableListOf<JsonElement>()
json!!.jsonObject!!.entries!!.forEach { list.add(it.value) }
return list
}
private fun fetchUrls(text: String?): List<String> {
if (text.isNullOrEmpty()) return listOf()
val linkRegex = "(https?://(www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_+.~#?&/=]*))".toRegex()
return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList()
}
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
document.select("script").forEach { script ->
if (script.data().contains("{\"props\":{\"pageProps\":")) {
val jObject = json.decodeFromString<JsonObject>(script.data())
val props = jObject["props"]!!.jsonObject
val pageProps = props["pageProps"]!!.jsonObject
val data = pageProps["data"]!!.jsonObject
val playersElement = data["players"]
val players = if (playersElement !is JsonArray) JsonArray(parseJsonArray(playersElement)) else playersElement!!.jsonArray
players.forEach { player ->
val servers = player!!.jsonArray
servers.forEach { server ->
val item = server!!.jsonObject
val request = client.newCall(
GET(
url = "https://api.animelatinohd.com/stream/${item["id"]!!.jsonPrimitive.content}",
headers = headers.newBuilder()
.add("Referer", "https://www.animelatinohd.com/")
.add("authority", "api.animelatinohd.com")
.add("upgrade-insecure-requests", "1")
.build(),
),
).execute()
val locationsDdh = request!!.networkResponse.toString()
fetchUrls(locationsDdh).map { url ->
val language = if (item["languaje"]!!.jsonPrimitive!!.content == "1") "[LAT]" else "[SUB]"
val embedUrl = url.lowercase()
if (embedUrl.contains("filemoon")) {
val vidHeaders = headers.newBuilder()
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
FilemoonExtractor(client).videosFromUrl(url, prefix = "$language Filemoon:", headers = vidHeaders).also(videoList::addAll)
}
if (embedUrl.contains("filelions") || embedUrl.contains("lion")) {
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "$language FileLions:$it" }).also(videoList::addAll)
}
if (embedUrl.contains("streamtape")) {
StreamTapeExtractor(client).videoFromUrl(url, "$language Streamtape")?.let { videoList.add(it) }
}
if (embedUrl.contains("dood")) {
DoodExtractor(client).videoFromUrl(url, "$language DoodStream")?.let { videoList.add(it) }
}
if (embedUrl.contains("okru") || embedUrl.contains("ok.ru")) {
OkruExtractor(client).videosFromUrl(url, language).also(videoList::addAll)
}
if (embedUrl.contains("solidfiles")) {
SolidFilesExtractor(client).videosFromUrl(url, language).also(videoList::addAll)
}
if (embedUrl.contains("od.lk")) {
videoList.add(Video(url, language + "Od.lk", url))
}
if (embedUrl.contains("cldup.com")) {
videoList.add(Video(url, language + "CldUp", url))
}
}
}
}
}
}
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)!!
val lang = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(lang) },
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
val stateFilter = filterList.find { it is StateFilter } as StateFilter
val typeFilter = filterList.find { it is TypeFilter } as TypeFilter
val filterUrl = if (query.isBlank()) {
"$baseUrl/animes?page=$page&genre=${genreFilter.toUriPart()}&status=${stateFilter.toUriPart()}&type=${typeFilter.toUriPart()}"
} else {
"$baseUrl/animes?page=$page&search=$query"
}
return GET(filterUrl)
}
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora los filtros"),
GenreFilter(),
StateFilter(),
TypeFilter(),
)
override fun searchAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val animeList = mutableListOf<SAnime>()
val hasNextPage = document.select("#__next > main > div > div[class*=\"Animes_paginate\"] a:last-child svg").any()
document.select("script").forEach { script ->
if (script.data().contains("{\"props\":{\"pageProps\":")) {
val jObject = json.decodeFromString<JsonObject>(script.data())
val props = jObject["props"]!!.jsonObject
val pageProps = props["pageProps"]!!.jsonObject
val data = pageProps["data"]!!.jsonObject
val arrData = data["data"]!!.jsonArray
arrData.forEach { item ->
val animeItem = item!!.jsonObject
val anime = SAnime.create()
anime.setUrlWithoutDomain(externalOrInternalImg("anime/${animeItem["slug"]!!.jsonPrimitive!!.content}"))
anime.thumbnail_url = "https://image.tmdb.org/t/p/w200${animeItem["poster"]!!.jsonPrimitive!!.content}"
anime.title = animeItem["name"]!!.jsonPrimitive!!.content
animeList.add(anime)
}
}
}
return AnimesPage(animeList, hasNextPage)
}
private class GenreFilter : UriPartFilter(
"Géneros",
arrayOf(
Pair("<Selecionar>", ""),
Pair("Acción", "accion"),
Pair("Aliens", "aliens"),
Pair("Artes Marciales", "artes-marciales"),
Pair("Aventura", "aventura"),
Pair("Ciencia Ficción", "ciencia-ficcion"),
Pair("Comedia", "comedia"),
Pair("Cyberpunk", "cyberpunk"),
Pair("Demonios", "demonios"),
Pair("Deportes", "deportes"),
Pair("Detectives", "detectives"),
Pair("Drama", "drama"),
Pair("Ecchi", "ecchi"),
Pair("Escolar", "escolar"),
Pair("Espacio", "espacio"),
Pair("Fantasía", "fantasia"),
Pair("Gore", "gore"),
Pair("Harem", "harem"),
Pair("Histórico", "historico"),
Pair("Horror", "horror"),
Pair("Josei", "josei"),
Pair("Juegos", "juegos"),
Pair("Kodomo", "kodomo"),
Pair("Magia", "magia"),
Pair("Maho Shoujo", "maho-shoujo"),
Pair("Mecha", "mecha"),
Pair("Militar", "militar"),
Pair("Misterio", "misterio"),
Pair("Musica", "musica"),
Pair("Parodia", "parodia"),
Pair("Policial", "policial"),
Pair("Psicológico", "psicologico"),
Pair("Recuentos De La Vida", "recuentos-de-la-vida"),
Pair("Romance", "romance"),
Pair("Samurais", "samurais"),
Pair("Seinen", "seinen"),
Pair("Shoujo", "shoujo"),
Pair("Shoujo Ai", "shoujo-ai"),
Pair("Shounen", "shounen"),
Pair("Shounen Ai", "shounen-ai"),
Pair("Sobrenatural", "sobrenatural"),
Pair("Soft Hentai", "soft-hentai"),
Pair("Super Poderes", "super-poderes"),
Pair("Suspenso", "suspenso"),
Pair("Terror", "terror"),
Pair("Vampiros", "vampiros"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
),
)
private class StateFilter : UriPartFilter(
"Estado",
arrayOf(
Pair("Todos", ""),
Pair("Finalizado", "0"),
Pair("En emisión", "1"),
),
)
private class TypeFilter : UriPartFilter(
"Tipo",
arrayOf(
Pair("Todos", ""),
Pair("Animes", "tv"),
Pair("Películas", "movie"),
Pair("Especiales", "special"),
Pair("OVAS", "ova"),
Pair("ONAS", "ona"),
),
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
private fun externalOrInternalImg(url: String) = if (url.contains("https")) url else "$baseUrl/$url"
private fun parseStatus(statusString: String): Int {
return when {
statusString.contains("1") -> SAnime.ONGOING
statusString.contains("0") -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
}
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_LANGUAGE_KEY
title = "Preferred language"
entries = LANGUAGE_LIST
entryValues = LANGUAGE_LIST
setDefaultValue(PREF_LANGUAGE_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_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)
}
}

View file

@ -0,0 +1,205 @@
package eu.kanade.tachiyomi.animeextension.es.animelatinohd.extractors
import java.util.regex.Pattern
import kotlin.math.pow
// https://github.com/cylonu87/JsUnpacker
class JsUnpacker(packedJS: String?) {
private var packedJS: String? = null
/**
* Detects whether the javascript is P.A.C.K.E.R. coded.
*
* @return true if it's P.A.C.K.E.R. coded.
*/
fun detect(): Boolean {
val js = packedJS!!.replace(" ", "")
val p = Pattern.compile("eval\\(function\\(p,a,c,k,e,[rd]")
val m = p.matcher(js)
return m.find()
}
/**
* Unpack the javascript
*
* @return the javascript unpacked or null.
*/
fun unpack(): String? {
val js = packedJS
runCatching {
var p =
Pattern.compile("""\}\s*\('(.*)',\s*(.*?),\s*(\d+),\s*'(.*?)'\.split\('\|'\)""", Pattern.DOTALL)
var m = p.matcher(js)
if (m.find() && m.groupCount() == 4) {
val payload = m.group(1).replace("\\'", "'")
val radixStr = m.group(2)
val countStr = m.group(3)
val symtab = m.group(4).split("\\|".toRegex()).toTypedArray()
val radix = radixStr.toIntOrNull() ?: 36
val count = countStr.toIntOrNull() ?: 0
if (symtab.size != count) {
throw Exception("Unknown p.a.c.k.e.r. encoding")
}
val unbase = Unbase(radix)
p = Pattern.compile("\\b\\w+\\b")
m = p.matcher(payload)
val decoded = StringBuilder(payload)
var replaceOffset = 0
while (m.find()) {
val word = m.group(0)
val x = unbase.unbase(word)
var value: String? = null
if (x < symtab.size && x >= 0) {
value = symtab[x]
}
if (value != null && value.isNotEmpty()) {
decoded.replace(m.start() + replaceOffset, m.end() + replaceOffset, value)
replaceOffset += value.length - word.length
}
}
return decoded.toString()
}
}
return null
}
private inner class Unbase(private val radix: Int) {
private val alphabet62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
private val alphabet95 =
" !\"#$%&\\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
private var alphabet: String? = null
private var dictionary: HashMap<String, Int>? = null
fun unbase(str: String): Int {
var ret = 0
if (alphabet == null) {
ret = str.toInt(radix)
} else {
val tmp = StringBuilder(str).reverse().toString()
for (i in tmp.indices) {
ret += (radix.toDouble().pow(i.toDouble()) * dictionary!![tmp.substring(i, i + 1)]!!).toInt()
}
}
return ret
}
init {
if (radix > 36) {
when {
radix < 62 -> {
alphabet = alphabet62.substring(0, radix)
}
radix in 63..94 -> {
alphabet = alphabet95.substring(0, radix)
}
radix == 62 -> {
alphabet = alphabet62
}
radix == 95 -> {
alphabet = alphabet95
}
}
dictionary = HashMap(95)
for (i in 0 until alphabet!!.length) {
dictionary!![alphabet!!.substring(i, i + 1)] = i
}
}
}
}
/**
* @param packedJS javascript P.A.C.K.E.R. coded.
*/
init {
this.packedJS = packedJS
}
companion object {
private val C =
listOf(
0x63,
0x6f,
0x6d,
0x2e,
0x67,
0x6f,
0x6f,
0x67,
0x6c,
0x65,
0x2e,
0x61,
0x6e,
0x64,
0x72,
0x6f,
0x69,
0x64,
0x2e,
0x67,
0x6d,
0x73,
0x2e,
0x61,
0x64,
0x73,
0x2e,
0x4d,
0x6f,
0x62,
0x69,
0x6c,
0x65,
0x41,
0x64,
0x73,
)
private val Z =
listOf(
0x63,
0x6f,
0x6d,
0x2e,
0x66,
0x61,
0x63,
0x65,
0x62,
0x6f,
0x6f,
0x6b,
0x2e,
0x61,
0x64,
0x73,
0x2e,
0x41,
0x64,
)
fun String.load(): String? {
return try {
var load = this
for (q in C.indices) {
if (C[q % 4] > 270) {
load += C[q % 3]
} else {
load += C[q].toChar()
}
}
Class.forName(load.substring(load.length - C.size, load.length)).name
} catch (_: Exception) {
try {
var f = C[2].toChar().toString()
for (w in Z.indices) {
f += Z[w].toChar()
}
return Class.forName(f.substring(0b001, f.length)).name
} catch (_: Exception) {
null
}
}
}
}
}

View file

@ -0,0 +1,27 @@
package eu.kanade.tachiyomi.animeextension.es.animelatinohd.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
}
}
}

View file

@ -0,0 +1,23 @@
ext {
extName = 'AnimeMovil'
extClass = '.AnimeMovil'
extVersionCode = 14
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:voe-extractor'))
implementation(project(':lib:burstcloud-extractor'))
implementation(project(':lib:mp4upload-extractor'))
implementation(project(':lib:streamwish-extractor'))
implementation(project(':lib:yourupload-extractor'))
implementation(project(':lib:fastream-extractor'))
implementation(project(':lib:upstream-extractor'))
implementation(project(':lib:filemoon-extractor'))
implementation(project(':lib:uqload-extractor'))
implementation(project(':lib:dood-extractor'))
implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:playlist-utils'))
implementation(project(':lib:streamlare-extractor'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

View file

@ -0,0 +1,473 @@
package eu.kanade.tachiyomi.animeextension.es.animemovil
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.AnimeFilter
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.streamlareextractor.StreamlareExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
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.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.io.UnsupportedEncodingException
import java.net.URLEncoder
class AnimeMovil : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "AnimeMovil"
override val baseUrl = "https://animemeow.xyz"
override val lang = "es"
private val json: Json by injectLazy()
override val supportsLatest = false
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
companion object {
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 = "Voe"
private val SERVER_LIST = arrayOf(
"PlusTube", "PlusVid", "PlusIm", "PlusWish", "PlusHub", "PlusDex",
"YourUpload", "Voe", "StreamWish", "Mp4Upload", "Doodstream",
"Uqload", "BurstCloud", "Upstream", "StreamTape", "PlusFilm",
"Fastream", "FileLions",
)
}
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/directorio/?p=$page", headers)
override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val elements = document.select(".grid-animes article")
val nextPage = document.select(".pagination .right:not(.disabledd) .page-link").any()
val animeList = elements.map { element ->
SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a")?.attr("abs:href") ?: "")
title = element.selectFirst("a > p")?.text() ?: ""
thumbnail_url = element.selectFirst("a .main-img img")?.attr("abs:src") ?: ""
status = when (element.select("a .figure-title > p").text().trim()) {
"Finalizado" -> SAnime.COMPLETED
"En emision" -> SAnime.ONGOING
else -> SAnime.UNKNOWN
}
}
}
return AnimesPage(animeList, nextPage)
}
override fun latestUpdatesRequest(page: Int) = popularAnimeRequest(page)
override fun latestUpdatesParse(response: Response) = popularAnimeParse(response)
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
val statusFilter = filterList.find { it is StatusFilter } as StatusFilter
val typeFilter = filterList.find { it is TypeFilter } as TypeFilter
val languageFilter = filterList.find { it is LanguageFilter } as LanguageFilter
val params = HashMap<String, String>()
if (genreFilter.state != 0) {
params["genero"] = genreFilter.toUriPart()
}
if (statusFilter.state != 0) {
params["estado"] = statusFilter.toUriPart()
}
if (typeFilter.state != 0) {
params["tipo"] = typeFilter.toUriPart()
}
if (languageFilter.state != 0) {
params["idioma"] = languageFilter.toUriPart()
}
params["p"] = "$page"
return when {
query.isNotBlank() -> GET("$baseUrl/directorio/?p=$page&q=$query", headers)
else -> GET("$baseUrl/directorio/?${urlEncodeUTF8(params)}", headers)
}
}
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
override fun animeDetailsParse(response: Response): SAnime {
val document = response.asJsoup()
return SAnime.create().apply {
title = document.selectFirst(".banner-info div.titles h1")?.text() ?: ""
description = document.select("#sinopsis").text()
thumbnail_url = document.selectFirst("#anime_image")?.attr("abs:src")
genre = document.select(".generos-wrap .item").joinToString { it.text() }
status = when (document.select(".banner-img .estado").text().trim()) {
"Finalizado" -> SAnime.COMPLETED
"En emision" -> SAnime.ONGOING
else -> SAnime.UNKNOWN
}
}
}
override fun episodeListParse(response: Response): List<SEpisode> {
val episodes = mutableListOf<SEpisode>()
val document = response.asJsoup()
val seasons = document.select(".temporadas-lista .btn-temporada")
if (seasons.any()) {
val token = try {
response.headers.first { it.first == "set-cookie" && it.second.startsWith("csrftoken") }
.second.substringAfter("=").substringBefore(";").replace("%3D", "=")
} catch (_: Exception) { "" }
seasons.reversed().map {
val sid = it.attr("data-sid")
val t = it.attr("data-t")
val mediaType = "application/json".toMediaType()
val requestBody = "{\"show\": \"$sid\",\"temporada\": \"$t\"}"
val request = Request.Builder()
.url("https://animemeow.xyz/api/obtener_episodios_x_temporada/")
.post(requestBody.toRequestBody(mediaType))
.header("authority", response.request.url.host)
.header("origin", "https://${response.request.url.host}")
.header("referer", response.request.url.toString())
.header("x-csrftoken", token)
.header("x-requested-with", "XMLHttpRequest")
.header("cookie", "csrftoken=$token")
.header("Content-Type", "application/json")
.build()
client.newCall(request).execute().asJsoup().let {
json.decodeFromString<EpisodesDto>(it.body().text()).episodios.forEachIndexed { idx, it ->
val episode = SEpisode.create().apply {
setUrlWithoutDomain(it.url)
name = "T$t - " + it.epNombre.replace("Ver", "").trim()
episode_number = idx.toFloat()
}
episodes.add(episode)
}
}
}
} else {
document.select("#eps li > a").reversed().forEachIndexed { idx, it ->
val nameEp = it.selectFirst("p")?.ownText() ?: ""
val episode = SEpisode.create().apply {
setUrlWithoutDomain(it.attr("abs:href"))
name = nameEp.replace("Ver", "").trim()
episode_number = idx.toFloat()
}
episodes.add(episode)
}
}
return episodes
}
private fun fetchUrls(text: String?): List<String> {
if (text.isNullOrEmpty()) return listOf()
val linkRegex = "(http|ftp|https):\\/\\/([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:\\/~+#-]*[\\w@?^=%&\\/~+#-])".toRegex()
return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList()
}
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
document.select("#fuentes button").forEach {
try {
val url = it.attr("data-url").substringAfter("redirect.php?id=").trim()
if (url.contains("php?id=")) {
val serverName = it.ownText().trim()
val serverDocument = client.newCall(GET(url)).execute().asJsoup()
val fileData = serverDocument.selectFirst("script:containsData(sources: [{file:)")?.data() ?: ""
val genericFiles = fetchUrls(fileData)
if (genericFiles.any()) {
genericFiles.forEach { fileSrc ->
if (fileSrc.contains(".m3u8")) {
videoList.add(Video(fileSrc, "$serverName:HLS", fileSrc, headers = null))
}
if (fileSrc.contains(".mp4")) {
videoList.add(Video(fileSrc, "$serverName:MP4", fileSrc, headers = null))
}
}
} else {
serverVideoResolver(url).let { videoList.addAll(it) }
}
} else {
serverVideoResolver(url).let { videoList.addAll(it) }
}
} catch (_: Exception) {}
}
return videoList
}
private fun serverVideoResolver(url: String): List<Video> {
val videoList = mutableListOf<Video>()
val embedUrl = url.lowercase()
try {
if (embedUrl.contains("voe")) {
VoeExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) {
FilemoonExtractor(client).videosFromUrl(url, prefix = "Filemoon:").also(videoList::addAll)
}
if (embedUrl.contains("uqload")) {
UqloadExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("mp4upload")) {
val newHeaders = headers.newBuilder().add("referer", "https://re.animepelix.net/").build()
Mp4uploadExtractor(client).videosFromUrl(url, newHeaders).also(videoList::addAll)
}
if (embedUrl.contains("wishembed") || embedUrl.contains("streamwish") || embedUrl.contains("wish")) {
val docHeaders = headers.newBuilder()
.add("Referer", "$baseUrl/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" }).also(videoList::addAll)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
DoodExtractor(client).videoFromUrl(url, "DoodStream", false)?.let { videoList.add(it) }
}
if (embedUrl.contains("streamlare")) {
StreamlareExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("yourupload")) {
YourUploadExtractor(client).videoFromUrl(url, headers = headers).also(videoList::addAll)
}
if (embedUrl.contains("burstcloud") || embedUrl.contains("burst")) {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).also(videoList::addAll)
}
if (embedUrl.contains("fastream")) {
FastreamExtractor(client, headers).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("streamtape")) {
StreamTapeExtractor(client).videoFromUrl(url)?.also(videoList::add)
}
if (embedUrl.contains("filelions") || embedUrl.contains("lion")) {
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "FileLions:$it" }).also(videoList::addAll)
}
} 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 getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenreFilter(),
TypeFilter(),
StatusFilter(),
LanguageFilter(),
)
private class GenreFilter : UriPartFilter(
"Géneros",
arrayOf(
Pair("Seleccionar", ""),
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"),
),
)
private class TypeFilter : UriPartFilter(
"Tipos",
arrayOf(
Pair("Seleccionar", ""),
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"),
),
)
private class StatusFilter : UriPartFilter(
"Estados",
arrayOf(
Pair("Seleccionar", ""),
Pair("Finalizado", "1"),
Pair("En emision", "2"),
Pair("Proximamente", "3"),
),
)
private class LanguageFilter : UriPartFilter(
"Idioma",
arrayOf(
Pair("Seleccionar", ""),
Pair("Japonés", "1"),
Pair("Latino", "2"),
),
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
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)
}
@Serializable
data class EpisodesDto(
val autenticado: Boolean,
val episodios: List<Episodio>,
)
@Serializable
data class Episodio(
val id: Long,
@SerialName("ep_nombre")
val epNombre: String,
val url: String,
)
private fun urlEncodeUTF8(s: String?): String? {
return try {
URLEncoder.encode(s, "UTF-8")
} catch (e: UnsupportedEncodingException) {
throw UnsupportedOperationException()
}
}
private fun urlEncodeUTF8(map: Map<*, *>): String? {
val sb = StringBuilder()
for ((key, value) in map) {
if (sb.isNotEmpty()) {
sb.append("&")
}
sb.append(String.format("%s=%s", urlEncodeUTF8(key.toString()), urlEncodeUTF8(value.toString())))
}
return sb.toString()
}
}

View file

@ -0,0 +1,17 @@
ext {
extName = 'AnimeOnline.Ninja'
extClass = '.AnimeOnlineNinja'
themePkg = 'dooplay'
baseUrl = 'https://ww3.animeonline.ninja'
overrideVersionCode = 38
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:dood-extractor'))
implementation(project(':lib:filemoon-extractor'))
implementation(project(':lib:mixdrop-extractor'))
implementation(project(':lib:uqload-extractor'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

View file

@ -0,0 +1,266 @@
package eu.kanade.tachiyomi.animeextension.es.animeonlineninja
import androidx.preference.CheckBoxPreference
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.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.api.get
class AnimeOnlineNinja : DooPlay(
"es",
"AnimeOnline.Ninja",
"https://ww3.animeonline.ninja",
) {
override val client by lazy {
if (preferences.getBoolean(PREF_VRF_INTERCEPT_KEY, PREF_VRF_INTERCEPT_DEFAULT)) {
network.client.newBuilder()
.addInterceptor(VrfInterceptor())
.build()
} else {
network.client
}
}
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/tendencias/$page")
override fun popularAnimeSelector() = latestUpdatesSelector()
override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector()
// =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = AnimeOnlineNinjaFilters.getSearchParameters(filters)
val path = when {
params.genre.isNotBlank() -> {
if (params.genre in listOf("tendencias", "ratings")) {
"/" + params.genre
} else {
"/genero/${params.genre}"
}
}
params.language.isNotBlank() -> "/genero/${params.language}"
params.year.isNotBlank() -> "/release/${params.year}"
params.movie.isNotBlank() -> {
if (params.movie == "pelicula") {
"/pelicula"
} else {
"/genero/${params.movie}"
}
}
else -> buildString {
append(
when {
query.isNotBlank() -> "/?s=$query"
params.letter.isNotBlank() -> "/letra/${params.letter}/?"
else -> "/tendencias/?"
},
)
append(
if (contains("tendencias")) {
"&get=${when (params.type){
"serie" -> "TV"
"pelicula" -> "movies"
else -> "todos"
}}"
} else {
"&tipo=${params.type}"
},
)
if (params.isInverted) append("&orden=asc")
}
}
return if (path.startsWith("/?s=")) {
GET("$baseUrl/page/$page$path")
} else if (path.startsWith("/letra") || path.startsWith("/tendencias")) {
val before = path.substringBeforeLast("/")
val after = path.substringAfterLast("/")
GET(baseUrl + before + "/page/$page/" + after)
} else {
GET("$baseUrl$path/page/$page")
}
}
// ============================== Episodes ==============================
override val episodeMovieText = "Película"
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val players = document.select("ul#playeroptionsul li")
return players.flatMap { player ->
val name = player.selectFirst("span.title")!!.text()
val url = getPlayerUrl(player)
extractVideos(url, name)
}
}
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
private val doodExtractor by lazy { DoodExtractor(client) }
private val streamTapeExtractor by lazy { StreamTapeExtractor(client) }
private val mixDropExtractor by lazy { MixDropExtractor(client) }
private val uqloadExtractor by lazy { UqloadExtractor(client) }
private fun extractVideos(url: String, lang: String): List<Video> {
return when {
"saidochesto.top" in url || "MULTISERVER" in lang.uppercase() ->
extractFromMulti(url)
"filemoon" in url ->
filemoonExtractor.videosFromUrl(url, "$lang Filemoon - ", headers)
"dood" in url ->
doodExtractor.videoFromUrl(url, "$lang DoodStream", false)
?.let(::listOf)
"streamtape" in url ->
streamTapeExtractor.videoFromUrl(url, "$lang StreamTape")
?.let(::listOf)
"mixdrop" in url ->
mixDropExtractor.videoFromUrl(url, lang)
"uqload" in url ->
uqloadExtractor.videosFromUrl(url)
"wolfstream" in url -> {
client.newCall(GET(url, headers)).execute()
.asJsoup()
.selectFirst("script:containsData(sources)")
?.data()
?.let { jsData ->
val videoUrl = jsData.substringAfter("{file:\"").substringBefore("\"")
listOf(Video(videoUrl, "$lang WolfStream", videoUrl, headers = headers))
}
}
else -> null
} ?: emptyList<Video>()
}
private fun extractFromMulti(url: String): List<Video> {
val document = client.newCall(GET(url)).execute().asJsoup()
val prefLang = preferences.getString(PREF_LANG_KEY, PREF_LANG_DEFAULT)!!
val langSelector = when {
prefLang.isBlank() -> "div"
else -> "div.OD_$prefLang"
}
return document.select("div.ODDIV $langSelector > li").flatMap {
val hosterUrl = it.attr("onclick").toString()
.substringAfter("('")
.substringBefore("')")
val lang = when (langSelector) {
"div" -> {
it.parent()?.attr("class").toString()
.substringAfter("OD_", "")
.substringBefore(" ")
}
else -> prefLang
}
extractVideos(hosterUrl, lang)
}
}
private fun getPlayerUrl(player: Element): String {
val type = player.attr("data-type")
val id = player.attr("data-post")
val num = player.attr("data-nume")
return client.newCall(GET("$baseUrl/wp-json/dooplayer/v1/post/$id?type=$type&source=$num"))
.execute()
.let { response ->
response.body.string()
.substringAfter("\"embed_url\":\"")
.substringBefore("\",")
.replace("\\", "")
}
}
// =========================== Anime Details ============================
override fun Document.getDescription(): String {
return select("$additionalInfoSelector div.wp-content p")
.eachText()
.joinToString("\n")
}
override val additionalInfoItems = listOf("Título", "Temporadas", "Episodios", "Duración media")
// =============================== Latest ===============================
override val latestUpdatesPath = "episodio"
override fun latestUpdatesNextPageSelector() = "div.pagination > *:last-child:not(span):not(.current)"
// ============================== Filters ===============================
override val fetchGenres = false
override fun getFilterList() = AnimeOnlineNinjaFilters.FILTER_LIST
// ============================== Settings ==============================
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()
}
}
val vrfIterceptPref = CheckBoxPreference(screen.context).apply {
key = PREF_VRF_INTERCEPT_KEY
title = PREF_VRF_INTERCEPT_TITLE
summary = PREF_VRF_INTERCEPT_SUMMARY
setDefaultValue(PREF_VRF_INTERCEPT_DEFAULT)
}
screen.addPreference(vrfIterceptPref)
screen.addPreference(langPref)
}
// ============================= Utilities ==============================
override fun String.toDate() = 0L
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(prefQualityKey, prefQualityDefault)!!
val lang = preferences.getString(PREF_LANG_KEY, PREF_LANG_DEFAULT)!!
return sortedWith(
compareBy(
{ it.quality.contains(lang) },
{ 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 = "SUB"
private val PREF_LANG_ENTRIES = arrayOf("SUB", "All", "ES", "LAT")
private val PREF_LANG_VALUES = arrayOf("SUB", "", "ES", "LAT")
private const val PREF_VRF_INTERCEPT_KEY = "vrf_intercept"
private const val PREF_VRF_INTERCEPT_TITLE = "Intercept VRF links (Requiere Reiniciar)"
private const val PREF_VRF_INTERCEPT_SUMMARY = "Intercept VRF links and open them in the browser"
private const val PREF_VRF_INTERCEPT_DEFAULT = false
}
}

View file

@ -0,0 +1,127 @@
package eu.kanade.tachiyomi.animeextension.es.animeonlineninja
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object AnimeOnlineNinjaFilters {
open class UriPartFilter(
displayName: String,
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 InvertedResultsFilter : AnimeFilter.CheckBox("Invertir resultados", false)
class TypeFilter : UriPartFilter("Tipo", AnimesOnlineNinjaData.TYPES)
class LetterFilter : UriPartFilter("Filtrar por letra", AnimesOnlineNinjaData.LETTERS)
class GenreFilter : UriPartFilter("Generos", AnimesOnlineNinjaData.GENRES)
class LanguageFilter : UriPartFilter("Idiomas", AnimesOnlineNinjaData.LANGUAGES)
class YearFilter : UriPartFilter("Año", AnimesOnlineNinjaData.YEARS)
class MovieFilter : UriPartFilter("Peliculas", AnimesOnlineNinjaData.MOVIES)
class OtherOptionsGroup : AnimeFilter.Group<UriPartFilter>(
"Otros filtros",
listOf(
GenreFilter(),
LanguageFilter(),
YearFilter(),
MovieFilter(),
),
)
private inline fun <reified R> AnimeFilter.Group<UriPartFilter>.getItemUri(): String {
return state.first { it is R }.toUriPart()
}
val FILTER_LIST get() = AnimeFilterList(
InvertedResultsFilter(),
TypeFilter(),
LetterFilter(),
AnimeFilter.Separator(),
AnimeFilter.Header("Estos filtros no afectan a la busqueda por texto"),
OtherOptionsGroup(),
)
data class FilterSearchParams(
val isInverted: Boolean = false,
val type: String = "",
val letter: String = "",
val genre: String = "",
val language: String = "",
val year: String = "",
val movie: String = "",
)
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
val others = filters.getFirst<OtherOptionsGroup>()
return FilterSearchParams(
filters.getFirst<InvertedResultsFilter>().state,
filters.asUriPart<TypeFilter>(),
filters.asUriPart<LetterFilter>(),
others.getItemUri<GenreFilter>(),
others.getItemUri<LanguageFilter>(),
others.getItemUri<YearFilter>(),
others.getItemUri<MovieFilter>(),
)
}
private object AnimesOnlineNinjaData {
val EVERY = Pair("Seleccionar", "")
val TYPES = arrayOf(
Pair("Todos", "todos"),
Pair("Series", "serie"),
Pair("Peliculas", "pelicula"),
)
val LETTERS = arrayOf(EVERY) + ('a'..'z').map {
Pair(it.toString(), it.toString())
}.toTypedArray()
val GENRES = arrayOf(
EVERY,
Pair("Sin Censura \uD83D\uDD1E", "sin-censura"),
Pair("En emisión ⏩", "en-emision"),
Pair("Blu-Ray / DVD \uD83D\uDCC0", "blu-ray-dvd"),
Pair("Próximamente", "proximamente"),
Pair("Live Action \uD83C\uDDEF\uD83C\uDDF5", "live-action"),
Pair("Popular en la web \uD83D\uDCAB", "tendencias"),
Pair("Mejores valorados ⭐", "ratings"),
)
val LANGUAGES = arrayOf(
EVERY,
Pair("Audio Latino \uD83C\uDDF2\uD83C\uDDFD", "audio-latino"),
Pair("Audio Castellano \uD83C\uDDEA\uD83C\uDDF8", "anime-castellano"),
)
val YEARS = arrayOf(EVERY) + (2024 downTo 1979).map {
Pair(it.toString(), it.toString())
}.toTypedArray()
val MOVIES = arrayOf(
EVERY,
Pair("Anime ㊗️", "pelicula"),
Pair("Live Action \uD83C\uDDEF\uD83C\uDDF5", "live-action"),
)
}
}

View file

@ -0,0 +1,40 @@
package eu.kanade.tachiyomi.animeextension.es.animeonlineninja
import app.cash.quickjs.QuickJs
import eu.kanade.tachiyomi.network.GET
import okhttp3.Interceptor
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import org.jsoup.Jsoup
class VrfInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
val respBody = response.body.string()
if (response.headers["Content-Type"]?.contains("image") == true) {
return chain.proceed(request)
}
val body = if (respBody.contains("One moment, please")) {
val parsed = Jsoup.parse(respBody)
val js = parsed.selectFirst("script:containsData(west=)")!!.data()
val west = js.substringAfter("west=").substringBefore(",")
val east = js.substringAfter("east=").substringBefore(",")
val form = parsed.selectFirst("form#wsidchk-form")!!.attr("action")
val eval = evalJs(west, east)
val getLink = "https://" + request.url.host + form + "?wsidchk=$eval"
chain.proceed(GET(getLink)).body
} else {
respBody.toResponseBody(response.body.contentType())
}
return response.newBuilder().body(body).build()
}
private fun evalJs(west: String, east: String): String {
return QuickJs.create().use { qjs ->
val jscript = """$west + $east;"""
qjs.evaluate(jscript).toString()
}
}
}

View file

@ -0,0 +1,10 @@
ext {
extName = 'Animeyt'
extClass = '.Animeyt'
extVersionCode = 10
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:fastream-extractor'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View file

@ -0,0 +1,188 @@
package eu.kanade.tachiyomi.animeextension.es.animeyt
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.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class Animeyt : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "AnimeYT"
override val baseUrl = "https://ytanime.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 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 = "Fastream"
private val SERVER_LIST = arrayOf("Fastream")
}
override fun popularAnimeSelector(): String = "div.video-block div.row div.col-md-2 div.video-card"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/mas-populares?page=$page")
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.select("div.video-card div.video-card-body div.video-title a").attr("href"))
anime.title = element.select("div.video-card div.video-card-body div.video-title a").text()
anime.thumbnail_url = element.select("div.video-card div.video-card-image a:nth-child(2) img").attr("src")
return anime
}
override fun popularAnimeNextPageSelector(): String = "ul.pagination li.page-item:last-child a"
override fun episodeListSelector() = "#caps ul.list-group li.list-group-item a"
override fun episodeFromElement(element: Element): SEpisode {
val episode = SEpisode.create()
val epNum = getNumberFromEpsString(element.select("span.sa-series-link__number").text())
episode.setUrlWithoutDomain(element.attr("href"))
val epParsed = when {
epNum.isNotEmpty() -> epNum.toFloatOrNull() ?: 1F
else -> 1F
}
episode.episode_number = epParsed
episode.name = "Episodio $epParsed"
return episode
}
private fun getNumberFromEpsString(epsStr: String): String {
return epsStr.filter { it.isDigit() }
}
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
document.select("#plays iframe").forEach { container ->
val server = container.attr("src")
.split(".")[0]
.replace("https://", "")
.replace("http://", "")
var url = container.attr("src")
if (server == "fastream") {
if (url.contains("emb.html")) {
val key = url.split("/").last()
url = "https://fastream.to/embed-$key.html"
}
FastreamExtractor(client, headers).videosFromUrl(url).also(videoList::addAll)
}
}
return videoList
}
override fun videoListSelector() = throw UnsupportedOperationException()
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
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 searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = GET("$baseUrl/search?q=$query&page=$page")
override fun searchAnimeFromElement(element: Element): SAnime {
return popularAnimeFromElement(element)
}
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.thumbnail_url = document.selectFirst("div.sa-series-dashboard__poster div.sa-layout__line.sa-layout__line--sm div figure.sa-poster__fig img")!!.attr("src")
anime.title = document.selectFirst("#info div.sa-layout__line div div.sa-title-series__title span")!!.html()
anime.description = document.selectFirst("#info div.sa-layout__line p.sa-text")!!.text().removeSurrounding("\"")
// anime.genre = document.select("nav.Nvgnrs a").joinToString { it.text() }
anime.status = parseStatus(document.select("#info > div:nth-child(2) > button").text())
return anime
}
private fun parseStatus(statusString: String): Int {
return when {
statusString.contains("En Emisión") -> SAnime.ONGOING
statusString.contains("Finalizado") -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
}
}
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/ultimos-animes?page=$page")
override fun latestUpdatesSelector() = popularAnimeSelector()
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)
}
}

View file

@ -0,0 +1,15 @@
ext {
extName = 'AnimeYT.es'
extClass = '.AnimeYTES'
themePkg = 'animestream'
baseUrl = 'https://animeyt.es'
overrideVersionCode = 3
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(":lib:okru-extractor"))
implementation(project(":lib:streamtape-extractor"))
implementation(project(":lib:sendvid-extractor"))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View file

@ -0,0 +1,29 @@
package eu.kanade.tachiyomi.animeextension.es.animeytes
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.sendvidextractor.SendvidExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.multisrc.animestream.AnimeStream
class AnimeYTES : AnimeStream(
"es",
"AnimeYT.es",
"https://animeyt.es",
) {
override val animeListUrl = "$baseUrl/tv"
// ============================ Video Links =============================
private val okruExtractor by lazy { OkruExtractor(client) }
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
private val sendvidExtractor by lazy { SendvidExtractor(client, headers) }
override fun getVideoList(url: String, name: String): List<Video> {
return when (name) {
"OK" -> okruExtractor.videosFromUrl(url)
"Stream" -> streamtapeExtractor.videosFromUrl(url)
"Send" -> sendvidExtractor.videosFromUrl(url)
else -> emptyList()
}
}
}

View file

@ -0,0 +1,27 @@
ext {
extName = 'AsiaLiveAction'
extClass = '.AsiaLiveAction'
extVersionCode = 30
}
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:vk-extractor'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -0,0 +1,357 @@
package eu.kanade.tachiyomi.animeextension.es.asialiveaction
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.asialiveaction.extractors.VidGuardExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.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.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.upstreamextractor.UpstreamExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
import eu.kanade.tachiyomi.lib.vkextractor.VkExtractor
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.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.util.Calendar
class AsiaLiveAction : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "AsiaLiveAction"
override val baseUrl = "https://asialiveaction.com"
override val lang = "es"
override val supportsLatest = true
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
companion object {
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 = "FileLions"
private val SERVER_LIST = arrayOf(
"YourUpload", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape",
"Fastream", "Filemoon", "StreamWish", "VidGuard",
"Amazon", "AmazonES", "Fireload", "FileLions",
"vk.com",
)
}
override fun popularAnimeSelector(): String = "div.TpRwCont main section ul.MovieList li.TPostMv article.TPost"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/todos/page/$page")
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.select("a").attr("href"))
anime.title = element.select("a h3.Title").text()
anime.thumbnail_url = element.select("a div.Image figure img").attr("src").trim().replace("//", "https://")
return anime
}
override fun popularAnimeNextPageSelector(): String = "div.TpRwCont main div a.next.page-numbers"
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.thumbnail_url = document.selectFirst("header div.Image figure img")!!.attr("src").trim().replace("//", "https://")
anime.title = document.selectFirst("header div.asia-post-header h1.Title")!!.text()
anime.description = document.selectFirst("header div.asia-post-main div.Description p:nth-child(2), header div.asia-post-main div.Description p")!!.text().removeSurrounding("\"")
anime.genre = document.select("div.asia-post-main p.Info span.tags a").joinToString { it.text() }
val year = document.select("header div.asia-post-main p.Info span.Date a").text().toInt()
val currentYear = Calendar.getInstance().get(Calendar.YEAR)
anime.status = when {
year < currentYear -> SAnime.COMPLETED
year == currentYear -> SAnime.ONGOING
else -> SAnime.UNKNOWN
}
return anime
}
override fun episodeListParse(response: Response): List<SEpisode> {
return super.episodeListParse(response).reversed()
}
override fun episodeListSelector() = "#ep-list div.TPTblCn span a, #ep-list div.TPTblCn .accordion"
override fun episodeFromElement(element: Element): SEpisode {
return if (element.attr("class").contains("accordion")) {
val epNum = getNumberFromEpsString(element.select("label span").text())
SEpisode.create().apply {
name = element.select("label span").text().trim()
episode_number = when {
epNum.isNotEmpty() -> epNum.toFloatOrNull() ?: 1F
else -> 1F
}
setUrlWithoutDomain(element.selectFirst("ul li a")?.attr("abs:href")!!)
}
} else {
val epNum = getNumberFromEpsString(element.select("div.flex-grow-1 p").text())
SEpisode.create().apply {
setUrlWithoutDomain(element.attr("abs:href"))
episode_number = when {
epNum.isNotEmpty() -> epNum.toFloatOrNull() ?: 1F
else -> 1F
}
name = element.select("div.flex-grow-1 p").text().trim()
}
}
}
private fun getNumberFromEpsString(epsStr: String): String {
return epsStr.filter { it.isDigit() }
}
private fun fetchUrls(text: String?): List<String> {
if (text.isNullOrEmpty()) return listOf()
val linkRegex = "(http|ftp|https):\\/\\/([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:\\/~+#-]*[\\w@?^=%&\\/~+#-])".toRegex()
return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList()
}
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
document.select("script:containsData(var videos)").forEach { script ->
fetchUrls(script.data()).map { url ->
try {
serverVideoResolver(url).also(videoList::addAll)
} catch (_: Exception) {}
}
}
return videoList
}
private fun serverVideoResolver(url: String): List<Video> {
val videoList = mutableListOf<Video>()
val embedUrl = url.lowercase()
try {
if (embedUrl.contains("voe")) {
VoeExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if ((embedUrl.contains("amazon") || embedUrl.contains("amz")) && !embedUrl.contains("disable")) {
val body = client.newCall(GET(url)).execute().asJsoup()
if (body.select("script:containsData(var shareId)").toString().isNotBlank()) {
val shareId = body.selectFirst("script:containsData(var shareId)")!!.data()
.substringAfter("shareId = \"").substringBefore("\"")
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("\"")
videoList.add(Video(videoUrl, "Amazon", videoUrl))
}
}
if (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)
}
if (embedUrl.contains("uqload")) {
UqloadExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("mp4upload")) {
Mp4uploadExtractor(client).videosFromUrl(url, headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("wishembed") ||
embedUrl.contains("streamwish") ||
embedUrl.contains("strwish") ||
embedUrl.contains("wish") ||
embedUrl.contains("sfastwish")
) {
val docHeaders = headers.newBuilder()
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" }).also(videoList::addAll)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
val url2 = url.replace("https://doodstream.com/e/", "https://dood.to/e/")
DoodExtractor(client).videoFromUrl(url2, "DoodStream", false)?.let { videoList.add(it) }
}
if (embedUrl.contains("streamlare")) {
StreamlareExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
if (embedUrl.contains("yourupload") || embedUrl.contains("upload")) {
YourUploadExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("burstcloud") || embedUrl.contains("burst")) {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("fastream")) {
FastreamExtractor(client, headers).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
if (embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape")) {
StreamTapeExtractor(client).videoFromUrl(url)?.let { videoList.add(it) }
}
if (embedUrl.contains("ahvsh") || embedUrl.contains("streamhide") || embedUrl.contains("hide")) {
StreamHideVidExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
if (embedUrl.contains("filelions") || embedUrl.contains("lion") || embedUrl.contains("fviplions")) {
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "FileLions:$it" }).also(videoList::addAll)
}
if (embedUrl.contains("vembed") || embedUrl.contains("guard")) {
VidGuardExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("vk")) {
VkExtractor(client, headers).videosFromUrl(url).also(videoList::addAll)
}
} catch (_: Exception) { }
return videoList
}
override fun videoListSelector() = throw UnsupportedOperationException()
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
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 searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
return when {
query.isNotBlank() -> GET("$baseUrl/page/$page/?s=$query")
genreFilter.state != 0 -> GET("$baseUrl/tag/${genreFilter.toUriPart()}/page/$page")
else -> popularAnimeRequest(page)
}
}
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenreFilter(),
)
private class GenreFilter : UriPartFilter(
"Géneros",
arrayOf(
Pair("<Selecionar>", "all"),
Pair("Acción", "accion"),
Pair("Aventura", "aventura"),
Pair("Ciencia Ficción", "ciencia-ficcion"),
Pair("Comedia", "comedia"),
Pair("Drama", "drama"),
Pair("Deporte", "deporte"),
Pair("Erótico", "erotico"),
Pair("Escolar", "escolar"),
Pair("Extraterrestres", "extraterrestres"),
Pair("Fantasía", "fantasia"),
Pair("Histórico", "historico"),
Pair("Horror", "horror"),
Pair("Lucha", "lucha"),
Pair("Misterio", "misterio"),
Pair("Música", "musica"),
Pair("Psicológico", "psicologico"),
Pair("Romance", "romance"),
Pair("Sobrenatural", "sobrenatural"),
Pair("Yaoi / BL", "yaoi-bl"),
Pair("Yuri / GL", "yuri-gl"),
),
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
override fun searchAnimeFromElement(element: Element): SAnime {
return popularAnimeFromElement(element)
}
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesRequest(page: Int) = popularAnimeRequest(page)
override fun latestUpdatesSelector() = popularAnimeSelector()
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)
}
}

View file

@ -0,0 +1,124 @@
package eu.kanade.tachiyomi.animeextension.es.asialiveaction.extractors
import android.app.Application
import android.os.Handler
import android.os.Looper
import android.util.Base64
import android.webkit.JavascriptInterface
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
class VidGuardExtractor(private val client: OkHttpClient) {
private val context: Application by injectLazy()
private val handler by lazy { Handler(Looper.getMainLooper()) }
class JsObject(private val latch: CountDownLatch) {
var payload: String = ""
@JavascriptInterface
fun passPayload(passedPayload: String) {
payload = passedPayload
latch.countDown()
}
}
fun videosFromUrl(url: String): List<Video> {
val doc = client.newCall(GET(url)).execute().asJsoup()
val scriptUrl = doc.selectFirst("script[src*=ad/plugin]")
?.absUrl("src")
?: return emptyList()
val headers = Headers.headersOf("Referer", url)
val script = client.newCall(GET(scriptUrl, headers)).execute()
.body.string()
val sources = getSourcesFromScript(script, url)
.takeIf { it.isNotBlank() && it != "undefined" }
?: return emptyList()
return sources.substringAfter("stream:[").substringBefore("}]")
.split('{')
.drop(1)
.mapNotNull { line ->
val resolution = line.substringAfter("Label\":\"").substringBefore('"')
val videoUrl = line.substringAfter("URL\":\"").substringBefore('"')
.takeIf(String::isNotBlank)
?.let(::fixUrl)
?: return@mapNotNull null
Video(videoUrl, "VidGuard:$resolution", videoUrl, headers)
}
}
private fun getSourcesFromScript(script: String, url: String): String {
val latch = CountDownLatch(1)
var webView: WebView? = null
val jsinterface = JsObject(latch)
handler.post {
val webview = WebView(context)
webView = webview
with(webview.settings) {
javaScriptEnabled = true
domStorageEnabled = true
databaseEnabled = true
useWideViewPort = false
loadWithOverviewMode = false
cacheMode = WebSettings.LOAD_NO_CACHE
}
webview.addJavascriptInterface(jsinterface, "android")
webview.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
view?.clearCache(true)
view?.clearFormData()
view?.evaluateJavascript(script) {}
view?.evaluateJavascript("window.android.passPayload(JSON.stringify(window.svg))") {}
}
}
webview.loadDataWithBaseURL(url, "<html></html>", "text/html", "UTF-8", null)
}
latch.await(5, TimeUnit.SECONDS)
handler.post {
webView?.stopLoading()
webView?.destroy()
webView = null
}
return jsinterface.payload
}
private fun fixUrl(url: String): String {
val httpUrl = url.toHttpUrl()
val originalSign = httpUrl.queryParameter("sig")!!
val newSign = originalSign.chunked(2).joinToString("") {
Char(it.toInt(16) xor 2).toString()
}
.let { String(Base64.decode(it, Base64.DEFAULT)) }
.substring(5)
.chunked(2)
.reversed()
.joinToString("")
.substring(5)
return httpUrl.newBuilder()
.removeAllQueryParameters("sig")
.addQueryParameter("sig", newSign)
.build()
.toString()
}
}

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,281 @@
package eu.kanade.tachiyomi.animeextension.es.beatzanime
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parseAs
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
class BeatZAnime : ParsedAnimeHttpSource() {
override val name = "BeatZ Anime"
override val baseUrl = "https://www.beatz-anime.net"
private val indexHost = "dd.beatz-anime.net"
private val indexHttpUrl = "https://$indexHost".toHttpUrl()
override val lang = "es"
override val supportsLatest = true
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int): Request {
val url = if (page > 1) {
"$baseUrl/emision/pagina=$page"
} else {
"$baseUrl/emision/"
}
return GET(url, headers)
}
override fun popularAnimeSelector(): String = ".row > div:has(a.titulo-largo)"
override fun popularAnimeFromElement(element: Element): SAnime = SAnime.create().apply {
thumbnail_url = element.selectFirst("img")!!.imgAttr()
with(element.selectFirst("a.titulo-largo")!!) {
setUrlWithoutDomain(attr("abs:href"))
title = text()
}
}
override fun popularAnimeNextPageSelector(): String = "ul.pagination > li.active + li:not(.disabled)"
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request {
val url = if (page > 1) {
"$baseUrl/index.php?pagina=$page"
} else {
"$baseUrl/"
}
return GET(url, headers)
}
override fun latestUpdatesSelector(): String = popularAnimeSelector()
override fun latestUpdatesFromElement(element: Element): SAnime = popularAnimeFromElement(element)
override fun latestUpdatesNextPageSelector(): String = popularAnimeNextPageSelector()
// =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val source = filters.filterIsInstance<SourceFilter>().first().getValue()
val status = filters.filterIsInstance<StatusFilter>().first().getValue()
val type = filters.filterIsInstance<TypeFilter>().first().getValue()
val url = "$baseUrl/lista-animes/index.php"
val formBody = FormBody.Builder().apply {
add("buscar", query)
add("fuente", source)
add("estado", status)
add("tipo-anime", type)
}.build()
val formHeaders = headersBuilder().apply {
add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
add("Host", baseUrl.toHttpUrl().host)
add("Origin", baseUrl)
add("Referer", url)
}.build()
return POST(url, formHeaders, formBody)
}
override fun searchAnimeSelector(): String = ".row > div:has(span.titulo)"
override fun searchAnimeFromElement(element: Element): SAnime = SAnime.create().apply {
thumbnail_url = element.selectFirst("img")!!.imgAttr()
with(element.selectFirst("a:has(span)")!!) {
setUrlWithoutDomain(attr("abs:href"))
title = text()
}
}
override fun searchAnimeNextPageSelector(): String? = null
// ============================== Filters ===============================
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
SourceFilter(),
StatusFilter(),
TypeFilter(),
)
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document): SAnime = SAnime.create().apply {
title = document.selectFirst("h1")!!.text()
thumbnail_url = document.selectFirst(".row > div > img")?.imgAttr()
genre = document.selectFirst("p.post-text span:has(b:contains(Generos))")?.ownText()
status = document.selectFirst("div:has(>h5:contains(Estado)) a").parseStatus()
description = buildString {
document.selectFirst("p.post-text")?.textNodes()?.let {
append(it.joinToString("\n\n") { it.text() })
}
append("\n\n")
document.selectFirst("p.post-text span:has(b:contains(Sinónimos))")?.let {
append("Sinónimos: ")
append(it.ownText())
}
}.trim()
}
private fun Element?.parseStatus(): Int = when (this?.text()?.lowercase()) {
"finalizado" -> SAnime.COMPLETED
"en emisión", "en emsión" -> SAnime.ONGOING
else -> SAnime.UNKNOWN
}
// ============================== Episodes ==============================
override fun episodeListSelector(): String = throw UnsupportedOperationException()
override fun episodeFromElement(element: Element): SEpisode =
throw UnsupportedOperationException()
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val episodeList = mutableListOf<SEpisode>()
val indexUrlRaw = document.selectFirst("a[href*=$indexHost]")!!.attr("abs:href").toHttpUrl()
val indexUrl = if (indexUrlRaw.encodedPath.contains("api/raw/")) {
val path = indexUrlRaw.queryParameter("path")!!.substringAfter("/")
.substringBefore("/")
"https://$indexHost/$path/"
} else {
indexUrlRaw.toString()
}
fun traverseFolder(basePath: String, relativePath: String, recursionDepth: Int = 0) {
if (recursionDepth == 2) return
val apiHeaders = headersBuilder().apply {
add("Accept", "application/json, text/plain, */*")
add("Host", indexHost)
add(
"Referer",
indexHttpUrl.newBuilder()
.addPathSegments(basePath)
.build()
.toString(),
)
}.build()
val apiUrl = indexHttpUrl.newBuilder().apply {
addPathSegment("api")
addPathSegment("")
addQueryParameter("path", basePath)
}.build()
val data = client.newCall(
GET(apiUrl, apiHeaders),
).execute().parseAs<IndexResponseDto>()
data.folder.value.forEach { item ->
if (item.folder != null) {
traverseFolder("$basePath/${item.name}", item.name, recursionDepth + 1)
} else if (item.file != null) {
val fileExt = item.name.substringAfterLast(".")
if (!SUPPORTED_FORMATS.any { it.equals(fileExt, true) }) return@forEach
episodeList.add(
SEpisode.create().apply {
name = item.name
url = "$basePath/${item.name}"
scanlator = buildList {
if (relativePath != "") add(relativePath)
add(item.size.formatBytes())
}.joinToString("")
},
)
}
}
}
traverseFolder("/${indexUrl.toHttpUrl().pathSegments.first()}", "")
return episodeList.reversed()
}
@Serializable
class IndexResponseDto(
val folder: FolderDto,
) {
@Serializable
class FolderDto(
val value: List<ItemDto>,
) {
@Serializable
class ItemDto(
val name: String,
val size: Long,
val folder: JsonObject? = null,
val file: JsonObject? = null,
)
}
}
private fun Long.formatBytes(): String = when {
this >= 1_000_000_000 -> "%.2f GB".format(this / 1_000_000_000.0)
this >= 1_000_000 -> "%.2f MB".format(this / 1_000_000.0)
this >= 1_000 -> "%.2f KB".format(this / 1_000.0)
this > 1 -> "$this bytes"
this == 1L -> "$this byte"
else -> ""
}
// ============================ Video Links =============================
override suspend fun getVideoList(episode: SEpisode): List<Video> {
val url = indexHttpUrl.newBuilder().apply {
addPathSegment("api")
addPathSegment("raw")
addPathSegment("")
addQueryParameter("path", episode.url)
}.build().toString()
val path = episode.url.substringAfter("/").substringBeforeLast("/") + "/"
val videoHeaders = headersBuilder().apply {
add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
add("Referer", indexHttpUrl.newBuilder().addPathSegments(path).build().toString())
}.build()
return listOf(Video(url, "Video", url, videoHeaders))
}
override fun videoListSelector() = throw UnsupportedOperationException()
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
// ============================= Utilities ==============================
private fun Element.imgAttr(): String = when {
hasAttr("data-lazy-src") -> attr("abs:data-lazy-src")
hasAttr("data-src") -> attr("abs:data-src")
else -> attr("abs:src")
}
companion object {
private val SUPPORTED_FORMATS = listOf("mp4", "mkv")
}
}

View file

@ -0,0 +1,45 @@
package eu.kanade.tachiyomi.animeextension.es.beatzanime
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
open class UriPartFilter(
name: String,
private val vals: Array<Pair<String, String>>,
defaultValue: String? = null,
) : AnimeFilter.Select<String>(
name,
vals.map { it.first }.toTypedArray(),
vals.indexOfFirst { it.second == defaultValue }.takeIf { it != -1 } ?: 0,
) {
fun getValue(): String {
return vals[state].second
}
}
class SourceFilter : UriPartFilter(
"Status",
arrayOf(
Pair("Todos", ""),
Pair("BDRip", "BDRip"),
Pair("WebRip", "WebRip"),
),
)
class StatusFilter : UriPartFilter(
"Estado",
arrayOf(
Pair("Todos", ""),
Pair("En Emision", "En Emision"),
Pair("Finalizado", "Finalizado"),
Pair("En Proceso", "En Proceso"),
),
)
class TypeFilter : UriPartFilter(
"Tipo",
arrayOf(
Pair("Todos", ""),
Pair("Serie", "Serie"),
Pair("Pelicula", "Pelicula"),
),
)

View file

@ -0,0 +1,26 @@
ext {
extName = 'Cuevana'
extClass = '.CuevanaFactory'
extVersionCode = 31
}
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 "dev.datlag.jsunpacker:jsunpacker:1.0.1"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -0,0 +1,398 @@
package eu.kanade.tachiyomi.animeextension.es.cuevana
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.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.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.streamlareextractor.StreamlareExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
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.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
class CuevanaCh(override val name: String, override val baseUrl: String) : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val lang = "es"
override val supportsLatest = false
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
companion object {
private const val PREF_LANGUAGE_KEY = "preferred_language"
private const val PREF_LANGUAGE_DEFAULT = "[LAT]"
private val LANGUAGE_LIST = arrayOf("[LAT]", "[SUB]", "[CAST]")
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 = "Voe"
private val SERVER_LIST = arrayOf(
"YourUpload", "BurstCloud", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon",
"Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare",
"Tomatomatela",
)
}
override fun popularAnimeSelector(): String = ".MovieList .TPostMv .TPost"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/peliculas?page=$page")
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
anime.title = element.select("a .Title").text()
anime.thumbnail_url = element.select("a .Image figure.Objf img").attr("abs:data-src")
return anime
}
override fun popularAnimeNextPageSelector(): String = "nav.navigation > div.nav-links > a.next.page-numbers"
override fun episodeListParse(response: Response): List<SEpisode> {
val episodes = mutableListOf<SEpisode>()
val document = response.asJsoup()
if (response.request.url.toString().contains("/serie/")) {
document.select("[id*=season-]").reversed().mapIndexed { idxSeason, season ->
val noSeason = try {
season.attr("id").substringAfter("season-").toInt()
} catch (e: Exception) {
idxSeason
}
season.select(".TPostMv article.TPost").reversed().mapIndexed { idxCap, cap ->
val epNum = try {
cap.select("a div.Image span.Year").text().substringAfter("x").toFloat()
} catch (e: Exception) {
idxCap.toFloat()
}
val episode = SEpisode.create()
val date = cap.select("a > p").text()
val epDate = try {
SimpleDateFormat("yyyy-MM-dd").parse(date)
} catch (e: Exception) {
null
}
episode.episode_number = epNum
episode.name = "T$noSeason - Episodio $epNum"
if (epDate != null) episode.date_upload = epDate.time
episode.setUrlWithoutDomain(cap.select("a").attr("href"))
episodes.add(episode)
}
}
} else {
val episode = SEpisode.create().apply {
episode_number = 1f
name = "PELÍCULA"
}
episode.setUrlWithoutDomain(response.request.url.toString())
episodes.add(episode)
}
return episodes.reversed()
}
override fun episodeListSelector() = throw UnsupportedOperationException()
override fun episodeFromElement(element: Element) = throw UnsupportedOperationException()
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
document.select("ul.anime_muti_link li").map {
val langPrefix = try {
val languageTag = it.selectFirst(".cdtr span")!!.text()
if (languageTag.lowercase().contains("latino")) {
"[LAT]"
} else if (languageTag.lowercase().contains("castellano")) {
"[CAST]"
} else if (languageTag.lowercase().contains("subtitulado")) {
"[SUB]"
} else {
""
}
} catch (e: Exception) {
""
}
val url = it.attr("abs:data-video")
try {
serverVideoResolver(url, langPrefix).also(videoList::addAll)
} catch (_: Exception) { }
}
return videoList
}
private fun serverVideoResolver(url: String, prefix: String = ""): List<Video> {
val videoList = mutableListOf<Video>()
val embedUrl = url.lowercase()
try {
if (embedUrl.contains("voe")) {
VoeExtractor(client).videosFromUrl(url, prefix).also(videoList::addAll)
}
if ((embedUrl.contains("amazon") || embedUrl.contains("amz")) && !embedUrl.contains("disable")) {
val body = client.newCall(GET(url)).execute().asJsoup()
if (body.select("script:containsData(var shareId)").toString().isNotBlank()) {
val shareId = body.selectFirst("script:containsData(var shareId)")!!.data()
.substringAfter("shareId = \"").substringBefore("\"")
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("\"")
videoList.add(Video(videoUrl, "$prefix Amazon", videoUrl))
}
}
if (embedUrl.contains("ok.ru") || embedUrl.contains("okru")) {
OkruExtractor(client).videosFromUrl(url, prefix).also(videoList::addAll)
}
if (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 = "$prefix Filemoon:", headers = vidHeaders).also(videoList::addAll)
}
if (embedUrl.contains("uqload")) {
UqloadExtractor(client).videosFromUrl(url, prefix = prefix).also(videoList::addAll)
}
if (embedUrl.contains("mp4upload")) {
Mp4uploadExtractor(client).videosFromUrl(url, headers, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("wishembed") || 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 = { "$prefix StreamWish:$it" }).also(videoList::addAll)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
val url2 = url.replace("https://doodstream.com/e/", "https://dood.to/e/")
DoodExtractor(client).videoFromUrl(url2, "$prefix DoodStream", false)?.let { videoList.add(it) }
}
if (embedUrl.contains("streamlare")) {
StreamlareExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("yourupload") || embedUrl.contains("upload")) {
YourUploadExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("burstcloud") || embedUrl.contains("burst")) {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("fastream")) {
FastreamExtractor(client, headers).videosFromUrl(url, prefix = "$prefix Fastream:").also(videoList::addAll)
}
if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape")) {
StreamTapeExtractor(client).videoFromUrl(url, quality = "$prefix StreamTape")?.let { videoList.add(it) }
}
if (embedUrl.contains("tomatomatela")) {
runCatching {
val mainUrl = url.substringBefore("/embed.html#").substringAfter("https://")
val headers = headers.newBuilder()
.set("authority", mainUrl)
.set("accept", "application/json, text/javascript, */*; q=0.01")
.set("accept-language", "es-MX,es-419;q=0.9,es;q=0.8,en;q=0.7")
.set("sec-ch-ua", "\"Chromium\";v=\"106\", \"Google Chrome\";v=\"106\", \"Not;A=Brand\";v=\"99\"")
.set("sec-ch-ua-mobile", "?0")
.set("sec-ch-ua-platform", "Windows")
.set("sec-fetch-dest", "empty")
.set("sec-fetch-mode", "cors")
.set("sec-fetch-site", "same-origin")
.set("x-requested-with", "XMLHttpRequest")
.build()
val token = url.substringAfter("/embed.html#")
val urlRequest = "https://$mainUrl/details.php?v=$token"
val response = client.newCall(GET(urlRequest, headers = headers)).execute().asJsoup()
val bodyText = response.select("body").text()
val json = json.decodeFromString<JsonObject>(bodyText)
val status = json["status"]!!.jsonPrimitive!!.content
val file = json["file"]!!.jsonPrimitive!!.content
if (status == "200") { videoList.add(Video(file, "$prefix Tomatomatela", file, headers = null)) }
}
}
if (embedUrl.contains("filelions") || embedUrl.contains("lion")) {
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "$prefix FileLions:$it" }).also(videoList::addAll)
}
} catch (_: Exception) { }
return videoList
}
override fun videoListSelector() = throw UnsupportedOperationException()
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
return when {
query.isNotBlank() -> GET("$baseUrl/search.html?keyword=$query&page=$page", headers)
genreFilter.state != 0 -> GET("$baseUrl/category/${genreFilter.toUriPart()}?page=$page")
else -> popularAnimeRequest(page)
}
}
override fun searchAnimeFromElement(element: Element): SAnime {
return popularAnimeFromElement(element)
}
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.title = document.selectFirst(".TPost header .Title")!!.text()
anime.thumbnail_url = document.selectFirst(".backdrop article div.Image figure img")!!.attr("abs:data-src")
anime.description = document.selectFirst(".backdrop article.TPost div.Description")!!.text().trim()
anime.genre = document.select("ul.InfoList li:nth-child(1) > a").joinToString { it.text() }
anime.status = SAnime.UNKNOWN
return anime
}
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException()
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
override fun latestUpdatesSelector() = throw UnsupportedOperationException()
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenreFilter(),
)
private class GenreFilter : UriPartFilter(
"Tipos",
arrayOf(
Pair("<selecionar>", ""),
Pair("Acción", "accion"),
Pair("Animación", "animacion"),
Pair("Aventura", "aventura"),
Pair("Bélico Guerra", "belico-guerra"),
Pair("Biográfia", "biografia"),
Pair("Ciencia Ficción", "ciencia-ficcion"),
Pair("Comedia", "comedia"),
Pair("Crimen", "crimen"),
Pair("Documentales", "documentales"),
Pair("Drama", "drama"),
Pair("Familiar", "familiar"),
Pair("Fantasía", "fantasia"),
Pair("Misterio", "misterio"),
Pair("Musical", "musical"),
Pair("Romance", "romance"),
Pair("Terror", "terror"),
Pair("Thriller", "thriller"),
),
)
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)!!
val lang = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(lang) },
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_LANGUAGE_KEY
title = "Preferred language"
entries = LANGUAGE_LIST
entryValues = LANGUAGE_LIST
setDefaultValue(PREF_LANGUAGE_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_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)
}
}

View file

@ -0,0 +1,399 @@
package eu.kanade.tachiyomi.animeextension.es.cuevana
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.cuevana.models.AnimeEpisodesList
import eu.kanade.tachiyomi.animeextension.es.cuevana.models.PopularAnimeList
import eu.kanade.tachiyomi.animeextension.es.cuevana.models.Videos
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
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 eu.kanade.tachiyomi.util.parallelMapBlocking
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat
class CuevanaEu(override val name: String, override val baseUrl: String) : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val lang = "es"
override val supportsLatest = false
private val json = Json { ignoreUnknownKeys = true }
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
companion object {
private const val PREF_LANGUAGE_KEY = "preferred_language"
private const val PREF_LANGUAGE_DEFAULT = "[LAT]"
private val LANGUAGE_LIST = arrayOf("[LAT]", "[ENG]", "[CAST]", "[JAP]")
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 = "Voe"
private val SERVER_LIST = arrayOf(
"Tomatomatela", "YourUpload", "Doodstream", "Okru",
"Voe", "StreamTape", "StreamWish", "Filemoon",
"FileLions",
)
}
override fun popularAnimeSelector(): String = ".MovieList .TPostMv .TPost"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/peliculas/estrenos/page/$page")
override fun popularAnimeFromElement(element: Element) = throw UnsupportedOperationException()
override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val animeList = mutableListOf<SAnime>()
val hasNextPage = document.select("nav.navigation > div.nav-links > a.next.page-numbers").any()
val script = document.selectFirst("script:containsData({\"props\":{\"pageProps\":{)")!!.data()
val responseJson = json.decodeFromString<PopularAnimeList>(script)
responseJson.props?.pageProps?.movies?.map { animeItem ->
val anime = SAnime.create()
val preSlug = animeItem.url?.slug ?: ""
val type = if (preSlug.startsWith("series")) "ver-serie" else "ver-pelicula"
anime.title = animeItem.titles?.name ?: ""
anime.thumbnail_url = animeItem.images?.poster?.replace("/original/", "/w200/") ?: ""
anime.description = animeItem.overview
anime.setUrlWithoutDomain("/$type/${animeItem.slug?.name}")
animeList.add(anime)
}
return AnimesPage(animeList, hasNextPage)
}
override fun popularAnimeNextPageSelector(): String = "uwu"
override fun episodeListParse(response: Response): List<SEpisode> {
val episodes = mutableListOf<SEpisode>()
val document = response.asJsoup()
if (response.request.url.toString().contains("/ver-serie/")) {
val script = document.selectFirst("script:containsData({\"props\":{\"pageProps\":{)")!!.data()
val responseJson = json.decodeFromString<AnimeEpisodesList>(script)
responseJson.props?.pageProps?.thisSerie?.seasons?.map {
it.episodes.map { ep ->
val episode = SEpisode.create()
val epDate = try {
ep.releaseDate?.substringBefore("T")?.let { date -> SimpleDateFormat("yyyy-MM-dd").parse(date) }
} catch (e: Exception) {
null
}
if (epDate != null) episode.date_upload = epDate.time
episode.name = "T${ep.slug?.season} - Episodio ${ep.slug?.episode}"
episode.episode_number = ep.number?.toFloat()!!
episode.setUrlWithoutDomain("/episodio/${ep.slug?.name}-temporada-${ep.slug?.season}-episodio-${ep.slug?.episode}")
episodes.add(episode)
}
}
} else {
val episode = SEpisode.create().apply {
episode_number = 1f
name = "PELÍCULA"
}
episode.setUrlWithoutDomain(response.request.url.toString())
episodes.add(episode)
}
return episodes.reversed()
}
override fun episodeListSelector() = throw UnsupportedOperationException()
override fun episodeFromElement(element: Element) = throw UnsupportedOperationException()
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
val script = document.selectFirst("script:containsData({\"props\":{\"pageProps\":{)")!!.data()
val responseJson = json.decodeFromString<AnimeEpisodesList>(script)
if (response.request.url.toString().contains("/episodio/")) {
serverIterator(responseJson.props?.pageProps?.episode?.videos).let {
videoList.addAll(it)
}
} else {
serverIterator(responseJson.props?.pageProps?.thisMovie?.videos).let {
videoList.addAll(it)
}
}
return videoList
}
private fun serverIterator(videos: Videos?): MutableList<Video> {
val videoList = mutableListOf<Video>()
videos?.latino?.parallelMapBlocking {
try {
val body = client.newCall(GET(it.result!!)).execute().asJsoup()
val url = body.selectFirst("script:containsData(var message)")?.data()?.substringAfter("var url = '")?.substringBefore("'") ?: ""
loadExtractor(url, "[LAT]").let { videoList.addAll(it) }
} catch (_: Exception) { }
}
videos?.spanish?.map {
try {
val body = client.newCall(GET(it.result!!)).execute().asJsoup()
val url = body.selectFirst("script:containsData(var message)")?.data()?.substringAfter("var url = '")?.substringBefore("'") ?: ""
loadExtractor(url, "[CAST]").let { videoList.addAll(it) }
} catch (_: Exception) { }
}
videos?.english?.map {
try {
val body = client.newCall(GET(it.result!!)).execute().asJsoup()
val url = body.selectFirst("script:containsData(var message)")?.data()?.substringAfter("var url = '")?.substringBefore("'") ?: ""
loadExtractor(url, "[ENG]").let { videoList.addAll(it) }
} catch (_: Exception) { }
}
videos?.japanese?.map {
val body = client.newCall(GET(it.result!!)).execute().asJsoup()
val url = body.selectFirst("script:containsData(var message)")?.data()?.substringAfter("var url = '")?.substringBefore("'") ?: ""
loadExtractor(url, "[JAP]").let { videoList.addAll(it) }
}
return videoList
}
private fun loadExtractor(url: String, prefix: String = ""): List<Video> {
val videoList = mutableListOf<Video>()
val embedUrl = url.lowercase()
if (embedUrl.contains("tomatomatela")) {
try {
val mainUrl = url.substringBefore("/embed.html#").substringAfter("https://")
val headers = headers.newBuilder()
.set("authority", mainUrl)
.set("accept", "application/json, text/javascript, */*; q=0.01")
.set("accept-language", "es-MX,es-419;q=0.9,es;q=0.8,en;q=0.7")
.set("sec-ch-ua", "\"Chromium\";v=\"106\", \"Google Chrome\";v=\"106\", \"Not;A=Brand\";v=\"99\"")
.set("sec-ch-ua-mobile", "?0")
.set("sec-ch-ua-platform", "Windows")
.set("sec-fetch-dest", "empty")
.set("sec-fetch-mode", "cors")
.set("sec-fetch-site", "same-origin")
.set("x-requested-with", "XMLHttpRequest")
.build()
val token = url.substringAfter("/embed.html#")
val urlRequest = "https://$mainUrl/details.php?v=$token"
val response = client.newCall(GET(urlRequest, headers = headers)).execute().asJsoup()
val bodyText = response.select("body").text()
val json = json.decodeFromString<JsonObject>(bodyText)
val status = json["status"]!!.jsonPrimitive!!.content
val file = json["file"]!!.jsonPrimitive!!.content
if (status == "200") { videoList.add(Video(file, "$prefix Tomatomatela", file, headers = null)) }
} catch (_: Exception) { }
}
if (embedUrl.contains("yourupload")) {
val videos = YourUploadExtractor(client).videoFromUrl(url, headers = headers)
videoList.addAll(videos)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
DoodExtractor(client).videoFromUrl(url, "$prefix DoodStream", false)
?.let { videoList.add(it) }
}
if (embedUrl.contains("okru") || embedUrl.contains("ok.ru")) {
OkruExtractor(client).videosFromUrl(url, prefix, true).also(videoList::addAll)
}
if (embedUrl.contains("voe")) {
VoeExtractor(client).videosFromUrl(url, prefix).also(videoList::addAll)
}
if (embedUrl.contains("streamtape")) {
StreamTapeExtractor(client).videoFromUrl(url, "$prefix StreamTape")?.let { videoList.add(it) }
}
if (embedUrl.contains("wishembed") || embedUrl.contains("streamwish") || embedUrl.contains("wish")) {
StreamWishExtractor(client, headers).videosFromUrl(url) { "$prefix StreamWish:$it" }
.also(videoList::addAll)
}
if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) {
FilemoonExtractor(client).videosFromUrl(url, "$prefix Filemoon:").also(videoList::addAll)
}
if (embedUrl.contains("filelions") || embedUrl.contains("lion")) {
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "$prefix FileLions:$it" }).also(videoList::addAll)
}
return videoList
}
override fun videoListSelector() = throw UnsupportedOperationException()
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
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)!!
val lang = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(lang) },
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
return when {
query.isNotBlank() -> GET("$baseUrl/search?q=$query", headers)
genreFilter.state != 0 -> GET("$baseUrl/${genreFilter.toUriPart()}/page/$page")
else -> popularAnimeRequest(page)
}
}
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun animeDetailsParse(document: Document) = throw UnsupportedOperationException()
override fun animeDetailsParse(response: Response): SAnime {
val document = response.asJsoup()
val newAnime = SAnime.create()
val script = document.selectFirst("script:containsData({\"props\":{\"pageProps\":{)")!!.data()
val responseJson = json.decodeFromString<AnimeEpisodesList>(script)
if (response.request.url.toString().contains("/ver-serie/")) {
val data = responseJson.props?.pageProps?.thisSerie
newAnime.status = SAnime.UNKNOWN
newAnime.title = data?.titles?.name ?: ""
newAnime.description = data?.overview ?: ""
newAnime.thumbnail_url = data?.images?.poster?.replace("/original/", "/w500/")
newAnime.genre = data?.genres?.joinToString { it.name ?: "" }
newAnime.artist = data?.cast?.acting?.firstOrNull()?.name ?: ""
newAnime.setUrlWithoutDomain(response.request.url.toString())
} else {
val data = responseJson.props?.pageProps?.thisMovie
newAnime.status = SAnime.UNKNOWN
newAnime.title = data?.titles?.name ?: ""
newAnime.description = data?.overview ?: ""
newAnime.thumbnail_url = data?.images?.poster?.replace("/original/", "/w500/")
newAnime.genre = data?.genres?.joinToString { it.name ?: "" }
newAnime.artist = data?.cast?.acting?.firstOrNull()?.name ?: ""
newAnime.setUrlWithoutDomain(response.request.url.toString())
}
return newAnime
}
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException()
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
override fun latestUpdatesSelector() = throw UnsupportedOperationException()
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenreFilter(),
)
private class GenreFilter : UriPartFilter(
"Tipos",
arrayOf(
Pair("<selecionar>", ""),
Pair("Series", "series/estrenos"),
Pair("Acción", "genero/accion"),
Pair("Aventura", "genero/aventura"),
Pair("Animación", "genero/animacion"),
Pair("Ciencia Ficción", "genero/ciencia-ficcion"),
Pair("Comedia", "genero/comedia"),
Pair("Crimen", "genero/crimen"),
Pair("Documentales", "genero/documental"),
Pair("Drama", "genero/drama"),
Pair("Familia", "genero/familia"),
Pair("Fantasía", "genero/fantasia"),
Pair("Misterio", "genero/misterio"),
Pair("Romance", "genero/romance"),
Pair("Suspenso", "genero/suspense"),
Pair("Terror", "genero/terror"),
),
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_LANGUAGE_KEY
title = "Preferred language"
entries = LANGUAGE_LIST
entryValues = LANGUAGE_LIST
setDefaultValue(PREF_LANGUAGE_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_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)
}
}

View file

@ -0,0 +1,11 @@
package eu.kanade.tachiyomi.animeextension.es.cuevana
import eu.kanade.tachiyomi.animesource.AnimeSource
import eu.kanade.tachiyomi.animesource.AnimeSourceFactory
class CuevanaFactory : AnimeSourceFactory {
override fun createSources(): List<AnimeSource> = listOf(
CuevanaCh("Cuevana3Ch", "https://ww1.cuevana3.ch"),
CuevanaEu("Cuevana3Eu", "https://www.cuevana3.eu"),
)
}

View file

@ -0,0 +1,55 @@
package eu.kanade.tachiyomi.animeextension.es.cuevana.models
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class AnimeEpisodesList(
@SerialName("props") var props: Props? = Props(),
@SerialName("page") var page: String? = null,
@SerialName("query") var query: Query? = Query(),
@SerialName("buildId") var buildId: String? = null,
@SerialName("isFallback") var isFallback: Boolean? = null,
@SerialName("gsp") var gsp: Boolean? = null,
@SerialName("locale") var locale: String? = null,
@SerialName("locales") var locales: ArrayList<String> = arrayListOf(),
@SerialName("defaultLocale") var defaultLocale: String? = null,
@SerialName("scriptLoader") var scriptLoader: ArrayList<String> = arrayListOf(),
)
@Serializable
data class Episodes(
@SerialName("title") var title: String? = null,
@SerialName("TMDbId") var TMDbId: String? = null,
@SerialName("number") var number: Int? = null,
@SerialName("releaseDate") var releaseDate: String? = null,
@SerialName("image") var image: String? = null,
@SerialName("url") var url: Url? = Url(),
@SerialName("slug") var slug: Slug? = Slug(),
)
@Serializable
data class Seasons(
@SerialName("number") var number: Int? = null,
@SerialName("episodes") var episodes: ArrayList<Episodes> = arrayListOf(),
)
@Serializable
data class Original(
@SerialName("name") var name: String? = null,
)
@Serializable
data class ThisSerie(
@SerialName("TMDbId") var TMDbId: String? = null,
@SerialName("seasons") var seasons: ArrayList<Seasons> = arrayListOf(),
@SerialName("titles") var titles: Titles? = Titles(),
@SerialName("images") var images: Images? = Images(),
@SerialName("overview") var overview: String? = null,
@SerialName("genres") var genres: ArrayList<Genres> = arrayListOf(),
@SerialName("cast") var cast: Cast? = Cast(),
@SerialName("rate") var rate: Rate? = Rate(),
@SerialName("url") var url: Url? = Url(),
@SerialName("slug") var slug: Slug? = Slug(),
@SerialName("releaseDate") var releaseDate: String? = null,
)

View file

@ -0,0 +1,176 @@
package eu.kanade.tachiyomi.animeextension.es.cuevana.models
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class PopularAnimeList(
@SerialName("props") var props: Props? = Props(),
@SerialName("page") var page: String? = null,
@SerialName("query") var query: Query? = Query(),
@SerialName("buildId") var buildId: String? = null,
@SerialName("isFallback") var isFallback: Boolean? = null,
@SerialName("gsp") var gsp: Boolean? = null,
@SerialName("locale") var locale: String? = null,
@SerialName("locales") var locales: ArrayList<String> = arrayListOf(),
@SerialName("defaultLocale") var defaultLocale: String? = null,
@SerialName("scriptLoader") var scriptLoader: ArrayList<String> = arrayListOf(),
)
@Serializable
data class Titles(
@SerialName("name") var name: String? = null,
@SerialName("original") var original: Original? = Original(),
)
@Serializable
data class Images(
@SerialName("poster") var poster: String? = null,
@SerialName("backdrop") var backdrop: String? = null,
)
@Serializable
data class Rate(
@SerialName("average") var average: Double? = null,
@SerialName("votes") var votes: Int? = null,
)
@Serializable
data class Genres(
@SerialName("id") var id: String? = null,
@SerialName("slug") var slug: String? = null,
@SerialName("name") var name: String? = null,
)
@Serializable
data class Acting(
@SerialName("id") var id: String? = null,
@SerialName("name") var name: String? = null,
)
@Serializable
data class Cast(
@SerialName("acting") var acting: ArrayList<Acting> = arrayListOf(),
@SerialName("directing") var directing: ArrayList<Directing> = arrayListOf(),
)
@Serializable
data class Url(
@SerialName("slug") var slug: String? = null,
)
@Serializable
data class Slug(
@SerialName("name") var name: String? = null,
@SerialName("season") var season: String? = null,
@SerialName("episode") var episode: String? = null,
)
@Serializable
data class Movies(
@SerialName("titles") var titles: Titles? = Titles(),
@SerialName("images") var images: Images? = Images(),
@SerialName("rate") var rate: Rate? = Rate(),
@SerialName("overview") var overview: String? = null,
@SerialName("TMDbId") var TMDbId: String? = null,
@SerialName("genres") var genres: ArrayList<Genres> = arrayListOf(),
@SerialName("cast") var cast: Cast? = Cast(),
@SerialName("runtime") var runtime: Int? = null,
@SerialName("releaseDate") var releaseDate: String? = null,
@SerialName("url") var url: Url? = Url(),
@SerialName("slug") var slug: Slug? = Slug(),
)
@Serializable
data class PageProps(
@SerialName("thisSerie") var thisSerie: ThisSerie? = ThisSerie(),
@SerialName("thisMovie") var thisMovie: ThisMovie? = ThisMovie(),
@SerialName("movies") var movies: ArrayList<Movies> = arrayListOf(),
@SerialName("pages") var pages: Int? = null,
@SerialName("season") var season: Season? = Season(),
@SerialName("episode") var episode: Episode? = Episode(),
)
@Serializable
data class Props(
@SerialName("pageProps") var pageProps: PageProps? = PageProps(),
@SerialName("__N_SSG") var _NSSG: Boolean? = null,
)
@Serializable
data class Query(
@SerialName("page") var page: String? = null,
@SerialName("serie") var serie: String? = null,
@SerialName("movie") var movie: String? = null,
@SerialName("episode") var episode: String? = null,
@SerialName("q") var q: String? = null,
)
@Serializable
data class Directing(
@SerialName("name") var name: String? = null,
)
@Serializable
data class Server(
@SerialName("cyberlocker") var cyberlocker: String? = null,
@SerialName("result") var result: String? = null,
@SerialName("quality") var quality: String? = null,
)
@Serializable
data class Videos(
@SerialName("latino") var latino: ArrayList<Server> = arrayListOf(),
@SerialName("spanish") var spanish: ArrayList<Server> = arrayListOf(),
@SerialName("english") var english: ArrayList<Server> = arrayListOf(),
@SerialName("japanese") var japanese: ArrayList<Server> = arrayListOf(),
)
@Serializable
data class Downloads(
@SerialName("cyberlocker") var cyberlocker: String? = null,
@SerialName("result") var result: String? = null,
@SerialName("quality") var quality: String? = null,
@SerialName("language") var language: String? = null,
)
@Serializable
data class ThisMovie(
@SerialName("TMDbId") var TMDbId: String? = null,
@SerialName("titles") var titles: Titles? = Titles(),
@SerialName("images") var images: Images? = Images(),
@SerialName("overview") var overview: String? = null,
@SerialName("runtime") var runtime: Int? = null,
@SerialName("genres") var genres: ArrayList<Genres> = arrayListOf(),
@SerialName("cast") var cast: Cast? = Cast(),
@SerialName("rate") var rate: Rate? = Rate(),
@SerialName("url") var url: Url? = Url(),
@SerialName("slug") var slug: Slug? = Slug(),
@SerialName("releaseDate") var releaseDate: String? = null,
@SerialName("videos") var videos: Videos? = Videos(),
@SerialName("downloads") var downloads: ArrayList<Downloads> = arrayListOf(),
)
@Serializable
data class Season(
@SerialName("number") var number: Int? = null,
)
@Serializable
data class NextEpisode(
@SerialName("title") var title: String? = null,
@SerialName("slug") var slug: String? = null,
)
@Serializable
data class Episode(
@SerialName("TMDbId") var TMDbId: String? = null,
@SerialName("title") var title: String? = null,
@SerialName("number") var number: Int? = null,
@SerialName("image") var image: String? = null,
@SerialName("url") var url: Url? = Url(),
@SerialName("slug") var slug: Slug? = Slug(),
@SerialName("nextEpisode") var nextEpisode: NextEpisode? = NextEpisode(),
@SerialName("previousEpisode") var previousEpisode: String? = null,
@SerialName("videos") var videos: Videos? = Videos(),
@SerialName("downloads") var downloads: ArrayList<Downloads> = arrayListOf(),
)

View file

@ -0,0 +1,26 @@
ext {
extName = 'Doramasflix'
extClass = '.Doramasflix'
extVersionCode = 21
}
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'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,193 @@
package eu.kanade.tachiyomi.animeextension.es.doramasflix
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
// -----------------------Season models------------------------//
@Serializable
data class SeasonModel(
val data: DataSeason = DataSeason(),
)
@Serializable
data class DataSeason(
val listSeasons: List<ListSeason> = emptyList(),
)
@Serializable
data class ListSeason(
val slug: String,
@SerialName("season_number")
val seasonNumber: Long,
@SerialName("poster_path")
val posterPath: String?,
@SerialName("air_date")
val airDate: String?,
@SerialName("serie_name")
val serieName: String?,
val poster: String?,
@SerialName("__typename")
val typename: String,
)
// -----------------------Episode Model------------------------//
@Serializable
data class EpisodeModel(
val data: DataEpisode = DataEpisode(),
)
@Serializable
data class DataEpisode(
val listEpisodes: List<ListEpisode> = emptyList(),
)
@Serializable
data class ListEpisode(
@SerialName("_id")
val id: String,
val name: String?,
val slug: String,
@SerialName("serie_name")
val serieName: String?,
@SerialName("serie_name_es")
val serieNameEs: String?,
@SerialName("air_date")
val airDate: String?,
@SerialName("season_number")
val seasonNumber: Long?,
@SerialName("episode_number")
val episodeNumber: Long?,
val poster: String?,
@SerialName("__typename")
val typename: String,
)
// -----------------------Pagination Model------------------------//
@Serializable
data class PaginationModel(
val data: DataPagination = DataPagination(),
)
@Serializable
data class DataPagination(
val paginationDorama: PaginationDorama? = null,
val paginationMovie: PaginationDorama? = null,
)
@Serializable
data class PaginationDorama(
val count: Long,
val pageInfo: PageInfo,
val items: List<Item> = emptyList(),
@SerialName("__typename")
val typename: String,
)
@Serializable
data class PageInfo(
val currentPage: Long,
val hasNextPage: Boolean,
val hasPreviousPage: Boolean,
@SerialName("__typename")
val typename: String,
)
@Serializable
data class Item(
@SerialName("_id")
val id: String,
val name: String,
@SerialName("name_es")
val nameEs: String?,
val slug: String,
val names: String?,
val overview: String?,
@SerialName("poster_path")
val posterPath: String?,
val poster: String?,
val genres: List<Genre> = emptyList(),
@SerialName("__typename")
val typename: String,
)
@Serializable
data class Genre(
val name: String?,
val slug: String?,
@SerialName("__typename")
val typename: String?,
)
// -----------------------Search Model------------------------//
@Serializable
data class SearchModel(
val data: Data = Data(),
)
@Serializable
data class Data(
val searchDorama: List<SearchDorama> = emptyList(),
val searchMovie: List<SearchDorama> = emptyList(),
)
@Serializable
data class SearchDorama(
@SerialName("_id")
val id: String,
val slug: String,
val name: String,
@SerialName("name_es")
val nameEs: String?,
@SerialName("poster_path")
val posterPath: String?,
val poster: String?,
@SerialName("__typename")
val typename: String,
)
// -------------------------------------------------------
@Serializable
data class VideoToken(
val link: String?,
val server: String?,
val app: String?,
val iat: Long?,
val exp: Long?,
)
// ------------------------------------------------
@Serializable
data class TokenModel(
val props: PropsToken = PropsToken(),
val page: String? = null,
val query: QueryToken = QueryToken(),
val buildId: String? = null,
val isFallback: Boolean? = false,
val isExperimentalCompile: Boolean? = false,
val gssp: Boolean? = false,
)
@Serializable
data class PropsToken(
val pageProps: PagePropsToken = PagePropsToken(),
@SerialName("__N_SSP")
val nSsp: Boolean? = false,
)
@Serializable
data class PagePropsToken(
val token: String? = null,
val name: String? = null,
val app: String? = null,
val server: String? = null,
val iosapp: String? = null,
val externalLink: String? = null,
)
@Serializable
data class QueryToken(
val token: String? = null,
)

View file

@ -0,0 +1,592 @@
package eu.kanade.tachiyomi.animeextension.es.doramasflix
import android.app.Application
import android.content.SharedPreferences
import android.util.Base64
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
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.upstreamextractor.UpstreamExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.vudeoextractor.VudeoExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Date
class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "Doramasflix"
override val baseUrl = "https://doramasflix.in"
private val apiUrl = "https://sv1.fluxcedene.net/api/gql"
// The token is made through a type of milliseconds encryption in combination
// with other calculated strings, the milliseconds indicate the expiration date
// of the token, so it was calculated to expire in 100 years.
private val accessPlatform = "RxARncfg1S_MdpSrCvreoLu_SikCGMzE1NzQzODc3NjE2MQ=="
private val mediaType = "application/json; charset=utf-8".toMediaType()
override val lang = "es"
override val supportsLatest = true
private val json: Json by injectLazy()
companion object {
private const val PREF_LANGUAGE_KEY = "preferred_language"
private const val PREF_LANGUAGE_DEFAULT = "[LAT]"
private val LANGUAGE_LIST = arrayOf(
"[ENG]", "[CAST]", "[LAT]", "[SUB]", "[POR]",
"[COR]", "[JAP]", "[MAN]", "[TAI]", "[FIL]",
"[IND]", "[VIET]",
)
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 = "Voe"
private val SERVER_LIST = arrayOf(
"YourUpload", "BurstCloud", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon",
"Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare",
"Uqload",
)
private val DATE_FORMATTER by lazy {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
}
}
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private val popularRequestHeaders = Headers.headersOf(
"authority", "sv1.fluxcedene.net",
"accept", "application/json, text/plain, */*",
"content-type", "application/json;charset=UTF-8",
"origin", "https://doramasflix.in",
"referer", "https://doramasflix.in/",
"platform", "doramasflix",
"authorization", "Bear",
"x-access-jwt-token", "",
"x-access-platform", accessPlatform,
)
private fun externalOrInternalImg(url: String, isThumb: Boolean = false): String {
return if (url.contains("https")) {
url
} else if (isThumb) {
"https://image.tmdb.org/t/p/w220_and_h330_face$url"
} else {
"https://image.tmdb.org/t/p/w500$url"
}
}
override fun animeDetailsParse(response: Response): SAnime {
val document = response.asJsoup()
val anime = SAnime.create()
document.select("script").map { el ->
if (el.data().contains("{\"props\":{\"pageProps\":{")) {
val apolloState = json.decodeFromString<JsonObject>(el.data())!!.jsonObject["props"]!!.jsonObject["pageProps"]!!.jsonObject["apolloState"]!!.jsonObject
val dorama = apolloState!!.entries!!.firstOrNull { (key, _) -> Regex("\\b(?:Movie|Dorama):[a-zA-Z0-9]+").matches(key) }!!.value!!.jsonObject
val genres = try { apolloState.entries.filter { x -> x.key.contains("genres") }.joinToString { it.value.jsonObject["name"]!!.jsonPrimitive.content } } catch (_: Exception) { "" }
val network = try { apolloState.entries.firstOrNull { x -> x.key.contains("networks") }?.value?.jsonObject?.get("name")!!.jsonPrimitive.content } catch (_: Exception) { "" }
val artist = try { dorama["cast"]?.jsonObject?.get("json")?.jsonArray?.firstOrNull()?.jsonObject?.get("name")?.jsonPrimitive?.content } catch (_: Exception) { "" }
val type = try { dorama["__typename"]!!.jsonPrimitive.content.lowercase() } catch (_: Exception) { "" }
val poster = try { dorama["poster_path"]!!.jsonPrimitive.content } catch (_: Exception) { "" }
val urlImg = try { poster.ifEmpty { dorama["poster"]!!.jsonPrimitive.content } } catch (_: Exception) { "" }
val id = dorama["_id"]!!.jsonPrimitive.content
anime.title = "${dorama["name"]?.jsonPrimitive?.content} (${dorama["name_es"]?.jsonPrimitive?.content})"
anime.description = dorama["overview"]?.jsonPrimitive?.content?.trim() ?: ""
if (genres.isNotEmpty()) anime.genre = genres
if (network.isNotEmpty()) anime.author = network
if (artist != null) anime.artist = artist
if (type.isNotEmpty()) anime.status = if (type == "movie") SAnime.COMPLETED else SAnime.UNKNOWN
if (urlImg.isNotEmpty()) anime.thumbnail_url = externalOrInternalImg(urlImg)
anime.setUrlWithoutDomain(urlSolverByType(dorama["__typename"]!!.jsonPrimitive!!.content, dorama["slug"]!!.jsonPrimitive!!.content, id))
}
}
return anime
}
override fun episodeListRequest(anime: SAnime): Request {
val id = anime.url.substringAfter("?id=")
return if (anime.url.contains("peliculas-online")) {
GET(baseUrl + anime.url)
} else {
val body = (
"{\"operationName\":\"listSeasons\",\"variables\":{\"serie_id\":\"$id\"},\"query\":\"query listSeasons(\$serie_id: MongoID!) " +
"{\\n listSeasons(sort: NUMBER_ASC, filter: {serie_id: \$serie_id}) {\\n slug\\n season_number\\n poster_path\\n air_date\\n " +
"serie_name\\n poster\\n backdrop\\n __typename\\n }\\n}\\n\"}"
).toRequestBody(mediaType)
POST("$apiUrl?id=$id", popularRequestHeaders, body)
}
}
override fun episodeListParse(response: Response): List<SEpisode> {
return if (response.request.url.toString().contains("peliculas-online")) {
listOf(
SEpisode.create().apply {
episode_number = 1F
name = "Película"
setUrlWithoutDomain(response.request.url.toString())
},
)
} else {
val id = response.request.url.toString().substringAfter("?id=")
val responseString = response.body.string()
val data = json.decodeFromString<SeasonModel>(responseString).data
data.listSeasons.parallelCatchingFlatMapBlocking {
val season = it.seasonNumber
val body = (
"{\"operationName\":\"listEpisodes\",\"variables\":{\"serie_id\":\"$id\",\"season_number\":$season},\"query\":\"query " +
"listEpisodes(\$season_number: Float!, \$serie_id: MongoID!) {\\n listEpisodes(sort: NUMBER_ASC, filter: {type_serie: \\\"dorama\\\", " +
"serie_id: \$serie_id, season_number: \$season_number}) {\\n _id\\n name\\n slug\\n serie_name\\n serie_name_es\\n " +
"serie_id\\n still_path\\n air_date\\n season_number\\n episode_number\\n languages\\n poster\\n backdrop\\n __typename\\n }\\n}\\n\"}"
).toRequestBody(mediaType)
val episodes = client.newCall(POST(apiUrl, popularRequestHeaders, body)).execute().let { resp ->
json.decodeFromString<EpisodeModel>(resp.body.string())
}
parseEpisodeListJson(episodes)
}
}.reversed()
}
private fun parseEpisodeListJson(episodes: EpisodeModel): List<SEpisode> {
var isUpcoming = false
val currentDate = Date().time
return episodes.data.listEpisodes.mapIndexed { idx, episodeObject ->
val dateEp = episodeObject.airDate
val nameEp = if (episodeObject.name.isNullOrEmpty()) "- Capítulo ${episodeObject.episodeNumber}" else "- ${episodeObject.name}"
if (dateEp != null && dateEp.toDate() > currentDate && !isUpcoming) isUpcoming = true
SEpisode.create().apply {
name = "T${episodeObject.seasonNumber} - E${episodeObject.episodeNumber} $nameEp"
episode_number = episodeObject.episodeNumber?.toFloat() ?: idx.toFloat()
date_upload = dateEp?.toDate() ?: 0L
scanlator = if (isUpcoming) "Próximamente..." else null
setUrlWithoutDomain(urlSolverByType("episode", episodeObject.slug))
}
}
}
override fun latestUpdatesParse(response: Response): AnimesPage {
val responseString = response.body.string()
return when {
responseString.contains("paginationMovie") -> parsePopularJson(responseString, "movie")
else -> parsePopularJson(responseString, "dorama")
}
}
override fun latestUpdatesRequest(page: Int): Request {
val mediaType = "application/json; charset=utf-8".toMediaType()
val body = (
"{\"operationName\":\"listDoramas\",\"variables\":{\"page\":$page,\"sort\":\"CREATEDAT_DESC\",\"perPage\":32,\"filter\":{\"isTVShow\":false}}," +
"\"query\":\"query listDoramas(\$page: Int, \$perPage: Int, \$sort: SortFindManyDoramaInput, \$filter: FilterFindManyDoramaInput) {\\n " +
"paginationDorama(page: \$page, perPage: \$perPage, sort: \$sort, filter: \$filter) {\\n count\\n pageInfo {\\n currentPage\\n " +
"hasNextPage\\n hasPreviousPage\\n __typename\\n }\\n items {\\n _id\\n name\\n name_es\\n slug\\n " +
"cast\\n names\\n overview\\n languages\\n created_by\\n popularity\\n poster_path\\n vote_average\\n " +
"backdrop_path\\n first_air_date\\n episode_run_time\\n isTVShow\\n poster\\n backdrop\\n genres {\\n " +
"name\\n slug\\n __typename\\n }\\n networks {\\n name\\n slug\\n __typename\\n }\\n " +
"__typename\\n }\\n __typename\\n }\\n}\\n\"}"
).toRequestBody(mediaType)
return POST(apiUrl, popularRequestHeaders, body)
}
override fun popularAnimeParse(response: Response): AnimesPage {
val responseString = response.body.string()
return when {
responseString.contains("paginationMovie") -> parsePopularJson(responseString, "movie")
else -> parsePopularJson(responseString, "dorama")
}
}
private val languages = arrayOf(
Pair("36", "[ENG]"),
Pair("37", "[CAST]"),
Pair("38", "[LAT]"),
Pair("192", "[SUB]"),
Pair("1327", "[POR]"),
Pair("13109", "[COR]"),
Pair("13110", "[JAP]"),
Pair("13111", "[MAN]"),
Pair("13112", "[TAI]"),
Pair("13113", "[FIL]"),
Pair("13114", "[IND]"),
Pair("343422", "[VIET]"),
)
private fun String.getLang(): String {
return languages.firstOrNull { it.first == this }?.second ?: ""
}
private fun parsePopularJson(jsonLine: String?, type: String): AnimesPage {
val jsonData = jsonLine ?: return AnimesPage(emptyList(), false)
val data = json.decodeFromString<PaginationModel>(jsonData).data
val pagination = when (type) {
"dorama" -> data.paginationDorama
"movie" -> data.paginationMovie
else -> throw IllegalArgumentException("Tipo de dato no válido: $type")
}
val hasNextPage = pagination?.pageInfo?.hasNextPage ?: false
val animeList = pagination?.items?.map { animeObject ->
val urlImg = when {
!animeObject.posterPath.isNullOrEmpty() -> animeObject.posterPath.toString()
!animeObject.poster.isNullOrEmpty() -> animeObject.poster.toString()
else -> ""
}
SAnime.create().apply {
title = "${animeObject.name} (${animeObject.nameEs})"
description = animeObject.overview
genre = animeObject.genres.joinToString { it.name ?: "" }
thumbnail_url = externalOrInternalImg(urlImg, true)
setUrlWithoutDomain(urlSolverByType(animeObject.typename, animeObject.slug, animeObject.id))
}
}
return AnimesPage(animeList ?: emptyList(), hasNextPage)
}
private fun urlSolverByType(type: String, slug: String, id: String? = ""): String {
return when (type.lowercase()) {
"dorama" -> "$baseUrl/doramas-online/$slug?id=$id"
"episode" -> "$baseUrl/episodios/$slug"
"movie" -> "$baseUrl/peliculas-online/$slug?id=$id"
else -> ""
}
}
override fun popularAnimeRequest(page: Int): Request {
val mediaType = "application/json; charset=utf-8".toMediaType()
val body = (
"{\"operationName\":\"listDoramas\",\"variables\":{\"page\":$page,\"sort\":\"POPULARITY_DESC\",\"perPage\":32,\"filter\":{\"isTVShow\":false}}," +
"\"query\":\"query listDoramas(\$page: Int, \$perPage: Int, \$sort: SortFindManyDoramaInput, \$filter: FilterFindManyDoramaInput) {\\n " +
"paginationDorama(page: \$page, perPage: \$perPage, sort: \$sort, filter: \$filter) {\\n count\\n pageInfo {\\n currentPage\\n " +
"hasNextPage\\n hasPreviousPage\\n __typename\\n }\\n items {\\n _id\\n name\\n name_es\\n slug\\n " +
"cast\\n names\\n overview\\n languages\\n created_by\\n popularity\\n poster_path\\n vote_average\\n " +
"backdrop_path\\n first_air_date\\n episode_run_time\\n isTVShow\\n poster\\n backdrop\\n genres {\\n " +
"name\\n slug\\n __typename\\n }\\n networks {\\n name\\n slug\\n __typename\\n }\\n " +
"__typename\\n }\\n __typename\\n }\\n}\\n\"}"
).toRequestBody(mediaType)
return POST(apiUrl, popularRequestHeaders, body)
}
override fun searchAnimeParse(response: Response): AnimesPage {
val responseString = response.body.string()
return when {
responseString.contains("searchDorama") -> parseSearchAnimeJson(responseString)
responseString.contains("paginationMovie") -> parsePopularJson(responseString, "movie")
else -> parsePopularJson(responseString, "dorama")
}
}
private fun parseSearchAnimeJson(jsonLine: String?): AnimesPage {
val jsonData = jsonLine ?: return AnimesPage(emptyList(), false)
val jsonObject = json.decodeFromString<SearchModel>(jsonData).data
val animeList = mutableListOf<SAnime>()
jsonObject.searchDorama.map { castToSAnime(it) }.also(animeList::addAll)
jsonObject.searchMovie.map { castToSAnime(it) }.also(animeList::addAll)
return AnimesPage(animeList, false)
}
private fun castToSAnime(animeObject: SearchDorama): SAnime {
val urlImg = when {
!animeObject.posterPath.isNullOrEmpty() -> animeObject.posterPath.toString()
!animeObject.poster.isNullOrEmpty() -> animeObject.poster.toString()
else -> ""
}
return SAnime.create().apply {
title = "${animeObject.name} (${animeObject.nameEs})"
thumbnail_url = externalOrInternalImg(urlImg, true)
setUrlWithoutDomain(urlSolverByType(animeObject.typename, animeObject.slug, animeObject.id))
}
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
return when {
query.isNotBlank() -> searchQueryRequest(query)
"peliculas" in genreFilter.toUriPart() -> popularMovieRequest(page)
"variedades" in genreFilter.toUriPart() -> popularVarietiesRequest(page)
else -> popularAnimeRequest(page)
}
}
private fun searchQueryRequest(query: String): Request {
val fxQuery = query.replace("+", " ")
val body = (
"{\"operationName\":\"searchAll\",\"variables\":{\"input\":\"$fxQuery\"},\"query\":\"query searchAll(\$input: String!) {\\n " +
"searchDorama(input: \$input, limit: 32) {\\n _id\\n slug\\n name\\n name_es\\n poster_path\\n poster\\n " +
"__typename\\n }\\n searchMovie(input: \$input, limit: 32) {\\n _id\\n name\\n name_es\\n slug\\n poster_path\\n " +
"poster\\n __typename\\n }\\n}\\n\"}"
).toRequestBody(mediaType)
return POST(apiUrl, popularRequestHeaders, body)
}
private fun popularMovieRequest(page: Int): Request {
val mediaType = "application/json; charset=utf-8".toMediaType()
val body = (
"{\"operationName\":\"listMovies\",\"variables\":{\"perPage\":32,\"sort\":\"CREATEDAT_DESC\",\"filter\":{},\"page\":$page},\"query\":\"query " +
"listMovies(\$page: Int, \$perPage: Int, \$sort: SortFindManyMovieInput, \$filter: FilterFindManyMovieInput) {\\n paginationMovie(page: \$page" +
", perPage: \$perPage, sort: \$sort, filter: \$filter) {\\n count\\n pageInfo {\\n currentPage\\n hasNextPage\\n hasPreviousPage\\n" +
" __typename\\n }\\n items {\\n _id\\n name\\n name_es\\n slug\\n cast\\n names\\n overview\\n " +
"languages\\n popularity\\n poster_path\\n vote_average\\n backdrop_path\\n release_date\\n runtime\\n poster\\n " +
"backdrop\\n genres {\\n name\\n __typename\\n }\\n networks {\\n name\\n __typename\\n }\\n " +
"__typename\\n }\\n __typename\\n }\\n}\\n\"}"
).toRequestBody(mediaType)
return POST(apiUrl, popularRequestHeaders, body)
}
private fun popularVarietiesRequest(page: Int): Request {
val mediaType = "application/json; charset=utf-8".toMediaType()
val body = (
"{\"operationName\":\"listDoramas\",\"variables\":{\"page\":$page,\"sort\":\"CREATEDAT_DESC\",\"perPage\":32,\"filter\":{\"isTVShow\":true}},\"query\":\"query " +
"listDoramas(\$page: Int, \$perPage: Int, \$sort: SortFindManyDoramaInput, \$filter: FilterFindManyDoramaInput) {\\n paginationDorama(page: \$page, perPage: \$perPage, " +
"sort: \$sort, filter: \$filter) {\\n count\\n pageInfo {\\n currentPage\\n hasNextPage\\n hasPreviousPage\\n __typename\\n }\\n " +
"items {\\n _id\\n name\\n name_es\\n slug\\n cast\\n names\\n overview\\n languages\\n created_by\\n " +
"popularity\\n poster_path\\n vote_average\\n backdrop_path\\n first_air_date\\n episode_run_time\\n isTVShow\\n poster\\n " +
"backdrop\\n genres {\\n name\\n slug\\n __typename\\n }\\n networks {\\n name\\n slug\\n " +
"__typename\\n }\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}"
).toRequestBody(mediaType)
return POST(apiUrl, popularRequestHeaders, body)
}
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenreFilter(),
)
private class GenreFilter : UriPartFilter(
"Géneros",
arrayOf(
Pair("Doramas", "doramas"),
Pair("Películas", "peliculas"),
Pair("Variedades", "variedades"),
),
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
private fun String.toDate(): Long {
return runCatching { DATE_FORMATTER.parse(trim())?.time }.getOrNull() ?: 0L
}
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val jsonData = document.selectFirst("script:containsData({\"props\":{\"pageProps\":{)")!!.data()
val apolloState = json.decodeFromString<JsonObject>(jsonData).jsonObject["props"]!!.jsonObject["pageProps"]!!.jsonObject["apolloState"]!!.jsonObject
val episodeItem = apolloState.entries.firstOrNull { x -> x.key.contains("Episode:") }
val episode = episodeItem?.value?.jsonObject
?: apolloState.entries.firstOrNull { (key, _) -> Regex("\\b(?:Movie|Dorama):[a-zA-Z0-9]+").matches(key) }?.value?.jsonObject
var linksOnline = episode?.get("links_online")?.jsonObject?.get("json")?.jsonArray
val bMovies = apolloState.entries.any { x -> x.key.contains("ROOT_QUERY.getMovieLinks(") }
if (bMovies && linksOnline == null) {
linksOnline = apolloState.entries.firstOrNull { x -> x.key.contains("ROOT_QUERY.getMovieLinks(") }
?.value?.jsonObject?.get("links_online")?.jsonObject?.get("json")?.jsonArray
}
return linksOnline?.parallelCatchingFlatMapBlocking {
val link = it.jsonObject["link"]!!.jsonPrimitive.content
val lang = it.jsonObject["lang"]?.jsonPrimitive?.content?.getLang() ?: ""
serverVideoResolver(link, lang)
} ?: apolloState.entries.filter { x -> x.key.contains("ROOT_QUERY.listProblems(") }
.mapNotNull { entry ->
val server = entry.value.jsonObject["server"]?.jsonObject?.get("json")?.jsonObject
val link = server?.get("link")?.jsonPrimitive?.content
val lang = server?.get("lang")?.jsonPrimitive?.content?.getLang() ?: ""
link?.let { it to lang }
}.distinctBy { it.first }
.parallelCatchingFlatMapBlocking { (link, lang) ->
val finalLink = getRealLink(link)
serverVideoResolver(finalLink, lang)
}
}
private fun getRealLink(link: String): String {
if (!link.contains("fkplayer.xyz")) return link
val token = client.newCall(GET(link)).execute()
.asJsoup().selectFirst("script:containsData({\"props\":{\"pageProps\":{)")?.data()
?.parseTo<TokenModel>()
val mediaType = "application/json".toMediaType()
val requestBody = "{\"token\":\"${token?.props?.pageProps?.token ?: token?.query?.token}\"}".toRequestBody(mediaType)
val headersVideo = headers.newBuilder()
.add("origin", "https://${link.toHttpUrl().host}")
.add("Content-Type", "application/json")
.build()
val json = client.newCall(POST("https://fkplayer.xyz/api/decoding", headersVideo, requestBody))
.execute().body.string().parseTo<VideoToken>()
return String(Base64.decode(json.link, Base64.DEFAULT))
}
private fun serverVideoResolver(url: String, prefix: String = ""): List<Video> {
val embedUrl = url.lowercase()
return when {
"voe" in embedUrl -> VoeExtractor(client).videosFromUrl(url, " $prefix")
"ok.ru" in embedUrl || "okru" in embedUrl -> OkruExtractor(client).videosFromUrl(url, prefix = "$prefix ")
"filemoon" in embedUrl || "moonplayer" in embedUrl -> {
val vidHeaders = headers.newBuilder()
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
FilemoonExtractor(client).videosFromUrl(url, prefix = "$prefix Filemoon:", headers = vidHeaders)
}
"uqload" in embedUrl -> UqloadExtractor(client).videosFromUrl(url, prefix = prefix)
"mp4upload" in embedUrl -> Mp4uploadExtractor(client).videosFromUrl(url, prefix = "$prefix ", headers = headers)
"doodstream" in embedUrl || "dood." in embedUrl ->
listOf(DoodExtractor(client).videoFromUrl(url.replace("https://doodstream.com/e/", "https://dood.to/e/"), "$prefix DoodStream", false)!!)
"streamlare" in embedUrl -> StreamlareExtractor(client).videosFromUrl(url, prefix = prefix)
"yourupload" in embedUrl || "upload" in embedUrl -> YourUploadExtractor(client).videoFromUrl(url, headers = headers, prefix = "$prefix ")
"wishembed" in embedUrl || "streamwish" in embedUrl || "strwish" in embedUrl || "wish" in embedUrl -> {
val docHeaders = headers.newBuilder()
.add("Origin", "https://streamwish.to")
.add("Referer", "https://streamwish.to/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "$prefix StreamWish:$it" })
}
"burstcloud" in embedUrl || "burst" in embedUrl -> BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = "$prefix ")
"fastream" in embedUrl -> FastreamExtractor(client, headers).videosFromUrl(url, prefix = "$prefix Fastream:")
"upstream" in embedUrl -> UpstreamExtractor(client).videosFromUrl(url, prefix = "$prefix ")
"streamtape" in embedUrl || "stp" in embedUrl || "stape" in embedUrl -> listOf(StreamTapeExtractor(client).videoFromUrl(url, quality = "$prefix StreamTape")!!)
"ahvsh" in embedUrl || "streamhide" in embedUrl -> StreamHideVidExtractor(client).videosFromUrl(url, "$prefix ")
"filelions" in embedUrl || "lion" in embedUrl -> StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "$prefix FileLions:$it" })
"vudeo" in embedUrl || "vudea" in embedUrl -> VudeoExtractor(client).videosFromUrl(url, "$prefix ")
else -> emptyList()
}
}
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)!!
val lang = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(lang) },
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_LANGUAGE_KEY
title = "Preferred language"
entries = LANGUAGE_LIST
entryValues = LANGUAGE_LIST
setDefaultValue(PREF_LANGUAGE_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_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)
}
private inline fun <reified T> String.parseTo(): T {
return json.decodeFromString<T>(this)
}
private inline fun <reified T> Response.parseTo(): T {
return json.decodeFromString<T>(this.body.string())
}
}

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Some files were not shown because too many files have changed in this diff Show more