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 +}