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