diff --git a/src/es/cinecalidad/build.gradle b/src/es/cinecalidad/build.gradle new file mode 100644 index 0000000000..739362dbae --- /dev/null +++ b/src/es/cinecalidad/build.gradle @@ -0,0 +1,18 @@ +ext { + extName = 'Cinecalidad' + extClass = '.Cinecalidad' + extVersionCode = 1 +} + +apply from: "$rootDir/common.gradle" + +dependencies { + implementation(project(':lib:streamtape-extractor')) + implementation(project(':lib:voe-extractor')) + implementation(project(':lib:filemoon-extractor')) + implementation(project(':lib:streamwish-extractor')) + implementation(project(':lib:dood-extractor')) + implementation(project(':lib:vidhide-extractor')) + implementation(project(':lib:okru-extractor')) + implementation libs.jsunpacker +} \ No newline at end of file diff --git a/src/es/cinecalidad/res/mipmap-hdpi/ic_launcher.png b/src/es/cinecalidad/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..e167e8d679 Binary files /dev/null and b/src/es/cinecalidad/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/es/cinecalidad/res/mipmap-hdpi/ic_launcher_adaptive_back.png b/src/es/cinecalidad/res/mipmap-hdpi/ic_launcher_adaptive_back.png new file mode 100644 index 0000000000..a31f7028f2 Binary files /dev/null and b/src/es/cinecalidad/res/mipmap-hdpi/ic_launcher_adaptive_back.png differ diff --git a/src/es/cinecalidad/res/mipmap-hdpi/ic_launcher_adaptive_fore.png b/src/es/cinecalidad/res/mipmap-hdpi/ic_launcher_adaptive_fore.png new file mode 100644 index 0000000000..3ce663b61a Binary files /dev/null and b/src/es/cinecalidad/res/mipmap-hdpi/ic_launcher_adaptive_fore.png differ diff --git a/src/es/cinecalidad/res/mipmap-mdpi/ic_launcher.png b/src/es/cinecalidad/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..95338ec621 Binary files /dev/null and b/src/es/cinecalidad/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/es/cinecalidad/res/mipmap-mdpi/ic_launcher_adaptive_back.png b/src/es/cinecalidad/res/mipmap-mdpi/ic_launcher_adaptive_back.png new file mode 100644 index 0000000000..21d5008be3 Binary files /dev/null and b/src/es/cinecalidad/res/mipmap-mdpi/ic_launcher_adaptive_back.png differ diff --git a/src/es/cinecalidad/res/mipmap-mdpi/ic_launcher_adaptive_fore.png b/src/es/cinecalidad/res/mipmap-mdpi/ic_launcher_adaptive_fore.png new file mode 100644 index 0000000000..49b2a43eb5 Binary files /dev/null and b/src/es/cinecalidad/res/mipmap-mdpi/ic_launcher_adaptive_fore.png differ diff --git a/src/es/cinecalidad/res/mipmap-xhdpi/ic_launcher.png b/src/es/cinecalidad/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..e1de508a56 Binary files /dev/null and b/src/es/cinecalidad/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/es/cinecalidad/res/mipmap-xhdpi/ic_launcher_adaptive_back.png b/src/es/cinecalidad/res/mipmap-xhdpi/ic_launcher_adaptive_back.png new file mode 100644 index 0000000000..776ce9a34f Binary files /dev/null and b/src/es/cinecalidad/res/mipmap-xhdpi/ic_launcher_adaptive_back.png differ diff --git a/src/es/cinecalidad/res/mipmap-xhdpi/ic_launcher_adaptive_fore.png b/src/es/cinecalidad/res/mipmap-xhdpi/ic_launcher_adaptive_fore.png new file mode 100644 index 0000000000..e18190fe70 Binary files /dev/null and b/src/es/cinecalidad/res/mipmap-xhdpi/ic_launcher_adaptive_fore.png differ diff --git a/src/es/cinecalidad/res/mipmap-xxhdpi/ic_launcher.png b/src/es/cinecalidad/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..a70cf2cd9d Binary files /dev/null and b/src/es/cinecalidad/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/es/cinecalidad/res/mipmap-xxhdpi/ic_launcher_adaptive_back.png b/src/es/cinecalidad/res/mipmap-xxhdpi/ic_launcher_adaptive_back.png new file mode 100644 index 0000000000..9de1ea8f7f Binary files /dev/null and b/src/es/cinecalidad/res/mipmap-xxhdpi/ic_launcher_adaptive_back.png differ diff --git a/src/es/cinecalidad/res/mipmap-xxhdpi/ic_launcher_adaptive_fore.png b/src/es/cinecalidad/res/mipmap-xxhdpi/ic_launcher_adaptive_fore.png new file mode 100644 index 0000000000..b65fd053da Binary files /dev/null and b/src/es/cinecalidad/res/mipmap-xxhdpi/ic_launcher_adaptive_fore.png differ diff --git a/src/es/cinecalidad/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/cinecalidad/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..5d84dcce2b Binary files /dev/null and b/src/es/cinecalidad/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/es/cinecalidad/res/mipmap-xxxhdpi/ic_launcher_adaptive_back.png b/src/es/cinecalidad/res/mipmap-xxxhdpi/ic_launcher_adaptive_back.png new file mode 100644 index 0000000000..81b316ba93 Binary files /dev/null and b/src/es/cinecalidad/res/mipmap-xxxhdpi/ic_launcher_adaptive_back.png differ diff --git a/src/es/cinecalidad/res/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png b/src/es/cinecalidad/res/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png new file mode 100644 index 0000000000..13dcfcb45e Binary files /dev/null and b/src/es/cinecalidad/res/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png differ diff --git a/src/es/cinecalidad/src/eu/kanade/tachiyomi/animeextension/es/cinecalidad/Cinecalidad.kt b/src/es/cinecalidad/src/eu/kanade/tachiyomi/animeextension/es/cinecalidad/Cinecalidad.kt new file mode 100644 index 0000000000..7205e8aa8e --- /dev/null +++ b/src/es/cinecalidad/src/eu/kanade/tachiyomi/animeextension/es/cinecalidad/Cinecalidad.kt @@ -0,0 +1,347 @@ +package eu.kanade.tachiyomi.animeextension.es.cinecalidad + +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource +import eu.kanade.tachiyomi.animesource.model.AnimeFilter +import eu.kanade.tachiyomi.animesource.model.AnimeFilterList +import eu.kanade.tachiyomi.animesource.model.AnimesPage +import eu.kanade.tachiyomi.animesource.model.SAnime +import eu.kanade.tachiyomi.animesource.model.SEpisode +import eu.kanade.tachiyomi.animesource.model.Video +import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource +import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor +import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor +import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor +import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor +import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor +import eu.kanade.tachiyomi.lib.vidhideextractor.VidHideExtractor +import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.awaitSuccess +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 Cinecalidad : ConfigurableAnimeSource, ParsedAnimeHttpSource() { + + override val name = "Cinecalidad" + + override val baseUrl = "https://www.cinecalidad.ec" + + override val lang = "es" + + override val supportsLatest = true + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + // ============================== Popular =============================== + override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/page/$page/") + + override fun popularAnimeSelector(): String = "div.custom.animation-2.items.normal article" + + override fun popularAnimeFromElement(element: Element): SAnime { + val anime = SAnime.create() + anime.title = element.select("div.poster div.in_title").text() + anime.thumbnail_url = element.select("div.poster img").attr("data-src") + anime.setUrlWithoutDomain(element.select("div.poster > a").attr("href")) + return anime + } + + override fun popularAnimeNextPageSelector(): String = "div.wp-pagenavi a.nextpostslink" + + // =============================== Latest =============================== + override fun latestUpdatesRequest(page: Int): Request = "$baseUrl/fecha-de-lanzamiento/2024/page/$page".let(::GET) + + override fun latestUpdatesSelector(): String = popularAnimeSelector() + + override fun latestUpdatesFromElement(element: Element): SAnime = popularAnimeFromElement(element) + + override fun latestUpdatesNextPageSelector(): String = popularAnimeNextPageSelector() + + // =============================== filters =============================== + override fun getFilterList(): AnimeFilterList = AnimeFilterList( + AnimeFilter.Header("La busqueda por texto ignora el filtro"), + GenreFilter(), + ) + + private class GenreFilter : UriPartFilter( + "Tipos", + arrayOf( + Pair("", ""), + Pair("Series", "ver-serie"), + Pair("Acción", "genero-de-la-pelicula/accion"), + Pair("Animación", "genero-de-la-pelicula/animacion"), + Pair("Anime", "genero-de-la-pelicula/anime"), + Pair("Aventura", "genero-de-la-pelicula/aventura"), + Pair("Bélico", "genero-de-la-pelicula/belica"), + Pair("Ciencia ficción", "genero-de-la-pelicula/ciencia-ficcion"), + Pair("Crimen", "genero-de-la-pelicula/crimen"), + Pair("Comedia", "genero-de-la-pelicula/comedia"), + Pair("Documental", "genero-de-la-pelicula/documental"), + Pair("Drama", "genero-de-la-pelicula/drama"), + Pair("Familiar", "genero-de-la-pelicula/familia"), + Pair("Fantasía", "genero-de-la-pelicula/fantasia"), + Pair("Historia", "genero-de-la-pelicula/historia"), + Pair("Música", "genero-de-la-pelicula/musica"), + Pair("Misterio", "genero-de-la-pelicula/misterio"), + Pair("Terror", "genero-de-la-pelicula/terror"), + Pair("Suspenso", "genero-de-la-pelicula/suspense"), + Pair("Romance", "genero-de-la-pelicula/romance"), + Pair("Dc Comics", "genero-de-la-pelicula/peliculas-de-dc-comics-online-cinecalidad"), + Pair("Marvel", "genero-de-la-pelicula/universo-marvel"), + ), + ) + private open class UriPartFilter(displayName: String, val vals: Array>) : + AnimeFilter.Select(displayName, vals.map { it.first }.toTypedArray()) { + fun toUriPart() = vals[state].second + } + + // =============================== 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/?s=$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 + return when { + query.isNotBlank() -> GET("$baseUrl/page/$page/?s=$query") + genreFilter.state != 0 -> GET("$baseUrl/${genreFilter.toUriPart()}/page/$page/") + else -> popularAnimeRequest(page) + } + } + + override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element) + + override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector() + + override fun searchAnimeSelector(): String = popularAnimeSelector() + + // =========================== Anime Details ============================ + override fun animeDetailsParse(document: Document): SAnime { + val anime = SAnime.create() + + anime.title = document.select("div.single_left h1").text() + + anime.thumbnail_url = document.select("div.single_left img").attr("data-src") + + val description: String + + val fbLikeDescription = document.select("div.single_left > table > tbody > tr > td:nth-child(2) > p:nth-child(4)").first()?.text() + val tdDescription = document.select("td[style='text-align:justify;'] > p").first()?.text() + + description = if (fbLikeDescription != null && fbLikeDescription.contains("Títulos:")) { + tdDescription ?: "sin descripción" + } else { + fbLikeDescription ?: tdDescription ?: "sin descripción" + } + + anime.description = description + + val genres = document.select("span:contains(Género:) a").map { it.text() } + + if (genres.isNotEmpty()) { + anime.genre = genres.joinToString(", ") + } + val creators = document.select("span:contains(Creador:) a").map { it.text() } + + if (creators.isNotEmpty()) { + anime.author = creators.joinToString(", ") + } + + val cast = document.select("span:contains(Elenco:) a").map { it.text() } + + if (cast.isNotEmpty()) { + anime.artist = cast.joinToString(", ") + } + + return anime + } + + // ============================== Episodes ============================== + + override fun episodeListSelector() = "uwu" + + override fun episodeListParse(response: Response): List { + val document = response.asJsoup() + val isMovie = document.select(".single_left h1").isNotEmpty() && document.select("div.se-c div.se-a ul.episodios").isEmpty() + + return if (isMovie) { + listOf( + SEpisode.create().apply { + name = "Película" + setUrlWithoutDomain(response.request.url.toString()) + episode_number = 1f + }, + ) + } else { + document.select("div.se-c div.se-a ul.episodios li").map { element -> + val episodeLink = element.selectFirst("a")!!.attr("href") + val seasonMatch = Regex("S(\\d+)-E(\\d+)").find(element.selectFirst(".numerando")!!.text()) + val seasonNumber = seasonMatch?.groups?.get(1)?.value?.toInt() ?: 1 + val episodeNumber = seasonMatch?.groups?.get(2)?.value?.toInt() ?: 0 + val episodeTitle = element.selectFirst(".episodiotitle a")!!.text() + + SEpisode.create().apply { + name = "T$seasonNumber - E$episodeNumber: $episodeTitle" + setUrlWithoutDomain(episodeLink) + episode_number = episodeNumber.toFloat() + } + } + }.reversed() + } + + override fun episodeFromElement(element: Element): SEpisode { throw UnsupportedOperationException() } + + // ============================ Video Links ============================= + + /*--------------------------------Video extractors------------------------------------*/ + private val streamTapeExtractor by lazy { StreamTapeExtractor(client) } + private val voeExtractor by lazy { VoeExtractor(client) } + private val filemoonExtractor by lazy { FilemoonExtractor(client) } + private val streamWishExtractor by lazy { StreamWishExtractor(client, headers) } + private val doodExtractor by lazy { DoodExtractor(client) } + private val vidHideExtractor by lazy { VidHideExtractor(client, headers) } + private val okruExtractor by lazy { OkruExtractor(client) } + + override fun videoListParse(response: Response): List