forked from AlmightyHak/extensions-source
Initial commit
This commit is contained in:
commit
98ed7e8839
2263 changed files with 108711 additions and 0 deletions
17
src/es/animeonlineninja/build.gradle
Normal file
17
src/es/animeonlineninja/build.gradle
Normal file
|
@ -0,0 +1,17 @@
|
|||
ext {
|
||||
extName = 'AnimeOnline.Ninja'
|
||||
extClass = '.AnimeOnlineNinja'
|
||||
themePkg = 'dooplay'
|
||||
baseUrl = 'https://ww3.animeonline.ninja'
|
||||
overrideVersionCode = 38
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib:streamtape-extractor'))
|
||||
implementation(project(':lib:dood-extractor'))
|
||||
implementation(project(':lib:filemoon-extractor'))
|
||||
implementation(project(':lib:mixdrop-extractor'))
|
||||
implementation(project(':lib:uqload-extractor'))
|
||||
}
|
BIN
src/es/animeonlineninja/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/es/animeonlineninja/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
BIN
src/es/animeonlineninja/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/es/animeonlineninja/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
BIN
src/es/animeonlineninja/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/es/animeonlineninja/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
BIN
src/es/animeonlineninja/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/es/animeonlineninja/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
BIN
src/es/animeonlineninja/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/es/animeonlineninja/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.7 KiB |
|
@ -0,0 +1,266 @@
|
|||
package eu.kanade.tachiyomi.animeextension.es.animeonlineninja
|
||||
|
||||
import androidx.preference.CheckBoxPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
|
||||
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
|
||||
import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor
|
||||
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
||||
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
|
||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class AnimeOnlineNinja : DooPlay(
|
||||
"es",
|
||||
"AnimeOnline.Ninja",
|
||||
"https://ww3.animeonline.ninja",
|
||||
) {
|
||||
override val client by lazy {
|
||||
if (preferences.getBoolean(PREF_VRF_INTERCEPT_KEY, PREF_VRF_INTERCEPT_DEFAULT)) {
|
||||
network.client.newBuilder()
|
||||
.addInterceptor(VrfInterceptor())
|
||||
.build()
|
||||
} else {
|
||||
network.client
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Popular ===============================
|
||||
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/tendencias/$page")
|
||||
|
||||
override fun popularAnimeSelector() = latestUpdatesSelector()
|
||||
|
||||
override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector()
|
||||
|
||||
// =============================== Search ===============================
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
val params = AnimeOnlineNinjaFilters.getSearchParameters(filters)
|
||||
val path = when {
|
||||
params.genre.isNotBlank() -> {
|
||||
if (params.genre in listOf("tendencias", "ratings")) {
|
||||
"/" + params.genre
|
||||
} else {
|
||||
"/genero/${params.genre}"
|
||||
}
|
||||
}
|
||||
params.language.isNotBlank() -> "/genero/${params.language}"
|
||||
params.year.isNotBlank() -> "/release/${params.year}"
|
||||
params.movie.isNotBlank() -> {
|
||||
if (params.movie == "pelicula") {
|
||||
"/pelicula"
|
||||
} else {
|
||||
"/genero/${params.movie}"
|
||||
}
|
||||
}
|
||||
else -> buildString {
|
||||
append(
|
||||
when {
|
||||
query.isNotBlank() -> "/?s=$query"
|
||||
params.letter.isNotBlank() -> "/letra/${params.letter}/?"
|
||||
else -> "/tendencias/?"
|
||||
},
|
||||
)
|
||||
|
||||
append(
|
||||
if (contains("tendencias")) {
|
||||
"&get=${when (params.type){
|
||||
"serie" -> "TV"
|
||||
"pelicula" -> "movies"
|
||||
else -> "todos"
|
||||
}}"
|
||||
} else {
|
||||
"&tipo=${params.type}"
|
||||
},
|
||||
)
|
||||
|
||||
if (params.isInverted) append("&orden=asc")
|
||||
}
|
||||
}
|
||||
|
||||
return if (path.startsWith("/?s=")) {
|
||||
GET("$baseUrl/page/$page$path")
|
||||
} else if (path.startsWith("/letra") || path.startsWith("/tendencias")) {
|
||||
val before = path.substringBeforeLast("/")
|
||||
val after = path.substringAfterLast("/")
|
||||
GET(baseUrl + before + "/page/$page/" + after)
|
||||
} else {
|
||||
GET("$baseUrl$path/page/$page")
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
override val episodeMovieText = "Película"
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val players = document.select("ul#playeroptionsul li")
|
||||
return players.flatMap { player ->
|
||||
val name = player.selectFirst("span.title")!!.text()
|
||||
val url = getPlayerUrl(player)
|
||||
extractVideos(url, name)
|
||||
}
|
||||
}
|
||||
|
||||
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
|
||||
private val doodExtractor by lazy { DoodExtractor(client) }
|
||||
private val streamTapeExtractor by lazy { StreamTapeExtractor(client) }
|
||||
private val mixDropExtractor by lazy { MixDropExtractor(client) }
|
||||
private val uqloadExtractor by lazy { UqloadExtractor(client) }
|
||||
|
||||
private fun extractVideos(url: String, lang: String): List<Video> {
|
||||
return when {
|
||||
"saidochesto.top" in url || "MULTISERVER" in lang.uppercase() ->
|
||||
extractFromMulti(url)
|
||||
"filemoon" in url ->
|
||||
filemoonExtractor.videosFromUrl(url, "$lang Filemoon - ", headers)
|
||||
"dood" in url ->
|
||||
doodExtractor.videoFromUrl(url, "$lang DoodStream", false)
|
||||
?.let(::listOf)
|
||||
"streamtape" in url ->
|
||||
streamTapeExtractor.videoFromUrl(url, "$lang StreamTape")
|
||||
?.let(::listOf)
|
||||
"mixdrop" in url ->
|
||||
mixDropExtractor.videoFromUrl(url, lang)
|
||||
"uqload" in url ->
|
||||
uqloadExtractor.videosFromUrl(url)
|
||||
"wolfstream" in url -> {
|
||||
client.newCall(GET(url, headers)).execute()
|
||||
.asJsoup()
|
||||
.selectFirst("script:containsData(sources)")
|
||||
?.data()
|
||||
?.let { jsData ->
|
||||
val videoUrl = jsData.substringAfter("{file:\"").substringBefore("\"")
|
||||
listOf(Video(videoUrl, "$lang WolfStream", videoUrl, headers = headers))
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
} ?: emptyList<Video>()
|
||||
}
|
||||
|
||||
private fun extractFromMulti(url: String): List<Video> {
|
||||
val document = client.newCall(GET(url)).execute().asJsoup()
|
||||
val prefLang = preferences.getString(PREF_LANG_KEY, PREF_LANG_DEFAULT)!!
|
||||
val langSelector = when {
|
||||
prefLang.isBlank() -> "div"
|
||||
else -> "div.OD_$prefLang"
|
||||
}
|
||||
return document.select("div.ODDIV $langSelector > li").flatMap {
|
||||
val hosterUrl = it.attr("onclick").toString()
|
||||
.substringAfter("('")
|
||||
.substringBefore("')")
|
||||
val lang = when (langSelector) {
|
||||
"div" -> {
|
||||
it.parent()?.attr("class").toString()
|
||||
.substringAfter("OD_", "")
|
||||
.substringBefore(" ")
|
||||
}
|
||||
else -> prefLang
|
||||
}
|
||||
extractVideos(hosterUrl, lang)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPlayerUrl(player: Element): String {
|
||||
val type = player.attr("data-type")
|
||||
val id = player.attr("data-post")
|
||||
val num = player.attr("data-nume")
|
||||
return client.newCall(GET("$baseUrl/wp-json/dooplayer/v1/post/$id?type=$type&source=$num"))
|
||||
.execute()
|
||||
.let { response ->
|
||||
response.body.string()
|
||||
.substringAfter("\"embed_url\":\"")
|
||||
.substringBefore("\",")
|
||||
.replace("\\", "")
|
||||
}
|
||||
}
|
||||
|
||||
// =========================== Anime Details ============================
|
||||
override fun Document.getDescription(): String {
|
||||
return select("$additionalInfoSelector div.wp-content p")
|
||||
.eachText()
|
||||
.joinToString("\n")
|
||||
}
|
||||
|
||||
override val additionalInfoItems = listOf("Título", "Temporadas", "Episodios", "Duración media")
|
||||
|
||||
// =============================== Latest ===============================
|
||||
override val latestUpdatesPath = "episodio"
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = "div.pagination > *:last-child:not(span):not(.current)"
|
||||
|
||||
// ============================== Filters ===============================
|
||||
override val fetchGenres = false
|
||||
|
||||
override fun getFilterList() = AnimeOnlineNinjaFilters.FILTER_LIST
|
||||
|
||||
// ============================== Settings ==============================
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
super.setupPreferenceScreen(screen) // Quality preference
|
||||
|
||||
val langPref = ListPreference(screen.context).apply {
|
||||
key = PREF_LANG_KEY
|
||||
title = PREF_LANG_TITLE
|
||||
entries = PREF_LANG_ENTRIES
|
||||
entryValues = PREF_LANG_VALUES
|
||||
setDefaultValue(PREF_LANG_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}
|
||||
|
||||
val vrfIterceptPref = CheckBoxPreference(screen.context).apply {
|
||||
key = PREF_VRF_INTERCEPT_KEY
|
||||
title = PREF_VRF_INTERCEPT_TITLE
|
||||
summary = PREF_VRF_INTERCEPT_SUMMARY
|
||||
setDefaultValue(PREF_VRF_INTERCEPT_DEFAULT)
|
||||
}
|
||||
|
||||
screen.addPreference(vrfIterceptPref)
|
||||
screen.addPreference(langPref)
|
||||
}
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
override fun String.toDate() = 0L
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val quality = preferences.getString(prefQualityKey, prefQualityDefault)!!
|
||||
val lang = preferences.getString(PREF_LANG_KEY, PREF_LANG_DEFAULT)!!
|
||||
return sortedWith(
|
||||
compareBy(
|
||||
{ it.quality.contains(lang) },
|
||||
{ it.quality.contains(quality) },
|
||||
),
|
||||
).reversed()
|
||||
}
|
||||
|
||||
override val prefQualityValues = arrayOf("480p", "720p", "1080p")
|
||||
override val prefQualityEntries = prefQualityValues
|
||||
|
||||
companion object {
|
||||
private const val PREF_LANG_KEY = "preferred_lang"
|
||||
private const val PREF_LANG_TITLE = "Preferred language"
|
||||
private const val PREF_LANG_DEFAULT = "SUB"
|
||||
private val PREF_LANG_ENTRIES = arrayOf("SUB", "All", "ES", "LAT")
|
||||
private val PREF_LANG_VALUES = arrayOf("SUB", "", "ES", "LAT")
|
||||
|
||||
private const val PREF_VRF_INTERCEPT_KEY = "vrf_intercept"
|
||||
private const val PREF_VRF_INTERCEPT_TITLE = "Intercept VRF links (Requiere Reiniciar)"
|
||||
private const val PREF_VRF_INTERCEPT_SUMMARY = "Intercept VRF links and open them in the browser"
|
||||
private const val PREF_VRF_INTERCEPT_DEFAULT = false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package eu.kanade.tachiyomi.animeextension.es.animeonlineninja
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
|
||||
object AnimeOnlineNinjaFilters {
|
||||
|
||||
open class UriPartFilter(
|
||||
displayName: String,
|
||||
val vals: Array<Pair<String, String>>,
|
||||
) : AnimeFilter.Select<String>(
|
||||
displayName,
|
||||
vals.map { it.first }.toTypedArray(),
|
||||
) {
|
||||
|
||||
fun toUriPart() = vals[state].second
|
||||
}
|
||||
|
||||
private inline fun <reified R> AnimeFilterList.getFirst(): R {
|
||||
return first { it is R } as R
|
||||
}
|
||||
|
||||
private inline fun <reified R> AnimeFilterList.asUriPart(): String {
|
||||
return getFirst<R>().let {
|
||||
(it as UriPartFilter).toUriPart()
|
||||
}
|
||||
}
|
||||
|
||||
class InvertedResultsFilter : AnimeFilter.CheckBox("Invertir resultados", false)
|
||||
class TypeFilter : UriPartFilter("Tipo", AnimesOnlineNinjaData.TYPES)
|
||||
class LetterFilter : UriPartFilter("Filtrar por letra", AnimesOnlineNinjaData.LETTERS)
|
||||
|
||||
class GenreFilter : UriPartFilter("Generos", AnimesOnlineNinjaData.GENRES)
|
||||
class LanguageFilter : UriPartFilter("Idiomas", AnimesOnlineNinjaData.LANGUAGES)
|
||||
class YearFilter : UriPartFilter("Año", AnimesOnlineNinjaData.YEARS)
|
||||
class MovieFilter : UriPartFilter("Peliculas", AnimesOnlineNinjaData.MOVIES)
|
||||
|
||||
class OtherOptionsGroup : AnimeFilter.Group<UriPartFilter>(
|
||||
"Otros filtros",
|
||||
listOf(
|
||||
GenreFilter(),
|
||||
LanguageFilter(),
|
||||
YearFilter(),
|
||||
MovieFilter(),
|
||||
),
|
||||
)
|
||||
|
||||
private inline fun <reified R> AnimeFilter.Group<UriPartFilter>.getItemUri(): String {
|
||||
return state.first { it is R }.toUriPart()
|
||||
}
|
||||
|
||||
val FILTER_LIST get() = AnimeFilterList(
|
||||
InvertedResultsFilter(),
|
||||
TypeFilter(),
|
||||
LetterFilter(),
|
||||
AnimeFilter.Separator(),
|
||||
AnimeFilter.Header("Estos filtros no afectan a la busqueda por texto"),
|
||||
OtherOptionsGroup(),
|
||||
)
|
||||
|
||||
data class FilterSearchParams(
|
||||
val isInverted: Boolean = false,
|
||||
val type: String = "",
|
||||
val letter: String = "",
|
||||
val genre: String = "",
|
||||
val language: String = "",
|
||||
val year: String = "",
|
||||
val movie: String = "",
|
||||
)
|
||||
|
||||
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
|
||||
if (filters.isEmpty()) return FilterSearchParams()
|
||||
|
||||
val others = filters.getFirst<OtherOptionsGroup>()
|
||||
|
||||
return FilterSearchParams(
|
||||
filters.getFirst<InvertedResultsFilter>().state,
|
||||
filters.asUriPart<TypeFilter>(),
|
||||
filters.asUriPart<LetterFilter>(),
|
||||
others.getItemUri<GenreFilter>(),
|
||||
others.getItemUri<LanguageFilter>(),
|
||||
others.getItemUri<YearFilter>(),
|
||||
others.getItemUri<MovieFilter>(),
|
||||
)
|
||||
}
|
||||
|
||||
private object AnimesOnlineNinjaData {
|
||||
val EVERY = Pair("Seleccionar", "")
|
||||
|
||||
val TYPES = arrayOf(
|
||||
Pair("Todos", "todos"),
|
||||
Pair("Series", "serie"),
|
||||
Pair("Peliculas", "pelicula"),
|
||||
)
|
||||
|
||||
val LETTERS = arrayOf(EVERY) + ('a'..'z').map {
|
||||
Pair(it.toString(), it.toString())
|
||||
}.toTypedArray()
|
||||
|
||||
val GENRES = arrayOf(
|
||||
EVERY,
|
||||
Pair("Sin Censura \uD83D\uDD1E", "sin-censura"),
|
||||
Pair("En emisión ⏩", "en-emision"),
|
||||
Pair("Blu-Ray / DVD \uD83D\uDCC0", "blu-ray-dvd"),
|
||||
Pair("Próximamente", "proximamente"),
|
||||
Pair("Live Action \uD83C\uDDEF\uD83C\uDDF5", "live-action"),
|
||||
Pair("Popular en la web \uD83D\uDCAB", "tendencias"),
|
||||
Pair("Mejores valorados ⭐", "ratings"),
|
||||
)
|
||||
|
||||
val LANGUAGES = arrayOf(
|
||||
EVERY,
|
||||
Pair("Audio Latino \uD83C\uDDF2\uD83C\uDDFD", "audio-latino"),
|
||||
Pair("Audio Castellano \uD83C\uDDEA\uD83C\uDDF8", "anime-castellano"),
|
||||
)
|
||||
|
||||
val YEARS = arrayOf(EVERY) + (2024 downTo 1979).map {
|
||||
Pair(it.toString(), it.toString())
|
||||
}.toTypedArray()
|
||||
|
||||
val MOVIES = arrayOf(
|
||||
EVERY,
|
||||
Pair("Anime ㊗️", "pelicula"),
|
||||
Pair("Live Action \uD83C\uDDEF\uD83C\uDDF5", "live-action"),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package eu.kanade.tachiyomi.animeextension.es.animeonlineninja
|
||||
|
||||
import app.cash.quickjs.QuickJs
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
class VrfInterceptor : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
val response = chain.proceed(request)
|
||||
val respBody = response.body.string()
|
||||
if (response.headers["Content-Type"]?.contains("image") == true) {
|
||||
return chain.proceed(request)
|
||||
}
|
||||
val body = if (respBody.contains("One moment, please")) {
|
||||
val parsed = Jsoup.parse(respBody)
|
||||
val js = parsed.selectFirst("script:containsData(west=)")!!.data()
|
||||
val west = js.substringAfter("west=").substringBefore(",")
|
||||
val east = js.substringAfter("east=").substringBefore(",")
|
||||
val form = parsed.selectFirst("form#wsidchk-form")!!.attr("action")
|
||||
val eval = evalJs(west, east)
|
||||
val getLink = "https://" + request.url.host + form + "?wsidchk=$eval"
|
||||
chain.proceed(GET(getLink)).body
|
||||
} else {
|
||||
respBody.toResponseBody(response.body.contentType())
|
||||
}
|
||||
return response.newBuilder().body(body).build()
|
||||
}
|
||||
|
||||
private fun evalJs(west: String, east: String): String {
|
||||
return QuickJs.create().use { qjs ->
|
||||
val jscript = """$west + $east;"""
|
||||
qjs.evaluate(jscript).toString()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue