update 1.主动加好友可修改附言;2.移除[自动回复]前缀;3.群内回复可@真提醒;4.学校类型企业兼容;5.搜索更加精准;6.其他已知问题修复

This commit is contained in:
尹甲仑
2022-09-15 20:45:58 +08:00
parent e4b56419c3
commit 78fa29bc40
16 changed files with 438 additions and 241 deletions

View File

@@ -1,21 +1,34 @@
package org.yameida.worktool package org.yameida.worktool
import com.blankj.utilcode.util.SPUtils import com.blankj.utilcode.util.SPUtils
import org.yameida.worktool.config.WebConfig
object Constant { object Constant {
val AVAILABLE_VERSION = arrayListOf("4.0.2", "4.0.6", "4.0.8", "4.0.10", "4.0.12") val AVAILABLE_VERSION = arrayListOf("4.0.2", "4.0.6", "4.0.8", "4.0.10", "4.0.12", "4.0.16")
const val PACKAGE_NAMES = "com.tencent.wework" const val PACKAGE_NAMES = "com.tencent.wework"
val BASE_URL = WebConfig.HOST.replace("wss", "https").replace("ws", "http") const val LISTEN_CHANNEL_ID = "LISTEN_CHANNEL_ID"
val URL_CHECK_UPDATE = "$BASE_URL/appUpdate/checkUpdate" const val WEWORK_NOTIFY = "wework_notify"
const val CHANGE_PAGE_INTERVAL = 1000L const val CHANGE_PAGE_INTERVAL = 1000L
const val POP_WINDOW_INTERVAL = 500L const val POP_WINDOW_INTERVAL = 500L
private const val DEFAULT_HOST = "wss://worktool.asrtts.cn"
var myName = "" var myName = ""
var regTrimTitle = "(…$)|(-.*$)|(\\(.*?\\)$)".toRegex()
var key = "9876543210abcdef".toByteArray() var key = "9876543210abcdef".toByteArray()
var iv = "0123456789abcdef".toByteArray() var iv = "0123456789abcdef".toByteArray()
val transformation = "AES/CBC/PKCS7Padding" val transformation = "AES/CBC/PKCS7Padding"
var encryptType = SPUtils.getInstance().getInt("encryptType", 1) var encryptType = SPUtils.getInstance().getInt("encryptType", 1)
var autoReply = SPUtils.getInstance().getInt("autoReply", 1) var autoReply = SPUtils.getInstance().getInt("autoReply", 1)
var host: String
get() = SPUtils.getInstance().getString("host", DEFAULT_HOST)
set(value) {
SPUtils.getInstance().put("host", value)
}
fun getWsUrl() = "$host/webserver/wework/" + SPUtils.getInstance().getString(Constant.LISTEN_CHANNEL_ID)
fun getCheckUpdateUrl() = "${getBaseUrl()}/appUpdate/checkUpdate"
private fun getBaseUrl() = host.replace("wss", "https").replace("ws", "http")
} }

View File

