feat(Scout): add authorType filter
whew, the code is getting quite big... A refactor will be coming soon Some light linting was also done here
This commit is contained in:
parent
302ea0094a
commit
e4ab9f936d
12 changed files with 347 additions and 37 deletions
|
|
@ -9,6 +9,7 @@ object FilterTypeExtension {
|
|||
lateinit var DURING: FilterType
|
||||
lateinit var AFTER: FilterType
|
||||
lateinit var EXCLUDE: FilterType
|
||||
lateinit var AUTHOR_TYPE: FilterType
|
||||
lateinit var dates: Array<FilterType>
|
||||
lateinit var filters: Array<FilterType>
|
||||
lateinit var values: Array<FilterType>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,17 @@
|
|||
@file:Suppress("EnumValuesSoftDeprecate", "CanConvertToMultiDollarString")
|
||||
|
||||
/**
|
||||
* Hi to anyone who might be reading this; I am sorry for the atrocious code in this plugin
|
||||
* but I promise I'll be fixing it up soon :3
|
||||
*/
|
||||
|
||||
package moe.lava.awoocord.scout
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import com.aliucord.Utils
|
||||
|
|
@ -27,6 +36,7 @@ import com.discord.api.channel.Channel
|
|||
import com.discord.api.channel.ChannelUtils
|
||||
import com.discord.api.channel.`ChannelUtils$getSortByNameAndType$1`
|
||||
import com.discord.api.permission.Permission
|
||||
import com.discord.databinding.WidgetSearchSuggestionItemHeaderBinding
|
||||
import com.discord.databinding.WidgetSearchSuggestionsItemHasBinding
|
||||
import com.discord.databinding.WidgetSearchSuggestionsItemSuggestionBinding
|
||||
import com.discord.models.member.GuildMember
|
||||
|
|
@ -71,6 +81,10 @@ import com.franmontiel.persistentcookiejar.cache.SetCookieCache
|
|||
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor
|
||||
import com.lytefast.flexinput.R
|
||||
import moe.lava.awoocord.scout.api.SearchAPIInterface
|
||||
import moe.lava.awoocord.scout.entries.AuthorTypeSuggestion
|
||||
import moe.lava.awoocord.scout.entries.AuthorTypeViewHolder
|
||||
import moe.lava.awoocord.scout.parsing.AuthorType
|
||||
import moe.lava.awoocord.scout.parsing.AuthorTypeNode
|
||||
import moe.lava.awoocord.scout.parsing.DateNode
|
||||
import moe.lava.awoocord.scout.parsing.SimpleParserRule
|
||||
import moe.lava.awoocord.scout.parsing.SortNode
|
||||
|
|
@ -84,7 +98,10 @@ import b.a.k.b as FormatUtils
|
|||
private val WidgetSearchSuggestionsAdapter.FilterViewHolder.binding
|
||||
by accessField<WidgetSearchSuggestionsItemSuggestionBinding>()
|
||||
|
||||
@AliucordPlugin()
|
||||
private val WidgetSearchSuggestionsAdapter.HeaderViewHolder.binding
|
||||
by accessField<WidgetSearchSuggestionItemHeaderBinding>()
|
||||
|
||||
@AliucordPlugin
|
||||
@Suppress("unused", "unchecked_cast")
|
||||
class Scout : Plugin() {
|
||||
lateinit var scoutRes: ScoutResource
|
||||
|
|
@ -107,6 +124,7 @@ class Scout : Plugin() {
|
|||
override fun start(context: Context) {
|
||||
extendFilterType()
|
||||
extendHasAnswerOption()
|
||||
extendSuggestionCategory()
|
||||
fixFiltersKeying()
|
||||
fixHasFilterSuggestion()
|
||||
fixSearchPadding()
|
||||
|
|
@ -120,9 +138,10 @@ class Scout : Plugin() {
|
|||
}
|
||||
|
||||
override fun stop(context: Context) {
|
||||
patcher.unpatchAll()
|
||||
resetFilterType()
|
||||
resetHasAnswerOption()
|
||||
patcher.unpatchAll()
|
||||
resetSuggestionCategory()
|
||||
}
|
||||
|
||||
// Creates a new custom search API implementation, for the extra `min_id` param in search queries
|
||||
|
|
@ -148,7 +167,7 @@ class Scout : Plugin() {
|
|||
|
||||
private var origFilterTypes: Array<FilterType>? = null
|
||||
// Creates new pseudo-values of the `FilterType` enum for date filters
|
||||
@Suppress("LocalVariableName")
|
||||
@Suppress("LocalVariableName", "AssignedValueIsNeverRead")
|
||||
private fun extendFilterType() {
|
||||
val cls = FilterType::class.java
|
||||
val constructor = cls.declaredConstructors[0]
|
||||
|
|
@ -163,18 +182,20 @@ class Scout : Plugin() {
|
|||
val EXPAND = constructor.newInstance("EXPAND", nextIdx++) as FilterType
|
||||
val SORT = constructor.newInstance("SORT", nextIdx++) as FilterType
|
||||
val EXCLUDE = constructor.newInstance("EXCLUDE", nextIdx++) as FilterType
|
||||
val AUTHOR_TYPE = constructor.newInstance("AUTHOR_TYPE", nextIdx++) as FilterType
|
||||
val BEFORE = constructor.newInstance("BEFORE", nextIdx++) as FilterType
|
||||
val DURING = constructor.newInstance("DURING", nextIdx++) as FilterType
|
||||
val AFTER = constructor.newInstance("AFTER", nextIdx++) as FilterType
|
||||
FilterTypeExtension.EXPAND = EXPAND
|
||||
FilterTypeExtension.SORT = SORT
|
||||
FilterTypeExtension.EXCLUDE = EXCLUDE
|
||||
FilterTypeExtension.AUTHOR_TYPE = AUTHOR_TYPE
|
||||
FilterTypeExtension.BEFORE = BEFORE
|
||||
FilterTypeExtension.DURING = DURING
|
||||
FilterTypeExtension.AFTER = AFTER
|
||||
FilterTypeExtension.dates = arrayOf(BEFORE, DURING, AFTER)
|
||||
FilterTypeExtension.values = arrayOf(EXPAND, SORT, EXCLUDE, BEFORE, DURING, AFTER)
|
||||
FilterTypeExtension.filters = arrayOf(SORT, EXCLUDE, BEFORE, DURING, AFTER)
|
||||
FilterTypeExtension.filters = arrayOf(SORT, AUTHOR_TYPE, EXCLUDE) + FilterTypeExtension.dates
|
||||
FilterTypeExtension.values = arrayOf(EXPAND) + FilterTypeExtension.filters
|
||||
|
||||
val newValues = values.toMutableList()
|
||||
newValues.addAll(FilterTypeExtension.values)
|
||||
|
|
@ -194,7 +215,7 @@ class Scout : Plugin() {
|
|||
|
||||
private var origHasAnswerOptions: Array<HasAnswerOption>? = null
|
||||
// Creates new pseudo-values of the `HasAnswerOption` enum for poll and forwarded filters
|
||||
@Suppress("LocalVariableName")
|
||||
@Suppress("LocalVariableName", "AssignedValueIsNeverRead")
|
||||
private fun extendHasAnswerOption() {
|
||||
val cls = HasAnswerOption::class.java
|
||||
val constructor = cls.declaredConstructors[0]
|
||||
|
|
@ -207,7 +228,7 @@ class Scout : Plugin() {
|
|||
var nextIdx = values.size
|
||||
|
||||
val POLL = constructor.newInstance("POLL", nextIdx++, "poll") as HasAnswerOption
|
||||
val SNAPSHOT = constructor.newInstance("SNAPSHOT", nextIdx, "snapshot") as HasAnswerOption
|
||||
val SNAPSHOT = constructor.newInstance("SNAPSHOT", nextIdx++, "snapshot") as HasAnswerOption
|
||||
HasAnswerOptionExtension.POLL = POLL
|
||||
HasAnswerOptionExtension.SNAPSHOT = SNAPSHOT
|
||||
HasAnswerOptionExtension.values = arrayOf(POLL, SNAPSHOT)
|
||||
|
|
@ -228,6 +249,40 @@ class Scout : Plugin() {
|
|||
origHasAnswerOptions = null
|
||||
}
|
||||
|
||||
private var origSuggestionCategories: Array<SearchSuggestion.Category>? = null
|
||||
// Creates new pseudo-values of the suggestion categories to add correct headers
|
||||
@Suppress("LocalVariableName", "AssignedValueIsNeverRead")
|
||||
private fun extendSuggestionCategory() {
|
||||
val cls = SearchSuggestion.Category::class.java
|
||||
val constructor = cls.declaredConstructors[0]
|
||||
constructor.isAccessible = true
|
||||
|
||||
val field = cls.getDeclaredField("\$VALUES")
|
||||
field.isAccessible = true
|
||||
val values = field.get(null) as Array<SearchSuggestion.Category>
|
||||
origSuggestionCategories = origSuggestionCategories ?: values
|
||||
var nextIdx = values.size
|
||||
|
||||
val AUTHOR_TYPE = constructor.newInstance("AUTHOR_TYPE", nextIdx++) as SearchSuggestion.Category
|
||||
SuggestionCategoryExtension.AUTHOR_TYPE = AUTHOR_TYPE
|
||||
SuggestionCategoryExtension.values = arrayOf(AUTHOR_TYPE)
|
||||
|
||||
val newValues = values.toMutableList()
|
||||
newValues.addAll(SuggestionCategoryExtension.values)
|
||||
field.set(null, newValues.toTypedArray())
|
||||
}
|
||||
|
||||
private fun resetSuggestionCategory() {
|
||||
if (origSuggestionCategories == null)
|
||||
return logger.error("No unpatched suggestion categories?", null)
|
||||
|
||||
val cls = SearchSuggestion.Category::class.java
|
||||
val field = cls.getDeclaredField("\$VALUES")
|
||||
field.isAccessible = true
|
||||
field.set(null, origSuggestionCategories)
|
||||
origSuggestionCategories = null
|
||||
}
|
||||
|
||||
// Patch to key filters properly for smoother recycling
|
||||
// Thank u discord for keying every filter type the same thing!! /s
|
||||
private fun fixFiltersKeying() {
|
||||
|
|
@ -341,24 +396,22 @@ class Scout : Plugin() {
|
|||
CharSequence::class.java,
|
||||
FilterType::class.java,
|
||||
SearchStringProvider::class.java,
|
||||
) { param ->
|
||||
val query = param.args[0] as CharSequence
|
||||
val filterType = param.args[1] as FilterType
|
||||
val ossProvider = param.args[2] as SearchStringProvider
|
||||
|
||||
if (filterType != FilterType.HAS && filterType != FilterTypeExtension.EXCLUDE)
|
||||
return@instead listOf<Any>()
|
||||
|
||||
val res = mutableListOf<HasSuggestion>()
|
||||
for (opt in HasAnswerOption.values()) {
|
||||
val filterText = opt.getLocalizedInputText(ossProvider)
|
||||
|
||||
if (filterText.contains(query))
|
||||
res.add(HasSuggestion(opt))
|
||||
}
|
||||
res.toList()
|
||||
) { (_, query: CharSequence, type: FilterType, provider: SearchStringProvider) ->
|
||||
// Generate entries for author type
|
||||
if (type == FilterTypeExtension.AUTHOR_TYPE) {
|
||||
return@instead AuthorType.values()
|
||||
.filter { it.value.contains(query) }
|
||||
.map { AuthorTypeSuggestion(it) }
|
||||
}
|
||||
|
||||
// Generate entries for has options, including new ones
|
||||
if (type == FilterType.HAS || type == FilterTypeExtension.EXCLUDE)
|
||||
return@instead HasAnswerOption.values()
|
||||
.filter { it.getLocalizedInputText(provider).contains(query) }
|
||||
.map { HasSuggestion(it) }
|
||||
|
||||
listOf<Any>()
|
||||
}
|
||||
}
|
||||
|
||||
// Patching HasNode related methods for our exclude: filter type
|
||||
|
|
@ -384,9 +437,9 @@ class Scout : Plugin() {
|
|||
val opt = field.get(this) as HasAnswerOption
|
||||
|
||||
if (filterType == FilterType.HAS)
|
||||
builder.appendParam("has", opt.restParamValue);
|
||||
builder.appendParam("has", opt.restParamValue)
|
||||
else if (filterType == FilterTypeExtension.EXCLUDE)
|
||||
builder.appendParam("has", "-" + opt.restParamValue);
|
||||
builder.appendParam("has", "-" + opt.restParamValue)
|
||||
}
|
||||
|
||||
// Patching the behaviour when the has suggestion is clicked
|
||||
|
|
@ -416,8 +469,6 @@ class Scout : Plugin() {
|
|||
)
|
||||
getAnswerReplacementStart.isAccessible = true
|
||||
|
||||
logger.info(query.joinToString("|") { it.text })
|
||||
|
||||
val replacementIdx = getAnswerReplacementStart.invoke(this, query) as Int
|
||||
val previousFilterText = query[replacementIdx]
|
||||
val filterNode = if (previousFilterText.text == ssProvider.excludeFilterString)
|
||||
|
|
@ -441,6 +492,7 @@ class Scout : Plugin() {
|
|||
var minID = params["min_id"]
|
||||
var maxID = params["max_id"]
|
||||
val sortOrder = params["sort_order"]
|
||||
val authorType = params["author_type"]
|
||||
self.`$oldestMessageId`?.let {
|
||||
if (sortOrder?.getOrNull(0) == "asc")
|
||||
minID = listOf(it.toString())
|
||||
|
|
@ -461,7 +513,8 @@ class Scout : Plugin() {
|
|||
retryAttempts,
|
||||
self.`$searchQuery`.includeNsfw,
|
||||
listOf("timestamp"),
|
||||
sortOrder
|
||||
sortOrder,
|
||||
authorType,
|
||||
)
|
||||
else
|
||||
searchApi.searchChannelMessages(
|
||||
|
|
@ -475,7 +528,8 @@ class Scout : Plugin() {
|
|||
retryAttempts,
|
||||
self.`$searchQuery`.includeNsfw,
|
||||
listOf("timestamp"),
|
||||
sortOrder
|
||||
sortOrder,
|
||||
authorType,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -495,6 +549,8 @@ class Scout : Plugin() {
|
|||
DateNode.getDateRule(),
|
||||
SortNode.getFilterRule(ssProvider.sortFilterString),
|
||||
SortNode.getSortRule(ssProvider),
|
||||
AuthorTypeNode.getFilterRule(ssProvider.authorTypeFilter),
|
||||
AuthorTypeNode.getAuthorTypesRule(),
|
||||
SimpleParserRule(Pattern.compile("^\\s*?${ssProvider.excludeFilterString}:", 64)) { _, _, obj ->
|
||||
ParseSpec(FilterNode(FilterTypeExtension.EXCLUDE, ssProvider.excludeFilterString), obj)
|
||||
}
|
||||
|
|
@ -503,6 +559,7 @@ class Scout : Plugin() {
|
|||
}
|
||||
|
||||
// This is probably the worst bit of this plugin
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun patchSearchUI(context: Context) {
|
||||
// Run when a filter suggestion is clicked
|
||||
// Most of the code is copied from its implementation
|
||||
|
|
@ -515,7 +572,7 @@ class Scout : Plugin() {
|
|||
) { param ->
|
||||
val filter = param.args[0] as FilterType
|
||||
if (filter !in FilterTypeExtension.values)
|
||||
return@before; // Exit if not an extended filter type
|
||||
return@before // Exit if not an extended filter type
|
||||
|
||||
val replaceAndPublish = StoreSearchInput::class.java.getDeclaredMethod(
|
||||
"replaceAndPublish",
|
||||
|
|
@ -549,7 +606,7 @@ class Scout : Plugin() {
|
|||
getAnswerReplacementStart.invoke(this, list),
|
||||
listOf(filterNode, DateNode(it)),
|
||||
list
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -558,14 +615,21 @@ class Scout : Plugin() {
|
|||
lastIndex,
|
||||
listOf(filterNode, SortNode(ssProvider.sortOldString)),
|
||||
list
|
||||
);
|
||||
)
|
||||
|
||||
if (filter == FilterTypeExtension.EXCLUDE)
|
||||
replaceAndPublish.invoke(this,
|
||||
lastIndex,
|
||||
listOf(filterNode),
|
||||
list
|
||||
);
|
||||
)
|
||||
|
||||
if (filter == FilterTypeExtension.AUTHOR_TYPE)
|
||||
replaceAndPublish.invoke(this,
|
||||
lastIndex,
|
||||
listOf(filterNode),
|
||||
list
|
||||
)
|
||||
|
||||
param.result = null
|
||||
}
|
||||
|
|
@ -584,6 +648,7 @@ class Scout : Plugin() {
|
|||
FilterTypeExtension.AFTER -> false to scoutRes.getDrawableId("baseline_update_24")
|
||||
FilterTypeExtension.SORT -> true to R.e.ic_sort_white_24dp
|
||||
FilterTypeExtension.EXCLUDE -> false to scoutRes.getDrawableId("baseline_do_disturb_on_24")
|
||||
FilterTypeExtension.AUTHOR_TYPE -> true to R.e.ic_members_24dp
|
||||
else -> false to null
|
||||
}
|
||||
|
||||
|
|
@ -605,6 +670,8 @@ class Scout : Plugin() {
|
|||
param.result = ScoutResource.SORT_ANSWER
|
||||
if (type == FilterTypeExtension.EXCLUDE)
|
||||
param.result = ssProvider.getIdentifier("search_answer_has")
|
||||
if (type == FilterTypeExtension.AUTHOR_TYPE)
|
||||
param.result = ScoutResource.AUTHOR_TYPE_ANSWER
|
||||
}
|
||||
|
||||
// Patch for retrieving filter name
|
||||
|
|
@ -619,6 +686,7 @@ class Scout : Plugin() {
|
|||
FilterTypeExtension.DURING -> ssProvider.getIdentifier("search_filter_during")
|
||||
FilterTypeExtension.AFTER -> ssProvider.getIdentifier("search_filter_after")
|
||||
FilterTypeExtension.SORT -> ScoutResource.SORT_FILTER
|
||||
FilterTypeExtension.AUTHOR_TYPE -> ScoutResource.AUTHOR_TYPE_FILTER
|
||||
else -> null
|
||||
}
|
||||
res?.let { param.result = it }
|
||||
|
|
@ -641,6 +709,8 @@ class Scout : Plugin() {
|
|||
ScoutResource.SORT_FILTER -> ssProvider.sortFilterString
|
||||
ScoutResource.SORT_ANSWER -> ssProvider.sortOldString
|
||||
ScoutResource.EXCLUDE_FILTER -> ssProvider.excludeFilterString
|
||||
ScoutResource.AUTHOR_TYPE_FILTER -> ssProvider.authorTypeFilter
|
||||
ScoutResource.AUTHOR_TYPE_ANSWER -> ssProvider.authorTypeAnswer
|
||||
else -> null
|
||||
}
|
||||
override?.let {
|
||||
|
|
@ -700,6 +770,56 @@ class Scout : Plugin() {
|
|||
}
|
||||
param.result = res.toList()
|
||||
}
|
||||
|
||||
// Patch to add header for new categories
|
||||
patcher.before<WidgetSearchSuggestionsAdapter.HeaderViewHolder>(
|
||||
"onConfigure",
|
||||
Int::class.javaPrimitiveType!!,
|
||||
MGRecyclerDataPayload::class.java,
|
||||
) { (param, _: Int, payload: SingleTypePayload<SearchSuggestion.Category>) ->
|
||||
val category = payload.data
|
||||
if (category == SuggestionCategoryExtension.AUTHOR_TYPE) {
|
||||
binding.b.text = "Author Type"
|
||||
param.result = null
|
||||
}
|
||||
}
|
||||
|
||||
// Patch to add entries depending on category
|
||||
patcher.after<WidgetSearchSuggestions.Model>(
|
||||
List::class.java,
|
||||
List::class.java,
|
||||
) { (_, _: List<QueryNode>, suggestions: List<SearchSuggestion>) ->
|
||||
var lastCategory: SearchSuggestion.Category? = null
|
||||
val newItems = mutableListOf<MGRecyclerDataPayload>()
|
||||
suggestions.forEach {
|
||||
if (it is AuthorTypeSuggestion) {
|
||||
if (lastCategory != it.category) {
|
||||
newItems.add(
|
||||
SingleTypePayload(it.category, it.category.name, 0)
|
||||
)
|
||||
lastCategory = it.category
|
||||
}
|
||||
newItems.add(
|
||||
SingleTypePayload(it, it.type.value, SuggestionCategoryExtension.AdapterType.AUTHOR_TYPE)
|
||||
)
|
||||
}
|
||||
}
|
||||
suggestionItems.removeAll { it in newItems }
|
||||
suggestionItems.addAll(0, newItems)
|
||||
}
|
||||
|
||||
// Patch to add new types of suggestion entries
|
||||
patcher.before<WidgetSearchSuggestionsAdapter>(
|
||||
"onCreateViewHolder",
|
||||
ViewGroup::class.java,
|
||||
Int::class.javaPrimitiveType!!,
|
||||
) { (param, _: ViewGroup, id: Int) ->
|
||||
when (id) {
|
||||
SuggestionCategoryExtension.AdapterType.AUTHOR_TYPE -> {
|
||||
param.result = AuthorTypeViewHolder(this, scoutRes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adds support for searching in threads
|
||||
|
|
@ -707,7 +827,7 @@ class Scout : Plugin() {
|
|||
// Patch query parser for in: to support names with spaces, by wrapping them in quotes
|
||||
// This enables searching for threads which can have spaces in their names
|
||||
patcher.instead<QueryParser.Companion>("getInAnswerRule") {
|
||||
val compile = Pattern.compile("^\\s*#(\".*?\"|[^ ]+)", 64);
|
||||
val compile = Pattern.compile("^\\s*#(\".*?\"|[^ ]+)", 64)
|
||||
`QueryParser$Companion$getInAnswerRule$1`(compile, compile)
|
||||
}
|
||||
|
||||
|
|
@ -802,7 +922,7 @@ class Scout : Plugin() {
|
|||
// Now it matches something like @<username>[#<discrim>] (bots still have discriminators)
|
||||
// The @ is required unfortunately, to distinguish it from literally any other word
|
||||
patcher.instead<QueryParser.Companion>("getUserRule") {
|
||||
val regex = Pattern.compile("^\\s*@(?:([^@#:]+)#([0-9]{4})|([a-z0-9._]{2,32}))", 64);
|
||||
val regex = Pattern.compile("^\\s*@(?:([^@#:]+)#([0-9]{4})|([a-z0-9._]{2,32}))", 64)
|
||||
|
||||
// Returns a new rule to support our optional second group (discriminator)
|
||||
return@instead SimpleParserRule(regex) { matcher, _, obj ->
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package moe.lava.awoocord.scout
|
||||
|
||||
import com.discord.utilities.search.suggestion.entries.SearchSuggestion
|
||||
|
||||
object SuggestionCategoryExtension {
|
||||
lateinit var AUTHOR_TYPE: SearchSuggestion.Category
|
||||
lateinit var values: Array<SearchSuggestion.Category>
|
||||
|
||||
object AdapterType {
|
||||
const val AUTHOR_TYPE = 7
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ interface SearchAPIInterface {
|
|||
@t("include_nsfw") includeNsfw: Boolean?,
|
||||
@t("sort_by") sortBy: List<String>?, // "timestamp" is one, not sure about any other sort types
|
||||
@t("sort_order") sortOrder: List<String>?, // "asc" or "desc"
|
||||
@t("author_type") authorType: List<String>?,
|
||||
): Observable<ModelSearchResponse?>
|
||||
|
||||
@f("guilds/{guildId}/messages/search")
|
||||
|
|
@ -40,5 +41,6 @@ interface SearchAPIInterface {
|
|||
@t("include_nsfw") includeNsfw: Boolean?,
|
||||
@t("sort_by") sortBy: List<String>?,
|
||||
@t("sort_order") sortOrder: List<String>?,
|
||||
@t("author_type") authorType: List<String>?,
|
||||
): Observable<ModelSearchResponse?>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package moe.lava.awoocord.scout.entries
|
||||
|
||||
import com.discord.utilities.search.suggestion.entries.SearchSuggestion
|
||||
import moe.lava.awoocord.scout.SuggestionCategoryExtension
|
||||
import moe.lava.awoocord.scout.parsing.AuthorType
|
||||
|
||||
data class AuthorTypeSuggestion(val type: AuthorType) : SearchSuggestion {
|
||||
override fun getCategory() = SuggestionCategoryExtension.AUTHOR_TYPE
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package moe.lava.awoocord.scout.entries
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.aliucord.Utils
|
||||
import com.aliucord.utils.ViewUtils.findViewById
|
||||
import com.discord.stores.StoreSearchInput
|
||||
import com.discord.stores.StoreStream
|
||||
import com.discord.utilities.mg_recycler.MGRecyclerDataPayload
|
||||
import com.discord.utilities.mg_recycler.MGRecyclerViewHolder
|
||||
import com.discord.utilities.mg_recycler.SingleTypePayload
|
||||
import com.discord.utilities.search.query.node.filter.FilterNode
|
||||
import com.discord.widgets.search.suggestions.`WidgetSearchSuggestions$configureUI$4`
|
||||
import com.discord.widgets.search.suggestions.WidgetSearchSuggestionsAdapter
|
||||
import com.lytefast.flexinput.R
|
||||
import moe.lava.awoocord.scout.FilterTypeExtension
|
||||
import moe.lava.awoocord.scout.parsing.AuthorType
|
||||
import moe.lava.awoocord.scout.parsing.AuthorTypeNode
|
||||
import moe.lava.awoocord.scout.ui.ScoutResource
|
||||
|
||||
private val replaceAndPublish = StoreSearchInput::class.java.getDeclaredMethod(
|
||||
"replaceAndPublish",
|
||||
Int::class.javaPrimitiveType!!,
|
||||
List::class.java,
|
||||
List::class.java
|
||||
).apply { isAccessible = true }
|
||||
|
||||
private val getAnswerReplacementStart = StoreSearchInput::class.java.getDeclaredMethod(
|
||||
"getAnswerReplacementStart",
|
||||
List::class.java,
|
||||
).apply { isAccessible = true }
|
||||
|
||||
class AuthorTypeViewHolder(
|
||||
adapter: WidgetSearchSuggestionsAdapter,
|
||||
// This should be fine (?)
|
||||
private val scoutRes: ScoutResource,
|
||||
) : MGRecyclerViewHolder<WidgetSearchSuggestionsAdapter, MGRecyclerDataPayload>(
|
||||
Utils.getResId("widget_search_suggestions_item_has", "layout"),
|
||||
adapter,
|
||||
) {
|
||||
private val imageView = itemView.findViewById<ImageView>("search_suggestions_item_has_icon")
|
||||
private val textView = itemView.findViewById<TextView>("search_suggestions_item_has_text")
|
||||
|
||||
override fun onConfigure(i: Int, oPayload: MGRecyclerDataPayload) {
|
||||
super.onConfigure(i, oPayload)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val payload = oPayload as SingleTypePayload<AuthorTypeSuggestion>
|
||||
val type = payload.data.type
|
||||
textView.text = when (type) {
|
||||
AuthorType.Bot -> "bot"
|
||||
AuthorType.User -> "user"
|
||||
AuthorType.Webhook -> "webhook"
|
||||
}
|
||||
when (type) {
|
||||
AuthorType.Bot -> imageView.setImageDrawable(scoutRes.getDrawable("smart_toy_24px"))
|
||||
AuthorType.User -> imageView.setImageResource(R.e.ic_members_24dp)
|
||||
AuthorType.Webhook -> imageView.setImageDrawable(scoutRes.getDrawable("webhook_24px"))
|
||||
}
|
||||
|
||||
itemView.setOnClickListener {
|
||||
val hasHandler = adapter.onHasClicked as `WidgetSearchSuggestions$configureUI$4`
|
||||
val query = hasHandler.`$model`.query
|
||||
|
||||
val storeInput = StoreStream.getSearch().storeSearchInput
|
||||
replaceAndPublish.invoke(
|
||||
storeInput,
|
||||
getAnswerReplacementStart.invoke(storeInput, query) as Int,
|
||||
listOf(
|
||||
FilterNode(FilterTypeExtension.AUTHOR_TYPE, "authorType"),
|
||||
AuthorTypeNode(type)
|
||||
),
|
||||
query,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package moe.lava.awoocord.scout.parsing
|
||||
|
||||
import android.content.Context
|
||||
import com.discord.simpleast.core.parser.ParseSpec
|
||||
import com.discord.simpleast.core.parser.Rule
|
||||
import com.discord.utilities.search.network.SearchQuery
|
||||
import com.discord.utilities.search.query.FilterType
|
||||
import com.discord.utilities.search.query.node.QueryNode
|
||||
import com.discord.utilities.search.query.node.answer.AnswerNode
|
||||
import com.discord.utilities.search.query.node.filter.FilterNode
|
||||
import com.discord.utilities.search.validation.SearchData
|
||||
import moe.lava.awoocord.scout.FilterTypeExtension
|
||||
import java.util.regex.Pattern
|
||||
|
||||
// TODO: not localised, maybe one day
|
||||
enum class AuthorType(val value: String) {
|
||||
User("user"),
|
||||
Bot("bot"),
|
||||
Webhook("webhook"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun from(value: String) = when (value) {
|
||||
"user" -> User
|
||||
"bot" -> Bot
|
||||
"webhook" -> Webhook
|
||||
else -> throw IllegalArgumentException("Unknown author type $value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AuthorTypeNode(val type: AuthorType): AnswerNode() {
|
||||
companion object {
|
||||
fun getAuthorTypesRule(): Rule<Context, QueryNode, Any> {
|
||||
val joined = AuthorType.values().joinToString("|") { it.value }
|
||||
val regexStr = "^\\s*(${joined})"
|
||||
val regex = Pattern.compile(regexStr, Pattern.UNICODE_CASE)
|
||||
return SimpleParserRule(regex) { matcher, _, obj ->
|
||||
ParseSpec(AuthorTypeNode(AuthorType.from(matcher.group())), obj)
|
||||
}
|
||||
}
|
||||
|
||||
fun getFilterRule(str: String): ParserRule {
|
||||
val regex = Pattern.compile("^\\s*?(${str}):", 64);
|
||||
return SimpleParserRule(regex) { _, _, obj ->
|
||||
ParseSpec(FilterNode(FilterTypeExtension.AUTHOR_TYPE, str), obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getValidFilters() = setOf(FilterTypeExtension.AUTHOR_TYPE)
|
||||
override fun isValid(searchData: SearchData?) = true
|
||||
override fun getText() = type.value
|
||||
|
||||
override fun updateQuery(
|
||||
builder: SearchQuery.Builder,
|
||||
searchData: SearchData?,
|
||||
filterType: FilterType?
|
||||
) {
|
||||
builder.appendParam("author_type", type.value)
|
||||
}
|
||||
}
|
||||
|
|
@ -36,11 +36,10 @@ class SortNode(private val text: String): AnswerNode() {
|
|||
override fun getText() = this.text
|
||||
|
||||
override fun updateQuery(
|
||||
builder: SearchQuery.Builder?,
|
||||
builder: SearchQuery.Builder,
|
||||
searchData: SearchData?,
|
||||
filterType: FilterType?
|
||||
) {
|
||||
checkNotNull(builder) { "queryBuilder" }
|
||||
builder.appendParam("sort_order", "asc")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ class ScoutResource(private val resources: Resources) {
|
|||
val SORT_FILTER = View.generateViewId()
|
||||
val SORT_ANSWER = View.generateViewId()
|
||||
val EXCLUDE_FILTER = View.generateViewId()
|
||||
val AUTHOR_TYPE_FILTER = View.generateViewId()
|
||||
val AUTHOR_TYPE_ANSWER = View.generateViewId()
|
||||
}
|
||||
|
||||
fun getId(name: String, type: String) =
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class ScoutSearchStringProvider(private val context: Context) {
|
|||
FilterTypeExtension.DURING -> duringFilterString
|
||||
FilterTypeExtension.AFTER -> afterFilterString
|
||||
FilterTypeExtension.SORT -> sortFilterString
|
||||
FilterTypeExtension.AUTHOR_TYPE -> authorTypeFilter
|
||||
else -> throw IllegalArgumentException("invalid extended filter type")
|
||||
}
|
||||
|
||||
|
|
@ -51,4 +52,9 @@ class ScoutSearchStringProvider(private val context: Context) {
|
|||
get() = "forward"
|
||||
val excludeFilterString: String
|
||||
get() = "exclude"
|
||||
val authorTypeFilter: String
|
||||
get() = "authorType"
|
||||
val authorTypeAnswer: String
|
||||
// TODO, could probably be localisable by joining each part together
|
||||
get() = "user, bot or webhook"
|
||||
}
|
||||
|
|
|
|||
10
plugins/Scout/src/main/res/drawable/smart_toy_24px.xml
Normal file
10
plugins/Scout/src/main/res/drawable/smart_toy_24px.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="#FFFFFF">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M160,600Q110,600 75,565Q40,530 40,480Q40,430 75,395Q110,360 160,360L160,280Q160,247 183.5,223.5Q207,200 240,200L360,200Q360,150 395,115Q430,80 480,80Q530,80 565,115Q600,150 600,200L720,200Q753,200 776.5,223.5Q800,247 800,280L800,360Q850,360 885,395Q920,430 920,480Q920,530 885,565Q850,600 800,600L800,760Q800,793 776.5,816.5Q753,840 720,840L240,840Q207,840 183.5,816.5Q160,793 160,760L160,600ZM402.5,502.5Q420,485 420,460Q420,435 402.5,417.5Q385,400 360,400Q335,400 317.5,417.5Q300,435 300,460Q300,485 317.5,502.5Q335,520 360,520Q385,520 402.5,502.5ZM642.5,502.5Q660,485 660,460Q660,435 642.5,417.5Q625,400 600,400Q575,400 557.5,417.5Q540,435 540,460Q540,485 557.5,502.5Q575,520 600,520Q625,520 642.5,502.5ZM320,680L640,680L640,600L320,600L320,680ZM240,760L720,760Q720,760 720,760Q720,760 720,760L720,280Q720,280 720,280Q720,280 720,280L240,280Q240,280 240,280Q240,280 240,280L240,760Q240,760 240,760Q240,760 240,760ZM480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520Z"/>
|
||||
</vector>
|
||||
10
plugins/Scout/src/main/res/drawable/webhook_24px.xml
Normal file
10
plugins/Scout/src/main/res/drawable/webhook_24px.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="#FFFFFF">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M280,840Q197,840 138.5,781.5Q80,723 80,640Q80,567 125.5,512.5Q171,458 240,444L240,527Q205,539 182.5,570Q160,601 160,640Q160,690 195,725Q230,760 280,760Q330,760 365,725Q400,690 400,640L400,600L635,600Q643,591 654.5,585.5Q666,580 680,580Q705,580 722.5,597.5Q740,615 740,640Q740,665 722.5,682.5Q705,700 680,700Q666,700 654.5,694.5Q643,689 635,680L476,680Q462,749 407.5,794.5Q353,840 280,840ZM680,840Q624,840 578.5,812.5Q533,785 507,740L614,740Q628,750 645,755Q662,760 680,760Q730,760 765,725Q800,690 800,640Q800,590 765,555Q730,520 680,520Q660,520 643,525.5Q626,531 611,542L489,339Q468,335 454,319Q440,303 440,280Q440,255 457.5,237.5Q475,220 500,220Q525,220 542.5,237.5Q560,255 560,280Q560,285 560,288.5Q560,292 558,297L645,443Q653,441 662,440.5Q671,440 680,440Q763,440 821.5,498.5Q880,557 880,640Q880,723 821.5,781.5Q763,840 680,840ZM280,700Q255,700 237.5,682.5Q220,665 220,640Q220,618 234,602Q248,586 268,581L362,425Q333,398 316.5,360.5Q300,323 300,280Q300,197 358.5,138.5Q417,80 500,80Q583,80 641.5,138.5Q700,197 700,280L620,280Q620,230 585,195Q550,160 500,160Q450,160 415,195Q380,230 380,280Q380,323 406,355.5Q432,388 472,397L337,622Q339,627 339.5,631Q340,635 340,640Q340,665 322.5,682.5Q305,700 280,700Z"/>
|
||||
</vector>
|
||||
Loading…
Add table
Add a link
Reference in a new issue