From 31ae3fc43a60f164ea03a63bfc8bc2113769fa04 Mon Sep 17 00:00:00 2001
From: Solitai7e <60846439+Solitai7e@users.noreply.github.com>
Date: Mon, 31 Jul 2023 17:38:05 +0000
Subject: [PATCH] Pixiv: support partial tag matches (#17330)

---
 src/all/pixiv/build.gradle                    |  2 +-
 .../tachiyomi/extension/all/pixiv/Pixiv.kt    | 38 ++++----
 .../extension/all/pixiv/PixivFilters.kt       | 93 ++++++++-----------
 3 files changed, 61 insertions(+), 72 deletions(-)

diff --git a/src/all/pixiv/build.gradle b/src/all/pixiv/build.gradle
index 7806001fb..dba0fad2d 100644
--- a/src/all/pixiv/build.gradle
+++ b/src/all/pixiv/build.gradle
@@ -6,7 +6,7 @@ ext {
     extName = 'Pixiv'
     pkgNameSuffix = 'all.pixiv'
     extClass = '.PixivFactory'
-    extVersionCode = 6
+    extVersionCode = 7
     isNsfw = true
 }
 
diff --git a/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/Pixiv.kt b/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/Pixiv.kt
index d2631ed0d..98a92ba10 100644
--- a/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/Pixiv.kt
+++ b/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/Pixiv.kt
@@ -97,37 +97,37 @@ class Pixiv(override val lang: String) : HttpSource() {
             if (query.isNotBlank()) {
                 searchSequence = makeIllustSearchSequence(
                     word = query,
-                    order = filters.order.toSearchParameter(),
-                    mode = filters.rating.toSearchParameter(),
+                    order = filters.order,
+                    mode = filters.rating,
                     sMode = "s_tc",
-                    type = filters.type.toSearchParameter(),
-                    dateBefore = filters.dateBefore.state.ifBlank { null },
-                    dateAfter = filters.dateAfter.state.ifBlank { null },
+                    type = filters.type,
+                    dateBefore = filters.dateBefore.ifBlank { null },
+                    dateAfter = filters.dateAfter.ifBlank { null },
                 )
 
                 predicates = buildList {
-                    filters.tags.toPredicate()?.let(::add)
-                    filters.users.toPredicate()?.let(::add)
+                    filters.makeTagsPredicate()?.let(::add)
+                    filters.makeUsersPredicate()?.let(::add)
                 }
-            } else if (filters.users.state.isNotBlank()) {
+            } else if (filters.users.isNotBlank()) {
                 searchSequence = makeUserIllustSearchSequence(
-                    nick = filters.users.state,
-                    type = filters.type.toSearchParameter(),
+                    nick = filters.users,
+                    type = filters.type,
                 )
 
                 predicates = buildList {
-                    filters.tags.toPredicate()?.let(::add)
-                    filters.rating.toPredicate()?.let(::add)
+                    filters.makeTagsPredicate()?.let(::add)
+                    filters.makeRatingPredicate()?.let(::add)
                 }
             } else {
                 searchSequence = makeIllustSearchSequence(
-                    word = filters.tags.state.ifBlank { "漫画" },
-                    order = filters.order.toSearchParameter(),
-                    mode = filters.rating.toSearchParameter(),
-                    sMode = "s_tag_full",
-                    type = filters.type.toSearchParameter(),
-                    dateBefore = filters.dateBefore.state.ifBlank { null },
-                    dateAfter = filters.dateAfter.state.ifBlank { null },
+                    word = filters.tags.ifBlank { "漫画" },
+                    order = filters.order,
+                    mode = filters.rating,
+                    sMode = filters.searchMode,
+                    type = filters.type,
+                    dateBefore = filters.dateBefore.ifBlank { null },
+                    dateAfter = filters.dateAfter.ifBlank { null },
                 )
 
                 predicates = emptyList()
diff --git a/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/PixivFilters.kt b/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/PixivFilters.kt
index 45e23af66..0e14013f5 100644
--- a/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/PixivFilters.kt
+++ b/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/PixivFilters.kt
@@ -1,72 +1,61 @@
 package eu.kanade.tachiyomi.extension.all.pixiv
 import eu.kanade.tachiyomi.source.model.Filter
 
+private val TYPE_VALUES = arrayOf("All", "Illustrations", "Manga")
+private val TYPE_PARAMS = arrayOf(null, "illust", "manga")
+
+private val TAGS_MODE_VALUES = arrayOf("Partial", "Full")
+private val TAGS_MODE_PARAMS = arrayOf("s_tag", "s_tag_full")
+
+private val RATING_VALUES = arrayOf("All", "All ages", "R-18")
+private val RATING_PARAMS = arrayOf(null, "all", "r18")
+
+private val RATING_PREDICATES: Array<((PixivIllust) -> Boolean)?> =
+    arrayOf(null, { it.x_restrict == "0" }, { it.x_restrict == "1" })
+
 internal class PixivFilters : MutableList<Filter<*>> by mutableListOf() {
-    class Type : Filter.Select<String>("Type", values, 2) {
-        companion object {
-            private val values: Array<String> =
-                arrayOf("All", "Illustrations", "Manga")
+    private val typeFilter = object : Filter.Select<String>("Type", TYPE_VALUES, 2) {}.also(::add)
+    private val tagsFilter = object : Filter.Text("Tags") {}.also(::add)
+    private val tagsModeFilter = object : Filter.Select<String>("Tags mode", TAGS_MODE_VALUES, 0) {}.also(::add)
+    private val usersFilter = object : Filter.Text("Users") {}.also(::add)
+    private val ratingFilter = object : Filter.Select<String>("Rating", RATING_VALUES, 0) {}.also(::add)
 
-            private val searchParams: Array<String?> =
-                arrayOf(null, "illust", "manga")
-        }
+    init { add(Filter.Header("(the following are ignored when the users filter is in use)")) }
 
-        fun toSearchParameter(): String? = searchParams[state]
-    }
+    private val orderFilter = object : Filter.Sort("Order", arrayOf("Date posted")) {}.also(::add)
+    private val dateBeforeFilter = object : Filter.Text("Posted before") {}.also(::add)
+    private val dateAfterFilter = object : Filter.Text("Posted after") {}.also(::add)
 
-    val type = Type().also(::add)
+    val type: String? get() = TYPE_PARAMS[typeFilter.state]
 
-    class Tags : Filter.Text("Tags") {
-        fun toPredicate(): ((PixivIllust) -> Boolean)? {
-            if (state.isBlank()) return null
+    val tags: String by tagsFilter::state
+    val searchMode: String get() = TAGS_MODE_PARAMS[tagsModeFilter.state]
 
-            val tags = state.split(' ')
+    fun makeTagsPredicate(): ((PixivIllust) -> Boolean)? {
+        val tags = tags.ifBlank { return null }.split(' ')
+
+        if (tagsModeFilter.state == 0) {
+            val regex = Regex(tags.joinToString("|") { Regex.escape(it) })
+            return { it.tags?.any(regex::containsMatchIn) == true }
+        } else {
             return { it.tags?.containsAll(tags) == true }
         }
     }
 
-    val tags = Tags().also(::add)
+    val users: String by usersFilter::state
 
-    class Users : Filter.Text("Users") {
-        fun toPredicate(): ((PixivIllust) -> Boolean)? {
-            if (state.isBlank()) return null
-            val regex = Regex(state.split(' ').joinToString("|") { Regex.escape(it) })
+    fun makeUsersPredicate(): ((PixivIllust) -> Boolean)? {
+        val users = users.ifBlank { return null }
+        val regex = Regex(users.split(' ').joinToString("|") { Regex.escape(it) })
 
-            return { it.author_details?.user_name?.contains(regex) == true }
-        }
+        return { it.author_details?.user_name?.contains(regex) == true }
     }
 
-    val users = Users().also(::add)
+    val rating: String? get() = RATING_PARAMS[ratingFilter.state]
+    fun makeRatingPredicate() = RATING_PREDICATES[ratingFilter.state]
 
-    class Rating : Filter.Select<String>("Rating", values, 0) {
-        companion object {
-            private val searchParams: Array<String?> =
-                arrayOf(null, "all", "r18")
+    val order: String? get() = orderFilter.state?.ascending?.let { "date" }
 
-            private val values: Array<String> =
-                arrayOf("All", "All ages", "R-18")
-
-            private val predicates: Array<((PixivIllust) -> Boolean)?> =
-                arrayOf(null, { it.x_restrict == "0" }, { it.x_restrict == "1" })
-        }
-
-        fun toPredicate(): ((PixivIllust) -> Boolean)? = predicates[state]
-        fun toSearchParameter(): String? = searchParams[state]
-    }
-
-    val rating = Rating().also(::add)
-
-    init { add(Filter.Header("(the following are ignored when the users filter is in use)")) }
-
-    class Order : Filter.Sort("Order", arrayOf("Date posted")) {
-        fun toSearchParameter(): String? = state?.ascending?.let { "date" }
-    }
-
-    val order = Order().also(::add)
-
-    class DateBefore : Filter.Text("Posted before")
-    val dateBefore = DateBefore().also(::add)
-
-    class DateAfter : Filter.Text("Posted after")
-    val dateAfter = DateAfter().also(::add)
+    val dateBefore: String by dateBeforeFilter::state
+    val dateAfter: String by dateAfterFilter::state
 }