fix(src/es): Fix Doramasyt and MonosChinos (#209)

* fix(src/es): Change domain for DoramasYT

Closes #181

* Fix MonosChinos extension

Closes #176
This commit is contained in:
imper1aldev 2024-09-04 11:31:27 -06:00 committed by GitHub
parent 395b6bef64
commit 2ab9311223
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 721 additions and 478 deletions

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Doramasyt' extName = 'Doramasyt'
extClass = '.Doramasyt' extClass = '.Doramasyt'
extVersionCode = 13 extVersionCode = 14
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"
@ -10,4 +10,9 @@ dependencies {
implementation(project(':lib:uqload-extractor')) implementation(project(':lib:uqload-extractor'))
implementation(project(':lib:streamtape-extractor')) implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:okru-extractor')) implementation(project(':lib:okru-extractor'))
implementation(project(':lib:voe-extractor'))
implementation(project(':lib:filemoon-extractor'))
implementation(project(':lib:streamwish-extractor'))
implementation(project(':lib:dood-extractor'))
implementation(project(':lib:mixdrop-extractor'))
} }

View file

@ -2,34 +2,41 @@ package eu.kanade.tachiyomi.animeextension.es.doramasyt
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.util.Log import android.util.Base64
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.doramasyt.extractors.SolidFilesExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource 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.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import eu.kanade.tachiyomi.util.parseAs
import okhttp3.FormBody
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import kotlin.math.ceil
class Doramasyt : ConfigurableAnimeSource, ParsedAnimeHttpSource() { class Doramasyt : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "Doramasyt" override val name = "Doramasyt"
override val baseUrl = "https://doramasyt.com" override val baseUrl = "https://www.doramasyt.com"
override val lang = "es" override val lang = "es"
@ -39,241 +46,203 @@ class Doramasyt : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
override fun popularAnimeSelector(): String = "div.col-lg-2.col-md-4.col-6 div.animes" 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")
override fun popularAnimeRequest(page: Int): Request = GET("https://doramasyt.com/doramas/?p=$page") private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Filemoon"
override fun popularAnimeFromElement(element: Element): SAnime { private val SERVER_LIST = arrayOf(
val anime = SAnime.create() "Voe",
anime.setUrlWithoutDomain( "StreamWish",
element.select("div.anithumb a").attr("href"), "Okru",
"Upload",
"FileLions",
"Filemoon",
"DoodStream",
"MixDrop",
"Streamtape",
) )
anime.title = element.select("div.animedtls p").text()
anime.thumbnail_url = element.select(" div.anithumb a img").attr("src")
anime.description = element.select("div.animedtls p").text()
return anime
} }
override fun popularAnimeNextPageSelector(): String = "ul.pagination li:last-child a" override fun animeDetailsParse(response: Response): SAnime {
val document = response.asJsoup()
val animeDetails = SAnime.create().apply {
title = document.selectFirst(".flex-column h1.text-capitalize")?.text() ?: ""
description = document.selectFirst(".h-100 .mb-3 p")?.text()
genre = document.select(".lh-lg span").joinToString { it.text() }
thumbnail_url = document.selectFirst(".gap-3 img")?.getImageUrl()
status = document.select(".lh-sm .ms-2").eachText().let { items ->
when {
items.any { it.contains("Finalizado") } -> SAnime.COMPLETED
items.any { it.contains("Estreno") } -> SAnime.ONGOING
else -> SAnime.UNKNOWN
}
}
}
return animeDetails
}
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/doramas?p=$page", headers)
override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val elements = document.select(".ficha_efecto a")
val nextPage = document.select(".pagination [rel=\"next\"]").any()
val animeList = elements.map { element ->
SAnime.create().apply {
title = element.selectFirst(".title_cap")!!.text()
thumbnail_url = element.selectFirst("img")?.getImageUrl()
setUrlWithoutDomain(element.attr("abs:href"))
}
}
return AnimesPage(animeList, nextPage)
}
override fun latestUpdatesParse(response: Response) = popularAnimeParse(response)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/emision?p=$page", headers)
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = DoramasytFilters.getSearchParameters(filters)
return when {
query.isNotBlank() -> GET("$baseUrl/buscar?q=$query", headers)
params.filter.isNotBlank() -> GET("$baseUrl/doramas${params.getQuery()}&p=$page", headers)
else -> popularAnimeRequest(page)
}
}
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
return super.episodeListParse(response).reversed() val document = response.asJsoup()
val token = document.select("meta[name='csrf-token']").attr("content")
val capListLink = document.select(".caplist").attr("data-ajax")
val referer = document.location()
val detail = getEpisodeDetails(capListLink, token, referer)
val total = detail.eps.size
val perPage = detail.perpage ?: return emptyList()
val pages = (total / perPage).ceilPage()
return (1..pages).parallelCatchingFlatMapBlocking {
getEpisodePage(detail.paginateUrl ?: "", it, token, referer).caps.mapIndexed { idx, ep ->
val episodeNumber = (ep.episodio ?: (idx + 1))
SEpisode.create().apply {
name = "Capítulo $episodeNumber"
episode_number = episodeNumber.toFloat()
setUrlWithoutDomain(ep.url ?: "")
}
}
}.reversed()
} }
override fun episodeListSelector(): String = "div.mainrowdiv.pagesdiv div.jpage div.col-item" private fun getEpisodeDetails(capListLink: String, token: String, referer: String): EpisodesDto {
val formBody = FormBody.Builder().add("_token", token).build()
val request = Request.Builder()
.url(capListLink)
.post(formBody)
.header("accept", "application/json, text/javascript, */*; q=0.01")
.header("accept-language", "es-419,es;q=0.8")
.header("content-type", "application/x-www-form-urlencoded; charset=UTF-8")
.header("origin", baseUrl)
.header("referer", referer)
.header("x-requested-with", "XMLHttpRequest")
.build()
override fun episodeFromElement(element: Element): SEpisode { return client.newCall(request).execute().parseAs<EpisodesDto>()
val episode = SEpisode.create()
val epNum = getNumberFromEpsString(element.select("a div.flimss div.dtlsflim p").text())
Log.i("bruh ep", element.select("a").attr("href"))
val formatedEp = when {
epNum.isNotEmpty() -> epNum.toFloatOrNull() ?: 1F
else -> 1F
}
episode.setUrlWithoutDomain(element.select("a").attr("href"))
episode.episode_number = formatedEp
episode.name = "Episodio $formatedEp"
return episode
} }
private fun getNumberFromEpsString(epsStr: String): String { private fun getEpisodePage(paginateUrl: String, page: Int, token: String, referer: String): EpisodeInfoDto {
return epsStr.filter { it.isDigit() } val formBodyEp = FormBody.Builder()
.add("_token", token)
.add("p", "$page")
.build()
val requestEp = Request.Builder()
.url(paginateUrl)
.post(formBodyEp)
.header("accept", "application/json, text/javascript, */*; q=0.01")
.header("accept-language", "es-419,es;q=0.8")
.header("content-type", "application/x-www-form-urlencoded; charset=UTF-8")
.header("origin", baseUrl)
.header("referer", referer)
.header("x-requested-with", "XMLHttpRequest")
.build()
return client.newCall(requestEp).execute().parseAs<EpisodeInfoDto>()
} }
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup() val document = response.asJsoup()
val videoList = mutableListOf<Video>() return document.select("[data-player]")
document.select("div.playermain ul.dropcaps li#play-video a.cap").forEach { players -> .map { String(Base64.decode(it.attr("data-player"), Base64.DEFAULT)) }
val server = players.text() .parallelCatchingFlatMapBlocking { serverVideoResolver(it) }
val urlEncoded = players.attr("data-player")
val byte = android.util.Base64.decode(urlEncoded, android.util.Base64.DEFAULT)
val url = String(byte, charset("UTF-8")).substringAfter("?url=")
if (server.contains("streamtape")) {
val video = StreamTapeExtractor(client).videoFromUrl(url)
if (video != null) {
videoList.add(video)
}
}
if (server.contains("ok")) {
val videos = OkruExtractor(client).videosFromUrl(url)
videoList.addAll(videos)
}
if (server.contains("zeus")) {
val videos = SolidFilesExtractor(client).videosFromUrl(url)
videoList.addAll(videos)
}
if (server.contains("uqload") || server.contains("upload")) {
videoList.addAll(UqloadExtractor(client).videosFromUrl(url))
}
}
return videoList
} }
override fun videoListSelector() = throw UnsupportedOperationException() override fun getFilterList(): AnimeFilterList = DoramasytFilters.FILTER_LIST
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException() private val voeExtractor by lazy { VoeExtractor(client) }
private val streamwishExtractor by lazy { StreamWishExtractor(client, headers) }
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
private val mixdropExtractor by lazy { MixDropExtractor(client) }
private val doodExtractor by lazy { DoodExtractor(client) }
private val streamTapeExtractor by lazy { StreamTapeExtractor(client) }
private val uqloadExtractor by lazy { UqloadExtractor(client) }
private val okruExtractor by lazy { OkruExtractor(client) }
override fun videoFromElement(element: Element) = throw UnsupportedOperationException() private fun serverVideoResolver(url: String): List<Video> {
val embedUrl = url.lowercase()
return when {
embedUrl.contains("voe") -> voeExtractor.videosFromUrl(url)
embedUrl.contains("uqload") -> uqloadExtractor.videosFromUrl(url)
embedUrl.contains("ok.ru") || embedUrl.contains("okru") -> okruExtractor.videosFromUrl(url)
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("filelions") || embedUrl.contains("lion") -> streamwishExtractor.videosFromUrl(url, videoNameGen = { "FileLions:$it" })
embedUrl.contains("mix") -> mixdropExtractor.videosFromUrl(url)
else -> emptyList()
}
}
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
return try { val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val videoSorted = this.sortedWith( val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
compareBy<Video> { it.quality.replace("[0-9]".toRegex(), "") }.thenByDescending { getNumberFromString(it.quality) }, return this.sortedWith(
).toTypedArray() compareBy(
val userPreferredQuality = preferences.getString("preferred_quality", "Okru:720p") { it.quality.contains(server, true) },
val preferredIdx = videoSorted.indexOfFirst { x -> x.quality == userPreferredQuality } { it.quality.contains(quality) },
if (preferredIdx != -1) { { Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
videoSorted.drop(preferredIdx + 1) ),
videoSorted[0] = videoSorted[preferredIdx] ).reversed()
}
videoSorted.toList()
} catch (e: Exception) {
this
}
} }
private fun getNumberFromString(epsStr: String): String { private fun Element.getImageUrl(): String? {
return epsStr.filter { it.isDigit() }.ifEmpty { "0" }
}
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 { return when {
query.isNotBlank() -> GET("$baseUrl/buscar?q=$query&p=$page", headers) isValidUrl("data-src") -> attr("abs:data-src")
genreFilter.state != 0 -> GET("$baseUrl/doramas?categoria=false&genero=${genreFilter.toUriPart()}&fecha=false&letra=false&p=$page") isValidUrl("data-lazy-src") -> attr("abs:data-lazy-src")
else -> GET("$baseUrl/doramas/?p=$page") isValidUrl("srcset") -> attr("abs:srcset").substringBefore(" ")
isValidUrl("src") -> attr("abs:src")
else -> ""
} }
} }
override fun searchAnimeFromElement(element: Element): SAnime { private fun Element.isValidUrl(attrName: String): Boolean {
val anime = SAnime.create() if (!hasAttr(attrName)) return false
anime.setUrlWithoutDomain( return !attr(attrName).contains("anime.png")
element.selectFirst("a")!!.attr("href"),
)
anime.title = element.select("div.animedtls p").text()
anime.thumbnail_url = element.select("a img").attr("src")
anime.description = element.select("div.animedtls p").text()
return anime
} }
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector() private fun Double.ceilPage(): Int = if (this % 1 == 0.0) this.toInt() else ceil(this).toInt()
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
// anime.thumbnail_url = document.selectFirst("div.herohead div.heroheadmain")!!.attr("style").substringAfter(",url(").substringBefore(") no-repeat;")
val sub = document.selectFirst("div.herohead div.heroheadmain strong")!!.text()
val title = document.selectFirst("div.herohead div.heroheadmain h1")!!.text().trim()
anime.title = title + if (sub.isNotEmpty()) " ($sub)" else ""
anime.description = document.selectFirst("div.herohead div.heroheadmain div.flimdtls p.textComplete")!!
.text().removeSurrounding("\"").replace("Ver menos", "")
anime.genre = document.select("div.herohead div.heroheadmain div.writersdiv div.nobel h6 a").joinToString { it.text() }
anime.status = parseStatus(document.select("div.herohead div.heroheadmain div.writersdiv div.state h6").text())
return anime
}
private fun parseStatus(statusString: String): Int {
return when {
statusString.contains("Estreno") -> SAnime.ONGOING
statusString.contains("Finalizado") -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
}
}
override fun latestUpdatesNextPageSelector() = "" // popularAnimeNextPageSelector()
override fun latestUpdatesFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(
element.selectFirst("a")!!.attr("href"),
)
anime.title = element.selectFirst("a div.chapter p")!!.text()
anime.thumbnail_url = element.select("a div.chapter img").attr("src")
anime.description = element.select("div.animedtls p").text()
return anime
}
override fun latestUpdatesRequest(page: Int) = GET(baseUrl)
override fun latestUpdatesSelector() = "div.heroarea div.heromain div.chapters div.row div.chaps" // popularAnimeSelector()
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenreFilter(),
)
private class GenreFilter : UriPartFilter(
"Generos",
arrayOf(
Pair("<selecionar>", "false"),
Pair("Acción", "accion"),
Pair("Amistad", "amistad"),
Pair("Artes marciales", "artes-marciales"),
Pair("Aventuras", "aventuras"),
Pair("Bélico", "belico"),
Pair("C-Drama", "c-drama"),
Pair("Ciencia Ficción", "ciencia-ficcion"),
Pair("Comedia", "comedia"),
Pair("Comida", "comida"),
Pair("Crimen ", "crimen"),
Pair("Deporte", "deporte"),
Pair("Documental", "documental"),
Pair("Drama", "drama"),
Pair("Escolar", "escolar"),
Pair("Familiar", "familiar"),
Pair("Fantasia", "fantasia"),
Pair("Histórico", "historico"),
Pair("HK-Drama", "hk-drama"),
Pair("Horror", "horror"),
Pair("Idols", "idols"),
Pair("J-Drama", "j-drama"),
Pair("Juvenil", "juvenil"),
Pair("K-Drama", "k-drama"),
Pair("Legal", "legal"),
Pair("Médico", "medico"),
Pair("Melodrama", "melodrama"),
Pair("Militar", "militar"),
Pair("Misterio", "misterio"),
Pair("Musical", "musical"),
Pair("Negocios", "negocios"),
Pair("Policial", "policial"),
Pair("Política", "politica"),
Pair("Psicológico", "psicologico"),
Pair("Reality Show", "reality-show"),
Pair("Recuentos de la vida", "recuentos-de-la-vida"),
Pair("Romance", "romance"),
Pair("Sobrenatural", "sobrenatural"),
Pair("Supervivencia", "supervivencia"),
Pair("Suspenso", "suspenso"),
Pair("Thai-Drama", "thai-drama"),
Pair("Thriller", "thriller"),
Pair("Time Travel", "time-travel"),
Pair("TW-Drama", "tw-drama"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
),
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf( ListPreference(screen.context).apply {
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", "Okru:144p", // Okru key = PREF_SERVER_KEY
"Uqload", "SolidFiles", "StreamTape", // video servers without resolution title = "Preferred server"
) entries = SERVER_LIST
val videoQualityPref = ListPreference(screen.context).apply { entryValues = SERVER_LIST
key = "preferred_quality" setDefaultValue(PREF_SERVER_DEFAULT)
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Okru:720p")
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -282,7 +251,22 @@ class Doramasyt : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
} }.also(screen::addPreference)
screen.addPreference(videoQualityPref)
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)
} }
} }

