refactor: MenuBuilderHook for QQNT
This commit is contained in:
@@ -51,6 +51,7 @@ import com.tencent.qqnt.kernel.nativeinterface.Contact;
|
||||
import com.tencent.qqnt.kernel.nativeinterface.IKernelMsgService;
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgAttributeInfo;
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord;
|
||||
import com.xiaoniu.dispatcher.OnMenuBuilder;
|
||||
import com.xiaoniu.util.ContextUtils;
|
||||
import de.robv.android.xposed.XC_MethodHook;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
@@ -75,7 +76,6 @@ import io.github.qauxv.util.dexkit.DexKit;
|
||||
import io.github.qauxv.util.dexkit.DexKitTarget;
|
||||
import io.github.qauxv.util.dexkit.VasAttrBuilder;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -90,7 +90,7 @@ import kotlinx.coroutines.flow.MutableStateFlow;
|
||||
|
||||
@FunctionHookEntry
|
||||
@UiItemAgentEntry
|
||||
public class RepeaterPlus extends BaseFunctionHook implements SessionHooker.IAIOParamUpdate {
|
||||
public class RepeaterPlus extends BaseFunctionHook implements SessionHooker.IAIOParamUpdate, OnMenuBuilder {
|
||||
|
||||
public static final RepeaterPlus INSTANCE = new RepeaterPlus();
|
||||
|
||||
@@ -230,58 +230,6 @@ public class RepeaterPlus extends BaseFunctionHook implements SessionHooker.IAIO
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
Class msgClass = Initiator.loadClass("com.tencent.mobileqq.aio.msg.AIOMsgItem");
|
||||
String[] component = new String[]{
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.text.AIOTextContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.pic.AIOPicContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.reply.AIOReplyComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.anisticker.AIOAniStickerContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.video.AIOVideoContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.multifoward.AIOMultifowardContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.longmsg.AIOLongMsgContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.mix.AIOMixContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.ark.AIOArkContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.file.AIOFileContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.LocationShare.AIOLocationShareComponent"
|
||||
};
|
||||
Method getMsg = null;
|
||||
String absListMethod = null;
|
||||
Method[] methods = Initiator.loadClass("com.tencent.mobileqq.aio.msglist.holder.component.BaseContentComponent").getDeclaredMethods();
|
||||
for (Method method : methods) {
|
||||
if (getMsg == null && method.getReturnType() == msgClass && method.getParameterTypes().length == 0) {
|
||||
getMsg = method;
|
||||
getMsg.setAccessible(true);
|
||||
} else if (absListMethod == null && Modifier.isAbstract(method.getModifiers()) && method.getReturnType() == List.class
|
||||
&& method.getParameterTypes().length == 0) {
|
||||
absListMethod = method.getName();
|
||||
}
|
||||
}
|
||||
Method finalGetMsg = getMsg;
|
||||
for (String s : component) {
|
||||
Class componentClazz = Initiator.loadClass(s);
|
||||
Method listMethod = componentClazz.getMethod(absListMethod);
|
||||
HookUtils.hookAfterIfEnabled(this, listMethod, param -> {
|
||||
if (ContextUtils.getCurrentActivity().getClass().getName().contains("MultiForwardActivity")) {
|
||||
return;
|
||||
}
|
||||
Object msg = finalGetMsg.invoke(param.thisObject);
|
||||
Object item = CustomMenu.createItemNt(msg, "+1", R.id.item_repeat, () -> {
|
||||
if (isMessageRepeatable(msg)) {
|
||||
repeatByForwardNt(msg);
|
||||
} else {
|
||||
Toasts.error(ContextUtils.getCurrentActivity(), "该消息不支持复读");
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
List list = (List) param.getResult();
|
||||
List result = new ArrayList<>();
|
||||
result.add(0, item);
|
||||
result.addAll(list);
|
||||
param.setResult(result);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -451,4 +399,44 @@ public class RepeaterPlus extends BaseFunctionHook implements SessionHooker.IAIO
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String[] getTargetComponentTypes() {
|
||||
return new String[]{
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.text.AIOTextContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.pic.AIOPicContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.reply.AIOReplyComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.anisticker.AIOAniStickerContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.video.AIOVideoContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.multifoward.AIOMultifowardContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.longmsg.AIOLongMsgContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.mix.AIOMixContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.ark.AIOArkContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.file.AIOFileContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.LocationShare.AIOLocationShareComponent"
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetMenuNt(@NonNull Object msg, @NonNull String componentType, @NonNull XC_MethodHook.MethodHookParam param) throws Exception {
|
||||
if (!isEnabled() || !RepeaterPlusIconSettingDialog.getIsShowInMenu()) {
|
||||
return;
|
||||
}
|
||||
if (ContextUtils.getCurrentActivity().getClass().getName().contains("MultiForwardActivity")) {
|
||||
return;
|
||||
}
|
||||
Object item = CustomMenu.createItemNt(msg, "+1", R.id.item_repeat, () -> {
|
||||
if (isMessageRepeatable(msg)) {
|
||||
repeatByForwardNt(msg);
|
||||
} else {
|
||||
Toasts.error(ContextUtils.getCurrentActivity(), "该消息不支持复读");
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
List list = (List) param.getResult();
|
||||
List result = new ArrayList<>();
|
||||
result.add(0, item);
|
||||
result.addAll(list);
|
||||
param.setResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,9 @@ import com.tencent.qqnt.kernel.nativeinterface.IKernelMsgService;
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement;
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord;
|
||||
import com.tencent.qqnt.kernel.nativeinterface.PicElement;
|
||||
import com.xiaoniu.dispatcher.OnMenuBuilder;
|
||||
import com.xiaoniu.util.ContextUtils;
|
||||
import de.robv.android.xposed.XC_MethodHook;
|
||||
import io.github.qauxv.R;
|
||||
import io.github.qauxv.base.annotation.FunctionHookEntry;
|
||||
import io.github.qauxv.base.annotation.UiItemAgentEntry;
|
||||
@@ -56,9 +58,7 @@ import io.github.qauxv.dsl.FunctionEntryRouter;
|
||||
import io.github.qauxv.hook.CommonSwitchFunctionHook;
|
||||
import io.github.qauxv.ui.CommonContextWrapper;
|
||||
import io.github.qauxv.util.CustomMenu;
|
||||
import io.github.qauxv.util.Initiator;
|
||||
import io.github.qauxv.util.SyncUtils;
|
||||
import io.github.qauxv.util.Toasts;
|
||||
import io.github.qauxv.util.dexkit.AbstractQQCustomMenuItem;
|
||||
import io.github.qauxv.util.dexkit.ChatPanel_InitPanel_QQNT;
|
||||
import io.github.qauxv.util.dexkit.DexKit;
|
||||
@@ -74,7 +74,7 @@ import org.json.JSONObject;
|
||||
|
||||
@FunctionHookEntry
|
||||
@UiItemAgentEntry
|
||||
public class StickerPanelEntryHooker extends CommonSwitchFunctionHook implements SessionHooker.IAIOParamUpdate {
|
||||
public class StickerPanelEntryHooker extends CommonSwitchFunctionHook implements SessionHooker.IAIOParamUpdate, OnMenuBuilder {
|
||||
public static final StickerPanelEntryHooker INSTANCE = new StickerPanelEntryHooker();
|
||||
public static Object AIOParam;
|
||||
private StickerPanelEntryHooker() {
|
||||
@@ -146,100 +146,6 @@ public class StickerPanelEntryHooker extends CommonSwitchFunctionHook implements
|
||||
}
|
||||
);
|
||||
|
||||
//Hook for longClick msgItem
|
||||
{
|
||||
Class msgClass = Initiator.loadClass("com.tencent.mobileqq.aio.msg.AIOMsgItem");
|
||||
String[] component = new String[]{
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.pic.AIOPicContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.mix.AIOMixContentComponent",
|
||||
};
|
||||
|
||||
|
||||
Method getMsg = null;
|
||||
Method[] methods = Initiator.loadClass("com.tencent.mobileqq.aio.msglist.holder.component.BaseContentComponent").getDeclaredMethods();
|
||||
for (Method method : methods) {
|
||||
if (method.getReturnType() == msgClass && method.getParameterTypes().length == 0) {
|
||||
getMsg = method;
|
||||
getMsg.setAccessible(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (String s : component) {
|
||||
Class componentClazz = Initiator.loadClass(s);
|
||||
Method listMethod = null;
|
||||
methods = componentClazz.getDeclaredMethods();
|
||||
for (Method method : methods) {
|
||||
if (method.getReturnType() == List.class && method.getParameterTypes().length == 0) {
|
||||
listMethod = method;
|
||||
listMethod.setAccessible(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Method finalGetMsg = getMsg;
|
||||
HookUtils.hookAfterIfEnabled(this, listMethod, param -> {
|
||||
Object msg = finalGetMsg.invoke(param.thisObject);
|
||||
Object item = CustomMenu.createItemNt(msg, "保存到面板", R.id.item_save_to_panel, () -> {
|
||||
try {
|
||||
long msgID = (long) Reflex.invokeVirtual(msg, "getMsgId");
|
||||
IKernelMsgService service = MsgServiceHelper.getKernelMsgService(AppRuntimeHelper.getAppRuntime());
|
||||
ArrayList<Long> msgIDs = new ArrayList<>();
|
||||
msgIDs.add(msgID);
|
||||
service.getMsgsByMsgId(SessionUtils.AIOParam2Contact(AIOParam), msgIDs, (result, errMsg, msgList) -> {
|
||||
SyncUtils.runOnUiThread(()->{
|
||||
for (MsgRecord msgRecord : msgList) {
|
||||
ArrayList<String> md5s = new ArrayList<>();
|
||||
ArrayList<String> urls = new ArrayList<>();
|
||||
|
||||
for (MsgElement element : msgRecord.getElements()){
|
||||
if (element.getPicElement() != null){
|
||||
PicElement picElement = element.getPicElement();
|
||||
//md5必须大写才能加载
|
||||
md5s.add(picElement.getMd5HexStr().toUpperCase());
|
||||
String originUrl = picElement.getOriginImageUrl();
|
||||
if (TextUtils.isEmpty(originUrl)){
|
||||
urls.add("https://gchat.qpic.cn/gchatpic_new/0/0-0-" + picElement.getMd5HexStr().toUpperCase() + "/0");
|
||||
}else {
|
||||
if (originUrl.startsWith("/download")){
|
||||
if (originUrl.contains("appid=1406")){
|
||||
urls.add("https://multimedia.nt.qq.com.cn" + originUrl + rkey_group);
|
||||
}else {
|
||||
urls.add("https://multimedia.nt.qq.com.cn" + originUrl + rkey_private);
|
||||
}
|
||||
}else {
|
||||
urls.add("https://gchat.qpic.cn"+picElement.getOriginImageUrl());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
if (!md5s.isEmpty()){
|
||||
if (md5s.size() > 1){
|
||||
PanelUtils.PreSaveMultiPicList(urls,md5s, CommonContextWrapper.createAppCompatContext(ContextUtils.getCurrentActivity()));
|
||||
}else {
|
||||
PanelUtils.PreSavePicToList(urls.get(0),md5s.get(0), CommonContextWrapper.createAppCompatContext(ContextUtils.getCurrentActivity()));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
XLog.e("StickerPanelEntryHooker.msgLongClickSaveToLocal", e);
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
List list = (List) param.getResult();
|
||||
List result = new ArrayList<>();
|
||||
result.add(0,item);
|
||||
result.addAll(list);
|
||||
param.setResult(result);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//Hook for change title
|
||||
|
||||
Method sendMsgMethod = XMethod
|
||||
@@ -330,4 +236,75 @@ public class StickerPanelEntryHooker extends CommonSwitchFunctionHook implements
|
||||
public void onAIOParamUpdate(Object AIOParam) {
|
||||
StickerPanelEntryHooker.AIOParam = AIOParam;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String[] getTargetComponentTypes() {
|
||||
return new String[]{
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.pic.AIOPicContentComponent",
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.mix.AIOMixContentComponent",
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetMenuNt(@NonNull Object msg, @NonNull String componentType, @NonNull XC_MethodHook.MethodHookParam param) throws Exception {
|
||||
if (!isEnabled()) return;
|
||||
//Hook for longClick msgItem
|
||||
Object item = CustomMenu.createItemNt(msg, "保存到面板", R.id.item_save_to_panel, () -> {
|
||||
try {
|
||||
long msgID = (long) Reflex.invokeVirtual(msg, "getMsgId");
|
||||
IKernelMsgService service = MsgServiceHelper.getKernelMsgService(AppRuntimeHelper.getAppRuntime());
|
||||
ArrayList<Long> msgIDs = new ArrayList<>();
|
||||
msgIDs.add(msgID);
|
||||
service.getMsgsByMsgId(SessionUtils.AIOParam2Contact(AIOParam), msgIDs, (result, errMsg, msgList) -> {
|
||||
SyncUtils.runOnUiThread(()->{
|
||||
for (MsgRecord msgRecord : msgList) {
|
||||
ArrayList<String> md5s = new ArrayList<>();
|
||||
ArrayList<String> urls = new ArrayList<>();
|
||||
|
||||
for (MsgElement element : msgRecord.getElements()){
|
||||
if (element.getPicElement() != null){
|
||||
PicElement picElement = element.getPicElement();
|
||||
//md5必须大写才能加载
|
||||
md5s.add(picElement.getMd5HexStr().toUpperCase());
|
||||
String originUrl = picElement.getOriginImageUrl();
|
||||
if (TextUtils.isEmpty(originUrl)){
|
||||
urls.add("https://gchat.qpic.cn/gchatpic_new/0/0-0-" + picElement.getMd5HexStr().toUpperCase() + "/0");
|
||||
}else {
|
||||
if (originUrl.startsWith("/download")){
|
||||
if (originUrl.contains("appid=1406")){
|
||||
urls.add("https://multimedia.nt.qq.com.cn" + originUrl + rkey_group);
|
||||
}else {
|
||||
urls.add("https://multimedia.nt.qq.com.cn" + originUrl + rkey_private);
|
||||
}
|
||||
}else {
|
||||
urls.add("https://gchat.qpic.cn"+picElement.getOriginImageUrl());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
if (!md5s.isEmpty()){
|
||||
if (md5s.size() > 1){
|
||||
PanelUtils.PreSaveMultiPicList(urls,md5s, CommonContextWrapper.createAppCompatContext(ContextUtils.getCurrentActivity()));
|
||||
}else {
|
||||
PanelUtils.PreSavePicToList(urls.get(0),md5s.get(0), CommonContextWrapper.createAppCompatContext(ContextUtils.getCurrentActivity()));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
XLog.e("StickerPanelEntryHooker.msgLongClickSaveToLocal", e);
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
List list = (List) param.getResult();
|
||||
List result = new ArrayList<>();
|
||||
result.add(0,item);
|
||||
result.addAll(list);
|
||||
param.setResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +24,12 @@ package cc.ioctl.hook.msg
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import cc.ioctl.util.HookUtils
|
||||
import cc.hicore.QApp.QAppUtils
|
||||
import cc.ioctl.util.Reflex
|
||||
import cc.ioctl.util.afterHookIfEnabled
|
||||
import cc.ioctl.util.beforeHookIfEnabled
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||
import com.xiaoniu.dispatcher.OnMenuBuilder
|
||||
import com.xiaoniu.util.ContextUtils
|
||||
import de.robv.android.xposed.XC_MethodHook.MethodHookParam
|
||||
import de.robv.android.xposed.XposedBridge
|
||||
@@ -41,48 +42,24 @@ import io.github.qauxv.hook.CommonSwitchFunctionHook
|
||||
import io.github.qauxv.util.CustomMenu
|
||||
import io.github.qauxv.util.CustomMenu.createItemNt
|
||||
import io.github.qauxv.util.Initiator
|
||||
import io.github.qauxv.util.QQVersion
|
||||
import io.github.qauxv.util.Toasts
|
||||
import io.github.qauxv.util.dexkit.AbstractQQCustomMenuItem
|
||||
import io.github.qauxv.util.dexkit.CArkAppItemBubbleBuilder
|
||||
import io.github.qauxv.util.dexkit.DexKit
|
||||
import io.github.qauxv.util.requireMinQQVersion
|
||||
import xyz.nextalone.util.SystemServiceUtils.copyToClipboard
|
||||
import xyz.nextalone.util.throwOrTrue
|
||||
import java.lang.reflect.Array
|
||||
import java.lang.reflect.Method
|
||||
|
||||
@FunctionHookEntry
|
||||
@UiItemAgentEntry
|
||||
object CopyCardMsg : CommonSwitchFunctionHook("CopyCardMsg::BaseChatPie", arrayOf(CArkAppItemBubbleBuilder, AbstractQQCustomMenuItem)) {
|
||||
object CopyCardMsg : CommonSwitchFunctionHook("CopyCardMsg::BaseChatPie", arrayOf(CArkAppItemBubbleBuilder, AbstractQQCustomMenuItem)), OnMenuBuilder {
|
||||
|
||||
override val name = "复制卡片消息"
|
||||
|
||||
override val uiItemLocation = FunctionEntryRouter.Locations.Auxiliary.MESSAGE_CATEGORY
|
||||
|
||||
override fun initOnce() = throwOrTrue {
|
||||
if (requireMinQQVersion(QQVersion.QQ_8_9_63)) {
|
||||
val msgClass = Initiator.loadClass("com.tencent.mobileqq.aio.msg.AIOMsgItem")
|
||||
val getMsg: Method = Initiator.loadClass("com.tencent.mobileqq.aio.msglist.holder.component.BaseContentComponent").declaredMethods.first {
|
||||
it.returnType == msgClass && it.parameterTypes.isEmpty()
|
||||
}.apply { isAccessible = true }
|
||||
val componentClazz = Initiator.loadClass("com.tencent.mobileqq.aio.msglist.holder.component.ark.AIOArkContentComponent")
|
||||
val listMethod: Method = componentClazz.declaredMethods.first {
|
||||
it.returnType == MutableList::class.java && it.parameterTypes.isEmpty()
|
||||
}.apply { isAccessible = true }
|
||||
HookUtils.hookAfterIfEnabled(this, listMethod) { param: MethodHookParam ->
|
||||
val ctx = ContextUtils.getCurrentActivity()
|
||||
val msg = getMsg.invoke(param.thisObject)
|
||||
val item = createItemNt(msg, "复制代码", R.id.item_copy_code) {
|
||||
val element = (msg.javaClass.declaredMethods.first {
|
||||
it.returnType == MsgElement::class.java && it.parameterTypes.isEmpty()
|
||||
}.apply { isAccessible = true }.invoke(msg) as MsgElement).arkElement
|
||||
copyToClipboard(ctx, element.bytesData)
|
||||
Toasts.info(ctx, "复制成功")
|
||||
}
|
||||
val list = param.result as MutableList<Any>
|
||||
list.add(item)
|
||||
}
|
||||
if (QAppUtils.isQQnt()) {
|
||||
return@throwOrTrue
|
||||
}
|
||||
|
||||
@@ -175,4 +152,19 @@ object CopyCardMsg : CommonSwitchFunctionHook("CopyCardMsg::BaseChatPie", arrayO
|
||||
}
|
||||
}
|
||||
}
|
||||
override val targetComponentTypes = arrayOf("com.tencent.mobileqq.aio.msglist.holder.component.ark.AIOArkContentComponent")
|
||||
|
||||
override fun onGetMenuNt(msg: Any, componentType: String, param: MethodHookParam) {
|
||||
if (!isEnabled) return
|
||||
val ctx = ContextUtils.getCurrentActivity()
|
||||
val item = createItemNt(msg, "复制代码", R.id.item_copy_code) {
|
||||
val element = (msg.javaClass.declaredMethods.first {
|
||||
it.returnType == MsgElement::class.java && it.parameterTypes.isEmpty()
|
||||
}.apply { isAccessible = true }.invoke(msg) as MsgElement).arkElement
|
||||
copyToClipboard(ctx, element.bytesData)
|
||||
Toasts.info(ctx, "复制成功")
|
||||
}
|
||||
val list = param.result as MutableList<Any>
|
||||
list.add(item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,15 +29,15 @@ import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import cc.hicore.QApp.QAppUtils;
|
||||
import cc.hicore.ReflectUtil.XField;
|
||||
import cc.hicore.ReflectUtil.XMethod;
|
||||
import cc.hicore.Utils.FunProtoData;
|
||||
import cc.ioctl.util.HookUtils;
|
||||
import cc.ioctl.util.HostInfo;
|
||||
import cc.ioctl.util.Reflex;
|
||||
import com.tencent.qphone.base.remote.FromServiceMsg;
|
||||
import com.tencent.qphone.base.remote.ToServiceMsg;
|
||||
import com.tencent.qqnt.kernel.nativeinterface.PicElement;
|
||||
import com.xiaoniu.dispatcher.OnMenuBuilder;
|
||||
import com.xiaoniu.util.ContextUtils;
|
||||
import de.robv.android.xposed.XC_MethodHook;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
@@ -51,7 +51,6 @@ import io.github.qauxv.ui.CustomDialog;
|
||||
import io.github.qauxv.util.CustomMenu;
|
||||
import io.github.qauxv.util.Initiator;
|
||||
import io.github.qauxv.util.LicenseStatus;
|
||||
import io.github.qauxv.util.QQVersion;
|
||||
import io.github.qauxv.util.Toasts;
|
||||
import io.github.qauxv.util.dexkit.AbstractQQCustomMenuItem;
|
||||
import io.github.qauxv.util.dexkit.DexKitTarget;
|
||||
@@ -65,7 +64,7 @@ import xyz.nextalone.util.SystemServiceUtils;
|
||||
|
||||
@FunctionHookEntry
|
||||
@UiItemAgentEntry
|
||||
public class PicMd5Hook extends CommonSwitchFunctionHook {
|
||||
public class PicMd5Hook extends CommonSwitchFunctionHook implements OnMenuBuilder {
|
||||
|
||||
public static final PicMd5Hook INSTANCE = new PicMd5Hook();
|
||||
|
||||
@@ -101,7 +100,7 @@ public class PicMd5Hook extends CommonSwitchFunctionHook {
|
||||
HookUtils.hookBeforeIfEnabled(this, XMethod.clz("mqq.app.msghandle.MsgRespHandler").name("dispatchRespMsg").ignoreParam().get(), param -> {
|
||||
FromServiceMsg fromServiceMsg = XField.obj(param.args[1]).name("fromServiceMsg").get();
|
||||
|
||||
if ("OidbSvcTrpcTcp.0x9067_202".equals(fromServiceMsg.getServiceCmd())){
|
||||
if ("OidbSvcTrpcTcp.0x9067_202".equals(fromServiceMsg.getServiceCmd())) {
|
||||
FunProtoData data = new FunProtoData();
|
||||
data.fromBytes(getUnpPackage(fromServiceMsg.getWupBuffer()));
|
||||
|
||||
@@ -118,51 +117,7 @@ public class PicMd5Hook extends CommonSwitchFunctionHook {
|
||||
}
|
||||
});
|
||||
|
||||
if (HostInfo.requireMinQQVersion(QQVersion.QQ_8_9_63)) {
|
||||
Class msgClass = Initiator.loadClass("com.tencent.mobileqq.aio.msg.AIOMsgItem");
|
||||
Method getMsg = null;
|
||||
Method[] methods = Initiator.loadClass("com.tencent.mobileqq.aio.msglist.holder.component.BaseContentComponent").getDeclaredMethods();
|
||||
for (Method method : methods) {
|
||||
if (method.getReturnType() == msgClass && method.getParameterTypes().length == 0) {
|
||||
getMsg = method;
|
||||
getMsg.setAccessible(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Class componentClazz = Initiator.loadClass("com.tencent.mobileqq.aio.msglist.holder.component.pic.AIOPicContentComponent");
|
||||
Method listMethod = null;
|
||||
methods = componentClazz.getDeclaredMethods();
|
||||
for (Method method : methods) {
|
||||
if (method.getReturnType() == List.class && method.getParameterTypes().length == 0) {
|
||||
listMethod = method;
|
||||
listMethod.setAccessible(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Method finalGetMsg = getMsg;
|
||||
HookUtils.hookAfterIfEnabled(this, listMethod, param -> {
|
||||
Object msg = finalGetMsg.invoke(param.thisObject);
|
||||
Object item = CustomMenu.createItemNt(msg, "MD5", R.id.item_showPicMd5, () -> {
|
||||
try {
|
||||
Method getElement = null;
|
||||
for (Method m : msg.getClass().getDeclaredMethods()) {
|
||||
if (m.getReturnType() == PicElement.class) {
|
||||
getElement = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
PicElement element = (PicElement) getElement.invoke(msg);
|
||||
String md5 = element.getMd5HexStr().toUpperCase();
|
||||
showMd5Dialog(ContextUtils.getCurrentActivity(), md5, element);
|
||||
} catch (Throwable e) {
|
||||
traceError(e);
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
List list = (List) param.getResult();
|
||||
list.add(item);
|
||||
});
|
||||
|
||||
if (QAppUtils.isQQnt()) {
|
||||
return true;
|
||||
}
|
||||
Class<?> cl_PicItemBuilder = Initiator._PicItemBuilder();
|
||||
@@ -199,6 +154,38 @@ public class PicMd5Hook extends CommonSwitchFunctionHook {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String[] getTargetComponentTypes() {
|
||||
return new String[]{
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.pic.AIOPicContentComponent"
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetMenuNt(@NonNull Object msg, @NonNull String componentType, @NonNull XC_MethodHook.MethodHookParam param) throws Exception {
|
||||
if (!isEnabled()) return;
|
||||
Object item = CustomMenu.createItemNt(msg, "MD5", R.id.item_showPicMd5, () -> {
|
||||
try {
|
||||
Method getElement = null;
|
||||
for (Method m : msg.getClass().getDeclaredMethods()) {
|
||||
if (m.getReturnType() == PicElement.class) {
|
||||
getElement = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
PicElement element = (PicElement) getElement.invoke(msg);
|
||||
String md5 = element.getMd5HexStr().toUpperCase();
|
||||
showMd5Dialog(ContextUtils.getCurrentActivity(), md5, element);
|
||||
} catch (Throwable e) {
|
||||
traceError(e);
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
List list = (List) param.getResult();
|
||||
list.add(item);
|
||||
}
|
||||
|
||||
public static class GetMenuItemCallBack extends XC_MethodHook {
|
||||
|
||||
public GetMenuItemCallBack() {
|
||||
|
||||
@@ -52,11 +52,13 @@ import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import cc.hicore.QApp.QAppUtils;
|
||||
import cc.ioctl.util.DebugUtils;
|
||||
import cc.ioctl.util.HookUtils;
|
||||
import cc.ioctl.util.HostStyledViewBuilder;
|
||||
import cc.ioctl.util.Reflex;
|
||||
import com.tencent.qqnt.kernel.nativeinterface.PttElement;
|
||||
import com.xiaoniu.dispatcher.OnMenuBuilder;
|
||||
import com.xiaoniu.util.ContextUtils;
|
||||
import de.robv.android.xposed.XC_MethodHook;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
@@ -74,10 +76,7 @@ import io.github.qauxv.ui.CommonContextWrapper;
|
||||
import io.github.qauxv.ui.CustomDialog;
|
||||
import io.github.qauxv.ui.ResUtils;
|
||||
import io.github.qauxv.util.CustomMenu;
|
||||
import io.github.qauxv.util.HostInfo;
|
||||
import io.github.qauxv.util.Initiator;
|
||||
import io.github.qauxv.util.Log;
|
||||
import io.github.qauxv.util.QQVersion;
|
||||
import io.github.qauxv.util.SyncUtils;
|
||||
import io.github.qauxv.util.Toasts;
|
||||
import io.github.qauxv.util.data.ContactDescriptor;
|
||||
@@ -99,7 +98,7 @@ import kotlin.Unit;
|
||||
|
||||
@FunctionHookEntry
|
||||
@UiItemAgentEntry
|
||||
public class PttForwardHook extends CommonSwitchFunctionHook {
|
||||
public class PttForwardHook extends CommonSwitchFunctionHook implements OnMenuBuilder {
|
||||
|
||||
public static final String qn_cache_ptt_save_last_parent_dir = "qn_cache_ptt_save_last_parent_dir";
|
||||
public static final PttForwardHook INSTANCE = new PttForwardHook();
|
||||
@@ -386,54 +385,7 @@ public class PttForwardHook extends CommonSwitchFunctionHook {
|
||||
}
|
||||
});
|
||||
|
||||
if (HostInfo.requireMinQQVersion(QQVersion.QQ_8_9_63)) {
|
||||
Class msgClass = Initiator.loadClass("com.tencent.mobileqq.aio.msg.AIOMsgItem");
|
||||
Method getMsg = null;
|
||||
Method[] methods = Initiator.loadClass("com.tencent.mobileqq.aio.msglist.holder.component.BaseContentComponent").getDeclaredMethods();
|
||||
for (Method method : methods) {
|
||||
if (method.getReturnType() == msgClass && method.getParameterTypes().length == 0) {
|
||||
getMsg = method;
|
||||
getMsg.setAccessible(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Class componentClazz = Initiator.loadClass("com.tencent.mobileqq.aio.msglist.holder.component.ptt.AIOPttContentComponent");
|
||||
Method listMethod = null;
|
||||
methods = componentClazz.getDeclaredMethods();
|
||||
for (Method method : methods) {
|
||||
if (method.getReturnType() == List.class && method.getParameterTypes().length == 0) {
|
||||
listMethod = method;
|
||||
listMethod.setAccessible(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Method finalGetMsg = getMsg;
|
||||
HookUtils.hookAfterIfEnabled(this, listMethod, param -> {
|
||||
Object msg = finalGetMsg.invoke(param.thisObject);
|
||||
Activity context = ContextUtils.getCurrentActivity();
|
||||
Object item = CustomMenu.createItemNt(msg, "转发", R.id.item_ptt_forward, () -> {
|
||||
File file = getPttFileByMsgNt(msg);
|
||||
if (!file.exists()) {
|
||||
Toasts.error(context, "未找到语音文件");
|
||||
} else {
|
||||
sendForwardIntent(context, file);
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
Object item2 = CustomMenu.createItemNt(msg, "保存", R.id.item_ptt_save, () -> {
|
||||
File file = getPttFileByMsgNt(msg);
|
||||
if (!file.exists()) {
|
||||
Toasts.error(context, "未找到语音文件");
|
||||
} else {
|
||||
showSavePttFileDialog(context, file);
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
List list = (List) param.getResult();
|
||||
list.add(item);
|
||||
list.add(item2);
|
||||
});
|
||||
|
||||
if (QAppUtils.isQQnt()) {
|
||||
return true;
|
||||
}
|
||||
Class<?> kPttItemBuilder = _PttItemBuilder();
|
||||
@@ -525,4 +477,40 @@ public class PttForwardHook extends CommonSwitchFunctionHook {
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String[] getTargetComponentTypes() {
|
||||
return new String[]{
|
||||
"com.tencent.mobileqq.aio.msglist.holder.component.ptt.AIOPttContentComponent"
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetMenuNt(@NonNull Object msg, @NonNull String componentType, @NonNull XC_MethodHook.MethodHookParam param) throws Exception {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
Activity context = ContextUtils.getCurrentActivity();
|
||||
Object item = CustomMenu.createItemNt(msg, "转发", R.id.item_ptt_forward, () -> {
|
||||
File file = getPttFileByMsgNt(msg);
|
||||
if (!file.exists()) {
|
||||
Toasts.error(context, "未找到语音文件");
|
||||
} else {
|
||||
sendForwardIntent(context, file);
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
Object item2 = CustomMenu.createItemNt(msg, "保存", R.id.item_ptt_save, () -> {
|
||||
File file = getPttFileByMsgNt(msg);
|
||||
if (!file.exists()) {
|
||||
Toasts.error(context, "未找到语音文件");
|
||||
} else {
|
||||
showSavePttFileDialog(context, file);
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
List list = (List) param.getResult();
|
||||
list.add(item);
|
||||
list.add(item2);
|
||||
}
|
||||
}
|
||||
|
||||
97
app/src/main/java/com/xiaoniu/dispatcher/MenuBuilderHook.kt
Normal file
97
app/src/main/java/com/xiaoniu/dispatcher/MenuBuilderHook.kt
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* QAuxiliary - An Xposed module for QQ/TIM
|
||||
* Copyright (C) 2019-2024 QAuxiliary developers
|
||||
* https://github.com/cinit/QAuxiliary
|
||||
*
|
||||
* This software is an opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version as published
|
||||
* by QAuxiliary contributors.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the General Public License
|
||||
* along with this software.
|
||||
* If not, see
|
||||
* <https://github.com/cinit/QAuxiliary/blob/master/LICENSE.md>.
|
||||
*/
|
||||
|
||||
package com.xiaoniu.dispatcher
|
||||
|
||||
import cc.hicore.QApp.QAppUtils
|
||||
import cc.hicore.hook.RepeaterPlus
|
||||
import cc.hicore.hook.stickerPanel.Hooker.StickerPanelEntryHooker
|
||||
import cc.ioctl.hook.msg.CopyCardMsg
|
||||
import cc.ioctl.hook.msg.PicMd5Hook
|
||||
import cc.ioctl.hook.msg.PttForwardHook
|
||||
import cc.ioctl.util.HookUtils
|
||||
import com.github.kyuubiran.ezxhelper.utils.isAbstract
|
||||
import de.robv.android.xposed.XC_MethodHook
|
||||
import io.github.duzhaokun123.hook.MessageCopyHook
|
||||
import io.github.qauxv.base.annotation.FunctionHookEntry
|
||||
import io.github.qauxv.hook.BasePersistBackgroundHook
|
||||
import io.github.qauxv.util.Initiator
|
||||
import me.ketal.hook.PicCopyToClipboard
|
||||
import java.lang.reflect.Method
|
||||
|
||||
@FunctionHookEntry
|
||||
object MenuBuilderHook : BasePersistBackgroundHook() {
|
||||
// These hooks are called when the menu is being built.
|
||||
private val decorators: Array<OnMenuBuilder> = arrayOf(
|
||||
RepeaterPlus.INSTANCE,
|
||||
StickerPanelEntryHooker.INSTANCE,
|
||||
PicMd5Hook.INSTANCE,
|
||||
PttForwardHook.INSTANCE,
|
||||
CopyCardMsg,
|
||||
MessageCopyHook,
|
||||
PicCopyToClipboard
|
||||
)
|
||||
|
||||
override fun initOnce(): Boolean {
|
||||
if (QAppUtils.isQQnt()) { // NT only
|
||||
val msgClass = Initiator.loadClass("com.tencent.mobileqq.aio.msg.AIOMsgItem")
|
||||
val baseContentComponentClass = Initiator.loadClass("com.tencent.mobileqq.aio.msglist.holder.component.BaseContentComponent")
|
||||
val getMsgMethod: Method = baseContentComponentClass.declaredMethods.first {
|
||||
it.returnType == msgClass && it.parameterTypes.isEmpty()
|
||||
}.apply { isAccessible = true }
|
||||
val listMethodName: String = baseContentComponentClass.declaredMethods.first {
|
||||
it.isAbstract && it.returnType == MutableList::class.java && it.parameterTypes.isEmpty()
|
||||
}.name
|
||||
val targets = mutableSetOf<String>()
|
||||
for (decorator in decorators) {
|
||||
targets.addAll(decorator.targetComponentTypes)
|
||||
}
|
||||
for (target in targets) {
|
||||
val targetClass = Initiator.loadClass(target)
|
||||
HookUtils.hookAfterAlways(this, targetClass.getMethod(listMethodName), 48) {
|
||||
val msg = getMsgMethod.invoke(it.thisObject)!!
|
||||
for (decorator in decorators) {
|
||||
if (target in decorator.targetComponentTypes) {
|
||||
try {
|
||||
decorator.onGetMenuNt(msg, target, it)
|
||||
} catch (e: Exception) {
|
||||
traceError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
interface OnMenuBuilder {
|
||||
val targetComponentTypes: Array<String>
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun onGetMenuNt(
|
||||
msg: Any,
|
||||
componentType: String,
|
||||
param: XC_MethodHook.MethodHookParam
|
||||
)
|
||||
}
|
||||
@@ -28,13 +28,13 @@ import android.content.Context
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import cc.hicore.QApp.QAppUtils
|
||||
import cc.ioctl.util.HostInfo
|
||||
import cc.ioctl.util.Reflex
|
||||
import cc.ioctl.util.afterHookIfEnabled
|
||||
import cc.ioctl.util.hookAfterIfEnabled
|
||||
import com.github.kyuubiran.ezxhelper.utils.invokeMethodAutoAs
|
||||
import com.github.kyuubiran.ezxhelper.utils.paramCount
|
||||
import com.xiaoniu.dispatcher.OnMenuBuilder
|
||||
import com.xiaoniu.util.ContextUtils
|
||||
import de.robv.android.xposed.XC_MethodHook
|
||||
import de.robv.android.xposed.XposedBridge
|
||||
import de.robv.android.xposed.XposedHelpers
|
||||
import io.github.qauxv.R
|
||||
@@ -51,40 +51,17 @@ import io.github.qauxv.util.dexkit.DexDeobfsProvider
|
||||
import io.github.qauxv.util.dexkit.DexKit
|
||||
import io.github.qauxv.util.dexkit.DexKitFinder
|
||||
import io.github.qauxv.util.dexkit.TextMsgItem_getText
|
||||
import xyz.nextalone.util.method
|
||||
import java.lang.reflect.Modifier
|
||||
|
||||
@FunctionHookEntry
|
||||
@UiItemAgentEntry
|
||||
object MessageCopyHook : CommonSwitchFunctionHook(), DexKitFinder {
|
||||
object MessageCopyHook : CommonSwitchFunctionHook(), DexKitFinder, OnMenuBuilder {
|
||||
const val TAG = "MessageCopyHook"
|
||||
override val name: String
|
||||
get() = "文本消息自由复制"
|
||||
|
||||
override fun initOnce(): Boolean {
|
||||
if (HostInfo.requireMinQQVersion(QQVersion.QQ_8_9_63)) { // TODO: support ark message
|
||||
val class_AIOMsgItem = Initiator.loadClass("com.tencent.mobileqq.aio.msg.AIOMsgItem")
|
||||
val class_BaseContentComponent = Initiator.loadClass("com.tencent.mobileqq.aio.msglist.holder.component.BaseContentComponent")
|
||||
val method_getMsg = class_BaseContentComponent.method { it.returnType == class_AIOMsgItem && it.paramCount == 0 }!!
|
||||
method_getMsg.isAccessible = true
|
||||
val class_AIOTextContentComponent = Initiator.loadClass("com.tencent.mobileqq.aio.msglist.holder.component.text.AIOTextContentComponent")
|
||||
val method_list = class_AIOTextContentComponent.method { it.returnType == List::class.java && it.paramCount == 0 }!!
|
||||
method_list.isAccessible = true
|
||||
hookAfterIfEnabled(method_list) { param ->
|
||||
val msg = method_getMsg.invoke(param.thisObject)
|
||||
val item = CustomMenu.createItemNt(msg, "自由复制", R.id.item_free_copy) {
|
||||
// Log.d(msg.javaClass.name)
|
||||
val text = try {
|
||||
DexKit.requireMethodFromCache(TextMsgItem_getText).also {
|
||||
it.isAccessible = true
|
||||
}.invoke(msg) as CharSequence
|
||||
} catch (e: Exception) {
|
||||
"${e.javaClass.name}: ${e.message}\n" + (e.stackTrace.joinToString("\n"))
|
||||
}
|
||||
showDialog(CommonContextWrapper.createAppCompatContext(ContextUtils.getCurrentActivity()), text)
|
||||
}
|
||||
param.result = (param.result as List<*>) + item
|
||||
}
|
||||
if (QAppUtils.isQQnt()) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -191,4 +168,22 @@ object MessageCopyHook : CommonSwitchFunctionHook(), DexKitFinder {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override val targetComponentTypes = arrayOf("com.tencent.mobileqq.aio.msglist.holder.component.text.AIOTextContentComponent")
|
||||
|
||||
override fun onGetMenuNt(msg: Any, componentType: String, param: XC_MethodHook.MethodHookParam) {
|
||||
if (!isEnabled) return
|
||||
// TODO: support ark message
|
||||
val item = CustomMenu.createItemNt(msg, "自由复制", R.id.item_free_copy) {
|
||||
val text = try {
|
||||
DexKit.requireMethodFromCache(TextMsgItem_getText).also {
|
||||
it.isAccessible = true
|
||||
}.invoke(msg) as CharSequence
|
||||
} catch (e: Exception) {
|
||||
"${e.javaClass.name}: ${e.message}\n" + (e.stackTrace.joinToString("\n"))
|
||||
}
|
||||
showDialog(CommonContextWrapper.createAppCompatContext(ContextUtils.getCurrentActivity()), text)
|
||||
}
|
||||
param.result = (param.result as List<*>) + item
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ import com.github.kyuubiran.ezxhelper.utils.Log
|
||||
import com.github.kyuubiran.ezxhelper.utils.findMethod
|
||||
import com.github.kyuubiran.ezxhelper.utils.findMethodOrNull
|
||||
import com.github.kyuubiran.ezxhelper.utils.tryOrLogFalse
|
||||
import com.xiaoniu.dispatcher.OnMenuBuilder
|
||||
import de.robv.android.xposed.XC_MethodHook
|
||||
import io.github.qauxv.R
|
||||
import io.github.qauxv.base.annotation.FunctionHookEntry
|
||||
import io.github.qauxv.base.annotation.UiItemAgentEntry
|
||||
@@ -62,7 +64,7 @@ object PicCopyToClipboard : CommonSwitchFunctionHook(
|
||||
arrayOf(
|
||||
AbstractQQCustomMenuItem
|
||||
)
|
||||
) {
|
||||
), OnMenuBuilder {
|
||||
override val name: String = "复制图片到剪贴板"
|
||||
|
||||
override val description: String = "复制图片到剪贴板,可以在聊天窗口中粘贴使用"
|
||||
@@ -73,7 +75,6 @@ object PicCopyToClipboard : CommonSwitchFunctionHook(
|
||||
|
||||
override fun initOnce() = tryOrLogFalse {
|
||||
if (QAppUtils.isQQnt()) {
|
||||
hookNt()
|
||||
return@tryOrLogFalse
|
||||
}
|
||||
val clsPicItemBuilder = _PicItemBuilder()
|
||||
@@ -119,31 +120,6 @@ object PicCopyToClipboard : CommonSwitchFunctionHook(
|
||||
}
|
||||
}
|
||||
|
||||
private fun hookNt() {
|
||||
val msgClass = Initiator.loadClass("com.tencent.mobileqq.aio.msg.AIOMsgItem")
|
||||
val picContentComponent = Initiator.loadClass("com.tencent.mobileqq.aio.msglist.holder.component.pic.AIOPicContentComponent")
|
||||
val listMethod = picContentComponent.findMethod {
|
||||
returnType == List::class.java && parameterTypes.isEmpty()
|
||||
}
|
||||
val getMsg = picContentComponent.findMethod(findSuper = true) {
|
||||
parameterTypes.isEmpty() && returnType == msgClass
|
||||
}
|
||||
listMethod.hookAfter(this) {
|
||||
val list = it.result as MutableList<Any>
|
||||
val msg = getMsg.invoke(it.thisObject)!!
|
||||
val context = it.thisObject.invoke("getMContext")!!
|
||||
val item = CustomMenu.createItemNt(msg, "复制图片", R.id.item_copyToClipboard) {
|
||||
runCatching {
|
||||
val file = File(getFilePathNt(msg))
|
||||
onClick(context as Context, file)
|
||||
}.onFailure { t ->
|
||||
Log.e(t)
|
||||
}
|
||||
}
|
||||
list.add(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onClick(context: Context, file: File) {
|
||||
if (!file.exists()) {
|
||||
Toasts.info(context, "请查看原图后复制")
|
||||
@@ -224,4 +200,21 @@ object PicCopyToClipboard : CommonSwitchFunctionHook(
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
override val targetComponentTypes = arrayOf("com.tencent.mobileqq.aio.msglist.holder.component.pic.AIOPicContentComponent")
|
||||
|
||||
override fun onGetMenuNt(msg: Any, componentType: String, param: XC_MethodHook.MethodHookParam) {
|
||||
if (!isEnabled) return
|
||||
val list = param.result as MutableList<Any>
|
||||
val context = param.thisObject.invoke("getMContext")!!
|
||||
val item = CustomMenu.createItemNt(msg, "复制图片", R.id.item_copyToClipboard) {
|
||||
runCatching {
|
||||
val file = File(getFilePathNt(msg))
|
||||
onClick(context as Context, file)
|
||||
}.onFailure { t ->
|
||||
Log.e(t)
|
||||
}
|
||||
}
|
||||
list.add(item)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user