From 7c5fd399b407f2ba1491c36c048e1caf3d30f2ce Mon Sep 17 00:00:00 2001
From: seew3l <90949336+seew3l@users.noreply.github.com>
Date: Mon, 13 Mar 2023 09:37:45 -0500
Subject: [PATCH] MangaOni: Fix global search (#15688)

* Added new filter and removed DTO

* Fix status

* Minor changes
---
 src/es/mangamx/build.gradle                   |   2 +-
 .../extension/es/mangamx/MangaOni.kt          | 120 +++++++++---------
 .../extension/es/mangamx/ResponseDto.kt       |  44 -------
 3 files changed, 59 insertions(+), 107 deletions(-)
 delete mode 100644 src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/ResponseDto.kt

diff --git a/src/es/mangamx/build.gradle b/src/es/mangamx/build.gradle
index b6c79582c..fb9d78ae2 100644
--- a/src/es/mangamx/build.gradle
+++ b/src/es/mangamx/build.gradle
@@ -6,7 +6,7 @@ ext {
     extName = 'MangaOni'
     pkgNameSuffix = 'es.mangamx'
     extClass = '.MangaOni'
-    extVersionCode = 14
+    extVersionCode = 15
     isNsfw = true
 }
 
diff --git a/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaOni.kt b/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaOni.kt
index f704ac5eb..4d13928e3 100644
--- a/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaOni.kt
+++ b/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaOni.kt
@@ -5,7 +5,6 @@ import android.content.SharedPreferences
 import android.net.Uri
 import android.util.Base64
 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
@@ -15,16 +14,12 @@ 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 kotlinx.serialization.decodeFromString
-import kotlinx.serialization.json.Json
-import okhttp3.FormBody
 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 uy.kohesive.injekt.injectLazy
 import java.nio.charset.Charset
 import java.text.SimpleDateFormat
 import java.util.Locale
@@ -43,16 +38,12 @@ open class MangaOni : ConfigurableSource, ParsedHttpSource() {
 
     override val client = network.cloudflareClient
 
-    private var csrfToken = ""
-
-    private val json: Json by injectLazy()
-
     override fun popularMangaRequest(page: Int) = GET(
         url = "$baseUrl/directorio?genero=false&estado=false&filtro=visitas&tipo=false&adulto=${if (hideNSFWContent()) "0" else "false"}&orden=desc&p=$page",
         headers = headers,
     )
 
-    override fun popularMangaNextPageSelector() = ".page-item a[rel=next]"
+    override fun popularMangaNextPageSelector() = "ul.pagination a[rel=next]"
 
     override fun popularMangaSelector() = "#article-div a"
 
@@ -64,7 +55,6 @@ open class MangaOni : ConfigurableSource, ParsedHttpSource() {
 
     override fun popularMangaParse(response: Response): MangasPage {
         val document = response.asJsoup()
-        csrfToken = document.select("meta[name=csrf-token]").attr("content")
 
         val mangas = document.select(popularMangaSelector()).map { element ->
             popularMangaFromElement(element)
@@ -92,18 +82,12 @@ open class MangaOni : ConfigurableSource, ParsedHttpSource() {
     }
 
     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+        val uri = Uri.parse("$baseUrl/${if (query.isNotBlank()) "buscar" else "directorio"}").buildUpon()
+
         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(url = "$baseUrl/buscar", headers = searchHeaders, body = formBody)
+            uri.appendQueryParameter("q", query)
         } else {
-            val uri = Uri.parse("$baseUrl/directorio").buildUpon()
-            uri.appendQueryParameter("adulto", if (hideNSFWContent()) { "0" } else { "1" })
+            uri.appendQueryParameter("adulto", if (hideNSFWContent()) { "0" } else { "false" })
 
             for (filter in filters) {
                 when (filter) {
@@ -126,52 +110,46 @@ open class MangaOni : ConfigurableSource, ParsedHttpSource() {
                         "genero",
                         genresArray[filter.state].second,
                     )
+                    is AdultContentFilter -> uri.appendQueryParameter(
+                        "adulto",
+                        adultContentArray[filter.state].second,
+                    )
                     else -> {}
                 }
             }
-            uri.appendQueryParameter("p", page.toString())
-            return GET(uri.toString(), headers)
         }
+        uri.appendQueryParameter("p", page.toString())
+        return GET(uri.toString(), headers)
     }
 
     override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector()
 
-    override fun searchMangaSelector(): String = popularMangaSelector()
+    override fun searchMangaSelector(): String = "#article-div > div"
 
-    override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
+    override fun searchMangaFromElement(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"))
+        }
+    }
 
     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)
-            }
+        val document = response.asJsoup()
 
-            val hasNextPage = searchMangaNextPageSelector()?.let { selector ->
-                document.select(selector).first()
-            } != null
-
-            return MangasPage(mangas, hasNextPage)
+        val mangas = if (document.location().startsWith("$baseUrl/directorio")) {
+            document.select(popularMangaSelector()).map { popularMangaFromElement(it) }
         } else {
-            val jsonString = response.body.string()
-            val result = json.decodeFromString<ResponseDto>(jsonString)
-
-            if (result.mangaList.isEmpty()) {
-                return MangasPage(emptyList(), hasNextPage = false)
-            }
-            val mangaList = result.mangaList
-                .map(::searchMangaFromObject)
-
-            return MangasPage(mangaList, hasNextPage = false)
+            document.select(searchMangaSelector()).map { searchMangaFromElement(it) }
         }
