Merged with dark25 (#636)
* merge merged lib, lib-multisrc, all, ar, de, en, es, fr, hi, id, it, pt, tr src from dark25 * patch
17
src/es/animebum/build.gradle
Normal file
|
@ -0,0 +1,17 @@
|
|||
ext {
|
||||
extName = 'AnimeBum'
|
||||
extClass = '.AnimeBum'
|
||||
extVersionCode = 4
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib:okru-extractor'))
|
||||
implementation(project(':lib:streamwish-extractor'))
|
||||
implementation(project(':lib:universal-extractor'))
|
||||
implementation(project(':lib:streamhidevid-extractor'))
|
||||
implementation(project(':lib:vidguard-extractor'))
|
||||
implementation(project(':lib:filemoon-extractor'))
|
||||
implementation(project(':lib:gdriveplayer-extractor'))
|
||||
}
|
BIN
src/es/animebum/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
src/es/animebum/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
src/es/animebum/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/es/animebum/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
src/es/animebum/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 34 KiB |
|
@ -0,0 +1,356 @@
|
|||
package eu.kanade.tachiyomi.animeextension.es.animebum
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
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.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.lib.gdriveplayerextractor.GdrivePlayerExtractor
|
||||
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamhidevidextractor.StreamHideVidExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
|
||||
import eu.kanade.tachiyomi.lib.vidguardextractor.VidGuardExtractor
|
||||
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 AnimeBum : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
override val name = "AnimeBum"
|
||||
|
||||
override val baseUrl = "https://www.animebum.net"
|
||||
|
||||
override val lang = "es"
|
||||
|
||||
override val supportsLatest = false
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
// ============================== Popular ===============================
|
||||
override fun popularAnimeRequest(page: Int): Request {
|
||||
return GET("$baseUrl/series?page=$page", headers)
|
||||
}
|
||||
|
||||
override fun popularAnimeSelector(): String = "article.serie"
|
||||
|
||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
||||
val anime = SAnime.create()
|
||||
|
||||
// Extraer el título y enlace
|
||||
val titleElement = element.selectFirst("div.title h3 a")
|
||||
anime.title = titleElement?.attr("title") ?: "Sin título"
|
||||
anime.setUrlWithoutDomain(titleElement?.attr("href") ?: "")
|
||||
// Extraer la imagen
|
||||
val imageElement = element.selectFirst("figure.image img")
|
||||
anime.thumbnail_url = imageElement?.attr("src") ?: ""
|
||||
|
||||
return anime
|
||||
}
|
||||
|
||||
override fun popularAnimeNextPageSelector(): String {
|
||||
return "ul.pagination li a[rel=next]"
|
||||
}
|
||||
|
||||
// =============================== Latest ===============================
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
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 filterList = if (filters.isEmpty()) getFilterList() else filters
|
||||
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
|
||||
|
||||
return when {
|
||||
query.isNotBlank() -> GET("$baseUrl/search?s=$query&page=$page", headers)
|
||||
genreFilter.state != 0 -> GET("$baseUrl/${genreFilter.toUriPart()}?page=$page", headers)
|
||||
else -> popularAnimeRequest(page)
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val animes = document.select(searchAnimeSelector()).map { searchAnimeFromElement(it) }
|
||||
val hasNextPage = searchAnimeNextPageSelector().let { selector ->
|
||||
document.select(selector).firstOrNull() != null
|
||||
}
|
||||
return AnimesPage(animes, hasNextPage)
|
||||
}
|
||||
|
||||
override fun searchAnimeSelector(): String {
|
||||
return "div.search-results__item"
|
||||
}
|
||||
|
||||
override fun searchAnimeFromElement(element: Element): SAnime {
|
||||
val anime = SAnime.create()
|
||||
|
||||
val titleElement = element.selectFirst("div.search-results__left a h2")
|
||||
anime.title = titleElement?.text().orEmpty()
|
||||
|
||||
val urlElement = element.selectFirst("div.search-results__left a")
|
||||
anime.setUrlWithoutDomain(urlElement?.attr("href").orEmpty())
|
||||
|
||||
val imgElement = element.selectFirst("div.search-results__img a img")
|
||||
anime.thumbnail_url = imgElement?.attr("src").orEmpty()
|
||||
|
||||
val descriptionElement = element.selectFirst("div.search-results__left div.description")
|
||||
anime.description = descriptionElement?.text().orEmpty()
|
||||
|
||||
return anime
|
||||
}
|
||||
|
||||
override fun searchAnimeNextPageSelector(): String {
|
||||
return "a.next.page-numbers"
|
||||
}
|
||||
|
||||
// =========================== Anime Details ============================
|
||||
override fun animeDetailsParse(document: Document): SAnime {
|
||||
val anime = SAnime.create()
|
||||
|
||||
val synopsisElement = document.selectFirst("div.description p")
|
||||
anime.description = synopsisElement?.text() ?: "Sin sinopsis"
|
||||
|
||||
val yearElement = document.selectFirst("p.datos-serie strong:contains(Año)")
|
||||
anime.genre = yearElement?.text() ?: ""
|
||||
// sie es fin o emison la clase
|
||||
val statusElement = if (document.selectFirst("p.datos-serie strong.emision") != null) {
|
||||
document.selectFirst("p.datos-serie strong.emision")
|
||||
} else {
|
||||
document.selectFirst("p.datos-serie strong.fin")
|
||||
}
|
||||
anime.status = parseStatus(statusElement?.text() ?: "")
|
||||
|
||||
val genresElement = document.select("div.boom-categories a")
|
||||
anime.genre = genresElement.joinToString(", ") { it.text() }
|
||||
|
||||
return anime
|
||||
}
|
||||
|
||||
private fun parseStatus(status: String): Int {
|
||||
return when (status) {
|
||||
"En emisión" -> SAnime.ONGOING
|
||||
"Finalizado" -> SAnime.COMPLETED
|
||||
else -> SAnime.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
override fun episodeListSelector(): String {
|
||||
return "ul.list-episodies li"
|
||||
}
|
||||
|
||||
override fun episodeFromElement(element: Element): SEpisode {
|
||||
val episode = SEpisode.create()
|
||||
|
||||
val episodeUrl = element.selectFirst("a")?.attr("href").orEmpty()
|
||||
val episodeTitle = element.selectFirst("a")?.ownText()?.trim().orEmpty()
|
||||
val episodeNumber = Regex("""Episodio (\d+)""").find(episodeTitle)?.groupValues?.get(1)?.toFloatOrNull()
|
||||
|
||||
episode.setUrlWithoutDomain(episodeUrl)
|
||||
episode.name = episodeTitle
|
||||
episode.episode_number = episodeNumber ?: 1F
|
||||
|
||||
return episode
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = response.asJsoup()
|
||||
return document.select(episodeListSelector()).map { episodeFromElement(it) }.sortedByDescending { it.episode_number }
|
||||
}
|
||||
|
||||
// ============================ Video Extractor ==========================
|
||||
|
||||
private val vidHideExtractor by lazy { StreamHideVidExtractor(client, headers) }
|
||||
private val okruExtractor by lazy { OkruExtractor(client) }
|
||||
private val streamWishExtractor by lazy { StreamWishExtractor(client, headers) }
|
||||
private val vidGuardExtractor by lazy { VidGuardExtractor(client) }
|
||||
private val gdrivePlayerExtractor by lazy { GdrivePlayerExtractor(client) }
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val videoList = mutableListOf<Video>()
|
||||
val scriptContent = document.select("script:containsData(var video = [])").firstOrNull()?.data()
|
||||
?: return videoList
|
||||
|
||||
val iframeRegex = """video\[\d+\]\s*=\s*['"]<iframe[^>]+src=["']([^"']+)["']""".toRegex()
|
||||
val matches = iframeRegex.findAll(scriptContent)
|
||||
|
||||
for (match in matches) {
|
||||
var videoUrl = match.groupValues[1]
|
||||
|
||||
if (videoUrl.startsWith("//")) {
|
||||
videoUrl = "https:$videoUrl"
|
||||
}
|
||||
|
||||
val vidHideDomains = listOf("vidhide", "VidHidePro", "luluvdo", "vidhideplus")
|
||||
|
||||
val video = when {
|
||||
vidHideDomains.any { videoUrl.contains(it, ignoreCase = true) } -> vidHideExtractor.videosFromUrl(videoUrl)
|
||||
"drive.google" in videoUrl -> {
|
||||
val newUrl = "https://gdriveplayer.to/embed2.php?link=$videoUrl"
|
||||
Log.d("AnimeBum", "New URL: $newUrl")
|
||||
gdrivePlayerExtractor.videosFromUrl(newUrl, "GdrivePlayer", headers)
|
||||
}
|
||||
videoUrl.contains("streamwish") -> streamWishExtractor.videosFromUrl(videoUrl)
|
||||
videoUrl.contains("ok.ru") -> okruExtractor.videosFromUrl(videoUrl)
|
||||
videoUrl.contains("listeamed") -> vidGuardExtractor.videosFromUrl(videoUrl)
|
||||
else -> emptyList()
|
||||
}
|
||||
videoList.addAll(video)
|
||||
}
|
||||
return videoList.sortedByDescending { it.quality }
|
||||
}
|
||||
|
||||
override fun videoListSelector(): String {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun videoFromElement(element: Element): Video {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun videoUrlParse(document: Document): String {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
// ============================ Filters =============================
|
||||
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
|
||||
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
|
||||
GenreFilter(),
|
||||
)
|
||||
|
||||
private class GenreFilter : UriPartFilter(
|
||||
"Género",
|
||||
arrayOf(
|
||||
Pair("<Seleccionar>", ""),
|
||||
Pair("Acción", "genero/accion"),
|
||||
Pair("Aventura", "genero/aventura"),
|
||||
Pair("Ciencia Ficción", "genero/ciencia-ficcion"),
|
||||
Pair("Comedia", "genero/comedia"),
|
||||
Pair("Drama", "genero/drama"),
|
||||
Pair("Terror", "genero/terror"),
|
||||
Pair("Suspenso", "genero/suspenso"),
|
||||
Pair("Romance", "genero/romance"),
|
||||
Pair("Magia", "genero/magia"),
|
||||
Pair("Misterio", "genero/misterio"),
|
||||
Pair("Superpoderes", "genero/super-poderes"),
|
||||
Pair("Shounen", "genero/shounen"),
|
||||
Pair("Deportes", "genero/deportes"),
|
||||
Pair("Fantasía", "genero/fantasia"),
|
||||
Pair("Sobrenatural", "genero/sobrenatural"),
|
||||
Pair("Música", "genero/musica"),
|
||||
Pair("Escolares", "genero/escolares"),
|
||||
Pair("Seinen", "genero/seinen"),
|
||||
Pair("Histórico", "genero/historico"),
|
||||
Pair("Psicológico", "genero/psicologico"),
|
||||
Pair("Mecha", "genero/mecha"),
|
||||
Pair("Juegos", "genero/juegos"),
|
||||
Pair("Militar", "genero/militar"),
|
||||
Pair("Recuentos de la Vida", "genero/recuentos-de-la-vida"),
|
||||
Pair("Demonios", "genero/demonios"),
|
||||
Pair("Artes Marciales", "genero/artes-marciales"),
|
||||
Pair("Espacial", "genero/espacial"),
|
||||
Pair("Shoujo", "genero/shoujo"),
|
||||
Pair("Samurái", "genero/samurai"),
|
||||
Pair("Harem", "genero/harem"),
|
||||
Pair("Parodia", "genero/parodia"),
|
||||
Pair("Ecchi", "genero/ecchi"),
|
||||
Pair("Demencia", "genero/demencia"),
|
||||
Pair("Vampiros", "genero/vampiros"),
|
||||
Pair("Josei", "genero/josei"),
|
||||
Pair("Shounen Ai", "genero/shounen-ai"),
|
||||
Pair("Shoujo Ai", "genero/shoujo-ai"),
|
||||
Pair("Latino", "genero/latino"),
|
||||
Pair("Policía", "genero/policia"),
|
||||
Pair("Yaoi", "genero/yaoi"),
|
||||
),
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ============================ Preferences =============================
|
||||
|
||||
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(
|
||||
"YourUpload", "BurstCloud", "Voe", "Mp4Upload", "Doodstream",
|
||||
"Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon",
|
||||
"Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare",
|
||||
)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
25
src/es/animefenix/build.gradle
Normal file
|
@ -0,0 +1,25 @@
|
|||
ext {
|
||||
extName = 'Animefenix'
|
||||
extClass = '.Animefenix'
|
||||
extVersionCode = 54
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib:mp4upload-extractor'))
|
||||
implementation(project(':lib:streamtape-extractor'))
|
||||
implementation(project(':lib:yourupload-extractor'))
|
||||
implementation(project(':lib:uqload-extractor'))
|
||||
implementation(project(':lib:okru-extractor'))
|
||||
implementation(project(':lib:burstcloud-extractor'))
|
||||
implementation(project(':lib:streamwish-extractor'))
|
||||
implementation(project(':lib:filemoon-extractor'))
|
||||
implementation(project(':lib:voe-extractor'))
|
||||
implementation(project(':lib:streamlare-extractor'))
|
||||
implementation(project(':lib:fastream-extractor'))
|
||||
implementation(project(':lib:dood-extractor'))
|
||||
implementation(project(':lib:upstream-extractor'))
|
||||
implementation(project(':lib:streamhidevid-extractor'))
|
||||
implementation(project(':lib:universal-extractor'))
|
||||
}
|
BIN
src/es/animefenix/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/es/animefenix/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/es/animefenix/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/es/animefenix/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/es/animefenix/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,161 @@
|
|||
package eu.kanade.tachiyomi.animeextension.es.animefenix
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import java.util.Calendar
|
||||
|
||||
object AnimeFenixFilters {
|
||||
open class QueryPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : AnimeFilter.Select<String>(
|
||||
displayName,
|
||||
vals.map { it.first }.toTypedArray(),
|
||||
) {
|
||||
fun toQueryPart(name: String) = vals[state].second.takeIf { it.isNotEmpty() }?.let { "&$name=${vals[state].second}" } ?: run { "" }
|
||||
}
|
||||
|
||||
open class CheckBoxFilterList(name: String, values: List<CheckBox>) : AnimeFilter.Group<AnimeFilter.CheckBox>(name, values)
|
||||
|
||||
private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
|
||||
|
||||
private inline fun <reified R> AnimeFilterList.parseCheckbox(
|
||||
options: Array<Pair<String, String>>,
|
||||
name: String,
|
||||
): String {
|
||||
return (this.getFirst<R>() as CheckBoxFilterList).state
|
||||
.mapNotNull { checkbox ->
|
||||
if (checkbox.state) {
|
||||
options.find { it.first == checkbox.name }!!.second
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.joinToString("&$name[]=").let {
|
||||
if (it.isBlank()) {
|
||||
""
|
||||
} else {
|
||||
"&$name[]=$it"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified R> AnimeFilterList.asQueryPart(name: String): String {
|
||||
return (this.getFirst<R>() as QueryPartFilter).toQueryPart(name)
|
||||
}
|
||||
|
||||
private inline fun <reified R> AnimeFilterList.getFirst(): R {
|
||||
return this.filterIsInstance<R>().first()
|
||||
}
|
||||
|
||||
private fun String.changePrefix() = this.takeIf { it.startsWith("&") }?.let { this.replaceFirst("&", "?") } ?: run { this }
|
||||
|
||||
data class FilterSearchParams(val filter: String = "") { fun getQuery() = filter.changePrefix() }
|
||||
|
||||
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
|
||||
if (filters.isEmpty()) return FilterSearchParams()
|
||||
return FilterSearchParams(
|
||||
filters.parseCheckbox<GenresFilter>(AnimeFenixFiltersData.GENRES, "genero") +
|
||||
filters.parseCheckbox<YearsFilter>(AnimeFenixFiltersData.YEARS, "year") +
|
||||
filters.parseCheckbox<TypesFilter>(AnimeFenixFiltersData.TYPES, "type") +
|
||||
filters.parseCheckbox<StateFilter>(AnimeFenixFiltersData.STATE, "estado") +
|
||||
filters.asQueryPart<SortFilter>("order"),
|
||||
)
|
||||
}
|
||||
|
||||
val FILTER_LIST get() = AnimeFilterList(
|
||||
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
|
||||
GenresFilter(),
|
||||
YearsFilter(),
|
||||
TypesFilter(),
|
||||
StateFilter(),
|
||||
SortFilter(),
|
||||
)
|
||||
|
||||
class GenresFilter : CheckBoxFilterList("Género", AnimeFenixFiltersData.GENRES.map { CheckBoxVal(it.first, false) })
|
||||
|
||||
class YearsFilter : CheckBoxFilterList("Año", AnimeFenixFiltersData.YEARS.map { CheckBoxVal(it.first, false) })
|
||||
|
||||
class TypesFilter : CheckBoxFilterList("Tipo", AnimeFenixFiltersData.TYPES.map { CheckBoxVal(it.first, false) })
|
||||
|
||||
class StateFilter : CheckBoxFilterList("Estado", AnimeFenixFiltersData.STATE.map { CheckBoxVal(it.first, false) })
|
||||
|
||||
class SortFilter : QueryPartFilter("Orden", AnimeFenixFiltersData.SORT)
|
||||
|
||||
private object AnimeFenixFiltersData {
|
||||
val YEARS = (1990..Calendar.getInstance().get(Calendar.YEAR)).map { Pair("$it", "$it") }.reversed().toTypedArray()
|
||||
|
||||
val TYPES = arrayOf(
|
||||
Pair("TV", "tv"),
|
||||
Pair("Película", "movie"),
|
||||
Pair("Especial", "special"),
|
||||
Pair("OVA", "ova"),
|
||||
Pair("DONGHUA", "donghua"),
|
||||
)
|
||||
|
||||
val STATE = arrayOf(
|
||||
Pair("Emisión", "1"),
|
||||
Pair("Finalizado", "2"),
|
||||
Pair("Próximamente", "3"),
|
||||
Pair("En Cuarentena", "4"),
|
||||
)
|
||||
|
||||
val SORT = arrayOf(
|
||||
Pair("Por Defecto", "default"),
|
||||
Pair("Recientemente Actualizados", "updated"),
|
||||
Pair("Recientemente Agregados", "added"),
|
||||
Pair("Nombre A-Z", "title"),
|
||||
Pair("Calificación", "likes"),
|
||||
Pair("Más Vistos", "visits"),
|
||||
)
|
||||
|
||||
val GENRES = arrayOf(
|
||||
Pair("Acción", "accion"),
|
||||
Pair("Ángeles", "angeles"),
|
||||
Pair("Artes Marciales", "artes-marciales"),
|
||||
Pair("Aventura", "aventura"),
|
||||
Pair("Ciencia Ficción", "Ciencia Ficción"),
|
||||
Pair("Comedia", "comedia"),
|
||||
Pair("Cyberpunk", "cyberpunk"),
|
||||
Pair("Demonios", "demonios"),
|
||||
Pair("Deportes", "deportes"),
|
||||
Pair("Dragones", "dragones"),
|
||||
Pair("Drama", "drama"),
|
||||
Pair("Ecchi", "ecchi"),
|
||||
Pair("Escolares", "escolares"),
|
||||
Pair("Fantasía", "fantasia"),
|
||||
Pair("Gore", "gore"),
|
||||
Pair("Harem", "harem"),
|
||||
Pair("Histórico", "historico"),
|
||||
Pair("Horror", "horror"),
|
||||
Pair("Infantil", "infantil"),
|
||||
Pair("Isekai", "isekai"),
|
||||
Pair("Josei", "josei"),
|
||||
Pair("Juegos", "juegos"),
|
||||
Pair("Magia", "magia"),
|
||||
Pair("Mecha", "mecha"),
|
||||
Pair("Militar", "militar"),
|
||||
Pair("Misterio", "misterio"),
|
||||
Pair("Música", "Musica"),
|
||||
Pair("Ninjas", "ninjas"),
|
||||
Pair("Parodia", "parodia"),
|
||||
Pair("Policía", "policia"),
|
||||
Pair("Psicológico", "psicologico"),
|
||||
Pair("Recuerdos de la vida", "Recuerdos de la vida"),
|
||||
Pair("Romance", "romance"),
|
||||
Pair("Samurai", "samurai"),
|
||||
Pair("Sci-Fi", "sci-fi"),
|
||||
Pair("Seinen", "seinen"),
|
||||
Pair("Shoujo", "shoujo"),
|
||||
Pair("Shoujo Ai", "shoujo-ai"),
|
||||
Pair("Shounen", "shounen"),
|
||||
Pair("Slice of life", "slice-of-life"),
|
||||
Pair("Sobrenatural", "sobrenatural"),
|
||||
Pair("Space", "space"),
|
||||
Pair("Spokon", "spokon"),
|
||||
Pair("Steampunk", "steampunk"),
|
||||
Pair("Superpoder", "superpoder"),
|
||||
Pair("Thriller", "thriller"),
|
||||
Pair("Vampiro", "vampiro"),
|
||||
Pair("Yaoi", "yaoi"),
|
||||
Pair("Yuri", "yuri"),
|
||||
Pair("Zombies", "zombies"),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,307 @@
|
|||
package eu.kanade.tachiyomi.animeextension.es.animefenix
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||
import eu.kanade.tachiyomi.lib.burstcloudextractor.BurstCloudExtractor
|
||||
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
|
||||
import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor
|
||||
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
|
||||
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
|
||||
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamhidevidextractor.StreamHideVidExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
|
||||
import eu.kanade.tachiyomi.lib.universalextractor.UniversalExtractor
|
||||
import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor
|
||||
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
|
||||
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
|
||||
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.net.URLDecoder
|
||||
|
||||
class Animefenix : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
override val name = "AnimeFenix"
|
||||
|
||||
override val baseUrl = "https://www3.animefenix.tv"
|
||||
|
||||
override val lang = "es"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
private val preferences: SharedPreferences by lazy { Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) }
|
||||
|
||||
companion object {
|
||||
private val SERVER_REGEX = """tabsArray\['?\d+'?]\s*=\s*['\"](https[^'\"]+)['\"]""".toRegex()
|
||||
private const val PREF_QUALITY_KEY = "preferred_quality"
|
||||
private const val PREF_QUALITY_DEFAULT = "1080"
|
||||
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
|
||||
|
||||
private const val PREF_SERVER_KEY = "preferred_server"
|
||||
private const val PREF_SERVER_DEFAULT = "Amazon"
|
||||
private val SERVER_LIST = arrayOf(
|
||||
"YourUpload", "Voe", "Mp4Upload", "Doodstream",
|
||||
"Upload", "BurstCloud", "Upstream", "StreamTape",
|
||||
"Fastream", "Filemoon", "StreamWish", "Okru",
|
||||
"Amazon", "AmazonES", "Fireload", "FileLions",
|
||||
)
|
||||
}
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/animes?order=likes&page=$page")
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val elements = document.select("div.container .grid.gap-4 a[href]")
|
||||
val nextPage = document.select("nav[aria-label=Pagination] span:containsOwn(Next)").any()
|
||||
val animeList = elements.map { element ->
|
||||
SAnime.create().apply {
|
||||
setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href"))
|
||||
title = element.selectFirst("div h3.text-primary")!!.ownText()
|
||||
thumbnail_url = element.selectFirst("img.object-cover")?.attr("abs:src")
|
||||
}
|
||||
}
|
||||
return AnimesPage(animeList, nextPage)
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/animes?order=added&page=$page")
|
||||
|
||||
override fun latestUpdatesParse(response: Response) = popularAnimeParse(response)
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
val params = AnimeFenixFilters.getSearchParameters(filters)
|
||||
|
||||
return when {
|
||||
query.isNotBlank() -> GET("$baseUrl/animes?q=$query&page=$page", headers)
|
||||
params.filter.isNotBlank() -> GET("$baseUrl/animes${params.getQuery()}&page=$page", headers)
|
||||
else -> GET("$baseUrl/animes?order=likes&page=$page")
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = response.asJsoup()
|
||||
return document.select("div.container > div > ul > li").map { element ->
|
||||
SEpisode.create().apply {
|
||||
name = element.selectFirst("span > span")!!.ownText()
|
||||
setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val videoList = mutableListOf<Video>()
|
||||
val serversData = document.selectFirst("script:containsData(var tabsArray)")?.data() ?: throw Exception("No se encontraron servidores")
|
||||
val servers = SERVER_REGEX.findAll(serversData).map { it.groupValues[1] }.toList()
|
||||
|
||||
servers.parallelForEachBlocking { server ->
|
||||
val decodedUrl = URLDecoder.decode(server, "UTF-8")
|
||||
val realUrl = try {
|
||||
client.newCall(GET(decodedUrl)).execute().asJsoup().selectFirst("script")!!
|
||||
.data().substringAfter("src=\"").substringBefore("\"")
|
||||
} catch (e: Exception) { "" }
|
||||
|
||||
try {
|
||||
serverVideoResolver(realUrl).let { videoList.addAll(it) }
|
||||
} catch (_: Exception) { }
|
||||
}
|
||||
return videoList.filter { it.url.contains("https") || it.url.contains("http") }
|
||||
}
|
||||
|
||||
private fun serverVideoResolver(url: String): List<Video> {
|
||||
val videoList = mutableListOf<Video>()
|
||||
val embedUrl = url.lowercase()
|
||||
try {
|
||||
when {
|
||||
embedUrl.contains("voe") -> {
|
||||
VoeExtractor(client).videosFromUrl(url).also(videoList::addAll)
|
||||
}
|
||||
(embedUrl.contains("amazon") || embedUrl.contains("amz")) && !embedUrl.contains("disable") -> {
|
||||
val video = amazonExtractor(baseUrl + url.substringAfter(".."))
|
||||
if (video.isNotBlank()) {
|
||||
if (url.contains("&ext=es")) {
|
||||
videoList.add(Video(video, "AmazonES", video))
|
||||
} else {
|
||||
videoList.add(Video(video, "Amazon", video))
|
||||
}
|
||||
}
|
||||
}
|
||||
embedUrl.contains("ok.ru") || embedUrl.contains("okru") -> {
|
||||
OkruExtractor(client).videosFromUrl(url).also(videoList::addAll)
|
||||
}
|
||||
embedUrl.contains("filemoon") || embedUrl.contains("moonplayer") -> {
|
||||
val vidHeaders = headers.newBuilder()
|
||||
.add("Origin", "https://${url.toHttpUrl().host}")
|
||||
.add("Referer", "https://${url.toHttpUrl().host}/")
|
||||
.build()
|
||||
FilemoonExtractor(client).videosFromUrl(url, prefix = "Filemoon:", headers = vidHeaders).also(videoList::addAll)
|
||||
}
|
||||
embedUrl.contains("uqload") -> {
|
||||
UqloadExtractor(client).videosFromUrl(url).also(videoList::addAll)
|
||||
}
|
||||
embedUrl.contains("mp4upload") -> {
|
||||
Mp4uploadExtractor(client).videosFromUrl(url, headers).let { videoList.addAll(it) }
|
||||
}
|
||||
embedUrl.contains("wishembed") || embedUrl.contains("embedwish") || embedUrl.contains("streamwish") || embedUrl.contains("strwish") || embedUrl.contains("wish") -> {
|
||||
val docHeaders = headers.newBuilder()
|
||||
.add("Origin", "https://streamwish.to")
|
||||
.add("Referer", "https://streamwish.to/")
|
||||
.build()
|
||||
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" }).also(videoList::addAll)
|
||||
}
|
||||
embedUrl.contains("doodstream") || embedUrl.contains("dood.") -> {
|
||||
DoodExtractor(client).videoFromUrl(url, "DoodStream")?.let { videoList.add(it) }
|
||||
}
|
||||
embedUrl.contains("streamlare") -> {
|
||||
StreamlareExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
|
||||
}
|
||||
embedUrl.contains("yourupload") || embedUrl.contains("upload") -> {
|
||||
YourUploadExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
|
||||
}
|
||||
embedUrl.contains("burstcloud") || embedUrl.contains("burst") -> {
|
||||
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
|
||||
}
|
||||
embedUrl.contains("fastream") -> {
|
||||
FastreamExtractor(client, headers).videosFromUrl(url).also(videoList::addAll)
|
||||
}
|
||||
embedUrl.contains("upstream") -> {
|
||||
UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
|
||||
}
|
||||
embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape") -> {
|
||||
StreamTapeExtractor(client).videoFromUrl(url)?.let { videoList.add(it) }
|
||||
}
|
||||
embedUrl.contains("ahvsh") || embedUrl.contains("streamhide") -> {
|
||||
StreamHideVidExtractor(client, headers).videosFromUrl(url).let { videoList.addAll(it) }
|
||||
}
|
||||
embedUrl.contains("/stream/fl.php") -> {
|
||||
val video = url.substringAfter("/stream/fl.php?v=")
|
||||
if (client.newCall(GET(video)).execute().code == 200) {
|
||||
videoList.add(Video(video, "FireLoad", video))
|
||||
}
|
||||
}
|
||||
embedUrl.contains("filelions") || embedUrl.contains("lion") -> {
|
||||
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "FileLions:$it" }).also(videoList::addAll)
|
||||
}
|
||||
else ->
|
||||
UniversalExtractor(client).videosFromUrl(url, headers).let { videoList.addAll(it) }
|
||||
}
|
||||
} catch (_: Exception) { }
|
||||
return videoList
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
|
||||
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
|
||||
return this.sortedWith(
|
||||
compareBy(
|
||||
{ it.quality.contains(server, true) },
|
||||
{ it.quality.contains(quality) },
|
||||
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
|
||||
),
|
||||
).reversed()
|
||||
}
|
||||
|
||||
override fun animeDetailsParse(response: Response) = SAnime.create().apply {
|
||||
val document = response.asJsoup()
|
||||
with(document.selectFirst("main > div.relative > div.container > div.flex")!!) {
|
||||
title = selectFirst("h1.font-bold")!!.ownText()
|
||||
genre = select("div:has(h2:containsOwn(Géneros)) > div.flex > a").joinToString { it.text() }
|
||||
status = parseStatus(selectFirst("li:has(> span:containsOwn(Estado))")!!.ownText())
|
||||
description = select("div:has(h2:containsOwn(Sinopsis)) > p").text()
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseStatus(statusString: String): Int {
|
||||
return when {
|
||||
statusString.contains("Emisión") -> SAnime.ONGOING
|
||||
statusString.contains("Finalizado") -> SAnime.COMPLETED
|
||||
else -> SAnime.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
private fun amazonExtractor(url: String): String {
|
||||
val document = client.newCall(GET(url)).execute().asJsoup()
|
||||
val videoURl = document.selectFirst("script:containsData(sources: [)")!!.data()
|
||||
.substringAfter("[{\"file\":\"")
|
||||
.substringBefore("\",").replace("\\", "")
|
||||
|
||||
return try {
|
||||
if (client.newCall(GET(videoURl)).execute().code == 200) videoURl else ""
|
||||
} catch (e: Exception) {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFilterList(): AnimeFilterList = AnimeFenixFilters.FILTER_LIST
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
ListPreference(screen.context).apply {
|
||||
key = PREF_QUALITY_KEY
|
||||
title = "Preferred quality"
|
||||
entries = QUALITY_LIST
|
||||
entryValues = QUALITY_LIST
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
key = PREF_SERVER_KEY
|
||||
title = "Preferred server"
|
||||
entries = SERVER_LIST
|
||||
entryValues = SERVER_LIST
|
||||
setDefaultValue(PREF_SERVER_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
suspend inline fun <A> Iterable<A>.parallelForEach(crossinline f: suspend (A) -> Unit) {
|
||||
coroutineScope {
|
||||
for (item in this@parallelForEach) {
|
||||
launch(Dispatchers.IO) {
|
||||
f(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <A> Iterable<A>.parallelForEachBlocking(crossinline f: suspend (A) -> Unit) {
|
||||
runBlocking {
|
||||
this@parallelForEachBlocking.parallelForEach(f)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'AnimeFLV'
|
||||
extClass = '.AnimeFlv'
|
||||
extVersionCode = 59
|
||||
extVersionCode = 63
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -11,4 +11,5 @@ dependencies {
|
|||
implementation(project(':lib:streamtape-extractor'))
|
||||
implementation(project(':lib:okru-extractor'))
|
||||
implementation(project(':lib:streamwish-extractor'))
|
||||
implementation(project(':lib:universal-extractor'))
|
||||
}
|
|
@ -13,6 +13,7 @@ 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.universalextractor.UniversalExtractor
|
||||
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
|
@ -57,7 +58,7 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||
|
||||
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 popularAnimeRequest(page: Int): Request = GET("$baseUrl/browse?order=rating&page=$page", headers)
|
||||
|
||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
||||
val anime = SAnime.create()
|
||||
|
@ -108,18 +109,19 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||
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()) }
|
||||
private val universalExtractor by lazy { UniversalExtractor(client) }
|
||||
|
||||
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 {
|
||||
return json.decodeFromString<ServerModel>(responseString).sub.parallelCatchingFlatMapBlocking { it ->
|
||||
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()
|
||||
else -> universalExtractor.videosFromUrl(it.url ?: it.code, headers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -132,7 +134,6 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
val params = AnimeFlvFilters.getSearchParameters(filters)
|
||||
|
||||
return when {
|
||||
query.isNotBlank() -> GET("$baseUrl/browse?q=$query&page=$page")
|
||||
params.filter.isNotBlank() -> GET("$baseUrl/browse${params.getQuery()}&page=$page")
|
||||
|
@ -166,13 +167,19 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
|
||||
override fun latestUpdatesRequest(page: Int) = GET(baseUrl, headers)
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
|
||||
override fun latestUpdatesNextPageSelector() = null
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/browse?order=added&page=$page")
|
||||
override fun latestUpdatesSelector() = "div.Container ul.ListEpisodios li a.fa-play"
|
||||
|
||||
override fun latestUpdatesSelector() = popularAnimeSelector()
|
||||
override fun latestUpdatesFromElement(element: Element): SAnime {
|
||||
val anime = SAnime.create()
|
||||
anime.setUrlWithoutDomain(element.select("a").attr("abs:href").replace("/ver/", "/anime/").substringBeforeLast("-"))
|
||||
anime.title = element.select("strong.Title").text()
|
||||
anime.thumbnail_url = element.select("span.Image img").attr("abs:src").replace("thumbs", "covers")
|
||||
return anime
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'AnimeID'
|
||||
extClass = '.AnimeID'
|
||||
extVersionCode = 10
|
||||
extVersionCode = 15
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -9,4 +9,5 @@ apply from: "$rootDir/common.gradle"
|
|||
dependencies {
|
||||
implementation(project(':lib:streamtape-extractor'))
|
||||
implementation(project(':lib:streamwish-extractor'))
|
||||
implementation(project(':lib:universal-extractor'))
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ 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.lib.streamwishextractor.StreamWishExtractor
|
||||
import eu.kanade.tachiyomi.lib.universalextractor.UniversalExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import kotlinx.serialization.json.Json
|
||||
|
@ -120,6 +121,7 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||
// ============================ Video Links =============================
|
||||
private val streamwishExtractor by lazy { StreamWishExtractor(client, headers) }
|
||||
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
|
||||
private val universalExtractor by lazy { UniversalExtractor(client) }
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
|
@ -128,11 +130,10 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||
val jsonString = script.attr("data")
|
||||
val jsonUnescape = unescapeJava(jsonString)!!.replace("\\", "")
|
||||
val url = fetchUrls(jsonUnescape).firstOrNull()?.replace("\\\\", "\\") ?: ""
|
||||
if (url.contains("streamtape") || url.contains("tape") || url.contains("stp")) {
|
||||
streamtapeExtractor.videosFromUrl(url).also(videoList::addAll)
|
||||
}
|
||||
if (url.contains("wish") || url.contains("fviplions") || url.contains("obeywish")) {
|
||||
streamwishExtractor.videosFromUrl(url, videoNameGen = { "StreamWish:$it" }).also(videoList::addAll)
|
||||
return when {
|
||||
url.contains("streamtape") || url.contains("tape") || url.contains("stp") -> streamtapeExtractor.videosFromUrl(url)
|
||||
url.contains("wish") || url.contains("fviplions") || url.contains("obeywish") -> streamwishExtractor.videosFromUrl(url, videoNameGen = { "StreamWish:$it" })
|
||||
else -> universalExtractor.videosFromUrl(url, headers)
|
||||
}
|
||||
}
|
||||
return videoList
|
||||
|
|
20
src/es/animejl/build.gradle
Normal file
|
@ -0,0 +1,20 @@
|
|||
ext {
|
||||
extName = 'Animejl'
|
||||
extClass = '.Animejl'
|
||||
extVersionCode = 4
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib:yourupload-extractor'))
|
||||
implementation(project(':lib:streamtape-extractor'))
|
||||
implementation(project(':lib:okru-extractor'))
|
||||
implementation(project(':lib:voe-extractor'))
|
||||
implementation(project(':lib:streamwish-extractor'))
|
||||
implementation(project(':lib:streamhidevid-extractor'))
|
||||
implementation(project(':lib:universal-extractor'))
|
||||
implementation(project(':lib:uqload-extractor'))
|
||||
implementation(project(':lib:mp4upload-extractor'))
|
||||
}
|
BIN
src/es/animejl/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
src/es/animejl/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
src/es/animejl/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
src/es/animejl/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
src/es/animejl/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 54 KiB |
|
@ -0,0 +1,244 @@
|
|||
package eu.kanade.tachiyomi.animeextension.es.animejl
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
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.mp4uploadextractor.Mp4uploadExtractor
|
||||
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamhidevidextractor.StreamHideVidExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
|
||||
import eu.kanade.tachiyomi.lib.universalextractor.UniversalExtractor
|
||||
import eu.kanade.tachiyomi.lib.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.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 kotlin.Exception
|
||||
|
||||
class Animejl : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
override val name = "Animejl"
|
||||
|
||||
override val baseUrl = "https://www.anime-jl.net"
|
||||
|
||||
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 = "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", "StreamHideVid", "Voe", "Uqload", "Mp4upload")
|
||||
}
|
||||
|
||||
override fun popularAnimeSelector(): String = "div.Container ul.ListAnimes li article"
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request =
|
||||
GET("$baseUrl/animes?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 = element.select("a div.Image figure img").attr("src").replace("/storage", "$baseUrl/storage")
|
||||
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>()
|
||||
|
||||
val script = document.select("script:containsData(var episodes =)").firstOrNull()?.data() ?: return emptyList()
|
||||
|
||||
val episodesPattern = Regex("var episodes = (\\[.*?\\]);", RegexOption.DOT_MATCHES_ALL)
|
||||
val episodesMatch = episodesPattern.find(script) ?: return emptyList()
|
||||
val episodesString = episodesMatch.groupValues[1]
|
||||
|
||||
val animeInfoPattern = Regex("var anime_info = \\[(.*?)\\];")
|
||||
val animeInfoMatch = animeInfoPattern.find(script) ?: return emptyList()
|
||||
val animeInfo = animeInfoMatch.groupValues[1].split(",").map { it.trim('"') }
|
||||
|
||||
val animeSlug = animeInfo.getOrNull(2) ?: ""
|
||||
val animeId = animeInfo.getOrNull(0) ?: ""
|
||||
val episodePattern = Regex("\\[(\\d+),\"(.*?)\",\"(.*?)\",\"(.*?)\"\\]")
|
||||
val episodeMatches = episodePattern.findAll(episodesString)
|
||||
|
||||
episodeMatches.forEach { match ->
|
||||
try {
|
||||
val episodeNumber = match.groupValues[1].toIntOrNull() ?: 0
|
||||
val url = "$baseUrl/anime/$animeId/$animeSlug/episodio-$episodeNumber"
|
||||
val episode = SEpisode.create()
|
||||
episode.setUrlWithoutDomain(url)
|
||||
episode.episode_number = episodeNumber.toFloat()
|
||||
episode.name = "Episodio $episodeNumber"
|
||||
episodeList.add(episode)
|
||||
} catch (e: Exception) {
|
||||
Log.e("Animejl", "Error processing episode: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
return episodeList.sortedByDescending { it.episode_number }
|
||||
}
|
||||
|
||||
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) }
|
||||
private val universalExtractor by lazy { UniversalExtractor(client) }
|
||||
private val streamHideVidExtractor by lazy { StreamHideVidExtractor(client, headers) }
|
||||
private val voeExtractor by lazy { VoeExtractor(client) }
|
||||
private val uqloadExtractor by lazy { UqloadExtractor(client) }
|
||||
private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) }
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val scriptContent = document.selectFirst("script:containsData(var video = [)")?.data()
|
||||
?: return emptyList()
|
||||
val videoList = mutableListOf<Video>()
|
||||
val videoPattern = Regex("""video\[\d+\] = '<iframe src="(.*?)"""")
|
||||
val matches = videoPattern.findAll(scriptContent)
|
||||
matches.forEach { match ->
|
||||
val url = match.groupValues[1]
|
||||
val videos = when {
|
||||
url.contains("streamtape") -> listOfNotNull(streamTapeExtractor.videoFromUrl(url))
|
||||
url.contains("ok.ru") -> okruExtractor.videosFromUrl(url)
|
||||
url.contains("yourupload") -> yourUploadExtractor.videoFromUrl(url, headers)
|
||||
url.contains("streamwish") || url.contains("playerwish") -> streamWishExtractor.videosFromUrl(url)
|
||||
url.contains("streamhidevid") -> streamHideVidExtractor.videosFromUrl(url)
|
||||
url.contains("voe") -> voeExtractor.videosFromUrl(url)
|
||||
url.contains("uqload") -> uqloadExtractor.videosFromUrl(url)
|
||||
url.contains("mp4upload") -> mp4uploadExtractor.videosFromUrl(url, headers)
|
||||
else -> universalExtractor.videosFromUrl(url, headers)
|
||||
}
|
||||
videoList.addAll(videos)
|
||||
}
|
||||
return videoList.sort()
|
||||
}
|
||||
|
||||
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 params = AnimejlFilters.getSearchParameters(filters)
|
||||
return when {
|
||||
query.isNotBlank() -> GET("$baseUrl/animes?q=$query&page=$page")
|
||||
params.filter.isNotBlank() -> GET("$baseUrl/animes${params.getQuery()}&page=$page")
|
||||
else -> popularAnimeRequest(page)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFilterList(): AnimeFilterList = AnimejlFilters.FILTER_LIST
|
||||
|
||||
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/animes?order=updated&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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
package eu.kanade.tachiyomi.animeextension.es.animejl
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import java.util.Calendar
|
||||
|
||||
object AnimejlFilters {
|
||||
open class QueryPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : AnimeFilter.Select<String>(
|
||||
displayName,
|
||||
vals.map { it.first }.toTypedArray(),
|
||||
) {
|
||||
fun toQueryPart(name: String) = vals[state].second.takeIf { it.isNotEmpty() }?.let { "&$name=${vals[state].second}" } ?: run { "" }
|
||||
}
|
||||
|
||||
open class CheckBoxFilterList(name: String, values: List<CheckBox>) : AnimeFilter.Group<AnimeFilter.CheckBox>(name, values)
|
||||
|
||||
private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
|
||||
|
||||
private inline fun <reified R> AnimeFilterList.parseCheckbox(
|
||||
options: Array<Pair<String, String>>,
|
||||
name: String,
|
||||
): String {
|
||||
return (this.getFirst<R>() as CheckBoxFilterList).state
|
||||
.mapNotNull { checkbox ->
|
||||
if (checkbox.state) {
|
||||
options.find { it.first == checkbox.name }!!.second
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.joinToString("&$name[]=").let {
|
||||
if (it.isBlank()) {
|
||||
""
|
||||
} else {
|
||||
"&$name[]=$it"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified R> AnimeFilterList.asQueryPart(name: String): String {
|
||||
return (this.getFirst<R>() as QueryPartFilter).toQueryPart(name)
|
||||
}
|
||||
|
||||
private inline fun <reified R> AnimeFilterList.getFirst(): R {
|
||||
return this.filterIsInstance<R>().first()
|
||||
}
|
||||
|
||||
private fun String.changePrefix() = this.takeIf { it.startsWith("&") }?.let { this.replaceFirst("&", "?") } ?: run { this }
|
||||
|
||||
data class FilterSearchParams(val filter: String = "") { fun getQuery() = filter.changePrefix() }
|
||||
|
||||
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
|
||||
if (filters.isEmpty()) return FilterSearchParams()
|
||||
return FilterSearchParams(
|
||||
filters.parseCheckbox<GenresFilter>(AnimeFlvFiltersData.GENRES, "genre") +
|
||||
filters.parseCheckbox<YearsFilter>(AnimeFlvFiltersData.YEARS, "year") +
|
||||
filters.parseCheckbox<TypesFilter>(AnimeFlvFiltersData.TYPES, "type") +
|
||||
filters.parseCheckbox<StateFilter>(AnimeFlvFiltersData.STATE, "estado") +
|
||||
filters.asQueryPart<SortFilter>("order"),
|
||||
)
|
||||
}
|
||||
|
||||
val FILTER_LIST get() = AnimeFilterList(
|
||||
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
|
||||
GenresFilter(),
|
||||
YearsFilter(),
|
||||
TypesFilter(),
|
||||
StateFilter(),
|
||||
SortFilter(),
|
||||
)
|
||||
|
||||
class GenresFilter : CheckBoxFilterList("Género", AnimeFlvFiltersData.GENRES.map { CheckBoxVal(it.first, false) })
|
||||
|
||||
class YearsFilter : CheckBoxFilterList("Año", AnimeFlvFiltersData.YEARS.map { CheckBoxVal(it.first, false) })
|
||||
|
||||
class TypesFilter : CheckBoxFilterList("Tipo", AnimeFlvFiltersData.TYPES.map { CheckBoxVal(it.first, false) })
|
||||
|
||||
class StateFilter : CheckBoxFilterList("Estado", AnimeFlvFiltersData.STATE.map { CheckBoxVal(it.first, false) })
|
||||
|
||||
class SortFilter : QueryPartFilter("Orden", AnimeFlvFiltersData.SORT)
|
||||
|
||||
private object AnimeFlvFiltersData {
|
||||
val YEARS = (1990..Calendar.getInstance().get(Calendar.YEAR)).map { Pair("$it", "$it") }.reversed().toTypedArray()
|
||||
|
||||
val TYPES = arrayOf(
|
||||
Pair("Anime", "1"),
|
||||
Pair("Ova", "2"),
|
||||
Pair("Pelicula", "3"),
|
||||
Pair("Donghua", "7"),
|
||||
)
|
||||
|
||||
val STATE = arrayOf(
|
||||
Pair("En emisión", "0"),
|
||||
Pair("Finalizado", "1"),
|
||||
Pair("Próximamente", "2"),
|
||||
)
|
||||
|
||||
val SORT = arrayOf(
|
||||
Pair("Por Defecto", "created"),
|
||||
Pair("Recientemente Actualizados", "updated"),
|
||||
Pair("Nombre A-Z", "titleaz"),
|
||||
Pair("Nombre Z-A", "titleza"),
|
||||
Pair("Calificación", "rating"),
|
||||
Pair("Vistas", "views"),
|
||||
)
|
||||
|
||||
val GENRES = arrayOf(
|
||||
Pair("Acción", "1"),
|
||||
Pair("Artes Marciales", "2"),
|
||||
Pair("Aventuras", "3"),
|
||||
Pair("Ciencia Ficción", "33"),
|
||||
Pair("Comedia", "9"),
|
||||
Pair("Cultivación", "71"),
|
||||
Pair("Demencia", "40"),
|
||||
Pair("Demonios", "42"),
|
||||
Pair("Deportes", "27"),
|
||||
Pair("Donghua", "50"),
|
||||
Pair("Drama", "10"),
|
||||
Pair("Ecchi", "25"),
|
||||
Pair("Escolares", "22"),
|
||||
Pair("Espacial", "48"),
|
||||
Pair("Fantasia", "6"),
|
||||
Pair("Gore", "67"),
|
||||
Pair("Harem", "32"),
|
||||
Pair("Hentai", "31"),
|
||||
Pair("Historico", "43"),
|
||||
Pair("Horror", "39"),
|
||||
Pair("Isekai", "45"),
|
||||
Pair("Josei", "70"),
|
||||
Pair("Juegos", "11"),
|
||||
Pair("Latino / Castellano", "46"),
|
||||
Pair("Magia", "38"),
|
||||
Pair("Mecha", "41"),
|
||||
Pair("Militar", "44"),
|
||||
Pair("Misterio", "26"),
|
||||
Pair("Mitología", "73"),
|
||||
Pair("Musica", "28"),
|
||||
Pair("Parodia", "13"),
|
||||
Pair("Policía", "51"),
|
||||
Pair("Psicologico", "29"),
|
||||
Pair("Recuentos de la vida", "23"),
|
||||
Pair("Reencarnación", "72"),
|
||||
Pair("Romance", "12"),
|
||||
Pair("Samurai", "69"),
|
||||
Pair("Seinen", "24"),
|
||||
Pair("Shoujo", "36"),
|
||||
Pair("Shounen", "4"),
|
||||
Pair("Sin Censura", "68"),
|
||||
Pair("Sobrenatural", "7"),
|
||||
Pair("Superpoderes", "5"),
|
||||
Pair("Suspenso", "21"),
|
||||
Pair("Terror", "20"),
|
||||
Pair("Vampiros", "49"),
|
||||
Pair("Venganza", "74"),
|
||||
Pair("Yaoi", "53"),
|
||||
Pair("Yuri", "52"),
|
||||
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'AnimeLatinoHD'
|
||||
extClass = '.AnimeLatinoHD'
|
||||
extVersionCode = 35
|
||||
extVersionCode = 38
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -85,21 +85,21 @@ class AnimeLatinoHD : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
if (url.contains("status=1")) {
|
||||
val latestData = data["data"]!!.jsonArray
|
||||
latestData.forEach { item ->
|
||||
val animeItem = item!!.jsonObject
|
||||
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
|
||||
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 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
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -122,12 +122,12 @@ class AnimeLatinoHD : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
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}"))
|
||||
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
|
||||
|
@ -144,11 +144,11 @@ class AnimeLatinoHD : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
val data = pageProps["data"]!!.jsonObject
|
||||
val arrEpisode = data["episodes"]!!.jsonArray
|
||||
arrEpisode.forEach { item ->
|
||||
val animeItem = item!!.jsonObject
|
||||
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()}"
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -158,7 +158,7 @@ class AnimeLatinoHD : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
|
||||
private fun parseJsonArray(json: JsonElement?): List<JsonElement> {
|
||||
val list = mutableListOf<JsonElement>()
|
||||
json!!.jsonObject!!.entries!!.forEach { list.add(it.value) }
|
||||
json!!.jsonObject.entries.forEach { list.add(it.value) }
|
||||
return list
|
||||
}
|
||||
|
||||
|
@ -178,11 +178,11 @@ class AnimeLatinoHD : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
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
|
||||
val players = if (playersElement !is JsonArray) JsonArray(parseJsonArray(playersElement)) else playersElement.jsonArray
|
||||
players.forEach { player ->
|
||||
val servers = player!!.jsonArray
|
||||
val servers = player.jsonArray
|
||||
servers.forEach { server ->
|
||||
val item = server!!.jsonObject
|
||||
val item = server.jsonObject
|
||||
val request = client.newCall(
|
||||
GET(
|
||||
url = "https://api.animelatinohd.com/stream/${item["id"]!!.jsonPrimitive.content}",
|
||||
|
@ -193,9 +193,9 @@ class AnimeLatinoHD : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
.build(),
|
||||
),
|
||||
).execute()
|
||||
val locationsDdh = request!!.networkResponse.toString()
|
||||
val locationsDdh = request.networkResponse.toString()
|
||||
fetchUrls(locationsDdh).map { url ->
|
||||
val language = if (item["languaje"]!!.jsonPrimitive!!.content == "1") "[LAT]" else "[SUB]"
|
||||
val language = if (item["languaje"]!!.jsonPrimitive.content == "1") "[LAT]" else "[SUB]"
|
||||
val embedUrl = url.lowercase()
|
||||
if (embedUrl.contains("filemoon")) {
|
||||
val vidHeaders = headers.newBuilder()
|
||||
|
@ -211,7 +211,7 @@ class AnimeLatinoHD : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
StreamTapeExtractor(client).videoFromUrl(url, "$language Streamtape")?.let { videoList.add(it) }
|
||||
}
|
||||
if (embedUrl.contains("dood")) {
|
||||
DoodExtractor(client).videoFromUrl(url, "$language DoodStream")?.let { videoList.add(it) }
|
||||
DoodExtractor(client).videoFromUrl(url, language)?.let { videoList.add(it) }
|
||||
}
|
||||
if (embedUrl.contains("okru") || embedUrl.contains("ok.ru")) {
|
||||
OkruExtractor(client).videosFromUrl(url, language).also(videoList::addAll)
|
||||
|
@ -281,11 +281,11 @@ class AnimeLatinoHD : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
val data = pageProps["data"]!!.jsonObject
|
||||
val arrData = data["data"]!!.jsonArray
|
||||
arrData.forEach { item ->
|
||||
val animeItem = item!!.jsonObject
|
||||
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
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,28 +31,32 @@ class JsUnpacker(packedJS: String?) {
|
|||
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 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 symtab = m.group(4)?.split("\\|".toRegex())?.toTypedArray()
|
||||
val radix = radixStr?.toIntOrNull() ?: 36
|
||||
val count = countStr?.toIntOrNull() ?: 0
|
||||
if (symtab != null) {
|
||||
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)
|
||||
m = payload?.let { p.matcher(it) }!!
|
||||
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 (symtab != null) {
|
||||
if (x < symtab.size && x >= 0) {
|
||||
value = symtab[x]
|
||||
}
|
||||
}
|
||||
if (value != null && value.isNotEmpty()) {
|
||||
if (!value.isNullOrEmpty()) {
|
||||
decoded.replace(m.start() + replaceOffset, m.end() + replaceOffset, value)
|
||||
replaceOffset += value.length - word.length
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'AnimeMovil'
|
||||
extClass = '.AnimeMovil'
|
||||
extVersionCode = 17
|
||||
extVersionCode = 27
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -20,4 +20,5 @@ dependencies {
|
|||
implementation(project(':lib:streamtape-extractor'))
|
||||
implementation(project(':lib:playlist-utils'))
|
||||
implementation(project(':lib:streamlare-extractor'))
|
||||
implementation(project(':lib:universal-extractor'))
|
||||
}
|
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.animeextension.es.animemovil
|
|||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||
|
@ -20,6 +21,7 @@ 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.universalextractor.UniversalExtractor
|
||||
import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor
|
||||
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
|
||||
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
|
||||
|
@ -233,50 +235,57 @@ class AnimeMovil : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
val videoList = mutableListOf<Video>()
|
||||
val embedUrl = url.lowercase()
|
||||
try {
|
||||
if (embedUrl.contains("voe")) {
|
||||
VoeExtractor(client).videosFromUrl(url).also(videoList::addAll)
|
||||
return when {
|
||||
embedUrl.contains("voe") -> {
|
||||
VoeExtractor(client).videosFromUrl(url).also(videoList::addAll)
|
||||
}
|
||||
embedUrl.contains("filemoon") || embedUrl.contains("moonplayer") -> {
|
||||
FilemoonExtractor(client).videosFromUrl(url, prefix = "Filemoon:").also(videoList::addAll)
|
||||
}
|
||||
embedUrl.contains("uqload") -> {
|
||||
UqloadExtractor(client).videosFromUrl(url).also(videoList::addAll)
|
||||
}
|
||||
embedUrl.contains("mp4upload") -> {
|
||||
val newHeaders = headers.newBuilder().add("referer", "https://re.animepelix.net/").build()
|
||||
Mp4uploadExtractor(client).videosFromUrl(url, newHeaders).also(videoList::addAll)
|
||||
}
|
||||
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)
|
||||
}
|
||||
embedUrl.contains("doodstream") || embedUrl.contains("dood.") -> {
|
||||
DoodExtractor(client).videosFromUrl(url, "DoodStream").also(videoList::addAll)
|
||||
}
|
||||
embedUrl.contains("streamlare") -> {
|
||||
StreamlareExtractor(client).videosFromUrl(url).also(videoList::addAll)
|
||||
}
|
||||
embedUrl.contains("yourupload") -> {
|
||||
YourUploadExtractor(client).videoFromUrl(url, headers = headers).also(videoList::addAll)
|
||||
}
|
||||
embedUrl.contains("burstcloud") || embedUrl.contains("burst") -> {
|
||||
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).also(videoList::addAll)
|
||||
}
|
||||
embedUrl.contains("fastream") -> {
|
||||
FastreamExtractor(client, headers).videosFromUrl(url).also(videoList::addAll)
|
||||
}
|
||||
embedUrl.contains("upstream") -> {
|
||||
UpstreamExtractor(client).videosFromUrl(url).also(videoList::addAll)
|
||||
}
|
||||
embedUrl.contains("streamtape") -> {
|
||||
StreamTapeExtractor(client).videosFromUrl(url).also(videoList::addAll)
|
||||
}
|
||||
embedUrl.contains("filelions") || embedUrl.contains("lion") -> {
|
||||
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "FileLions:$it" }).also(videoList::addAll)
|
||||
}
|
||||
else -> {
|
||||
UniversalExtractor(client).videosFromUrl(url, headers).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) {}
|
||||
} catch (_: Exception) {
|
||||
Log.e("AnimeMovil", "Error: Server not supported")
|
||||
}
|
||||
return videoList
|
||||
}
|
||||
|
||||
|
|
15
src/es/animenix/build.gradle
Normal file
|
@ -0,0 +1,15 @@
|
|||
ext {
|
||||
extName = 'Animenix'
|
||||
extClass = '.Animenix'
|
||||
themePkg = 'dooplay'
|
||||
baseUrl = 'https://animenix.com'
|
||||
overrideVersionCode = 8
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation(project(":lib:filemoon-extractor"))
|
||||
implementation(project(":lib:streamwish-extractor"))
|
||||
implementation(project(":lib:universal-extractor"))
|
||||
}
|
BIN
src/es/animenix/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
src/es/animenix/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
src/es/animenix/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
src/es/animenix/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 8 KiB |
BIN
src/es/animenix/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 11 KiB |
|
@ -0,0 +1,215 @@
|
|||
package eu.kanade.tachiyomi.animeextension.es.animenix
|
||||
|
||||
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.filemoonextractor.FilemoonExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
|
||||
import eu.kanade.tachiyomi.lib.universalextractor.UniversalExtractor
|
||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class Animenix : DooPlay(
|
||||
"es",
|
||||
"Animenix",
|
||||
"https://animenix.com",
|
||||
) {
|
||||
|
||||
// ============================== Popular ===============================
|
||||
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/ratings/$page")
|
||||
|
||||
override fun popularAnimeSelector() = latestUpdatesSelector()
|
||||
|
||||
override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector()
|
||||
|
||||
// =============================== Search ===============================
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
override val episodeMovieText = "Película"
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val players = response.asJsoup().select("li.dooplay_player_option")
|
||||
return players.flatMap { player ->
|
||||
runCatching {
|
||||
val link = getPlayerUrl(player)
|
||||
getPlayerVideos(link)
|
||||
}.getOrElse { emptyList() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPlayerUrl(player: Element): String {
|
||||
val body = FormBody.Builder()
|
||||
.add("action", "doo_player_ajax")
|
||||
.add("post", player.attr("data-post"))
|
||||
.add("nume", player.attr("data-nume"))
|
||||
.add("type", player.attr("data-type"))
|
||||
.build()
|
||||
return client.newCall(POST("$baseUrl/wp-admin/admin-ajax.php", headers, body))
|
||||
.execute()
|
||||
.let { response ->
|
||||
response.body.string()
|
||||
.substringAfter("\"embed_url\":\"")
|
||||
.substringBefore("\",")
|
||||
.replace("\\", "")
|
||||
}
|
||||
}
|
||||
|
||||
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
|
||||
private val streamWishExtractor by lazy { StreamWishExtractor(headers = headers, client = client) }
|
||||
private val universalExtractor by lazy { UniversalExtractor(client) }
|
||||
|
||||
private fun getPlayerVideos(link: String): List<Video> {
|
||||
return when {
|
||||
link.contains("filemoon") -> filemoonExtractor.videosFromUrl(link)
|
||||
link.contains("swdyu") -> streamWishExtractor.videosFromUrl(link)
|
||||
link.contains("wishembed") || link.contains("cdnwish") || link.contains("flaswish") || link.contains("sfastwish") || link.contains("streamwish") || link.contains("asnwish") -> streamWishExtractor.videosFromUrl(link)
|
||||
else -> universalExtractor.videosFromUrl(link, headers)
|
||||
}
|
||||
}
|
||||
|
||||
// =========================== 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 fun latestUpdatesRequest(page: Int) = GET("$baseUrl/ver/page/$page", headers)
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = "div.pagination > *:last-child:not(span):not(.current)"
|
||||
|
||||
// =============================== Search ===============================
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
val params = AnimenixFilters.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){
|
||||
"anime" -> "serie"
|
||||
"pelicula" -> "pelicula"
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Filters ===============================
|
||||
override val fetchGenres = false
|
||||
|
||||
override fun getFilterList() = AnimenixFilters.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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
package eu.kanade.tachiyomi.animeextension.es.animenix
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
|
||||
object AnimenixFilters {
|
||||
|
||||
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 YearFilter : UriPartFilter("Año", AnimesOnlineNinjaData.YEARS)
|
||||
|
||||
class OtherOptionsGroup : AnimeFilter.Group<UriPartFilter>(
|
||||
"Otros filtros",
|
||||
listOf(
|
||||
GenreFilter(),
|
||||
YearFilter(),
|
||||
),
|
||||
)
|
||||
|
||||
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<YearFilter>(),
|
||||
)
|
||||
}
|
||||
|
||||
private object AnimesOnlineNinjaData {
|
||||
val EVERY = Pair("Seleccionar", "")
|
||||
|
||||
val TYPES = arrayOf(
|
||||
Pair("Seleccionar", ""),
|
||||
Pair("Anime", "anime"),
|
||||
Pair("Peliculas", "pelicula"),
|
||||
)
|
||||
|
||||
val LETTERS = arrayOf(EVERY) + ('a'..'z').map {
|
||||
Pair(it.toString(), it.toString())
|
||||
}.toTypedArray()
|
||||
|
||||
val GENRES = arrayOf(
|
||||
EVERY,
|
||||
Pair("Acción", "accion"),
|
||||
Pair("Action", "action"),
|
||||
Pair("Action-Adventure", "action-adventure"),
|
||||
Pair("Aventura", "aventura"),
|
||||
Pair("Adventure", "adventure"),
|
||||
Pair("Animación", "animacion"),
|
||||
Pair("Animation", "animation"),
|
||||
Pair("Aventura (Torrent)", "aventura-torrent"),
|
||||
Pair("Bélica", "belica"),
|
||||
Pair("Ciencia Ficción", "ciencia-ficcion"),
|
||||
Pair("Comedia", "comedia"),
|
||||
Pair("Comedy", "comedy"),
|
||||
Pair("Crimen", "crimen"),
|
||||
Pair("Demonios", "demonios"),
|
||||
Pair("Deportes", "deportes"),
|
||||
Pair("Documental", "documental"),
|
||||
Pair("Drama", "drama"),
|
||||
Pair("Ecchi", "ecchi"),
|
||||
Pair("En Emisión", "en-emision"),
|
||||
Pair("Escolares", "escolares"),
|
||||
Pair("Familia", "familia"),
|
||||
Pair("Fantasía", "fantasia"),
|
||||
Pair("Fantasy", "fantasy"),
|
||||
Pair("Harem", "harem"),
|
||||
Pair("Historia", "historia"),
|
||||
Pair("Histórico", "historico"),
|
||||
Pair("History", "history"),
|
||||
Pair("Horror", "horror"),
|
||||
Pair("Kids", "kids"),
|
||||
Pair("Magia", "magia"),
|
||||
Pair("Misterio", "misterio"),
|
||||
Pair("Música", "musica"),
|
||||
Pair("Parodia", "parodia"),
|
||||
Pair("Película de TV", "pelicula-de-tv"),
|
||||
Pair("Reality", "reality"),
|
||||
Pair("Recuerdos de la Vida", "recuerdos-de-la-vida"),
|
||||
Pair("Romance", "romance"),
|
||||
Pair("Sci-Fi Fantasy", "sci-fi-fantasy"),
|
||||
Pair("Science Fiction", "science-fiction"),
|
||||
Pair("Seinen", "seinen"),
|
||||
Pair("Shojo", "shojo"),
|
||||
Pair("Shounen", "shounen"),
|
||||
Pair("Soap", "soap"),
|
||||
Pair("Sobrenatural", "sobrenatural"),
|
||||
Pair("Suspense", "suspense"),
|
||||
Pair("Terror", "terror"),
|
||||
Pair("Thriller", "thriller"),
|
||||
Pair("War & Politics", "war-politics"),
|
||||
Pair("Western", "western"),
|
||||
Pair("Yaoi", "yaoi"),
|
||||
Pair("Yuri", "yuri"),
|
||||
)
|
||||
val YEARS = arrayOf(EVERY) + (2024 downTo 1979).map {
|
||||
Pair(it.toString(), it.toString())
|
||||
}.toTypedArray()
|
||||
}
|
||||
}
|
|
@ -2,8 +2,8 @@ ext {
|
|||
extName = 'AnimeYT.es'
|
||||
extClass = '.AnimeYTES'
|
||||
themePkg = 'animestream'
|
||||
baseUrl = 'https://animeyt.es'
|
||||
overrideVersionCode = 3
|
||||
baseUrl = 'https://animeyt.pro'
|
||||
overrideVersionCode = 8
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -12,4 +12,8 @@ dependencies {
|
|||
implementation(project(":lib:okru-extractor"))
|
||||
implementation(project(":lib:streamtape-extractor"))
|
||||
implementation(project(":lib:sendvid-extractor"))
|
||||
implementation(project(":lib:yourupload-extractor"))
|
||||
implementation(project(":lib:burstcloud-extractor"))
|
||||
implementation(project(":lib:filemoon-extractor"))
|
||||
implementation(project(":lib:universal-extractor"))
|
||||
}
|
|
@ -1,29 +1,111 @@
|
|||
package eu.kanade.tachiyomi.animeextension.es.animeytes
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.lib.burstcloudextractor.BurstCloudExtractor
|
||||
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
|
||||
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.lib.universalextractor.UniversalExtractor
|
||||
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
|
||||
import eu.kanade.tachiyomi.multisrc.animestream.AnimeStream
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class AnimeYTES : AnimeStream(
|
||||
"es",
|
||||
"AnimeYT.es",
|
||||
"https://animeyt.es",
|
||||
) {
|
||||
override 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",
|
||||
"SendVid",
|
||||
"BurstCloud",
|
||||
"StreamTape",
|
||||
"Filemoon",
|
||||
"Okru",
|
||||
)
|
||||
}
|
||||
|
||||
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) }
|
||||
private val youruploadExtractor by lazy { YourUploadExtractor(client) }
|
||||
private val burstcloudExtractor by lazy { BurstCloudExtractor(client) }
|
||||
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
|
||||
private val universalExtractor by lazy { UniversalExtractor(client) }
|
||||
|
||||
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()
|
||||
"Your" -> youruploadExtractor.videoFromUrl(url, headers)
|
||||
"Alpha" -> burstcloudExtractor.videoFromUrl(url, headers)
|
||||
"Moon" -> filemoonExtractor.videosFromUrl(url)
|
||||
else -> universalExtractor.videosFromUrl(url, headers)
|
||||
}
|
||||
}
|
||||
|
||||
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_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)
|
||||
}
|
||||
}
|
||||
|
|
22
src/es/azanimex/AndroidManifest.xml
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application>
|
||||
<activity
|
||||
android:name=".es.azanimex.AzanimexUrlActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="www.az-animex.com"
|
||||
android:pathPattern="/anime/..*"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
7
src/es/azanimex/build.gradle
Normal file
|
@ -0,0 +1,7 @@
|
|||
ext {
|
||||
extName = 'az-animex'
|
||||
extClass = '.Azanimex'
|
||||
extVersionCode = 2
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
BIN
src/es/azanimex/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
src/es/azanimex/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
src/es/azanimex/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
src/es/azanimex/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/es/azanimex/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 19 KiB |
|
@ -0,0 +1,242 @@
|
|||
package eu.kanade.tachiyomi.animeextension.es.azanimex
|
||||
|
||||
import android.util.Log
|
||||
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.util.asJsoup
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.net.URLEncoder
|
||||
|
||||
class Azanimex : ParsedAnimeHttpSource() {
|
||||
|
||||
override val name = "az-animex"
|
||||
|
||||
override val baseUrl = "https://www.az-animex.com"
|
||||
|
||||
override val lang = "es"
|
||||
|
||||
override val supportsLatest = false
|
||||
|
||||
// ============================== Popular ===============================
|
||||
override fun popularAnimeRequest(page: Int): Request {
|
||||
val url = if (page > 1) "$baseUrl/?query-22-page=$page" else baseUrl
|
||||
return GET(url)
|
||||
}
|
||||
override fun popularAnimeSelector(): String = "li.wp-block-post"
|
||||
|
||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
||||
return SAnime.create().apply {
|
||||
setUrlWithoutDomain(element.selectFirst("h2.wp-block-post-title a")?.attr("href") ?: "")
|
||||
title =
|
||||
element.selectFirst("h2.wp-block-post-title a")?.text()?.substringBefore("[") ?: ""
|
||||
thumbnail_url =
|
||||
element.selectFirst("figure.wp-block-post-featured-image img")?.attr("data-src")
|
||||
|
||||
val genres = mutableListOf<String>()
|
||||
element.select("div[class*=taxonomy-genero] a").forEach { genres.add(it.text()) }
|
||||
element.select("div[class*=taxonomy-tipo] a")
|
||||
.forEach { genres.add("Tipo: ${it.text()}") }
|
||||
genre = genres.joinToString(", ")
|
||||
}
|
||||
}
|
||||
|
||||
override fun popularAnimeNextPageSelector(): String = "a.page-numbers:not(.prev):not(.next)"
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
override fun episodeListSelector() = throw Exception("Not used")
|
||||
|
||||
override fun episodeFromElement(element: Element): SEpisode {
|
||||
throw Exception("Not used")
|
||||
}
|
||||
|
||||
private fun getPathFromUrl(url: String): String {
|
||||
val cleanUrl = url.replace("https://", "").replace("http://", "")
|
||||
return cleanUrl.substringAfter("/").replace("//", "/").replaceFirst("es/", "")
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = response.asJsoup()
|
||||
|
||||
val mainUrl = document.select("a.su-button[href*='.az-animex.com']").firstOrNull()
|
||||
?.attr("href") ?: throw Exception("No se encontró la URL de los episodios")
|
||||
|
||||
val updatedUrl = updateDomainInUrl(mainUrl)
|
||||
|
||||
val path = getPathFromUrl(updatedUrl)
|
||||
|
||||
val url1 = updatedUrl.substringAfter("https://").substringBefore("/")
|
||||
|
||||
val apiUrl = "https://$url1/api?path=$path"
|
||||
|
||||
val apiResponse = client.newCall(
|
||||
GET(apiUrl, headers = headers),
|
||||
).execute()
|
||||
|
||||
return try {
|
||||
val json = Json { ignoreUnknownKeys = true }
|
||||
val responseBody = apiResponse.body.string()
|
||||
val result = json.decodeFromString<OneDriveResponse>(responseBody)
|
||||
|
||||
val episodes = mutableListOf<SEpisode>()
|
||||
|
||||
result.file?.let { file ->
|
||||
if (file.name.endsWith(".mp4")) {
|
||||
val episode = SEpisode.create().apply {
|
||||
val animeurl = "https://$url1/api/raw/?path=$path"
|
||||
name = file.name.substringAfter("] ").substringBeforeLast(" [")
|
||||
episode_number = parseEpisodeNumber(file.name)
|
||||
Log.d("Azanimex", "URL del anime: $animeurl")
|
||||
|
||||
url = animeurl
|
||||
}
|
||||
episodes.add(episode)
|
||||
}
|
||||
}
|
||||
|
||||
result.folder?.value?.forEach { file ->
|
||||
if (file.name.endsWith(".mp4")) {
|
||||
val episode = SEpisode.create().apply {
|
||||
val fileUrl = URLEncoder.encode(file.name, "UTF-8")
|
||||
val animeurl = "https://$url1/api/raw/?path=$path/$fileUrl"
|
||||
|
||||
// Ajuste para extraer correctamente el nombre del episodio
|
||||
name = file.name.substringAfter("] ").substringBeforeLast(" [")
|
||||
episode_number = parseEpisodeNumber(file.name)
|
||||
Log.d("Azanimex", "URL del anime: $animeurl")
|
||||
|
||||
url = animeurl
|
||||
}
|
||||
episodes.add(episode)
|
||||
}
|
||||
}
|
||||
episodes.sortedByDescending { it.name }
|
||||
} catch (e: Exception) {
|
||||
throw Exception("Error al procesar los episodios: ${e.message}, Respuesta JSON: ")
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseEpisodeNumber(filename: String): Float {
|
||||
val regex = Regex("""-(\d+)\s*\[""")
|
||||
return regex.find(filename)?.groupValues?.get(1)?.toFloatOrNull() ?: 0f
|
||||
}
|
||||
|
||||
private fun updateDomainInUrl(url: String): String {
|
||||
return when {
|
||||
url.contains("series-am") -> url.replace("series-am", "series-am2")
|
||||
url.contains("series-nz") -> url.replace("series-nz", "series-nz2")
|
||||
else -> url
|
||||
}
|
||||
}
|
||||
|
||||
// =========================== Anime Details ===========================
|
||||
override fun animeDetailsParse(document: Document): SAnime {
|
||||
return SAnime.create().apply {
|
||||
title = document.select("span.post-info:contains(Título) + br").first()?.previousSibling()?.toString()?.trim() ?: ""
|
||||
|
||||
val infoMap = document.select("span.post-info").associate { span ->
|
||||
val label = span.text().trim()
|
||||
val value = span.nextSibling()?.toString()?.trim() ?: ""
|
||||
label to value
|
||||
}
|
||||
|
||||
description = document.select("div.su-spoiler-content").first()?.text()?.trim() ?: ""
|
||||
|
||||
genre = infoMap["Géneros"]?.substringBefore(".")
|
||||
author = infoMap["Estudio"]
|
||||
status = when (infoMap["Episodios"]?.substringAfter("de ")?.trim()) {
|
||||
infoMap["Episodios"]?.substringBefore(" de")?.trim() -> SAnime.COMPLETED
|
||||
else -> SAnime.ONGOING
|
||||
}
|
||||
|
||||
// Información adicional para la descripción
|
||||
val additionalInfo = buildString {
|
||||
appendLine("\n\nInformación:")
|
||||
if (!infoMap["Año"].isNullOrBlank()) appendLine("• Año: ${infoMap["Año"]}")
|
||||
if (!infoMap["Episodios"].isNullOrBlank()) appendLine("• Episodios: ${infoMap["Episodios"]}")
|
||||
if (!infoMap["Duración"].isNullOrBlank()) appendLine("• Duración: ${infoMap["Duración"]}")
|
||||
if (!infoMap["Fansub"].isNullOrBlank()) appendLine("• Fansub: ${infoMap["Fansub"]}")
|
||||
if (!infoMap["Versión"].isNullOrBlank()) appendLine("• Versión: ${infoMap["Versión"]}")
|
||||
if (!infoMap["Resolución"].isNullOrBlank()) appendLine("• Resolución: ${infoMap["Resolución"]}")
|
||||
if (!infoMap["Formato"].isNullOrBlank()) appendLine("• Formato: ${infoMap["Formato"]}")
|
||||
}
|
||||
|
||||
description += additionalInfo
|
||||
}
|
||||
}
|
||||
|
||||
// =============================== Search ===============================
|
||||
override fun searchAnimeFromElement(element: Element): SAnime {
|
||||
val anime = SAnime.create()
|
||||
|
||||
val urlElement = element.selectFirst("h2.wp-block-post-title a")
|
||||
anime.setUrlWithoutDomain(urlElement?.attr("href") ?: "")
|
||||
|
||||
anime.title = urlElement?.text() ?: "Título desconocido"
|
||||
|
||||
anime.thumbnail_url = element.selectFirst("figure.gs-hover-scale-img img")?.attr("src") ?: ""
|
||||
Log.d("Azanimex", "URL de la imagen: ${anime.thumbnail_url}")
|
||||
|
||||
val genres = element.select("div.taxonomy-tipo a, div.taxonomy-version a").joinToString { it.text() }
|
||||
anime.genre = genres.ifEmpty { "Desconocido" }
|
||||
|
||||
return anime
|
||||
}
|
||||
|
||||
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
|
||||
|
||||
override fun searchAnimeSelector(): String = popularAnimeSelector()
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
val url = if (page > 1) {
|
||||
"$baseUrl/page/$page/?s=$query"
|
||||
} else {
|
||||
"$baseUrl/?s=$query"
|
||||
}
|
||||
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
// ============================ Video URLS =============================
|
||||
|
||||
override fun videoListSelector() = throw Exception("Not used")
|
||||
|
||||
override fun videoFromElement(element: Element) = throw Exception("Not used")
|
||||
override fun videoUrlParse(document: Document): String {
|
||||
throw Exception("Not used")
|
||||
}
|
||||
|
||||
override suspend fun getVideoList(episode: SEpisode): List<Video> {
|
||||
val videoUrl = episode.url
|
||||
val video = Video(videoUrl, "az-animex", videoUrl)
|
||||
return listOf(video)
|
||||
}
|
||||
|
||||
// =============================== Latest ===============================
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun latestUpdatesSelector(): String {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SAnime {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun latestUpdatesNextPageSelector(): String? {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PREFIX_SEARCH = "id:"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package eu.kanade.tachiyomi.animeextension.es.azanimex
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
* Springboard that accepts https://www.az-animex.com/anime/<item> intents
|
||||
* and redirects them to the main Aniyomi process.
|
||||
*/
|
||||
class AzanimexUrlActivity : Activity() {
|
||||
|
||||
private val tag = javaClass.simpleName
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val pathSegments = intent?.data?.pathSegments
|
||||
if (pathSegments != null && pathSegments.size > 1) {
|
||||
val item = pathSegments[1]
|
||||
val mainIntent = Intent().apply {
|
||||
action = "eu.kanade.tachiyomi.ANIMESEARCH"
|
||||
putExtra("query", "${Azanimex.PREFIX_SEARCH}$item")
|
||||
putExtra("filter", packageName)
|
||||
}
|
||||
|
||||
try {
|
||||
startActivity(mainIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.e(tag, e.toString())
|
||||
}
|
||||
} else {
|
||||
Log.e(tag, "could not parse uri from intent $intent")
|
||||
}
|
||||
|
||||
finish()
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package eu.kanade.tachiyomi.animeextension.es.azanimex
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class OneDriveResponse(
|
||||
val file: FileItem? = null, // El archivo puede ser null
|
||||
val folder: Folder? = null, // La carpeta también puede ser null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Folder(
|
||||
val value: List<FileItem>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class FileInfo(
|
||||
val mimeType: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class FileItem(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val file: FileInfo,
|
||||
val video: VideoInfo?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class VideoInfo(
|
||||
val height: Int,
|
||||
val width: Int,
|
||||
val audioChannels: Int,
|
||||
val audioBitsPerSample: Int,
|
||||
val frameRate: Double,
|
||||
val bitRate: Int,
|
||||
val duration: Int,
|
||||
)
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'Cine24h'
|
||||
extClass = '.Cine24h'
|
||||
extVersionCode = 4
|
||||
extVersionCode = 10
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -12,4 +12,5 @@ dependencies {
|
|||
implementation(project(':lib:filemoon-extractor'))
|
||||
implementation(project(':lib:voe-extractor'))
|
||||
implementation(project(':lib:vidguard-extractor'))
|
||||
implementation(project(':lib:universal-extractor'))
|
||||
}
|
|
@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
|||
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.universalextractor.UniversalExtractor
|
||||
import eu.kanade.tachiyomi.lib.vidguardextractor.VidGuardExtractor
|
||||
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
|
@ -155,6 +156,7 @@ open class Cine24h : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
private val doodExtractor by lazy { DoodExtractor(client) }
|
||||
private val voeExtractor by lazy { VoeExtractor(client) }
|
||||
private val vidGuardExtractor by lazy { VidGuardExtractor(client) }
|
||||
private val universalExtractor by lazy { UniversalExtractor(client) }
|
||||
|
||||
private fun serverVideoResolver(url: String): List<Video> {
|
||||
val embedUrl = url.lowercase()
|
||||
|
@ -167,7 +169,7 @@ open class Cine24h : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
arrayOf("voe").any(url) -> voeExtractor.videosFromUrl(url)
|
||||
arrayOf("doodstream", "dood.", "ds2play", "doods.").any(url) -> doodExtractor.videosFromUrl(url)
|
||||
arrayOf("vembed", "guard", "listeamed", "bembed", "vgfplay").any(url) -> vidGuardExtractor.videosFromUrl(url)
|
||||
else -> emptyList()
|
||||
else -> universalExtractor.videosFromUrl(url, headers)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'CineCalidad'
|
||||
extClass = '.CineCalidad'
|
||||
extVersionCode = 5
|
||||
extVersionCode = 16
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -23,4 +23,6 @@ dependencies {
|
|||
implementation(project(':lib:fastream-extractor'))
|
||||
implementation(project(':lib:upstream-extractor'))
|
||||
implementation(project(':lib:streamhidevid-extractor'))
|
||||
implementation(project(':lib:goodstream-extractor'))
|
||||
implementation(project(':lib:universal-extractor'))
|
||||
}
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 5.3 KiB |
BIN
src/es/cinecalidad/res/mipmap-hdpi/ic_launcher_adaptive_back.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
src/es/cinecalidad/res/mipmap-hdpi/ic_launcher_adaptive_fore.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 3.1 KiB |
BIN
src/es/cinecalidad/res/mipmap-mdpi/ic_launcher_adaptive_back.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
src/es/cinecalidad/res/mipmap-mdpi/ic_launcher_adaptive_fore.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 9.2 KiB |
After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 41 KiB |
|
@ -15,12 +15,14 @@ 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.goodstramextractor.GoodStreamExtractor
|
||||
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
|
||||
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamhidevidextractor.StreamHideVidExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
|
||||
import eu.kanade.tachiyomi.lib.universalextractor.UniversalExtractor
|
||||
import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor
|
||||
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
|
||||
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
|
||||
|
@ -167,7 +169,7 @@ class CineCalidad : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" })
|
||||
}
|
||||
embedUrl.contains("doodstream") || embedUrl.contains("dood.") || embedUrl.contains("ds2play") || embedUrl.contains("doods.") -> {
|
||||
DoodExtractor(client).videosFromUrl(url.replace("https://doodstream.com/e/", "https://dood.to/e/"), "DoodStream", false)
|
||||
DoodExtractor(client).videosFromUrl(url.replace("https://doodstream.com/e/", "https://dood.to/e/"), "DoodStream")
|
||||
}
|
||||
embedUrl.contains("streamlare") -> StreamlareExtractor(client).videosFromUrl(url)
|
||||
embedUrl.contains("yourupload") || embedUrl.contains("upload") -> YourUploadExtractor(client).videoFromUrl(url, headers = headers)
|
||||
|
@ -175,8 +177,9 @@ class CineCalidad : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||
embedUrl.contains("fastream") -> FastreamExtractor(client, headers).videosFromUrl(url, prefix = "Fastream:")
|
||||
embedUrl.contains("upstream") -> UpstreamExtractor(client).videosFromUrl(url)
|
||||
embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape") -> listOf(StreamTapeExtractor(client).videoFromUrl(url, quality = "StreamTape")!!)
|
||||
embedUrl.contains("ahvsh") || embedUrl.contains("streamhide") || embedUrl.contains("guccihide") || embedUrl.contains("streamvid") || embedUrl.contains("vidhide") -> StreamHideVidExtractor(client).videosFromUrl(url)
|
||||
else -> emptyList()
|
||||
embedUrl.contains("ahvsh") || embedUrl.contains("streamhide") || embedUrl.contains("guccihide") || embedUrl.contains("streamvid") || embedUrl.contains("vidhide") -> StreamHideVidExtractor(client, headers).videosFromUrl(url)
|
||||
embedUrl.contains("goodstream") -> GoodStreamExtractor(client, headers).videosFromUrl(url, name = "GoodStream: ")
|
||||
else -> UniversalExtractor(client).videosFromUrl(url, headers)
|
||||
}
|
||||
}.getOrNull() ?: emptyList()
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ ext {
|
|||
extClass = '.Cineplus123'
|
||||
themePkg = 'dooplay'
|
||||
baseUrl = 'https://cineplus123.org'
|
||||
overrideVersionCode = 3
|
||||
overrideVersionCode = 5
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -11,4 +11,5 @@ apply from: "$rootDir/common.gradle"
|
|||
dependencies {
|
||||
implementation(project(":lib:streamwish-extractor"))
|
||||
implementation(project(":lib:uqload-extractor"))
|
||||
implementation(project(":lib:universal-extractor"))
|
||||
}
|
|
@ -5,6 +5,7 @@ import androidx.preference.PreferenceScreen
|
|||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
|
||||
import eu.kanade.tachiyomi.lib.universalextractor.UniversalExtractor
|
||||
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
|
||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
|
@ -39,6 +40,7 @@ class Cineplus123 : DooPlay(
|
|||
|
||||
private val uqloadExtractor by lazy { UqloadExtractor(client) }
|
||||
private val streamWishExtractor by lazy { StreamWishExtractor(client, headers) }
|
||||
private val universalExtractor by lazy { UniversalExtractor(client) }
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
|
@ -56,8 +58,8 @@ class Cineplus123 : DooPlay(
|
|||
return when {
|
||||
"uqload" in url -> uqloadExtractor.videosFromUrl(url, "$lang -")
|
||||
"strwish" in url -> streamWishExtractor.videosFromUrl(url, lang)
|
||||
else -> null
|
||||
} ?: emptyList()
|
||||
else -> universalExtractor.videosFromUrl(url, headers, prefix = lang)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPlayerUrl(player: Element): String? {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'Cuevana'
|
||||
extClass = '.CuevanaFactory'
|
||||
extVersionCode = 36
|
||||
extVersionCode = 45
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -22,5 +22,6 @@ dependencies {
|
|||
implementation(project(':lib:burstcloud-extractor'))
|
||||
implementation(project(':lib:fastream-extractor'))
|
||||
implementation(project(':lib:upstream-extractor'))
|
||||
implementation(project(':lib:universal-extractor'))
|
||||
implementation(libs.jsunpacker)
|
||||
}
|
|
@ -20,6 +20,7 @@ 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.universalextractor.UniversalExtractor
|
||||
import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor
|
||||
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
|
||||
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
|
||||
|
@ -160,98 +161,121 @@ class CuevanaCh(override val name: String, override val baseUrl: String) : Confi
|
|||
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"))
|
||||
return when {
|
||||
embedUrl.contains("voe") -> {
|
||||
VoeExtractor(client).videosFromUrl(url, prefix).also(videoList::addAll)
|
||||
videoList
|
||||
}
|
||||
(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 videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":").substringAfter("tempLink\":\"").substringBefore("\"")
|
||||
videoList.add(Video(videoUrl, "$prefix Amazon", videoUrl))
|
||||
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))
|
||||
}
|
||||
videoList
|
||||
}
|
||||
}
|
||||
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")
|
||||
embedUrl.contains("ok.ru") || embedUrl.contains("okru") -> {
|
||||
OkruExtractor(client).videosFromUrl(url, prefix).also(videoList::addAll)
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("filemoon") || embedUrl.contains("moonplayer") -> {
|
||||
val vidHeaders = headers.newBuilder()
|
||||
.add("Origin", "https://${url.toHttpUrl().host}")
|
||||
.add("Referer", "https://${url.toHttpUrl().host}/")
|
||||
.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)) }
|
||||
FilemoonExtractor(client).videosFromUrl(url, prefix = "$prefix Filemoon:", headers = vidHeaders).also(videoList::addAll)
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("uqload") -> {
|
||||
UqloadExtractor(client).videosFromUrl(url, prefix = prefix).also(videoList::addAll)
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("mp4upload") -> {
|
||||
Mp4uploadExtractor(client).videosFromUrl(url, headers, prefix = prefix).let { videoList.addAll(it) }
|
||||
videoList
|
||||
}
|
||||
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)
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("doodstream") || embedUrl.contains("dood.") -> {
|
||||
val url2 = url.replace("https://doodstream.com/e/", "https://dood.to/e/")
|
||||
DoodExtractor(client).videoFromUrl(url2, "$prefix DoodStream")?.let { videoList.add(it) }
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("streamlare") -> {
|
||||
StreamlareExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) }
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("yourupload") || embedUrl.contains("upload") -> {
|
||||
YourUploadExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) }
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("burstcloud") || embedUrl.contains("burst") -> {
|
||||
BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) }
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("fastream") -> {
|
||||
FastreamExtractor(client, headers).videosFromUrl(url, prefix = "$prefix Fastream:").also(videoList::addAll)
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("upstream") -> {
|
||||
UpstreamExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) }
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape") -> {
|
||||
StreamTapeExtractor(client).videoFromUrl(url, quality = "$prefix StreamTape")?.let { videoList.add(it) }
|
||||
videoList
|
||||
}
|
||||
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)) }
|
||||
}
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("filelions") || embedUrl.contains("lion") -> {
|
||||
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "$prefix FileLions:$it" }).also(videoList::addAll)
|
||||
videoList
|
||||
}
|
||||
else -> {
|
||||
UniversalExtractor(client).videosFromUrl(url, headers, prefix = prefix).also(videoList::addAll)
|
||||
}
|
||||
}
|
||||
if (embedUrl.contains("filelions") || embedUrl.contains("lion")) {
|
||||
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "$prefix FileLions:$it" }).also(videoList::addAll)
|
||||
}
|
||||
} catch (_: Exception) { }
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return videoList
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ 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.universalextractor.UniversalExtractor
|
||||
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
|
||||
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
|
@ -180,34 +181,47 @@ class CuevanaEu(override val name: String, override val baseUrl: String) : Confi
|
|||
private fun loadExtractor(url: String, prefix: String = ""): List<Video> {
|
||||
val videoList = mutableListOf<Video>()
|
||||
val embedUrl = url.lowercase()
|
||||
if (embedUrl.contains("yourupload")) {
|
||||
val videos = YourUploadExtractor(client).videoFromUrl(url, headers = headers)
|
||||
videoList.addAll(videos)
|
||||
return when {
|
||||
embedUrl.contains("yourupload") -> {
|
||||
val videos = YourUploadExtractor(client).videoFromUrl(url, headers = headers)
|
||||
videoList.addAll(videos)
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("doodstream") || embedUrl.contains("dood.") -> {
|
||||
DoodExtractor(client).videoFromUrl(url, "$prefix DoodStream")
|
||||
?.let { videoList.add(it) }
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("okru") || embedUrl.contains("ok.ru") -> {
|
||||
OkruExtractor(client).videosFromUrl(url, prefix, true).also(videoList::addAll)
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("voe") -> {
|
||||
VoeExtractor(client).videosFromUrl(url, prefix).also(videoList::addAll)
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("streamtape") -> {
|
||||
StreamTapeExtractor(client).videoFromUrl(url, "$prefix StreamTape")?.let { videoList.add(it) }
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("wishembed") || embedUrl.contains("streamwish") || embedUrl.contains("wish") -> {
|
||||
StreamWishExtractor(client, headers).videosFromUrl(url) { "$prefix StreamWish:$it" }
|
||||
.also(videoList::addAll)
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("filemoon") || embedUrl.contains("moonplayer") -> {
|
||||
FilemoonExtractor(client).videosFromUrl(url, "$prefix Filemoon:").also(videoList::addAll)
|
||||
videoList
|
||||
}
|
||||
embedUrl.contains("filelions") || embedUrl.contains("lion") -> {
|
||||
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "$prefix FileLions:$it" }).also(videoList::addAll)
|
||||
videoList
|
||||
}
|
||||
else -> {
|
||||
UniversalExtractor(client).videosFromUrl(url, headers, prefix = prefix).also(videoList::addAll)
|
||||
videoList
|
||||
}
|
||||
}
|
||||
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()
|
||||
|
|
18
src/es/detodopeliculas/build.gradle
Normal file
|
@ -0,0 +1,18 @@
|
|||
ext {
|
||||
extName = 'DeTodoPeliculas'
|
||||
extClass = '.DeTodoPeliculas'
|
||||
themePkg = 'dooplay'
|
||||
baseUrl = 'https://detodopeliculas.nu'
|
||||
overrideVersionCode = 2
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation(project(":lib:streamwish-extractor"))
|
||||
implementation(project(":lib:uqload-extractor"))
|
||||
implementation(project(":lib:universal-extractor"))
|
||||
implementation(project(':lib:streamhidevid-extractor'))
|
||||
implementation(project(':lib:vidguard-extractor'))
|
||||
implementation(project(':lib:voe-extractor'))
|
||||
}
|
BIN
src/es/detodopeliculas/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
src/es/detodopeliculas/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
src/es/detodopeliculas/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
src/es/detodopeliculas/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
src/es/detodopeliculas/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 49 KiB |
|
@ -0,0 +1,205 @@
|
|||
package eu.kanade.tachiyomi.animeextension.es.detodopeliculas
|
||||
|
||||
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.streamhidevidextractor.StreamHideVidExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
|
||||
import eu.kanade.tachiyomi.lib.universalextractor.UniversalExtractor
|
||||
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
|
||||
import eu.kanade.tachiyomi.lib.vidguardextractor.VidGuardExtractor
|
||||
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
|
||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.util.parallelFlatMapBlocking
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class DeTodoPeliculas : DooPlay(
|
||||
"es",
|
||||
"DeTodo Peliculas",
|
||||
"https://detodopeliculas.nu",
|
||||
) {
|
||||
// ============================== Popular ===============================
|
||||
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/novedades/page/$page")
|
||||
|
||||
override fun popularAnimeSelector() = latestUpdatesSelector()
|
||||
|
||||
override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector()
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/peliculas-de-estreno/page/$page", headers)
|
||||
|
||||
override fun videoListSelector() = "li.dooplay_player_option" // ul#playeroptionsul
|
||||
|
||||
override val episodeMovieText = "Película"
|
||||
|
||||
override val episodeSeasonPrefix = "Temporada"
|
||||
override val prefQualityTitle = "Calidad preferida"
|
||||
|
||||
private val uqloadExtractor by lazy { UqloadExtractor(client) }
|
||||
private val streamWishExtractor by lazy { StreamWishExtractor(client, headers) }
|
||||
private val universalExtractor by lazy { UniversalExtractor(client) }
|
||||
private val streamHideVidExtractor by lazy { StreamHideVidExtractor(client, headers) }
|
||||
private val vidGuardExtractor by lazy { VidGuardExtractor(client) }
|
||||
private val voeExtractor by lazy { VoeExtractor(client) }
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val players = document.select("ul#playeroptionsul li")
|
||||
return players.parallelFlatMapBlocking { player ->
|
||||
val flagSrc = player.selectFirst("span.flag img")?.attr("data-lazy-src") ?: ""
|
||||
val lang = when {
|
||||
"sub.png" in flagSrc -> "[SUB]"
|
||||
"cas.png" in flagSrc -> "[CAST]"
|
||||
"lat.png" in flagSrc -> "[LAT]"
|
||||
else -> "UNKNOWN"
|
||||
}
|
||||
val url = getPlayerUrl(player)
|
||||
?: return@parallelFlatMapBlocking emptyList<Video>()
|
||||
extractVideos(url, lang)
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractVideos(url: String, lang: String): List<Video> {
|
||||
val vidHideDomains = listOf("vidhide", "VidHidePro", "luluvdo", "vidhideplus")
|
||||
try {
|
||||
val videos = vidHideDomains.firstOrNull { it in url }?.let { domain ->
|
||||
streamHideVidExtractor.videosFromUrl(url, videoNameGen = { "$lang - $domain : $it" })
|
||||
} ?: emptyList()
|
||||
return when {
|
||||
videos.isNotEmpty() -> videos
|
||||
"uqload" in url -> uqloadExtractor.videosFromUrl(url, "$lang - ")
|
||||
"strwish" in url -> streamWishExtractor.videosFromUrl(url, "$lang - ")
|
||||
"vidguard" in url || "listeamed" in url -> vidGuardExtractor.videosFromUrl(url, "$lang - ")
|
||||
"voe" in url -> voeExtractor.videosFromUrl(url, "$lang - ")
|
||||
|
||||
else -> emptyList()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPlayerUrl(player: Element): String? {
|
||||
val body = FormBody.Builder()
|
||||
.add("action", "doo_player_ajax")
|
||||
.add("post", player.attr("data-post"))
|
||||
.add("nume", player.attr("data-nume"))
|
||||
.add("type", player.attr("data-type"))
|
||||
.build()
|
||||
|
||||
return client.newCall(POST("$baseUrl/wp-admin/admin-ajax.php", headers, body))
|
||||
.execute().body.string()
|
||||
.substringAfter("\"embed_url\":\"")
|
||||
.substringBefore("\",")
|
||||
.replace("\\", "")
|
||||
.takeIf(String::isNotBlank)
|
||||
}
|
||||
|
||||
// ============================== Filters ===============================
|
||||
override val fetchGenres = false
|
||||
|
||||
override fun getFilterList() = DeTodoPeliculasFilters.FILTER_LIST
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
val params = DeTodoPeliculasFilters.getSearchParameters(filters)
|
||||
val path = when {
|
||||
params.genre.isNotBlank() -> {
|
||||
if (params.genre in listOf("peliculas-de-estreno", "novedades", "peliculas-recomendadas", "peliculas")) {
|
||||
"/${params.genre}"
|
||||
} else {
|
||||
"/genero/${params.genre}"
|
||||
}
|
||||
}
|
||||
else -> buildString {
|
||||
append(
|
||||
when {
|
||||
query.isNotBlank() -> "/?s=$query"
|
||||
else -> "/"
|
||||
},
|
||||
)
|
||||
|
||||
if (params.isInverted) append("&orden=asc")
|
||||
}
|
||||
}
|
||||
|
||||
return if (path.startsWith("/?s=")) {
|
||||
GET("$baseUrl/page/$page$path")
|
||||
} else {
|
||||
GET("$baseUrl$path/page/$page")
|
||||
}
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
super.setupPreferenceScreen(screen) // Quality preference
|
||||
|
||||
val langPref = ListPreference(screen.context).apply {
|
||||
key = PREF_LANG_KEY
|
||||
title = PREF_LANG_TITLE
|
||||
entries = PREF_LANG_ENTRIES
|
||||
entryValues = PREF_LANG_VALUES
|
||||
setDefaultValue(PREF_LANG_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}
|
||||
ListPreference(screen.context).apply {
|
||||
key = PREF_SERVER_KEY
|
||||
title = "Preferred server"
|
||||
entries = SERVER_LIST
|
||||
entryValues = SERVER_LIST
|
||||
setDefaultValue(PREF_SERVER_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
screen.addPreference(langPref)
|
||||
}
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
override fun String.toDate() = 0L
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val quality = preferences.getString(prefQualityKey, prefQualityDefault)!!
|
||||
val lang = preferences.getString(PREF_LANG_KEY, PREF_LANG_DEFAULT)!!
|
||||
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
|
||||
return sortedWith(
|
||||
compareBy(
|
||||
{ it.quality.contains(lang) },
|
||||
{ it.quality.contains(server, true) },
|
||||
{ it.quality.contains(quality) },
|
||||
),
|
||||
).reversed()
|
||||
}
|
||||
|
||||
override val prefQualityValues = arrayOf("480p", "720p", "1080p")
|
||||
override val prefQualityEntries = prefQualityValues
|
||||
|
||||
companion object {
|
||||
private const val PREF_LANG_KEY = "preferred_lang"
|
||||
private const val PREF_LANG_TITLE = "Preferred language"
|
||||
private const val PREF_LANG_DEFAULT = "[LAT]"
|
||||
private const val PREF_SERVER_KEY = "preferred_server"
|
||||
private const val PREF_SERVER_DEFAULT = "Uqload"
|
||||
private val PREF_LANG_ENTRIES = arrayOf("[LAT]", "[SUB]", "[CAST]")
|
||||
private val PREF_LANG_VALUES = arrayOf("[LAT]", "[SUB]", "[CAST]")
|
||||
private val SERVER_LIST = arrayOf("StreamWish", "Uqload", "VidGuard", "StreamHideVid", "Voe")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package eu.kanade.tachiyomi.animeextension.es.detodopeliculas
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
|
||||
object DeTodoPeliculasFilters {
|
||||
|
||||
open class UriPartFilter(
|
||||
displayName: String,
|
||||
private val vals: Array<Pair<String, String>>,
|
||||
) : AnimeFilter.Select<String>(
|
||||
displayName,
|
||||
vals.map { it.first }.toTypedArray(),
|
||||
) {
|
||||
|
||||
fun toUriPart() = vals[state].second
|
||||
}
|
||||
|
||||
private inline fun <reified R> AnimeFilterList.getFirst(): R {
|
||||
return first { it is R } as R
|
||||
}
|
||||
|
||||
private inline fun <reified R> AnimeFilterList.asUriPart(): String {
|
||||
return getFirst<R>().let {
|
||||
(it as UriPartFilter).toUriPart()
|
||||
}
|
||||
}
|
||||
|
||||
class GenreFilter : UriPartFilter("Generos", AnimesOnlineNinjaData.GENRES)
|
||||
class YearFilter : UriPartFilter("Año", AnimesOnlineNinjaData.YEARS)
|
||||
|
||||
val FILTER_LIST get() = AnimeFilterList(
|
||||
GenreFilter(),
|
||||
YearFilter(),
|
||||
)
|
||||
|
||||
data class FilterSearchParams(
|
||||
val isInverted: Boolean = false,
|
||||
val genre: String = "",
|
||||
val year: String = "",
|
||||
)
|
||||
|
||||
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
|
||||
if (filters.isEmpty()) return FilterSearchParams()
|
||||
|
||||
return FilterSearchParams(
|
||||
genre = filters.asUriPart<GenreFilter>(),
|
||||
year = filters.asUriPart<YearFilter>(),
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
private object AnimesOnlineNinjaData {
|
||||
val EVERY = Pair("Seleccionar", "")
|
||||
|
||||
val GENRES = arrayOf(
|
||||
EVERY,
|
||||
Pair("Acción", "accion"),
|
||||
Pair("Animación", "animacion"),
|
||||
Pair("Aventura", "aventura"),
|
||||
Pair("Bélica", "belica"),
|
||||
Pair("Ciencia ficción", "ciencia-ficcion"),
|
||||
Pair("Comedia", "comedia"),
|
||||
Pair("Crimen", "crimen"),
|
||||
Pair("Documental", "documental"),
|
||||
Pair("Drama", "drama"),
|
||||
Pair("Familia", "familia"),
|
||||
)
|
||||
|
||||
val YEARS = arrayOf(EVERY) + (2024 downTo 1979).map {
|
||||
Pair(it.toString(), it.toString())
|
||||
}.toTypedArray()
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'Doramasflix'
|
||||
extClass = '.Doramasflix'
|
||||
extVersionCode = 25
|
||||
extVersionCode = 32
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -498,7 +498,7 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
"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)!!)
|
||||
listOf(DoodExtractor(client).videoFromUrl(url.replace("https://doodstream.com/e/", "https://dood.to/e/"), "$prefix DoodStream")!!)
|
||||
"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 -> {
|
||||
|
@ -512,7 +512,7 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
"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 ")
|
||||
"ahvsh" in embedUrl || "streamhide" in embedUrl -> StreamHideVidExtractor(client, headers).videosFromUrl(url, videoNameGen = { "$prefix StreamHide:$it" })
|
||||
"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()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'Doramasyt'
|
||||
extClass = '.Doramasyt'
|
||||
extVersionCode = 17
|
||||
extVersionCode = 19
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -15,4 +15,5 @@ dependencies {
|
|||
implementation(project(':lib:streamwish-extractor'))
|
||||
implementation(project(':lib:dood-extractor'))
|
||||
implementation(project(':lib:mixdrop-extractor'))
|
||||
implementation(project(':lib:universal-extractor'))
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor
|
|||
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.universalextractor.UniversalExtractor
|
||||
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
|
||||
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
|
@ -190,6 +191,7 @@ class Doramasyt : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
private val streamTapeExtractor by lazy { StreamTapeExtractor(client) }
|
||||
private val uqloadExtractor by lazy { UqloadExtractor(client) }
|
||||
private val okruExtractor by lazy { OkruExtractor(client) }
|
||||
private val universalExtractor by lazy { UniversalExtractor(client) }
|
||||
|
||||
private fun serverVideoResolver(url: String): List<Video> {
|
||||
val embedUrl = url.lowercase()
|
||||
|
@ -200,10 +202,10 @@ class Doramasyt : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
embedUrl.contains("filemoon") || embedUrl.contains("moonplayer") -> filemoonExtractor.videosFromUrl(url, prefix = "Filemoon:")
|
||||
embedUrl.contains("wishembed") || embedUrl.contains("streamwish") || embedUrl.contains("strwish") || embedUrl.contains("wish") || embedUrl.contains("wishfast") -> streamwishExtractor.videosFromUrl(url, videoNameGen = { "StreamWish:$it" })
|
||||
embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape") -> streamTapeExtractor.videosFromUrl(url)
|
||||
embedUrl.contains("doodstream") || embedUrl.contains("dood.") || embedUrl.contains("ds2play") || embedUrl.contains("doods.") -> doodExtractor.videosFromUrl(url, "DoodStream", false)
|
||||
embedUrl.contains("doodstream") || embedUrl.contains("dood.") || embedUrl.contains("ds2play") || embedUrl.contains("doods.") -> doodExtractor.videosFromUrl(url)
|
||||
embedUrl.contains("filelions") || embedUrl.contains("lion") -> streamwishExtractor.videosFromUrl(url, videoNameGen = { "FileLions:$it" })
|
||||
embedUrl.contains("mix") -> mixdropExtractor.videosFromUrl(url)
|
||||
else -> emptyList()
|
||||
else -> universalExtractor.videosFromUrl(url, headers)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package eu.kanade.tachiyomi.animeextension.es.doramasyt.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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'EnNovelas'
|
||||
extClass = '.EnNovelas'
|
||||
extVersionCode = 12
|
||||
extVersionCode = 19
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -14,4 +14,5 @@ dependencies {
|
|||
implementation(project(':lib:streamlare-extractor'))
|
||||
implementation(project(':lib:uqload-extractor'))
|
||||
implementation(project(':lib:vudeo-extractor'))
|
||||
implementation(project(':lib:dailymotion-extractor'))
|
||||
}
|
|
@ -5,12 +5,14 @@ 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.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.lib.dailymotionextractor.DailymotionExtractor
|
||||
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
|
||||
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
|
||||
|
@ -20,6 +22,7 @@ import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
|
|||
import eu.kanade.tachiyomi.lib.vudeoextractor.VudeoExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.Headers
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
|
@ -38,7 +41,7 @@ class EnNovelas : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||
|
||||
override val name = "EnNovelas"
|
||||
|
||||
override val baseUrl = "https://u.ennovelas.net"
|
||||
override val baseUrl = "https://tv.ennovelas.net/"
|
||||
|
||||
override val lang = "es"
|
||||
|
||||
|
@ -50,7 +53,7 @@ class EnNovelas : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||
|
||||
override fun popularAnimeSelector(): String = ".block-post"
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/telenovelas/page/$page")
|
||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/category/novelas-completas/page/$page")
|
||||
|
||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
||||
val anime = SAnime.create()
|
||||
|
@ -146,7 +149,7 @@ class EnNovelas : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||
.add("upgrade-insecure-requests", "1")
|
||||
.build()
|
||||
|
||||
client.newCall(POST(urlRequest, headers, body)).execute().asJsoup().select(".serversList li").map {
|
||||
client.newCall(POST(urlRequest, headers, body)).execute().asJsoup().select(".serversList li").map { it ->
|
||||
val frameString = it.attr("abs:data-server")
|
||||
val link = frameString.substringAfter("src='").substringBefore("'")
|
||||
.replace("https://api.mycdn.moe/sblink.php?id=", "https://streamsb.net/e/")
|
||||
|
@ -188,7 +191,12 @@ class EnNovelas : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||
}
|
||||
if (link.contains("streamlare")) {
|
||||
try {
|
||||
StreamlareExtractor(client).videosFromUrl(link)?.let { videoList.addAll(it) }
|
||||
StreamlareExtractor(client).videosFromUrl(link).let { videoList.addAll(it) }
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
if (link.contains("dailymotion")) {
|
||||
try {
|
||||
DailymotionExtractor(client, headers).videosFromUrl(link).let { videoList.addAll(it) }
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
|
@ -219,13 +227,6 @@ class EnNovelas : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||
return this
|
||||
}
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
return when {
|
||||
query.isNotBlank() -> GET("$baseUrl/search/$query/page/$page/")
|
||||
else -> popularAnimeRequest(page)
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val animeList = mutableListOf<SAnime>()
|
||||
|
@ -242,9 +243,48 @@ class EnNovelas : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||
}
|
||||
return AnimesPage(animeList, hasNextPage)
|
||||
}
|
||||
override fun searchAnimeFromElement(element: Element): SAnime = throw UnsupportedOperationException()
|
||||
override fun searchAnimeNextPageSelector(): String = throw UnsupportedOperationException()
|
||||
override fun searchAnimeSelector(): String = throw UnsupportedOperationException()
|
||||
|
||||
// =============================== Search ===============================
|
||||
override suspend fun getSearchAnime(page: Int, query: String, filters: AnimeFilterList): AnimesPage {
|
||||
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
|
||||
val id = query.removePrefix(PREFIX_SEARCH)
|
||||
client.newCall(GET("$baseUrl/search/$id", headers))
|
||||
.awaitSuccess()
|
||||
.use(::searchAnimeByIdParse)
|
||||
} else {
|
||||
super.getSearchAnime(page, query, filters)
|
||||
}
|
||||
}
|
||||
|
||||
private fun searchAnimeByIdParse(response: Response): AnimesPage {
|
||||
val details = animeDetailsParse(response.asJsoup())
|
||||
.apply {
|
||||
setUrlWithoutDomain(response.request.url.toString())
|
||||
initialized = true
|
||||
}
|
||||
return AnimesPage(listOf(details), false)
|
||||
}
|
||||
|
||||
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 yearFilter = filterList.find { it is YearFilter } as YearFilter
|
||||
val typeFilter = filterList.find { it is TypeFilter } as TypeFilter
|
||||
return when {
|
||||
query.isNotBlank() -> GET("$baseUrl/search/$query/page/$page/")
|
||||
genreFilter.state != 0 -> GET("$baseUrl/${genreFilter.toUriPart()}/page/$page/")
|
||||
yearFilter.state != 0 -> GET("$baseUrl/${yearFilter.toUriPart()}/page/$page/")
|
||||
typeFilter.state != 0 -> GET("$baseUrl/${typeFilter.toUriPart()}/page/$page/")
|
||||
|
||||
else -> popularAnimeRequest(page)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
@ -267,6 +307,62 @@ class EnNovelas : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
|
||||
AnimeFilter.Header("La búsqueda por texto ignora el filtro"),
|
||||
GenreFilter(),
|
||||
YearFilter(),
|
||||
TypeFilter(),
|
||||
)
|
||||
|
||||
private class GenreFilter : UriPartFilter(
|
||||
"Categorías",
|
||||
arrayOf(
|
||||
Pair("Seleccionar", ""),
|
||||
Pair("Novelas Mexicanas", "genre/novelas-mexicanas"),
|
||||
Pair("Novelas Colombianas", "genre/novelas-colombianas"),
|
||||
Pair("Series Y Novelas Turcas", "genre/series-y-novelas-turcas"),
|
||||
Pair("Novelas Brasileñas", "genre/novelas-brasilenas"),
|
||||
Pair("Novelas Americanas", "genre/novelas-americanas"),
|
||||
Pair("Novelas Españolas", "genre/novelas-espanolas"),
|
||||
Pair("Novelas Chilenas", "genre/telenovelas-chilenas"),
|
||||
Pair("Novelas Peruanas", "genre/novelas-peruanas"),
|
||||
Pair("Novelas Venezolanas", "genre/novelas-venezolanas"),
|
||||
Pair("Novelas Reino Unido", "genre/novelas-reino-unido"),
|
||||
Pair("Novelas Argentinas", "genre/novelas-argentinas"),
|
||||
Pair("Novelas Filipinas", "genre/novelas-filipinas"),
|
||||
Pair("Novelas Indias", "genre/novelas-indias"),
|
||||
),
|
||||
)
|
||||
|
||||
private class YearFilter : UriPartFilter(
|
||||
"Años",
|
||||
arrayOf(
|
||||
Pair("Seleccionar", ""),
|
||||
Pair("2024", "years/2024"),
|
||||
Pair("2023", "years/2023"),
|
||||
Pair("2022", "years/2022"),
|
||||
Pair("2021", "years/2021"),
|
||||
Pair("2020", "years/2020"),
|
||||
Pair("2019", "years/2019"),
|
||||
Pair("2018", "years/2018"),
|
||||
Pair("2017", "years/2017"),
|
||||
Pair("2016", "years/2016"),
|
||||
Pair("2015", "years/2015"),
|
||||
),
|
||||
)
|
||||
private class TypeFilter : UriPartFilter(
|
||||
"Tipo",
|
||||
arrayOf(
|
||||
Pair("Seleccionar", ""),
|
||||
Pair("Peliculas", "movies"),
|
||||
),
|
||||
)
|
||||
|
||||
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 latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
|
||||
|
@ -275,6 +371,10 @@ class EnNovelas : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||
|
||||
override fun latestUpdatesSelector() = popularAnimeSelector()
|
||||
|
||||
companion object {
|
||||
const val PREFIX_SEARCH = "id:"
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val qualities = arrayOf(
|
||||
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", "Okru:144p", // Okru
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'EstrenosDoramas'
|
||||
extClass = '.EstrenosDoramas'
|
||||
extVersionCode = 3
|
||||
extVersionCode = 4
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -135,7 +135,7 @@ class EstrenosDoramas : ConfigurableAnimeSource, AnimeHttpSource() {
|
|||
/*--------------------------------Video extractors------------------------------------*/
|
||||
private val okruExtractor by lazy { OkruExtractor(client) }
|
||||
private val streamWishExtractor by lazy { StreamWishExtractor(client, headers) }
|
||||
private val streamHideVidExtractor by lazy { StreamHideVidExtractor(client) }
|
||||
private val streamHideVidExtractor by lazy { StreamHideVidExtractor(client, headers) }
|
||||
private val voeExtractor by lazy { VoeExtractor(client) }
|
||||
private val yourUploadExtractor by lazy { YourUploadExtractor(client) }
|
||||
private val vidGuardExtractor by lazy { VidGuardExtractor(client) }
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'FanPelis'
|
||||
extClass = '.FanPelis'
|
||||
extVersionCode = 12
|
||||
extVersionCode = 15
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -116,7 +116,7 @@ class FanPelis : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||
}
|
||||
if (embedUrl.contains("doodstream") || embedUrl.contains("dood")) {
|
||||
val video = try {
|
||||
DoodExtractor(client).videoFromUrl(url, "DoodStream", true)
|
||||
DoodExtractor(client).videoFromUrl(url, "DoodStream")
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
|
33
src/es/flixlatam/build.gradle
Normal file
|
@ -0,0 +1,33 @@
|
|||
ext {
|
||||
extName = 'FlixLatam'
|
||||
extClass = '.FlixLatam'
|
||||
themePkg = 'dooplay'
|
||||
baseUrl = 'https://flixlatam.com'
|
||||
overrideVersionCode = 3
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib:vudeo-extractor'))
|
||||
implementation(project(':lib:uqload-extractor'))
|
||||
implementation(project(':lib:streamwish-extractor'))
|
||||
implementation(project(':lib:filemoon-extractor'))
|
||||
implementation(project(':lib:streamlare-extractor'))
|
||||
implementation(project(':lib:yourupload-extractor'))
|
||||
implementation(project(':lib:streamtape-extractor'))
|
||||
implementation(project(':lib:dood-extractor'))
|
||||
implementation(project(':lib:voe-extractor'))
|
||||
implementation(project(':lib:okru-extractor'))
|
||||
implementation(project(':lib:mp4upload-extractor'))
|
||||
implementation(project(':lib:mixdrop-extractor'))
|
||||
implementation(project(':lib:burstcloud-extractor'))
|
||||
implementation(project(':lib:fastream-extractor'))
|
||||
implementation(project(':lib:upstream-extractor'))
|
||||
implementation(project(':lib:streamhidevid-extractor'))
|
||||
implementation(project(':lib:streamsilk-extractor'))
|
||||
implementation(project(':lib:vidguard-extractor'))
|
||||
implementation(project(':lib:universal-extractor'))
|
||||
implementation(project(':lib:cryptoaes'))
|
||||
implementation(libs.jsunpacker)
|
||||
}
|
BIN
src/es/flixlatam/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 9 KiB |
BIN
src/es/flixlatam/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
src/es/flixlatam/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 14 KiB |