From 40ea248097a0a0141f512404f0594c05fae2fd45 Mon Sep 17 00:00:00 2001
From: Arraiment <76941874+Arraiment@users.noreply.github.com>
Date: Sun, 22 Aug 2021 01:32:34 +0800
Subject: [PATCH] Implement search and filters (#8706)

---
 src/id/comicfx/build.gradle                   |   3 +-
 .../tachiyomi/extension/id/comicfx/ComicFx.kt | 131 +++++++++++++++---
 2 files changed, 112 insertions(+), 22 deletions(-)

diff --git a/src/id/comicfx/build.gradle b/src/id/comicfx/build.gradle
index 49d4df83a..f35bff0cc 100644
--- a/src/id/comicfx/build.gradle
+++ b/src/id/comicfx/build.gradle
@@ -1,11 +1,12 @@
 apply plugin: 'com.android.application'
 apply plugin: 'kotlin-android'
+apply plugin: 'kotlinx-serialization'
 
 ext {
     extName = 'Comic Fx'
     pkgNameSuffix = 'id.comicfx'
     extClass = '.ComicFx'
-    extVersionCode = 2
+    extVersionCode = 3
     libVersion = '1.2'
 }
 
diff --git a/src/id/comicfx/src/eu/kanade/tachiyomi/extension/id/comicfx/ComicFx.kt b/src/id/comicfx/src/eu/kanade/tachiyomi/extension/id/comicfx/ComicFx.kt
index ebe75afe5..32253b429 100644
--- a/src/id/comicfx/src/eu/kanade/tachiyomi/extension/id/comicfx/ComicFx.kt
+++ b/src/id/comicfx/src/eu/kanade/tachiyomi/extension/id/comicfx/ComicFx.kt
@@ -1,18 +1,27 @@
 package eu.kanade.tachiyomi.extension.id.comicfx
 
 import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.asObservableSuccess
 import eu.kanade.tachiyomi.source.model.Filter
 import eu.kanade.tachiyomi.source.model.FilterList
+import eu.kanade.tachiyomi.source.model.MangasPage
 import eu.kanade.tachiyomi.source.model.Page
 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.json.Json
+import kotlinx.serialization.json.jsonArray
+import kotlinx.serialization.json.jsonObject
+import kotlinx.serialization.json.jsonPrimitive
+import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
 import okhttp3.Request
 import okhttp3.Response
 import org.jsoup.nodes.Document
 import org.jsoup.nodes.Element
-import uy.kohesive.injekt.api.get
+import rx.Observable
+import uy.kohesive.injekt.injectLazy
+import java.text.ParseException
 import java.text.SimpleDateFormat
 import java.util.Locale
 
@@ -62,14 +71,54 @@ class ComicFx : ParsedHttpSource() {
     override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
 
     // Search
+
+    override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
+        // Text search cannot use filters
+        if (query.isNotEmpty()) {
+            return client.newCall(GET("$baseUrl/search?query=$query"))
+                .asObservableSuccess()
+                .map { response ->
+                    parseSearchApiResponse(response)
+                }
+        }
+        return super.fetchSearchManga(page, query, filters)
+    }
+
+    private val json: Json by injectLazy()
+
+    private fun parseSearchApiResponse(response: Response): MangasPage {
+        val results = json.parseToJsonElement(response.body!!.string()).jsonObject["suggestions"]!!.jsonArray
+        val manga = results.map {
+            SManga.create().apply {
+                title = it.jsonObject["value"]!!.jsonPrimitive.content
+                url = "/komik/${it.jsonObject["data"]!!.jsonPrimitive.content}"
+            }
+        }
+        return MangasPage(manga, false)
+    }
+
     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
-        val filters = if (filters.isEmpty()) getFilterList() else filters
-        val genre = filters.findInstance<GenreList>()?.toUriPart()
-        val order = filters.findInstance<OrderByFilter>()?.toUriPart()
+        val filterList = if (filters.isEmpty()) getFilterList() else filters
 
-        // todo search
+        val url = "$baseUrl/filterList".toHttpUrlOrNull()!!.newBuilder()
 
-        return GET("$baseUrl/filterList?page=$page&cstatus=&ctype=&cat=$genre&alpha=&$order&author=&artist=&tag=") // filter
+        for (filter in filterList) {
+            when (filter) {
+                is GenreFilter -> url.addQueryParameter("cat", filter.toUriPart())
+                is SortFilter -> {
+                    url.addQueryParameter("sortBy", filter.toUriPart())
+                    url.addQueryParameter("asc", filter.state!!.ascending.toString())
+                }
+                is StatusFilter -> url.addQueryParameter("cstatus", filter.toUriPart())
+                is TypeFilter -> url.addQueryParameter("ctype", filter.toUriPart())
+                is AuthorFilter -> url.addQueryParameter("author", filter.state)
+                is ArtistFilter -> url.addQueryParameter("artist", filter.state)
+            }
+        }
+
+        url.addQueryParameter("page", page.toString())
+        // Unimplemented parameters: "alpha" (For filtering by alphabet) and "tag" (idk)
+        return GET(url.toString())
     }
 
     override fun searchMangaSelector() = popularMangaSelector()
