From d883349b2e917eb34e66b8c6ed738761d3f90991 Mon Sep 17 00:00:00 2001 From: Nacho Date: Fri, 1 May 2026 12:25:14 +0800 Subject: [PATCH] feat: replace HintBlackShellUser with TagIcon and refactor message listeners - Remove `HintBlackShellUser` hook and its associated shortcut menu logic - Implement `TagIcon` (Birthmark) API hook to inject custom icons into message bubbles based on bubble attributes - Update `QQSendMsgListener` to support `MsgAttributeInfo` interception via new listeners - Generalize `QQMessageViewListener` to support generic `BaseHookItem` types, allowing non-switchable hooks to listen for view updates - Clean up `BottomShortcutMenu` by removing deleted feature entries --- .../hooks/base/api/QQMessageViewListener.kt | 31 +- .../ono/hooks/base/api/QQSendMsgListener.kt | 12 + .../java/moe/ono/hooks/base/api/TagIcon.kt | 136 ++++++++ .../ono/hooks/item/chat/BottomShortcutMenu.kt | 8 - .../ono/hooks/item/chat/HintBlackShellUser.kt | 329 ------------------ 5 files changed, 160 insertions(+), 356 deletions(-) create mode 100644 app/src/main/java/moe/ono/hooks/base/api/TagIcon.kt delete mode 100644 app/src/main/java/moe/ono/hooks/item/chat/HintBlackShellUser.kt diff --git a/app/src/main/java/moe/ono/hooks/base/api/QQMessageViewListener.kt b/app/src/main/java/moe/ono/hooks/base/api/QQMessageViewListener.kt index 83c3fd8b..cda63bdf 100644 --- a/app/src/main/java/moe/ono/hooks/base/api/QQMessageViewListener.kt +++ b/app/src/main/java/moe/ono/hooks/base/api/QQMessageViewListener.kt @@ -1,34 +1,19 @@ package moe.ono.hooks.base.api -import android.content.Context -import android.os.Bundle -import android.util.Log import android.view.View import com.tencent.qqnt.kernel.nativeinterface.MsgRecord -import de.robv.android.xposed.XposedBridge import moe.ono.bridge.kernelcompat.ContactCompat -import moe.ono.config.ConfigManager import moe.ono.config.ONOConf -import moe.ono.constants.Constants import moe.ono.hooks._base.ApiHookItem +import moe.ono.hooks._base.BaseHookItem import moe.ono.hooks._base.BaseSwitchFunctionHookItem import moe.ono.hooks._core.annotation.HookItem import moe.ono.hooks._core.factory.ExceptionFactory -import moe.ono.hooks._core.factory.HookItemFactory -import moe.ono.hooks.item.chat.SelfMessageReactor -import moe.ono.hooks.protocol.buildMessage -import moe.ono.hooks.protocol.sendPacket -import moe.ono.hostInfo import moe.ono.reflex.ClassUtils import moe.ono.reflex.FieldUtils -import moe.ono.reflex.Ignore import moe.ono.reflex.MethodUtils -import moe.ono.service.QQInterfaces -import moe.ono.util.Initiator.loadClass import moe.ono.util.Logger import moe.ono.util.QAppUtils -import java.lang.ref.WeakReference -import java.lang.reflect.Method @HookItem(path = "API/监听QQMsgView更新") @@ -37,7 +22,7 @@ class QQMessageViewListener : ApiHookItem() { companion object { - private val ON_AIO_CHAT_VIEW_UPDATE_LISTENER_MAP: HashMap = + private val ON_AIO_CHAT_VIEW_UPDATE_LISTENER_MAP: HashMap = HashMap() /** @@ -45,7 +30,7 @@ class QQMessageViewListener : ApiHookItem() { */ @JvmStatic fun addMessageViewUpdateListener( - hookItem: BaseSwitchFunctionHookItem, + hookItem: BaseHookItem, onMsgViewUpdateListener: OnChatViewUpdateListener ) { ON_AIO_CHAT_VIEW_UPDATE_LISTENER_MAP[hookItem] = onMsgViewUpdateListener @@ -85,7 +70,15 @@ class QQMessageViewListener : ApiHookItem() { for ((switchFunctionHookItem, listener) in ON_AIO_CHAT_VIEW_UPDATE_LISTENER_MAP.entries) { - if (switchFunctionHookItem.isEnabled) { + if (switchFunctionHookItem is BaseSwitchFunctionHookItem) { + if (switchFunctionHookItem.isEnabled) { + try { + listener.onViewUpdateAfter(msgView, msgRecord) + } catch (e: Throwable) { + ExceptionFactory.add(switchFunctionHookItem, e) + } + } + } else { try { listener.onViewUpdateAfter(msgView, msgRecord) } catch (e: Throwable) { diff --git a/app/src/main/java/moe/ono/hooks/base/api/QQSendMsgListener.kt b/app/src/main/java/moe/ono/hooks/base/api/QQSendMsgListener.kt index 5b8304f4..6c620281 100644 --- a/app/src/main/java/moe/ono/hooks/base/api/QQSendMsgListener.kt +++ b/app/src/main/java/moe/ono/hooks/base/api/QQSendMsgListener.kt @@ -1,6 +1,7 @@ package moe.ono.hooks.base.api import android.text.TextUtils +import com.tencent.qqnt.kernel.nativeinterface.MsgAttributeInfo import com.tencent.qqnt.kernel.nativeinterface.MsgElement import de.robv.android.xposed.XC_MethodHook import moe.ono.config.ONOConf @@ -18,11 +19,20 @@ class QQSendMsgListener : ApiHookItem() { hookBefore(sendMsgMethod) { param -> val elements = param.args[2] as ArrayList + val attributeInfos = param.args[3] as java.util.HashMap for (listener in listeners) { listener(param, elements) } + for (attributeInfoListener in attributeInfoListeners) { + attributeInfoListener(param, attributeInfos) + } + + for (allListener in allListeners) { + allListener(param, elements, attributeInfos) + } + if (ONOConf.getBoolean("global", "sticker_panel_set_ch_change_title", false)) { val text: String = ONOConf.getString("global", "sticker_panel_set_ed_change_title", "") @@ -40,5 +50,7 @@ class QQSendMsgListener : ApiHookItem() { companion object { val listeners = mutableListOf<(param: XC_MethodHook.MethodHookParam, elems: ArrayList) -> Unit>() + val attributeInfoListeners = mutableListOf<(param: XC_MethodHook.MethodHookParam, attributeInfos: HashMap) -> Unit>() + val allListeners = mutableListOf<(param: XC_MethodHook.MethodHookParam, elems: ArrayList, attributeInfos: HashMap) -> Unit>() } } \ No newline at end of file diff --git a/app/src/main/java/moe/ono/hooks/base/api/TagIcon.kt b/app/src/main/java/moe/ono/hooks/base/api/TagIcon.kt new file mode 100644 index 00000000..88446a0a --- /dev/null +++ b/app/src/main/java/moe/ono/hooks/base/api/TagIcon.kt @@ -0,0 +1,136 @@ +package moe.ono.hooks.base.api + +import android.content.Context +import android.graphics.drawable.GradientDrawable +import android.view.View +import android.view.ViewGroup +import android.view.ViewOutlineProvider +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import androidx.core.view.children +import moe.ono.R +import moe.ono.hooks._base.ApiHookItem +import moe.ono.hooks._core.annotation.HookItem +import moe.ono.reflex.FieldUtils +import moe.ono.util.AppRuntimeHelper +import moe.ono.util.Logger + +@HookItem(path = "API/胎记") +class TagIcon : ApiHookItem() { + override fun entry(classLoader: ClassLoader) { + QQSendMsgListener.attributeInfoListeners.add { _, attributeInfos -> + attributeInfos[0 as Integer]?.let { + it.vasMsgInfo?.bubbleInfo?.bubbleDiyTextId = 114515 + attributeInfos[0 as Integer] = it + } + } + + QQMessageViewListener.addMessageViewUpdateListener( + this, + object : QQMessageViewListener.OnChatViewUpdateListener { + override fun onViewUpdateAfter( + msgItemView: View, + msgRecord: Any + ) { + try { + val msgAttrs: HashMap = FieldUtils.create(msgRecord) + .fieldName("msgAttrs") + .fieldType(HashMap::class.java) + .firstValue(msgRecord) + + val msgAttr = msgAttrs[0 as Integer] ?: return + + val vasMsgInfo: Any = FieldUtils.create(msgAttr) + .fieldName("vasMsgInfo") + .firstValue(msgAttr) + + val bubbleInfo: Any = FieldUtils.create(vasMsgInfo) + .fieldName("bubbleInfo") + .firstValue(vasMsgInfo) + + val bubbleDiyTextId: Integer? = FieldUtils.create(bubbleInfo) + .fieldName("bubbleDiyTextId") + .fieldType(Integer::class.java) + .firstValue(bubbleInfo) + + val senderUin: Long = FieldUtils.create(msgRecord) + .fieldName("senderUin") + .fieldType(Long::class.java) + .firstValue(msgRecord) + + val rootView = msgItemView as ViewGroup + if (!QQMsgViewAdapter.hasContentMessage(rootView)) return + + val targetLayout = rootView.children.find { + if (it is FrameLayout) { + if (it.childCount == 1 && it.getChildAt(0) is LinearLayout) { + val linearLayout = it.getChildAt(0) as LinearLayout + if ( + (linearLayout.childCount == 2 && + linearLayout.children.find { it1 -> it1.tag == 114514 } != null) || + (linearLayout.childCount == 1 && + linearLayout.getChildAt(0) is LinearLayout) + ) { + return@find true + } + } + } + false + } + + if (targetLayout is FrameLayout) { + val target = targetLayout.getChildAt(0) as? ViewGroup ?: return + + if (target.children.find { it.tag == 114514 } != null) { + if (bubbleDiyTextId != 114515 as Integer) { + target.children.toList().forEach { + if (it.tag == 114514) { + target.removeView(it) + } + } + } + return + } + + val size = + (16 * target.context.resources.displayMetrics.density).toInt() + val margin = + (4 * target.context.resources.displayMetrics.density).toInt() + + fun dp(context: Context, value: Float): Float { + return value * context.resources.displayMetrics.density + } + + val icon = ImageView(target.context).apply { + setImageResource(R.drawable.ic_ouo) + scaleType = ImageView.ScaleType.FIT_CENTER + adjustViewBounds = true + layoutParams = ViewGroup.MarginLayoutParams(size, size).apply { + leftMargin = margin + rightMargin = margin + } + tag = 114514 + clipToOutline = true + outlineProvider = ViewOutlineProvider.BACKGROUND + background = GradientDrawable().apply { + cornerRadius = dp(context, 3f) + } + } + + if (bubbleDiyTextId == 114515 as Integer) { + if (senderUin == AppRuntimeHelper.getLongAccountUin()) { + target.addView(icon) + } else { + target.addView(icon, 0) + } + } + } + } catch (e: Exception) { + Logger.e(e) + } + } + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/moe/ono/hooks/item/chat/BottomShortcutMenu.kt b/app/src/main/java/moe/ono/hooks/item/chat/BottomShortcutMenu.kt index 91fe3dc6..143cb86b 100644 --- a/app/src/main/java/moe/ono/hooks/item/chat/BottomShortcutMenu.kt +++ b/app/src/main/java/moe/ono/hooks/item/chat/BottomShortcutMenu.kt @@ -181,10 +181,6 @@ class BottomShortcutMenu : BaseClickableFunctionHookItem() { val bigForward = ConfigManager.getDefaultConfig().getBooleanOrFalse(Constants.PrekXXX + getItem( BigForward::class.java).path) - val hintBlackShellUser = ConfigManager.getDefaultConfig().getBooleanOrFalse(Constants.PrekXXX + getItem( - HintBlackShellUser::class.java).path) - - // 定义菜单分组结构 data class MenuItem(val label: String, val action: () -> Unit) data class MenuGroup(val groupName: String, val items: List) @@ -264,10 +260,6 @@ class BottomShortcutMenu : BaseClickableFunctionHookItem() { val item = getItem(FakeLocationShare::class.java) if (item is IShortcutMenu) item.clickHandle(view.context) }) - if (hintBlackShellUser) msgItems.add(MenuItem("自定义黑色壳子用户底部 Tag") { - val item = getItem(HintBlackShellUser::class.java) - if (item is IShortcutMenu) item.clickHandle(view.context) - }) msgItems.add(MenuItem("匿名化"){ autoMosaicNameNT() }) diff --git a/app/src/main/java/moe/ono/hooks/item/chat/HintBlackShellUser.kt b/app/src/main/java/moe/ono/hooks/item/chat/HintBlackShellUser.kt deleted file mode 100644 index 3edb47a0..00000000 --- a/app/src/main/java/moe/ono/hooks/item/chat/HintBlackShellUser.kt +++ /dev/null @@ -1,329 +0,0 @@ -package moe.ono.hooks.item.chat - -import android.annotation.SuppressLint -import android.graphics.Color -import android.graphics.drawable.GradientDrawable -import android.view.Gravity -import android.content.Context -import moe.ono.hooks.base.util.Toasts -import androidx.core.view.children -import android.os.Handler -import android.os.Looper -import android.view.View -import android.view.ViewGroup -import android.widget.LinearLayout -import android.widget.TextView -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.constraintlayout.widget.ConstraintSet -import com.github.kyuubiran.ezxhelper.utils.argTypes -import com.github.kyuubiran.ezxhelper.utils.args -import com.github.kyuubiran.ezxhelper.utils.invokeMethod -import com.github.kyuubiran.ezxhelper.utils.newInstance -import com.lxj.xpopup.util.XPopupUtils -import moe.ono.hooks._base.BaseSwitchFunctionHookItem -import moe.ono.hooks._core.annotation.HookItem -import moe.ono.hooks.base.api.QQMessageViewListener -import moe.ono.hooks.base.api.QQMsgViewAdapter -import moe.ono.hooks.clazz -import moe.ono.reflex.ClassUtils -import moe.ono.reflex.ConstructorUtils -import moe.ono.reflex.FieldUtils -import moe.ono.util.AppRuntimeHelper -import moe.ono.util.QAppUtils -import org.json.JSONArray -import org.json.JSONObject -import java.io.File -import java.net.HttpURLConnection -import android.os.Environment -import java.net.URL -import de.robv.android.xposed.XposedBridge -import moe.ono.config.ConfigManager -import moe.ono.constants.Constants -import moe.ono.creator.PacketHelperDialog -import moe.ono.hooks._core.factory.HookItemFactory.getItem -import moe.ono.hooks.item.developer.NoReport -import moe.ono.loader.hookapi.IShortcutMenu -import moe.ono.ui.CommonContextWrapper -import moe.ono.util.SyncUtils -import moe.ono.util.analytics.ActionReporter.reportVisitor -import java.security.MessageDigest - -@HookItem( - path = "聊天与消息/提示黑色壳子用户", - description = "在黑色壳子用户的消息下面显示提示是黑色壳子用户\n\n* 对方开启 开发者选项/关掉操作上报 后提示不了\n* 快捷菜单里面可以自定义黑色壳子用户底部Tag" -) -class HintBlackShellUser : BaseSwitchFunctionHookItem(), IShortcutMenu { - override fun isAdd(): Boolean { - return this.isEnabled - } - - override val menuName: String - get() = "自定义黑色壳子用户底部Tag" - - private val ID_HINT_LAYOUT = 0x114520 - private val ID_HINT_TEXTVIEW = 0x114510 - - private val constraintSetClz by lazy { "androidx.constraintlayout.widget.ConstraintSet".clazz!! } - private val constraintLayoutClz by lazy { "androidx.constraintlayout.widget.ConstraintLayout".clazz!! } - - private var blacklistMd5Set: Set = emptySet() - private var lastLoadTime = 0L - - // 自定义 Tag,默认值为 "黑色壳子用户" - private var customTag: String = "黑色壳子用户" - private val TAG_FILE_PATH = "/sdcard/Android/media/com.tencent.mobileqq/blackshell/blackshell_usertag.txt" - - override fun entry(classLoader: ClassLoader) { - - loadList() - Thread { - try { - val noReport = ConfigManager.getDefaultConfig() - .getBooleanOrFalse(Constants.PrekXXX + getItem(NoReport::class.java).path) - - val file = File( - Environment.getExternalStorageDirectory().path + - "/Android/media/com.tencent.mobileqq/blackshell/users.json" - ) - - if (file.exists() && System.currentTimeMillis() - file.lastModified() < 3600_000) { - return@Thread - } - - file.parentFile?.mkdirs() - - val uin = QAppUtils.getCurrentUin().toLong() - if (uin == 0L) return@Thread - - val req = JSONObject().apply { - put("uin", uin) - put("noReport", noReport) - } - - val conn = (URL("https://service.blackshellx.org/api/v1/users").openConnection() as HttpURLConnection).apply { - requestMethod = "POST" - connectTimeout = 5000 - readTimeout = 5000 - doOutput = true - setRequestProperty("Content-Type", "application/json") - } - - conn.outputStream.use { it.write(req.toString().toByteArray(Charsets.UTF_8)) } - - val response = conn.inputStream.bufferedReader().use { it.readText() } - - val arr = JSONObject(response).getJSONArray("users_md5") - - file.writeText(arr.toString()) - - } catch (t: Throwable) { - XposedBridge.log(t) - } - }.start() - QQMessageViewListener.addMessageViewUpdateListener( - this, - object : QQMessageViewListener.OnChatViewUpdateListener { - override fun onViewUpdateAfter(msgItemView: View, msgRecord: Any) { - val rootView = msgItemView as ViewGroup - if (!QQMsgViewAdapter.hasContentMessage(rootView)) return - - // 移除旧提示(View 复用时清理) - rootView.findViewById(ID_HINT_LAYOUT)?.let { - rootView.removeView(it) - } - - val senderUin: Long = FieldUtils.create(msgRecord) - .fieldName("senderUin") - .fieldType(Long::class.java) - .firstValue(msgRecord) - - // 自己发的不提示 - if (senderUin == AppRuntimeHelper.getLongAccountUin()) return - - // 超过1小时重新读文件 - if (System.currentTimeMillis() - lastLoadTime > 3600_000) { - loadList() - } - - if (isBlackShellUser(senderUin)) { - addHintView(rootView, senderUin) - } - } - } - ) - } - - private fun isBlackShellUser(uin: Long): Boolean { - val md5 = md5(uin.toString()) - return blacklistMd5Set.contains(md5) - } - - private fun loadList() { - try { - val file = File("/sdcard/Android/media/com.tencent.mobileqq/blackshell/users.json") - if (!file.exists()) return - val arr = JSONArray(file.readText()) - blacklistMd5Set = (0 until arr.length()).map { arr.getString(it) }.toHashSet() - lastLoadTime = System.currentTimeMillis() - } catch (e: Exception) { - // 读取失败不影响其他功能 - } - - // 读取自定义 Tag - try { - val tagFile = File(TAG_FILE_PATH) - if (tagFile.exists()) { - val tag = tagFile.readText().trim() - if (tag.isNotEmpty()) customTag = tag - } - } catch (e: Exception) { - // 读取 Tag 失败时保持默认值 - } - } - - private fun md5(input: String): String { - val bytes = MessageDigest.getInstance("MD5").digest(input.toByteArray()) - return bytes.joinToString("") { "%02x".format(it) } - } - - @SuppressLint("SetTextI18n") - private fun addHintView(rootView: ViewGroup, senderUin: Long) { - val parentLayoutId = rootView.id - val contentId: Int = QQMsgViewAdapter.getContentViewId() - - val newLayoutParams = ConstructorUtils.newInstance( - ClassUtils.findClass("androidx.constraintlayout.widget.ConstraintLayout\$LayoutParams"), - arrayOf?>(Int::class.javaPrimitiveType, Int::class.javaPrimitiveType), - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) as ViewGroup.LayoutParams - - FieldUtils.create(newLayoutParams).fieldName("startToStart").setFirst(newLayoutParams, parentLayoutId) - FieldUtils.create(newLayoutParams).fieldName("endToEnd").setFirst(newLayoutParams, parentLayoutId) - FieldUtils.create(newLayoutParams).fieldName("topToTop").setFirst(newLayoutParams, contentId) - - val layout = LinearLayout(rootView.context).apply { - layoutParams = ConstraintLayout.LayoutParams( - 0, // MATCH_CONSTRAINT - ConstraintLayout.LayoutParams.WRAP_CONTENT - ) - id = ID_HINT_LAYOUT - val drawable = GradientDrawable() - drawable.shape = GradientDrawable.RECTANGLE - drawable.setColor(Color.BLACK) - drawable.cornerRadius = 10f - drawable.alpha = 0x22 - background = drawable - - val _4 = XPopupUtils.dp2px(rootView.context, 4f) - val _6 = XPopupUtils.dp2px(rootView.context, 6f) - setPadding(_6, _4, _6, _4) - } - - val textView = TextView(rootView.context).apply { - id = ID_HINT_TEXTVIEW - textSize = 15f - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ) - setTextColor(Color.WHITE) - text = customTag // 使用自定义 Tag - } - - layout.gravity = Gravity.CENTER - layout.addView(textView) - rootView.addView(layout) - - val constraintSet = constraintSetClz.newInstance(args())!! - constraintSet.invokeMethod("clone", args(rootView), argTypes(constraintLayoutClz)) - - val iMsg = rootView.children.indexOfFirst { it is LinearLayout && it.id != View.NO_ID } - val idMsg = rootView.getChildAt(iMsg).id - val idName = rootView.getChildAt(iMsg - 1).id - - constraintSet.invokeMethod( - "connect", - args(ID_HINT_LAYOUT, ConstraintLayout.LayoutParams.TOP, idMsg, ConstraintLayout.LayoutParams.BOTTOM, 0), - argTypes(Int::class.java, Int::class.java, Int::class.java, Int::class.java, Int::class.java) - ) - - if (senderUin != AppRuntimeHelper.getLongAccountUin()) { - constraintSet.invokeMethod( - "connect", - args(ID_HINT_LAYOUT, ConstraintSet.LEFT, idName, ConstraintSet.LEFT), - argTypes(Int::class.java, Int::class.java, Int::class.java, Int::class.java) - ) - constraintSet.invokeMethod( - "setMargin", - args(ID_HINT_LAYOUT, ConstraintSet.START, XPopupUtils.dp2px(rootView.context, 10f)), - argTypes(Int::class.java, Int::class.java, Int::class.java) - ) - } else { - constraintSet.invokeMethod( - "connect", - args(ID_HINT_LAYOUT, ConstraintSet.RIGHT, idName, ConstraintSet.RIGHT), - argTypes(Int::class.java, Int::class.java, Int::class.java, Int::class.java) - ) - constraintSet.invokeMethod( - "setMargin", - args(ID_HINT_LAYOUT, ConstraintSet.END, XPopupUtils.dp2px(rootView.context, 10f)), - argTypes(Int::class.java, Int::class.java, Int::class.java) - ) - } - - constraintSet.invokeMethod("applyTo", args(rootView), argTypes(constraintLayoutClz)) - } - - override fun clickHandle(context: Context) { - val fixContext = CommonContextWrapper.createAppCompatContext(context) - reportVisitor(AppRuntimeHelper.getAccount(), "CreateView-SetBlackShellUserTag") - - val etText = android.widget.EditText(fixContext).apply { - hint = "要设置的 Tag" - setText(customTag) // 预填当前 Tag - layoutParams = android.widget.LinearLayout.LayoutParams( - android.widget.LinearLayout.LayoutParams.MATCH_PARENT, - android.widget.LinearLayout.LayoutParams.WRAP_CONTENT - ) - } - - val root = android.widget.LinearLayout(fixContext).apply { - orientation = android.widget.LinearLayout.VERTICAL - setPadding(60, 40, 60, 20) - addView(etText) - } - - val dialog = android.app.AlertDialog.Builder(fixContext) - .setTitle("自定义黑色壳子用户底部 Tag") - .setView(root) - .setPositiveButton("设置", null) - .setNegativeButton("取消", null) - .create() - - dialog.setOnShowListener { - dialog.getButton(android.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener { - val text = etText.text.toString().also { - if (it.isEmpty()) { - Toasts.error(context, "请输入要设置的 Tag") - return@setOnClickListener - } - } - dialog.dismiss() - // 写入文件 /sdcard/Android/media/com.tencent.mobileqq/blackshell/blackshell_usertag.txt - try { - val tagFile = File(TAG_FILE_PATH) - tagFile.parentFile?.mkdirs() - tagFile.writeText(text) - customTag = text - Toasts.success(context, "Tag 已设置为:$text") - Toasts.success(context, "重新进入会话生效!") - } catch (e: Exception) { - Toasts.error(context, "写入失败:${e.message}") - } - } - } - - dialog.show() - } -} \ No newline at end of file