diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/HasAnswerOptionExtension.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/HasAnswerOptionExtension.kt new file mode 100644 index 0000000..b23ec8f --- /dev/null +++ b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/HasAnswerOptionExtension.kt @@ -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 +} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/Scout.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/Scout.kt index dfc20e2..ac3e2f6 100644 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/Scout.kt +++ b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/Scout.kt @@ -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? = 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 + 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( + "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( + "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( + "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( + "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( + "getHasSuggestions", + CharSequence::class.java, + FilterType::class.java, + SearchStringProvider::class.java, + ) { param -> + val query = param.args[0] as CharSequence + val res = (param.result as List).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( diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutSearchStringProvider.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutSearchStringProvider.kt index ee7ac1b..ce85b6f 100644 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutSearchStringProvider.kt +++ b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutSearchStringProvider.kt @@ -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" }