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