From 98ccbbde2569899746ecbdba7a822754172a180b Mon Sep 17 00:00:00 2001 From: happywillow0 <45346080+happywillow0@users.noreply.github.com> Date: Wed, 11 Mar 2020 23:18:59 -0400 Subject: [PATCH] Fix MangaMx (#2409) Fix MangaMx --- src/es/mangamx/build.gradle | 4 +- .../tachiyomi/extension/es/mangamx/MangaMx.kt | 394 ++++++++---------- .../extension/es/mangamx/MangaMxFactory.kt | 156 ------- 3 files changed, 176 insertions(+), 378 deletions(-) delete mode 100644 src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMxFactory.kt diff --git a/src/es/mangamx/build.gradle b/src/es/mangamx/build.gradle index 47dbcc436..933fb637a 100644 --- a/src/es/mangamx/build.gradle +++ b/src/es/mangamx/build.gradle @@ -4,8 +4,8 @@ apply plugin: 'kotlin-android' ext { appName = 'Tachiyomi: MangaMx & Doujin-Yang' pkgNameSuffix = 'es.mangamx' - extClass = '.MangaMxFactory' - extVersionCode = 5 + extClass = '.MangaMx' + extVersionCode = 6 libVersion = '1.2' } diff --git a/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMx.kt b/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMx.kt index cd170427f..65ce63ac5 100644 --- a/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMx.kt +++ b/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMx.kt @@ -21,123 +21,145 @@ import java.util.Locale open class MangaMx : ParsedHttpSource() { + //Info + override val name = "MangaMx" override val baseUrl = "https://manga-mx.com" override val lang = "es" override val supportsLatest = true + private var csrfToken = "" - override fun popularMangaSelector() = "article[id=item]" - override fun latestUpdatesSelector() = "div.manga-item" - override fun searchMangaSelector() = "article[id=item]" - override fun chapterListSelector() = throw Exception ("Not Used") + //Popular - override fun popularMangaNextPageSelector() = "a[href*=directorio]:containsOwn(Última)" - override fun latestUpdatesNextPageSelector() = "a[href*=reciente]:containsOwn(Última)" - override fun searchMangaNextPageSelector() = "a[href*=/?s]:containsOwn(Última), a[href*=directorio]:containsOwn(Última)" + override fun popularMangaRequest(page: Int) = + GET("$baseUrl/directorio?filtro=visitas&p=$page", headers) - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/directorio/?orden=visitas&p=$page", headers) - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/reciente/capitulos?p=$page", headers) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val uri = if (query.isNotBlank()) { - Uri.parse(baseUrl).buildUpon() - .appendQueryParameter("s", query) - } else { - val uri = Uri.parse("$baseUrl/directorio").buildUpon() - //Append uri filters - filters.forEach { - if (it is UriFilter) - it.addToUri(uri) - } - uri.appendQueryParameter("p", page.toString()) - } - return GET(uri.toString(), headers) + override fun popularMangaNextPageSelector() = ".page-item a[rel=next]" + override fun popularMangaSelector() = "#article-div a" + override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { + setUrlWithoutDomain(element.attr("href")) + thumbnail_url = element.select("img").attr("src") + title = element.select("div:eq(1)").text().trim() } - //override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl + manga.url, headers) - //override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url, headers) - - override fun chapterListRequest(manga: SManga): Request { - val body = FormBody.Builder() - .addEncoded("cap_list","") - .build() - val headers = headersBuilder() - .add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") - .build() - return POST(baseUrl + manga.url, headers, body) - } - - override fun latestUpdatesParse(response: Response): MangasPage { + override fun popularMangaParse(response: Response): MangasPage { val document = response.asJsoup() - val mangas = document.select(latestUpdatesSelector()) - .distinctBy { it.select("a").first().attr("abs:href") } - .map { latestUpdatesFromElement(it) } - val hasNextPage = latestUpdatesNextPageSelector().let { selector -> + csrfToken = document.select("meta[name=csrf-token]").attr("content") + + val mangas = document.select(popularMangaSelector()).map { element -> + popularMangaFromElement(element) + } + + val hasNextPage = popularMangaNextPageSelector().let { selector -> document.select(selector).first() } != null + return MangasPage(mangas, hasNextPage) } - override fun popularMangaFromElement(element: Element) = mangaFromElement(element) - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - manga.setUrlWithoutDomain(element.select("a").first().attr("abs:href")) - manga.title = element.select("a").first().text().trim() - return manga - } - override fun searchMangaFromElement(element: Element)= mangaFromElement(element) + //Latest - private fun mangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.setUrlWithoutDomain(element.select("a").first().attr("abs:href")) - manga.title = element.select("h2").text().trim() - //manga.thumbnail_url = "https:" + element.select("img").attr("src") - manga.thumbnail_url = element.select("img").attr("abs:src") - return manga - } - - - override fun chapterListParse(response: Response): List { - val jsonData = response.body()!!.string() - val results = JsonParser().parse(jsonData).asJsonArray - val chapters = mutableListOf() - val url = "https:" + results[0].string - for (i in 1 until results.size()) { - val obj = results[i] - chapters.add(chapterFromJson(obj, url)) + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/recientes?p=$page", headers) + override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector() + override fun latestUpdatesSelector() = "div._1bJU3" + override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { + thumbnail_url = element.select("img").attr("src") + element.select("div a").apply { + title = this.text().trim() + setUrlWithoutDomain(this.attr("href")) } - return chapters } - private fun chapterFromJson (obj: JsonElement, url: String): SChapter { - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(url + obj["id"].string) - chapter.name = obj["tc"].string + obj["titulo"].string - chapter.chapter_number = obj["numero"].string.toFloat() - chapter.date_upload = parseDate(obj["datetime"].string) - return chapter + //Search + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + if (query.isNotBlank()) { + val formBody = FormBody.Builder() + .add("buscar", query) + .add("_token", csrfToken) + .build() + val searchHeaders = headers.newBuilder().add("X-Requested-With", "XMLHttpRequest") + .add("Referer", baseUrl).build() + return POST("$baseUrl/buscar", searchHeaders, formBody) + } else { + val uri = Uri.parse("$baseUrl/directorio").buildUpon() + //Append uri filters + for (filter in filters) { + when (filter) { + is StatusFilter -> uri.appendQueryParameter( + filter.name.toLowerCase(Locale.ROOT), + statusArray[filter.state].second + ) + is FilterFilter -> uri.appendQueryParameter( + filter.name.toLowerCase(Locale.ROOT), + filterArray[filter.state].second + ) + is TypeFilter -> uri.appendQueryParameter( + filter.name.toLowerCase(Locale.ROOT), + typedArray[filter.state].second + ) + is AdultFilter -> uri.appendQueryParameter( + filter.name.toLowerCase(Locale.ROOT), + adultArray[filter.state].second + ) + is OrderFilter -> uri.appendQueryParameter( + filter.name.toLowerCase(Locale.ROOT), + orderArray[filter.state].second + ) + } + } + uri.appendQueryParameter("p", page.toString()) + return GET(uri.toString(), headers) + } } + override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector() + override fun searchMangaSelector(): String = popularMangaSelector() + override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) + override fun searchMangaParse(response: Response): MangasPage { + if (!response.isSuccessful) throw Exception("Búsqueda fallida ${response.code()}") + if ("directorio" in response.request().url().toString()) { + val document = response.asJsoup() + val mangas = document.select(searchMangaSelector()).map { element -> + searchMangaFromElement(element) + } - private fun parseDate(date: String): Long { - return SimpleDateFormat("yyyy-MM-dd kk:mm:ss", Locale.US ).parse(date).time + val hasNextPage = searchMangaNextPageSelector()?.let { selector -> + document.select(selector).first() + } != null + + return MangasPage(mangas, hasNextPage) + } else { + val body = response.body()!!.string() + if (body == "[]") throw Exception("Término de búsqueda demasiado corto") + val json = JsonParser().parse(body)["mangas"].asJsonArray + + val mangas = json.map { jsonElement -> searchMangaFromJson(jsonElement) } + val hasNextPage = false + return MangasPage(mangas, hasNextPage) + } } - override fun chapterFromElement(element: Element)= throw Exception("Not used") + private fun searchMangaFromJson(jsonElement: JsonElement): SManga = SManga.create().apply { + title = jsonElement["nombre"].string + setUrlWithoutDomain(jsonElement["url"].string) + thumbnail_url = jsonElement["img"].string.replace("/thumb", "/cover") + } + + //Details override fun mangaDetailsParse(document: Document): SManga { val manga = SManga.create() manga.thumbnail_url = document.select("img[src*=cover]").attr("abs:src") - manga.description = document.select("div[id=sinopsis]").last().ownText() - manga.author = document.select("div[id=info-i]").text().let { + manga.description = document.select("div#sinopsis").last().ownText() + manga.author = document.select("div#info-i").text().let { if (it.contains("Autor", true)) { it.substringAfter("Autor:").substringBefore("Fecha:").trim() } else "N/A" } manga.artist = manga.author - val glist = document.select("div[id=categ] a[href*=genero]").map { it.text() } - manga.genre = glist.joinToString(", ") - manga.status = when (document.select("span[id=desarrollo]")?.first()?.text()) { + manga.genre = document.select("div#categ a").joinToString(", ") { it.text() } + manga.status = when (document.select("span#desarrollo")?.first()?.text()) { "En desarrollo" -> SManga.ONGOING //"Completed" -> SManga.COMPLETED else -> SManga.UNKNOWN @@ -145,156 +167,88 @@ open class MangaMx : ParsedHttpSource() { return manga } - override fun pageListParse(response: Response): List { - val body = response.asJsoup() - val script = body.select("script:containsData(cap_info)").html() - val jsonData = script.substringAfter("var cap_info = ").substringBeforeLast(";") - val results = JsonParser().parse(jsonData).asJsonArray - val jsonImg = results[1].asJsonArray - val url = "https:" + jsonImg[0].string - val pages = mutableListOf() - for (i in 1 until jsonImg.size()) { - pages.add(Page(i, "",url + jsonImg[i].string)) - } - return pages + //Chapters + + override fun chapterListSelector(): String = "div#c_list a" + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { + name = element.text().trim() + setUrlWithoutDomain(element.attr("href")) + chapter_number = element.select("span").attr("data-num").toFloat() + date_upload = parseDate(element.select("span").attr("datetime")) + } + + private fun parseDate(date: String): Long { + return SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).parse(date)?.time ?: 0 + } + + //Pages + + override fun pageListParse(document: Document): List = mutableListOf().apply { + val script = document.select("script:containsData(hojas)").html() + val dir = script.substringAfter("var dir = '").substringBefore("';") + val imgList = + script.substringAfter("var hojas = [\"").substringBefore("\"];").split("\",\"") + imgList.forEach { + add(Page(size, "", dir + it)) + } } - override fun pageListParse(document: Document)= throw Exception("Not Used") override fun imageUrlParse(document: Document) = throw Exception("Not Used") + //Filters + override fun getFilterList() = FilterList( Filter.Header("NOTA: ¡Ignorado si usa la búsqueda de texto!"), Filter.Separator(), - GenreFilter(), - LetterFilter(), - StatusFilter(), - TypeFilter(), - AdultFilter(), - SortFilter() + StatusFilter("Estado", statusArray), + FilterFilter("Filtro", filterArray), + TypeFilter("Tipo", typedArray), + AdultFilter("Adulto", adultArray), + OrderFilter("Orden", orderArray) ) - class GenreFilter : UriPartFilter("Género", "genero", arrayOf( - Pair("all","All"), - Pair("3","Acción"), - Pair("35","Artes Marciales"), - Pair("7","Aventura"), - Pair("31","Ciencia ficción"), - Pair("1","Comedia"), - Pair("37","Demonios"), - Pair("10","Deportes"), - Pair("2","Drama"), - Pair("6","Ecchi"), - Pair("42","Eroge"), - Pair("4","Escolar"), - Pair("12","Fantasía"), - Pair("20","Ficción"), - Pair("14","Gore"), - Pair("21","Harem"), - Pair("27","Histórico"), - Pair("36","Horror"), - Pair("43","Isekai"), - Pair("33","Josei"), - Pair("34","Magia"), - Pair("13","Mecha"), - Pair("41","Militar"), - Pair("17","Misterio"), - Pair("30","Músical"), - Pair("11","Psicológico"), - Pair("39","Recuentos de la vida"), - Pair("5","Romance"), - Pair("19","Seinen"), - Pair("9","Shōjo"), - Pair("32","Shōjo-ai"), - Pair("8","Shōnen"), - Pair("40","Shōnen ai"), - Pair("18","Sobrenatural"), - Pair("38","Supervivencia"), - Pair("25","Webtoon"), - Pair("15","Yaoi"), - Pair("16","Yuri") - )) - class LetterFilter : UriPartFilter("Letra","letra", arrayOf( - Pair("all","All"), - Pair("a","A"), - Pair("b","B"), - Pair("c","C"), - Pair("d","D"), - Pair("e","E"), - Pair("f","F"), - Pair("g","G"), - Pair("h","H"), - Pair("i","I"), - Pair("j","J"), - Pair("k","K"), - Pair("l","L"), - Pair("m","M"), - Pair("n","N"), - Pair("o","O"), - Pair("p","P"), - Pair("q","Q"), - Pair("r","R"), - Pair("s","S"), - Pair("t","T"), - Pair("u","U"), - Pair("v","V"), - Pair("w","W"), - Pair("x","X"), - Pair("y","Y"), - Pair("z","Z") - )) + private class StatusFilter(name: String, values: Array>) : + Filter.Select(name, values.map { it.first }.toTypedArray()) - class StatusFilter : UriPartFilter("Estado", "estado", arrayOf( - Pair("all","All"),Pair("1","En desarrollo"), Pair("0","Finalizado"))) + private class FilterFilter(name: String, values: Array>) : + Filter.Select(name, values.map { it.first }.toTypedArray()) - private class TypeFilter : UriPartFilter("Tipo", "tipo", arrayOf( - Pair("all","All"), - Pair("0","Manga"), - Pair("1","Manhwa"), - Pair("2","One Shot"), - Pair("3","Manhua"), - Pair("4","Novela") - )) + private class TypeFilter(name: String, values: Array>) : + Filter.Select(name, values.map { it.first }.toTypedArray()) - private class AdultFilter : UriPartFilter("Filtro adulto", "adulto", arrayOf( - Pair("all","All"),Pair("0","Mostrar solo +18"), Pair("1","No mostrar +18"))) + private class AdultFilter(name: String, values: Array>) : + Filter.Select(name, values.map { it.first }.toTypedArray()) - class SortFilter : UriPartFilterreq("Sort", "orden", arrayOf( - Pair("visitas","Visitas"), - Pair("desc","Descendente"), - Pair("asc","Ascendente"), - Pair("lanzamiento","Lanzamiento"), - Pair("nombre","Nombre") - )) + private class OrderFilter(name: String, values: Array>) : + Filter.Select(name, values.map { it.first }.toTypedArray()) - /** - * Class that creates a select filter. Each entry in the dropdown has a name and a display name. - * If an entry is selected it is appended as a query parameter onto the end of the URI. - * If `firstIsUnspecified` is set to true, if the first entry is selected, nothing will be appended on the the URI. - */ - //vals: - open class UriPartFilter(displayName: String, private val uriParam: String, private val vals: Array>, - private val firstIsUnspecified: Boolean = true, - defaultValue: Int = 0) : - Filter.Select(displayName, vals.map { it.second }.toTypedArray(), defaultValue), UriFilter { - override fun addToUri(uri: Uri.Builder) { - if (state != 0 || !firstIsUnspecified) - uri.appendQueryParameter(uriParam, vals[state].first) - } - } - - open class UriPartFilterreq(displayName: String, private val uriParam: String, private val vals: Array>) : - Filter.Select(displayName, vals.map { it.second }.toTypedArray()), UriFilter { - override fun addToUri(uri: Uri.Builder) { - uri.appendQueryParameter(uriParam, vals[state].first) - } - } - - /** - * Represents a filter that is able to modify a URI. - */ - private interface UriFilter { - fun addToUri(uri: Uri.Builder) - } + private val statusArray = arrayOf( + Pair("Estado", "false"), + Pair("En desarrollo", "1"), + Pair("Completo", "0") + ) + private val filterArray = arrayOf( + Pair("Visitas", "visitas"), + Pair("Recientes", "id"), + Pair("Alfabético", "nombre") + ) + private val typedArray = arrayOf( + Pair("Todo", "false"), + Pair("Mangas", "0"), + Pair("Manhwas", "1"), + Pair("One Shot", "2"), + Pair("Manhuas", "3"), + Pair("Novelas", "4") + ) + private val adultArray = arrayOf( + Pair("Filtro adulto", "false"), + Pair("No mostrar +18", "0"), + Pair("Mostrar +18", "1") + ) + private val orderArray = arrayOf( + Pair("Descendente", "desc"), + Pair("Ascendente", "asc") + ) } diff --git a/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMxFactory.kt b/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMxFactory.kt deleted file mode 100644 index 3a6254559..000000000 --- a/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMxFactory.kt +++ /dev/null @@ -1,156 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.mangamx - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory -import eu.kanade.tachiyomi.source.model.* -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response - -class MangaMxFactory : SourceFactory { - override fun createSources(): List = listOf( - MangaMx(), - DoujinYang() - ) -} - -class DoujinYang: MangaMx() { - override val baseUrl = "https://doujin-yang.es" - - override val name = "Doujin-Yang" - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/reciente/doujin?p=$page", headers) - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesNextPageSelector() = "nav#paginacion a:contains(Última)" - - override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector() - - override fun chapterListRequest(manga: SManga): Request { - return GET(baseUrl + manga.url, headers) - } - - override fun chapterListParse(response: Response): List { - return response.asJsoup().select("div#c_list a").map { element -> - SChapter.create().apply { - name = element.select("h3").text() - setUrlWithoutDomain(element.attr("abs:href")) - } - } - } - - override fun pageListRequest(chapter: SChapter): Request { - return POST(baseUrl + chapter.url, - headersBuilder().add("Content-Type", "application/x-www-form-urlencoded").build(), - RequestBody.create(null, "info") - ) - } - - override fun pageListParse(response: Response): List { - return response.body()!!.string().substringAfter(",[").substringBefore("]") - .replace(Regex("""[\\"]"""), "").split(",").let { list -> - val path = "https:" + list[0] - list.drop(1).mapIndexed { i, img -> Page(i, "", path + img) } - } - } - - override fun getFilterList() = FilterList( - Filter.Header("NOTA: ¡La búsqueda de títulos no funciona!"), // "Title search not working" - Filter.Separator(), - GenreFilter(), - LetterFilter(), - StatusFilter(), - SortFilter() - ) - - class GenreFilter : UriPartFilter("Género", "genero", arrayOf( - Pair("all","All"), - Pair("1","Ahegao"), - Pair("379","Alien"), - Pair("2","Anal"), - Pair("490","Android18"), - Pair("717","Angel"), - Pair("633","Asphyxiation"), - Pair("237","Bandages"), - Pair("77","Bbw"), - Pair("143","Bdsm"), - Pair("23","Blackmail"), - Pair("113","Blindfold"), - Pair("24","Blowjob"), - Pair("166","Blowjobface"), - Pair("25","Body Writing"), - Pair("314","Bodymodification"), - Pair("806","Bodystocking"), - Pair("366","Bodysuit"), - Pair("419","Bodyswap"), - Pair("325","Bodywriting"), - Pair("5","Bondage"), - Pair("51","Bukkake"), - Pair("410","Catgirl"), - Pair("61","Chastitybelt"), - Pair("78","Cheating"), - Pair("293","Cheerleader"), - Pair("62","Collar"), - Pair("120","Compilation"), - Pair("74","Condom"), - Pair("63","Corruption"), - Pair("191","Corset"), - Pair("234","Cosplaying"), - Pair("389","Cowgirl"), - Pair("256","Crossdressing"), - Pair("179","Crotchtattoo"), - Pair("689","Crown"), - Pair("733","Cumflation"), - Pair("385","Cumswap"), - Pair("251","Cunnilingus"), - Pair("75","Darkskin"), - Pair("180","Daughter"), - Pair("52","Deepthroat"), - Pair("28","Defloration"), - Pair("198","Demon"), - Pair("145","Demongirl"), - Pair("64","Drugs"), - Pair("95","Drunk"), - Pair("462","Femalesonly"), - Pair("82","Femdom"), - Pair("139","Ffmthreesome"), - Pair("823","Fftthreesome"), - Pair("55","Full Color"), - Pair("181","Fullbodytattoo"), - Pair("203","Fullcensorship"), - Pair("111","Fullcolor"), - Pair("114","Gag"), - Pair("3","Glasses"), - Pair("515","Gloryhole"), - Pair("116","Humanpet"), - Pair("32","Humiliation"), - Pair("147","Latex"), - Pair("12","Maid"), - Pair("4","Milf"), - Pair("245","Military"), - Pair("414","Milking"), - Pair("34","Mind Control"), - Pair("68","Mindbreak"), - Pair("124","Mindcontrol"), - Pair("645","Nun"), - Pair("312","Nurse"), - Pair("272","Robot"), - Pair("7","Romance"), - Pair("761","Sundress"), - Pair("412","Tailplug"), - Pair("253","Tutor"), - Pair("259","Twins"), - Pair("207","Twintails"), - Pair("840","Valkyrie"), - Pair("530","Vampire"), - Pair("16","Yuri"), - Pair("273","Zombie") - )) -} - - -