diff --git a/.gitignore b/.gitignore index bfc7dc4..3db9f30 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ out/ # Gradle files .gradle/ build/ +release/ # Local configuration file (sdk path, etc) local.properties diff --git a/app/release/app-release.apk b/app/release/app-release.apk deleted file mode 100644 index b6433e5..0000000 Binary files a/app/release/app-release.apk and /dev/null differ diff --git a/app/release/output.json b/app/release/output.json deleted file mode 100644 index 04419af..0000000 --- a/app/release/output.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": 1, - "artifactType": { - "type": "APK", - "kind": "Directory" - }, - "applicationId": "org.yameida.worktool", - "variantName": "release", - "elements": [ - { - "type": "SINGLE", - "filters": [], - "properties": [], - "versionCode": 1, - "versionName": "1", - "enabled": true, - "outputFile": "app-release.apk" - } - ] -} \ No newline at end of file diff --git a/app/src/main/java/org/yameida/worktool/Demo.kt b/app/src/main/java/org/yameida/worktool/Demo.kt index c4c703f..1e414fe 100644 --- a/app/src/main/java/org/yameida/worktool/Demo.kt +++ b/app/src/main/java/org/yameida/worktool/Demo.kt @@ -20,6 +20,15 @@ object Demo { //打印当前视图树 // AccessibilityUtil.printNodeClazzTree(getRoot()) + //手机号添加好友 +// WeworkOperationImpl.addFriendByPhone(WeworkMessageBean.Friend().apply { +// this.phone = "13010001000" +// this.markName = "hhh" +// this.markCorp = "corp" +// this.markExtra = "ex" +// this.tagList = arrayListOf("tag1", "tag2") +// }) + //自动通过好友 // WeworkLoopImpl.getFriendRequest() diff --git a/app/src/main/java/org/yameida/worktool/model/WeworkMessageBean.java b/app/src/main/java/org/yameida/worktool/model/WeworkMessageBean.java index 8ba21f1..b0cec53 100644 --- a/app/src/main/java/org/yameida/worktool/model/WeworkMessageBean.java +++ b/app/src/main/java/org/yameida/worktool/model/WeworkMessageBean.java @@ -167,6 +167,9 @@ public class WeworkMessageBean { //对象名称(图片、文件、小程序等) public String objectName; + //添加好友 + public Friend friend; + public WeworkMessageBean() {} public WeworkMessageBean(String receivedName, String receivedContent, int type, Integer roomType, List titleList, List messageList, String log) { @@ -224,6 +227,22 @@ public class WeworkMessageBean { public String job; } + //添加好友 + public static class Friend { + //按手机号搜索 + public String phone; + //好友姓名 + public String name; + //备注名 + public String markName; + //备注企业 + public String markCorp; + //备注更多描述 + public String markExtra; + //备注标签(推荐) + public List tagList; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/app/src/main/java/org/yameida/worktool/service/GlobalMethod.kt b/app/src/main/java/org/yameida/worktool/service/GlobalMethod.kt index f253c17..a672220 100644 --- a/app/src/main/java/org/yameida/worktool/service/GlobalMethod.kt +++ b/app/src/main/java/org/yameida/worktool/service/GlobalMethod.kt @@ -30,7 +30,7 @@ fun goHomeTab(title: String): Boolean { var atHome = false var find = false while (!atHome) { - val list = getRoot().findAccessibilityNodeInfosByText("消息") + val list = AccessibilityUtil.findAllByText(getRoot(), "消息", timeout = 0) for (item in list) { if (item.parent.parent.parent.childCount == 5) { //处理侧边栏抽屉打开 @@ -42,7 +42,7 @@ fun goHomeTab(title: String): Boolean { } } atHome = true - val tempList = getRoot().findAccessibilityNodeInfosByText(title) + val tempList = AccessibilityUtil.findAllByText(getRoot(), title, timeout = 0) for (tempItem in tempList) { if (tempItem.parent.parent.parent.childCount == 5) { AccessibilityUtil.performClick(tempItem) @@ -54,7 +54,6 @@ fun goHomeTab(title: String): Boolean { } if (!atHome) { backPress() - sleep(1500) } } LogUtils.v("进入首页-${title}页") @@ -65,7 +64,7 @@ fun goHomeTab(title: String): Boolean { * 当前是否在首页 */ fun isAtHome(): Boolean { - val list = getRoot().findAccessibilityNodeInfosByText("消息") + val list = AccessibilityUtil.findAllByText(getRoot(), "消息", timeout = 0) return list.count { it.parent.parent.parent.childCount == 5 } > 0 } @@ -106,30 +105,32 @@ fun getRoot(ignoreCheck: Boolean): AccessibilityNodeInfo { * 后退 */ fun backPress() { - val textView = AccessibilityUtil.findOneByClazz(getRoot(), Views.TextView) + val textView = AccessibilityUtil.findOnceByClazz(getRoot(), Views.TextView) if (textView != null && textView.text.isNullOrBlank() && AccessibilityUtil.performClick(textView)) { LogUtils.v("找到回退按钮") } else { - val ivButton = AccessibilityUtil.findOneByClazz(getRoot(), Views.ImageView) + val ivButton = AccessibilityUtil.findOnceByClazz(getRoot(), Views.ImageView) if (ivButton != null && ivButton.isClickable && AccessibilityUtil.findFrontNode(ivButton) == null) { LogUtils.d("未找到回退按钮 点击第一个IV按钮") AccessibilityUtil.performClick(ivButton) } else { LogUtils.d("未找到回退按钮 点击第一个BT按钮") - val button = AccessibilityUtil.findOneByClazz(getRoot(), Views.Button) + val button = AccessibilityUtil.findOnceByClazz(getRoot(), Views.Button) if (button != null && button.childCount > 0) { AccessibilityUtil.performClick(button.getChild(0)) } else if (button != null) { AccessibilityUtil.performClick(button) } else { LogUtils.d("未找到BT按钮") - if (AccessibilityUtil.findTextAndClick(getRoot(), "确定")) { + val confirm = AccessibilityUtil.findOnceByText(getRoot(), "确定") + if (confirm != null) { LogUtils.d("尝试点击确定") + AccessibilityUtil.performClick(confirm) } } } } - sleep(1000) + sleep(500) } /** diff --git a/app/src/main/java/org/yameida/worktool/service/MyLooper.kt b/app/src/main/java/org/yameida/worktool/service/MyLooper.kt index c8199b2..fc906a1 100644 --- a/app/src/main/java/org/yameida/worktool/service/MyLooper.kt +++ b/app/src/main/java/org/yameida/worktool/service/MyLooper.kt @@ -78,6 +78,7 @@ object MyLooper { for (message in LinkedHashSet(messageList.list)) { getInstance().removeMessages(WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE) if (message.type == WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE) { + WeworkController.enableLoopRunning = true if (!WeworkController.mainLoopRunning) { getInstance().sendMessage(Message.obtain().apply { what = WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE @@ -134,6 +135,7 @@ object MyLooper { WeworkMessageBean.PASS_ALL_FRIEND_REQUEST -> { } WeworkMessageBean.ADD_FRIEND_BY_PHONE -> { + WeworkController.addFriendByPhone(message) } WeworkMessageBean.SHOW_GROUP_INFO -> { WeworkController.showGroupInfo(message) diff --git a/app/src/main/java/org/yameida/worktool/service/WeworkController.kt b/app/src/main/java/org/yameida/worktool/service/WeworkController.kt index 7c5fdd2..00d2e84 100644 --- a/app/src/main/java/org/yameida/worktool/service/WeworkController.kt +++ b/app/src/main/java/org/yameida/worktool/service/WeworkController.kt @@ -12,6 +12,7 @@ import org.yameida.worktool.model.WeworkMessageBean object WeworkController { lateinit var weworkService: WeworkService + var enableLoopRunning = false var mainLoopRunning = false /** @@ -21,6 +22,7 @@ object WeworkController { @RequestMapping fun stopAndGoHome() { LogUtils.d("stopAndGoHome()") + enableLoopRunning = false mainLoopRunning = false goHome() } @@ -60,7 +62,7 @@ object WeworkController { */ @RequestMapping fun replyMessage(message: WeworkMessageBean): Boolean { - LogUtils.d("replyMessage(): ${message.receivedContent}") + LogUtils.d("replyMessage(): ${message.receivedName} ${message.originalContent} ${message.receivedContent}") return WeworkOperationImpl.replyMessage( message.titleList, message.receivedName, @@ -214,6 +216,17 @@ object WeworkController { ) } + /** + * 按手机号添加好友 + * @see WeworkMessageBean.ADD_FRIEND_BY_PHONE + * @param message#friend 待添加用户列表 + */ + @RequestMapping + fun addFriendByPhone(message: WeworkMessageBean): Boolean { + LogUtils.d("addFriendByPhone(): ${message.friend}") + return WeworkOperationImpl.addFriendByPhone(message.friend) + } + /** * 展示群信息 * @see WeworkMessageBean.SHOW_GROUP_INFO diff --git a/app/src/main/java/org/yameida/worktool/service/WeworkGetImpl.kt b/app/src/main/java/org/yameida/worktool/service/WeworkGetImpl.kt index c1536a7..4c5dcbc 100644 --- a/app/src/main/java/org/yameida/worktool/service/WeworkGetImpl.kt +++ b/app/src/main/java/org/yameida/worktool/service/WeworkGetImpl.kt @@ -47,7 +47,7 @@ object WeworkGetImpl { if (!goHomeTab("我")) { LogUtils.d("未找到我的信息") goHomeTab("消息") - val firstTv = AccessibilityUtil.findAllByClazz(getRoot(), Views.TextView) + val firstTv = AccessibilityUtil.findAllByClazz(getRoot(), Views.TextView, root = true) .firstOrNull { it.text == null } AccessibilityUtil.performClick(firstTv) sleep(1000) @@ -67,11 +67,10 @@ object WeworkGetImpl { } } AccessibilityUtil.performClick(AccessibilityUtil.findOneByClazz(getRoot(), Views.ImageView)) - sleep(1500) - val relativeLayoutList = AccessibilityUtil.findAllByClazz(getRoot(), Views.RelativeLayout) + val relativeLayoutList = AccessibilityUtil.findAllByClazz(getRoot(), Views.RelativeLayout, minSize = 70) val myInfo = WeworkMessageBean.MyInfo() for (relativeLayout in relativeLayoutList.filter { it.childCount >= 2 }) { - val textViewList = AccessibilityUtil.findAllByClazz(relativeLayout, Views.TextView) + val textViewList = AccessibilityUtil.findAllOnceByClazz(relativeLayout, Views.TextView) if (textViewList.size >= 2) { val firstText = textViewList[0].text?.toString() if (firstText == "姓名" && myInfo.name == null) { @@ -105,6 +104,7 @@ object WeworkGetImpl { weworkMessageBean.type = WeworkMessageBean.GET_MY_INFO weworkMessageBean.myInfo = myInfo WeworkController.weworkService.webSocketManager.send(weworkMessageBean) + WeworkLoopImpl.startLoop() return true } @@ -114,30 +114,44 @@ object WeworkGetImpl { fun getGroupInfoDetail(): WeworkMessageBean { val weworkMessageBean = WeworkMessageBean() weworkMessageBean.type = WeworkMessageBean.GET_GROUP_INFO - val tvManagerFlag = - AccessibilityUtil.findOneByText(getRoot(), "微信用户创建") + val tvManagerFlag = AccessibilityUtil.findOneByText(getRoot(), "微信用户创建", timeout = 2000) + //不是管理员的群可能没有微信用户创建 todo +// AccessibilityUtil.findOneByText(getRoot(), "全部群成员", timeout = 2000) val button = AccessibilityUtil.findFrontNode(tvManagerFlag) - val tvGroupName = AccessibilityUtil.findOneByClazz(button, Views.TextView) + val tvGroupName = AccessibilityUtil.findOnceByClazz(button, Views.TextView) if (tvGroupName != null && tvGroupName.text != null) { LogUtils.d("群名: " + tvGroupName.text) weworkMessageBean.groupName = tvGroupName.text.toString() + } else { + val groupNameTv = AccessibilityUtil.findOnceByText(getRoot(), "群聊名称") + if (groupNameTv != null) { + val tvList = AccessibilityUtil.findAllOnceByClazz( + groupNameTv.parent.parent.parent, + Views.TextView + ) + if (tvList.size >= 2) { + val groupName = tvList[1] + LogUtils.d("群名: " + groupName.text) + weworkMessageBean.groupName = groupName.text.toString() + } + } } val gridView = AccessibilityUtil.findOneByClazz(getRoot(), Views.GridView) if (gridView != null && gridView.childCount >= 2) { - val tvOwnerName = AccessibilityUtil.findOneByClazz(gridView.getChild(0), Views.TextView) + val tvOwnerName = AccessibilityUtil.findOnceByClazz(gridView.getChild(0), Views.TextView) if (tvOwnerName != null && tvOwnerName.text != null) { LogUtils.d("群主: " + tvOwnerName.text) weworkMessageBean.groupOwner = tvOwnerName.text.toString() } } - val tvCountFlag = AccessibilityUtil.findOneByText(getRoot(), "查看全部群成员") + val tvCountFlag = AccessibilityUtil.findOnceByText(getRoot(), "查看全部群成员") val tvCount = AccessibilityUtil.findBackNode(tvCountFlag) if (tvCount != null && tvCount.text != null) { LogUtils.d("群成员: " + tvCount.text) val count = tvCount.text.toString().replace("人", "") weworkMessageBean.groupNumber = count.toIntOrNull() } - val tvAnnouncementFlag = AccessibilityUtil.findOneByText(getRoot(), "群公告") + val tvAnnouncementFlag = AccessibilityUtil.findOnceByText(getRoot(), "群公告") val tvAnnouncement = AccessibilityUtil.findBackNode(tvAnnouncementFlag) if (tvAnnouncement != null && tvAnnouncement.text != null) { LogUtils.d("群公告: " + tvAnnouncement.text) diff --git a/app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt b/app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt index 1e9ca2a..acfad83 100644 --- a/app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt +++ b/app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt @@ -1,9 +1,9 @@ package org.yameida.worktool.service +import android.os.Message import android.view.accessibility.AccessibilityNodeInfo import androidx.core.text.isDigitsOnly import com.blankj.utilcode.util.LogUtils -import org.yameida.worktool.Demo import org.yameida.worktool.model.WeworkMessageBean import org.yameida.worktool.service.WeworkController.mainLoopRunning import org.yameida.worktool.utils.* @@ -15,14 +15,35 @@ import java.lang.StringBuilder */ object WeworkLoopImpl { + val stopWords = arrayListOf("解析中") var logIndex = 0 + /** + * 如果远端开启接收新消息则本地自动在队列任务结束后调用接收新消息 + * 该方法在每个任务结束时调用 + */ + fun startLoop(delay: Long = 0) { + LogUtils.d("startLoop() delay: $delay") + val myLooper = MyLooper.getInstance() + if (WeworkController.enableLoopRunning) { + myLooper.removeMessages(WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE) + if (!mainLoopRunning) { + myLooper.sendMessageDelayed(Message.obtain().apply { + what = WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE + obj = WeworkMessageBean().apply { type = WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE } + }, delay) + } + } + } + fun mainLoop() { mainLoopRunning = true try { while (mainLoopRunning) { + if (WeworkRoomUtil.getRoomType(getRoot()) != WeworkMessageBean.ROOM_TYPE_UNKNOWN + && getChatMessageList()) { + } goHomeTab("消息") - //todo 下上滚动 if (getChatroomList() && getChatMessageList()) { mainLoopRunning = false break @@ -31,7 +52,7 @@ object WeworkLoopImpl { mainLoopRunning = false break } - sleep(1000) + sleep(500) } } catch (e: Exception) { mainLoopRunning = false @@ -43,13 +64,12 @@ object WeworkLoopImpl { * 读取通讯录好友请求 */ fun getFriendRequest(): Boolean { - val list = getRoot().findAccessibilityNodeInfosByText("通讯录") + val list = AccessibilityUtil.findAllByText(getRoot(), "通讯录", timeout = 0) for (item in list) { if (item.parent.parent.parent.childCount == 5) { if (item.parent.childCount > 1) { LogUtils.d("通讯录有红点") AccessibilityUtil.performClick(item) - sleep(500) val addButton = AccessibilityUtil.findOneByText(getRoot(), "添加客户") val backNode = AccessibilityUtil.findBackNode(addButton) LogUtils.d(backNode?.className) @@ -81,6 +101,54 @@ object WeworkLoopImpl { return false } + /** + * 聊天页 + * 1.获取群名 + * 2.获取消息列表 + */ + fun getChatMessageList(): Boolean { + AccessibilityUtil.performScrollDown(getRoot(), 0) + val roomType = WeworkRoomUtil.getRoomType(getRoot()) + var titleList = WeworkRoomUtil.getRoomTitle(getRoot()) + if (titleList.contains("对方正在输入…")) { + titleList = WeworkRoomUtil.getFriendName() + } + if (titleList.size > 0) { + val title = titleList.joinToString() + LogUtils.i("聊天: $title") + log("聊天: $title") + val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) + if (list != null) { + LogUtils.d("消息条数: " + list.childCount) + val messageList = arrayListOf() + for (i in 0 until list.childCount) { + val item = list.getChild(i) + if (item != null && item.childCount > 0) { + messageList.add(parseChatMessageItem(item, roomType)) + } + } + WeworkController.weworkService.webSocketManager.send( + WeworkMessageBean( + null, null, + WeworkMessageBean.TYPE_RECEIVE_MESSAGE_LIST, + roomType, + titleList, + messageList, + null + ) + ) + //todo 推迟执行获取新消息 + //检查如果当前房间最后一条消息未变化则不推迟 + startLoop(3500) + return true + } else { + LogUtils.e("未找到聊天消息列表") + error("未找到聊天消息列表") + } + } + return false + } + /** * 查看好友请求并通过 */ @@ -88,7 +156,7 @@ object WeworkLoopImpl { val nameList = arrayListOf() val imageView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ImageView) if (imageView != null) { - val textViewList = AccessibilityUtil.findAllByClazz(imageView.parent, Views.TextView) + val textViewList = AccessibilityUtil.findAllOnceByClazz(imageView.parent, Views.TextView) val filter = textViewList.filter { it.text != null && it.text.toString() != "微信" } if (filter.isNotEmpty()) { val tvNick = filter[0] @@ -110,7 +178,7 @@ object WeworkLoopImpl { //回到上一页 var retry = 5 while (retry-- > 0 && !isAtHome()) { - val textView = AccessibilityUtil.findOneByText(getRoot(), "新的客户") + val textView = AccessibilityUtil.findOnceByText(getRoot(), "新的客户") if (textView == null) { backPress() } @@ -124,6 +192,13 @@ object WeworkLoopImpl { * 读取聊天列表 */ private fun getChatroomList(): Boolean { + if (logIndex % 3 == 0) { + AccessibilityUtil.performScrollUp(getRoot(), 0) + AccessibilityUtil.performScrollUp(getRoot(), 0) + AccessibilityUtil.performScrollUp(getRoot(), 0) + } else if (logIndex % 120 < 3) { + AccessibilityUtil.performScrollDown(getRoot(), 0) + } if (logIndex++ % 15 == 0) { LogUtils.i("读取首页聊天列表") log("读取首页聊天列表") @@ -176,58 +251,12 @@ object WeworkLoopImpl { break } } - sleep(1000) return true } else { return false } } - /** - * 聊天页 - * 1.获取群名 - * 2.获取消息列表 - */ - private fun getChatMessageList(): Boolean { - AccessibilityUtil.performScrollDown(getRoot(), 0) - val roomType = WeworkRoomUtil.getRoomType(getRoot()) - var titleList = WeworkRoomUtil.getRoomTitle(getRoot()) - if (titleList.contains("对方正在输入…")) { - titleList = WeworkRoomUtil.getFriendName() - } - if (titleList.size > 0) { - val title = titleList.joinToString() - LogUtils.i("聊天: $title") - log("聊天: $title") - val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) - if (list != null) { - LogUtils.d("消息条数: " + list.childCount) - val messageList = arrayListOf() - for (i in 0 until list.childCount) { - val item = list.getChild(i) - if (item != null && item.childCount > 0) { - messageList.add(parseChatMessageItem(item, roomType)) - } - } - WeworkController.weworkService.webSocketManager.send( - WeworkMessageBean( - null, null, - WeworkMessageBean.TYPE_RECEIVE_MESSAGE_LIST, - roomType, - titleList, - messageList, - null - ) - ) - return true - } else { - LogUtils.e("未找到聊天消息列表") - error("未找到聊天消息列表") - } - } - return false - } - /** * 解析消息列表里的一条消息 */ @@ -240,10 +269,10 @@ object WeworkLoopImpl { val itemMessageList = arrayListOf() LogUtils.d("开始解析一条消息...") //消息头(在消息主体上方 如时间信息) - val linearLayoutItem = AccessibilityUtil.findOneByClazz(node, Views.LinearLayout, 1) + val linearLayoutItem = AccessibilityUtil.findOnceByClazz(node, Views.LinearLayout, 1) if (linearLayoutItem != null) { val sb = StringBuilder("消息头: ") - val tvList = AccessibilityUtil.findAllByClazz(linearLayoutItem, Views.TextView) + val tvList = AccessibilityUtil.findAllOnceByClazz(linearLayoutItem, Views.TextView) for (item in tvList.filter { it.text != null && !it.text.isNullOrBlank() }) { val text = item.text.toString() val itemMessage = WeworkMessageBean.ItemMessageBean(0, text) @@ -253,41 +282,44 @@ object WeworkLoopImpl { LogUtils.d(sb.toString()) } //消息主体 - val relativeLayoutItem = AccessibilityUtil.findOneByClazz(node, Views.RelativeLayout, 1) + val relativeLayoutItem = AccessibilityUtil.findOnceByClazz(node, Views.RelativeLayout, 1) if (relativeLayoutItem != null && relativeLayoutItem.childCount >= 2) { if (Views.ImageView.equals(relativeLayoutItem.getChild(0).className)) { LogUtils.v("头像在左边 本条消息发送者为其他联系人") nameList.addAll(WeworkTextUtil.getNameList(node)) var textType = WeworkMessageBean.TEXT_TYPE_UNKNOWN val relativeLayoutContent = - AccessibilityUtil.findOneByClazz(relativeLayoutItem, Views.RelativeLayout, 2) + AccessibilityUtil.findOnceByClazz(relativeLayoutItem, Views.RelativeLayout, 2) if (relativeLayoutContent != null) { -// AccessibilityUtil.printNodeClazzTree(relativeLayoutContent) textType = WeworkTextUtil.getTextType(relativeLayoutContent) LogUtils.v("textType: $textType") val tvList = - AccessibilityUtil.findAllByClazz(relativeLayoutContent, Views.TextView) + AccessibilityUtil.findAllOnceByClazz(relativeLayoutContent, Views.TextView) for (item in tvList.filter { it.text != null && !it.text.isNullOrBlank() }) { val text = item.text.toString() LogUtils.d(text) - val itemMessage = WeworkMessageBean.ItemMessageBean(2, text) - itemMessageList.add(itemMessage) + if (text !in stopWords) { + val itemMessage = WeworkMessageBean.ItemMessageBean(2, text) + itemMessageList.add(itemMessage) + } } } message = WeworkMessageBean.SubMessageBean(0, textType, itemMessageList, nameList) } else if (Views.ImageView.equals(relativeLayoutItem.getChild(1).className)) { LogUtils.v("头像在右边 本条消息发送者为自己") - val tvList = AccessibilityUtil.findAllByClazz(relativeLayoutItem, Views.TextView) + val tvList = AccessibilityUtil.findAllOnceByClazz(relativeLayoutItem, Views.TextView) for (item in tvList.filter { it.text != null && !it.text.isNullOrBlank() }) { val text = item.text.toString() LogUtils.d(text) - val itemMessage = WeworkMessageBean.ItemMessageBean(2, text) - itemMessageList.add(itemMessage) + if (text !in stopWords) { + val itemMessage = WeworkMessageBean.ItemMessageBean(2, text) + itemMessageList.add(itemMessage) + } } message = WeworkMessageBean.SubMessageBean(1, 0, itemMessageList, nameList) } else { // 没有头像的消息(撤销消息、其他可能的系统消息) - val tvList = AccessibilityUtil.findAllByClazz(node, Views.TextView) + val tvList = AccessibilityUtil.findAllOnceByClazz(node, Views.TextView) for (item in tvList.filter { it.text != null && !it.text.isNullOrBlank() }) { val text = item.text.toString() LogUtils.d(text) @@ -300,7 +332,7 @@ object WeworkLoopImpl { } else { // 没有头像的消息(撤销消息、其他可能的系统消息) val sb = StringBuilder("未发现头像 本条消息发送者未知") - val tvList = AccessibilityUtil.findAllByClazz(node, Views.TextView) + val tvList = AccessibilityUtil.findAllOnceByClazz(node, Views.TextView) for (item in tvList.filter { it.text != null && !it.text.isNullOrBlank() }) { val text = item.text.toString() sb.append(text).append("/t") diff --git a/app/src/main/java/org/yameida/worktool/service/WeworkOperationImpl.kt b/app/src/main/java/org/yameida/worktool/service/WeworkOperationImpl.kt index 10ca3b8..51323bb 100644 --- a/app/src/main/java/org/yameida/worktool/service/WeworkOperationImpl.kt +++ b/app/src/main/java/org/yameida/worktool/service/WeworkOperationImpl.kt @@ -59,13 +59,15 @@ object WeworkOperationImpl { ) ) { LogUtils.d("开始回复") - sleep(1000) sendChatMessage(receivedContent, "[自动回复]") LogUtils.d("$title: 回复成功") + WeworkLoopImpl.getChatMessageList() return true } else { - LogUtils.d("$title: 回复失败") - error("$title: 回复失败 $receivedContent") + LogUtils.d("$title: 回复失败 直接发送答案") + error("$title: 回复失败 直接发送答案 $receivedContent") + sendChatMessage(receivedContent, "[自动回复]【$originalContent】@$receivedName\n") + WeworkLoopImpl.getChatMessageList() } } else { LogUtils.d("$title: 回复失败") @@ -171,10 +173,10 @@ object WeworkOperationImpl { if (newGroupName != null) { groupRename(newGroupName) } - if (selectList != null) { + if (!selectList.isNullOrEmpty()) { groupAddMember(selectList, showMessageHistory) } - if (removeList != null) { + if (!removeList.isNullOrEmpty()) { groupRemoveMember(removeList) } if (newGroupAnnouncement != null) { @@ -201,32 +203,23 @@ object WeworkOperationImpl { val node = AccessibilityUtil.scrollAndFindByText(getRoot(), "微盘") if (node != null) { AccessibilityUtil.performClick(node) - sleep(2000) - val buttonList = AccessibilityUtil.findAllByClazz(getRoot(), Views.Button) + val buttonList = AccessibilityUtil.findAllByClazz(getRoot(), Views.Button, root = true) if (buttonList.size >= 4) { AccessibilityUtil.performClick(buttonList[2]) - sleep(1000) AccessibilityUtil.findTextInput(getRoot(), objectName) - sleep(2000) - val editText = AccessibilityUtil.findOneByClazz(getRoot(), Views.EditText) - val backNode = AccessibilityUtil.findBackNode(editText) - val imageViewList = AccessibilityUtil.findAllByClazz(backNode, Views.ImageView) + val imageViewList = AccessibilityUtil.findAllByClazz(getRoot(), Views.ImageView, root = true) if (imageViewList.size >= 2) { AccessibilityUtil.performClick(imageViewList[1]) - sleep(2000) val shareFileButton = AccessibilityUtil.findOneByDesc(getRoot(), "以原文件分享") AccessibilityUtil.performClick(shareFileButton) - sleep(2000) val shareToWorkButton = AccessibilityUtil.findOneByText(getRoot(true), "发送给同事") AccessibilityUtil.performClick(shareToWorkButton) - sleep(2000) relaySelectTarget(titleList, extraText) - sleep(2000) val stayButton = AccessibilityUtil.findOneByText(getRoot(), "留在企业微信") AccessibilityUtil.performClick(stayButton) return true } else { - LogUtils.e("微盘未搜索到相关文件: $objectName") + LogUtils.e("微盘未搜索到相关图片: $objectName") } } else { LogUtils.e("未找到微盘内搜索") @@ -252,24 +245,16 @@ object WeworkOperationImpl { val node = AccessibilityUtil.scrollAndFindByText(getRoot(), "微盘") if (node != null) { AccessibilityUtil.performClick(node) - sleep(2000) - val buttonList = AccessibilityUtil.findAllByClazz(getRoot(), Views.Button) + val buttonList = AccessibilityUtil.findAllByClazz(getRoot(), Views.Button, root = true) if (buttonList.size >= 4) { AccessibilityUtil.performClick(buttonList[2]) - sleep(1000) AccessibilityUtil.findTextInput(getRoot(), objectName) - sleep(2000) - val editText = AccessibilityUtil.findOneByClazz(getRoot(), Views.EditText) - val backNode = AccessibilityUtil.findBackNode(editText) - val imageViewList = AccessibilityUtil.findAllByClazz(backNode, Views.ImageView) + val imageViewList = AccessibilityUtil.findAllByClazz(getRoot(), Views.ImageView, root = true) if (imageViewList.size >= 2) { AccessibilityUtil.performClick(imageViewList[1]) - sleep(2000) val shareFileButton = AccessibilityUtil.findOneByDesc(getRoot(), "转发") AccessibilityUtil.performClick(shareFileButton) - sleep(2000) relaySelectTarget(titleList, extraText) - sleep(2000) return true } else { LogUtils.e("微盘未搜索到相关文件: $objectName") @@ -298,11 +283,9 @@ object WeworkOperationImpl { val node = AccessibilityUtil.scrollAndFindByText(getRoot(), "用过的小程序") if (node != null) { AccessibilityUtil.performClick(node) - sleep(2000) - val textViewList = AccessibilityUtil.findAllByClazz(getRoot(), Views.TextView) + val textViewList = AccessibilityUtil.findAllByClazz(getRoot(), Views.TextView, root = true) if (textViewList.size > 3) { AccessibilityUtil.performClick(textViewList[2]) - sleep(1000) AccessibilityUtil.findTextInput(getRoot(), objectName) sleep(2000) AccessibilityUtil.findListOneAndClick(getRoot(), 1) @@ -336,31 +319,22 @@ object WeworkOperationImpl { return false } AccessibilityUtil.performClick(allButton) - sleep(1000) val myFileButton = AccessibilityUtil.findOneByText(getRoot(), "共享空间") if (myFileButton == null) { LogUtils.e("未找到共享空间按钮") return false } AccessibilityUtil.performClick(myFileButton) - sleep(2000) - val buttonList = AccessibilityUtil.findAllByClazz(getRoot(), Views.Button) + val buttonList = AccessibilityUtil.findAllByClazz(getRoot(), Views.Button, root = true) if (buttonList.size >= 4) { AccessibilityUtil.performClick(buttonList[3]) - sleep(1000) AccessibilityUtil.findTextInput(getRoot(), objectName) - sleep(2000) - val editText = AccessibilityUtil.findOneByClazz(getRoot(), Views.EditText) - val backNode = AccessibilityUtil.findBackNode(editText) - val imageViewList = AccessibilityUtil.findAllByClazz(backNode, Views.ImageView) + val imageViewList = AccessibilityUtil.findAllByClazz(getRoot(), Views.ImageView, root = true) if (imageViewList.size >= 2) { AccessibilityUtil.performClick(imageViewList[1]) - sleep(2000) val shareFileButton = AccessibilityUtil.findOneByDesc(getRoot(), "转发") AccessibilityUtil.performClick(shareFileButton) - sleep(2000) relaySelectTarget(titleList, extraText) - sleep(2000) return true } else { LogUtils.e("文档未搜索到相关文件: $objectName") @@ -371,6 +345,130 @@ object WeworkOperationImpl { return false } + /** + * 手机号添加好友 + * @see WeworkMessageBean.ADD_FRIEND_BY_PHONE + * @param friend 待添加用户列表 + */ + fun addFriendByPhone( + friend: WeworkMessageBean.Friend + ): Boolean { + goHome() + val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) + if (list != null) { + val frontNode = AccessibilityUtil.findFrontNode(list) + val textViewList = AccessibilityUtil.findAllOnceByClazz(frontNode, Views.TextView) + if (textViewList.size >= 2) { + val searchButton: AccessibilityNodeInfo = textViewList[textViewList.size - 2] + val multiButton: AccessibilityNodeInfo = textViewList[textViewList.size - 1] + AccessibilityUtil.performClick(multiButton) + sleep(500) + val listViewList = AccessibilityUtil.findAllByClazz(getRoot(), Views.ListView, root = true) + if (!listViewList.isNullOrEmpty()) { + if (AccessibilityUtil.findTextAndClick(listViewList.last(), "添加客户")) { + AccessibilityUtil.findTextAndClick(getRoot(), "搜索手机号添加") + AccessibilityUtil.findTextInput(getRoot(), friend.phone.trim()) + if (AccessibilityUtil.findTextAndClick(getRoot(), "网络查找手机")) { + val bothUsedTv = AccessibilityUtil.findOneByText(getRoot(), "对方同时使用") + if (bothUsedTv != null) { + if (AccessibilityUtil.performClick( + AccessibilityUtil.findOnceByClazz( + AccessibilityUtil.findBackNode(bothUsedTv), + Views.ImageView + ) + ) + ) { + sleep(2000) + } else { + LogUtils.e("未找到可点击图标") + } + } + } else { + LogUtils.e("未找到查找手机选项") + } + if (AccessibilityUtil.findOneByText(getRoot(), "标签") != null) { + var markTv = AccessibilityUtil.findOnceByText(getRoot(), "设置备注和描述") + if (markTv == null) { + markTv = AccessibilityUtil.findOnceByText(getRoot(), "企业") + } + if (markTv == null) { + markTv = AccessibilityUtil.findOnceByText(getRoot(), "描述") + } + //设置备注 + if (markTv != null && (friend.markName != null + || friend.markCorp != null || friend.markExtra != null) + ) { + AccessibilityUtil.performClick(markTv) + val etList = + AccessibilityUtil.findAllByClazz(getRoot(), Views.EditText, root = true, minSize = 5) + if (etList.size >= 5) { + if (friend.markName != null) { + AccessibilityUtil.editTextInput(etList[0], friend.markName) + } + if (friend.markCorp != null) { + AccessibilityUtil.editTextInput(etList[1], friend.markCorp) + } + if (friend.markExtra != null) { + AccessibilityUtil.editTextInput(etList[4], friend.markExtra) + } + } + AccessibilityUtil.findTextAndClick(getRoot(), "保存") + sleep(2000) + } + //设置标签 + if (!friend.tagList.isNullOrEmpty()) { + if (AccessibilityUtil.findTextAndClick(getRoot(), "标签")) { + sleep(1000) + setFriendTags(friend.tagList) + sleep(1000) + } + } + //添加联系人 + val imageView = + AccessibilityUtil.findOneByClazz(getRoot(), Views.ImageView) + if (imageView != null) { + val textViewList = AccessibilityUtil.findAllOnceByClazz( + imageView.parent, + Views.TextView + ) + val filter = + textViewList.filter { it.text != null && it.text.toString() != "微信" } + if (filter.isNotEmpty()) { + val tvNick = filter[0] + LogUtils.d("好友昵称或备注名: " + tvNick.text) + } + } + if (AccessibilityUtil.findTextAndClick(getRoot(), "添加为联系人")) { + LogUtils.d("添加好友成功: " + friend.phone) + sleep(2000) + if (AccessibilityUtil.findTextAndClick(getRoot(), "发送添加邀请")) { + LogUtils.d("发送添加邀请成功: " + friend.phone) + } + } else { + if (AccessibilityUtil.findOnceByText(getRoot(), "发消息") != null) { + LogUtils.e("已经添加联系人,请勿重复添加") + } else { + LogUtils.e("未找到添加为联系人") + } + } + } else { + LogUtils.e("未找到标签") + } + } else { + LogUtils.e("未找到添加客户按钮") + } + } else { + LogUtils.e("未找到添加客户列表") + } + return true + } else { + LogUtils.e("未找到搜索按钮") + } + } + LogUtils.e("未找到聊天列表") + return false + } + /** * 展示群信息 * @see WeworkMessageBean.SHOW_GROUP_INFO @@ -409,20 +507,21 @@ object WeworkOperationImpl { val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) if (list != null) { val frontNode = AccessibilityUtil.findFrontNode(list) - val textViewList = AccessibilityUtil.findAllByClazz(frontNode, Views.TextView) + val textViewList = AccessibilityUtil.findAllOnceByClazz(frontNode, Views.TextView) if (textViewList.size >= 2) { val searchButton: AccessibilityNodeInfo = textViewList[textViewList.size - 2] val multiButton: AccessibilityNodeInfo = textViewList[textViewList.size - 1] AccessibilityUtil.performClick(multiButton) sleep(1000) AccessibilityUtil.performClick(searchButton) + //todo 搜索需要在循环内 sleep(1000) for (select in selectList) { AccessibilityUtil.findTextInput(getRoot(), select) sleep(2000) val selectListView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) val imageView = - AccessibilityUtil.findOneByClazz(selectListView, Views.ImageView) + AccessibilityUtil.findOnceByClazz(selectListView, Views.ImageView) if (imageView != null) { AccessibilityUtil.performClick(imageView) } @@ -438,7 +537,7 @@ object WeworkOperationImpl { AccessibilityUtil.findTextInput(getRoot(), extraText) sleep(1000) } - val sendButtonList = getRoot().findAccessibilityNodeInfosByText("发送") + val sendButtonList = AccessibilityUtil.findAllByText(getRoot(), "发送", timeout = 0) for (sendButton in sendButtonList.filter { it.text != null }) { if (sendButton.text == "发送" || sendButton.text == "发送(${selectList.size})") { AccessibilityUtil.performClick(sendButton) @@ -468,10 +567,8 @@ object WeworkOperationImpl { val textViewGroup = AccessibilityUtil.scrollAndFindByText(getRoot(), "客户群") if (AccessibilityUtil.performClick(textViewGroup)) { LogUtils.d("进入客户群应用") - sleep(2000) val textView = AccessibilityUtil.findOneByText(getRoot(), "创建一个客户群") AccessibilityUtil.performClick(textView) - sleep(3000) return true } else { LogUtils.d("未找到客户群应用") @@ -484,12 +581,11 @@ object WeworkOperationImpl { */ private fun groupRename(groupName: String): Boolean { if (WeworkRoomUtil.intoGroupManager()) { - val textView = - AccessibilityUtil.findOneByText(getRoot(), "微信用户创建") + val textView = AccessibilityUtil.findOneByText(getRoot(), "微信用户创建") + //todo 微信用户创建可能找不到 val button = AccessibilityUtil.findFrontNode(textView) if (button != null) { AccessibilityUtil.performClick(button) - sleep(1000) AccessibilityUtil.findTextInput(getRoot(), groupName) val confirmButton = AccessibilityUtil.findOneByText(getRoot(), "确定") AccessibilityUtil.performClick(confirmButton) @@ -520,7 +616,6 @@ object WeworkOperationImpl { } else { AccessibilityUtil.performClick(gridView.getChild(gridView.childCount - 2)) } - sleep(1000) } else { LogUtils.e("未找到添加成员按钮") return false @@ -528,32 +623,29 @@ object WeworkOperationImpl { val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) if (list != null) { val frontNode = AccessibilityUtil.findFrontNode(list) - val textViewList = AccessibilityUtil.findAllByClazz(frontNode, Views.TextView) + val textViewList = AccessibilityUtil.findAllOnceByClazz(frontNode, Views.TextView) if (textViewList.size >= 2) { val multiButton = textViewList.lastOrNull() - AccessibilityUtil.performClick(multiButton) - sleep(1000) for (select in selectList) { + AccessibilityUtil.performClick(multiButton) AccessibilityUtil.findTextInput(getRoot(), select) - sleep(2000) val selectListView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) val imageView = - AccessibilityUtil.findOneByClazz(selectListView, Views.ImageView) - if (imageView != null) { - AccessibilityUtil.performClick(imageView) + AccessibilityUtil.findOneByClazz(selectListView, Views.ImageView, root = false) + AccessibilityUtil.performClick(imageView) + val textView = AccessibilityUtil.findOnceByClazz(getRoot(), Views.TextView) + if (textView != null && textView.text.isNullOrBlank()) { + AccessibilityUtil.performClick(textView) } - sleep(1000) } if (showMessageHistory) { - val button = AccessibilityUtil.findOneByText(getRoot(), "附带聊天记录") - if (button != null) AccessibilityUtil.performClick(button) + AccessibilityUtil.findTextAndClick(getRoot(), "聊天记录") } val confirmButton = AccessibilityUtil.findOneByText(getRoot(), "确定(${selectList.size})") if (confirmButton != null) { AccessibilityUtil.performClick(confirmButton) - sleep(1000) } else { LogUtils.e("未发现确认按钮: ") return false @@ -574,6 +666,7 @@ object WeworkOperationImpl { * 移除群成员/踢人 */ private fun groupRemoveMember(removeList: List): Boolean { + if (removeList.isNullOrEmpty()) return true if (WeworkRoomUtil.intoGroupManager()) { val gridView = AccessibilityUtil.findOneByClazz(getRoot(), Views.GridView) if (gridView != null && gridView.childCount >= 2) { @@ -582,7 +675,6 @@ object WeworkOperationImpl { } else { AccessibilityUtil.performClick(gridView.getChild(gridView.childCount - 1)) } - sleep(1000) } else { LogUtils.e("未找到删除成员按钮") return false @@ -590,28 +682,26 @@ object WeworkOperationImpl { val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) if (list != null) { val frontNode = AccessibilityUtil.findFrontNode(list) - val textViewList = AccessibilityUtil.findAllByClazz(frontNode, Views.TextView) + val textViewList = AccessibilityUtil.findAllOnceByClazz(frontNode, Views.TextView) if (textViewList.size >= 2) { val multiButton = textViewList.lastOrNull() - AccessibilityUtil.performClick(multiButton) - sleep(1000) for (select in removeList) { + AccessibilityUtil.performClick(multiButton) AccessibilityUtil.findTextInput(getRoot(), select) - sleep(2000) val selectListView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) val imageView = - AccessibilityUtil.findOneByClazz(selectListView, Views.ImageView) - if (imageView != null) { - AccessibilityUtil.performClick(imageView) + AccessibilityUtil.findOneByClazz(selectListView, Views.ImageView, root = false) + AccessibilityUtil.performClick(imageView) + val textView = AccessibilityUtil.findOnceByClazz(getRoot(), Views.TextView) + if (textView != null && textView.text.isNullOrBlank()) { + AccessibilityUtil.performClick(textView) } - sleep(1000) } val confirmButton = AccessibilityUtil.findOneByText(getRoot(), "移出(${removeList.size})") if (confirmButton != null) { AccessibilityUtil.performClick(confirmButton) - sleep(1000) } else { LogUtils.e("未发现移出按钮: ") return false @@ -631,6 +721,7 @@ object WeworkOperationImpl { /** * 修改群公告 * 注:首次为发布 后续为编辑 + * 注2:外部群为edittext 内部群为webview(只能追加文本) */ private fun groupChangeAnnouncement(groupAnnouncement: String? = null): Boolean { if (groupAnnouncement == null) return true @@ -638,8 +729,7 @@ object WeworkOperationImpl { val textView = AccessibilityUtil.findOneByText(getRoot(), "群公告") if (textView != null) { AccessibilityUtil.performClick(textView) - sleep(1000) - val editButton = AccessibilityUtil.findOneByText(getRoot(), "编辑") + val editButton = AccessibilityUtil.findOneByText(getRoot(), "编辑", timeout = 2000) if (editButton != null) { LogUtils.d("群公告编辑中: $groupAnnouncement") AccessibilityUtil.performClick(editButton) @@ -647,15 +737,15 @@ object WeworkOperationImpl { } if (AccessibilityUtil.findTextInput(getRoot(), groupAnnouncement)) { LogUtils.d("群公告发布中: $groupAnnouncement") - sleep(500) - val button = AccessibilityUtil.findOneByText(getRoot(), "发布") - AccessibilityUtil.performClick(button) - sleep(1000) - val publishButtonList = getRoot().findAccessibilityNodeInfosByText("发布") - if (publishButtonList.size >= 2) { - AccessibilityUtil.performClick(publishButtonList[1]) + if (AccessibilityUtil.findTextAndClick(getRoot(), "发布")) { + val publishButtonList = AccessibilityUtil.findAllByText(getRoot(), "发布") + if (publishButtonList.size >= 2) { + AccessibilityUtil.performClick(publishButtonList[1]) + } + sleep(3000) + } else { + LogUtils.e("无法进行群公告发布: ") } - sleep(3000) } else { LogUtils.e("无法进行群公告发布和编辑: ") return false @@ -664,6 +754,8 @@ object WeworkOperationImpl { LogUtils.e("未找到群公告按钮") return false } + } else { + LogUtils.e("进入群管理页失败") } return true } @@ -672,36 +764,78 @@ object WeworkOperationImpl { * 发送消息 */ private fun sendChatMessage(text: String, prefix: String = "") { - val editText = AccessibilityUtil.findOneByClazz(getRoot(), Views.EditText) - if (editText != null) { - AccessibilityUtil.editTextInput(editText, prefix + text) - //输入完文字等待出现发送按钮 - sleep(500) - } else { - LogUtils.e("未找到输入框") - error("未找到输入框") - } - var index = 0 - while (index++ < 5) { - val buttonList = getRoot().findAccessibilityNodeInfosByText("发送") - var sendButton: AccessibilityNodeInfo? = null - for (button in buttonList) { - if (button.className == Views.Button) { - sendButton = button - } - } + if (AccessibilityUtil.findTextInput(getRoot(), prefix + text)) { + val sendButton = AccessibilityUtil.findAllByClazz(getRoot(), Views.Button) + .firstOrNull { it.text == "发送" } if (sendButton != null) { - LogUtils.i("发送消息: \n$text") + LogUtils.d("发送消息: \n$text") log("发送消息: \n$text") AccessibilityUtil.performClick(sendButton) - sleep(500) - break } else { LogUtils.e("未找到发送按钮") error("未找到发送按钮") } - sleep(500) + } else { + LogUtils.e("未找到输入框") + error("未找到输入框") } } + /** + * 设置好友标签 + */ + private fun setFriendTags(tagList: List): Boolean { + val tagList = if (tagList.size > 5) tagList.subList(0, 5) else tagList + val tvTag = AccessibilityUtil.findAllByText(getRoot(), "个人标签").lastOrNull() + if (tvTag != null) { + val list = AccessibilityUtil.findBackNode(tvTag) + if (list != null && list.childCount > 0) { + LogUtils.v("list.childCount: " + list.childCount) + val tvAdd = list.getChild(0) + val oldTagList = arrayListOf() + for (i in 0 until list.childCount) { + val child = list.getChild(i) + if (child.className.equals(Views.TextView) && child.text != null) { + oldTagList.add(child.text.toString()) + } + } + //不存在的标签先添加 + for (tag in tagList) { + if (!oldTagList.contains(tag)) { + AccessibilityUtil.performClick(tvAdd) + sleep(500) + AccessibilityUtil.findTextInput(getRoot(), tag) + AccessibilityUtil.findTextAndClick(getRoot(), "确定") + sleep(1000) + } + } + //确认只选择列表里的标签 + val count = list.childCount + for (i in 0 until count) { + val child = list.getChild(i) + if (child != null) { + val text = child.text + val selected = child.isSelected + LogUtils.v("text: $text selected: $selected") + if (tagList.count { it == text } > 0) { + if (!selected) { + AccessibilityUtil.performClick(child) + } + } else { + if (selected) { + AccessibilityUtil.performClick(child) + } + } + } + list.refresh() + } + if (AccessibilityUtil.findTextAndClick(getRoot(), "确定")) { + return true + } + } + } + LogUtils.e("未找到个人标签") + return false + } + } \ No newline at end of file diff --git a/app/src/main/java/org/yameida/worktool/utils/AccessibilityUtil.kt b/app/src/main/java/org/yameida/worktool/utils/AccessibilityUtil.kt index 9f30f24..77d3f67 100644 --- a/app/src/main/java/org/yameida/worktool/utils/AccessibilityUtil.kt +++ b/app/src/main/java/org/yameida/worktool/utils/AccessibilityUtil.kt @@ -11,6 +11,10 @@ import android.os.Bundle import android.util.Log import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo +import com.blankj.utilcode.util.LogUtils +import org.yameida.worktool.service.getRoot +import java.lang.Exception +import java.lang.Thread.sleep /** * 1.查询类 @@ -45,6 +49,8 @@ import android.view.accessibility.AccessibilityNodeInfo */ object AccessibilityUtil { private const val tag = "AccessibilityUtil" + private const val SHORT_INTERVAL = 100L + private const val SCROLL_INTERVAL = 300L //编辑EditView(非粘贴 推荐) fun editTextInput(nodeInfo: AccessibilityNodeInfo?, text: String): Boolean { @@ -65,22 +71,31 @@ object AccessibilityUtil { } //寻找第一个EditView编辑框并输入文本 - fun findTextInput(nodeInfo: AccessibilityNodeInfo?, text: String): Boolean { - val nodeInfo: AccessibilityNodeInfo = nodeInfo ?: return false - val editText = findOneByClazz(nodeInfo, "android.widget.EditText") ?: return false - val arguments = Bundle() - arguments.putCharSequence( - AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, - text - ) - editText.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments) + fun findTextInput(nodeInfo: AccessibilityNodeInfo?, text: String, root: Boolean = true): Boolean { + if (root) { + val editText = findOneByClazz(nodeInfo, "android.widget.EditText") ?: return false + val arguments = Bundle() + arguments.putCharSequence( + AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, + text + ) + editText.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments) + } else { + val editText = findOnceByClazz(nodeInfo, "android.widget.EditText") ?: return false + val arguments = Bundle() + arguments.putCharSequence( + AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, + text + ) + editText.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments) + } return true } //寻找第一个列表并点击指定条目(默认点击第一个条目) fun findListOneAndClick(nodeInfo: AccessibilityNodeInfo, index: Int = 0): Boolean { - val rv = findOneByClazz(nodeInfo, "androidx.recyclerview.widget.RecyclerView") - val lv = findOneByClazz(nodeInfo, "android.widget.ListView") + val rv = findOnceByClazz(nodeInfo, "androidx.recyclerview.widget.RecyclerView") + val lv = findOnceByClazz(nodeInfo, "android.widget.ListView") if (rv == null && lv == null) return false if (rv != null && rv.childCount > index) { performClick(rv.getChild(index)) @@ -99,16 +114,14 @@ object AccessibilityUtil { var index = 0 while (index++ < maxRetry) { performScrollUp(nodeInfo, 0) - Thread.sleep(300) - val node = findOneByText(nodeInfo, text) + val node = findOnceByText(nodeInfo, text) if (node != null) { return node } } while (index++ < maxRetry * 2) { performScrollDown(nodeInfo, 0) - Thread.sleep(300) - val node = findOneByText(nodeInfo, text) + val node = findOnceByText(nodeInfo, text) if (node != null) { return node } @@ -249,6 +262,7 @@ object AccessibilityUtil { val canScrollNodeList = findCanScrollNode(node) if (canScrollNodeList.size > index) { canScrollNodeList[index].performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) + sleep(SCROLL_INTERVAL) return true } return false @@ -260,6 +274,7 @@ object AccessibilityUtil { val canScrollNodeList = findCanScrollNode(node) if (canScrollNodeList.size > index) { canScrollNodeList[index].performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) + sleep(SCROLL_INTERVAL) return true } return false @@ -301,56 +316,134 @@ object AccessibilityUtil { service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME) } - //按描述寻找节点和子节点内的一个匹配项 + /** + * 按描述寻找节点和子节点内的一个匹配项 + * @param node 节点 + * @param desc 描述 + * @param timeout 检查超时时间 + */ fun findOneByDesc( node: AccessibilityNodeInfo?, desc: String, - desc2: String? = null + timeout: Long = 5000 + ): AccessibilityNodeInfo? { + var node = node ?: return null + val description = node.contentDescription?.toString() + if (description == desc) { + return node + } + val startTime = System.currentTimeMillis() + var currentTime = startTime + while (currentTime - startTime <= timeout) { + val result = findOnceByDesc(node, desc) + if (result != null) return result + sleep(SHORT_INTERVAL) + node = getRoot(true) + currentTime = System.currentTimeMillis() + } + Log.e(tag, "findOneByDesc: not found: $desc") + return null + } + + fun findOnceByDesc( + node: AccessibilityNodeInfo?, + desc: String ): AccessibilityNodeInfo? { if (node == null) return null val description = node.contentDescription?.toString() - if (description == desc || (desc2 != null && description == desc2)) { + if (description == desc) { return node } for (i in 0 until node.childCount) { - val result = findOneByDesc(node.getChild(i), desc, desc2) + val result = findOnceByDesc(node.getChild(i), desc) if (result != null) return result } return null } - //按文本(关键词)寻找节点和子节点内的一个匹配项 + /** + * 按文本(关键词)寻找节点和子节点内的一个匹配项 + * @param node 节点 + * @param text 关键词 + * @param timeout 检查超时时间 + */ fun findOneByText( node: AccessibilityNodeInfo?, text: String, - text2: String? = null + exact: Boolean = false, + timeout: Long = 5000, + root: Boolean = true ): AccessibilityNodeInfo? { - if (node == null) return null - val textViewList = node.findAccessibilityNodeInfosByText(text) - if (textViewList != null && textViewList.size > 0) { - for (textView in textViewList) { - if (textView.text == text) { - return textView + var node = node ?: return null + val startTime = System.currentTimeMillis() + var currentTime = startTime + while (currentTime - startTime <= timeout) { + val textViewList = node.findAccessibilityNodeInfosByText(text) + LogUtils.v("text: $text count: " + textViewList.size) + if (textViewList != null && textViewList.size > 0) { + for (textView in textViewList) { + if (textView.text == text) { + return textView + } + } + if (!exact) { + return textViewList[0] } } - return textViewList[0] - } else if (text2 != null) - return findOneByText(node, text2) + sleep(SHORT_INTERVAL) + if (root) { + node = getRoot(true) + } else { + node.refresh() + } + currentTime = System.currentTimeMillis() + } + Log.e(tag, "findOneByText: not found: $text") return null } - //按文本(关键词)寻找节点和子节点内的所有匹配项 + fun findOnceByText( + node: AccessibilityNodeInfo?, + text: String, + exact: Boolean = false + ): AccessibilityNodeInfo? { + return findOneByText(node, text, exact, 0) + } + + /** + * 按文本(关键词)寻找节点和子节点内的所有匹配项 + * @param node 节点 + * @param text 关键词 + * @param timeout 检查超时时间 + */ fun findAllByText( node: AccessibilityNodeInfo?, text: String, - text2: String? = null + exact: Boolean = false, + timeout: Long = 5000, + root: Boolean = true ): List { - if (node == null) return arrayListOf() - val textViewList = node.findAccessibilityNodeInfosByText(text) - if (textViewList != null && textViewList.size > 0) - return textViewList - else if (text2 != null) - return findAllByText(node, text2) + var node = node ?: return arrayListOf() + val startTime = System.currentTimeMillis() + var currentTime = startTime + while (currentTime - startTime <= timeout) { + val tvList = node.findAccessibilityNodeInfosByText(text) + if (tvList != null && tvList.size > 0) { + if (!exact) { + return tvList + } else if (tvList.count { it.text == text } > 0) { + return tvList.filter { it.text == text } + } + } + sleep(SHORT_INTERVAL) + if (root) { + node = getRoot(true) + } else { + node.refresh() + } + currentTime = System.currentTimeMillis() + } + Log.e(tag, "findAllByText: not found: $text") return arrayListOf() } @@ -359,21 +452,59 @@ object AccessibilityUtil { * node 节点 * clazz 类名 * limitDepth 深度 限制深度搜索深度必须匹配提供值且类名相同才返回 不填默认不限制 + * timeout 检查超时时间 */ fun findOneByClazz( + node: AccessibilityNodeInfo?, + clazz: String, + limitDepth: Int? = null, + depth: Int = 0, + timeout: Long = 5000, + root: Boolean = true + ): AccessibilityNodeInfo? { + var node = node ?: return null + if (node.className == clazz) { + if (limitDepth == null || limitDepth == depth) + return node + } + val startTime = System.currentTimeMillis() + var currentTime = startTime + while (currentTime - startTime <= timeout) { + val result = findOnceByClazz(node, clazz, limitDepth, depth) + LogUtils.v("clazz: $clazz result == null: ${result == null}") + if (result != null) return result + sleep(SHORT_INTERVAL) + if (root) { + node = getRoot(true) + } else { + node.refresh() + } + currentTime = System.currentTimeMillis() + } + LogUtils.e("findOneByClazz Exception()") + Exception().printStackTrace() + return null + } + + /** + * 按类名寻找节点和子节点内的一个匹配项 + * node 节点 + * clazz 类名 + * limitDepth 深度 限制深度搜索深度必须匹配提供值且类名相同才返回 不填默认不限制 + */ + fun findOnceByClazz( node: AccessibilityNodeInfo?, clazz: String, limitDepth: Int? = null, depth: Int = 0 ): AccessibilityNodeInfo? { if (node == null) return null -// Log.d(tag, "node.className: " + node.className) if (node.className == clazz) { if (limitDepth == null || limitDepth == depth) return node } for (i in 0 until node.childCount) { - val result = findOneByClazz(node.getChild(i), clazz, limitDepth, depth + 1) + val result = findOnceByClazz(node.getChild(i), clazz, limitDepth, depth + 1) if (result != null) return result } return null @@ -386,15 +517,48 @@ object AccessibilityUtil { * limitDepth 深度 限制深度搜索深度必须匹配提供值且类名相同才返回 不填默认不限制 */ fun findAllByClazz( + node: AccessibilityNodeInfo?, + clazz: String, + list: ArrayList = ArrayList(), + timeout: Long = 5000, + root: Boolean = true, + minSize: Int = 1 + ): ArrayList { + var node = node ?: return list + val startTime = System.currentTimeMillis() + var currentTime = startTime + while (currentTime - startTime <= timeout) { + val result = findAllOnceByClazz(node, clazz) + LogUtils.v("clazz: $clazz count: " + result.size) + if (result.size >= minSize) return result + sleep(SHORT_INTERVAL) + if (root) { + node = getRoot(true) + } else { + node.refresh() + } + currentTime = System.currentTimeMillis() + } + LogUtils.e("findAllByClazz Exception()") + Exception().printStackTrace() + return list + } + + /** + * 按类名寻找节点和子节点内的所有匹配项 + * node 节点 + * clazz 类名 + * limitDepth 深度 限制深度搜索深度必须匹配提供值且类名相同才返回 不填默认不限制 + */ + fun findAllOnceByClazz( node: AccessibilityNodeInfo?, clazz: String, list: ArrayList = ArrayList() ): ArrayList { if (node == null) return list -// Log.d(tag, "node.className: " + node.className) if (node.className == clazz) list.add(node) for (i in 0 until node.childCount) { - findAllByClazz(node.getChild(i), clazz, list) + findAllOnceByClazz(node.getChild(i), clazz, list) } return list } @@ -405,7 +569,6 @@ object AccessibilityUtil { */ fun findFrontNode(node: AccessibilityNodeInfo?): AccessibilityNodeInfo? { if (node == null) return null -// Log.d(tag, "node.className: " + node.className) var parent: AccessibilityNodeInfo? = node.parent var son: AccessibilityNodeInfo? = node while (parent != null) { @@ -431,7 +594,6 @@ object AccessibilityUtil { */ fun findBackNode(node: AccessibilityNodeInfo?): AccessibilityNodeInfo? { if (node == null) return null -// Log.d(tag, "node.className: " + node.className) var parent: AccessibilityNodeInfo? = node.parent var son: AccessibilityNodeInfo? = node while (parent != null) { diff --git a/app/src/main/java/org/yameida/worktool/utils/WeworkRoomUtil.kt b/app/src/main/java/org/yameida/worktool/utils/WeworkRoomUtil.kt index b17421c..9904b4c 100644 --- a/app/src/main/java/org/yameida/worktool/utils/WeworkRoomUtil.kt +++ b/app/src/main/java/org/yameida/worktool/utils/WeworkRoomUtil.kt @@ -9,7 +9,7 @@ import org.yameida.worktool.service.backPress import org.yameida.worktool.service.getRoot import org.yameida.worktool.service.goHome import org.yameida.worktool.service.sleep -import org.yameida.worktool.utils.AccessibilityUtil.findAllByClazz +import org.yameida.worktool.utils.AccessibilityUtil.findAllOnceByClazz /** * 房间特征分析工具类 @@ -56,10 +56,10 @@ object WeworkRoomUtil { */ fun getRoomTitle(root: AccessibilityNodeInfo): ArrayList { val titleList = arrayListOf() - val list = findOneByClazz(root, Views.ListView) + val list = AccessibilityUtil.findOnceByClazz(root, Views.ListView) if (list != null) { val frontNode = findFrontNode(list.parent.parent) - val textViewList = findAllByClazz(frontNode, Views.TextView) + val textViewList = findAllOnceByClazz(frontNode, Views.TextView) for (textView in textViewList) { if (!textView.text.isNullOrBlank()) { titleList.add(textView.text.toString().replace("\\(\\d+\\)$".toRegex(), "")) @@ -78,7 +78,11 @@ object WeworkRoomUtil { val titleList = getRoomTitle(getRoot()) val roomType = getRoomType(getRoot()) if (roomType != WeworkMessageBean.ROOM_TYPE_UNKNOWN - && titleList.count { it.replace("…", "") == title.replace("…", "") } > 0) { + && titleList.count { + it.replace("…", "").replace("\\(.*?\\)".toRegex(), "") == title.replace("…", "") + .replace("\\(.*?\\)".toRegex(), "") + } > 0 + ) { LogUtils.d("当前正在房间") return true } @@ -86,25 +90,16 @@ object WeworkRoomUtil { val list = findOneByClazz(getRoot(), Views.ListView) if (list != null) { val frontNode = findFrontNode(list) - val textViewList = findAllByClazz(frontNode, Views.TextView) + val textViewList = findAllOnceByClazz(frontNode, Views.TextView) if (textViewList.size >= 2) { val searchButton: AccessibilityNodeInfo = textViewList[textViewList.size - 2] val multiButton: AccessibilityNodeInfo = textViewList[textViewList.size - 1] AccessibilityUtil.performClick(searchButton) - sleep(1000) AccessibilityUtil.findTextInput(getRoot(), title.replace("…", "")) sleep(1000) - var selectListView: AccessibilityNodeInfo? = null - while (selectListView == null) { - LogUtils.d("未找到搜索结果列表") - selectListView = findOneByClazz(getRoot(), Views.ListView) - sleep(500) - } - val imageView = findOneByClazz(selectListView, Views.ImageView) - if (imageView != null) { - AccessibilityUtil.performClick(imageView) - } - sleep(2000) + val selectListView = findOneByClazz(getRoot(), Views.ListView) + val imageView = AccessibilityUtil.findOnceByClazz(selectListView, Views.ImageView) + AccessibilityUtil.performClick(imageView) return true } else { LogUtils.e("未找到搜索按钮") @@ -119,17 +114,17 @@ object WeworkRoomUtil { * @return true 成功进入群管理页 */ fun intoGroupManager(): Boolean { - if (AccessibilityUtil.findOneByText(getRoot(), "微信用户创建") != null) { + if (AccessibilityUtil.findOnceByText(getRoot(), "全部群成员") != null + || AccessibilityUtil.findOnceByText(getRoot(), "微信用户创建") != null) { return true } val list = findOneByClazz(getRoot(), Views.ListView) if (list != null) { val frontNode = AccessibilityUtil.findFrontNode(list.parent.parent) - val textViewList = findAllByClazz(frontNode, Views.TextView) + val textViewList = findAllOnceByClazz(frontNode, Views.TextView) if (textViewList.size >= 2) { val multiButton = textViewList.lastOrNull() AccessibilityUtil.performClick(multiButton) - sleep(2000) return true } else { LogUtils.e("未找到群管理按钮") @@ -149,11 +144,10 @@ object WeworkRoomUtil { val list = findOneByClazz(getRoot(), Views.ListView) if (list != null) { val frontNode = AccessibilityUtil.findFrontNode(list.parent.parent) - val textViewList = findAllByClazz(frontNode, Views.TextView) + val textViewList = findAllOnceByClazz(frontNode, Views.TextView) if (textViewList.size >= 2) { val multiButton = textViewList.lastOrNull() AccessibilityUtil.performClick(multiButton) - sleep(2000) return true } else { LogUtils.e("未找到好友详情按钮") @@ -172,7 +166,7 @@ object WeworkRoomUtil { if (intoFriendDetail()) { val gridView = findOneByClazz(getRoot(), Views.GridView) if (gridView != null && gridView.childCount >= 2) { - val tvList = findAllByClazz(gridView.getChild(0), Views.TextView) + val tvList = findAllOnceByClazz(gridView.getChild(0), Views.TextView) for (textView in tvList) { if (textView.text != null) { titleList.add(textView.text.toString()) @@ -189,12 +183,12 @@ object WeworkRoomUtil { * 群右上角有两个按钮(快速会议按钮、更多按钮) */ private fun isGroupChat(root: AccessibilityNodeInfo): Boolean { - val list = findOneByClazz(root, Views.ListView) + val list = AccessibilityUtil.findOnceByClazz(root, Views.ListView) if (list != null) { val frontNode = findFrontNode(list.parent.parent) - val textViewList = findAllByClazz(frontNode, Views.TextView) + val textViewList = findAllOnceByClazz(frontNode, Views.TextView) if (textViewList.size >= 2) { - val buttonList = findAllByClazz(textViewList.last().parent.parent, Views.TextView) + val buttonList = findAllOnceByClazz(textViewList.last().parent.parent, Views.TextView) return buttonList.size == 2 } else { LogUtils.d("未找到群管理按钮") @@ -210,12 +204,12 @@ object WeworkRoomUtil { * listview前兄弟控件 && text包含外部群 */ private fun isExternalGroup(root: AccessibilityNodeInfo): Boolean { - val listView = findOneByClazz(root, Views.ListView, null, 0) + val listView = AccessibilityUtil.findOnceByClazz(root, Views.ListView, null, 0) if (listView != null) { val frontNode = findFrontNode(listView) if (frontNode != null) { - val nodeList = frontNode.findAccessibilityNodeInfosByText("外部群") - return nodeList.size > 0 + val nodeList = AccessibilityUtil.findAllByText(frontNode, "外部群", timeout = 0) + return nodeList.isNotEmpty() } } return false @@ -226,8 +220,8 @@ object WeworkRoomUtil { * 有列表和输入框 */ private fun isSingleChat(root: AccessibilityNodeInfo): Boolean { - val list = findOneByClazz(root, Views.ListView) - val editText = findOneByClazz(root, Views.EditText) + val list = AccessibilityUtil.findOnceByClazz(root, Views.ListView) + val editText = AccessibilityUtil.findOnceByClazz(root, Views.EditText) if (list != null && editText != null) { return true } diff --git a/app/src/main/java/org/yameida/worktool/utils/WeworkTextUtil.kt b/app/src/main/java/org/yameida/worktool/utils/WeworkTextUtil.kt index 6713da9..1771b66 100644 --- a/app/src/main/java/org/yameida/worktool/utils/WeworkTextUtil.kt +++ b/app/src/main/java/org/yameida/worktool/utils/WeworkTextUtil.kt @@ -4,9 +4,8 @@ import android.view.accessibility.AccessibilityNodeInfo import com.blankj.utilcode.util.LogUtils import org.yameida.worktool.model.WeworkMessageBean import org.yameida.worktool.service.getRoot -import org.yameida.worktool.service.sleep -import org.yameida.worktool.utils.AccessibilityUtil.findOneByClazz import org.yameida.worktool.utils.AccessibilityUtil.findAllByClazz +import org.yameida.worktool.utils.AccessibilityUtil.findAllOnceByClazz import java.util.* /** @@ -204,9 +203,9 @@ object WeworkTextUtil { * @see WeworkMessageBean.TEXT_TYPE */ fun getTextType(node: AccessibilityNodeInfo, isGroup: Boolean = true): Int { - val tvList = findAllByClazz(node, Views.TextView) + val tvList = findAllOnceByClazz(node, Views.TextView) val tvCount = tvList.size - val ivCount = findAllByClazz(node, Views.ImageView).size + val ivCount = findAllOnceByClazz(node, Views.ImageView).size return when { tvCount == 1 && ivCount == 0 -> WeworkMessageBean.TEXT_TYPE_PLAIN tvCount == 0 && ivCount == 1 -> WeworkMessageBean.TEXT_TYPE_IMAGE @@ -258,9 +257,9 @@ object WeworkTextUtil { */ fun getNameList(item: AccessibilityNodeInfo): List { val nameList = ArrayList() - val node = findOneByClazz(item, Views.ViewGroup) + val node = AccessibilityUtil.findOnceByClazz(item, Views.ViewGroup) if (node != null) { - val textViewList = findAllByClazz(node, Views.TextView) + val textViewList = findAllOnceByClazz(node, Views.TextView) for (textView in textViewList) { if (textView.text != null) { nameList.add(textView.text.toString()) @@ -289,13 +288,13 @@ object WeworkTextUtil { ): Boolean { if (node == null) return false for (i in 0 until node.childCount) { - val item = node.getChild(i) + val item = node.getChild(node.childCount - 1 - i) ?: continue val nameList = getNameList(item) - for (name in nameList.reversed()) { + for (name in nameList) { if (name == replyNick) { val backNode = getMessageListNode(item) if (backNode != null) { - val textNode = AccessibilityUtil.findOneByText(backNode, replyContent) + val textNode = AccessibilityUtil.findOnceByText(backNode, replyContent) if (textNode != null) { LogUtils.d("nameList: $nameList\nreplyContent: $replyContent") return longClickMessageItem(item, key) @@ -310,10 +309,9 @@ object WeworkTextUtil { private fun longClickMessageItem(item: AccessibilityNodeInfo, key: String): Boolean { val backNode = getMessageListNode(item) AccessibilityUtil.performLongClickWithSon(backNode) - sleep(1000) - val optionRvList = findAllByClazz(getRoot(), Views.RecyclerView) + val optionRvList = findAllByClazz(getRoot(), Views.RecyclerView, root = true) for (optionRv in optionRvList) { - val optionTvList = findAllByClazz(optionRv, Views.TextView) + val optionTvList = findAllOnceByClazz(optionRv, Views.TextView) for (optionTv in optionTvList) { if (optionTv.text == key) { AccessibilityUtil.performClick(optionTv) @@ -330,7 +328,7 @@ object WeworkTextUtil { * @param item 消息item节点 */ private fun getMessageListNode(item: AccessibilityNodeInfo): AccessibilityNodeInfo? { - val node = findOneByClazz(item, Views.ViewGroup) + val node = AccessibilityUtil.findOnceByClazz(item, Views.ViewGroup) if (node != null) { return AccessibilityUtil.findBackNode(node) }