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 85e79f7..3384519 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 @@ -3,20 +3,31 @@ package moe.lava.awoocord.scout import android.content.Context import android.content.res.Resources import android.view.View +import android.widget.ImageView import androidx.core.content.res.ResourcesCompat import com.aliucord.Utils import com.aliucord.annotations.AliucordPlugin import com.aliucord.entities.Plugin import com.aliucord.patcher.* import com.aliucord.utils.DimenUtils.dp +import com.aliucord.utils.ViewUtils.findViewById +import com.aliucord.wrappers.ChannelWrapper.Companion.id +import com.aliucord.wrappers.ChannelWrapper.Companion.type import com.discord.BuildConfig +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.WidgetSearchSuggestionsItemHasBinding +import com.discord.models.member.GuildMember +import com.discord.models.user.User import com.discord.restapi.RequiredHeadersInterceptor import com.discord.restapi.RequiredHeadersInterceptor.HeadersProvider import com.discord.restapi.RestAPIBuilder import com.discord.simpleast.core.parser.* import com.discord.stores.StoreSearch import com.discord.stores.StoreSearchInput +import com.discord.stores.StoreStream import com.discord.utilities.mg_recycler.MGRecyclerDataPayload import com.discord.utilities.mg_recycler.SingleTypePayload import com.discord.utilities.rest.RestAPI.AppHeadersProvider @@ -24,11 +35,13 @@ import com.discord.utilities.search.network.`SearchFetcher$getRestObservable$3` 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.ChannelNode import com.discord.utilities.search.query.node.answer.HasAnswerOption import com.discord.utilities.search.query.node.answer.HasNode 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.query.parsing.`QueryParser$Companion$getInAnswerRule$1` import com.discord.utilities.search.strings.SearchStringProvider import com.discord.utilities.search.suggestion.SearchSuggestionEngine import com.discord.utilities.search.suggestion.entries.* @@ -40,6 +53,7 @@ import com.franmontiel.persistentcookiejar.PersistentCookieJar import com.franmontiel.persistentcookiejar.cache.SetCookieCache import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor import com.lytefast.flexinput.R +import de.robv.android.xposed.XposedBridge import moe.lava.awoocord.scout.api.SearchAPIInterface import moe.lava.awoocord.scout.parsing.* import moe.lava.awoocord.scout.ui.* @@ -67,6 +81,7 @@ class Scout : Plugin() { patchQueryParser() patchSearchUI(context) patchSearchPadding() + patchThreadSupport() } override fun stop(context: Context) { @@ -593,4 +608,94 @@ class Scout : Plugin() { } } } + + private fun patchThreadSupport() { + // 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("getInAnswerRule") { + val compile = Pattern.compile("^\\s*#(\".*?\"|[^ ]+)", 64); + `QueryParser$Companion$getInAnswerRule$1`(compile, compile) + } + + // Patch Search data model builder to also add in threads + patcher.before( + "buildForGuild", + Map::class.java, + Map::class.java, + Map::class.java, + Map::class.java + ) { ( + param, + members: Map, + users: Map, + channels: Map, + permissions: Map + ) -> + val threads = StoreStream.getChannels().`getThreadsForGuildInternal$app_productionGoogleRelease`( + StoreStream.getGuildSelected().selectedGuildId + ) + val mergedChannels = channels.toMutableMap() + val mergedPermissions = permissions.toMutableMap() + for (thread in threads) { + mergedChannels[thread.id] = thread + mergedPermissions[thread.id] = Permission.VIEW_CHANNEL + } + param.args[2] = mergedChannels + param.args[3] = mergedPermissions + } + + // Post-process the name-id map to wrap the names in quotes if they have spaces + patcher.after( + "buildForGuild", + Map::class.java, + Map::class.java, + Map::class.java, + Map::class.java + ) { param -> + val res = param.result as SearchData + val nameMap = res.channelNameIndex as HashMap + nameMap + .filter { (name) -> name.contains(" ") } + .forEach { (name, value) -> + val wrapped = "\"${name}\"" + nameMap.remove(name) + nameMap[wrapped] = value + } + } + + // Patch the channel node to automatically insert quotes for names with spaces + patcher.before(String::class.java) { (param, name: String) -> + if (name.contains(" ") && !name.startsWith("\"")) + param.args[0] = "\"${name}\"" + } + + // Patch the search sorter to place threads last + patcher.before<`ChannelUtils$getSortByNameAndType$1`<*>>( + "compare", + Object::class.java, // ?? :sob: + Object::class.java, + ) { (param, ch1: Channel, ch2: Channel) -> + // ChannelUtils.H <=> ChannelUtils.isThread + if (ChannelUtils.H(ch1) && !ChannelUtils.H(ch2)) { + param.result = 1 + } + if (!ChannelUtils.H(ch1) && ChannelUtils.H(ch2)) { + param.result = -1 + } + } + + // Patch search suggestions to set icon to thread icon if it is a thread + patcher.after( + "onConfigure", + Int::class.javaPrimitiveType!!, + MGRecyclerDataPayload::class.java + ) { (param, _: Int, payload: SingleTypePayload) -> + StoreStream.getChannels().getChannel(payload.data.channelId)?.let { + if (ChannelUtils.H(it)) { + itemView.findViewById("search_suggestions_item_channel_icon") + .setImageResource(R.e.ic_thread_white_24dp) + } + } + } + } }