11package moe.ouom.wekit.hooks.item.chat.risk
22
33import android.annotation.SuppressLint
4+ import android.app.Activity
45import android.content.Context
6+ import android.content.Intent
57import android.widget.EditText
68import com.afollestad.materialdialogs.MaterialDialog
79import com.google.android.material.dialog.MaterialAlertDialogBuilder
10+ import de.robv.android.xposed.XC_MethodHook
11+ import de.robv.android.xposed.XposedBridge
812import de.robv.android.xposed.XposedHelpers
913import moe.ouom.wekit.core.model.BaseHookItem
1014import moe.ouom.wekit.core.model.BaseSwitchFunctionHookItem
@@ -14,10 +18,107 @@ import moe.ouom.wekit.hooks.sdk.api.WeMessageApi
1418import moe.ouom.wekit.hooks.sdk.ui.WeChatFooterApi
1519import moe.ouom.wekit.loader.hookapi.IMenu
1620import 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 = " 长按'发送'按钮,自动发送卡片消息或快捷菜单" )
2026class 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 }
0 commit comments