feat(Scout): add has:poll and has:forward filters

This commit is contained in:
LavaDesu 2025-05-29 20:59:45 +10:00
parent da07f17650
commit 47ae69de2d
Signed by: cilly
GPG key ID: 6500251E087653C9
3 changed files with 130 additions and 0 deletions

View file

@ -0,0 +1,9 @@
package moe.lava.awoocord.scout
import com.discord.utilities.search.query.node.answer.HasAnswerOption
object HasAnswerOptionExtension {
lateinit var POLL: HasAnswerOption
lateinit var SNAPSHOT: HasAnswerOption
lateinit var values: Array<HasAnswerOption>
}

View file

@ -9,6 +9,7 @@ import com.aliucord.entities.Plugin
import com.aliucord.patcher.PreHook
import com.aliucord.patcher.after
import com.aliucord.patcher.before
import com.aliucord.patcher.instead
import com.discord.BuildConfig
import com.discord.restapi.RequiredHeadersInterceptor
import com.discord.restapi.RequiredHeadersInterceptor.HeadersProvider
@ -21,12 +22,14 @@ import com.discord.utilities.rest.RestAPI.AppHeadersProvider
import com.discord.utilities.search.network.`SearchFetcher$getRestObservable$3`
import com.discord.utilities.search.query.FilterType
import com.discord.utilities.search.query.node.QueryNode
import com.discord.utilities.search.query.node.answer.HasAnswerOption
import com.discord.utilities.search.query.node.content.ContentNode
import com.discord.utilities.search.query.node.filter.FilterNode
import com.discord.utilities.search.query.parsing.QueryParser
import com.discord.utilities.search.strings.SearchStringProvider
import com.discord.utilities.search.suggestion.SearchSuggestionEngine
import com.discord.utilities.search.suggestion.entries.FilterSuggestion
import com.discord.utilities.search.suggestion.entries.HasSuggestion
import com.discord.utilities.search.suggestion.entries.SearchSuggestion
import com.discord.widgets.search.suggestions.WidgetSearchSuggestionsAdapter
import com.franmontiel.persistentcookiejar.PersistentCookieJar
@ -50,13 +53,16 @@ class Scout : Plugin() {
ssProvider = ScoutSearchStringProvider(context)
searchApi = buildSearchApi(context)
extendFilterType()
extendHasAnswerOption()
patchQueryParser()
patchQuery()
patchHasAnswerOption()
patchSearchUI(context)
}
override fun stop(context: Context) {
resetFilterType()
resetHasAnswerOption()
patcher.unpatchAll()
}
@ -123,6 +129,107 @@ class Scout : Plugin() {
origFilterTypes = null
}
private var origHasAnswerOptions: Array<HasAnswerOption>? = null
// Creates new pseudo-values of the `HasAnswerOption` enum for poll and forwarded filters
@Suppress("LocalVariableName")
private fun extendHasAnswerOption() {
val cls = HasAnswerOption::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<HasAnswerOption>
origHasAnswerOptions = origHasAnswerOptions ?: values
var nextIdx = values.size
val POLL = constructor.newInstance("POLL", nextIdx++, "poll") as HasAnswerOption
val SNAPSHOT = constructor.newInstance("SNAPSHOT", nextIdx, "snapshot") as HasAnswerOption
HasAnswerOptionExtension.POLL = POLL
HasAnswerOptionExtension.SNAPSHOT = SNAPSHOT
HasAnswerOptionExtension.values = arrayOf(POLL, SNAPSHOT)
val newValues = values.toMutableList()
newValues.addAll(HasAnswerOptionExtension.values)
field.set(null, newValues.toTypedArray())
}
private fun resetHasAnswerOption() {
if (origHasAnswerOptions == null)
return logger.error("No unpatched 'has' options?", null)
val cls = HasAnswerOption::class.java
val field = cls.getDeclaredField("\$VALUES")
field.isAccessible = true
field.set(null, origHasAnswerOptions)
origHasAnswerOptions = null
}
// Patches various methods that use HasAnswerOption to include our new options
private fun patchHasAnswerOption() {
patcher.before<HasAnswerOption.Companion>(
"getOptionFromString",
String::class.java,
SearchStringProvider::class.java
) { param ->
val str = param.args[0] as String
if (str == ssProvider.hasPollString)
param.result = HasAnswerOptionExtension.POLL
else if (str == ssProvider.hasForwardString)
param.result = HasAnswerOptionExtension.SNAPSHOT
}
patcher.before<HasAnswerOption>(
"getLocalizedInputText",
SearchStringProvider::class.java
) { param ->
if (this == HasAnswerOptionExtension.POLL)
param.result = ssProvider.hasPollString
else if (this == HasAnswerOptionExtension.SNAPSHOT)
param.result = ssProvider.hasForwardString
}
// private final String createHasAnswerRegex(SearchStringProvider searchStringProvider) {
patcher.instead<QueryParser.Companion>(
"createHasAnswerRegex",
SearchStringProvider::class.java
) { param ->
val ossProvider = param.args[0] as SearchStringProvider
val matches = HasAnswerOption.values().joinToString("|") { it.getLocalizedInputText(ossProvider) }
"^\\s*($matches)"
}
// Patch to set icons
patcher.before<WidgetSearchSuggestionsAdapter.HasViewHolder.Companion>(
"getIconRes",
HasAnswerOption::class.java
) { param ->
val type = param.args[0] as HasAnswerOption
if (type == HasAnswerOptionExtension.POLL)
param.result = 0x7f08032e
else if (type == HasAnswerOptionExtension.SNAPSHOT)
param.result = 0x7f08032e
}
patcher.after<SearchSuggestionEngine>(
"getHasSuggestions",
CharSequence::class.java,
FilterType::class.java,
SearchStringProvider::class.java,
) { param ->
val query = param.args[0] as CharSequence
val res = (param.result as List<SearchSuggestion>).toMutableList()
for (type in HasAnswerOptionExtension.values) {
val st = ssProvider.stringFor(type) + ":"
if (st.contains(query))
res.add(HasSuggestion(type))
}
param.result = res.toList()
}
}
// Patches the search query to also insert `min_id`, required for searching "after:" and "during:"
private fun patchQuery() {
patcher.patch(

View file

@ -2,7 +2,9 @@ package moe.lava.awoocord.scout.ui
import android.content.Context
import com.discord.utilities.search.query.FilterType
import com.discord.utilities.search.query.node.answer.HasAnswerOption
import moe.lava.awoocord.scout.FilterTypeExtension
import moe.lava.awoocord.scout.HasAnswerOptionExtension
private fun String.decapitalise(context: Context) =
this.replaceFirstChar { it.lowercase(context.resources.configuration.locales[0]) }
@ -21,6 +23,12 @@ class ScoutSearchStringProvider(private val context: Context) {
else -> throw IllegalArgumentException("invalid extended filter type")
}
fun stringFor(type: HasAnswerOption) = when (type) {
HasAnswerOptionExtension.POLL -> hasPollString
HasAnswerOptionExtension.SNAPSHOT -> hasForwardString
else -> throw IllegalArgumentException("invalid extended filter type")
}
// Surprising!! Discord has localised strings of these
val beforeFilterString: String
get() = getString("search_filter_before")
@ -32,4 +40,10 @@ class ScoutSearchStringProvider(private val context: Context) {
get() = getString("sort").decapitalise(context)
val sortOldString: String
get() = getString("search_oldest_short").decapitalise(context)
// Not localised
val hasPollString: String
get() = "poll"
val hasForwardString: String
get() = "forward"
}