diff --git a/src/es/animemovil/AndroidManifest.xml b/src/es/animemovil/AndroidManifest.xml new file mode 100644 index 0000000000..568741e54f --- /dev/null +++ b/src/es/animemovil/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/es/animemovil/build.gradle b/src/es/animemovil/build.gradle new file mode 100644 index 0000000000..ea22a3e81b --- /dev/null +++ b/src/es/animemovil/build.gradle @@ -0,0 +1,29 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'AnimeMovil' + pkgNameSuffix = 'es.animemovil' + extClass = '.AnimeMovil' + extVersionCode = 1 + libVersion = '13' +} + +dependencies { + implementation(project(':lib-voe-extractor')) + implementation(project(':lib-burstcloud-extractor')) + implementation(project(':lib-mp4upload-extractor')) + implementation(project(':lib-streamwish-extractor')) + implementation(project(':lib-yourupload-extractor')) + implementation(project(':lib-fastream-extractor')) + implementation(project(':lib-upstream-extractor')) + implementation(project(':lib-filemoon-extractor')) + implementation(project(':lib-uqload-extractor')) + implementation(project(':lib-dood-extractor')) + implementation(project(':lib-streamtape-extractor')) + implementation(project(':lib-playlist-utils')) + implementation(project(':lib-streamlare-extractor')) +} + +apply from: "$rootDir/common.gradle" diff --git a/src/es/animemovil/res/mipmap-hdpi/ic_launcher.png b/src/es/animemovil/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..e2acb9e625 Binary files /dev/null and b/src/es/animemovil/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/es/animemovil/res/mipmap-mdpi/ic_launcher.png b/src/es/animemovil/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..261eb4ec62 Binary files /dev/null and b/src/es/animemovil/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/es/animemovil/res/mipmap-xhdpi/ic_launcher.png b/src/es/animemovil/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..44290ae94e Binary files /dev/null and b/src/es/animemovil/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/es/animemovil/res/mipmap-xxhdpi/ic_launcher.png b/src/es/animemovil/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..fdf61b0412 Binary files /dev/null and b/src/es/animemovil/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/es/animemovil/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/animemovil/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..1d853066fc Binary files /dev/null and b/src/es/animemovil/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/es/animemovil/src/eu/kanade/tachiyomi/animeextension/es/animemovil/AnimeMovil.kt b/src/es/animemovil/src/eu/kanade/tachiyomi/animeextension/es/animemovil/AnimeMovil.kt new file mode 100644 index 0000000000..f60c88d536 --- /dev/null +++ b/src/es/animemovil/src/eu/kanade/tachiyomi/animeextension/es/animemovil/AnimeMovil.kt @@ -0,0 +1,456 @@ +package eu.kanade.tachiyomi.animeextension.es.animemovil + +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.AnimeHttpSource +import eu.kanade.tachiyomi.lib.burstcloudextractor.BurstCloudExtractor +import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor +import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor +import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor +import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor +import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor +import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor +import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor +import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor +import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor +import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor +import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy +import java.io.UnsupportedEncodingException +import java.net.URLEncoder + +class AnimeMovil : ConfigurableAnimeSource, AnimeHttpSource() { + + override val name = "AnimeMovil" + + override val baseUrl = "https://animemeow.xyz" + + override val lang = "es" + + private val json: Json by injectLazy() + + override val supportsLatest = false + + override val client: OkHttpClient = network.cloudflareClient + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + override fun popularAnimeRequest(page: Int) = GET("$baseUrl/directorio/?p=$page", headers) + + override fun popularAnimeParse(response: Response): AnimesPage { + val document = response.asJsoup() + val elements = document.select(".grid-animes article") + val nextPage = document.select(".pagination .right:not(.disabledd) .page-link").any() + val animeList = elements.map { element -> + SAnime.create().apply { + setUrlWithoutDomain(element.selectFirst("a")?.attr("abs:href") ?: "") + title = element.selectFirst("a > p")?.text() ?: "" + thumbnail_url = element.selectFirst("a .main-img img")?.attr("abs:src") ?: "" + status = when (element.select("a .figure-title > p").text().trim()) { + "Finalizado" -> SAnime.COMPLETED + "En emision" -> SAnime.ONGOING + else -> SAnime.UNKNOWN + } + } + } + return AnimesPage(animeList, nextPage) + } + + override fun latestUpdatesRequest(page: Int) = popularAnimeRequest(page) + + override fun latestUpdatesParse(response: Response) = popularAnimeParse(response) + + override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { + val filterList = if (filters.isEmpty()) getFilterList() else filters + val genreFilter = filterList.find { it is GenreFilter } as GenreFilter + val statusFilter = filterList.find { it is StatusFilter } as StatusFilter + val typeFilter = filterList.find { it is TypeFilter } as TypeFilter + val languageFilter = filterList.find { it is LanguageFilter } as LanguageFilter + + val params = HashMap() + if (genreFilter.state != 0) { + params["genero"] = genreFilter.toUriPart() + } + if (statusFilter.state != 0) { + params["estado"] = statusFilter.toUriPart() + } + if (typeFilter.state != 0) { + params["tipo"] = typeFilter.toUriPart() + } + if (languageFilter.state != 0) { + params["idioma"] = languageFilter.toUriPart() + } + params["p"] = "$page" + + return when { + query.isNotBlank() -> GET("$baseUrl/directorio/?p=$page&q=$query", headers) + else -> GET("$baseUrl/directorio/?${urlEncodeUTF8(params)}", headers) + } + } + + override fun searchAnimeParse(response: Response) = popularAnimeParse(response) + + override fun animeDetailsParse(response: Response): SAnime { + val document = response.asJsoup() + return SAnime.create().apply { + title = document.selectFirst(".banner-info div.titles h1")?.text() ?: "" + description = document.select("#sinopsis").text() + thumbnail_url = document.selectFirst("#anime_image")?.attr("abs:src") + genre = document.select(".generos-wrap .item").joinToString { it.text() } + status = when (document.select(".banner-img .estado").text().trim()) { + "Finalizado" -> SAnime.COMPLETED + "En emision" -> SAnime.ONGOING + else -> SAnime.UNKNOWN + } + } + } + + override fun episodeListParse(response: Response): List { + val episodes = mutableListOf() + val document = response.asJsoup() + val seasons = document.select(".temporadas-lista .btn-temporada") + if (seasons.any()) { + val token = try { + response.headers.first { it.first == "set-cookie" && it.second.startsWith("csrftoken") } + .second.substringAfter("=").substringBefore(";").replace("%3D", "=") + } catch (_: Exception) { "" } + seasons.reversed().map { + val sid = it.attr("data-sid") + val t = it.attr("data-t") + + val mediaType = "application/json".toMediaType() + val requestBody = "{\"show\": \"$sid\",\"temporada\": \"$t\"}" + val request = Request.Builder() + .url("https://animemeow.xyz/api/obtener_episodios_x_temporada/") + .post(requestBody.toRequestBody(mediaType)) + .header("authority", response.request.url.host) + .header("origin", "https://${response.request.url.host}") + .header("referer", response.request.url.toString()) + .header("x-csrftoken", token) + .header("x-requested-with", "XMLHttpRequest") + .header("cookie", "csrftoken=$token") + .header("Content-Type", "application/json") + .build() + + client.newCall(request).execute().asJsoup().let { + json.decodeFromString(it.body().text()).episodios.forEachIndexed { idx, it -> + val episode = SEpisode.create().apply { + setUrlWithoutDomain(it.url) + name = "T$t - " + it.epNombre.replace("Ver", "").trim() + episode_number = idx.toFloat() + } + episodes.add(episode) + } + } + } + } else { + document.select("#eps li > a").reversed().forEachIndexed { idx, it -> + val nameEp = it.selectFirst("p")?.ownText() ?: "" + val episode = SEpisode.create().apply { + setUrlWithoutDomain(it.attr("abs:href")) + name = nameEp.replace("Ver", "").trim() + episode_number = idx.toFloat() + } + episodes.add(episode) + } + } + return episodes + } + + private fun fetchUrls(text: String?): List { + if (text.isNullOrEmpty()) return listOf() + val linkRegex = "(http|ftp|https):\\/\\/([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:\\/~+#-]*[\\w@?^=%&\\/~+#-])".toRegex() + return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList() + } + + override fun videoListParse(response: Response): List