Skip to content
This repository was archived by the owner on Jul 15, 2026. It is now read-only.

Commit 5170ebd

Browse files
committed
feat: migrate image picker and enhance XML message sending
- Deleted `FuckThumbUrl.kt` and moved `HookImagePicker` utility to `WeSendXml.kt`. - Updated `WeSendXml` to require image selection via `HookImagePicker` before sending AppMsg (XML). - Added a new `sendXmlAppMsg` overload to `WeMessageApi` that accepts `ByteArray` for image data.
1 parent f4b706f commit 5170ebd

3 files changed

Lines changed: 117 additions & 167 deletions

File tree

app/src/main/java/moe/ouom/wekit/hooks/item/chat/msg/FuckThumbUrl.kt

Lines changed: 0 additions & 163 deletions
This file was deleted.

app/src/main/java/moe/ouom/wekit/hooks/item/chat/risk/WeSendXml.kt

Lines changed: 109 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package moe.ouom.wekit.hooks.item.chat.risk
22

33
import android.annotation.SuppressLint
4+
import android.app.Activity
45
import android.content.Context
6+
import android.content.Intent
57
import android.widget.EditText
68
import com.afollestad.materialdialogs.MaterialDialog
79
import com.google.android.material.dialog.MaterialAlertDialogBuilder
10+
import de.robv.android.xposed.XC_MethodHook
11+
import de.robv.android.xposed.XposedBridge
812
import de.robv.android.xposed.XposedHelpers
913
import moe.ouom.wekit.core.model.BaseHookItem
1014
import moe.ouom.wekit.core.model.BaseSwitchFunctionHookItem
@@ -14,10 +18,107 @@ import moe.ouom.wekit.hooks.sdk.api.WeMessageApi
1418
import moe.ouom.wekit.hooks.sdk.ui.WeChatFooterApi
1519
import moe.ouom.wekit.loader.hookapi.IMenu
1620
import moe.ouom.wekit.util.common.Toasts
21+
import moe.ouom.wekit.util.common.Utils
22+
import moe.ouom.wekit.util.log.WeLogger
1723

