diff --git a/src/es/metroseries/AndroidManifest.xml b/src/es/metroseries/AndroidManifest.xml
new file mode 100644
index 000000000..568741e54
--- /dev/null
+++ b/src/es/metroseries/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/src/es/metroseries/build.gradle b/src/es/metroseries/build.gradle
new file mode 100644
index 000000000..888991033
--- /dev/null
+++ b/src/es/metroseries/build.gradle
@@ -0,0 +1,24 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlinx-serialization'
+
+ext {
+ extName = 'MetroSeries'
+ pkgNameSuffix = 'es.metroseries'
+ extClass = '.MetroSeries'
+ extVersionCode = 1
+ libVersion = '13'
+}
+
+dependencies {
+ implementation(project(':lib-burstcloud-extractor'))
+ implementation(project(':lib-mp4upload-extractor'))
+ implementation(project(':lib-streamwish-extractor'))
+ implementation(project(':lib-voe-extractor'))
+ implementation(project(':lib-yourupload-extractor'))
+ implementation(project(':lib-fastream-extractor'))
+ implementation(project(':lib-upstream-extractor'))
+ implementation(project(':lib-filemoon-extractor'))
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/es/metroseries/res/mipmap-hdpi/ic_launcher.png b/src/es/metroseries/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..9fe6ce837
Binary files /dev/null and b/src/es/metroseries/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/es/metroseries/res/mipmap-mdpi/ic_launcher.png b/src/es/metroseries/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..0e6e21e74
Binary files /dev/null and b/src/es/metroseries/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/es/metroseries/res/mipmap-xhdpi/ic_launcher.png b/src/es/metroseries/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..1015fa3ea
Binary files /dev/null and b/src/es/metroseries/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/es/metroseries/res/mipmap-xxhdpi/ic_launcher.png b/src/es/metroseries/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..5b0d38a80
Binary files /dev/null and b/src/es/metroseries/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/es/metroseries/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/metroseries/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..ce6069700
Binary files /dev/null and b/src/es/metroseries/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/es/metroseries/src/eu/kanade/tachiyomi/animeextension/es/metroseries/MetroSeries.kt b/src/es/metroseries/src/eu/kanade/tachiyomi/animeextension/es/metroseries/MetroSeries.kt
new file mode 100644
index 000000000..e3db778dc
--- /dev/null
+++ b/src/es/metroseries/src/eu/kanade/tachiyomi/animeextension/es/metroseries/MetroSeries.kt
@@ -0,0 +1,238 @@
+package eu.kanade.tachiyomi.animeextension.es.metroseries
+
+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.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.fastreamextractor.FastreamExtractor
+import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
+import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
+import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
+import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor
+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.json.Json
+import okhttp3.FormBody
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import uy.kohesive.injekt.injectLazy
+import java.text.SimpleDateFormat
+import java.util.Locale
+
+class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
+
+ override val name = "MetroSeries"
+
+ override val baseUrl = "https://metroseries.net"
+
+ 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/series/page/$page", headers)
+
+ override fun popularAnimeParse(response: Response): AnimesPage {
+ val document = response.asJsoup()
+ val elements = document.select(".post-list, .results-post > .post")
+ val nextPage = document.select(".nav-links .current ~ a").any()
+ val animeList = elements.map { element ->
+ SAnime.create().apply {
+ setUrlWithoutDomain(element.selectFirst(".lnk-blk")?.attr("abs:href") ?: "")
+ title = element.selectFirst(".entry-header .entry-title")?.text() ?: ""
+ thumbnail_url = element.selectFirst(".post-thumbnail figure img")?.attr("abs:src") ?: ""
+ }
+ }
+ 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) = GET("$baseUrl/?s=$query", 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("main .entry-header .entry-title")?.text() ?: ""
+ description = document.select("main .entry-content p").joinToString { it.text() }
+ thumbnail_url = document.selectFirst("main .post-thumbnail img")?.attr("abs:src")
+ genre = document.select("main .entry-content .tagcloud a").joinToString { it.text() }
+ status = SAnime.UNKNOWN
+ }
+ }
+
+ override fun episodeListParse(response: Response): List {
+ val episodes = mutableListOf()
+ val document = response.asJsoup()
+ document.select(".season-list li a")
+ .sortedByDescending { it.attr("data-season") }.map {
+ val post = it.attr("data-post")
+ val season = it.attr("data-season")
+ val objectNumber = document.select("#aa-season").attr("data-object")
+
+ val formBody = FormBody.Builder()
+ .add("action", "action_select_season")
+ .add("season", season)
+ .add("post", post)
+ .add("object", objectNumber)
+ .build()
+
+ val request = Request.Builder()
+ .url("https://metroseries.net/wp-admin/admin-ajax.php")
+ .post(formBody)
+ .header("Origin", baseUrl)
+ .header("Referer", response.request.url.toString())
+ .header("Content-Type", "application/x-www-form-urlencoded")
+ .build()
+ val docEpisodes = client.newCall(request).execute().asJsoup()
+
+ docEpisodes.select(".episodes-list li a").reversed().map {
+ val epNumber = it.ownText().substringAfter("x").substringBefore("–").trim()
+ val episode = SEpisode.create().apply {
+ setUrlWithoutDomain(it.attr("abs:href"))
+ name = "T$season - E$epNumber - ${it.ownText().substringAfter("–").trim()}"
+ date_upload = try {
+ SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH).parse(it.select("span").text()).time
+ } catch (_: Exception) { System.currentTimeMillis() }
+ }
+ episodes.add(episode)
+ }
+ }
+ return episodes
+ }
+
+ override fun videoListParse(response: Response): List