From c3b551cadbc6f107b919e2bba0ac10e165c971b6 Mon Sep 17 00:00:00 2001 From: klxiaoniu Date: Fri, 21 Jul 2023 20:15:26 +0800 Subject: [PATCH] fix: CardMsgSender and AioChatPieClipPasteHook for QQNT --- .../java/cc/hicore/hook/ReplyMsgWithImg.java | 4 +- .../hook/msg/AioChatPieClipPasteHook.java | 14 ++-- .../decorator/IBaseChatPieInitDecorator.kt | 6 +- .../dispacher/InputButtonHookDispatcher.java | 79 +++++++++++++------ 4 files changed, 71 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/cc/hicore/hook/ReplyMsgWithImg.java b/app/src/main/java/cc/hicore/hook/ReplyMsgWithImg.java index 47faec9cd4..836ab48fca 100644 --- a/app/src/main/java/cc/hicore/hook/ReplyMsgWithImg.java +++ b/app/src/main/java/cc/hicore/hook/ReplyMsgWithImg.java @@ -224,6 +224,8 @@ protected boolean initOnce() throws Exception { Log.d("ReplyMsgWithImg PhotoListPanel.a chatPie is null"); return; } + + // TODO: NT版本请用getSessionByAIOParam() Parcelable sessionInfo = InputButtonHookDispatcher.getSessionInfo(chatPie); int isTroop = SessionInfoImpl.getUinType(sessionInfo); if (isTroop == 1 || isTroop == 0) { @@ -488,7 +490,7 @@ public static void addEditText(@NonNull EditText ed, @NonNull String text) { } @Override - public void onInitBaseChatPie(@NonNull Object baseChatPie, @NonNull ViewGroup aioRootView, @NonNull Parcelable session, @NonNull Context ctx, + public void onInitBaseChatPie(@NonNull Object baseChatPie, @NonNull ViewGroup aioRootView, @Nullable Parcelable session, @NonNull Context ctx, @NonNull AppRuntime rt) { mBaseChatPie = new WeakReference<>(Objects.requireNonNull(baseChatPie, "baseChatPie is null")); EditText input = aioRootView.findViewById(ctx.getResources().getIdentifier("input", "id", ctx.getPackageName())); diff --git a/app/src/main/java/cc/ioctl/hook/msg/AioChatPieClipPasteHook.java b/app/src/main/java/cc/ioctl/hook/msg/AioChatPieClipPasteHook.java index 157f3b6e3c..bbe98f08b8 100644 --- a/app/src/main/java/cc/ioctl/hook/msg/AioChatPieClipPasteHook.java +++ b/app/src/main/java/cc/ioctl/hook/msg/AioChatPieClipPasteHook.java @@ -44,9 +44,9 @@ import androidx.core.view.ViewCompat; import androidx.core.view.inputmethod.EditorInfoCompat; import androidx.core.view.inputmethod.InputConnectionCompat; +import cc.hicore.message.bridge.Chat_facade_bridge; import cc.ioctl.util.SendCacheUtils; import cc.ioctl.util.ui.FaultyDialog; -import cc.hicore.message.bridge.Chat_facade_bridge; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedBridge; import io.github.qauxv.R; @@ -150,7 +150,7 @@ protected void afterHookedMethod(MethodHookParam param) { @Override public void onInitBaseChatPie(@NonNull Object baseChatPie, @NonNull ViewGroup aioRootView, - @NonNull Parcelable session, @NonNull Context ctx, @NonNull AppRuntime rt) { + @Nullable Parcelable session, @NonNull Context ctx, @NonNull AppRuntime rt) { int inputTextId = ctx.getResources().getIdentifier("input", "id", ctx.getPackageName()); EditText input = aioRootView.findViewById(inputTextId); Objects.requireNonNull(input, "onInitBaseChatPie: findViewById R.id.input is null"); @@ -160,7 +160,7 @@ public void onInitBaseChatPie(@NonNull Object baseChatPie, @NonNull ViewGroup ai if (item != null && item.getFirst().hasMimeType(MIME_IMAGE)) { Uri uri = item.getSecond().getUri(); if (uri != null && "content".equals(uri.getScheme())) { - handleSendUriPicture(ctx, session, uri, aioRootView, rt); + handleSendUriPicture(ctx, session == null ? InputButtonHookDispatcher.INSTANCE.getSessionByAIOParam() : session, uri, aioRootView, rt); return null; } } @@ -173,7 +173,7 @@ public void onInitBaseChatPie(@NonNull Object baseChatPie, @NonNull ViewGroup ai if (item != null && item.getFirst().hasMimeType(MIME_IMAGE)) { Uri uri = item.getSecond().getUri(); if (uri != null && "content".equals(uri.getScheme())) { - handleSendUriPicture(ctx, session, uri, aioRootView, rt); + handleSendUriPicture(ctx, session == null ? InputButtonHookDispatcher.INSTANCE.getSessionByAIOParam() : session, uri, aioRootView, rt); return true; } } @@ -206,7 +206,7 @@ private static Pair getClipDataItem0(@Nullable ClipData c } private static void handleSendUriPicture(@NonNull Context ctx, @NonNull Parcelable session, @NonNull Uri uri, - @NonNull ViewGroup aioRootView, @NonNull AppRuntime rt) { + @NonNull ViewGroup aioRootView, @NonNull AppRuntime rt) { AtomicBoolean cpTimeout = new AtomicBoolean(true); // call ContentResolver#openInputStream(Uri) asynchronously to avoid blocking UI thread SyncUtils.async(() -> { @@ -237,7 +237,7 @@ private static void handleSendUriPicture(@NonNull Context ctx, @NonNull Parcelab } private static void confirmSendMessage(@NonNull Context context, @NonNull Parcelable session, @NonNull byte[] data, - @NonNull ViewGroup aioRootView, @NonNull AppRuntime rt) { + @NonNull ViewGroup aioRootView, @NonNull AppRuntime rt) { Bitmap bitmap; try { bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); @@ -270,7 +270,7 @@ private static void confirmSendMessage(@NonNull Context context, @NonNull Parcel } private static void executeSendMessage(@NonNull Context context, @NonNull Parcelable session, @NonNull byte[] data, - @NonNull ViewGroup aioRootView, @NonNull AppRuntime rt) { + @NonNull ViewGroup aioRootView, @NonNull AppRuntime rt) { try { File file = SendCacheUtils.saveAsCacheFile(context, data); Chat_facade_bridge.sendPic(session, file); diff --git a/app/src/main/java/io/github/qauxv/router/decorator/IBaseChatPieInitDecorator.kt b/app/src/main/java/io/github/qauxv/router/decorator/IBaseChatPieInitDecorator.kt index 6a60999614..10f7029394 100644 --- a/app/src/main/java/io/github/qauxv/router/decorator/IBaseChatPieInitDecorator.kt +++ b/app/src/main/java/io/github/qauxv/router/decorator/IBaseChatPieInitDecorator.kt @@ -29,5 +29,9 @@ import mqq.app.AppRuntime interface IBaseChatPieInitDecorator : IBaseChatPieDecorator { @Throws(Throwable::class) - fun onInitBaseChatPie(baseChatPie: Any, aioRootView: ViewGroup, session: Parcelable, ctx: Context, rt: AppRuntime) + /** + * On NT Version, param session is null, get it by calling InputButtonHookDispatcher.INSTANCE.getSessionByAIOParam(). + * DO NOT call it right after onInitBaseChatPie, for it is not initialized before entering an AIO. + */ + fun onInitBaseChatPie(baseChatPie: Any, aioRootView: ViewGroup, session: Parcelable?, ctx: Context, rt: AppRuntime) } diff --git a/app/src/main/java/io/github/qauxv/router/dispacher/InputButtonHookDispatcher.java b/app/src/main/java/io/github/qauxv/router/dispacher/InputButtonHookDispatcher.java index 475ad4fda5..196f2f9352 100644 --- a/app/src/main/java/io/github/qauxv/router/dispacher/InputButtonHookDispatcher.java +++ b/app/src/main/java/io/github/qauxv/router/dispacher/InputButtonHookDispatcher.java @@ -34,15 +34,19 @@ import android.widget.TextView; import androidx.annotation.NonNull; import cc.hicore.QApp.QAppUtils; -import cc.hicore.ReflectUtil.MRes; import cc.hicore.hook.ReplyMsgWithImg; import cc.hicore.message.chat.SessionHooker; +import cc.hicore.message.chat.SessionUtils; import cc.ioctl.hook.experimental.CardMsgSender; import cc.ioctl.hook.msg.AioChatPieClipPasteHook; import cc.ioctl.util.HookUtils; +import com.tencent.qqnt.kernel.nativeinterface.Contact; import io.github.duzhaokun123.hook.SendTTSHook; import io.github.qauxv.R; import io.github.qauxv.base.annotation.FunctionHookEntry; +import io.github.qauxv.bridge.AppRuntimeHelper; +import io.github.qauxv.bridge.SessionInfoImpl; +import io.github.qauxv.bridge.ntapi.RelationNTUinAndUidApi; import io.github.qauxv.hook.BaseHookDispatcher; import io.github.qauxv.router.decorator.IBaseChatPieDecorator; import io.github.qauxv.router.decorator.IBaseChatPieInitDecorator; @@ -102,51 +106,67 @@ public boolean initOnce() throws Exception { HookUtils.hookAfterIfEnabled(this, DexKit.requireMethodFromCache(AIO_InputRootInit_QQNT.INSTANCE), 40, param -> { Button sendBtn = null; EditText editText = null; + ViewGroup inputRoot = null; Field[] fs = param.thisObject.getClass().getDeclaredFields(); for (Field f : fs) { Class type = f.getType(); if (type.equals(Button.class)) { f.setAccessible(true); - if ("send_btn".equals(MRes.getViewResName((View) f.get(param.thisObject)))) { - sendBtn = (Button) f.get(param.thisObject); - } + sendBtn = (Button) f.get(param.thisObject); } else if (type.equals(EditText.class)) { f.setAccessible(true); editText = (EditText) f.get(param.thisObject); + } else if (type.equals(ViewGroup.class)) { + f.setAccessible(true); + inputRoot = (ViewGroup) f.get(param.thisObject); } } + + AppRuntime qqApp = AppRuntimeHelper.getAppRuntime(); + Objects.requireNonNull(qqApp, "QQAppInterface is null"); + if (sendBtn != null && editText != null) { EditText finalEditText = editText; Button finalSendBtn = sendBtn; sendBtn.setOnLongClickListener(v -> { Context ctx = v.getContext(); String text = finalEditText.getText().toString(); - Toasts.info(ctx, text); - // TODO: AIOParam是不是onFunBtnLongClick的第二个参数?以及qqApp参数的获取 -// if (((TextView) v).length() == 0) { //|| !CardMsgHook.INSTANCE.isEnabled() -// return false; -// } -// for (IBaseChatPieDecorator decorator : DECORATORS) { -// if (decorator instanceof IInputButtonDecorator) { -// IInputButtonDecorator d = (IInputButtonDecorator) decorator; -// try { -// if (d.isEnabled() && d.onFunBtnLongClick(text, (Parcelable) AIOParam, finalEditText, finalSendBtn, ctx, null)) { -// return true; -// } -// } catch (Throwable e) { -// decorator.traceError(e); -// } -// } -// } - // TODO: BaseChatPie已不存在,onInitBaseChatPie何去何从 + for (IBaseChatPieDecorator decorator : DECORATORS) { + if (decorator instanceof IInputButtonDecorator) { + IInputButtonDecorator d = (IInputButtonDecorator) decorator; + try { + if (d.isEnabled() && d.onFunBtnLongClick(text, getSessionByAIOParam(), finalEditText, finalSendBtn, ctx, qqApp)) { + return true; + } + } catch (Throwable e) { + decorator.traceError(e); + } + } + } return true; }); } else { - Log.e("send_btn field not found"); + Log.e("SendBtn or EditText field not found"); + } + + // 这样写比较好地兼容了原有代码,但不太像是onInitBaseChatPie的意思了,有待优化 + Objects.requireNonNull(inputRoot, "inputRoot is null"); + for (IBaseChatPieDecorator baseDecorator : DECORATORS) { + if (baseDecorator instanceof IBaseChatPieInitDecorator) { + IBaseChatPieInitDecorator decorator = (IBaseChatPieInitDecorator) baseDecorator; + try { + if (decorator.isEnabled()) { + decorator.onInitBaseChatPie(param.thisObject, inputRoot, null, inputRoot.getContext(), qqApp); + } + } catch (Throwable e) { + decorator.traceError(e); + } + } } }); return true; } + //Begin: send btn HookUtils.hookAfterIfEnabled(this, DexKit.requireMethodFromCache(NBaseChatPie_init.INSTANCE), 40, param -> { @@ -259,6 +279,19 @@ public boolean onLongClick(View v) { return true; } + /** + * QQNT: Get session info from AIOParam + */ + public Parcelable getSessionByAIOParam() { + Contact c = SessionUtils.AIOParam2Contact(AIOParam); + int type = c.getChatType() - 1; // chatType: 1 for friend, 2 for group + String uin = c.getPeerUid(); + if (uin.startsWith("u_")) { + uin = RelationNTUinAndUidApi.getUinFromUid(uin); + } + return SessionInfoImpl.createSessionInfo(uin, type); + } + @Override public boolean isEnabled() { for (IBaseChatPieDecorator decorator : DECORATORS) {