diff --git a/src/es/tumangasnet/build.gradle b/src/es/tumangasnet/build.gradle new file mode 100644 index 000000000..c899f7e9e --- /dev/null +++ b/src/es/tumangasnet/build.gradle @@ -0,0 +1,8 @@ +ext { + extName = 'TuMangas.net' + extClass = '.TuMangasNet' + extVersionCode = 1 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/es/tumangasnet/res/mipmap-hdpi/ic_launcher.png b/src/es/tumangasnet/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..f1fc01011 Binary files /dev/null and b/src/es/tumangasnet/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/es/tumangasnet/res/mipmap-mdpi/ic_launcher.png b/src/es/tumangasnet/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..77fcd2f14 Binary files /dev/null and b/src/es/tumangasnet/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/es/tumangasnet/res/mipmap-xhdpi/ic_launcher.png b/src/es/tumangasnet/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..38cfa6951 Binary files /dev/null and b/src/es/tumangasnet/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/es/tumangasnet/res/mipmap-xxhdpi/ic_launcher.png b/src/es/tumangasnet/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..c8a37e4e1 Binary files /dev/null and b/src/es/tumangasnet/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/es/tumangasnet/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/tumangasnet/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..b479dc6ce Binary files /dev/null and b/src/es/tumangasnet/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/es/tumangasnet/src/eu/kanade/tachiyomi/extension/es/tumangasnet/TuMangasNet.kt b/src/es/tumangasnet/src/eu/kanade/tachiyomi/extension/es/tumangasnet/TuMangasNet.kt new file mode 100644 index 000000000..18c73145e --- /dev/null +++ b/src/es/tumangasnet/src/eu/kanade/tachiyomi/extension/es/tumangasnet/TuMangasNet.kt @@ -0,0 +1,120 @@ +package eu.kanade.tachiyomi.extension.es.tumangasnet + +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.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +class TuMangasNet : ParsedHttpSource() { + + override val name = "TuMangas.net" + + override val baseUrl = "https://tumangas.net" + + override val lang = "es" + + override val supportsLatest = true + + override val client = network.cloudflareClient.newBuilder() + .rateLimitHost(baseUrl.toHttpUrl(), 3, 1) + .build() + + override fun headersBuilder() = super.headersBuilder() + .add("Referer", "$baseUrl/") + + override fun popularMangaRequest(page: Int) = GET("$baseUrl/biblioteca-manga?page=$page", headers) + + override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() + + override fun popularMangaSelector() = searchMangaSelector() + + override fun popularMangaFromElement(element: Element) = searchMangaFromElement(element) + + override fun latestUpdatesRequest(page: Int) = GET(baseUrl, headers) + + override fun latestUpdatesNextPageSelector() = null + + override fun latestUpdatesSelector() = "ul.episodes article.episode" + + override fun latestUpdatesFromElement(element: Element) = SManga.create().apply { + setUrlWithoutDomain( + element.selectFirst("a")!!.attr("href") + .substringBeforeLast("-") + .replace("/leer-manga/", "/manga/"), + ) + title = element.selectFirst(".title")!!.text().substringBeforeLast("Ep.").trim() + thumbnail_url = element.selectFirst("figure > img")?.attr("src") + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + if (query.isNotBlank()) { + val url = "$baseUrl/biblioteca-manga".toHttpUrl().newBuilder() + .addQueryParameter("buscar", query) + .addQueryParameter("page", page.toString()) + .build() + + return GET(url, headers) + } + + val url = "$baseUrl/tag".toHttpUrl().newBuilder() + filters.forEach { filter -> + when (filter) { + is GenreFilter -> { + url.addPathSegment(filter.toUriPart()) + } + else -> {} + } + } + + url.addQueryParameter("page", page.toString()) + + return GET(url.build(), headers) + } + + override fun searchMangaNextPageSelector() = "ul.pagination li.page-item a[rel=next]" + + override fun searchMangaSelector() = "ul.animes article.anime" + + override fun searchMangaFromElement(element: Element) = SManga.create().apply { + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + title = element.selectFirst(".title")!!.text() + thumbnail_url = element.selectFirst("figure > img")?.attr("src") + } + + override fun getFilterList() = FilterList( + Filter.Header("NOTA: Los filtros no funcionan en la búsqueda por texto."), + GenreFilter(), + ) + + override fun mangaDetailsParse(document: Document) = SManga.create().apply { + document.selectFirst("article.anime-single")!!.let { element -> + title = element.selectFirst(".title")!!.text() + genre = element.select("p.genres > span").joinToString { it.text() } + description = element.selectFirst(".sinopsis")?.text() + thumbnail_url = element.selectFirst("div.thumb figure > img")?.attr("abs:src") + } + } + + override fun chapterListSelector() = "ul.episodes-list > li" + + override fun chapterFromElement(element: Element) = SChapter.create().apply { + setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href")) + name = element.selectFirst("a > span")!!.text() + } + + override fun pageListParse(document: Document): List { + return document.select("div#chapter_imgs img[src]").mapIndexed { i, img -> + Page(i, imageUrl = img.attr("abs:src")) + } + } + + override fun imageUrlParse(document: Document) = throw UnsupportedOperationException() +} diff --git a/src/es/tumangasnet/src/eu/kanade/tachiyomi/extension/es/tumangasnet/TuMangasNetFilters.kt b/src/es/tumangasnet/src/eu/kanade/tachiyomi/extension/es/tumangasnet/TuMangasNetFilters.kt new file mode 100644 index 000000000..479b904cb --- /dev/null +++ b/src/es/tumangasnet/src/eu/kanade/tachiyomi/extension/es/tumangasnet/TuMangasNetFilters.kt @@ -0,0 +1,62 @@ +package eu.kanade.tachiyomi.extension.es.tumangasnet + +import eu.kanade.tachiyomi.source.model.Filter + +class GenreFilter() : UriPartFilter( + "Género", + arrayOf( + Pair("Acción", "accion"), + Pair("Aventura", "aventura"), + Pair("Comedia", "comedia"), + Pair("Drama", "drama"), + Pair("Recuentos de la vida", "recuentos-de-la-vida"), + Pair("Ecchi", "ecchi"), + Pair("Fantasia", "fantasia"), + Pair("Magia", "magia"), + Pair("Sobrenatural", "sobrenatural"), + Pair("Horror", "horror"), + Pair("Misterio", "misterio"), + Pair("Psicológico", "psicologico"), + Pair("Romance", "romance"), + Pair("Ciencia Ficción", "ciencia-ficcion"), + Pair("Thriller", "thriller"), + Pair("Deporte", "deporte"), + Pair("Girls Love", "girls-love"), + Pair("Boys Love", "boys-love"), + Pair("Harem", "harem"), + Pair("Mecha", "mecha"), + Pair("Supervivencia", "supervivencia"), + Pair("Reencarnación", "reencarnacion"), + Pair("Gore", "gore"), + Pair("Apocalíptico", "apocaliptico"), + Pair("Tragedia", "tragedia"), + Pair("Vida Escolar", "vida-escolar"), + Pair("Historia", "historia"), + Pair("Militar", "militar"), + Pair("Policiaco", "policiaco"), + Pair("Crimen", "crimen"), + Pair("Superpoderes", "superpoderes"), + Pair("Vampiros", "vampiros"), + Pair("Artes Marciales", "artes-marciales"), + Pair("Samurái", "samurai"), + Pair("Género Bender", "genero-bender"), + Pair("Realidad Virtual", "realidad-virtual"), + Pair("Ciberpunk", "ciberpunk"), + Pair("Musica", "musica"), + Pair("Parodia", "parodia"), + Pair("Animación", "animacion"), + Pair("Demonios", "demonios"), + Pair("Familia", "familia"), + Pair("Extranjero", "extranjero"), + Pair("Niños", "ninos"), + Pair("Realidad", "realidad"), + Pair("Telenovela", "telenovela"), + Pair("Guerra", "guerra"), + Pair("Oeste", "oeste"), + ), +) + +open class UriPartFilter(displayName: String, val vals: Array>) : + Filter.Select(displayName, vals.map { it.first }.toTypedArray()) { + fun toUriPart() = vals[state].second +}