From 0dc4bf428625ab3936e6528e27a80f3c044fab62 Mon Sep 17 00:00:00 2001 From: LavaDesu Date: Thu, 7 Aug 2025 02:39:27 +1000 Subject: [PATCH] fix(ComponentsV2): refactor, prevent viewraw crash, and add cv2 tag --- .../com/aliucord/coreplugins/CV2Compat.kt | 137 ++++++++++++++++++ .../com/aliucord/coreplugins/ComponentsV2.kt | 75 +--------- .../views/MediaGalleryComponentView.kt | 4 +- 3 files changed, 141 insertions(+), 75 deletions(-) create mode 100644 canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/CV2Compat.kt diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/CV2Compat.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/CV2Compat.kt new file mode 100644 index 0000000..598aed3 --- /dev/null +++ b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/CV2Compat.kt @@ -0,0 +1,137 @@ +package com.aliucord.coreplugins + +import android.annotation.SuppressLint +import android.view.View +import android.widget.TextView +import com.aliucord.Constants +import com.aliucord.Utils +import com.aliucord.api.PatcherAPI +import com.aliucord.coreplugins.componentsv2.ComponentV2Type +import com.aliucord.patcher.* +import com.aliucord.utils.GsonUtils +import com.aliucord.utils.GsonUtils.toJson +import com.aliucord.utils.ReflectUtils +import com.discord.api.botuikit.ComponentType +import com.discord.api.botuikit.gson.ComponentRuntimeTypeAdapter +import com.discord.api.botuikit.gson.ComponentTypeTypeAdapter +import com.discord.api.message.attachment.MessageAttachment +import com.discord.models.domain.Model +import com.discord.models.message.Message +import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemMessage +import com.google.gson.stream.JsonReader +import java.io.File +import b.a.b.a as TypeAdapterRegistrar +import b.i.d.c as FieldNamingPolicy +import b.i.d.e as GsonBuilder + +fun ComponentsV2.compat(patcher: PatcherAPI) { + // check for old cursed plugin, probably not needed anymore + val oldFile = File("${Constants.PLUGINS_PATH}/ComponentsV2-Beta.zip") + if (oldFile.exists()) { + logger.info("old plugin found, deleting and prompting restart") + oldFile.delete() + Utils.promptRestart() + return + } + + // I'm sorry + // ViewRaw crashes without this + val cuteGson = GsonBuilder().run { + c = FieldNamingPolicy.m // LOWER_CASE_WITH_UNDERSCORES + TypeAdapterRegistrar.a(this) + e.add(Model.TypeAdapterFactory()) + a().apply { + ReflectUtils.setField(this, "k", true) + } + } + patcher.patch(GsonUtils::class.java.getDeclaredMethod("toJsonPretty", Object::class.java)) + { (param, obj: Any) -> + if (obj is Message && obj.isComponentV2) + param.result = cuteGson.toJson(obj) + } + + // add cv2 tag + patcher.after("configureItemTag", Message::class.java, Boolean::class.javaPrimitiveType!!) + { (_, msg: Message) -> + val textView = ReflectUtils.getField(this, "itemTag") as TextView? + ?: return@after + + if (!msg.isComponentV2) + return@after + + if (textView.text.isEmpty()) { + // this code path shouldn't really ever run (only bots can send cv2, and bots have the tag already) + // but idk maybe someone self-bots or something + textView.visibility = View.VISIBLE + @SuppressLint("SetTextI18n") + textView.text = "CV2" + textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) + } else { + @SuppressLint("SetTextI18n") + textView.text = textView.text.toString() + " | CV2" + } + } + + ComponentV2Type.make() + patchGson(patcher) +} + +fun ComponentsV2.stopCompat() { + unpatchGson() + ComponentV2Type.unmake(logger) +} + +private fun patchGson(patcher: PatcherAPI) { + val factory = ComponentRuntimeTypeAdapter.INSTANCE.a() + val typeToClass = factory.l + val classToType = factory.m + ComponentV2Type.newValues?.forEach { + typeToClass[it.type.toString()] = it.clazz + classToType[it.clazz] = it.type.toString() + } + + patcher.instead("read", JsonReader::class.java) + { (_, jsonReader: JsonReader) -> + val type: Int = b.c.a.a0.d.n1(jsonReader) + ComponentType.values().find { it.type == type } ?: ComponentType.UNKNOWN + } +} + +private fun unpatchGson() { + val factory = ComponentRuntimeTypeAdapter.INSTANCE.a() + val typeToClass = factory.l + val classToType = factory.m + ComponentV2Type.newValues?.forEach { + typeToClass.remove(it.type.toString()) + classToType.remove(it.clazz) + } +} + +object CV2Compat { + /** Creates a new [MessageAttachment] */ + fun createAttachment( + filename: String, + filesize: Long, + proxyUrl: String, + url: String, + width: Int, + height: Int, + ): MessageAttachment { + val inst = ReflectUtils.allocateInstance(clazz) + filenameField.set(inst, filename) + filesizeField.set(inst, filesize) + proxyUrlField.set(inst, proxyUrl) + urlField.set(inst, url) + widthField.set(inst, width) + heightField.set(inst, height) + return inst + } +} + +private val clazz = MessageAttachment::class.java +private val filenameField = clazz.getDeclaredField("filename").apply { isAccessible = true } +private val filesizeField = clazz.getDeclaredField("size").apply { isAccessible = true } +private val proxyUrlField = clazz.getDeclaredField("proxyUrl").apply { isAccessible = true } +private val urlField = clazz.getDeclaredField("url").apply { isAccessible = true } +private val widthField = clazz.getDeclaredField("width").apply { isAccessible = true } +private val heightField = clazz.getDeclaredField("height").apply { isAccessible = true } diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/ComponentsV2.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/ComponentsV2.kt index 61acc91..64a147f 100644 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/ComponentsV2.kt +++ b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/ComponentsV2.kt @@ -4,7 +4,6 @@ import android.content.Context import android.view.ViewGroup import android.widget.LinearLayout import androidx.constraintlayout.widget.ConstraintLayout -import com.aliucord.Constants import com.aliucord.Utils import com.aliucord.annotations.AliucordPlugin import com.aliucord.coreplugins.componentsv2.ComponentV2Type @@ -13,11 +12,7 @@ import com.aliucord.coreplugins.componentsv2.patchMessageItems import com.aliucord.coreplugins.componentsv2.views.* import com.aliucord.entities.Plugin import com.aliucord.patcher.* -import com.aliucord.utils.ReflectUtils import com.discord.api.botuikit.* -import com.discord.api.botuikit.gson.ComponentRuntimeTypeAdapter -import com.discord.api.botuikit.gson.ComponentTypeTypeAdapter -import com.discord.api.message.attachment.MessageAttachment import com.discord.models.botuikit.* import com.discord.models.message.Message import com.discord.stores.StoreApplicationInteractions.InteractionSendState @@ -29,57 +24,17 @@ import com.discord.widgets.botuikit.views.select.SelectComponentView import com.discord.widgets.chat.list.adapter.WidgetChatListAdapter import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemBotComponentRow import com.discord.widgets.chat.list.entries.BotUiComponentEntry -import com.google.gson.stream.JsonReader import com.lytefast.flexinput.R import de.robv.android.xposed.XposedBridge -import java.io.File val Message.isComponentV2 get() = (flags shr 15) and 1 == 1L @AliucordPlugin(requiresRestart = true) @Suppress("unused") class ComponentsV2 : Plugin() { - companion object { - /** Creates a new [MessageAttachment] */ - fun createAttachment( - filename: String, - filesize: Long, - proxyUrl: String, - url: String, - width: Int, - height: Int, - ): MessageAttachment { - val inst = ReflectUtils.allocateInstance(clazz) - filenameField.set(inst, filename) - filesizeField.set(inst, filesize) - proxyUrlField.set(inst, proxyUrl) - urlField.set(inst, url) - widthField.set(inst, width) - heightField.set(inst, height) - return inst - } - - private val clazz = MessageAttachment::class.java - private val filenameField = clazz.getDeclaredField("filename").apply { isAccessible = true } - private val filesizeField = clazz.getDeclaredField("size").apply { isAccessible = true } - private val proxyUrlField = clazz.getDeclaredField("proxyUrl").apply { isAccessible = true } - private val urlField = clazz.getDeclaredField("url").apply { isAccessible = true } - private val widthField = clazz.getDeclaredField("width").apply { isAccessible = true } - private val heightField = clazz.getDeclaredField("height").apply { isAccessible = true } - } - override fun start(context: Context) { - val oldFile = File("${Constants.PLUGINS_PATH}/ComponentsV2-Beta.zip") - if (oldFile.exists()) { - logger.info("old plugin found, deleting and prompting restart") - oldFile.delete() - Utils.promptRestart() - return - } - + compat(patcher) XposedBridge.makeClassInheritable(BotUiComponentEntry::class.java) - ComponentV2Type.make() - patchGson() // https://github.com/LSPosed/LSPlant/issues/41 patchMessageItems(patcher) @@ -205,32 +160,6 @@ class ComponentsV2 : Plugin() { override fun stop(context: Context) { patcher.unpatchAll() - unpatchGson() - ComponentV2Type.unmake(logger) - } - - private fun patchGson() { - val factory = ComponentRuntimeTypeAdapter.INSTANCE.a() - val typeToClass = factory.l - val classToType = factory.m - ComponentV2Type.newValues?.forEach { - typeToClass[it.type.toString()] = it.clazz - classToType[it.clazz] = it.type.toString() - } - - patcher.instead("read", JsonReader::class.java) - { (_, jsonReader: JsonReader) -> - val type: Int = b.c.a.a0.d.n1(jsonReader) - ComponentType.values().find { it.type == type } ?: ComponentType.UNKNOWN - } - } - private fun unpatchGson() { - val factory = ComponentRuntimeTypeAdapter.INSTANCE.a() - val typeToClass = factory.l - val classToType = factory.m - ComponentV2Type.newValues?.forEach { - typeToClass.remove(it.type.toString()) - classToType.remove(it.clazz) - } + stopCompat() } } diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/MediaGalleryComponentView.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/MediaGalleryComponentView.kt index fc48d4c..64c44d4 100644 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/MediaGalleryComponentView.kt +++ b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/MediaGalleryComponentView.kt @@ -10,7 +10,7 @@ import android.widget.FrameLayout import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID import com.aliucord.Logger -import com.aliucord.coreplugins.ComponentsV2 +import com.aliucord.coreplugins.CV2Compat import com.aliucord.coreplugins.componentsv2.BotUiComponentV2Entry import com.aliucord.coreplugins.componentsv2.ComponentV2Type import com.aliucord.coreplugins.componentsv2.models.MediaGalleryMessageComponent @@ -77,7 +77,7 @@ class MediaGalleryComponentView(ctx: Context) : ConstraintLayout(ctx), Component val media = it.media // TODO: there's probably a utility to extract filename from url val name = media.url.split("/").last().split("?").first() - val attachment = ComponentsV2.createAttachment( + val attachment = CV2Compat.createAttachment( name, 0, media.proxyUrl,