-    }
 
-    private fun searchMangaFromObject(manga: MangaDto): SManga = SManga.create().apply {
-        title = manga.name
-        thumbnail_url = manga.img.replace("/thumb", "/cover")
-        setUrlWithoutDomain(manga.url)
+        val hasNextPage = searchMangaNextPageSelector()?.let { selector ->
+            document.select(selector).first()
+        } != null
+
+        return MangasPage(mangas, hasNextPage)
     }
 
     override fun mangaDetailsParse(document: Document): SManga {
@@ -187,9 +165,9 @@ open class MangaOni : ConfigurableSource, ParsedHttpSource() {
         }
         manga.artist = manga.author
         manga.genre = document.select("div#categ a").joinToString(", ") { it.text() }
-        manga.status = when (document.select("span#desarrollo").first()?.text()) {
+        manga.status = when (document.select("strong:contains(Estado) + span").first()?.text()) {
             "En desarrollo" -> SManga.ONGOING
-            // "Completed" -> SManga.COMPLETED
+            "Finalizado" -> SManga.COMPLETED
             else -> SManga.UNKNOWN
         }
         return manga
@@ -222,14 +200,23 @@ open class MangaOni : ConfigurableSource, ParsedHttpSource() {
 
     override fun imageUrlParse(document: Document) = throw Exception("Not Used")
 
-    override fun getFilterList() = FilterList(
-        Filter.Header("NOTA: Se ignoran si se usa el buscador"),
-        Filter.Separator(),
-        SortBy("Ordenar por", sortables),
-        StatusFilter("Estado", statusArray),
-        TypeFilter("Tipo", typedArray),
-        GenreFilter("Géneros", genresArray),
-    )
+    override fun getFilterList(): FilterList {
+        val filterList = mutableListOf(
+            Filter.Header("NOTA: Se ignoran si se usa el buscador"),
+            Filter.Separator(),
+            SortBy("Ordenar por", sortables),
+            StatusFilter("Estado", statusArray),
+            TypeFilter("Tipo", typedArray),
+            GenreFilter("Géneros", genresArray),
+        )
+
+        if (!hideNSFWContent()) {
+            filterList.add(
+                AdultContentFilter("Contenido +18", adultContentArray),
+            )
+        }
+        return FilterList(filterList)
+    }
 
     private class StatusFilter(name: String, values: Array<Pair<String, String>>) :
         Filter.Select<String>(name, values.map { it.first }.toTypedArray())
@@ -240,6 +227,9 @@ open class MangaOni : ConfigurableSource, ParsedHttpSource() {
     private class GenreFilter(name: String, values: Array<Pair<String, String>>) :
         Filter.Select<String>(name, values.map { it.first }.toTypedArray())
 
+    private class AdultContentFilter(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(),
@@ -267,9 +257,15 @@ open class MangaOni : ConfigurableSource, ParsedHttpSource() {
         Pair("Alfabético", "nombre"),
     )
 
+    private val adultContentArray = arrayOf(
+        Pair("Mostrar todo", "false"),
+        Pair("Mostrar solo +18", "1"),
+        Pair("No mostrar +18", "0"),
+    )
+
     /**
      * Url: https://manga-mx.com/directorio/
-     * Last check: 27/03/2021
+     * Last check: 12/03/2023
      * JS script: Array.from(document.querySelectorAll('select[name="genero"] option'))
      * .map(a => `Pair("${a.innerText}", "${a.value}")`).join(',\n')
      */
@@ -339,7 +335,7 @@ open class MangaOni : ConfigurableSource, ParsedHttpSource() {
     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_SUMMARY = "Ocultar el contenido erótico en mangas populares y filtros, no funciona en los mangas recientes ni búsquedas textuales."
         private const val CONTENT_PREF_DEFAULT_VALUE = false
     }
 }
diff --git a/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/ResponseDto.kt b/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/ResponseDto.kt
deleted file mode 100644
index 420c845c0..000000000
--- a/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/ResponseDto.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-package eu.kanade.tachiyomi.extension.es.mangamx
-
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-
-@Serializable
-data class ResponseDto(
-    @SerialName("mangas") val mangaList: List<MangaDto> = emptyList(),
-    @SerialName("usuarios") val usersList: List<UserDto>? = emptyList(),
-    @SerialName("grupos") val groupsList: List<GroupDto>? = emptyList(),
-)
-
-@Serializable
-data class MangaDto(
-    @SerialName("nombre") val name: String = "",
-    @SerialName("alterno") val alternative: String? = "",
-    @SerialName("tipo") val type: Int = 0,
-    @SerialName("lanzamiento") val releaseYear: Int = 0,
-    @SerialName("autor") val author: String? = "",
-    val visible: Int = 0,
-    val cover: String? = "",
-    val slug: String = "",
-    val url: String = "",
-    val img: String = "",
-)
-
-@Serializable
-data class UserDto(
-    @SerialName("usuario") val username: String = "",
-    @SerialName("perfil") val profile: String? = "",
-    @SerialName("genero") val gender: String? = "",
-    val id: Int? = 0,
-    val url: String = "",
-    val img: String = "",
-)
-
-@Serializable
-data class GroupDto(
-    @SerialName("nombre") val name: String = "",
-    val id: Int? = 0,
-    val cover: String? = "",
-    val url: String = "",
-    val img: String = "",
-)