update 避免群重复创建;可回调获取群二维码;优化回退;其他稳定性优化

This commit is contained in:
尹甲仑
2022-07-04 11:51:50 +08:00
parent 5613725ccf
commit 7e71b8749b
16 changed files with 204 additions and 65 deletions

View File

@@ -61,5 +61,7 @@ dependencies {
//自动更新
implementation 'com.teprinciple:updateapputilsx:2.3.0'
//ok
api 'com.lzy.net:okgo:3.0.4'
implementation 'com.lzy.net:okgo:3.0.4'
//qrcode
implementation 'com.github.yoojia:next-qrcode:2.0-2'
}

View File

@@ -27,7 +27,8 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true">
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true">
<activity
android:name="org.yameida.worktool.activity.ListenActivity"

View File

@@ -11,6 +11,7 @@ object Constant {
const val CHANGE_PAGE_INTERVAL = 1000L
const val POP_WINDOW_INTERVAL = 500L
var myName = ""
var key = "9876543210abcdef".toByteArray()
var iv = "0123456789abcdef".toByteArray()
val transformation = "AES/CBC/PKCS7Padding"

View File

@@ -20,6 +20,9 @@ object Demo {
//打印当前视图树
// AccessibilityUtil.printNodeClazzTree(getRoot())
//获取群二维码
// WeworkOperationImpl.getGroupQrcode("测试群01")
//手机号添加好友
// WeworkOperationImpl.addFriendByPhone(WeworkMessageBean.Friend().apply {
// this.phone = "13010001000"

View File

@@ -5,6 +5,7 @@ import android.content.Intent
import android.os.Bundle
import android.provider.Settings
import android.text.TextUtils.SimpleStringSplitter
import android.view.WindowManager
import android.widget.CompoundButton
import android.widget.Switch
import androidx.appcompat.app.AlertDialog
@@ -22,12 +23,14 @@ class ListenActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
title = "WorkTool"
setContentView(R.layout.activity_listen)
initView()
initAccessibility()
UpdateUtil.checkUpdate()
PermissionUtils.permission("android.permission.READ_EXTERNAL_STORAGE").request()
}
override fun onStart() {

View File

@@ -58,6 +58,9 @@ public class WeworkMessageBean {
public static final int PASS_ALL_FRIEND_REQUEST = 212;
public static final int ADD_FRIEND_BY_PHONE = 213;
public static final int SHOW_GROUP_INFO = 214;
public static final int INIT_ANTI_HARASSMENT_RULE = 215;
public static final int UPDATE_ANTI_HARASSMENT_RULE = 216;
public static final int DELETED_ANTI_HARASSMENT_RULE = 217;
public static final int ROBOT_LOG = 301;
public static final int ROBOT_ERROR_LOG = 302;
@@ -66,6 +69,7 @@ public class WeworkMessageBean {
public static final int GET_GROUP_INFO = 501;
public static final int GET_FRIEND_INFO = 502;
public static final int GET_MY_INFO = 503;
public static final int GET_GROUP_QRCODE = 504;
/**
* roomType
@@ -166,6 +170,8 @@ public class WeworkMessageBean {
public MyInfo myInfo;
//对象名称(图片、文件、小程序等)
public String objectName;
//二维码转码
public String qrcode;
//添加好友
public Friend friend;

View File

@@ -91,7 +91,9 @@ fun getRoot(ignoreCheck: Boolean): AccessibilityNodeInfo {
return root
} else {
LogUtils.e("当前不在企业微信: ${root.packageName}")
error("当前不在企业微信: ${root.packageName}")
if (System.currentTimeMillis() % 30 == 0L) {
error("当前不在企业微信: ${root.packageName}")
}
if (ignoreCheck) {
return root
}
@@ -123,8 +125,10 @@ fun backPress() {
} else {
LogUtils.d("未找到BT按钮")
val confirm = AccessibilityUtil.findOnceByText(getRoot(), "确定")
?: AccessibilityUtil.findOnceByText(getRoot(), "我知道了")
?: AccessibilityUtil.findOnceByText(getRoot(), "暂不进入")
if (confirm != null) {
LogUtils.d("尝试点击确定")
LogUtils.d("尝试点击确定/我知道了/暂不进入")
AccessibilityUtil.performClick(confirm)
}
}

View File

@@ -76,15 +76,8 @@ object MyLooper {
}
//去重处理 丢弃之前的重复指令 丢弃之前的获取新消息指令
for (message in LinkedHashSet(messageList.list)) {
getInstance().removeMessages(WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE)
if (message.type == WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE) {
WeworkController.enableLoopRunning = true
if (!WeworkController.mainLoopRunning) {
getInstance().sendMessage(Message.obtain().apply {
what = WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE
obj = message
})
}
} else {
WeworkController.mainLoopRunning = false
getInstance().removeMessages(message.type * message.hashCode())
@@ -93,6 +86,11 @@ object MyLooper {
obj = message
})
}
getInstance().removeMessages(WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE)
getInstance().sendMessage(Message.obtain().apply {
what = WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE
obj = WeworkMessageBean().apply { type = WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE }
})
}
}
}

View File

@@ -33,7 +33,7 @@ object WeworkController {
*/
@RequestMapping
fun loopReceiveNewMessage() {
LogUtils.d("loopReceiveNewMessage()")
LogUtils.d("loopReceiveNewMessage() enableLoopRunning: $enableLoopRunning")
WeworkLoopImpl.mainLoop()
}

View File

@@ -56,7 +56,8 @@ object WeworkGetImpl {
val nickname = newFirstTv?.text?.toString()
AccessibilityUtil.performClick(firstTv)
if (nickname != null) {
LogUtils.d("我的昵称: $nickname")
Constant.myName = nickname
LogUtils.d("我的昵称: ${Constant.myName}")
val weworkMessageBean = WeworkMessageBean()
weworkMessageBean.type = WeworkMessageBean.GET_MY_INFO
weworkMessageBean.myInfo = WeworkMessageBean.MyInfo().apply { name = nickname }
@@ -77,6 +78,8 @@ object WeworkGetImpl {
val firstText = textViewList[0].text?.toString()
if (firstText == "姓名" && myInfo.name == null) {
myInfo.name = textViewList[1].text?.toString() ?: ""
Constant.myName = myInfo.name
LogUtils.d("我的昵称: ${Constant.myName}")
}
if (firstText == "别名" && myInfo.alias == null) {
myInfo.alias = textViewList[1].text?.toString() ?: ""
@@ -106,7 +109,6 @@ object WeworkGetImpl {
weworkMessageBean.type = WeworkMessageBean.GET_MY_INFO
weworkMessageBean.myInfo = myInfo
WeworkController.weworkService.webSocketManager.send(weworkMessageBean)
WeworkLoopImpl.startLoop()
return true
}

View File

@@ -1,6 +1,6 @@
package org.yameida.worktool.service
import android.os.Message
import android.os.Build
import android.view.accessibility.AccessibilityNodeInfo
import androidx.core.text.isDigitsOnly
import com.blankj.utilcode.util.LogUtils
@@ -19,39 +19,22 @@ object WeworkLoopImpl {
val stopWords = arrayListOf("解析中")
var logIndex = 0
/**
* 如果远端开启接收新消息则本地自动在队列任务结束后调用接收新消息
* 该方法在每个任务结束时调用
*/
fun startLoop(delay: Long = 0) {
LogUtils.d("startLoop() delay: $delay")
val myLooper = MyLooper.getInstance()
if (WeworkController.enableLoopRunning) {
myLooper.removeMessages(WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE)
if (!mainLoopRunning) {
myLooper.sendMessageDelayed(Message.obtain().apply {
what = WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE
obj = WeworkMessageBean().apply { type = WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE }
}, delay)
}
}
}
fun mainLoop() {
if (!WeworkController.enableLoopRunning)
return
mainLoopRunning = true
try {
while (mainLoopRunning) {
if (WeworkRoomUtil.getRoomType(getRoot(), false) != WeworkMessageBean.ROOM_TYPE_UNKNOWN
&& getChatMessageList()) {
}
if (!mainLoopRunning) break
goHomeTab("消息")
if (getChatroomList() && getChatMessageList()) {
mainLoopRunning = false
break
if (!mainLoopRunning) break
if (getChatroomList()) {
}
if (!mainLoopRunning) break
if (getFriendRequest()) {
mainLoopRunning = false
break
}
sleep(500)
}
@@ -107,7 +90,7 @@ object WeworkLoopImpl {
* 1.获取群名
* 2.获取消息列表
*/
fun getChatMessageList(): Boolean {
fun getChatMessageList(timeout: Long = 3000): Boolean {
AccessibilityUtil.performScrollDown(getRoot(), 0)
val roomType = WeworkRoomUtil.getRoomType(getRoot())
var titleList = WeworkRoomUtil.getRoomTitle(getRoot())
@@ -138,9 +121,27 @@ object WeworkLoopImpl {
null
)
)
//todo 推迟执行获取新消息
//检查如果当前房间最后一条消息未变化则不推迟
startLoop(3500)
val lastMessage = messageList.lastOrNull { it.sender == 0 }
if (lastMessage != null) {
var tempContent = ""
for (itemMessage in lastMessage.itemMessageList) {
if (itemMessage.text.contains("@" + Constant.myName)
|| itemMessage.text.isDigitsOnly()) {
tempContent = itemMessage.text
}
}
if (roomType == WeworkMessageBean.ROOM_TYPE_EXTERNAL_CONTACT
|| roomType == WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT
|| tempContent.isNotBlank()) {
LogUtils.v("推测需要回复: $tempContent")
val startTime = System.currentTimeMillis()
var currentTime = startTime
while (mainLoopRunning && currentTime - startTime <= timeout) {
sleep(Constant.POP_WINDOW_INTERVAL / 5)
currentTime = System.currentTimeMillis()
}
}
}
return true
} else {
LogUtils.e("未找到聊天消息列表")
@@ -245,10 +246,14 @@ object WeworkLoopImpl {
if (spotNodeList.size > 0) {
LogUtils.i("发现未读消息: " + spotNodeList.size + "")
log("发现未读消息: " + spotNodeList.size + "")
for (spotNode in spotNodeList) {
if (AccessibilityUtil.performClick(spotNode)) {
//进入聊天页 下一步 getChatMessageList
break
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可用)")
}
}
return true

View File

@@ -1,13 +1,16 @@
package org.yameida.worktool.service
import android.view.accessibility.AccessibilityNodeInfo
import com.blankj.utilcode.util.LogUtils
import org.yameida.worktool.Constant
import org.yameida.worktool.model.WeworkMessageBean
import org.yameida.worktool.utils.AccessibilityUtil
import org.yameida.worktool.utils.Views
import org.yameida.worktool.utils.WeworkRoomUtil
import org.yameida.worktool.utils.WeworkTextUtil
import com.github.yoojia.qrcode.qrcode.QRCodeDecoder
import com.blankj.utilcode.util.*
import java.lang.Exception
/**
* 全局操作类型 200 实现类
@@ -145,13 +148,76 @@ object WeworkOperationImpl {
selectList: List<String>?,
groupAnnouncement: String?
): Boolean {
if (!createGroup() || !groupRename(groupName) || !groupAddMember(selectList)
|| !groupChangeAnnouncement(groupAnnouncement)
) return false
backPress()
if (!WeworkRoomUtil.isGroupExists(groupName)) {
if (!createGroup() || !groupRename(groupName) || !groupAddMember(selectList)
|| !groupChangeAnnouncement(groupAnnouncement)
) return false
} else {
if (!groupRename(groupName) || !groupAddMember(selectList)
|| !groupChangeAnnouncement(groupAnnouncement)
) return false
}
getGroupQrcode(groupName)
return true
}
fun getGroupQrcode(groupName: String): Boolean {
if (WeworkRoomUtil.intoRoom(groupName) && WeworkRoomUtil.intoGroupManager()) {
val tvList = AccessibilityUtil.findAllOnceByClazz(getRoot(), Views.TextView)
tvList.forEachIndexed { index, tv ->
if (tv.text != null && tv.text.contains("微信用户创建")) {
if (index + 1 < tvList.size) {
val tvQr = tvList[index + 1]
AccessibilityUtil.performClick(tvQr)
}
}
}
AccessibilityUtil.findOneByText(getRoot(), "保存到相册")
val startTime = System.currentTimeMillis()
var currentTime = startTime
while (currentTime - startTime <= Constant.CHANGE_PAGE_INTERVAL * 5) {
AccessibilityUtil.findOnceByClazz(getRoot(), Views.ProgressBar) ?: break
sleep(Constant.POP_WINDOW_INTERVAL / 5)
currentTime = System.currentTimeMillis()
}
if (AccessibilityUtil.findTextAndClick(getRoot(), "保存到相册")) {
sleep(Constant.CHANGE_PAGE_INTERVAL)
val fileDirPath = "/storage/emulated/0/DCIM/WeixinWork"
val fileDir = FileUtils.getFileByPath(fileDirPath)
if (fileDir.isDirectory) {
for (file in fileDir.listFiles().filter { it.name.endsWith(".jpg") }) {
val fileTime = file.name.replace("mmexport", "")
.replace(".jpg", "")
LogUtils.v("fileTime: $fileTime")
if (fileTime.isNotBlank()) {
if (fileTime.toLong() > currentTime) {
LogUtils.d("找到最新保存二维码图片: $fileTime")
try {
val bitmap = ImageUtils.bytes2Bitmap(file.readBytes())
val mDecoder = QRCodeDecoder.Builder().build()
val qrcode = mDecoder.decode(bitmap)
LogUtils.e("group: $groupName qrcode: $qrcode")
val weworkMessageBean = WeworkMessageBean()
weworkMessageBean.type = WeworkMessageBean.GET_GROUP_QRCODE
weworkMessageBean.groupName = groupName
weworkMessageBean.qrcode = qrcode
WeworkController.weworkService.webSocketManager.send(
weworkMessageBean
)
return true
} catch (e: Exception) {
e.printStackTrace()
LogUtils.e(e)
}
}
}
}
}
}
}
return false
}
/**
* 进入群聊并修改群配置
* 群名称、群公告、拉人、踢人
@@ -284,12 +350,12 @@ object WeworkOperationImpl {
val node = AccessibilityUtil.scrollAndFindByText(getRoot(), "用过的小程序")
if (node != null) {
AccessibilityUtil.performClick(node)
sleep(Constant.CHANGE_PAGE_INTERVAL)
val textViewList = AccessibilityUtil.findAllByClazz(getRoot(), Views.TextView)
if (textViewList.size > 3) {
AccessibilityUtil.performClick(textViewList[2])
AccessibilityUtil.findTextInput(getRoot(), objectName)
sleep(2000)
AccessibilityUtil.findListOneAndClick(getRoot(), 1)
AccessibilityUtil.findOneByClazz(getRoot(), Views.RecyclerView)
sleep(2000)
//todo 转发小程序
return true

View File

@@ -1,11 +1,15 @@
package org.yameida.worktool.utils
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
@@ -15,6 +19,7 @@ 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
/**
* 1.查询类
@@ -639,4 +644,33 @@ object AccessibilityUtil {
}
}
/**
* Gesture手势实现点击(Android7+)
* 解决 clickable=false 无法点击问题
*/
@RequiresApi(api = Build.VERSION_CODES.N)
fun clickByNode(
service: AccessibilityService,
nodeInfo: AccessibilityNodeInfo
): Boolean {
val rect = Rect()
nodeInfo.getBoundsInScreen(rect)
val x: Int = (rect.left + rect.right) / 2
val y: Int = (rect.top + rect.bottom) / 2
val point = Point(x, y)
val builder = GestureDescription.Builder()
val path = Path()
path.moveTo(point.x.toFloat(), point.y.toFloat())
builder.addStroke(StrokeDescription(path, 0L, 100L))
val gesture = builder.build()
return service.dispatchGesture(gesture, object : GestureResultCallback() {
override fun onCompleted(gestureDescription: GestureDescription) {
LogUtils.e("click okk onCompleted")
}
override fun onCancelled(gestureDescription: GestureDescription) {
LogUtils.e("click okk onCancelled")
}
}, null)
}
}

View File

@@ -12,4 +12,5 @@ public class Views {
public static String GridView = "android.widget.GridView";
public static String RelativeLayout = "android.widget.RelativeLayout";
public static String LinearLayout = "android.widget.LinearLayout";
public static String ProgressBar = "android.widget.ProgressBar";
}

View File

@@ -77,21 +77,21 @@ public class WebSocketManager {
}
public void send(WeworkMessageListBean msg) {
send(msg, false);
send(msg, msg.getSocketType() == 2);
}
public void send(WeworkMessageListBean msg, boolean log) {
String json = GsonUtils.toJson(msg);
boolean suc = socket.send(json);
if (log)
LogUtils.v(url, json, (suc ? "通讯消息发送成功!" : "通讯消息发送失败!"));
else
LogUtils.e(url, json, (suc ? "通讯消息发送成功!" : "通讯消息发送失败!"));
boolean success = socket.send(json);
if (log && success)
LogUtils.d(url, json, "通讯消息发送成功!");
if (!success)
LogUtils.e(url, json, "通讯消息发送失败!");
}
public void send(String msg) {
boolean suc = socket.send(msg);
LogUtils.e(url, msg, (suc ? "通讯消息发送成功!" : "通讯消息发送失败!"));
boolean success = socket.send(msg);
LogUtils.e(url, msg, (success ? "通讯消息发送成功!" : "通讯消息发送失败!"));
}
public void close(int code, String reason) {

View File

@@ -100,14 +100,19 @@ object WeworkRoomUtil {
sleep(Constant.CHANGE_PAGE_INTERVAL)
val selectListView = findOneByClazz(getRoot(), Views.ListView)
val imageView = AccessibilityUtil.findOnceByClazz(selectListView, Views.ImageView)
AccessibilityUtil.performClick(imageView)
sleep(Constant.CHANGE_PAGE_INTERVAL)
return true
if (imageView != null) {
AccessibilityUtil.performClick(imageView)
sleep(Constant.CHANGE_PAGE_INTERVAL)
return true
} else {
LogUtils.e("未搜索到结果")
}
} else {
LogUtils.e("未找到搜索按钮")
}
} else {
LogUtils.e("未找到聊天列表")
}
LogUtils.e("未找到聊天列表")
return false
}
@@ -127,6 +132,7 @@ object WeworkRoomUtil {
if (textViewList.size >= 2) {
val multiButton = textViewList.lastOrNull()
AccessibilityUtil.performClick(multiButton)
sleep(Constant.CHANGE_PAGE_INTERVAL)
return true
} else {
LogUtils.e("未找到群管理按钮")
@@ -180,6 +186,13 @@ object WeworkRoomUtil {
return titleList
}
/**
* 群名是否存在
*/
fun isGroupExists(groupName: String): Boolean {
return intoRoom(groupName)
}
/**
* 是否是群聊
* 群右上角有两个按钮(快速会议按钮、更多按钮)