From 794ab8e694704aebd174c174d8ad514bf86f2d1b Mon Sep 17 00:00:00 2001 From: suzhelan Date: Tue, 19 Dec 2023 21:22:51 +0800 Subject: [PATCH] fix: Clean Recent Chat - DeleteMode : including group chat, other chat, all chat, topChat --- .../top/linl/hook/FixCleanRecentChat.java | 68 +++++++++++++++++-- .../xyz/nextalone/hook/CleanRecentChat.kt | 29 +++++++- 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/top/linl/hook/FixCleanRecentChat.java b/app/src/main/java/top/linl/hook/FixCleanRecentChat.java index 52edf8d8d7..e23262f29e 100644 --- a/app/src/main/java/top/linl/hook/FixCleanRecentChat.java +++ b/app/src/main/java/top/linl/hook/FixCleanRecentChat.java @@ -22,6 +22,7 @@ package top.linl.hook; +import android.app.Activity; import android.view.View; import android.widget.ImageView; import android.widget.TextView; @@ -29,13 +30,12 @@ import io.github.qauxv.util.Toasts; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -50,14 +50,17 @@ */ public class FixCleanRecentChat { - private static final HashMap viewHolderList = new LinkedHashMap<>(); + private static final ConcurrentHashMap viewHolderList = new ConcurrentHashMap<>(); private static int deleteTextViewId; private final CleanRecentChat cleanRecentChat; + private Activity activity; + public FixCleanRecentChat(CleanRecentChat cleanRecentChat) { this.cleanRecentChat = cleanRecentChat; } + private void hookGetDeleteViewId() { Class superClass = ClassUtils.getClass("com.tencent.qqnt.chats.biz.guild.GuildDiscoveryItemBuilder").getSuperclass(); Class findClass = null; @@ -100,10 +103,11 @@ public void loadHook() throws Exception { Method onCreateMethod = MethodTool.find("com.tencent.mobileqq.activity.home.Conversation").name("onResume").params(boolean.class).get(); HookUtils.hookAfterIfEnabled(cleanRecentChat, onCreateMethod, param -> { ImageView imageView = FieIdUtils.getFirstField(param.thisObject, ImageView.class); + activity = (Activity) imageView.getContext(); imageView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { - new Thread(new DeleteAllItemTask()).start(); + cleanRecentChat.showDialog(activity); return true; } }); @@ -134,14 +138,28 @@ private void hookOnHolder() { viewHolderList.put(param.thisObject, adapterIndex); }); + Method onCreate = MethodTool.find("com.tencent.qqnt.chats.core.adapter.ChatsListAdapter") + .name("onCreateViewHolder") + .params(android.view.ViewGroup.class, int.class) + .get(); + HookUtils.hookAfterIfEnabled(cleanRecentChat, onCreate, param -> { + viewHolderList.put(param.getResult(), (int) param.args[1]); + }); } - private static class DeleteAllItemTask implements Runnable { + public static class DeleteAllItemTask implements Runnable { private static final AtomicReference deleteMethod = new AtomicReference<>(); private static Class utilType; private static Field itemField; + public boolean isDeleteTopMsg = false; + public String deleteMode; + + public DeleteAllItemTask(String deleteMode) { + this.deleteMode = deleteMode; + } + private Object findItemField(Object viewHolder) throws IllegalAccessException { if (itemField != null) { return itemField.get(viewHolder); @@ -185,6 +203,9 @@ private Class findUtilClassType(Object viewHolder) { throw new RuntimeException(e); } } + if (utilType == null) { + throw new RuntimeException("not find Class , viewHolder ClassName is " + viewHolder.getClass().getName()); + } return utilType; } @@ -226,6 +247,11 @@ public void run() { if (size == 0) { try { //停一下等待ItemHolder重新bind到屏幕上 然后继续删除 + /* + * 假设一次能清理屏幕中的 8 个item + * 2000 / 100 * 8 = 400 (个item) + * 清掉所有聊天项应该戳戳有余 有问题调高延迟和时长应该可以解决 + */ TimeUnit.MILLISECONDS.sleep(100); continue; } catch (InterruptedException e) { @@ -237,21 +263,51 @@ public void run() { Map.Entry viewHolderEntry = iterator.next(); try { Object recentContactItemHolder = viewHolderEntry.getKey(); + if (recentContactItemHolder == null) { + continue; + } //delete util Object util = FieIdUtils.getFirstField(recentContactItemHolder, findUtilClassType(recentContactItemHolder));//util run time obj int adapterIndex = viewHolderEntry.getValue();//call param 1 + /* + * { uid=0000, + * title=name, + * contactType=2, + * unreadCount=UnreadInfo(type=2, count=437).count , + * showTime=晚上8:03, + * summary=八 , + * isTop=true, + * isDraft=false} + * */ Object itemInfo = findItemField(recentContactItemHolder);//call param 2 + String itemToString = String.valueOf(itemInfo); + //面向字符串编程 + if (deleteMode.equals("清理群消息")) { + if (!itemToString.contains("contactType=2")) { + continue; + } + } else if (deleteMode.equals("清理其他消息")) { + if (itemToString.contains("contactType=2") || itemToString.contains("contactType=1")) { + continue; + } + } + if (!isDeleteTopMsg) { + if (itemToString.contains("isTop=true")) { + continue; + } + } Object itemBinder = FieIdUtils.getFirstField(recentContactItemHolder, ClassUtils.getClass("com.tencent.qqnt.chats.core.adapter.holder.RecentContactItemBinding"));//call param 3 int viewId = deleteTextViewId;//call param 4 getDeleteMethod(recentContactItemHolder).invoke(util, adapterIndex, itemInfo, itemBinder, viewId); deleteQuantity++; } catch (Exception e) { - throw new RuntimeException(e); + } iterator.remove(); } } + System.gc();//调用gc 防止viewHolder还没被回收 Toasts.show("已清理结束 数量" + deleteQuantity + "个"); } } diff --git a/app/src/main/java/xyz/nextalone/hook/CleanRecentChat.kt b/app/src/main/java/xyz/nextalone/hook/CleanRecentChat.kt index 8bf432d128..c926d1a6a1 100644 --- a/app/src/main/java/xyz/nextalone/hook/CleanRecentChat.kt +++ b/app/src/main/java/xyz/nextalone/hook/CleanRecentChat.kt @@ -22,6 +22,7 @@ package xyz.nextalone.hook +import android.annotation.SuppressLint import android.content.Context import android.widget.ImageView import android.widget.RelativeLayout @@ -67,10 +68,32 @@ object CleanRecentChat : CommonSwitchFunctionHook(arrayOf(NFriendsStatusUtil_isC private const val INCLUDE_TOPPED = "CleanRecentChat_include_topped" private var includeTopped = getDefaultConfig().getBooleanOrDefault(INCLUDE_TOPPED, false) + @SuppressLint("StaticFieldLeak") + private var fixCleanRecentChat: FixCleanRecentChat? = null + fun showDialog(context: Context) { + val contextWrapper = CommonContextWrapper.createMaterialDesignContext(context) + val list = listOf("清理群消息", "清理其他消息", "清理所有消息") + MaterialDialog(contextWrapper).show { + var containsTops = false + title(text = "消息清理") + + checkBoxPrompt(text = "包含置顶消息", isCheckedDefault = includeTopped) { checked -> + containsTops = checked + } + listItems(items = list) { dialog, _, text -> + Toasts.showToast(dialog.context, Toasts.TYPE_INFO, text, Toasts.LENGTH_SHORT) + val deleteAllItemTask = FixCleanRecentChat.DeleteAllItemTask(text as String?) + deleteAllItemTask.isDeleteTopMsg = containsTops + Thread(deleteAllItemTask).start() + + }.ignoreResult() + } + } + override fun initOnce(): Boolean = throwOrTrue { if (QAppUtils.isQQnt()) { - val fix = FixCleanRecentChat(this) - fix.loadHook() + fixCleanRecentChat = FixCleanRecentChat(this) + fixCleanRecentChat!!.loadHook() } else { DexKit.requireMethodFromCache(NConversation_onCreate) .hookAfter(this) { @@ -118,6 +141,8 @@ object CleanRecentChat : CommonSwitchFunctionHook(arrayOf(NFriendsStatusUtil_isC == 1 联系人 == 103 公众号 == 2 群聊 + == 7 群助手 + == 134 我的电脑 == 131 我的关联QQ账号 uid=9992 class: com.tencent.qqnt.chats.core.adapter.holder.deleteMsg extend View.OnClickListener */