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:
Edgar Mejía 2021-03-28 14:19:11 -06:00 committed by GitHub
parent 461228d7d7
commit 39ac1f81a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 163 additions and 59 deletions

View File

@ -5,7 +5,7 @@ ext {
extName = 'LectorManga' extName = 'LectorManga'
pkgNameSuffix = 'es.lectormanga' pkgNameSuffix = 'es.lectormanga'
extClass = '.LectorManga' extClass = '.LectorManga'
extVersionCode = 15 extVersionCode = 16
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
@ -39,6 +40,15 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
override val supportsLatest = true 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 imageCDNUrl = "https://img1.followmanga.com"
private val preferences: SharedPreferences by lazy { private val preferences: SharedPreferences by lazy {

View File

@ -5,7 +5,7 @@ ext {
extName = 'MangaMx' extName = 'MangaMx'
pkgNameSuffix = 'es.mangamx' pkgNameSuffix = 'es.mangamx'
extClass = '.MangaMx' extClass = '.MangaMx'
extVersionCode = 9 extVersionCode = 10
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -1,6 +1,10 @@
package eu.kanade.tachiyomi.extension.es.mangamx package eu.kanade.tachiyomi.extension.es.mangamx
import android.app.Application
import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
import android.support.v7.preference.CheckBoxPreference
import android.support.v7.preference.PreferenceScreen
import android.util.Base64 import android.util.Base64
import com.github.salomonbrys.kotson.get import com.github.salomonbrys.kotson.get
import com.github.salomonbrys.kotson.string import com.github.salomonbrys.kotson.string
@ -8,6 +12,7 @@ import com.google.gson.JsonElement
import com.google.gson.JsonParser import com.google.gson.JsonParser
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST 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.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
@ -21,29 +26,36 @@ import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.nio.charset.Charset import java.nio.charset.Charset
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
open class MangaMx : ParsedHttpSource() { open class MangaMx : ConfigurableSource, ParsedHttpSource() {
// Info
override val name = "MangaMx" override val name = "MangaMx"
override val baseUrl = "https://manga-mx.com" override val baseUrl = "https://manga-mx.com"
override val lang = "es" override val lang = "es"
override val supportsLatest = true override val supportsLatest = true
override val client = network.cloudflareClient override val client = network.cloudflareClient
private var csrfToken = "" private var csrfToken = ""
// Popular override fun popularMangaRequest(page: Int) = GET(
"$baseUrl/directorio?genero=false" +
override fun popularMangaRequest(page: Int) = "&estado=false&filtro=visitas&tipo=false&adulto=${if (hideNSFWContent()) "0" else "false"}&orden=desc&p=$page",
GET("$baseUrl/directorio?filtro=visitas&p=$page", headers) headers
)
override fun popularMangaNextPageSelector() = ".page-item a[rel=next]" override fun popularMangaNextPageSelector() = ".page-item a[rel=next]"
override fun popularMangaSelector() = "#article-div a" override fun popularMangaSelector() = "#article-div a"
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
setUrlWithoutDomain(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
thumbnail_url = element.select("img").attr("data-src") thumbnail_url = element.select("img").attr("data-src")
@ -65,11 +77,12 @@ open class MangaMx : ParsedHttpSource() {
return MangasPage(mangas, hasNextPage) return MangasPage(mangas, hasNextPage)
} }
// Latest
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/recientes?p=$page", headers) override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/recientes?p=$page", headers)
override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector() override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector()
override fun latestUpdatesSelector() = "div._1bJU3" override fun latestUpdatesSelector() = "div._1bJU3"
override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply {
thumbnail_url = element.select("img").attr("data-src") thumbnail_url = element.select("img").attr("data-src")
element.select("div a").apply { element.select("div a").apply {
@ -78,8 +91,6 @@ open class MangaMx : ParsedHttpSource() {
} }
} }
// Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (query.isNotBlank()) { if (query.isNotBlank()) {
val formBody = FormBody.Builder() val formBody = FormBody.Builder()
@ -91,28 +102,32 @@ open class MangaMx : ParsedHttpSource() {
return POST("$baseUrl/buscar", searchHeaders, formBody) return POST("$baseUrl/buscar", searchHeaders, formBody)
} else { } else {
val uri = Uri.parse("$baseUrl/directorio").buildUpon() val uri = Uri.parse("$baseUrl/directorio").buildUpon()
// Append uri filters
uri.appendQueryParameter(
"adulto",
if (hideNSFWContent()) { "0" } else { "1" }
)
for (filter in filters) { for (filter in filters) {
when (filter) { when (filter) {
is StatusFilter -> uri.appendQueryParameter( is StatusFilter -> uri.appendQueryParameter(
filter.name.toLowerCase(Locale.ROOT), filter.name.toLowerCase(Locale.ROOT),
statusArray[filter.state].second statusArray[filter.state].second
) )
is FilterFilter -> uri.appendQueryParameter( is SortBy -> {
filter.name.toLowerCase(Locale.ROOT), uri.appendQueryParameter("filtro", sortables[filter.state!!.index].second)
filterArray[filter.state].second uri.appendQueryParameter(
) "orden",
if (filter.state!!.ascending) { "asc" } else { "desc" }
)
}
is TypeFilter -> uri.appendQueryParameter( is TypeFilter -> uri.appendQueryParameter(
filter.name.toLowerCase(Locale.ROOT), filter.name.toLowerCase(Locale.ROOT),
typedArray[filter.state].second typedArray[filter.state].second
) )
is AdultFilter -> uri.appendQueryParameter( is GenreFilter -> uri.appendQueryParameter(
filter.name.toLowerCase(Locale.ROOT), "genero",
adultArray[filter.state].second genresArray[filter.state].second
)
is OrderFilter -> uri.appendQueryParameter(
filter.name.toLowerCase(Locale.ROOT),
orderArray[filter.state].second
) )
} }
} }
@ -122,8 +137,11 @@ open class MangaMx : ParsedHttpSource() {
} }
override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector() override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector()
override fun searchMangaSelector(): String = popularMangaSelector() override fun searchMangaSelector(): String = popularMangaSelector()
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
if (!response.isSuccessful) throw Exception("Búsqueda fallida ${response.code()}") if (!response.isSuccessful) throw Exception("Búsqueda fallida ${response.code()}")
if ("directorio" in response.request().url().toString()) { if ("directorio" in response.request().url().toString()) {
@ -154,8 +172,6 @@ open class MangaMx : ParsedHttpSource() {
thumbnail_url = jsonElement["img"].string.replace("/thumb", "/cover") thumbnail_url = jsonElement["img"].string.replace("/thumb", "/cover")
} }
// Details
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val manga = SManga.create() val manga = SManga.create()
manga.thumbnail_url = document.select("img[src*=cover]").attr("abs:src") manga.thumbnail_url = document.select("img[src*=cover]").attr("abs:src")
@ -175,8 +191,6 @@ open class MangaMx : ParsedHttpSource() {
return manga return manga
} }
// Chapters
override fun chapterListSelector(): String = "div#c_list a" override fun chapterListSelector(): String = "div#c_list a"
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { 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 return SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).parse(date)?.time ?: 0
} }
// Pages
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val encoded = document.select("script:containsData(unicap)").firstOrNull() val encoded = document.select("script:containsData(unicap)").firstOrNull()
?.data()?.substringAfter("'")?.substringBefore("'")?.reversed() ?.data()?.substringAfter("'")?.substringBefore("'")?.reversed()
?: throw Exception("unicap not found") ?: throw Exception("unicap not found")
val drop = encoded.length % 4 val drop = encoded.length % 4
val decoded = Base64.decode(encoded.dropLast(drop), Base64.DEFAULT).toString(Charset.defaultCharset()) val decoded = Base64.decode(encoded.dropLast(drop), Base64.DEFAULT).toString(Charset.defaultCharset())
val path = decoded.substringBefore("||") val path = decoded.substringBefore("||")
@ -207,43 +218,35 @@ open class MangaMx : ParsedHttpSource() {
override fun imageUrlParse(document: Document) = throw Exception("Not Used") override fun imageUrlParse(document: Document) = throw Exception("Not Used")
// Filters
override fun getFilterList() = FilterList( 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(), Filter.Separator(),
SortBy("Ordenar por", sortables),
StatusFilter("Estado", statusArray), StatusFilter("Estado", statusArray),
FilterFilter("Filtro", filterArray),
TypeFilter("Tipo", typedArray), TypeFilter("Tipo", typedArray),
AdultFilter("Adulto", adultArray), GenreFilter("Géneros", genresArray)
OrderFilter("Orden", orderArray)
) )
private class StatusFilter(name: String, values: Array<Pair<String, String>>) : private class StatusFilter(name: String, values: Array<Pair<String, String>>) :
Filter.Select<String>(name, values.map { it.first }.toTypedArray()) 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>>) : private class TypeFilter(name: String, values: Array<Pair<String, String>>) :
Filter.Select<String>(name, values.map { it.first }.toTypedArray()) 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()) Filter.Select<String>(name, values.map { it.first }.toTypedArray())
private class OrderFilter(name: String, values: Array<Pair<String, String>>) : class SortBy(name: String, values: Array<Pair<String, String>>) : Filter.Sort(
Filter.Select<String>(name, values.map { it.first }.toTypedArray()) name, values.map { it.first }.toTypedArray(),
Selection(0, false)
)
private val statusArray = arrayOf( private val statusArray = arrayOf(
Pair("Estado", "false"), Pair("Estado", "false"),
Pair("En desarrollo", "1"), Pair("En desarrollo", "1"),
Pair("Completo", "0") Pair("Completo", "0")
) )
private val filterArray = arrayOf(
Pair("Visitas", "visitas"),
Pair("Recientes", "id"),
Pair("Alfabético", "nombre")
)
private val typedArray = arrayOf( private val typedArray = arrayOf(
Pair("Todo", "false"), Pair("Todo", "false"),
Pair("Mangas", "0"), Pair("Mangas", "0"),
@ -252,13 +255,104 @@ open class MangaMx : ParsedHttpSource() {
Pair("Manhuas", "3"), Pair("Manhuas", "3"),
Pair("Novelas", "4") Pair("Novelas", "4")
) )
private val adultArray = arrayOf(
Pair("Filtro adulto", "false"), private val sortables = arrayOf(
Pair("No mostrar +18", "0"), Pair("Visitas", "visitas"),
Pair("Mostrar +18", "1") 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
}
} }

View File

@ -5,7 +5,7 @@ ext {
extName = 'TuMangaOnline' extName = 'TuMangaOnline'
pkgNameSuffix = 'es.tumangaonline' pkgNameSuffix = 'es.tumangaonline'
extClass = '.TuMangaOnline' extClass = '.TuMangaOnline'
extVersionCode = 32 extVersionCode = 33
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -140,8 +140,8 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
is GenreList -> { is GenreList -> {
filter.state.forEach { genre -> filter.state.forEach { genre ->
when (genre.state) { when (genre.state) {
Filter.TriState.STATE_INCLUDE -> url.addQueryParameter("exclude_genders[]", genre.id) Filter.TriState.STATE_INCLUDE -> url.addQueryParameter("genders[]", genre.id)
Filter.TriState.STATE_EXCLUDE -> url.addQueryParameter("genders[]", genre.id) Filter.TriState.STATE_EXCLUDE -> url.addQueryParameter("exclude_genders[]", genre.id)
} }
} }
} }