1824
@SuppressLint("DiscouragedApi")
1925
@HookItem(path = "聊天与消息/发送 AppMsg(XML)", desc = "长按'发送'按钮,自动发送卡片消息或快捷菜单")
2026
class WeSendXml : BaseSwitchFunctionHookItem() {
27+
object HookImagePicker {
28+
private const val REQUEST_CODE = 0x4417
29+
private var callback: ((ByteArray?) -> Unit)? = null
30+
private val hookedClasses = HashSet<Class<*>>()
31+
32+
fun pick(activity: Activity, callback: (ByteArray?) -> Unit) {
33+
this.callback = callback
34+
35+
WeLogger.d("HookImagePicker.pick activity=${activity.javaClass.name}")
36+
37+
hookOnActivityResultIfNeeded(activity.javaClass)
38+
39+
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
40+
type = "image/*"
41+
addCategory(Intent.CATEGORY_OPENABLE)
42+
}
43+
44+
activity.startActivityForResult(
45+
Intent.createChooser(intent, "选择图片"),
46+
REQUEST_CODE
47+
)
48+
}
49+
50+
private fun hookOnActivityResultIfNeeded(clazz: Class<*>) {
51+
var current: Class<*>? = clazz
52+
53+
while (current != null && Activity::class.java.isAssignableFrom(current)) {
54+
if (hookedClasses.contains(current)) {
55+
current = current.superclass
56+
continue
57+
}
58+
59+
val declared = current.declaredMethods.any {
60+
it.name == "onActivityResult" &&
61+
it.parameterTypes.size == 3 &&
62+
it.parameterTypes[0] == Int::class.javaPrimitiveType &&
63+
it.parameterTypes[1] == Int::class.javaPrimitiveType &&
64+
Intent::class.java.isAssignableFrom(it.parameterTypes[2])
65+
}
66+
67+
if (declared) {
68+
hookedClasses.add(current)
69+
70+
XposedBridge.hookAllMethods(current, "onActivityResult", object : XC_MethodHook() {
71+
override fun afterHookedMethod(param: MethodHookParam) {
72+
val activity = param.thisObject as? Activity ?: return
73+
val requestCode = param.args.getOrNull(0) as? Int ?: return
74+
val resultCode = param.args.getOrNull(1) as? Int ?: return
75+
val data = param.args.getOrNull(2) as? Intent
76+
77+
WeLogger.d(
78+
"HookImagePicker.onActivityResult hit activity=${activity.javaClass.name}, " +
79+
"requestCode=$requestCode, resultCode=$resultCode, data=$data"
80+
)
81+
82+
onActivityResult(activity, requestCode, resultCode, data)
83+
}
84+
})
85+
86+
WeLogger.d("HookImagePicker hooked onActivityResult in ${current.name}")
87+
return
88+
}
89+
90+
current = current.superclass
91+
}
92+
93+
WeLogger.e("HookImagePicker: no declared onActivityResult found for ${clazz.name}")
94+
}
95+
96+
fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
97+
if (requestCode != REQUEST_CODE) return
98+
99+
val cb = callback
100+
callback = null
101+
102+
if (resultCode != Activity.RESULT_OK) {
103+
cb?.invoke(null)
104+
return
105+
}
106+
107+
val uri = data?.data ?: run {
108+
cb?.invoke(null)
109+
return
110+
}
111+
112+
val bytes = runCatching {
113+
activity.contentResolver.openInputStream(uri)?.use { it.readBytes() }
114+
}.onFailure {
115+
WeLogger.e("read image bytes failed: ${it.stackTraceToString()}")
116+
}.getOrNull()
117+
118+
cb?.invoke(bytes)
119+
}
120+
}
121+
21122
override fun entry(classLoader: ClassLoader) {
22123
ShortcutMenu.menus.add(object : IMenu {
23124
override val creator: BaseHookItem
@@ -35,10 +136,14 @@ class WeSendXml : BaseSwitchFunctionHookItem() {
35136

36137
setView(xml)
37138
setPositiveButton("发送") { _, _ ->
38-
val xmlContent = xml.text.toString()
39-
WeMessageApi.INSTANCE?.sendXmlAppMsg(XposedHelpers.getAdditionalInstanceField(footer,
40-
WeChatFooterApi.FIELD_TO_USER) as String, xmlContent)
41-
Toasts.showToast(context, "发送成功")
139+
Toasts.showToast(context, "请选择图片")
140+
HookImagePicker.pick(Utils.getCurrentActivity()!!) { bytes ->
141+
val xmlContent = xml.text.toString()
142+
if (bytes == null) return@pick Toasts.showToast(context, "失败")
143+
WeMessageApi.INSTANCE?.sendXmlAppMsg(XposedHelpers.getAdditionalInstanceField(footer,
144+
WeChatFooterApi.FIELD_TO_USER) as String, xmlContent, bytes)
145+
Toasts.showToast(context, "发送成功")
146+
}
42147
}
43148
}.show()
44149
}

app/src/main/java/moe/ouom/wekit/hooks/sdk/api/WeMessageApi.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,14 @@ class WeMessageApi : ApiHookItem(), IDexFind {
724724
return WeAppMsgApi.INSTANCE?.sendXmlAppMsg(toUser, title, appId, null, null, xmlContent) ?: false
725725
}
726726

727+
fun sendXmlAppMsg(toUser: String, xmlContent: String, data: ByteArray): Boolean {
728+
val appId = extractXmlAttr(xmlContent, "appid")
729+
val title = extractXmlTag(xmlContent, "title")
730+
731+
WeLogger.d(TAG, "解析信息: AppId=$appId, Title=$title")
732+
return WeAppMsgApi.INSTANCE?.sendXmlAppMsg(toUser, title, appId, null, data, xmlContent) ?: false
733+
}
734+
727735
/**
728736
* 使用微信内部 VFS 引擎进行物理拷贝
729737
*/

0 commit comments

Comments
 (0)