View file

@ -0,0 +1,29 @@
package eu.kanade.tachiyomi.animeextension.es.doramasyt
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class EpisodesDto(
@SerialName("paginate_url") var paginateUrl: String? = null,
@SerialName("perpage") var perpage: Double? = null,
@SerialName("eps") var eps: ArrayList<Eps> = arrayListOf(),
)
@Serializable
data class Eps(
@SerialName("num") var num: Int? = null,
)
@Serializable
data class EpisodeInfoDto(
@SerialName("default") var default: String? = null,
@SerialName("caps") var caps: ArrayList<Caps> = arrayListOf(),
)
@Serializable
data class Caps(
@SerialName("episodio") var episodio: Int? = null,
@SerialName("url") var url: String? = null,
@SerialName("thumb") var thumb: String? = null,
)

View file

@ -0,0 +1,117 @@
package eu.kanade.tachiyomi.animeextension.es.doramasyt
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object DoramasytFilters {
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 { "" }
}
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.asQueryPart<CategoriesFilter>("categoria") +
filters.asQueryPart<GenresFilter>("genero") +
filters.asQueryPart<YearsFilter>("fecha") +
filters.asQueryPart<LettersFilter>("letra"),
)
}
val FILTER_LIST get() = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
CategoriesFilter(),
GenresFilter(),
YearsFilter(),
LettersFilter(),
)
class CategoriesFilter : QueryPartFilter("Categoría", DoramasytFiltersData.CATEGORIES)
class GenresFilter : QueryPartFilter("Género", DoramasytFiltersData.GENRES)
class YearsFilter : QueryPartFilter("Año", DoramasytFiltersData.YEARS)
class LettersFilter : QueryPartFilter("Letra", DoramasytFiltersData.LETTER)
private object DoramasytFiltersData {
val CATEGORIES = arrayOf(
Pair("<Selecionar>", ""),
Pair("Dorama", "dorama"),
Pair("Live Action", "live-action"),
Pair("Pelicula", "pelicula"),
Pair("Series Turcas", "serie-turcas"),
)
val YEARS = arrayOf(Pair("<Seleccionar>", "")) + (1982..2024).map { Pair("$it", "$it") }.reversed().toTypedArray()
val LETTER = arrayOf(Pair("<Seleccionar>", "")) + ('A'..'Z').map { Pair("$it", "$it") }.toTypedArray()
val GENRES = arrayOf(
Pair("<Selecionar>", ""),
Pair("Policial", "policial"),
Pair("Romance", "romance"),
Pair("Comedia", "comedia"),
Pair("Escolar", "escolar"),
Pair("Acción", "accion"),
Pair("Thriller", "thriller"),
Pair("Drama", "drama"),
Pair("Misterio", "misterio"),
Pair("Fantasia", "fantasia"),
Pair("Histórico", "historico"),
Pair("Bélico", "belico"),
Pair("Militar", "militar"),
Pair("Médico", "medico"),
Pair("Ciencia Ficción", "ciencia-ficcion"),
Pair("Sobrenatural", "sobrenatural"),
Pair("Horror", "horror"),
Pair("Política", "politica"),
Pair("Familiar", "familiar"),
Pair("Melodrama", "melodrama"),
Pair("Deporte", "deporte"),
Pair("Comida", "comida"),
Pair("Supervivencia", "supervivencia"),
Pair("Aventuras", "aventuras"),
Pair("Artes marciales", "artes-marciales"),
Pair("Recuentos de la vida", "recuentos-de-la-vida"),
Pair("Amistad", "amistad"),
Pair("Psicológico", "psicologico"),
Pair("Yuri", "yuri"),
Pair("K-Drama", "k-drama"),
Pair("J-Drama", "j-drama"),
Pair("C-Drama", "c-drama"),
Pair("HK-Drama", "hk-drama"),
Pair("TW-Drama", "tw-drama"),
Pair("Thai-Drama", "thai-drama"),
Pair("Idols", "idols"),
Pair("Suspenso", "suspenso"),
Pair("Negocios", "negocios"),
Pair("Time Travel", "time-travel"),
Pair("Crimen ", "crimen"),
Pair("Yaoi", "yaoi"),
Pair("Legal", "legal"),
Pair("Juvenil", "juvenil"),
Pair("Musical", "musical"),
Pair("Reality Show", "reality-show"),
Pair("Documental", "documental"),
Pair("Turcas", "turcas"),
)
}
}

