diff --git a/src/es/monoschinos/build.gradle b/src/es/monoschinos/build.gradle index 4cd9d9bf..a43225a7 100644 --- a/src/es/monoschinos/build.gradle +++ b/src/es/monoschinos/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'MonosChinos' extClass = '.MonosChinos' - extVersionCode = 27 + extVersionCode = 28 } apply from: "$rootDir/common.gradle" @@ -12,4 +12,8 @@ dependencies { implementation(project(':lib:okru-extractor')) implementation(project(':lib:streamtape-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')) } \ No newline at end of file diff --git a/src/es/monoschinos/res/mipmap-hdpi/ic_launcher.png b/src/es/monoschinos/res/mipmap-hdpi/ic_launcher.png index 4252a48a..397648b7 100644 Binary files a/src/es/monoschinos/res/mipmap-hdpi/ic_launcher.png and b/src/es/monoschinos/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/es/monoschinos/res/mipmap-mdpi/ic_launcher.png b/src/es/monoschinos/res/mipmap-mdpi/ic_launcher.png index 4252a48a..d11cb6bc 100644 Binary files a/src/es/monoschinos/res/mipmap-mdpi/ic_launcher.png and b/src/es/monoschinos/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/es/monoschinos/res/mipmap-xhdpi/ic_launcher.png b/src/es/monoschinos/res/mipmap-xhdpi/ic_launcher.png index 4252a48a..1f22941b 100644 Binary files a/src/es/monoschinos/res/mipmap-xhdpi/ic_launcher.png and b/src/es/monoschinos/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/es/monoschinos/res/mipmap-xxhdpi/ic_launcher.png b/src/es/monoschinos/res/mipmap-xxhdpi/ic_launcher.png index 4252a48a..efd76e33 100644 Binary files a/src/es/monoschinos/res/mipmap-xxhdpi/ic_launcher.png and b/src/es/monoschinos/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/es/monoschinos/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/monoschinos/res/mipmap-xxxhdpi/ic_launcher.png index 4252a48a..49650bfb 100644 Binary files a/src/es/monoschinos/res/mipmap-xxxhdpi/ic_launcher.png and b/src/es/monoschinos/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/es/monoschinos/src/eu/kanade/tachiyomi/animeextension/es/monoschinos/MonosChinos.kt b/src/es/monoschinos/src/eu/kanade/tachiyomi/animeextension/es/monoschinos/MonosChinos.kt index d93f7744..9adcbbcf 100644 --- a/src/es/monoschinos/src/eu/kanade/tachiyomi/animeextension/es/monoschinos/MonosChinos.kt +++ b/src/es/monoschinos/src/eu/kanade/tachiyomi/animeextension/es/monoschinos/MonosChinos.kt @@ -5,275 +5,250 @@ import android.content.SharedPreferences import android.util.Base64 import androidx.preference.ListPreference import androidx.preference.PreferenceScreen -import eu.kanade.tachiyomi.animeextension.es.monoschinos.extractors.SolidFilesExtractor import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource -import eu.kanade.tachiyomi.animesource.model.AnimeFilter import eu.kanade.tachiyomi.animesource.model.AnimeFilterList +import eu.kanade.tachiyomi.animesource.model.AnimesPage import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.Video -import eu.kanade.tachiyomi.animesource.online.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.mp4uploadextractor.Mp4uploadExtractor 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.uqloadextractor.UqloadExtractor +import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor import eu.kanade.tachiyomi.network.GET 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.Response -import org.jsoup.nodes.Document import org.jsoup.nodes.Element import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import kotlin.math.ceil -class MonosChinos : ConfigurableAnimeSource, ParsedAnimeHttpSource() { +class MonosChinos : ConfigurableAnimeSource, AnimeHttpSource() { override val name = "MonosChinos" override val baseUrl = "https://monoschinos2.com" + override val id = 6957694006954649296 + override val lang = "es" - override val supportsLatest = false + override val supportsLatest = true private val preferences: SharedPreferences by lazy { Injekt.get().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 { - val thumbDiv = element.select("a div.series div.seriesimg img") - return SAnime.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - title = element.select("a div.series div.seriesdetails h3").text() - thumbnail_url = if (thumbDiv.attr("src").contains("/public/img")) { - thumbDiv.attr("data-src") - } else { - thumbDiv.attr("src") + 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/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 { - val jsoup = response.asJsoup() - val animeId = response.request.url.pathSegments.last().replace("-sub-espanol", "").replace("-080p", "-1080p") - return jsoup.select("div.col-item").map { it -> - val epNum = it.attr("data-episode") - SEpisode.create().apply { - episode_number = epNum.toFloat() - name = "Episodio $epNum" - url = "/ver/$animeId-episodio-$epNum" + 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() = 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() + } + + 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() + } override fun videoListParse(response: Response): List