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
import com.blankj.utilcode.util.SPUtils
import org.yameida.worktool.config.WebConfig
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"
val BASE_URL = WebConfig.HOST.replace("wss", "https").replace("ws", "http")
val URL_CHECK_UPDATE = "$BASE_URL/appUpdate/checkUpdate"
const val LISTEN_CHANNEL_ID = "LISTEN_CHANNEL_ID"
const val WEWORK_NOTIFY = "wework_notify"
const val CHANGE_PAGE_INTERVAL = 1000L
const val POP_WINDOW_INTERVAL = 500L
private const val DEFAULT_HOST = "wss://worktool.asrtts.cn"
var myName = ""
var regTrimTitle = "(…$)|(-.*$)|(\\(.*?\\)$)".toRegex()
var key = "9876543210abcdef".toByteArray()
var iv = "0123456789abcdef".toByteArray()
val transformation = "AES/CBC/PKCS7Padding"
var encryptType = SPUtils.getInstance().getInt("encryptType", 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 org.yameida.worktool.*
import org.yameida.worktool.service.WeworkService
import org.yameida.worktool.config.WebConfig
import org.yameida.worktool.utils.UpdateUtil
import android.app.Dialog
import android.widget.Button
import android.widget.EditText
class ListenActivity : AppCompatActivity() {
@@ -42,12 +45,12 @@ class ListenActivity : AppCompatActivity() {
}
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 {
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("保存成功")
sendBroadcast(Intent(WebConfig.WEWORK_NOTIFY).apply {
sendBroadcast(Intent(Constant.WEWORK_NOTIFY).apply {
putExtra("type", "modify_channel")
})
MobclickAgent.onProfileSignIn(channel)
@@ -64,7 +67,11 @@ class ListenActivity : AppCompatActivity() {
Constant.autoReply = if (isChecked) 1 else 0
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()}"
tv_version.text = version
val workVersionName = AppUtils.getAppInfo(Constant.PACKAGE_NAMES)?.versionName
@@ -92,7 +99,7 @@ class ListenActivity : AppCompatActivity() {
sw_accessibility.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
LogUtils.i("sw_accessibility onCheckedChanged: $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
ToastUtils.showLong("请先填写并保存链接号~")
} 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;
//内容移除了@me
public String receivedContent;
//回复内容前缀
public String prefix;
//想要at的昵称
public String at;
//原始内容text
@@ -254,6 +252,8 @@ public class WeworkMessageBean {
public List<String> tagList;
//是否是新好友
public Boolean newFriend;
//留言
public String leavingMsg;
}
@Override
@@ -266,7 +266,7 @@ public class WeworkMessageBean {
@Override
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,17 +5,14 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.util.Log
import android.view.accessibility.AccessibilityEvent
import androidx.annotation.RequiresApi
import com.blankj.utilcode.util.*
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import org.yameida.worktool.Constant
import org.yameida.worktool.Demo
import org.yameida.worktool.config.WebConfig
import org.yameida.worktool.utils.*
import java.lang.Exception
@@ -31,9 +28,7 @@ class WeworkService : AccessibilityService() {
override fun onServiceConnected() {
LogUtils.i("初始化成功")
//隐藏软键盘模式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
softKeyboardController.showMode = SHOW_MODE_HIDDEN
}
WeworkController.weworkService = this
//初始化长连接
initWebSocket()
@@ -51,12 +46,11 @@ class WeworkService : AccessibilityService() {
initWebSocket()
}
}
}, IntentFilter(WebConfig.WEWORK_NOTIFY))
}, IntentFilter(Constant.WEWORK_NOTIFY))
}
private fun initWebSocket() {
val url =
WebConfig.WEWORK_URL + SPUtils.getInstance().getString(WebConfig.LISTEN_CHANNEL_ID)
val url = Constant.getWsUrl()
val listener = EchoWebSocketListener()
LogUtils.d("initWebSocket: $url")
webSocketManager = WebSocketManager(url, listener)
@@ -67,7 +61,6 @@ class WeworkService : AccessibilityService() {
* TYPE_VIEW_SCROLLED 列表滚动
* @param event
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
override fun onAccessibilityEvent(event: AccessibilityEvent) {
}
@@ -79,9 +72,7 @@ class WeworkService : AccessibilityService() {
super.onDestroy()
LogUtils.i("onDestroy")
//隐藏软键盘模式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
softKeyboardController.showMode = SHOW_MODE_AUTO
}
webSocketManager.close(1000, "service Destroy")
}
@@ -91,7 +82,7 @@ class WeworkService : AccessibilityService() {
override fun onOpen(webSocket: WebSocket, response: Response) {
socket = webSocket
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 workVersion= SPUtils.getInstance().getString("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.GestureDescription
import android.accessibilityservice.GestureDescription.StrokeDescription
import android.annotation.TargetApi
import android.app.Notification
import android.app.PendingIntent
import android.graphics.Path
import android.graphics.Point
import android.graphics.Rect
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.accessibility.AccessibilityEvent
@@ -19,7 +17,6 @@ import com.blankj.utilcode.util.LogUtils
import org.yameida.worktool.service.getRoot
import java.lang.Exception
import java.lang.Thread.sleep
import androidx.annotation.RequiresApi
import com.blankj.utilcode.util.ScreenUtils
import org.yameida.worktool.service.WeworkController
@@ -56,8 +53,8 @@ import org.yameida.worktool.service.WeworkController
*/
object AccessibilityUtil {
private const val tag = "AccessibilityUtil"
private const val SHORT_INTERVAL = 100L
private const val SCROLL_INTERVAL = 300L
private const val SHORT_INTERVAL = 150L
private const val SCROLL_INTERVAL = 500L
//编辑EditView(非粘贴 推荐)
fun editTextInput(nodeInfo: AccessibilityNodeInfo?, text: String): Boolean {
@@ -154,7 +151,6 @@ object AccessibilityUtil {
}
//输入x, y坐标模拟点击事件
@TargetApi(Build.VERSION_CODES.N)
fun performXYClick(service: AccessibilityService, x: Float, y: Float): Boolean {
val path = Path()
path.moveTo(x, y)
@@ -175,16 +171,23 @@ object AccessibilityUtil {
/**
* 对某个节点或父节点进行点击
*/
fun performClick(nodeInfo: AccessibilityNodeInfo?): Boolean {
var nodeInfo: AccessibilityNodeInfo? = nodeInfo ?: return false
while (nodeInfo != null) {
if (nodeInfo.isClickable) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK)
fun performClick(nodeInfo: AccessibilityNodeInfo?, retry: Boolean = true): Boolean {
var tempNodeInfo: AccessibilityNodeInfo? = nodeInfo ?: return false
while (tempNodeInfo != null) {
if (tempNodeInfo.isClickable) {
tempNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK)
LogUtils.v("performClick success! ${nodeInfo.className}")
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
}
@@ -380,6 +383,54 @@ object AccessibilityUtil {
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 节点
@@ -495,6 +546,60 @@ object AccessibilityUtil {
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 节点
@@ -509,13 +614,14 @@ object AccessibilityUtil {
depth: Int = 0,
timeout: Long = 5000,
root: Boolean = true,
minChildCount: Int = 0
minChildCount: Int = 0,
firstChildClazz: String? = null
): AccessibilityNodeInfo? {
var node = node ?: return null
val startTime = System.currentTimeMillis()
var currentTime = startTime
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}")
if (result != null) return result
sleep(SHORT_INTERVAL)
@@ -542,15 +648,18 @@ object AccessibilityUtil {
vararg clazzList: String,
limitDepth: Int? = null,
depth: Int = 0,
minChildCount: Int = 0
minChildCount: Int = 0,
firstChildClazz: String? = null
): AccessibilityNodeInfo? {
if (node == null) return null
if (node.className in clazzList) {
if ((limitDepth == null || limitDepth == depth) && node.childCount >= minChildCount)
if (firstChildClazz == null || (node.childCount > 0 && firstChildClazz == node.getChild(0).className)) {
return node
}
}
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
}
return null
@@ -696,7 +805,7 @@ object AccessibilityUtil {
for (i in 0 until depth) {
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) {
Log.d(tag, "$s depth: $depth text: " + node.text)
}
@@ -712,11 +821,11 @@ object AccessibilityUtil {
* Gesture手势实现点击(Android7+)
* 解决 clickable=false 无法点击问题
*/
@RequiresApi(api = Build.VERSION_CODES.N)
fun clickByNode(
service: AccessibilityService,
nodeInfo: AccessibilityNodeInfo
): Boolean {
nodeInfo.refresh()
val rect = Rect()
nodeInfo.getBoundsInScreen(rect)
val x: Int = (rect.left + rect.right) / 2
@@ -743,7 +852,6 @@ object AccessibilityUtil {
* Gesture手势实现滚动(Android7+)
* 解决 scrollable=false 无法滚动问题
*/
@RequiresApi(api = Build.VERSION_CODES.N)
fun scrollDownByNode(
service: AccessibilityService,
nodeInfo: AccessibilityNodeInfo
@@ -781,7 +889,6 @@ object AccessibilityUtil {
distanceX: Int = 0,
distanceY: Int = 0
): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val rect = Rect()
nodeInfo.getBoundsInScreen(rect)
val point = Point((rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2)
@@ -800,10 +907,6 @@ object AccessibilityUtil {
LogUtils.v("scroll ok onCancelled")
}
}, null)
} else {
LogUtils.e("系统版本<7.0 不支持手势操作")
return false
}
}
/**
@@ -819,7 +922,6 @@ object AccessibilityUtil {
distanceX: Int = 0,
distanceY: Int = 0
): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val builder = GestureDescription.Builder()
val path = Path()
path.moveTo(x.toFloat(), y.toFloat())
@@ -835,9 +937,5 @@ object AccessibilityUtil {
LogUtils.v("scroll ok onCancelled")
}
}, null)
} else {
LogUtils.e("系统版本<7.0 不支持手势操作")
return false
}
}
}

View File

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

View File

@@ -6,10 +6,7 @@ import org.yameida.worktool.utils.AccessibilityUtil.findFrontNode
import org.yameida.worktool.model.WeworkMessageBean
import com.blankj.utilcode.util.LogUtils
import org.yameida.worktool.Constant
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.service.*
import org.yameida.worktool.utils.AccessibilityUtil.findAllOnceByClazz
/**
@@ -101,13 +98,21 @@ object WeworkRoomUtil {
val searchButton: AccessibilityNodeInfo = textViewList[textViewList.size - 2]
val multiButton: AccessibilityNodeInfo = textViewList[textViewList.size - 1]
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)
//消息页搜索结果列表
val selectListView = findOneByClazz(getRoot(), Views.ListView)
val imageView = AccessibilityUtil.findOnceByClazz(selectListView, Views.ImageView)
if (imageView != null) {
AccessibilityUtil.performClick(imageView)
val searchResult = AccessibilityUtil.findOneByText(
selectListView,
trimTitle,
exact = !needTrim,
timeout = 2000,
root = false
)
if (searchResult != null) {
AccessibilityUtil.performClick(searchResult)
LogUtils.d("进入房间: $title")
sleep(Constant.CHANGE_PAGE_INTERVAL)
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_end_start_padding">25dp</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_end_font_width">60dp</dimen>
<dimen name="setting_start_image_width">20dp</dimen>