From 0a13705c087854b517acb258b6d6b0e5830b3390 Mon Sep 17 00:00:00 2001 From: Nacho Date: Thu, 2 Oct 2025 13:27:34 +0800 Subject: [PATCH 1/3] feat: add modify text message --- .../ono/hooks/dispatcher/MenuBuilderHook.kt | 2 + .../item/entertainment/ModifyTextMessage.kt | 160 ++++++++++++++++++ app/src/main/res/values/ids.xml | 1 + 3 files changed, 163 insertions(+) create mode 100644 app/src/main/java/moe/ono/hooks/item/entertainment/ModifyTextMessage.kt diff --git a/app/src/main/java/moe/ono/hooks/dispatcher/MenuBuilderHook.kt b/app/src/main/java/moe/ono/hooks/dispatcher/MenuBuilderHook.kt index 52aa1f6e..984058e6 100644 --- a/app/src/main/java/moe/ono/hooks/dispatcher/MenuBuilderHook.kt +++ b/app/src/main/java/moe/ono/hooks/dispatcher/MenuBuilderHook.kt @@ -5,6 +5,7 @@ import moe.ono.hooks._base.ApiHookItem import moe.ono.hooks._core.annotation.HookItem import moe.ono.hooks.item.chat.StickerPanelEntry import moe.ono.hooks.item.developer.QQMessageFetcher +import moe.ono.hooks.item.entertainment.ModifyTextMessage import moe.ono.hooks.item.sigma.QQMessageTracker import moe.ono.util.Logger import java.lang.reflect.Modifier @@ -15,6 +16,7 @@ class MenuBuilderHook : ApiHookItem() { StickerPanelEntry(), QQMessageTracker(), QQMessageFetcher(), + ModifyTextMessage() ) override fun entry(classLoader: ClassLoader) { diff --git a/app/src/main/java/moe/ono/hooks/item/entertainment/ModifyTextMessage.kt b/app/src/main/java/moe/ono/hooks/item/entertainment/ModifyTextMessage.kt new file mode 100644 index 00000000..0efac7fb --- /dev/null +++ b/app/src/main/java/moe/ono/hooks/item/entertainment/ModifyTextMessage.kt @@ -0,0 +1,160 @@ +package moe.ono.hooks.item.entertainment + +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import android.widget.TextView +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import de.robv.android.xposed.XC_MethodHook.MethodHookParam +import moe.ono.R +import moe.ono.bridge.ntapi.MsgServiceHelper +import moe.ono.config.ConfigManager +import moe.ono.constants.Constants +import moe.ono.hooks._base.BaseSwitchFunctionHookItem +import moe.ono.hooks._core.annotation.HookItem +import moe.ono.hooks._core.factory.HookItemFactory.getItem +import moe.ono.hooks.base.api.QQMessageViewListener +import moe.ono.hooks.base.api.QQMsgViewAdapter +import moe.ono.hooks.base.util.Toasts +import moe.ono.hooks.dispatcher.OnMenuBuilder +import moe.ono.reflex.FieldUtils +import moe.ono.reflex.Reflex +import moe.ono.ui.CommonContextWrapper +import moe.ono.util.AppRuntimeHelper +import moe.ono.util.ContextUtils +import moe.ono.util.CustomMenu +import moe.ono.util.Logger +import moe.ono.util.Session +import moe.ono.util.SyncUtils + +@HookItem(path = "娱乐功能/修改文本消息内容", description = "修改显示文本\n\n* 文本消息长按菜单中使用") +class ModifyTextMessage : BaseSwitchFunctionHookItem(), OnMenuBuilder { + + companion object { + val modifyMap = mutableMapOf() + } + + override fun entry(classLoader: ClassLoader) { + QQMessageViewListener.addMessageViewUpdateListener(this, + object : QQMessageViewListener.OnChatViewUpdateListener { + override fun onViewUpdateAfter(msgItemView: View, msgRecord: Any) { + modifyMessage(msgItemView, msgRecord) + } + }) + } + + /** + * 修改消息内容 + */ + private fun modifyMessage(msgItemView: View, msgRecord: Any) { + if (!ConfigManager.getDefaultConfig().getBooleanOrFalse(Constants.PrekXXX + getItem(this::class.java).path)) return + + val contentView = QQMsgViewAdapter.getContentView(msgItemView) + + val peerUid: String = FieldUtils.create(msgRecord) + .fieldName("peerUid") + .fieldType(String::class.java) + .firstValue(msgRecord) + + val msgSeq: Long = FieldUtils.create(msgRecord) + .fieldName("msgSeq") + .fieldType(Long::class.javaPrimitiveType) + .firstValue(msgRecord) + + val senderUin: Long = FieldUtils.create(msgRecord) + .fieldName("senderUin") + .fieldType(Long::class.java) + .firstValue(msgRecord) + + if (!modifyMap.contains("$peerUid:$msgSeq:$senderUin")) return + + val msgType: Int = FieldUtils.create(msgRecord) + .fieldName("msgType") + .fieldType(Int::class.java) + .firstValue(msgRecord) + + if (msgType <= 2) { + if (contentView is ViewGroup) { + if (contentView.getChildAt(0) is ViewGroup) { + val parent = contentView.getChildAt(0) as ViewGroup + if (parent.getChildAt(0) is ViewGroup) { + val parent2 = parent.getChildAt(0) as ViewGroup + if (parent2.getChildAt(0) is ViewGroup) { + val parent3 = parent2.getChildAt(0) as ViewGroup + val aioMsgTextView = parent3.getChildAt(0) as TextView + aioMsgTextView.setText(modifyMap.get("$peerUid:$msgSeq:$senderUin")) + } + } + } + } + } + } + + override val targetTypes = arrayOf( + "com.tencent.mobileqq.aio.msglist.holder.component.text.AIOTextContentComponent", + ) + + override fun onGetMenu(aioMsgItem: Any, targetType: String, param: MethodHookParam) { + if (!getItem(this.javaClass).isEnabled) { + return + } + + val item: Any = CustomMenu.createItemIconNt( + aioMsgItem, + "Edit", + R.drawable.ic_baseline_border_color_24, + R.id.item_modify_text_message + ) { + try { + val msgID = Reflex.invokeVirtual(aioMsgItem, "getMsgId") as Long + val msgIDs = java.util.ArrayList() + msgIDs.add(msgID) + AppRuntimeHelper.getAppRuntime() + ?.let { + MsgServiceHelper.getKernelMsgService( + it + ) + }?.getMsgsByMsgId( + Session.getContact(), + msgIDs + ) { _, _, msgList -> + SyncUtils.runOnUiThread { + for (msgRecord in msgList) { + if (!(msgRecord.msgType == 2 && msgRecord.subMsgType == 1)) { + Toasts.error(ContextUtils.getCurrentActivity(), "仅支持文本消息") + } else { + val elem = msgRecord.elements[0].textElement + if (elem == null) { + Toasts.error(ContextUtils.getCurrentActivity(), "仅支持文本消息") + } else { + val context = CommonContextWrapper.createAppCompatContext( + ContextUtils.getCurrentActivity()) + + val dialog = MaterialAlertDialogBuilder(context) + dialog.setTitle("修改文本消息") + + val content = EditText(context) + content.setHint("请输入修改后的内容") + content.setText(elem.content) + + dialog.setPositiveButton("修改") { dialogInterface, i -> + modifyMap.put("${msgRecord.peerUid}:${msgRecord.msgSeq}:${msgRecord.senderUin}", content.text.toString()) + Toasts.success(ContextUtils.getCurrentActivity(), "修改成功重新进入此界面生效") + dialogInterface.dismiss() + } + + dialog.setView(content) + dialog.show() + } + } + } + } + } + } catch (e: Exception) { + Logger.e("QQPullMsgEntry.msgLongClick", e) + } + Unit + } + param.result = listOf(item) + param.result as List<*> + } +} \ No newline at end of file diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index 7a34a44c..52688253 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -7,4 +7,5 @@ + \ No newline at end of file From ece956743c059c556eb885eadb81aa07aee88c30 Mon Sep 17 00:00:00 2001 From: Nacho Date: Thu, 2 Oct 2025 13:38:45 +0800 Subject: [PATCH 2/3] fix(QQBubbleRedirect): use toast show error --- .../java/moe/ono/creator/center/ClickableFunctionDialog.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/moe/ono/creator/center/ClickableFunctionDialog.kt b/app/src/main/java/moe/ono/creator/center/ClickableFunctionDialog.kt index 9d91c5e0..fd0038e9 100644 --- a/app/src/main/java/moe/ono/creator/center/ClickableFunctionDialog.kt +++ b/app/src/main/java/moe/ono/creator/center/ClickableFunctionDialog.kt @@ -27,6 +27,7 @@ import moe.ono.config.ONOConf import moe.ono.constants.Constants import moe.ono.hooks._base.BaseClickableFunctionHookItem import moe.ono.hooks._core.factory.HookItemFactory +import moe.ono.hooks.base.util.Toasts import moe.ono.hooks.item.chat.QQBubbleRedirect import moe.ono.hooks.item.chat.StickerPanelEntry import moe.ono.hooks.item.sigma.QQMessageTracker @@ -659,8 +660,7 @@ object ClickableFunctionDialog { builder.setPositiveButton("确定") { dialog, i -> val itemId = input.text.toString().trim() if (itemId.isEmpty()) { - warningText.text = "气泡 Item ID 不能为空" - dialog.dismiss() + Toasts.error(context, "ItemID 不能为空") return@setPositiveButton } QQBubbleRedirect.createCacheFile(itemId) From 49d9c5e745f9a2cf68c577908b147134b942e0ba Mon Sep 17 00:00:00 2001 From: Nacho Date: Thu, 2 Oct 2025 13:48:27 +0800 Subject: [PATCH 3/3] feat: respond face --- .../ono/hooks/dispatcher/MenuBuilderHook.kt | 4 +- .../hooks/item/entertainment/RespondFace.kt | 75 +++++++++++++++++++ app/src/main/res/values/ids.xml | 1 + 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/moe/ono/hooks/item/entertainment/RespondFace.kt diff --git a/app/src/main/java/moe/ono/hooks/dispatcher/MenuBuilderHook.kt b/app/src/main/java/moe/ono/hooks/dispatcher/MenuBuilderHook.kt index 984058e6..89caebd6 100644 --- a/app/src/main/java/moe/ono/hooks/dispatcher/MenuBuilderHook.kt +++ b/app/src/main/java/moe/ono/hooks/dispatcher/MenuBuilderHook.kt @@ -6,6 +6,7 @@ import moe.ono.hooks._core.annotation.HookItem import moe.ono.hooks.item.chat.StickerPanelEntry import moe.ono.hooks.item.developer.QQMessageFetcher import moe.ono.hooks.item.entertainment.ModifyTextMessage +import moe.ono.hooks.item.entertainment.RespondFace import moe.ono.hooks.item.sigma.QQMessageTracker import moe.ono.util.Logger import java.lang.reflect.Modifier @@ -16,7 +17,8 @@ class MenuBuilderHook : ApiHookItem() { StickerPanelEntry(), QQMessageTracker(), QQMessageFetcher(), - ModifyTextMessage() + ModifyTextMessage(), + RespondFace() ) override fun entry(classLoader: ClassLoader) { diff --git a/app/src/main/java/moe/ono/hooks/item/entertainment/RespondFace.kt b/app/src/main/java/moe/ono/hooks/item/entertainment/RespondFace.kt new file mode 100644 index 00000000..cce5ea2f --- /dev/null +++ b/app/src/main/java/moe/ono/hooks/item/entertainment/RespondFace.kt @@ -0,0 +1,75 @@ +package moe.ono.hooks.item.entertainment + +import android.annotation.SuppressLint +import de.robv.android.xposed.XC_MethodHook.MethodHookParam +import moe.ono.R +import moe.ono.bridge.ntapi.MsgServiceHelper +import moe.ono.hooks._base.BaseSwitchFunctionHookItem +import moe.ono.hooks._core.annotation.HookItem +import moe.ono.hooks._core.factory.HookItemFactory.getItem +import moe.ono.hooks.dispatcher.OnMenuBuilder +import moe.ono.hooks.protocol.sendPacket +import moe.ono.reflex.Reflex +import moe.ono.util.AppRuntimeHelper +import moe.ono.util.CustomMenu +import moe.ono.util.Logger +import moe.ono.util.Session +import moe.ono.util.SyncUtils +import kotlin.random.Random + + +@SuppressLint("DiscouragedApi") +@HookItem(path = "娱乐功能/回应表情", description = "回应 20 个小表情\n\n* 长按消息菜单 “回应” 按钮") +class RespondFace : BaseSwitchFunctionHookItem(), OnMenuBuilder { + + override fun entry(classLoader: ClassLoader) {} + + override fun onGetMenu(aioMsgItem: Any, targetType: String, param: MethodHookParam) { + if (!getItem(this.javaClass).isEnabled) { + return + } + + val item: Any = CustomMenu.createItemIconNt( + aioMsgItem, + "回应", + R.drawable.ic_baseline_auto_fix_high_24, + R.id.item_respond_face + ) { + try { + val msgID = Reflex.invokeVirtual(aioMsgItem, "getMsgId") as Long + val msgIDs = java.util.ArrayList() + msgIDs.add(msgID) + AppRuntimeHelper.getAppRuntime() + ?.let { + MsgServiceHelper.getKernelMsgService( + it + ) + }?.getMsgsByMsgId( + Session.getContact(), + msgIDs + ) { _, _, msgList -> + SyncUtils.runOnUiThread { + for (msgRecord in msgList) { + val peerUid = msgRecord.peerUid + val msgSeq = msgRecord.msgSeq + + Thread { + for (i in 1 .. 50) { + val faceId = Random.nextInt(1, 200) + val body = """{"1":36994,"2":1,"4":{"2":$peerUid,"3":$msgSeq,"4":"$faceId","5":1,"6":0,"7":0},"12":1}""" + + sendPacket("OidbSvcTrpcTcp.0x9082_2", body) + Thread.sleep(100) + } + }.start() + } + } + } + } catch (e: Exception) { + Logger.e("QQPullMsgEntry.msgLongClick", e) + } + Unit + } + param.result = listOf(item) + param.result as List<*> + } +} \ No newline at end of file diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index 52688253..6d2bb443 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -8,4 +8,5 @@ + \ No newline at end of file