View file

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

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'MonosChinos' extName = 'MonosChinos'
extClass = '.MonosChinos' extClass = '.MonosChinos'
extVersionCode = 27 extVersionCode = 28
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"
@ -12,4 +12,8 @@ dependencies {
implementation(project(':lib:okru-extractor')) implementation(project(':lib:okru-extractor'))
implementation(project(':lib:streamtape-extractor')) implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:filemoon-extractor')) implementation(project(':lib:filemoon-extractor'))
implementation(project(':lib:voe-extractor'))
implementation(project(':lib:streamwish-extractor'))
implementation(project(':lib:mixdrop-extractor'))
implementation(project(':lib:dood-extractor'))
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

View file

@ -5,275 +5,250 @@ import android.content.SharedPreferences
import android.util.Base64 import android.util.Base64
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.monoschinos.extractors.SolidFilesExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource 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.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import eu.kanade.tachiyomi.util.parseAs
import okhttp3.FormBody
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import kotlin.math.ceil
class MonosChinos : ConfigurableAnimeSource, ParsedAnimeHttpSource() { class MonosChinos : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "MonosChinos" override val name = "MonosChinos"
override val baseUrl = "https://monoschinos2.com" override val baseUrl = "https://monoschinos2.com"
override val id = 6957694006954649296
override val lang = "es" override val lang = "es"
override val supportsLatest = false override val supportsLatest = true
private val preferences: SharedPreferences by lazy { private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
override fun popularAnimeSelector(): String = "div.heromain div.row div.col-md-4" 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")
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/animes?p=$page") private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Filemoon"
private val SERVER_LIST = arrayOf(
"Voe",
"StreamWish",
"Okru",
"Upload",
"FileLions",
"Filemoon",
"DoodStream",
"MixDrop",
"Streamtape",
"Mp4Upload",
)
}
override fun popularAnimeFromElement(element: Element): SAnime { override fun animeDetailsParse(response: Response): SAnime {
val thumbDiv = element.select("a div.series div.seriesimg img") val document = response.asJsoup()
return SAnime.create().apply { val animeDetails = SAnime.create().apply {
setUrlWithoutDomain(element.select("a").attr("href")) title = document.selectFirst(".flex-column h1.text-capitalize")?.text() ?: ""
title = element.select("a div.series div.seriesdetails h3").text() description = document.selectFirst(".h-100 .mb-3 p")?.text()
thumbnail_url = if (thumbDiv.attr("src").contains("/public/img")) { genre = document.select(".lh-lg span").joinToString { it.text() }
thumbDiv.attr("data-src") thumbnail_url = document.selectFirst(".gap-3 img")?.getImageUrl()
} else { status = document.select(".lh-sm .ms-2").eachText().let { items ->
thumbDiv.attr("src") when {
items.any { it.contains("Finalizado") } -> SAnime.COMPLETED
items.any { it.contains("Estreno") } -> SAnime.ONGOING
else -> SAnime.UNKNOWN
}
} }
} }
return animeDetails
}
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/animes?p=$page", headers)
override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val elements = document.select(".ficha_efecto a")
val nextPage = document.select(".pagination [rel=\"next\"]").any()
val animeList = elements.map { element ->
SAnime.create().apply {
title = element.selectFirst(".title_cap")!!.text()
thumbnail_url = element.selectFirst("img")?.getImageUrl()
setUrlWithoutDomain(element.attr("abs:href"))
}
}
return AnimesPage(animeList, nextPage)
}
override fun latestUpdatesParse(response: Response) = popularAnimeParse(response)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/emision?p=$page", headers)
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = MonosChinosFilters.getSearchParameters(filters)
return when {
query.isNotBlank() -> GET("$baseUrl/buscar?q=$query", headers)
params.filter.isNotBlank() -> GET("$baseUrl/animes${params.getQuery()}&p=$page", headers)
else -> popularAnimeRequest(page)
}
} }
override fun popularAnimeNextPageSelector(): String = "li.page-item a.page-link" override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val jsoup = response.asJsoup() val document = response.asJsoup()
val animeId = response.request.url.pathSegments.last().replace("-sub-espanol", "").replace("-080p", "-1080p") val token = document.select("meta[name='csrf-token']").attr("content")
return jsoup.select("div.col-item").map { it -> val capListLink = document.select(".caplist").attr("data-ajax")
val epNum = it.attr("data-episode") val referer = document.location()
SEpisode.create().apply {
episode_number = epNum.toFloat() val detail = getEpisodeDetails(capListLink, token, referer)
name = "Episodio $epNum" val total = detail.eps.size
url = "/ver/$animeId-episodio-$epNum" val perPage = detail.perpage ?: return emptyList()
val pages = (total / perPage).ceilPage()
return (1..pages).parallelCatchingFlatMapBlocking {
getEpisodePage(detail.paginateUrl ?: "", it, token, referer).caps.mapIndexed { idx, ep ->
val episodeNumber = (ep.episodio ?: (idx + 1))
SEpisode.create().apply {
name = "Capítulo $episodeNumber"
episode_number = episodeNumber.toFloat()
setUrlWithoutDomain(ep.url ?: "")
}
} }
}.reversed() }.reversed()
} }
override fun episodeListSelector() = throw UnsupportedOperationException() private fun getEpisodeDetails(capListLink: String, token: String, referer: String): EpisodesDto {
val formBody = FormBody.Builder().add("_token", token).build()
val request = Request.Builder()
.url(capListLink)
.post(formBody)
.header("accept", "application/json, text/javascript, */*; q=0.01")
.header("accept-language", "es-419,es;q=0.8")
.header("content-type", "application/x-www-form-urlencoded; charset=UTF-8")
.header("origin", baseUrl)
.header("referer", referer)
.header("x-requested-with", "XMLHttpRequest")
.build()
override fun episodeFromElement(element: Element) = throw UnsupportedOperationException() return client.newCall(request).execute().parseAs<EpisodesDto>()
}
private fun getEpisodePage(paginateUrl: String, page: Int, token: String, referer: String): EpisodeInfoDto {
val formBodyEp = FormBody.Builder()
.add("_token", token)
.add("p", "$page")
.build()
val requestEp = Request.Builder()
.url(paginateUrl)
.post(formBodyEp)
.header("accept", "application/json, text/javascript, */*; q=0.01")
.header("accept-language", "es-419,es;q=0.8")
.header("content-type", "application/x-www-form-urlencoded; charset=UTF-8")
.header("origin", baseUrl)
.header("referer", referer)
.header("x-requested-with", "XMLHttpRequest")
.build()
return client.newCall(requestEp).execute().parseAs<EpisodeInfoDto>()
}
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup() val document = response.asJsoup()
val videoList = mutableListOf<Video>() return document.select("[data-player]")
document.select("div.heroarea div.row div.col-md-12 ul.dropcaps li").forEach { it -> .map { String(Base64.decode(it.attr("data-player"), Base64.DEFAULT)) }
// val server = it.select("a").text() .parallelCatchingFlatMapBlocking { serverVideoResolver(it) }
val urlBase64 = it.select("a").attr("data-player")
val url = Base64.decode(urlBase64, Base64.DEFAULT).toString(Charsets.UTF_8).substringAfter("=")
when {
url.contains("ok") -> if (!url.contains("streamcherry")) videoList.addAll(OkruExtractor(client).videosFromUrl(url))
url.contains("solidfiles") -> videoList.addAll(SolidFilesExtractor(client).videosFromUrl(url))
url.contains("uqload") -> {
videoList.addAll(UqloadExtractor(client).videosFromUrl(url))
}
url.contains("mp4upload") -> {
val videos = Mp4uploadExtractor(client).videosFromUrl(url, headers)
videoList.addAll(videos)
}
url.contains("streamtape") -> {
val videos = StreamTapeExtractor(client).videosFromUrl(url)
videoList.addAll(videos)
}
url.contains("filemoon") -> {
val videos = FilemoonExtractor(client).videosFromUrl(url)
videoList.addAll(videos)
}
}
}
return videoList.filter { video -> video.url.contains("http") }
} }
override fun videoListSelector() = throw UnsupportedOperationException() override fun getFilterList(): AnimeFilterList = MonosChinosFilters.FILTER_LIST
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException() private val voeExtractor by lazy { VoeExtractor(client) }
private val streamwishExtractor by lazy { StreamWishExtractor(client, headers) }
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
private val mixdropExtractor by lazy { MixDropExtractor(client) }
private val doodExtractor by lazy { DoodExtractor(client) }
private val streamTapeExtractor by lazy { StreamTapeExtractor(client) }
private val uqloadExtractor by lazy { UqloadExtractor(client) }
private val okruExtractor by lazy { OkruExtractor(client) }
private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) }
override fun videoFromElement(element: Element) = throw UnsupportedOperationException() private fun serverVideoResolver(url: String): List<Video> {
val embedUrl = url.lowercase()
return when {
embedUrl.contains("voe") -> voeExtractor.videosFromUrl(url)
embedUrl.contains("uqload") -> uqloadExtractor.videosFromUrl(url)
embedUrl.contains("ok.ru") || embedUrl.contains("okru") -> okruExtractor.videosFromUrl(url)
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("filelions") || embedUrl.contains("lion") -> streamwishExtractor.videosFromUrl(url, videoNameGen = { "FileLions:$it" })
embedUrl.contains("mp4upload") || embedUrl.contains("mp4") -> mp4uploadExtractor.videosFromUrl(url, headers)
embedUrl.contains("mix") -> mixdropExtractor.videosFromUrl(url)
else -> emptyList()
}
}
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
return try { val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val videoSorted = this.sortedWith( val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
compareBy<Video> { it.quality.replace("[0-9]".toRegex(), "") }.thenByDescending { getNumberFromString(it.quality) }, return this.sortedWith(
).toTypedArray() compareBy(
val userPreferredQuality = preferences.getString("preferred_quality", "Okru:720p") { it.quality.contains(server, true) },
val preferredIdx = videoSorted.indexOfFirst { x -> x.quality == userPreferredQuality } { it.quality.contains(quality) },
if (preferredIdx != -1) { { Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
videoSorted.drop(preferredIdx + 1) ),
videoSorted[0] = videoSorted[preferredIdx] ).reversed()
}
videoSorted.toList()
} catch (e: Exception) {
this
}
} }
private fun getNumberFromString(epsStr: String): String { private fun Element.getImageUrl(): String? {
return epsStr.filter { it.isDigit() }.ifEmpty { "0" }
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val genreFilter = filters.filterIsInstance<GenreFilter>().firstOrNull() ?: GenreFilter()
val yearFilter = try {
(filters.find { it is YearFilter } as YearFilter).state.toInt()
} catch (e: Exception) {
"false"
}
val letterFilter = try {
(filters.find { it is LetterFilter } as LetterFilter).state.first().uppercase()
} catch (e: Exception) {
"false"
}
return when { return when {
query.isNotBlank() -> GET("$baseUrl/buscar?q=$query&p=$page") isValidUrl("data-src") -> attr("abs:data-src")
else -> GET("$baseUrl/animes?categoria=false&genero=${genreFilter.toUriPart()}&fecha=$yearFilter&letra=$letterFilter&p=$page") isValidUrl("data-lazy-src") -> attr("abs:data-lazy-src")
isValidUrl("srcset") -> attr("abs:srcset").substringBefore(" ")
isValidUrl("src") -> attr("abs:src")
else -> ""
} }
} }
override fun searchAnimeFromElement(element: Element): SAnime { private fun Element.isValidUrl(attrName: String): Boolean {
return popularAnimeFromElement(element) if (!hasAttr(attrName)) return false
return !attr(attrName).contains("anime.png")
} }
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector() private fun Double.ceilPage(): Int = if (this % 1 == 0.0) this.toInt() else ceil(this).toInt()
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun animeDetailsParse(document: Document): SAnime {
return SAnime.create().apply {
thumbnail_url = document.selectFirst("div.chapterpic img")!!.attr("src")
title = document.selectFirst("div.chapterdetails h1")!!.text()
description = document.selectFirst("p.textShort")!!.ownText()
genre = document.select("ol.breadcrumb li.breadcrumb-item a").joinToString { it.text() }
status = parseStatus(document.select("div.butns button.btn1").text())
}
}
private fun parseStatus(statusString: String): Int {
return when {
statusString.contains("Estreno") -> SAnime.ONGOING
statusString.contains("Finalizado") -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
}
}
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException()
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
override fun latestUpdatesSelector() = throw UnsupportedOperationException()
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenreFilter(),
AnimeFilter.Separator(),
YearFilter(),
LetterFilter(),
)
private class YearFilter : AnimeFilter.Text("Año", "2022")
private class LetterFilter : AnimeFilter.Text("Letra", "")
private class GenreFilter : UriPartFilter(
"Generos",
arrayOf(
Pair("<selecionar>", ""),
Pair("Latino", "latino"),
Pair("Castellano", "castellano"),
Pair("Acción", "acción"),
Pair("Aventura", "aventura"),
Pair("Carreras", "carreras"),
Pair("Comedia", "comedia"),
Pair("Cyberpunk", "cyberpunk"),
Pair("Deportes", "deportes"),
Pair("Drama", "drama"),
Pair("Ecchi", "ecchi"),
Pair("Escolares", "escolares"),
Pair("Fantasía", "fantasía"),
Pair("Gore", "gore"),
Pair("Harem", "harem"),
Pair("Horror", "horror"),
Pair("Josei", "josei"),
Pair("Lucha", "lucha"),
Pair("Magia", "magia"),
Pair("Josei", "josei"),
Pair("Mecha", "mecha"),
Pair("Militar", "militar"),
Pair("Misterio", "misterio"),
Pair("Música", "música"),
Pair("Parodias", "parodias"),
Pair("Psicológico", "psicológico"),
Pair("Recuerdos de la vida", "recuerdos-de-la-vida"),
Pair("Seinen", "seinen"),
Pair("Shojo", "shojo"),
Pair("Shonen", "shonen"),
Pair("Sobrenatural", "sobrenatural"),
Pair("Vampiros", "vampiros"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
Pair("Espacial", "espacial"),
Pair("Histórico", "histórico"),
Pair("Samurai", "samurai"),
Pair("Artes Marciales", "artes-marciales"),
Pair("Demonios", "demonios"),
Pair("Romance", "romance"),
Pair("Policía", " policía"),
Pair("Historia paralela", "historia-paralela"),
Pair("Aenime", "aenime"),
Pair("Donghua", "donghua"),
Pair("Blu-ray", "blu-ray"),
Pair("Monogatari", "monogatari"),
),
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf( ListPreference(screen.context).apply {
"Okru:1080p", key = PREF_SERVER_KEY
"Okru:720p", title = "Preferred server"
"Okru:480p", entries = SERVER_LIST
"Okru:360p", entryValues = SERVER_LIST
"Okru:240p", // Okru setDefaultValue(PREF_SERVER_DEFAULT)
"SolidFiles",
"Upload", // video servers without resolution
"StreamTape",
"FileMoon",
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Okru:720p")
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -282,7 +257,22 @@ class MonosChinos : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
} }.also(screen::addPreference)
screen.addPreference(videoQualityPref)
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)
} }
} }

