diff --git a/src/es/mangalatino/AndroidManifest.xml b/src/es/mangalatino/AndroidManifest.xml
new file mode 100644
index 000000000..8072ee00d
--- /dev/null
+++ b/src/es/mangalatino/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/src/es/mangalatino/build.gradle b/src/es/mangalatino/build.gradle
new file mode 100644
index 000000000..2ee302f41
--- /dev/null
+++ b/src/es/mangalatino/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+
+ext {
+ extName = 'Manga Latino'
+ pkgNameSuffix = 'es.mangalatino'
+ extClass = '.MangaLatino'
+ extVersionCode = 1
+ isNsfw = true
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/es/mangalatino/res/mipmap-hdpi/ic_launcher.png b/src/es/mangalatino/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..0597e0076
Binary files /dev/null and b/src/es/mangalatino/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/es/mangalatino/res/mipmap-mdpi/ic_launcher.png b/src/es/mangalatino/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..e9ba9432c
Binary files /dev/null and b/src/es/mangalatino/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/es/mangalatino/res/mipmap-xhdpi/ic_launcher.png b/src/es/mangalatino/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..3b2116bc2
Binary files /dev/null and b/src/es/mangalatino/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/es/mangalatino/res/mipmap-xxhdpi/ic_launcher.png b/src/es/mangalatino/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..ac91ec0a2
Binary files /dev/null and b/src/es/mangalatino/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/es/mangalatino/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/mangalatino/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..1f0cd1cfc
Binary files /dev/null and b/src/es/mangalatino/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/es/mangalatino/res/web_hi_res_512.png b/src/es/mangalatino/res/web_hi_res_512.png
new file mode 100644
index 000000000..61cff7ca0
Binary files /dev/null and b/src/es/mangalatino/res/web_hi_res_512.png differ
diff --git a/src/es/mangalatino/src/eu/kanade/tachiyomi/extension/es/mangalatino/MangaLatino.kt b/src/es/mangalatino/src/eu/kanade/tachiyomi/extension/es/mangalatino/MangaLatino.kt
new file mode 100644
index 000000000..427866ff5
--- /dev/null
+++ b/src/es/mangalatino/src/eu/kanade/tachiyomi/extension/es/mangalatino/MangaLatino.kt
@@ -0,0 +1,119 @@
+package eu.kanade.tachiyomi.extension.es.mangalatino
+
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
+import eu.kanade.tachiyomi.source.model.Filter
+import eu.kanade.tachiyomi.source.model.FilterList
+import eu.kanade.tachiyomi.source.model.Page
+import eu.kanade.tachiyomi.source.model.SChapter
+import eu.kanade.tachiyomi.source.model.SManga
+import eu.kanade.tachiyomi.source.online.ParsedHttpSource
+import okhttp3.Headers
+import okhttp3.HttpUrl.Companion.toHttpUrl
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+
+class MangaLatino : ParsedHttpSource() {
+
+ override val name = "MangaLatino"
+
+ override val baseUrl = "https://mangalatino.com"
+
+ override val lang = "es"
+
+ override val supportsLatest = true
+
+ override val client: OkHttpClient = network.client.newBuilder()
+ .rateLimitHost(baseUrl.toHttpUrl(), 2)
+ .build()
+
+ override fun headersBuilder(): Headers.Builder = Headers.Builder()
+ .add("Referer", baseUrl)
+
+ override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/mangas?page=$page", headers)
+
+ override fun popularMangaSelector(): String = "section.blog-listing div.row div.blog-grid"
+
+ override fun popularMangaNextPageSelector(): String = "nav > ul.pagination > li > a[rel=next]"
+
+ override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
+ setUrlWithoutDomain(element.select("div.blog-info a").attr("href"))
+ title = element.select("div.blog-info a").text()
+ thumbnail_url = element.selectFirst("div.blog-img img")?.attr("abs:src")
+ }
+
+ override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers)
+
+ override fun latestUpdatesSelector(): String = "div.row > div.col-sm-12:eq(1) ~ div.col-6:not(div.col-sm-12:gt(1) ~ div.col-6)"
+
+ override fun latestUpdatesNextPageSelector(): String? = null
+
+ override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply {
+ val href = element.selectFirst("div.blog-info a")!!.attr("href")
+ val slug = href.substringAfterLast("/").substringBeforeLast("-")
+ url = "/serie/$slug"
+ title = element.select("div.blog-info a").text().substringBeforeLast("Capítulo").trim()
+ thumbnail_url = element.selectFirst("div.blog-img img")?.attr("abs:src")
+ }
+
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+ val url = "$baseUrl/mangas".toHttpUrl().newBuilder()
+ if (query.isNotEmpty()) {
+ url.addQueryParameter("buscar", query)
+ } else {
+ filters.forEach { filter ->
+ when (filter) {
+ is GenreFilter -> {
+ url.addQueryParameter("tag", filter.toUriPart())
+ }
+ else -> {}
+ }
+ }
+ }
+ url.addQueryParameter("page", page.toString())
+ return GET(url.build(), headers)
+ }
+
+ override fun searchMangaSelector(): String = popularMangaSelector()
+
+ override fun searchMangaNextPageSelector(): String = popularMangaNextPageSelector()
+
+ override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
+
+ override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
+ with(document.selectFirst("div.starter-template")!!) {
+ selectFirst("img[src]")?.let { thumbnail_url = it.attr("abs:src") }
+ selectFirst("h1")?.let { title = it.text() }
+ description = selectFirst("p")?.text()
+ genre = select("> a.btn").joinToString { it.text() }
+ }
+ }
+
+ override fun chapterListSelector(): String = "div.panel ul.list-group > li > a"
+
+ override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
+ setUrlWithoutDomain(element.attr("href"))
+ name = element.selectFirst("span")!!.text()
+ }
+
+ override fun pageListParse(document: Document): List {
+ return document.select("section.blog-listing div.panel-body > img[src]").mapIndexed { i, element ->
+ Page(i, "", element.attr("abs:src"))
+ }
+ }
+
+ override fun imageRequest(page: Page): Request {
+ val noRefererHeader = headers.newBuilder().removeAll("Referer").build()
+ return GET(page.imageUrl!!, noRefererHeader)
+ }
+
+ override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used!")
+
+ override fun getFilterList(): FilterList = FilterList(
+ Filter.Header("NOTA: La búsqueda por texto ignorará los demás filtros."),
+ Filter.Separator(),
+ GenreFilter(),
+ )
+}
diff --git a/src/es/mangalatino/src/eu/kanade/tachiyomi/extension/es/mangalatino/MangaLatinoFilters.kt b/src/es/mangalatino/src/eu/kanade/tachiyomi/extension/es/mangalatino/MangaLatinoFilters.kt
new file mode 100644
index 000000000..d6a39b5fe
--- /dev/null
+++ b/src/es/mangalatino/src/eu/kanade/tachiyomi/extension/es/mangalatino/MangaLatinoFilters.kt
@@ -0,0 +1,62 @@
+package eu.kanade.tachiyomi.extension.es.mangalatino
+
+import eu.kanade.tachiyomi.source.model.Filter
+
+class GenreFilter() : UriPartFilter(
+ "Tags",
+ arrayOf(
+ Pair("Acción", "accion"),
+ Pair("Animación", "animacion"),
+ Pair("Apocalíptico", "apocaliptico"),
+ Pair("Artes Marciales", "artes-marciales"),
+ Pair("Aventura", "aventura"),
+ Pair("Boys Love", "boys-love"),
+ Pair("Ciberpunk", "ciberpunk"),
+ Pair("Ciencia Ficción", "ciencia-ficcion"),
+ Pair("Comedia", "comedia"),
+ Pair("Crimen", "crimen"),
+ Pair("Demonios", "demonios"),
+ Pair("Deporte", "deporte"),
+ Pair("Drama", "drama"),
+ Pair("Ecchi", "ecchi"),
+ Pair("Extranjero", "extranjero"),
+ Pair("Familia", "familia"),
+ Pair("Fantasia", "fantasia"),
+ Pair("Género Bender", "genero-bender"),
+ Pair("Girls Love", "girls-love"),
+ Pair("Gore", "gore"),
+ Pair("Guerra", "guerra"),
+ Pair("Harem", "harem"),
+ Pair("Historia", "historia"),
+ Pair("Horror", "horror"),
+ Pair("Magia", "magia"),
+ Pair("Mecha", "mecha"),
+ Pair("Militar", "militar"),
+ Pair("Misterio", "misterio"),
+ Pair("Musica", "musica"),
+ Pair("Niños", "ninos"),
+ Pair("Oeste", "oeste"),
+ Pair("Parodia", "parodia"),
+ Pair("Policiaco", "policiaco"),
+ Pair("Psicológico", "psicologico"),
+ Pair("Realidad", "realidad"),
+ Pair("Realidad Virtual", "realidad-virtual"),
+ Pair("Recuentos de la vida", "recuentos-de-la-vida"),
+ Pair("Reencarnación", "reencarnacion"),
+ Pair("Romance", "romance"),
+ Pair("Samurái", "samurai"),
+ Pair("Sobrenatural", "sobrenatural"),
+ Pair("Superpoderes", "superpoderes"),
+ Pair("Supervivencia", "supervivencia"),
+ Pair("Telenovela", "telenovela"),
+ Pair("Thriller", "thriller"),
+ Pair("Tragedia", "tragedia"),
+ Pair("Vampiros", "vampiros"),
+ Pair("Vida Escolar", "vida-escolar"),
+ ),
+)
+
+open class UriPartFilter(displayName: String, val vals: Array>) :
+ Filter.Select(displayName, vals.map { it.first }.toTypedArray()) {
+ fun toUriPart() = vals[state].second
+}