From 9637963a6c669f742838595456411b4d277dbd05 Mon Sep 17 00:00:00 2001 From: "Cuong M. Tran" Date: Fri, 29 Mar 2024 14:01:07 +0700 Subject: [PATCH] VyvyManga: add filters & update domain (#2150) * Vyvymanga: implemented new filters for advanced search # Conflicts: # src/en/vyvymanga/src/eu/kanade/tachiyomi/extension/en/vyvymanga/VyvyManga.kt * Move filters to separated file * Fetch online genres * Update domain, bump version * revert unnecessary change --------- Co-authored-by: Ota Takushima --- src/en/vyvymanga/build.gradle | 2 +- .../extension/en/vyvymanga/VyvyManga.kt | 36 +++++- .../en/vyvymanga/VyvyMangaFilters.kt | 108 ++++++++++++++++++ 3 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 src/en/vyvymanga/src/eu/kanade/tachiyomi/extension/en/vyvymanga/VyvyMangaFilters.kt diff --git a/src/en/vyvymanga/build.gradle b/src/en/vyvymanga/build.gradle index 2965c0540..5b28fc048 100644 --- a/src/en/vyvymanga/build.gradle +++ b/src/en/vyvymanga/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'VyvyManga' extClass = '.VyvyManga' - extVersionCode = 4 + extVersionCode = 5 isNsfw = true } diff --git a/src/en/vyvymanga/src/eu/kanade/tachiyomi/extension/en/vyvymanga/VyvyManga.kt b/src/en/vyvymanga/src/eu/kanade/tachiyomi/extension/en/vyvymanga/VyvyManga.kt index d04762b11..6841c0e61 100644 --- a/src/en/vyvymanga/src/eu/kanade/tachiyomi/extension/en/vyvymanga/VyvyManga.kt +++ b/src/en/vyvymanga/src/eu/kanade/tachiyomi/extension/en/vyvymanga/VyvyManga.kt @@ -18,7 +18,7 @@ import java.util.Locale class VyvyManga : ParsedHttpSource() { override val name = "VyvyManga" - override val baseUrl = "https://vyvymanga.net" + override val baseUrl = "https://vymanga.net" override val lang = "en" @@ -44,7 +44,23 @@ class VyvyManga : ParsedHttpSource() { val url = "$baseUrl/search".toHttpUrl().newBuilder() .addQueryParameter("q", query) .addQueryParameter("page", page.toString()) - + (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> + when (filter) { + is SearchType -> url.addQueryParameter("search_po", filter.selected) + is SearchDescription -> if (filter.state) url.addQueryParameter("check_search_desc", "1") + is AuthorSearchType -> url.addQueryParameter("author_po", filter.selected) + is AuthorFilter -> url.addQueryParameter("author", filter.state) + is StatusFilter -> url.addQueryParameter("completed", filter.selected) + is SortFilter -> url.addQueryParameter("sort", filter.selected) + is SortType -> url.addQueryParameter("sort_type", filter.selected) + is GenreFilter -> { + filter.state.forEach { + if (!it.isIgnored()) url.addQueryParameter(if (it.isIncluded()) "genre[]" else "exclude_genre[]", it.id) + } + } + else -> {} + } + } return GET(url.build(), headers) } @@ -68,7 +84,7 @@ class VyvyManga : ParsedHttpSource() { override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element) - override fun latestUpdatesNextPageSelector(): String? = + override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() // Details @@ -143,4 +159,18 @@ class VyvyManga : ParsedHttpSource() { else -> 0 } } + + override fun getFilterList(): FilterList { + launchIO { fetchGenres(baseUrl, headers, client) } + return FilterList( + SearchType(), + SearchDescription(), + AuthorSearchType(), + AuthorFilter(), + StatusFilter(), + SortFilter(), + SortType(), + GenreFilter(), + ) + } } diff --git a/src/en/vyvymanga/src/eu/kanade/tachiyomi/extension/en/vyvymanga/VyvyMangaFilters.kt b/src/en/vyvymanga/src/eu/kanade/tachiyomi/extension/en/vyvymanga/VyvyMangaFilters.kt new file mode 100644 index 000000000..4785fc530 --- /dev/null +++ b/src/en/vyvymanga/src/eu/kanade/tachiyomi/extension/en/vyvymanga/VyvyMangaFilters.kt @@ -0,0 +1,108 @@ +package eu.kanade.tachiyomi.extension.en.vyvymanga + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.jsoup.nodes.Document + +abstract class SelectFilter(displayName: String, private val options: Array>) : + Filter.Select( + displayName, + options.map { it.first }.toTypedArray(), + ) { + open val selected get() = options[state].second.takeUnless { it.isEmpty() } +} + +class SearchType : SelectFilter( + "Title should contain/begin/end with typed text", + arrayOf( + Pair("Contain", "0"), + Pair("Begin", "1"), + Pair("End", "2"), + ), +) + +class SearchDescription : Filter.CheckBox("Search In Description") + +class AuthorSearchType : SelectFilter( + "Author should contain/begin/end with typed text", + arrayOf( + Pair("Contain", "0"), + Pair("Begin", "1"), + Pair("End", "2"), + ), +) + +class AuthorFilter : Filter.Text("Author") + +class StatusFilter : SelectFilter( + "Status", + arrayOf( + Pair("All", "2"), + Pair("Ongoing", "0"), + Pair("Completed", "1"), + ), +) + +class SortFilter : SelectFilter( + "Sort by", + arrayOf( + Pair("Viewed", "viewed"), + Pair("Scored", "scored"), + Pair("Newest", "created_at"), + Pair("Latest Update", "updated_at"), + ), +) + +class SortType : SelectFilter( + "Sort order", + arrayOf( + Pair("Descending", "desc"), + Pair("Ascending", "asc"), + ), +) + +class Genre(name: String, val id: String) : Filter.TriState(name) + +class GenreFilter : Filter.Group("Genre", genrePairs.map { Genre(it.name, it.id) }) + +private var genrePairs: List = emptyList() + +private val scope = CoroutineScope(Dispatchers.IO) + +fun launchIO(block: () -> Unit) = scope.launch { block() } + +private var fetchGenresAttempts: Int = 0 + +fun fetchGenres(baseUrl: String, headers: okhttp3.Headers, client: okhttp3.OkHttpClient) { + if (fetchGenresAttempts < 3 && genrePairs.isEmpty()) { + try { + genrePairs = + client.newCall(genresRequest(baseUrl, headers)).execute() + .asJsoup() + .let(::parseGenres) + } catch (_: Exception) { + } finally { + fetchGenresAttempts++ + } + } +} + +private fun genresRequest(baseUrl: String, headers: okhttp3.Headers) = GET("$baseUrl/search", headers) + +private const val genresSelector = ".check-genre div div:has(.checkbox-genre)" + +private fun parseGenres(document: Document): List { + val items = document.select(genresSelector) + return buildList(items.size) { + items.mapTo(this) { + Genre( + it.select("label").text(), + it.select(".checkbox-genre").attr("data-value"), + ) + } + } +}