|
|
|
@ -1,6 +1,10 @@
|
|
|
|
|
package eu.kanade.tachiyomi.extension.es.mangamx
|
|
|
|
|
|
|
|
|
|
import android.app.Application
|
|
|
|
|
import android.content.SharedPreferences
|
|
|
|
|
import android.net.Uri
|
|
|
|
|
import android.support.v7.preference.CheckBoxPreference
|
|
|
|
|
import android.support.v7.preference.PreferenceScreen
|
|
|
|
|
import android.util.Base64
|
|
|
|
|
import com.github.salomonbrys.kotson.get
|
|
|
|
|
import com.github.salomonbrys.kotson.string
|
|
|
|
@ -8,6 +12,7 @@ import com.google.gson.JsonElement
|
|
|
|
|
import com.google.gson.JsonParser
|
|
|
|
|
import eu.kanade.tachiyomi.network.GET
|
|
|
|
|
import eu.kanade.tachiyomi.network.POST
|
|
|
|
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
|
|
|
|
import eu.kanade.tachiyomi.source.model.Filter
|
|
|
|
|
import eu.kanade.tachiyomi.source.model.FilterList
|
|
|
|
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
|
|
|
@ -21,29 +26,36 @@ import okhttp3.Request
|
|
|
|
|
import okhttp3.Response
|
|
|
|
|
import org.jsoup.nodes.Document
|
|
|
|
|
import org.jsoup.nodes.Element
|
|
|
|
|
import uy.kohesive.injekt.Injekt
|
|
|
|
|
import uy.kohesive.injekt.api.get
|
|
|
|
|
import java.nio.charset.Charset
|
|
|
|
|
import java.text.SimpleDateFormat
|
|
|
|
|
import java.util.Locale
|
|
|
|
|
|
|
|
|
|
open class MangaMx : ParsedHttpSource() {
|
|
|
|
|
|
|
|
|
|
// Info
|
|
|
|
|
open class MangaMx : ConfigurableSource, ParsedHttpSource() {
|
|
|
|
|
|
|
|
|
|
override val name = "MangaMx"
|
|
|
|
|
|
|
|
|
|
override val baseUrl = "https://manga-mx.com"
|
|
|
|
|
|
|
|
|
|
override val lang = "es"
|
|
|
|
|
|
|
|
|
|
override val supportsLatest = true
|
|
|
|
|
|
|
|
|
|
override val client = network.cloudflareClient
|
|
|
|
|
|
|
|
|
|
private var csrfToken = ""
|
|
|
|
|
|
|
|
|
|
// Popular
|
|
|
|
|
|
|
|
|
|
override fun popularMangaRequest(page: Int) =
|
|
|
|
|
GET("$baseUrl/directorio?filtro=visitas&p=$page", headers)
|
|
|
|
|
override fun popularMangaRequest(page: Int) = GET(
|
|
|
|
|
"$baseUrl/directorio?genero=false" +
|
|
|
|
|
"&estado=false&filtro=visitas&tipo=false&adulto=${if (hideNSFWContent()) "0" else "false"}&orden=desc&p=$page",
|
|
|
|
|
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("data-src")
|
|
|
|
@ -65,11 +77,12 @@ open class MangaMx : ParsedHttpSource() {
|
|
|
|
|
return MangasPage(mangas, hasNextPage)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Latest
|
|
|
|
|
|
|
|
|
|
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("data-src")
|
|
|
|
|
element.select("div a").apply {
|
|
|
|
@ -78,8 +91,6 @@ open class MangaMx : ParsedHttpSource() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Search
|
|
|
|
|
|
|
|
|
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
|
|
|
|
if (query.isNotBlank()) {
|
|
|
|
|
val formBody = FormBody.Builder()
|
|
|
|
@ -91,28 +102,32 @@ open class MangaMx : ParsedHttpSource() {
|
|
|
|
|
return POST("$baseUrl/buscar", searchHeaders, formBody)
|
|
|
|
|
} else {
|
|
|
|
|
val uri = Uri.parse("$baseUrl/directorio").buildUpon()
|
|
|
|
|
// Append uri filters
|
|
|
|
|
|
|
|
|
|
uri.appendQueryParameter(
|
|
|
|
|
"adulto",
|
|
|
|
|
if (hideNSFWContent()) { "0" } else { "1" }
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
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 SortBy -> {
|
|
|
|
|
uri.appendQueryParameter("filtro", sortables[filter.state!!.index].second)
|
|
|
|
|
uri.appendQueryParameter(
|
|
|
|
|
"orden",
|
|
|
|
|
if (filter.state!!.ascending) { "asc" } else { "desc" }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
is GenreFilter -> uri.appendQueryParameter(
|
|
|
|
|
"genero",
|
|
|
|
|
genresArray[filter.state].second
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -122,8 +137,11 @@ open class MangaMx : ParsedHttpSource() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()) {
|
|
|
|
@ -154,8 +172,6 @@ open class MangaMx : ParsedHttpSource() {
|
|
|
|
|
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")
|
|
|
|
@ -175,8 +191,6 @@ open class MangaMx : ParsedHttpSource() {
|
|
|
|
|
return manga
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Chapters
|
|
|
|
|
|
|
|
|
|
override fun chapterListSelector(): String = "div#c_list a"
|
|
|
|
|
|
|
|
|
|
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
|
|
|
|
@ -190,13 +204,10 @@ open class MangaMx : ParsedHttpSource() {
|
|
|
|
|
return SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).parse(date)?.time ?: 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pages
|
|
|
|
|
|
|
|
|
|
override fun pageListParse(document: Document): List<Page> {
|
|
|
|
|
val encoded = document.select("script:containsData(unicap)").firstOrNull()
|
|
|
|
|
?.data()?.substringAfter("'")?.substringBefore("'")?.reversed()
|
|
|
|
|
?: throw Exception("unicap not found")
|
|
|
|
|
|
|
|
|
|
val drop = encoded.length % 4
|
|
|
|
|
val decoded = Base64.decode(encoded.dropLast(drop), Base64.DEFAULT).toString(Charset.defaultCharset())
|
|
|
|
|
val path = decoded.substringBefore("||")
|
|
|
|
@ -207,43 +218,35 @@ open class MangaMx : ParsedHttpSource() {
|
|
|
|
|
|
|
|
|
|
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.Header("NOTA: Se ignoran si se usa el buscador"),
|
|
|
|
|
Filter.Separator(),
|
|
|
|
|
SortBy("Ordenar por", sortables),
|
|
|
|
|
StatusFilter("Estado", statusArray),
|
|
|
|
|
FilterFilter("Filtro", filterArray),
|
|
|
|
|
TypeFilter("Tipo", typedArray),
|
|
|
|
|
AdultFilter("Adulto", adultArray),
|
|
|
|
|
OrderFilter("Orden", orderArray)
|
|
|
|
|
GenreFilter("Géneros", genresArray)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
private class StatusFilter(name: String, values: Array<Pair<String, String>>) :
|
|
|
|
|
Filter.Select<String>(name, values.map { it.first }.toTypedArray())
|
|
|
|
|
|
|
|
|
|
private class FilterFilter(name: String, values: Array<Pair<String, String>>) :
|
|
|
|
|
Filter.Select<String>(name, values.map { it.first }.toTypedArray())
|
|
|
|
|
|
|
|
|
|
private class TypeFilter(name: String, values: Array<Pair<String, String>>) :
|
|
|
|
|
Filter.Select<String>(name, values.map { it.first }.toTypedArray())
|
|
|
|
|
|
|
|
|
|
private class AdultFilter(name: String, values: Array<Pair<String, String>>) :
|
|
|
|
|
private class GenreFilter(name: String, values: Array<Pair<String, String>>) :
|
|
|
|
|
Filter.Select<String>(name, values.map { it.first }.toTypedArray())
|
|
|
|
|
|
|
|
|
|
private class OrderFilter(name: String, values: Array<Pair<String, String>>) :
|
|
|
|
|
Filter.Select<String>(name, values.map { it.first }.toTypedArray())
|
|
|
|
|
class SortBy(name: String, values: Array<Pair<String, String>>) : Filter.Sort(
|
|
|
|
|
name, values.map { it.first }.toTypedArray(),
|
|
|
|
|
Selection(0, false)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
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"),
|
|
|
|
@ -252,13 +255,104 @@ open class MangaMx : ParsedHttpSource() {
|
|
|
|
|
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 sortables = arrayOf(
|
|
|
|
|
Pair("Visitas", "visitas"),
|
|
|
|
|
Pair("Recientes", "id"),
|
|
|
|
|
Pair("Alfabético", "nombre"),
|
|
|
|
|
)
|
|
|
|
|
private val orderArray = arrayOf(
|
|
|
|
|
Pair("Descendente", "desc"),
|
|
|
|
|
Pair("Ascendente", "asc")
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Url: https://manga-mx.com/directorio/
|
|
|
|
|
* Last check: 27/03/2021
|
|
|
|
|
* JS script: Array.from(document.querySelectorAll('select[name="genero"] option'))
|
|
|
|
|
* .map(a => `Pair("${a.innerText}", "${a.value}")`).join(',\n')
|
|
|
|
|
*/
|
|
|
|
|
private val genresArray = arrayOf(
|
|
|
|
|
Pair("Todos", "false"),
|
|
|
|
|
Pair("Comedia", "1"),
|
|
|
|
|
Pair("Drama", "2"),
|
|
|
|
|
Pair("Acción", "3"),
|
|
|
|
|
Pair("Escolar", "4"),
|
|
|
|
|
Pair("Romance", "5"),
|
|
|
|
|
Pair("Ecchi", "6"),
|
|
|
|
|
Pair("Aventura", "7"),
|
|
|
|
|
Pair("Shōnen", "8"),
|
|
|
|
|
Pair("Shōjo", "9"),
|
|
|
|
|
Pair("Deportes", "10"),
|
|
|
|
|
Pair("Psicológico", "11"),
|
|
|
|
|
Pair("Fantasía", "12"),
|
|
|
|
|
Pair("Mecha", "13"),
|
|
|
|
|
Pair("Gore", "14"),
|
|
|
|
|
Pair("Yaoi", "15"),
|
|
|
|
|
Pair("Yuri", "16"),
|
|
|
|
|
Pair("Misterio", "17"),
|
|
|
|
|
Pair("Sobrenatural", "18"),
|
|
|
|
|
Pair("Seinen", "19"),
|
|
|
|
|
Pair("Ficción", "20"),
|
|
|
|
|
Pair("Harem", "21"),
|
|
|
|
|
Pair("Webtoon", "25"),
|
|
|
|
|
Pair("Histórico", "27"),
|
|
|
|
|
Pair("Músical", "30"),
|
|
|
|
|
Pair("Ciencia ficción", "31"),
|
|
|
|
|
Pair("Shōjo-ai", "32"),
|
|
|
|
|
Pair("Josei", "33"),
|
|
|
|
|
Pair("Magia", "34"),
|
|
|
|
|
Pair("Artes Marciales", "35"),
|
|
|
|
|
Pair("Horror", "36"),
|
|
|
|
|
Pair("Demonios", "37"),
|
|
|
|
|
Pair("Supervivencia", "38"),
|
|
|
|
|
Pair("Recuentos de la vida", "39"),
|
|
|
|
|
Pair("Shōnen ai", "40"),
|
|
|
|
|
Pair("Militar", "41"),
|
|
|
|
|
Pair("Eroge", "42"),
|
|
|
|
|
Pair("Isekai", "43")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
private val preferences: SharedPreferences by lazy {
|
|
|
|
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
|
|
|
|
|
|
|
|
|
|
val contentPref = androidx.preference.CheckBoxPreference(screen.context).apply {
|
|
|
|
|
key = CONTENT_PREF
|
|
|
|
|
title = CONTENT_PREF_TITLE
|
|
|
|
|
summary = CONTENT_PREF_SUMMARY
|
|
|
|
|
setDefaultValue(CONTENT_PREF_DEFAULT_VALUE)
|
|
|
|
|
|
|
|
|
|
setOnPreferenceChangeListener { _, newValue ->
|
|
|
|
|
val checkValue = newValue as Boolean
|
|
|
|
|
preferences.edit().putBoolean(CONTENT_PREF, checkValue).commit()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
screen.addPreference(contentPref)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
|
|
|
|
|
|
|
|
|
val contentPref = CheckBoxPreference(screen.context).apply {
|
|
|
|
|
key = CONTENT_PREF
|
|
|
|
|
title = CONTENT_PREF_TITLE
|
|
|
|
|
summary = CONTENT_PREF_SUMMARY
|
|
|
|
|
setDefaultValue(CONTENT_PREF_DEFAULT_VALUE)
|
|
|
|
|
|
|
|
|
|
setOnPreferenceChangeListener { _, newValue ->
|
|
|
|
|
val checkValue = newValue as Boolean
|
|
|
|
|
preferences.edit().putBoolean(CONTENT_PREF, checkValue).commit()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
screen.addPreference(contentPref)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun hideNSFWContent(): Boolean = preferences.getBoolean(CONTENT_PREF, CONTENT_PREF_DEFAULT_VALUE)
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
private const val CONTENT_PREF = "showNSFWContent"
|
|
|
|
|
private const val CONTENT_PREF_TITLE = "Ocultar contenido +18"
|
|
|
|
|
private const val CONTENT_PREF_SUMMARY = "Ocultar el contenido erótico en explorar y buscar, no funciona en los mangas recientes."
|
|
|
|
|
private const val CONTENT_PREF_DEFAULT_VALUE = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|