Fix LectorManga HTTP error 403, Add MangaMx genre filter and config screen (#6323)
* Fix LectorManga HTTP error 403 * Add genres filter, config +18 filter. * Fix tachiyomiorg/tachiyomi-extensions#6325
This commit is contained in:
parent
461228d7d7
commit
39ac1f81a6
@ -5,7 +5,7 @@ ext {
|
||||
extName = 'LectorManga'
|
||||
pkgNameSuffix = 'es.lectormanga'
|
||||
extClass = '.LectorManga'
|
||||
extVersionCode = 15
|
||||
extVersionCode = 16
|
||||
libVersion = '1.2'
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
@ -39,6 +40,15 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
private val userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
|
||||
"(KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36"
|
||||
|
||||
override fun headersBuilder(): Headers.Builder {
|
||||
return Headers.Builder()
|
||||
.add("User-Agent", userAgent)
|
||||
.add("Referer", "$baseUrl/")
|
||||
}
|
||||
|
||||
private val imageCDNUrl = "https://img1.followmanga.com"
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
|
@ -5,7 +5,7 @@ ext {
|
||||
extName = 'MangaMx'
|
||||
pkgNameSuffix = 'es.mangamx'
|
||||
extClass = '.MangaMx'
|
||||
extVersionCode = 9
|
||||
extVersionCode = 10
|
||||
libVersion = '1.2'
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ ext {
|
||||
extName = 'TuMangaOnline'
|
||||
pkgNameSuffix = 'es.tumangaonline'
|
||||
extClass = '.TuMangaOnline'
|
||||
extVersionCode = 32
|
||||
extVersionCode = 33
|
||||
libVersion = '1.2'
|
||||
}
|
||||
|
||||
|
@ -140,8 +140,8 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
||||
is GenreList -> {
|
||||
filter.state.forEach { genre ->
|
||||
when (genre.state) {
|
||||
Filter.TriState.STATE_INCLUDE -> url.addQueryParameter("exclude_genders[]", genre.id)
|
||||
Filter.TriState.STATE_EXCLUDE -> url.addQueryParameter("genders[]", genre.id)
|
||||
Filter.TriState.STATE_INCLUDE -> url.addQueryParameter("genders[]", genre.id)
|
||||
Filter.TriState.STATE_EXCLUDE -> url.addQueryParameter("exclude_genders[]", genre.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user