@@ -105,18 +154,31 @@ class ComicFx : ParsedHttpSource() {
         val updateOn = document.select(".infokomik .infolengkap span:contains(update) b").text()
         val date = document.select(".infokomik .infolengkap span:contains(update)").text().substringAfter(updateOn)
         val checkChapter = document.select(chapterListSelector()).firstOrNull()
-        if (date != "" && checkChapter != null) chapters[0].date_upload = parseDate(date)
+        if (date != "" && checkChapter != null && chapters[0].date_upload == 0L) {
+            chapters[0].date_upload = SimpleDateFormat("dd mmm yyyy", Locale.ENGLISH).parse(date)?.time ?: 0L
+        }
 
         return chapters
     }
 
     private fun parseDate(date: String): Long {
-        return SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH).parse(date)?.time ?: 0L
+        return when (date) {
+            "hari ini" -> System.currentTimeMillis()
+            "kemarin" -> System.currentTimeMillis() - (1000 * 60 * 60 * 24) // yesterday
+            else -> {
+                try {
+                    SimpleDateFormat("dd-mm-yyyy", Locale.ENGLISH).parse(date)?.time ?: 0L
+                } catch (_: ParseException) {
+                    0L
+                }
+            }
+        }
     }
 
     override fun chapterFromElement(element: Element) = SChapter.create().apply {
         setUrlWithoutDomain(element.attr("href"))
-        name = element.text()
+        name = element.selectFirst("span.chapternum").text()
+        date_upload = parseDate(element.selectFirst("span.chapterdate").text())
     }
 
     // Pages
@@ -137,22 +199,29 @@ class ComicFx : ParsedHttpSource() {
 
     // filters
     override fun getFilterList() = FilterList(
-        OrderByFilter(),
-        GenreList()
+        SortFilter(sortList),
+        GenreFilter(),
+        StatusFilter(),
+        TypeFilter(),
+        ArtistFilter("Artist"),
+        AuthorFilter("Author")
     )
 
-    private class OrderByFilter : UriPartFilter(
-        "Sort by",
-        arrayOf(
-            Pair("sortBy=name&asc=true", "Default"),
-            Pair("sortBy=name&asc=true", "A-Z"),
-            Pair("sortBy=name&asc=false", "Z-A"),
-            Pair("sortBy=views&asc=false", "Popular to Less"),
-            Pair("sortBy=views&asc=true", "Less to Popular")
-        )
+    private class ArtistFilter(name: String) : Filter.Text(name)
+    private class AuthorFilter(name: String) : Filter.Text(name)
+
+    private class SortFilter(val sortables: List<Pair<String, String>>) : Filter.Sort("Sort", sortables.map { it.second }.toTypedArray(), Selection(1, false)) {
+        fun toUriPart(): String {
+            return sortables[this.state!!.index].first
+        }
+    }
+
+    private val sortList = listOf(
+        Pair("name", "Alphabetical"),
+        Pair("views", "Popular"),
     )
 
-    private class GenreList : UriPartFilter(
+    private class GenreFilter : UriPartFilter(
         "Select Genre",
         arrayOf(
             Pair("", "<select>"),
@@ -190,6 +259,26 @@ class ComicFx : ParsedHttpSource() {
         )
     )
 
+    private class StatusFilter : UriPartFilter(
+        "Status",
+        arrayOf(
+            Pair("", "All"),
+            Pair("1", "Ongoing"),
+            Pair("2", "Complete")
+        )
+    )
+
+    private class TypeFilter : UriPartFilter(
+        "Type",
+        arrayOf(
+            Pair("", "All"),
+            Pair("1", "Manhua (Chinese)"),
+            Pair("2", "Manhwa (Korean)"),
+            Pair("3", "Manga"),
+            Pair("4", "Oneshot"),
+        )
+    )
+
     private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
         Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray()) {
         fun toUriPart() = vals[state].first