update 全面兼容企业微信最新版本(4.0.10)和政务微信;控件搜索优化;已知问题修复;

This commit is contained in:
gallonyin
2022-08-11 18:01:16 +08:00
parent 67f6d0baff
commit 732454522f
15 changed files with 305 additions and 149 deletions

View File

@@ -9,8 +9,8 @@ android {
applicationId "org.yameida.worktool"
minSdkVersion 23
targetSdkVersion 30
versionCode 1
versionName "1.3"
versionCode 2
versionName "2.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.yameida.worktool">
<uses-permission android:name="android.permission.INTERNET" />
@@ -17,6 +18,8 @@
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" />
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<application
android:name="org.yameida.worktool.MyApplication"

View File

@@ -5,6 +5,7 @@ import org.yameida.worktool.config.WebConfig
object Constant {
val AVAILABLE_VERSION = arrayListOf("4.0.2", "4.0.6", "4.0.8", "4.0.10")
const val PACKAGE_NAMES = "com.tencent.wework"
val BASE_URL = WebConfig.HOST.replace("wss", "https").replace("ws", "http")
val URL_CHECK_UPDATE = "$BASE_URL/appUpdate/checkUpdate"
@@ -15,5 +16,6 @@ object Constant {
var key = "9876543210abcdef".toByteArray()
var iv = "0123456789abcdef".toByteArray()
val transformation = "AES/CBC/PKCS7Padding"
var encryptType = SPUtils.getInstance().getInt("encryptType", 0)
var encryptType = SPUtils.getInstance().getInt("encryptType", 1)
var autoReply = SPUtils.getInstance().getInt("autoReply", 1)
}

View File

@@ -107,13 +107,14 @@ object Demo {
"titleList":[
"$name"
],
"receivedContent":"你好~我是机器人你可以和我聊天你也可以通过API文档来让我发送消息或完成建群等任务。接口文档https://www.apifox.cn/apidoc/project-1035094/api-23520034"
"receivedContent":"你好~我是机器人,你可以@我和我聊天你也可以通过API文档来让我发送消息或完成建群等任务。接口文档https://www.apifox.cn/apidoc/project-1035094/api-23520034"
},
{
"type": 206,
"groupName": "$groupName",
"selectList": [
"$name"
"$name",
"尹甲仑"
],
"groupAnnouncement": "(自动填写群公告) WorkTool欢迎大家~WorkTool管家是机器人有问题可以在QQ群反馈~@我可以聊天~"
}

View File

@@ -52,15 +52,39 @@ class ListenActivity : AppCompatActivity() {
})
MobclickAgent.onProfileSignIn(channel)
}
Constant.encryptType = SPUtils.getInstance().getInt("encryptType", 0)
sw_encrypt.isChecked = Constant.encryptType == 1
sw_encrypt.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
LogUtils.i("sw_encrypt onCheckedChanged: $isChecked")
Constant.encryptType = if (isChecked) 1 else 0
SPUtils.getInstance().put("encryptType", Constant.encryptType)
})
sw_auto_reply.isChecked = Constant.autoReply == 1
sw_auto_reply.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
LogUtils.i("sw_auto_reply onCheckedChanged: $isChecked")
Constant.autoReply = if (isChecked) 1 else 0
SPUtils.getInstance().put("autoReply", Constant.autoReply)
})
tv_host.text = WebConfig.HOST
tv_version.text = AppUtils.getAppVersionName()
val workVersionName = AppUtils.getAppInfo(Constant.PACKAGE_NAMES)?.versionName
when (workVersionName) {
null -> {
LogUtils.e("系统检测到您尚未安装企业微信,请先安装企业微信")
tv_work_version.text = "检测到您尚未安装企业微信,请先安装登录!"
}
in Constant.AVAILABLE_VERSION -> {
LogUtils.i("当前企业微信版本已适配: $workVersionName")
val tip = "$workVersionName 已适配,可放心使用~"
tv_work_version.text = tip
}
else -> {
LogUtils.e("当前企业微信版本未兼容: $workVersionName")
val tip = "$workVersionName 可能存在部分兼容性问题!"
tv_work_version.text = tip
}
}
SPUtils.getInstance().put("appVersion", AppUtils.getAppVersionName())
SPUtils.getInstance().put("workVersion", workVersionName)
}
private fun initAccessibility() {

View File

@@ -30,7 +30,7 @@ fun goHomeTab(title: String): Boolean {
var atHome = false
var find = false
while (!atHome) {
val list = AccessibilityUtil.findAllByText(getRoot(), "消息", timeout = 0)
val list = AccessibilityUtil.findAllOnceByText(getRoot(), "消息", exact = true)
for (item in list) {
if (item.parent.parent.parent.childCount == 5) {
//处理侧边栏抽屉打开
@@ -42,7 +42,7 @@ fun goHomeTab(title: String): Boolean {
}
}
atHome = true
val tempList = AccessibilityUtil.findAllByText(getRoot(), title, timeout = 0)
val tempList = AccessibilityUtil.findAllOnceByText(getRoot(), title, exact = true)
for (tempItem in tempList) {
if (tempItem.parent.parent.parent.childCount == 5) {
AccessibilityUtil.performClick(tempItem)
@@ -64,7 +64,7 @@ fun goHomeTab(title: String): Boolean {
* 当前是否在首页
*/
fun isAtHome(): Boolean {
val list = AccessibilityUtil.findAllByText(getRoot(), "消息", timeout = 0)
val list = AccessibilityUtil.findAllOnceByText(getRoot(), "消息", exact = true)
return list.count { it.parent.parent.parent.childCount == 5 } > 0
}
@@ -85,8 +85,7 @@ fun getRoot(ignoreCheck: Boolean): AccessibilityNodeInfo {
val root = WeworkController.weworkService.rootInActiveWindow
if (tempRoot != root) {
LogUtils.e("tempRoot != root")
}
if (root != null) {
} else if (root != null) {
if (root.packageName == Constant.PACKAGE_NAMES) {
return root
} else {
@@ -124,9 +123,7 @@ fun backPress() {
AccessibilityUtil.performClick(button)
} else {
LogUtils.d("未找到BT按钮")
val confirm = AccessibilityUtil.findOnceByText(getRoot(), "确定")
?: AccessibilityUtil.findOnceByText(getRoot(), "我知道了")
?: AccessibilityUtil.findOnceByText(getRoot(), "暂不进入")
val confirm = AccessibilityUtil.findOnceByText(getRoot(), "确定", "我知道了", "暂不进入", "不用了", "取消")
if (confirm != null) {
LogUtils.d("尝试点击确定/我知道了/暂不进入")
AccessibilityUtil.performClick(confirm)

View File

@@ -1,5 +1,6 @@
package org.yameida.worktool.service
import com.blankj.utilcode.util.GsonUtils
import com.blankj.utilcode.util.LogUtils
import org.yameida.worktool.Constant
import org.yameida.worktool.model.WeworkMessageBean
@@ -104,7 +105,7 @@ object WeworkGetImpl {
}
}
}
LogUtils.d("我的信息", myInfo)
LogUtils.d("我的信息", GsonUtils.toJson(myInfo))
val weworkMessageBean = WeworkMessageBean()
weworkMessageBean.type = WeworkMessageBean.GET_MY_INFO
weworkMessageBean.myInfo = myInfo

View File

@@ -26,7 +26,7 @@ object WeworkLoopImpl {
mainLoopRunning = true
try {
while (mainLoopRunning) {
if (WeworkRoomUtil.getRoomType(getRoot(), false) != WeworkMessageBean.ROOM_TYPE_UNKNOWN
if (WeworkRoomUtil.getRoomType(false) != WeworkMessageBean.ROOM_TYPE_UNKNOWN
&& getChatMessageList()) {
}
if (!mainLoopRunning) break
@@ -49,7 +49,7 @@ object WeworkLoopImpl {
* 读取通讯录好友请求
*/
fun getFriendRequest(): Boolean {
val list = AccessibilityUtil.findAllByText(getRoot(), "通讯录", timeout = 0)
val list = AccessibilityUtil.findAllOnceByText(getRoot(), "通讯录", exact = true)
for (item in list) {
if (item.parent.parent.parent.childCount == 5) {
if (item.parent.childCount > 1) {
@@ -94,9 +94,10 @@ object WeworkLoopImpl {
* 2.获取消息列表
*/
fun getChatMessageList(timeout: Long = 3000): Boolean {
if (Constant.autoReply == 0) return true
AccessibilityUtil.performScrollDown(getRoot(), 0)
val roomType = WeworkRoomUtil.getRoomType(getRoot())
var titleList = WeworkRoomUtil.getRoomTitle(getRoot())
val roomType = WeworkRoomUtil.getRoomType()
var titleList = WeworkRoomUtil.getRoomTitle()
if (titleList.contains("对方正在输入…")) {
titleList = WeworkRoomUtil.getFriendName()
}
@@ -183,7 +184,7 @@ object WeworkLoopImpl {
//回到上一页
var retry = 5
while (retry-- > 0 && !isAtHome()) {
val textView = AccessibilityUtil.findOnceByText(getRoot(), "新的客户")
val textView = AccessibilityUtil.findOnceByText(getRoot(), "新的客户", "新的居民")
if (textView == null) {
backPress()
}
@@ -197,6 +198,7 @@ object WeworkLoopImpl {
* 读取聊天列表
*/
private fun getChatroomList(): Boolean {
if (Constant.autoReply == 0) return true
if (!isAtHome()) return true
if (logIndex % 3 == 0) {
AccessibilityUtil.performScrollUp(getRoot(), 0)

View File

@@ -47,7 +47,7 @@ object WeworkOperationImpl {
*/
fun replyMessage(
titleList: List<String>,
receivedName: String,
receivedName: String?,
originalContent: String,
textType: Int,
receivedContent: String
@@ -70,7 +70,11 @@ object WeworkOperationImpl {
} else {
LogUtils.d("$title: 回复失败 直接发送答案")
error("$title: 回复失败 直接发送答案 $receivedContent")
if (receivedName == null) {
sendChatMessage(receivedContent, "[自动回复]【$originalContent\n")
} else {
sendChatMessage(receivedContent, "[自动回复]【$originalContent】@$receivedName\n")
}
WeworkLoopImpl.getChatMessageList()
}
} else {
@@ -432,8 +436,7 @@ object WeworkOperationImpl {
sleep(Constant.POP_WINDOW_INTERVAL)
val listViewList = AccessibilityUtil.findAllByClazz(getRoot(), Views.ListView)
if (!listViewList.isNullOrEmpty()) {
// if (AccessibilityUtil.findTextAndClick(listViewList.last(), "添加客户")) {
if (AccessibilityUtil.findTextAndClick(listViewList.last(), "添加")) {
if (AccessibilityUtil.findTextAndClick(listViewList.last(), "添加客户", "添加居民", "加微信")) {
AccessibilityUtil.findTextAndClick(getRoot(), "搜索手机号添加")
AccessibilityUtil.findTextInput(getRoot(), friend.phone.trim())
if (AccessibilityUtil.findTextAndClick(getRoot(), "网络查找手机")) {
@@ -500,7 +503,7 @@ object WeworkOperationImpl {
}
if (AccessibilityUtil.findTextAndClick(getRoot(), "添加为联系人")) {
LogUtils.d("添加好友成功: " + friend.phone)
if (AccessibilityUtil.findTextAndClick(getRoot(), "发送添加邀请")) {
if (AccessibilityUtil.findTextAndClick(getRoot(), "发送添加邀请", "发送申请")) {
LogUtils.d("发送添加邀请成功: " + friend.phone)
}
} else {
@@ -565,38 +568,33 @@ object WeworkOperationImpl {
private fun relaySelectTarget(selectList: List<String>, extraText: String? = null): Boolean {
val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView)
if (list != null) {
val frontNode = AccessibilityUtil.findFrontNode(list)
val frontNode = AccessibilityUtil.findFrontNode(list, 2)
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)
AccessibilityUtil.findTextInput(getRoot(), select.replace("", "").replace("-.*$".toRegex(), ""))
sleep(Constant.CHANGE_PAGE_INTERVAL * 2)
val selectListView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView)
val imageView =
AccessibilityUtil.findOnceByClazz(selectListView, Views.ImageView)
val imageView = AccessibilityUtil.findOneByClazz(selectListView, Views.ImageView)
if (imageView != null) {
AccessibilityUtil.performClick(imageView)
}
sleep(1000)
sleep(Constant.CHANGE_PAGE_INTERVAL)
}
val confirmButton =
AccessibilityUtil.findOneByText(getRoot(), "确定(${selectList.size})")
if (confirmButton != null) {
AccessibilityUtil.performClick(confirmButton)
sleep(1000)
sleep(Constant.POP_WINDOW_INTERVAL)
if (!extraText.isNullOrBlank()) {
LogUtils.d("extraText: $extraText")
AccessibilityUtil.findTextInput(getRoot(), extraText)
sleep(1000)
}
val sendButtonList = AccessibilityUtil.findAllByText(getRoot(), "发送", timeout = 0)
val sendButtonList = AccessibilityUtil.findAllByText(getRoot(), "发送")
for (sendButton in sendButtonList.filter { it.text != null }) {
if (sendButton.text == "发送" || sendButton.text == "发送(${selectList.size})") {
AccessibilityUtil.performClick(sendButton)
@@ -623,10 +621,10 @@ object WeworkOperationImpl {
*/
private fun createGroup(): Boolean {
goHomeTab("工作台")
val textViewGroup = AccessibilityUtil.scrollAndFindByText(getRoot(), "客户群")
val textViewGroup = AccessibilityUtil.scrollAndFindByText(getRoot(), "客户群", "居民群")
if (AccessibilityUtil.performClick(textViewGroup)) {
LogUtils.d("进入客户群应用")
val textView = AccessibilityUtil.findOneByText(getRoot(), "创建一个客户群")
val textView = AccessibilityUtil.findOneByText(getRoot(), "创建一个客户群", "创建一个居民群")
AccessibilityUtil.performClick(textView)
return true
} else {
@@ -681,15 +679,18 @@ object WeworkOperationImpl {
}
val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView)
if (list != null) {
val frontNode = AccessibilityUtil.findFrontNode(list)
val frontNode = AccessibilityUtil.findFrontNode(list, 2)
val textViewList = AccessibilityUtil.findAllOnceByClazz(frontNode, Views.TextView)
if (textViewList.size >= 2) {
val multiButton = textViewList.lastOrNull()
for (select in selectList) {
AccessibilityUtil.performClick(multiButton)
AccessibilityUtil.findTextInput(getRoot(), select)
val selectListView =
sleep(Constant.POP_WINDOW_INTERVAL)
val selectListViewTemp =
AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView)
val selectListView =
AccessibilityUtil.findOnceByClazz(getRoot(), Views.RecyclerView) ?: selectListViewTemp
val imageView =
AccessibilityUtil.findOneByClazz(selectListView, Views.ImageView, root = false)
AccessibilityUtil.performClick(imageView)
@@ -740,7 +741,7 @@ object WeworkOperationImpl {
}
val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView)
if (list != null) {
val frontNode = AccessibilityUtil.findFrontNode(list)
val frontNode = AccessibilityUtil.findFrontNode(list, 2)
val textViewList = AccessibilityUtil.findAllOnceByClazz(frontNode, Views.TextView)
if (textViewList.size >= 2) {
val multiButton = textViewList.lastOrNull()

View File

@@ -25,6 +25,7 @@ import java.lang.Exception
* event.source则不需要验证包名获取窗口并可获得事件详情
*/
class WeworkService : AccessibilityService() {
private val TAG = "WeworkService"
lateinit var webSocketManager: WebSocketManager
override fun onServiceConnected() {
@@ -85,10 +86,15 @@ class WeworkService : AccessibilityService() {
}
class EchoWebSocketListener() : WebSocketListener() {
private val TAG = "WeworkService.EchoWebSocketListener"
private lateinit var socket: WebSocket
override fun onOpen(webSocket: WebSocket, response: Response) {
socket = webSocket
Log.e("ChatMaskActivityListener", "链接建立")
Log.e(TAG, "链接建立")
val robotId = SPUtils.getInstance().getString(WebConfig.LISTEN_CHANNEL_ID, "")
val appVersion = SPUtils.getInstance().getString("appVersion", "")
val workVersion= SPUtils.getInstance().getString("workVersion", "")
log("链接建立: $robotId appVersion: $appVersion workVersion: $workVersion")
}
override fun onMessage(webSocket: WebSocket, text: String) {
@@ -104,19 +110,19 @@ class WeworkService : AccessibilityService() {
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
super.onClosed(webSocket, code, reason)
//服务器关闭后
Log.e("ChatMaskActivityListener", "链接关闭 $reason")
Log.e(TAG, "链接关闭 $reason")
}
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
super.onClosing(webSocket, code, reason)
socket.close(code, reason)
Log.e("ChatMaskActivityListener", "服务端关闭连接 $code: $reason")
Log.e(TAG, "服务端关闭连接 $code: $reason")
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
//服务器中断
Log.e("ChatMaskActivityListener", "链接错误: " + t.toString() + response.toString())
Log.e(TAG, "链接错误: " + t.toString() + response.toString())
}
}
}

View File

@@ -70,8 +70,8 @@ object AccessibilityUtil {
}
//寻找第一个文本匹配(关键词)并点击
fun findTextAndClick(nodeInfo: AccessibilityNodeInfo?, text: String): Boolean {
val textView = findOneByText(nodeInfo, text) ?: return false
fun findTextAndClick(nodeInfo: AccessibilityNodeInfo?, vararg textList: String): Boolean {
val textView = findOneByText(nodeInfo, *textList) ?: return false
return performClick(textView)
}
@@ -113,20 +113,20 @@ object AccessibilityUtil {
//滚动并按文本寻找第一个控件
fun scrollAndFindByText(
nodeInfo: AccessibilityNodeInfo,
text: String,
vararg textList: String,
maxRetry: Int = 3
): AccessibilityNodeInfo? {
var index = 0
while (index++ < maxRetry) {
performScrollUp(nodeInfo, 0)
val node = findOnceByText(nodeInfo, text)
val node = findOnceByText(nodeInfo, *textList)
if (node != null) {
return node
}
}
while (index++ < maxRetry * 2) {
performScrollDown(nodeInfo, 0)
val node = findOnceByText(nodeInfo, text)
val node = findOnceByText(nodeInfo, *textList)
if (node != null) {
return node
}
@@ -369,12 +369,12 @@ object AccessibilityUtil {
/**
* 按文本(关键词)寻找节点和子节点内的一个匹配项
* @param node 节点
* @param text 关键词
* @param textList 关键词
* @param timeout 检查超时时间
*/
fun findOneByText(
node: AccessibilityNodeInfo?,
text: String,
vararg textList: String,
exact: Boolean = false,
timeout: Long = 5000,
root: Boolean = true
@@ -383,18 +383,9 @@ object AccessibilityUtil {
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]
}
}
val result = findOnceByText(node, *textList, exact = exact)
LogUtils.v("text: $textList result == null: ${result == null}")
if (result != null) return result
sleep(SHORT_INTERVAL)
if (root) {
node = getRoot(true)
@@ -403,43 +394,53 @@ object AccessibilityUtil {
}
currentTime = System.currentTimeMillis()
}
Log.e(tag, "findOneByText: not found: $text")
Log.e(tag, "findOneByText: not found: $textList")
return null
}
fun findOnceByText(
node: AccessibilityNodeInfo?,
text: String,
vararg textList: String,
exact: Boolean = false
): AccessibilityNodeInfo? {
return findOneByText(node, text, exact, 0)
if (node == null) return null
val textNodeList = findAllOnceByText(node, *textList, exact = exact)
LogUtils.v("text: $textList count: " + textNodeList.size)
if (exact) return textNodeList[0]
else if (textNodeList.size > 0) {
for (textNode in textNodeList) {
for (text in textList) {
if (textNode.text == text) {
return textNode
}
}
}
return textNodeList[0]
}
return null
}
/**
* 按文本(关键词)寻找节点和子节点内的所有匹配项
* @param node 节点
* @param text 关键词
* @param textList 关键词
* @param timeout 检查超时时间
*/
fun findAllByText(
node: AccessibilityNodeInfo?,
text: String,
vararg textList: String,
exact: Boolean = false,
timeout: Long = 5000,
root: Boolean = true
root: Boolean = true,
minSize: Int = 1
): List<AccessibilityNodeInfo> {
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 }
}
}
val result = findAllOnceByText(node, *textList, exact = exact)
LogUtils.v("text: $textList count: " + result.size)
if (result.size >= minSize) return result
sleep(SHORT_INTERVAL)
if (root) {
node = getRoot(true)
@@ -448,10 +449,39 @@ object AccessibilityUtil {
}
currentTime = System.currentTimeMillis()
}
Log.e(tag, "findAllByText: not found: $text")
Log.e(tag, "findAllByText: not found: $textList")
return arrayListOf()
}
/**
* 按文本(关键词)寻找节点和子节点内的所有匹配项
* node 节点
* clazz 类名
* limitDepth 深度 限制深度搜索深度必须匹配提供值且类名相同才返回 不填默认不限制
*/
fun findAllOnceByText(
node: AccessibilityNodeInfo?,
vararg textList: String,
exact: Boolean = false,
list: ArrayList<AccessibilityNodeInfo> = ArrayList()
): ArrayList<AccessibilityNodeInfo> {
if (node == null) return list
val nodeText = node.text
if (nodeText != null) {
for (text in textList) {
if (exact && nodeText == text) {
list.add(node)
} else if (!exact && nodeText.contains(text)) {
list.add(node)
}
}
}
for (i in 0 until node.childCount) {
findAllOnceByText(node.getChild(i), *textList, exact = exact, list = list)
}
return list
}
/**
* 按类名寻找节点和子节点内的一个匹配项
* node 节点
@@ -468,10 +498,6 @@ object AccessibilityUtil {
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) {
@@ -524,12 +550,11 @@ object AccessibilityUtil {
fun findAllByClazz(
node: AccessibilityNodeInfo?,
clazz: String,
list: ArrayList<AccessibilityNodeInfo> = ArrayList(),
timeout: Long = 5000,
root: Boolean = true,
minSize: Int = 1
): ArrayList<AccessibilityNodeInfo> {
var node = node ?: return list
var node = node ?: return arrayListOf()
val startTime = System.currentTimeMillis()
var currentTime = startTime
while (currentTime - startTime <= timeout) {
@@ -546,7 +571,7 @@ object AccessibilityUtil {
}
LogUtils.e("findAllByClazz Exception()")
Exception().printStackTrace()
return list
return arrayListOf()
}
/**
@@ -568,11 +593,23 @@ object AccessibilityUtil {
return list
}
/**
* 查找节点的前兄弟节点 直到该节点满足子节点数
* node 节点
*/
fun findFrontNode(node: AccessibilityNodeInfo?, minChildCount: Int = 0): AccessibilityNodeInfo? {
var findFrontNode = findFrontNode(node) ?: return null
while (findFrontNode.childCount < minChildCount) {
findFrontNode = findFrontNode(findFrontNode) ?: return null
}
return findFrontNode
}
/**
* 查找节点的前兄弟节点
* node 节点
*/
fun findFrontNode(node: AccessibilityNodeInfo?): AccessibilityNodeInfo? {
private fun findFrontNode(node: AccessibilityNodeInfo?): AccessibilityNodeInfo? {
if (node == null) return null
var parent: AccessibilityNodeInfo? = node.parent
var son: AccessibilityNodeInfo? = node
@@ -593,11 +630,23 @@ object AccessibilityUtil {
return null
}
/**
* 查找节点的后兄弟节点 直到该节点满足子节点数
* node 节点
*/
fun findBackNode(node: AccessibilityNodeInfo?, minChildCount: Int = 0): AccessibilityNodeInfo? {
var findBackNode = findBackNode(node) ?: return null
while (findBackNode.childCount < minChildCount) {
findBackNode = findFrontNode(findBackNode) ?: return null
}
return findBackNode
}
/**
* 查找节点的后兄弟节点
* node 节点
*/
fun findBackNode(node: AccessibilityNodeInfo?): AccessibilityNodeInfo? {
private fun findBackNode(node: AccessibilityNodeInfo?): AccessibilityNodeInfo? {
if (node == null) return null
var parent: AccessibilityNodeInfo? = node.parent
var son: AccessibilityNodeInfo? = node

View File

@@ -16,12 +16,6 @@ import update.UpdateAppUtils
object UpdateUtil {
fun checkUpdate() {
// val remoteVersionCode = 10
// val remoteVersionName = "1.0.1"
// val forceUpdate = false
// val updateLog = "修复Bug\n优化用户体验"
// val downloadUrl = "https://down.qq.com/qqweb/QQ_1/android_apk/Android_8.5.0.5025_537066738.apk"
// val fileMD5 = "560017dc94e8f9b65f4ca997c7feb326"
OkGo.get<String>(Constant.URL_CHECK_UPDATE)
.execute(object : StringCallback() {
override fun onSuccess(response: Response<String>) {

View File

@@ -21,22 +21,22 @@ object WeworkRoomUtil {
* 房间类型 ROOM_TYPE
* @see WeworkMessageBean.ROOM_TYPE
*/
fun getRoomType(root: AccessibilityNodeInfo, print: Boolean = true): Int {
fun getRoomType(print: Boolean = true): Int {
val roomTitle = getRoomTitle()
when {
isExternalSingleChat(root) -> {
isExternalSingleChat(roomTitle) -> {
LogUtils.d("ROOM_TYPE: ROOM_TYPE_EXTERNAL_CONTACT")
return WeworkMessageBean.ROOM_TYPE_EXTERNAL_CONTACT
}
isGroupChat(root) -> {
return if (isExternalGroup(root)) {
isExternalGroup() -> {
LogUtils.d("ROOM_TYPE: ROOM_TYPE_EXTERNAL_GROUP")
WeworkMessageBean.ROOM_TYPE_EXTERNAL_GROUP
} else {
return WeworkMessageBean.ROOM_TYPE_EXTERNAL_GROUP
}
isGroupChat(roomTitle) -> {
LogUtils.d("ROOM_TYPE: ROOM_TYPE_INTERNAL_GROUP")
WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP
return WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP
}
}
isSingleChat(root) -> {
isSingleChat() -> {
LogUtils.d("ROOM_TYPE: ROOM_TYPE_INTERNAL_CONTACT")
return WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT
}
@@ -55,19 +55,23 @@ object WeworkRoomUtil {
* @see WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP
* @see WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT
*/
fun getRoomTitle(root: AccessibilityNodeInfo): ArrayList<String> {
fun getRoomTitle(print: Boolean = true): ArrayList<String> {
val titleList = arrayListOf<String>()
val list = AccessibilityUtil.findOnceByClazz(root, Views.ListView)
val list = AccessibilityUtil.findOnceByClazz(getRoot(), Views.ListView)
if (list != null) {
val frontNode = findFrontNode(list.parent.parent)
val textViewList = findAllOnceByClazz(frontNode, Views.TextView)
for (textView in textViewList) {
if (!textView.text.isNullOrBlank()) {
titleList.add(textView.text.toString().replace("\\(\\d+\\)$".toRegex(), ""))
val text = textView.text.toString()
titleList.add(text.replace("\\(\\d+\\)$".toRegex(), ""))
if (text.contains("\\(\\d+\\)$".toRegex())) {
titleList.add(text)
}
}
}
LogUtils.v("getRoomTitle: ", titleList)
}
if (print) LogUtils.v("getRoomTitle: ", titleList)
return titleList
}
@@ -76,8 +80,8 @@ object WeworkRoomUtil {
*/
fun intoRoom(title: String): Boolean {
LogUtils.d("intoRoom(): $title")
val titleList = getRoomTitle(getRoot())
val roomType = getRoomType(getRoot())
val titleList = getRoomTitle(false)
val roomType = getRoomType()
if (roomType != WeworkMessageBean.ROOM_TYPE_UNKNOWN
&& titleList.count {
it.replace("", "").replace("\\(.*?\\)".toRegex(), "") == title.replace("", "")
@@ -96,7 +100,7 @@ object WeworkRoomUtil {
val searchButton: AccessibilityNodeInfo = textViewList[textViewList.size - 2]
val multiButton: AccessibilityNodeInfo = textViewList[textViewList.size - 1]
AccessibilityUtil.performClick(searchButton)
AccessibilityUtil.findTextInput(getRoot(), title.replace("", ""))
AccessibilityUtil.findTextInput(getRoot(), title.replace("", "").replace("-.*$".toRegex(), ""))
sleep(Constant.CHANGE_PAGE_INTERVAL)
val selectListView = findOneByClazz(getRoot(), Views.ListView)
val imageView = AccessibilityUtil.findOnceByClazz(selectListView, Views.ImageView)
@@ -195,35 +199,22 @@ object WeworkRoomUtil {
/**
* 是否是群聊
* 群右上角有两个按钮(快速会议按钮、更多按钮)
* 群名最后有(\d)显示群人数
*/
private fun isGroupChat(root: AccessibilityNodeInfo): Boolean {
val list = AccessibilityUtil.findOnceByClazz(root, Views.ListView)
if (list != null) {
val frontNode = findFrontNode(list.parent.parent)
val textViewList = findAllOnceByClazz(frontNode, Views.TextView)
if (textViewList.size >= 2) {
val buttonList = findAllOnceByClazz(textViewList.last().parent.parent, Views.TextView)
return buttonList.size == 2
} else {
LogUtils.v("未找到群管理按钮")
}
} else {
LogUtils.d("未找到消息列表")
}
return false
private fun isGroupChat(roomTitle: ArrayList<String>): Boolean {
return roomTitle.size > 1 && roomTitle[1].contains("\\(\\d+\\)$".toRegex())
}
/**
* 是否是外部群
* listview前兄弟控件 && text包含外部群
*/
private fun isExternalGroup(root: AccessibilityNodeInfo): Boolean {
val listView = AccessibilityUtil.findOnceByClazz(root, Views.ListView, null, 0)
private fun isExternalGroup(): Boolean {
val listView = AccessibilityUtil.findOnceByClazz(getRoot(), Views.ListView, null, 0)
if (listView != null) {
val frontNode = findFrontNode(listView)
if (frontNode != null) {
val nodeList = AccessibilityUtil.findAllByText(frontNode, "外部群", timeout = 0)
val nodeList = AccessibilityUtil.findAllOnceByText(frontNode, "外部群")
return nodeList.isNotEmpty()
}
}
@@ -234,9 +225,9 @@ object WeworkRoomUtil {
* 是否是单聊
* 有列表和输入框
*/
private fun isSingleChat(root: AccessibilityNodeInfo): Boolean {
val list = AccessibilityUtil.findOnceByClazz(root, Views.ListView)
val editText = AccessibilityUtil.findOnceByClazz(root, Views.EditText)
private fun isSingleChat(): Boolean {
val list = AccessibilityUtil.findOnceByClazz(getRoot(), Views.ListView)
val editText = AccessibilityUtil.findOnceByClazz(getRoot(), Views.EditText)
if (list != null && editText != null) {
return true
}
@@ -247,8 +238,7 @@ object WeworkRoomUtil {
* 是否是外部单聊
* 姓名下面有@xx
*/
private fun isExternalSingleChat(root: AccessibilityNodeInfo): Boolean {
val roomTitle = getRoomTitle(root)
private fun isExternalSingleChat(roomTitle: ArrayList<String>): Boolean {
return roomTitle.size > 1 && roomTitle.count { it.matches("^[@].*?".toRegex()) } > 0
}

View File

@@ -282,7 +282,7 @@ object WeworkTextUtil {
fun longClickMessageItem(
node: AccessibilityNodeInfo?,
replyTextType: Int,
replyNick: String,
replyNick: String?,
replyContent: String,
key: String
): Boolean {
@@ -290,14 +290,24 @@ object WeworkTextUtil {
for (i in 0 until node.childCount) {
val item = node.getChild(node.childCount - 1 - i) ?: continue
val nameList = getNameList(item)
for (name in nameList) {
if (name == replyNick) {
val backNode = getMessageListNode(item)
if (nameList.isEmpty()) {
val backNode = getMessageListNode(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT)
if (backNode != null) {
val textNode = AccessibilityUtil.findOnceByText(backNode, replyContent)
if (textNode != null) {
LogUtils.d("nameList: $nameList\nreplyContent: $replyContent")
return longClickMessageItem(item, key)
return longClickMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT, key)
}
}
}
for (name in nameList) {
if (name == replyNick) {
val backNode = getMessageListNode(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP)
if (backNode != null) {
val textNode = AccessibilityUtil.findOnceByText(backNode, replyContent)
if (textNode != null) {
LogUtils.d("nameList: $nameList\nreplyContent: $replyContent")
return longClickMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP, key)
}
}
}
@@ -306,8 +316,8 @@ object WeworkTextUtil {
return false
}
private fun longClickMessageItem(item: AccessibilityNodeInfo, key: String): Boolean {
val backNode = getMessageListNode(item)
private fun longClickMessageItem(item: AccessibilityNodeInfo, roomType: Int, key: String): Boolean {
val backNode = getMessageListNode(item, roomType)
AccessibilityUtil.performLongClickWithSon(backNode)
val optionRvList = findAllByClazz(getRoot(), Views.RecyclerView)
for (optionRv in optionRvList) {
@@ -327,11 +337,18 @@ object WeworkTextUtil {
* 适用于左侧发言者
* @param item 消息item节点
*/
private fun getMessageListNode(item: AccessibilityNodeInfo): AccessibilityNodeInfo? {
private fun getMessageListNode(item: AccessibilityNodeInfo, roomType: Int): AccessibilityNodeInfo? {
if (roomType in arrayOf(WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT, WeworkMessageBean.ROOM_TYPE_EXTERNAL_CONTACT)) {
val node = AccessibilityUtil.findOnceByClazz(item, Views.ImageView)
if (node != null) {
return AccessibilityUtil.findBackNode(node)
}
} else if (roomType in arrayOf(WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP, WeworkMessageBean.ROOM_TYPE_EXTERNAL_GROUP)) {
val node = AccessibilityUtil.findOnceByClazz(item, Views.ViewGroup)
if (node != null) {
return AccessibilityUtil.findBackNode(node)
}
}
return null
}
}

View File

@@ -150,6 +150,40 @@
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/setting_start_padding"
android:paddingTop="@dimen/setting_vertical_padding"
android:paddingEnd="@dimen/setting_end_padding"
android:paddingBottom="@dimen/setting_vertical_padding">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_start_padding"
android:layout_marginEnd="@dimen/setting_end_padding"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="企业微信版本"
android:textColor="@color/color_333333"
android:textSize="@dimen/setting_start_font_size" />
<TextView
android:id="@+id/tv_work_version"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="4.0.2"
android:textColor="@color/color_999999"
android:textSize="@dimen/setting_end_font_size" />
</LinearLayout>
</RelativeLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -232,6 +266,41 @@
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/setting_start_padding"
android:paddingTop="@dimen/setting_vertical_padding"
android:paddingEnd="@dimen/setting_end_start_padding"
android:paddingBottom="@dimen/setting_vertical_padding">
<Switch
android:id="@+id/sw_auto_reply"
android:layout_width="@dimen/setting_end_font_width"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_end_start_padding" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_start_padding"
android:layout_toStartOf="@id/sw_auto_reply"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="开启自动回复"
android:textColor="@color/color_333333"
android:textSize="@dimen/setting_start_font_size" />
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -273,13 +342,13 @@
android:orientation="vertical">
<Button
android:id="@+id/bt_save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="50dp"
android:paddingEnd="50dp"
android:layout_gravity="center_horizontal"
android:background="@drawable/comment_red_btn"
android:id="@+id/bt_save"
android:paddingStart="50dp"
android:paddingEnd="50dp"
android:text="保存" />