@@ -15,8 +15,11 @@ import com.umeng.analytics.MobclickAgent
import kotlinx.android.synthetic.main.activity_listen.* import kotlinx.android.synthetic.main.activity_listen.*
import org.yameida.worktool.* import org.yameida.worktool.*
import org.yameida.worktool.service.WeworkService import org.yameida.worktool.service.WeworkService
import org.yameida.worktool.config.WebConfig
import org.yameida.worktool.utils.UpdateUtil import org.yameida.worktool.utils.UpdateUtil
import android.app.Dialog
import android.widget.Button
import android.widget.EditText
class ListenActivity : AppCompatActivity() { class ListenActivity : AppCompatActivity() {
@@ -42,12 +45,12 @@ class ListenActivity : AppCompatActivity() {
} }
private fun initView() { private fun initView() {
et_channel.setText(SPUtils.getInstance().getString(WebConfig.LISTEN_CHANNEL_ID)) et_channel.setText(SPUtils.getInstance().getString(Constant.LISTEN_CHANNEL_ID))
bt_save.setOnClickListener { bt_save.setOnClickListener {
val channel = et_channel.text.toString().trim() val channel = et_channel.text.toString().trim()
SPUtils.getInstance().put(WebConfig.LISTEN_CHANNEL_ID, channel) SPUtils.getInstance().put(Constant.LISTEN_CHANNEL_ID, channel)
ToastUtils.showLong("保存成功") ToastUtils.showLong("保存成功")
sendBroadcast(Intent(WebConfig.WEWORK_NOTIFY).apply { sendBroadcast(Intent(Constant.WEWORK_NOTIFY).apply {
putExtra("type", "modify_channel") putExtra("type", "modify_channel")
}) })
MobclickAgent.onProfileSignIn(channel) MobclickAgent.onProfileSignIn(channel)
@@ -64,7 +67,11 @@ class ListenActivity : AppCompatActivity() {
Constant.autoReply = if (isChecked) 1 else 0 Constant.autoReply = if (isChecked) 1 else 0
SPUtils.getInstance().put("autoReply", Constant.autoReply) SPUtils.getInstance().put("autoReply", Constant.autoReply)
}) })
tv_host.text = WebConfig.HOST tv_host.text = Constant.host
tv_host.setOnLongClickListener {
showInputDialog()
true
}
val version = "${AppUtils.getAppVersionName()} Android ${DeviceUtils.getSDKVersionName()} ${DeviceUtils.getManufacturer()} ${DeviceUtils.getModel()}" val version = "${AppUtils.getAppVersionName()} Android ${DeviceUtils.getSDKVersionName()} ${DeviceUtils.getManufacturer()} ${DeviceUtils.getModel()}"
tv_version.text = version tv_version.text = version
val workVersionName = AppUtils.getAppInfo(Constant.PACKAGE_NAMES)?.versionName val workVersionName = AppUtils.getAppInfo(Constant.PACKAGE_NAMES)?.versionName
@@ -92,7 +99,7 @@ class ListenActivity : AppCompatActivity() {
sw_accessibility.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked -> sw_accessibility.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
LogUtils.i("sw_accessibility onCheckedChanged: $isChecked") LogUtils.i("sw_accessibility onCheckedChanged: $isChecked")
if (isChecked) { if (isChecked) {
if (SPUtils.getInstance().getString(WebConfig.LISTEN_CHANNEL_ID).isNullOrBlank()) { if (SPUtils.getInstance().getString(Constant.LISTEN_CHANNEL_ID).isNullOrBlank()) {
sw_accessibility.isChecked = false sw_accessibility.isChecked = false
ToastUtils.showLong("请先填写并保存链接号~") ToastUtils.showLong("请先填写并保存链接号~")
} else if (!isAccessibilitySettingOn()) { } else if (!isAccessibilitySettingOn()) {
@@ -193,4 +200,32 @@ class ListenActivity : AppCompatActivity() {
} }
} }
private fun showInputDialog() {
ToastUtils.showLong("请输入专线网络")
val commentDialog = Dialog(this)
commentDialog.setContentView(R.layout.dialog_input)
val et: EditText = commentDialog.findViewById(R.id.body) as EditText
val okBtn: Button = commentDialog.findViewById(R.id.ok) as Button
okBtn.setOnClickListener {
val text = et.text.toString()
if (text.isNotBlank()) {
if (text.matches("ws{1,2}://[^/]+.*".toRegex())) {
Constant.host = text
tv_host.text = text
ToastUtils.showLong("保存成功")
commentDialog.dismiss()
} else {
ToastUtils.showLong("格式异常!")
}
} else {
ToastUtils.showLong("请勿为空!")
}
}
val cancelBtn: Button = commentDialog.findViewById(R.id.cancel) as Button
cancelBtn.setOnClickListener {
commentDialog.dismiss()
}
commentDialog.show()
}
} }

View File

@@ -1,8 +0,0 @@
package org.yameida.worktool.config;
public class WebConfig {
public static final String WEWORK_NOTIFY = "wework_notify";
public static final String HOST = "wss://worktool.asrtts.cn";
public static final String WEWORK_URL = HOST + "/webserver/wework/";
public static String LISTEN_CHANNEL_ID = "LISTEN_CHANNEL_ID";
}

View File

@@ -139,8 +139,6 @@ public class WeworkMessageBean {
public String receivedName; public String receivedName;
//内容移除了@me //内容移除了@me
public String receivedContent; public String receivedContent;
//回复内容前缀
public String prefix;
//想要at的昵称 //想要at的昵称
public String at; public String at;
//原始内容text //原始内容text
@@ -254,6 +252,8 @@ public class WeworkMessageBean {
public List<String> tagList; public List<String> tagList;
//是否是新好友 //是否是新好友
public Boolean newFriend; public Boolean newFriend;
//留言
public String leavingMsg;
} }
@Override @Override
@@ -266,7 +266,7 @@ public class WeworkMessageBean {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(titleList, messageList, log, roomType, receivedName, receivedContent, originalContent, nameList, extraText, textType, groupName, groupOwner, selectList, groupNumber, groupAnnouncement, newGroupName, newGroupAnnouncement, removeList, showMessageHistory, myInfo, objectName, type); return Objects.hash(titleList, messageList, log, roomType, receivedName, receivedContent, originalContent, nameList, extraText, textType, groupName, groupOwner, selectList, groupNumber, groupAnnouncement, newGroupName, newGroupAnnouncement, removeList, showMessageHistory, myInfo, objectName, type, friend);
} }
@Override @Override

View File

@@ -107,23 +107,23 @@ fun getRoot(ignoreCheck: Boolean): AccessibilityNodeInfo {
*/ */
fun backPress() { fun backPress() {
val textView = AccessibilityUtil.findOnceByClazz(getRoot(), Views.TextView) val textView = AccessibilityUtil.findOnceByClazz(getRoot(), Views.TextView)
if (textView != null && textView.text.isNullOrBlank() && AccessibilityUtil.performClick(textView)) { if (textView != null && textView.text.isNullOrBlank() && AccessibilityUtil.performClick(textView, retry = false)) {
LogUtils.v("找到回退按钮") LogUtils.v("找到回退按钮")
} else { } else {
val ivButton = AccessibilityUtil.findOnceByClazz(getRoot(), Views.ImageView) val ivButton = AccessibilityUtil.findOnceByClazz(getRoot(), Views.ImageView)
if (ivButton != null && ivButton.isClickable && AccessibilityUtil.findFrontNode(ivButton) == null) { if (ivButton != null && ivButton.isClickable && AccessibilityUtil.findFrontNode(ivButton) == null) {
LogUtils.d("未找到回退按钮 点击第一个IV按钮") LogUtils.d("未找到回退按钮 点击第一个IV按钮")
AccessibilityUtil.performClick(ivButton) AccessibilityUtil.performClick(ivButton, retry = false)
} else { } else {
LogUtils.d("未找到回退按钮 点击第一个BT按钮") LogUtils.d("未找到回退按钮 点击第一个BT按钮")
val button = AccessibilityUtil.findOnceByClazz(getRoot(), Views.Button) val button = AccessibilityUtil.findOnceByClazz(getRoot(), Views.Button)
if (button != null && button.childCount > 0) { if (button != null && button.childCount > 0) {
AccessibilityUtil.performClick(button.getChild(0)) AccessibilityUtil.performClick(button.getChild(0), retry = false)
} else if (button != null) { } else if (button != null) {
AccessibilityUtil.performClick(button) AccessibilityUtil.performClick(button, retry = false)
} else { } else {
LogUtils.d("未找到BT按钮") LogUtils.d("未找到BT按钮")
val confirm = AccessibilityUtil.findOnceByText(getRoot(), "确定", "我知道了", "暂不进入", "不用了", "取消") val confirm = AccessibilityUtil.findOnceByText(getRoot(), "确定", "我知道了", "暂不进入", "不用了", "取消", "暂不", exact = true)
if (confirm != null) { if (confirm != null) {
LogUtils.d("尝试点击确定/我知道了/暂不进入") LogUtils.d("尝试点击确定/我知道了/暂不进入")
AccessibilityUtil.performClick(confirm) AccessibilityUtil.performClick(confirm)

View File

@@ -80,7 +80,6 @@ object MyLooper {
WeworkController.enableLoopRunning = true WeworkController.enableLoopRunning = true
} else { } else {
WeworkController.mainLoopRunning = false WeworkController.mainLoopRunning = false
getInstance().removeMessages(message.type * message.hashCode())
getInstance().sendMessage(Message.obtain().apply { getInstance().sendMessage(Message.obtain().apply {
what = message.type * message.hashCode() what = message.type * message.hashCode()
obj = message obj = message

View File

@@ -59,7 +59,6 @@ object WeworkController {
* @param message#originalContent 原始消息的内容 * @param message#originalContent 原始消息的内容
* @param message#textType 原始消息的消息类型 * @param message#textType 原始消息的消息类型
* @param message#receivedContent 回复内容 * @param message#receivedContent 回复内容
* @param message#prefix 回复内容前缀
* @see WeworkMessageBean.TEXT_TYPE * @see WeworkMessageBean.TEXT_TYPE
*/ */
@RequestMapping @RequestMapping

View File

@@ -53,7 +53,7 @@ object WeworkGetImpl {
goHomeTab("消息") goHomeTab("消息")
val firstTv = AccessibilityUtil.findAllByClazz(getRoot(), Views.TextView) val firstTv = AccessibilityUtil.findAllByClazz(getRoot(), Views.TextView)
.firstOrNull { it.text == null } .firstOrNull { it.text == null }
AccessibilityUtil.performClick(firstTv) AccessibilityUtil.performClick(firstTv, retry = false)
sleep(Constant.CHANGE_PAGE_INTERVAL) sleep(Constant.CHANGE_PAGE_INTERVAL)
val newFirstTv = AccessibilityUtil.findOneByClazz(getRoot(), Views.TextView) val newFirstTv = AccessibilityUtil.findOneByClazz(getRoot(), Views.TextView)
val nickname = newFirstTv?.text?.toString() val nickname = newFirstTv?.text?.toString()

View File

@@ -1,6 +1,5 @@
package org.yameida.worktool.service package org.yameida.worktool.service
import android.os.Build
import android.view.accessibility.AccessibilityNodeInfo import android.view.accessibility.AccessibilityNodeInfo
import androidx.core.text.isDigitsOnly import androidx.core.text.isDigitsOnly
import com.blankj.utilcode.util.LogUtils import com.blankj.utilcode.util.LogUtils
@@ -185,7 +184,7 @@ object WeworkLoopImpl {
//回到上一页 //回到上一页
var retry = 5 var retry = 5
while (retry-- > 0 && !isAtHome()) { while (retry-- > 0 && !isAtHome()) {
val textView = AccessibilityUtil.findOnceByText(getRoot(), "新的客户", "新的居民", exact = true) val textView = AccessibilityUtil.findOnceByText(getRoot(), "新的客户", "新的居民", "新的学员", exact = true)
if (textView == null) { if (textView == null) {
backPress() backPress()
} }
@@ -202,30 +201,20 @@ object WeworkLoopImpl {
if (Constant.autoReply == 0) return true if (Constant.autoReply == 0) return true
if (!isAtHome()) return true if (!isAtHome()) return true
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { val list = AccessibilityUtil.findAllOnceByText(getRoot(), "消息", exact = true)
val list = AccessibilityUtil.findAllOnceByText(getRoot(), "消息", exact = true) for (item in list) {
for (item in list) { if (item.parent.parent.parent.childCount == 5) {
if (item.parent.parent.parent.childCount == 5) { if (item.parent.childCount > 1) {
if (item.parent.childCount > 1) { LogUtils.d("消息有红点")
LogUtils.d("消息有红点") AccessibilityUtil.clickByNode(WeworkController.weworkService, item)
AccessibilityUtil.clickByNode(WeworkController.weworkService, item) sleep(100)
sleep(100) AccessibilityUtil.clickByNode(WeworkController.weworkService, item)
AccessibilityUtil.clickByNode(WeworkController.weworkService, item)
}
} }
} }
if (logIndex % 120 == 0) { }
goHomeTab("通讯录") if (logIndex % 120 == 0) {
goHomeTab("消息") goHomeTab("通讯录")
} goHomeTab("消息")
} else {
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 (!isAtHome()) return true if (!isAtHome()) return true
if (logIndex++ % 30 == 0) { if (logIndex++ % 30 == 0) {
@@ -277,12 +266,7 @@ object WeworkLoopImpl {
if (AccessibilityUtil.performClick(spotNodeList.first())) { if (AccessibilityUtil.performClick(spotNodeList.first())) {
//进入聊天页 下一步 getChatMessageList //进入聊天页 下一步 getChatMessageList
} else { } else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { AccessibilityUtil.clickByNode(WeworkController.weworkService, spotNodeList.first().parent)
AccessibilityUtil
.clickByNode(WeworkController.weworkService, spotNodeList.first().parent)
} else {
LogUtils.e("请将手机版本提升到 Android 7.0+ 或将企业微信版本回退到 4.0.8之前(4.0.6可用)")
}
} }
return true return true
} else { } else {

View File

@@ -1,6 +1,5 @@
package org.yameida.worktool.service package org.yameida.worktool.service
import android.os.Build
import android.view.accessibility.AccessibilityNodeInfo import android.view.accessibility.AccessibilityNodeInfo
import org.yameida.worktool.Constant import org.yameida.worktool.Constant
import org.yameida.worktool.model.WeworkMessageBean import org.yameida.worktool.model.WeworkMessageBean
@@ -25,7 +24,11 @@ object WeworkOperationImpl {
* @param at 要at的昵称 * @param at 要at的昵称
* @see WeworkMessageBean.TEXT_TYPE * @see WeworkMessageBean.TEXT_TYPE
*/ */
fun sendMessage(titleList: List<String>, receivedContent: String, at: String? = null): Boolean { fun sendMessage(titleList: List<String>, receivedContent: String?, at: String? = null): Boolean {
if (receivedContent.isNullOrEmpty()) {
LogUtils.d("未发现发送内容")
return false
}
for (title in titleList) { for (title in titleList) {
if (WeworkRoomUtil.intoRoom(title)) { if (WeworkRoomUtil.intoRoom(title)) {
sendChatMessage(receivedContent, at = at) sendChatMessage(receivedContent, at = at)
@@ -45,7 +48,6 @@ object WeworkOperationImpl {
* @param originalContent 原始消息的内容 * @param originalContent 原始消息的内容
* @param textType 原始消息的消息类型 * @param textType 原始消息的消息类型
* @param receivedContent 回复内容 * @param receivedContent 回复内容
* @param prefix 回复内容前缀
* @see WeworkMessageBean.TEXT_TYPE * @see WeworkMessageBean.TEXT_TYPE
*/ */
fun replyMessage( fun replyMessage(
@@ -53,9 +55,12 @@ object WeworkOperationImpl {
receivedName: String?, receivedName: String?,
originalContent: String, originalContent: String,
textType: Int, textType: Int,
receivedContent: String, receivedContent: String?
prefix: String = "[自动回复]"
): Boolean { ): Boolean {
if (receivedContent.isNullOrEmpty()) {
LogUtils.d("未发现回复内容")
return false
}
for (title in titleList) { for (title in titleList) {
if (WeworkRoomUtil.intoRoom(title)) { if (WeworkRoomUtil.intoRoom(title)) {
if (WeworkTextUtil.longClickMessageItem( if (WeworkTextUtil.longClickMessageItem(
@@ -68,17 +73,25 @@ object WeworkOperationImpl {
) )
) { ) {
LogUtils.v("开始回复") LogUtils.v("开始回复")
sendChatMessage(receivedContent, prefix) sendChatMessage(receivedContent, reply = true)
LogUtils.d("$title: 回复成功") LogUtils.d("$title: 回复成功")
WeworkLoopImpl.getChatMessageList() WeworkLoopImpl.getChatMessageList()
return true return true
} else { } else {
LogUtils.d("$title: 回复失败 直接发送答案") LogUtils.d("$title: 回复失败 直接发送答案")
error("$title: 回复失败 直接发送答案 $receivedContent") error("$title: 回复失败 直接发送答案 $receivedContent")
if (receivedName == null) { if (originalContent.isNotEmpty()) {
sendChatMessage(receivedContent, "$prefix$originalContent\n") if (receivedName == null) {
sendChatMessage("$originalContent\n$receivedContent")
} else {
sendChatMessage("$originalContent\n$receivedContent", receivedName)
}
} else { } else {
sendChatMessage(receivedContent, "$prefix$originalContent】@$receivedName\n") if (receivedName == null) {
sendChatMessage(receivedContent)
} else {
sendChatMessage(receivedContent, receivedName)
}
} }
WeworkLoopImpl.getChatMessageList() WeworkLoopImpl.getChatMessageList()
} }
@@ -167,7 +180,8 @@ object WeworkOperationImpl {
|| !groupChangeAnnouncement(groupAnnouncement) || !groupChangeAnnouncement(groupAnnouncement)
) return false ) return false
} }
getGroupQrcode(groupName) //TODO 暂移除获取群二维码
// getGroupQrcode(groupName)
return true return true
} }
@@ -222,15 +236,8 @@ object WeworkOperationImpl {
goHomeTab("工作台") goHomeTab("工作台")
val node = AccessibilityUtil.scrollAndFindByText(WeworkController.weworkService, getRoot(), "微盘") val node = AccessibilityUtil.scrollAndFindByText(WeworkController.weworkService, getRoot(), "微盘")
if (node != null) { if (node != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { AccessibilityUtil.performClick(node)
AccessibilityUtil.performClick(node) sleep(Constant.POP_WINDOW_INTERVAL)
sleep(Constant.POP_WINDOW_INTERVAL)
if (AccessibilityUtil.findOnceByText(getRoot(), "微盘", exact = true) != null) {
AccessibilityUtil.clickByNode(WeworkController.weworkService, node)
}
} else {
AccessibilityUtil.performClick(node)
}
val buttonList = AccessibilityUtil.findAllByClazz(getRoot(), Views.Button) val buttonList = AccessibilityUtil.findAllByClazz(getRoot(), Views.Button)
if (buttonList.size >= 4) { if (buttonList.size >= 4) {
AccessibilityUtil.performClick(buttonList[2]) AccessibilityUtil.performClick(buttonList[2])
@@ -272,15 +279,8 @@ object WeworkOperationImpl {
goHomeTab("工作台") goHomeTab("工作台")
val node = AccessibilityUtil.scrollAndFindByText(WeworkController.weworkService, getRoot(), "微盘") val node = AccessibilityUtil.scrollAndFindByText(WeworkController.weworkService, getRoot(), "微盘")
if (node != null) { if (node != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { AccessibilityUtil.performClick(node)
AccessibilityUtil.performClick(node) sleep(Constant.POP_WINDOW_INTERVAL)
sleep(Constant.POP_WINDOW_INTERVAL)
if (AccessibilityUtil.findOnceByText(getRoot(), "微盘", exact = true) != null) {
AccessibilityUtil.clickByNode(WeworkController.weworkService, node)
}
} else {
AccessibilityUtil.performClick(node)
}
val buttonList = AccessibilityUtil.findAllByClazz(getRoot(), Views.Button) val buttonList = AccessibilityUtil.findAllByClazz(getRoot(), Views.Button)
if (buttonList.size >= 4) { if (buttonList.size >= 4) {
AccessibilityUtil.performClick(buttonList[2]) AccessibilityUtil.performClick(buttonList[2])
@@ -401,17 +401,10 @@ object WeworkOperationImpl {
sleep(Constant.POP_WINDOW_INTERVAL) sleep(Constant.POP_WINDOW_INTERVAL)
val list = AccessibilityUtil.findAllByClazz(getRoot(), Views.ListView).lastOrNull() val list = AccessibilityUtil.findAllByClazz(getRoot(), Views.ListView).lastOrNull()
if (list != null) { if (list != null) {
val button = AccessibilityUtil.findOneByText(list, "添加客户", "添加居民", "加微信") val button = AccessibilityUtil.findOneByText(list, "添加客户", "添加居民", "加微信", "加学员", exact = true)
if (button != null) { if (button != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { AccessibilityUtil.performClick(button)
AccessibilityUtil.performClick(button) sleep(Constant.POP_WINDOW_INTERVAL)
sleep(Constant.POP_WINDOW_INTERVAL)
if (AccessibilityUtil.findOnceByText(list, "添加客户", "添加居民", "加微信", exact = true) != null) {
AccessibilityUtil.clickByNode(WeworkController.weworkService, button)
}
} else {
AccessibilityUtil.performClick(button)
}
AccessibilityUtil.findTextAndClick(getRoot(), "搜索手机号添加") AccessibilityUtil.findTextAndClick(getRoot(), "搜索手机号添加")
AccessibilityUtil.findTextInput(getRoot(), friend.phone.trim()) AccessibilityUtil.findTextInput(getRoot(), friend.phone.trim())
if (AccessibilityUtil.findTextAndClick(getRoot(), "网络查找手机")) { if (AccessibilityUtil.findTextAndClick(getRoot(), "网络查找手机")) {
@@ -475,7 +468,10 @@ object WeworkOperationImpl {
} }
} }
if (AccessibilityUtil.findTextAndClick(getRoot(), "添加为联系人")) { if (AccessibilityUtil.findTextAndClick(getRoot(), "添加为联系人")) {
LogUtils.d("添加好友成功: " + friend.phone) LogUtils.d("准备发送好友邀请: " + friend.phone)
if (!friend.leavingMsg.isNullOrEmpty()) {
AccessibilityUtil.findTextInput(getRoot(), friend.leavingMsg)
}
if (AccessibilityUtil.findTextAndClick(getRoot(), "发送添加邀请", "发送申请")) { if (AccessibilityUtil.findTextAndClick(getRoot(), "发送添加邀请", "发送申请")) {
LogUtils.d("发送添加邀请成功: " + friend.phone) LogUtils.d("发送添加邀请成功: " + friend.phone)
} }
@@ -553,17 +549,39 @@ object WeworkOperationImpl {
AccessibilityUtil.performClick(multiButton) AccessibilityUtil.performClick(multiButton)
AccessibilityUtil.performClick(searchButton) AccessibilityUtil.performClick(searchButton)
for (select in selectList) { for (select in selectList) {
AccessibilityUtil.findTextInput(getRoot(), select.replace("", "").replace("-.*$".toRegex(), "")) val needTrim = select.contains(Constant.regTrimTitle)
sleep(Constant.CHANGE_PAGE_INTERVAL * 2) val trimTitle = select.replace(Constant.regTrimTitle, "")
val selectListView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView, Views.RecyclerView, Views.ViewGroup, minChildCount = 2) AccessibilityUtil.findTextInput(getRoot(), trimTitle)
val imageView = AccessibilityUtil.findOneByClazz(selectListView, Views.ImageView)
if (imageView != null) {
AccessibilityUtil.performClick(imageView)
}
sleep(Constant.CHANGE_PAGE_INTERVAL) sleep(Constant.CHANGE_PAGE_INTERVAL)
val selectListView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView, Views.RecyclerView, Views.ViewGroup, minChildCount = 2)
val regex = "^$trimTitle(-.*)?(…)?(\\(.*?\\))?" + if (needTrim) "" else "$"
val matchSelect = AccessibilityUtil.findOneByTextRegex(
selectListView,
regex,
timeout = 2000,
root = false
)
if (selectListView != null && matchSelect != null) {
for (i in 0 until selectListView.childCount) {
val item = selectListView.getChild(i)
val searchResult = AccessibilityUtil.findOnceByTextRegex(item, regex)
if (searchResult != null) {
item.refresh()
val imageView =
AccessibilityUtil.findOneByClazz(item, Views.ImageView, root = false)
AccessibilityUtil.performClick(imageView)
}
}
}
if (matchSelect != null) {
LogUtils.d("找到搜索结果: $select")
} else {
LogUtils.e("未搜索到结果")
}
sleep(Constant.POP_WINDOW_INTERVAL)
} }
val confirmButton = val confirmButton =
AccessibilityUtil.findOneByText(getRoot(), "确定(${selectList.size})") AccessibilityUtil.findOneByTextRegex(getRoot(), "^确定(\\(.*?\\))?\$")
if (confirmButton != null) { if (confirmButton != null) {
AccessibilityUtil.performClick(confirmButton) AccessibilityUtil.performClick(confirmButton)
sleep(Constant.POP_WINDOW_INTERVAL) sleep(Constant.POP_WINDOW_INTERVAL)
@@ -571,12 +589,10 @@ object WeworkOperationImpl {
LogUtils.d("extraText: $extraText") LogUtils.d("extraText: $extraText")
AccessibilityUtil.findTextInput(getRoot(), extraText) AccessibilityUtil.findTextInput(getRoot(), extraText)
} }
val sendButtonList = AccessibilityUtil.findAllByText(getRoot(), "发送") val sendButton = AccessibilityUtil.findOneByTextRegex(getRoot(), "^发送(\\(.*?\\))?\$")
for (sendButton in sendButtonList.filter { it.text != null }) { if (sendButton != null) {
if (sendButton.text == "发送" || sendButton.text == "发送(${selectList.size})") { AccessibilityUtil.performClick(sendButton)
AccessibilityUtil.performClick(sendButton) return true
return true
}
} }
LogUtils.e("未发现发送按钮: ") LogUtils.e("未发现发送按钮: ")
return false return false
@@ -598,28 +614,14 @@ object WeworkOperationImpl {
*/ */
private fun createGroup(): Boolean { private fun createGroup(): Boolean {
goHomeTab("工作台") goHomeTab("工作台")
val node = AccessibilityUtil.scrollAndFindByText(WeworkController.weworkService, getRoot(), "客户群", "居民群") val node = AccessibilityUtil.scrollAndFindByText(WeworkController.weworkService, getRoot(), "客户群", "居民群", "学员群")
if (node != null) { if (node != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { AccessibilityUtil.performClick(node)
AccessibilityUtil.performClick(node) sleep(Constant.POP_WINDOW_INTERVAL)
sleep(Constant.POP_WINDOW_INTERVAL) LogUtils.d("进入客户群应用")
if (AccessibilityUtil.findOnceByText(getRoot(), "客户群", "居民群", exact = true) != null) { val textView =
if (AccessibilityUtil.clickByNode(WeworkController.weworkService, node)) { AccessibilityUtil.findOneByText(getRoot(), "创建一个客户群", "创建一个居民群", "创建一个学员群")
LogUtils.d("进入客户群应用") return AccessibilityUtil.performClick(textView)
val textView =
AccessibilityUtil.findOneByText(getRoot(), "创建一个客户群", "创建一个居民群")
return AccessibilityUtil.performClick(textView)
}
} else {
LogUtils.d("进入客户群应用")
val textView = AccessibilityUtil.findOneByText(getRoot(), "创建一个客户群", "创建一个居民群")
return AccessibilityUtil.performClick(textView)
}
} else if (AccessibilityUtil.performClick(node)) {
LogUtils.d("进入客户群应用")
val textView = AccessibilityUtil.findOneByText(getRoot(), "创建一个客户群", "创建一个居民群")
return AccessibilityUtil.performClick(textView)
}
} }
LogUtils.e("未找到客户群应用") LogUtils.e("未找到客户群应用")
log("未找到客户群应用") log("未找到客户群应用")
@@ -696,28 +698,47 @@ object WeworkOperationImpl {
if (textViewList.size >= 2) { if (textViewList.size >= 2) {
val multiButton = textViewList.lastOrNull() val multiButton = textViewList.lastOrNull()
for (select in selectList) { for (select in selectList) {
val needTrim = select.contains(Constant.regTrimTitle)
val trimTitle = select.replace(Constant.regTrimTitle, "")
AccessibilityUtil.performClick(multiButton) AccessibilityUtil.performClick(multiButton)
AccessibilityUtil.findTextInput(getRoot(), select) AccessibilityUtil.findTextInput(getRoot(), trimTitle)
sleep(Constant.POP_WINDOW_INTERVAL) sleep(Constant.POP_WINDOW_INTERVAL)
val selectListView = val selectListView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView, Views.RecyclerView, Views.ViewGroup, minChildCount = 2, firstChildClazz = Views.TextView)
AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView, Views.RecyclerView, Views.ViewGroup, minChildCount = 2) val regex = "^$trimTitle(-.*)?(…)?(\\(.*?\\))?" + if (needTrim) "" else "$"
if (selectListView != null) { val matchSelect = AccessibilityUtil.findOneByTextRegex(
val imageView = selectListView,
AccessibilityUtil.findOneByClazz(selectListView, Views.ImageView, root = false) regex,
AccessibilityUtil.performClick(imageView) timeout = 2000,
val textView = AccessibilityUtil.findOnceByClazz(getRoot(), Views.TextView) root = false
if (textView != null && textView.text.isNullOrBlank()) { )
AccessibilityUtil.performClick(textView) if (selectListView != null && matchSelect != null) {
for (i in 0 until selectListView.childCount) {
val item = selectListView.getChild(i)
val searchResult = AccessibilityUtil.findOnceByTextRegex(item, regex)
if (searchResult != null) {
item.refresh()
val imageView =
AccessibilityUtil.findOneByClazz(item, Views.ImageView, root = false)
AccessibilityUtil.performClick(imageView)
}
} }
}
val textView = AccessibilityUtil.findOnceByClazz(getRoot(), Views.TextView)
if (textView != null && textView.text.isNullOrBlank()) {
AccessibilityUtil.performClick(textView)
sleep(Constant.POP_WINDOW_INTERVAL)
}
if (matchSelect != null) {
LogUtils.d("找到搜索结果: $select")
} else { } else {
LogUtils.e("查到列表结果") LogUtils.e("搜索到结果")
} }
} }
if (showMessageHistory) { if (showMessageHistory) {
AccessibilityUtil.findTextAndClick(getRoot(), "聊天记录") AccessibilityUtil.findTextAndClick(getRoot(), "聊天记录")
} }
val confirmButton = val confirmButton =
AccessibilityUtil.findOneByText(getRoot(), "确定(${selectList.size})") AccessibilityUtil.findOneByTextRegex(getRoot(), "^确定(\\(.*?\\))?\$")
if (confirmButton != null) { if (confirmButton != null) {
AccessibilityUtil.performClick(confirmButton) AccessibilityUtil.performClick(confirmButton)
} else { } else {
@@ -764,21 +785,44 @@ object WeworkOperationImpl {
if (textViewList.size >= 2) { if (textViewList.size >= 2) {
val multiButton = textViewList.lastOrNull() val multiButton = textViewList.lastOrNull()
for (select in removeList) { for (select in removeList) {
val needTrim = select.contains(Constant.regTrimTitle)
val trimTitle = select.replace(Constant.regTrimTitle, "")
AccessibilityUtil.performClick(multiButton) AccessibilityUtil.performClick(multiButton)
AccessibilityUtil.findTextInput(getRoot(), select) AccessibilityUtil.findTextInput(getRoot(), trimTitle)
sleep(Constant.POP_WINDOW_INTERVAL) sleep(Constant.POP_WINDOW_INTERVAL)
val selectListView = val selectListView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView, Views.RecyclerView, Views.ViewGroup, minChildCount = 2, firstChildClazz = Views.RelativeLayout)
AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView, Views.RecyclerView, Views.ViewGroup, minChildCount = 2) val regex = "^$trimTitle(-.*)?(…)?(\\(.*?\\))?" + if (needTrim) "" else "$"
val imageView = val matchSelect = AccessibilityUtil.findOneByTextRegex(
AccessibilityUtil.findOneByClazz(selectListView, Views.ImageView, root = false) selectListView,
AccessibilityUtil.performClick(imageView) regex,
timeout = 2000,
root = false
)
if (selectListView != null && matchSelect != null) {
for (i in 0 until selectListView.childCount) {
val item = selectListView.getChild(i)
val searchResult = AccessibilityUtil.findOnceByTextRegex(item, regex)
if (searchResult != null) {
item.refresh()
val imageView =
AccessibilityUtil.findOneByClazz(item, Views.ImageView, root = false)
AccessibilityUtil.performClick(imageView)
}
}
}
val textView = AccessibilityUtil.findOnceByClazz(getRoot(), Views.TextView) val textView = AccessibilityUtil.findOnceByClazz(getRoot(), Views.TextView)
if (textView != null && textView.text.isNullOrBlank()) { if (textView != null && textView.text.isNullOrBlank()) {
AccessibilityUtil.performClick(textView) AccessibilityUtil.performClick(textView)
sleep(Constant.POP_WINDOW_INTERVAL)
}
if (matchSelect != null) {
LogUtils.d("找到搜索结果: $select")
} else {
LogUtils.e("未搜索到结果")
} }
} }
val confirmButton = val confirmButton =
AccessibilityUtil.findOneByText(getRoot(), "移出(${removeList.size})") AccessibilityUtil.findOneByText(getRoot(), "移出(")
if (confirmButton != null) { if (confirmButton != null) {
AccessibilityUtil.performClick(confirmButton) AccessibilityUtil.performClick(confirmButton)
} else { } else {
@@ -847,7 +891,7 @@ object WeworkOperationImpl {
/** /**
* 发送消息+@at * 发送消息+@at
*/ */
private fun sendChatMessage(text: String, prefix: String = "", at: String? = null) { private fun sendChatMessage(text: String, at: String? = null, reply: Boolean? = false) {
val voiceFlag = AccessibilityUtil.findOnceByText(getRoot(), "按住 说话", "按住说话", exact = true) val voiceFlag = AccessibilityUtil.findOnceByText(getRoot(), "按住 说话", "按住说话", exact = true)
if (voiceFlag != null) { if (voiceFlag != null) {
AccessibilityUtil.performClickWithSon(AccessibilityUtil.findFrontNode(voiceFlag)) AccessibilityUtil.performClickWithSon(AccessibilityUtil.findFrontNode(voiceFlag))
@@ -889,8 +933,8 @@ object WeworkOperationImpl {
} }
} }
} }
val content = if (atFailed) "@$at $prefix$text" else "$prefix$text" val content = if (atFailed) "@$at $text" else "$text"
val append = !at.isNullOrEmpty() && !atFailed val append = (reply == true) || (!at.isNullOrEmpty() && !atFailed)
if (AccessibilityUtil.findTextInput(getRoot(), content, append = append)) { if (AccessibilityUtil.findTextInput(getRoot(), content, append = append)) {
val sendButton = AccessibilityUtil.findAllByClazz(getRoot(), Views.Button) val sendButton = AccessibilityUtil.findAllByClazz(getRoot(), Views.Button)
.firstOrNull { it.text == "发送" } .firstOrNull { it.text == "发送" }

View File

@@ -5,17 +5,14 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.Build
import android.util.Log import android.util.Log
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
import androidx.annotation.RequiresApi
import com.blankj.utilcode.util.* import com.blankj.utilcode.util.*
import okhttp3.Response import okhttp3.Response
import okhttp3.WebSocket import okhttp3.WebSocket
import okhttp3.WebSocketListener import okhttp3.WebSocketListener
import org.yameida.worktool.Constant import org.yameida.worktool.Constant
import org.yameida.worktool.Demo import org.yameida.worktool.Demo
import org.yameida.worktool.config.WebConfig
import org.yameida.worktool.utils.* import org.yameida.worktool.utils.*
import java.lang.Exception import java.lang.Exception
@@ -31,9 +28,7 @@ class WeworkService : AccessibilityService() {
override fun onServiceConnected() { override fun onServiceConnected() {
LogUtils.i("初始化成功") LogUtils.i("初始化成功")
//隐藏软键盘模式 //隐藏软键盘模式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { softKeyboardController.showMode = SHOW_MODE_HIDDEN
softKeyboardController.showMode = SHOW_MODE_HIDDEN
}
WeworkController.weworkService = this WeworkController.weworkService = this
//初始化长连接 //初始化长连接
initWebSocket() initWebSocket()
@@ -51,12 +46,11 @@ class WeworkService : AccessibilityService() {
initWebSocket() initWebSocket()
} }
} }
}, IntentFilter(WebConfig.WEWORK_NOTIFY)) }, IntentFilter(Constant.WEWORK_NOTIFY))
} }
private fun initWebSocket() { private fun initWebSocket() {
val url = val url = Constant.getWsUrl()
WebConfig.WEWORK_URL + SPUtils.getInstance().getString(WebConfig.LISTEN_CHANNEL_ID)
val listener = EchoWebSocketListener() val listener = EchoWebSocketListener()
LogUtils.d("initWebSocket: $url") LogUtils.d("initWebSocket: $url")
webSocketManager = WebSocketManager(url, listener) webSocketManager = WebSocketManager(url, listener)
@@ -67,7 +61,6 @@ class WeworkService : AccessibilityService() {
* TYPE_VIEW_SCROLLED 列表滚动 * TYPE_VIEW_SCROLLED 列表滚动
* @param event * @param event
*/ */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
override fun onAccessibilityEvent(event: AccessibilityEvent) { override fun onAccessibilityEvent(event: AccessibilityEvent) {
} }
@@ -79,9 +72,7 @@ class WeworkService : AccessibilityService() {
super.onDestroy() super.onDestroy()
LogUtils.i("onDestroy") LogUtils.i("onDestroy")
//隐藏软键盘模式 //隐藏软键盘模式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { softKeyboardController.showMode = SHOW_MODE_AUTO
softKeyboardController.showMode = SHOW_MODE_AUTO
}
webSocketManager.close(1000, "service Destroy") webSocketManager.close(1000, "service Destroy")
} }
@@ -91,7 +82,7 @@ class WeworkService : AccessibilityService() {
override fun onOpen(webSocket: WebSocket, response: Response) { override fun onOpen(webSocket: WebSocket, response: Response) {
socket = webSocket socket = webSocket
Log.e(TAG, "链接建立") Log.e(TAG, "链接建立")
val robotId = SPUtils.getInstance().getString(WebConfig.LISTEN_CHANNEL_ID, "") val robotId = SPUtils.getInstance().getString(Constant.LISTEN_CHANNEL_ID, "")
val appVersion = SPUtils.getInstance().getString("appVersion", "") val appVersion = SPUtils.getInstance().getString("appVersion", "")
val workVersion= SPUtils.getInstance().getString("workVersion", "") val workVersion= SPUtils.getInstance().getString("workVersion", "")
log("链接建立: $robotId appVersion: $appVersion workVersion: $workVersion") log("链接建立: $robotId appVersion: $appVersion workVersion: $workVersion")

View File

@@ -4,13 +4,11 @@ import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.AccessibilityService.GestureResultCallback import android.accessibilityservice.AccessibilityService.GestureResultCallback
import android.accessibilityservice.GestureDescription import android.accessibilityservice.GestureDescription
import android.accessibilityservice.GestureDescription.StrokeDescription import android.accessibilityservice.GestureDescription.StrokeDescription
import android.annotation.TargetApi
import android.app.Notification import android.app.Notification
import android.app.PendingIntent import android.app.PendingIntent
import android.graphics.Path import android.graphics.Path
import android.graphics.Point import android.graphics.Point
import android.graphics.Rect import android.graphics.Rect
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
@@ -19,7 +17,6 @@ import com.blankj.utilcode.util.LogUtils
import org.yameida.worktool.service.getRoot import org.yameida.worktool.service.getRoot
import java.lang.Exception import java.lang.Exception
import java.lang.Thread.sleep import java.lang.Thread.sleep
import androidx.annotation.RequiresApi
import com.blankj.utilcode.util.ScreenUtils import com.blankj.utilcode.util.ScreenUtils
import org.yameida.worktool.service.WeworkController import org.yameida.worktool.service.WeworkController
@@ -56,8 +53,8 @@ import org.yameida.worktool.service.WeworkController
*/ */
object AccessibilityUtil { object AccessibilityUtil {
private const val tag = "AccessibilityUtil" private const val tag = "AccessibilityUtil"
private const val SHORT_INTERVAL = 100L private const val SHORT_INTERVAL = 150L
private const val SCROLL_INTERVAL = 300L private const val SCROLL_INTERVAL = 500L
//编辑EditView(非粘贴 推荐) //编辑EditView(非粘贴 推荐)
fun editTextInput(nodeInfo: AccessibilityNodeInfo?, text: String): Boolean { fun editTextInput(nodeInfo: AccessibilityNodeInfo?, text: String): Boolean {
@@ -154,7 +151,6 @@ object AccessibilityUtil {
} }
//输入x, y坐标模拟点击事件 //输入x, y坐标模拟点击事件
@TargetApi(Build.VERSION_CODES.N)
fun performXYClick(service: AccessibilityService, x: Float, y: Float): Boolean { fun performXYClick(service: AccessibilityService, x: Float, y: Float): Boolean {
val path = Path() val path = Path()
path.moveTo(x, y) path.moveTo(x, y)
@@ -175,16 +171,23 @@ object AccessibilityUtil {
/** /**
* 对某个节点或父节点进行点击 * 对某个节点或父节点进行点击
*/ */
fun performClick(nodeInfo: AccessibilityNodeInfo?): Boolean { fun performClick(nodeInfo: AccessibilityNodeInfo?, retry: Boolean = true): Boolean {
var nodeInfo: AccessibilityNodeInfo? = nodeInfo ?: return false var tempNodeInfo: AccessibilityNodeInfo? = nodeInfo ?: return false
while (nodeInfo != null) { while (tempNodeInfo != null) {
if (nodeInfo.isClickable) { if (tempNodeInfo.isClickable) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK) tempNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK)
LogUtils.v("performClick success! ${nodeInfo.className}")
return true return true
} }
nodeInfo = nodeInfo.parent tempNodeInfo = tempNodeInfo.parent
}
LogUtils.e("performClick failed! retry: $retry ${nodeInfo.className}")
if (retry) {
sleep(SHORT_INTERVAL * 2)
nodeInfo.refresh()
val click = clickByNode(WeworkController.weworkService, nodeInfo)
LogUtils.e("performClick failed! clickByNode: $click")
} }
LogUtils.e("performClick failed! ${nodeInfo?.className}")
return false return false
} }
@@ -380,6 +383,54 @@ object AccessibilityUtil {
return null return null
} }
/**
* 按正则表达式寻找节点和子节点内的一个匹配项
* @param node 节点
* @param regex 表达式
* @param timeout 检查超时时间
*/
fun findOneByTextRegex(
node: AccessibilityNodeInfo?,
regex: String,
timeout: Long = 5000,
root: Boolean = true
): AccessibilityNodeInfo? {
var node = node ?: return null
val startTime = System.currentTimeMillis()
var currentTime = startTime
while (currentTime - startTime <= timeout) {
val result = findOnceByTextRegex(node, regex)
if (result != null) return result
sleep(SHORT_INTERVAL)
if (root) {
node = getRoot(true)
} else {
node.refresh()
}
currentTime = System.currentTimeMillis()
}
Log.e(tag, "findOneByTextRegex: not found: $regex")
return null
}
/**
* 按正则表达式寻找节点和子节点内的一个匹配项
* @param node 节点
* @param regex 表达式
*/
fun findOnceByTextRegex(
node: AccessibilityNodeInfo?,
regex: String
): AccessibilityNodeInfo? {
if (node == null) return null
val textNodeList = findAllOnceByTextRegex(node, regex)
LogUtils.v("regex: $regex count: " + textNodeList.size)
if (textNodeList.size > 0) {
return textNodeList[0]
}
return null
}
/** /**
* 按文本(关键词)寻找节点和子节点内的一个匹配项 * 按文本(关键词)寻找节点和子节点内的一个匹配项
* @param node 节点 * @param node 节点
@@ -495,6 +546,60 @@ object AccessibilityUtil {
return list return list
} }
/**
* 按正则表达式寻找节点和子节点内的所有匹配项
* @param node 节点
* @param regex 关键词
* @param timeout 检查超时时间
*/
fun findAllByTextRegex(
node: AccessibilityNodeInfo?,
regex: String,
timeout: Long = 5000,
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 result = findAllOnceByTextRegex(node, regex)
LogUtils.v("regex: $regex count: " + result.size)
if (result.size >= minSize) return result
sleep(SHORT_INTERVAL)
if (root) {
node = getRoot(true)
} else {
node.refresh()
}
currentTime = System.currentTimeMillis()
}
Log.e(tag, "findAllByTextRegex: not found: $regex")
return arrayListOf()
}
/**
* 按正则表达式寻找节点和子节点内的所有匹配项
* node 节点
* clazz 类名
* limitDepth 深度 限制深度搜索深度必须匹配提供值且类名相同才返回 不填默认不限制
*/
fun findAllOnceByTextRegex(
node: AccessibilityNodeInfo?,
regex: String,
list: ArrayList<AccessibilityNodeInfo> = ArrayList()
): ArrayList<AccessibilityNodeInfo> {
if (node == null) return list
val nodeText = node.text?.toString()
if (nodeText != null && nodeText.matches(regex.toRegex())) {
list.add(node)
}
for (i in 0 until node.childCount) {
findAllOnceByTextRegex(node.getChild(i), regex, list = list)
}
return list
}
/** /**
* 按类名寻找节点和子节点内的一个匹配项 * 按类名寻找节点和子节点内的一个匹配项
* node 节点 * node 节点
@@ -509,13 +614,14 @@ object AccessibilityUtil {
depth: Int = 0, depth: Int = 0,
timeout: Long = 5000, timeout: Long = 5000,
root: Boolean = true, root: Boolean = true,
minChildCount: Int = 0 minChildCount: Int = 0,
firstChildClazz: String? = null
): AccessibilityNodeInfo? { ): AccessibilityNodeInfo? {
var node = node ?: return null var node = node ?: return null
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
var currentTime = startTime var currentTime = startTime
while (currentTime - startTime <= timeout) { while (currentTime - startTime <= timeout) {
val result = findOnceByClazz(node, *clazzList, limitDepth = limitDepth, depth = depth, minChildCount = minChildCount) val result = findOnceByClazz(node, *clazzList, limitDepth = limitDepth, depth = depth, minChildCount = minChildCount, firstChildClazz = firstChildClazz)
LogUtils.v("clazz: ${clazzList.joinToString()} result == null: ${result == null}") LogUtils.v("clazz: ${clazzList.joinToString()} result == null: ${result == null}")
if (result != null) return result if (result != null) return result
sleep(SHORT_INTERVAL) sleep(SHORT_INTERVAL)
@@ -542,15 +648,18 @@ object AccessibilityUtil {
vararg clazzList: String, vararg clazzList: String,
limitDepth: Int? = null, limitDepth: Int? = null,
depth: Int = 0, depth: Int = 0,
minChildCount: Int = 0 minChildCount: Int = 0,
firstChildClazz: String? = null
): AccessibilityNodeInfo? { ): AccessibilityNodeInfo? {
if (node == null) return null if (node == null) return null
if (node.className in clazzList) { if (node.className in clazzList) {
if ((limitDepth == null || limitDepth == depth) && node.childCount >= minChildCount) if ((limitDepth == null || limitDepth == depth) && node.childCount >= minChildCount)
return node if (firstChildClazz == null || (node.childCount > 0 && firstChildClazz == node.getChild(0).className)) {
return node
}
} }
for (i in 0 until node.childCount) { for (i in 0 until node.childCount) {
val result = findOnceByClazz(node.getChild(i), *clazzList, limitDepth = limitDepth, depth = depth + 1, minChildCount = minChildCount) val result = findOnceByClazz(node.getChild(i), *clazzList, limitDepth = limitDepth, depth = depth + 1, minChildCount = minChildCount, firstChildClazz = firstChildClazz)
if (result != null) return result if (result != null) return result
} }
return null return null
@@ -696,7 +805,7 @@ object AccessibilityUtil {
for (i in 0 until depth) { for (i in 0 until depth) {
s += "---" s += "---"
} }
Log.d(tag, "$s depth: $depth className: " + node.className) Log.d(tag, "$s depth: $depth className: " + node.className + " isClickable: " + node.isClickable)
if (printText && node.text != null) { if (printText && node.text != null) {
Log.d(tag, "$s depth: $depth text: " + node.text) Log.d(tag, "$s depth: $depth text: " + node.text)
} }
@@ -712,11 +821,11 @@ object AccessibilityUtil {
* Gesture手势实现点击(Android7+) * Gesture手势实现点击(Android7+)
* 解决 clickable=false 无法点击问题 * 解决 clickable=false 无法点击问题
*/ */
@RequiresApi(api = Build.VERSION_CODES.N)
fun clickByNode( fun clickByNode(
service: AccessibilityService, service: AccessibilityService,
nodeInfo: AccessibilityNodeInfo nodeInfo: AccessibilityNodeInfo
): Boolean { ): Boolean {
nodeInfo.refresh()
val rect = Rect() val rect = Rect()
nodeInfo.getBoundsInScreen(rect) nodeInfo.getBoundsInScreen(rect)
val x: Int = (rect.left + rect.right) / 2 val x: Int = (rect.left + rect.right) / 2
@@ -743,7 +852,6 @@ object AccessibilityUtil {
* Gesture手势实现滚动(Android7+) * Gesture手势实现滚动(Android7+)
* 解决 scrollable=false 无法滚动问题 * 解决 scrollable=false 无法滚动问题
*/ */
@RequiresApi(api = Build.VERSION_CODES.N)
fun scrollDownByNode( fun scrollDownByNode(
service: AccessibilityService, service: AccessibilityService,
nodeInfo: AccessibilityNodeInfo nodeInfo: AccessibilityNodeInfo
@@ -781,29 +889,24 @@ object AccessibilityUtil {
distanceX: Int = 0, distanceX: Int = 0,
distanceY: Int = 0 distanceY: Int = 0
): Boolean { ): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { val rect = Rect()
val rect = Rect() nodeInfo.getBoundsInScreen(rect)
nodeInfo.getBoundsInScreen(rect) val point = Point((rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2)
val point = Point((rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2) val builder = GestureDescription.Builder()
val builder = GestureDescription.Builder() val path = Path()
val path = Path() path.moveTo(point.x.toFloat(), point.y.toFloat())
path.moveTo(point.x.toFloat(), point.y.toFloat()) path.lineTo(point.x.toFloat() + distanceX, point.y.toFloat() + distanceY)
path.lineTo(point.x.toFloat() + distanceX, point.y.toFloat() + distanceY) builder.addStroke(StrokeDescription(path, 0L, 300L))
builder.addStroke(StrokeDescription(path, 0L, 300L)) val gesture = builder.build()
val gesture = builder.build() return service.dispatchGesture(gesture, object : GestureResultCallback() {
return service.dispatchGesture(gesture, object : GestureResultCallback() { override fun onCompleted(gestureDescription: GestureDescription) {
override fun onCompleted(gestureDescription: GestureDescription) { LogUtils.v("scroll ok onCompleted")
LogUtils.v("scroll ok onCompleted") }
}
override fun onCancelled(gestureDescription: GestureDescription) { override fun onCancelled(gestureDescription: GestureDescription) {
LogUtils.v("scroll ok onCancelled") LogUtils.v("scroll ok onCancelled")
} }
}, null) }, null)
} else {
LogUtils.e("系统版本<7.0 不支持手势操作")
return false
}
} }
/** /**
@@ -819,25 +922,20 @@ object AccessibilityUtil {
distanceX: Int = 0, distanceX: Int = 0,
distanceY: Int = 0 distanceY: Int = 0
): Boolean { ): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { val builder = GestureDescription.Builder()
val builder = GestureDescription.Builder() val path = Path()
val path = Path() path.moveTo(x.toFloat(), y.toFloat())
path.moveTo(x.toFloat(), y.toFloat()) path.lineTo(x.toFloat() + distanceX, y.toFloat() + distanceY)
path.lineTo(x.toFloat() + distanceX, y.toFloat() + distanceY) builder.addStroke(StrokeDescription(path, 0L, 300L))
builder.addStroke(StrokeDescription(path, 0L, 300L)) val gesture = builder.build()
val gesture = builder.build() return service.dispatchGesture(gesture, object : GestureResultCallback() {
return service.dispatchGesture(gesture, object : GestureResultCallback() { override fun onCompleted(gestureDescription: GestureDescription) {
override fun onCompleted(gestureDescription: GestureDescription) { LogUtils.v("scroll ok onCompleted")
LogUtils.v("scroll ok onCompleted") }
}
override fun onCancelled(gestureDescription: GestureDescription) { override fun onCancelled(gestureDescription: GestureDescription) {
LogUtils.v("scroll ok onCancelled") LogUtils.v("scroll ok onCancelled")
} }
}, null) }, null)
} else {
LogUtils.e("系统版本<7.0 不支持手势操作")
return false
}
} }
} }

View File

@@ -16,7 +16,7 @@ import update.UpdateAppUtils
object UpdateUtil { object UpdateUtil {
fun checkUpdate() { fun checkUpdate() {
OkGo.get<String>(Constant.URL_CHECK_UPDATE) OkGo.get<String>(Constant.getCheckUpdateUrl())
.execute(object : StringCallback() { .execute(object : StringCallback() {
override fun onSuccess(response: Response<String>) { override fun onSuccess(response: Response<String>) {
val commonResult = val commonResult =

View File

@@ -6,10 +6,7 @@ import org.yameida.worktool.utils.AccessibilityUtil.findFrontNode
import org.yameida.worktool.model.WeworkMessageBean import org.yameida.worktool.model.WeworkMessageBean
import com.blankj.utilcode.util.LogUtils import com.blankj.utilcode.util.LogUtils
import org.yameida.worktool.Constant import org.yameida.worktool.Constant
import org.yameida.worktool.service.backPress import org.yameida.worktool.service.*
import org.yameida.worktool.service.getRoot
import org.yameida.worktool.service.goHome
import org.yameida.worktool.service.sleep
import org.yameida.worktool.utils.AccessibilityUtil.findAllOnceByClazz import org.yameida.worktool.utils.AccessibilityUtil.findAllOnceByClazz
/** /**
@@ -101,13 +98,21 @@ object WeworkRoomUtil {
val searchButton: AccessibilityNodeInfo = textViewList[textViewList.size - 2] val searchButton: AccessibilityNodeInfo = textViewList[textViewList.size - 2]
val multiButton: AccessibilityNodeInfo = textViewList[textViewList.size - 1] val multiButton: AccessibilityNodeInfo = textViewList[textViewList.size - 1]
AccessibilityUtil.performClick(searchButton) AccessibilityUtil.performClick(searchButton)
AccessibilityUtil.findTextInput(getRoot(), title.replace("", "").replace("-.*$".toRegex(), "")) val needTrim = title.contains(Constant.regTrimTitle)
val trimTitle = title.replace(Constant.regTrimTitle, "")
AccessibilityUtil.findTextInput(getRoot(), trimTitle)
sleep(Constant.CHANGE_PAGE_INTERVAL) sleep(Constant.CHANGE_PAGE_INTERVAL)
//消息页搜索结果列表 //消息页搜索结果列表
val selectListView = findOneByClazz(getRoot(), Views.ListView) val selectListView = findOneByClazz(getRoot(), Views.ListView)
val imageView = AccessibilityUtil.findOnceByClazz(selectListView, Views.ImageView) val searchResult = AccessibilityUtil.findOneByText(
if (imageView != null) { selectListView,
AccessibilityUtil.performClick(imageView) trimTitle,
exact = !needTrim,
timeout = 2000,
root = false
)
if (searchResult != null) {
AccessibilityUtil.performClick(searchResult)
LogUtils.d("进入房间: $title") LogUtils.d("进入房间: $title")
sleep(Constant.CHANGE_PAGE_INTERVAL) sleep(Constant.CHANGE_PAGE_INTERVAL)
return true return true

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#fff"
android:orientation="vertical">
<EditText
android:id="@+id/body"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:hint="wss://"
android:lines="3"
android:textColor="#000000" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#169BD5"
android:text="确定" />
<Button
android:id="@+id/cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#F4F4F4"
android:text="@android:string/cancel" />
</LinearLayout>
</LinearLayout>

View File

@@ -6,7 +6,7 @@
<dimen name="setting_start_padding">22dp</dimen> <dimen name="setting_start_padding">22dp</dimen>
<dimen name="setting_end_start_padding">25dp</dimen> <dimen name="setting_end_start_padding">25dp</dimen>
<dimen name="setting_end_padding">10dp</dimen> <dimen name="setting_end_padding">10dp</dimen>
<dimen name="setting_vertical_padding">7dp</dimen> <dimen name="setting_vertical_padding">5dp</dimen>
<dimen name="setting_start_font_width">150dp</dimen> <dimen name="setting_start_font_width">150dp</dimen>
<dimen name="setting_end_font_width">60dp</dimen> <dimen name="setting_end_font_width">60dp</dimen>
<dimen name="setting_start_image_width">20dp</dimen> <dimen name="setting_start_image_width">20dp</dimen>