View file

@ -0,0 +1,29 @@
package eu.kanade.tachiyomi.animeextension.es.monoschinos
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class EpisodesDto(
@SerialName("paginate_url") var paginateUrl: String? = null,
@SerialName("perpage") var perpage: Double? = null,
@SerialName("eps") var eps: ArrayList<Eps> = arrayListOf(),
)
@Serializable
data class Eps(
@SerialName("num") var num: Int? = null,
)
@Serializable
data class EpisodeInfoDto(
@SerialName("default") var default: String? = null,
@SerialName("caps") var caps: ArrayList<Caps> = arrayListOf(),
)
@Serializable
data class Caps(
@SerialName("episodio") var episodio: Int? = null,
@SerialName("url") var url: String? = null,
@SerialName("thumb") var thumb: String? = null,
)

View file

@ -0,0 +1,139 @@
package eu.kanade.tachiyomi.animeextension.es.monoschinos
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import java.util.Calendar
object MonosChinosFilters {
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(",").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>(DoramasytFiltersData.GENRES, "genero") +
filters.parseCheckbox<TypesFilter>(DoramasytFiltersData.TYPES, "tipo") +
filters.asQueryPart<YearsFilter>("fecha") +
filters.asQueryPart<LettersFilter>("letra"),
)
}
val FILTER_LIST get() = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenresFilter(),
TypesFilter(),
YearsFilter(),
LettersFilter(),
)
class GenresFilter : CheckBoxFilterList("Género", DoramasytFiltersData.GENRES.map { CheckBoxVal(it.first, false) })
class TypesFilter : CheckBoxFilterList("Tipo", DoramasytFiltersData.TYPES.map { CheckBoxVal(it.first, false) })
class YearsFilter : QueryPartFilter("Año", DoramasytFiltersData.YEARS)
class LettersFilter : QueryPartFilter("Letra", DoramasytFiltersData.LETTER)
private object DoramasytFiltersData {
val TYPES = arrayOf(
Pair("<Selecionar>", ""),
Pair("Pelicula", "pelicula"),
Pair("Anime", "anime"),
)
val YEARS = arrayOf(Pair("<Seleccionar>", "")) + (1982..Calendar.getInstance().get(Calendar.YEAR)).map { Pair("$it", "$it") }.reversed().toTypedArray()
val LETTER = arrayOf(Pair("<Seleccionar>", "")) + ('A'..'Z').map { Pair("$it", "$it") }.toTypedArray()
val GENRES = arrayOf(
Pair("<Selecionar>", ""),
Pair("Acción", "accion"),
Pair("Aventura", "aventura"),
Pair("Carreras", "carreras"),
Pair("Ciencia Ficción", "ciencia-ficcion"),
Pair("Comedia", "comedia"),
Pair("Cyberpunk", "cyberpunk"),
Pair("Deportes", "deportes"),
Pair("Drama", "drama"),
Pair("Ecchi", "ecchi"),
Pair("Escolares", "escolares"),
Pair("Fantasía", "fantasia"),
Pair("Gore", "gore"),
Pair("Harem", "harem"),
Pair("Horror", "horror"),
Pair("Josei", "josei"),
Pair("Lucha", "lucha"),
Pair("Magia", "magia"),
Pair("Mecha", "mecha"),
Pair("Militar", "militar"),
Pair("Misterio", "misterio"),
Pair("Música", "musica"),
Pair("Parodias", "parodias"),
Pair("Psicológico", "psicologico"),
Pair("Recuerdos de la vida", "recuerdos-de-la-vida"),
Pair("Seinen", "seinen"),
Pair("Shojo", "shojo"),
Pair("Shonen", "shonen"),
Pair("Sobrenatural", "sobrenatural"),
Pair("Vampiros", "vampiros"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
Pair("Latino", "latino"),
Pair("Espacial", "espacial"),
Pair("Histórico", "historico"),
Pair("Samurai", "samurai"),
Pair("Artes Marciales", "artes-marciales"),
Pair("Demonios", "demonios"),
Pair("Romance", "romance"),
Pair("Dementia", "dementia"),
Pair(" Policía", "policia"),
Pair("Castellano", "castellano"),
Pair("Historia paralela", "historia-paralela"),
Pair("Aenime", "aenime"),
Pair("Blu-ray", "blu-ray"),
Pair("Monogatari", "monogatari"),
)
}
}

View file

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