Merge branch 'master' into modify_group_member_info

This commit is contained in:
gallonyin
2023-03-27 21:22:26 +08:00
committed by GitHub
11 changed files with 205 additions and 22 deletions

View File

@@ -8,12 +8,15 @@
**发送消息**
<img src="https://github.com/gallonyin/worktool/blob/master/images/send_message.gif" height="500" width="280">
注:动图为机器人自动运行
更多演示看这里
https://www.apifox.cn/apidoc/project-1035094/doc-840833
<img src="https://github.com/gallonyin/worktool/blob/master/images/chatgpt.png" height="500" width="360">
机器人集成ChatGPT效果
## 兼容版本(重要)
经过测试验证的版本:企业微信 4.0.2 至 4.1.0 (1月10日上架)、企业微信政务版

View File

@@ -4,7 +4,7 @@ import com.blankj.utilcode.util.SPUtils
object Constant {
val AVAILABLE_VERSION = arrayListOf("4.0.2", "4.0.6", "4.0.8", "4.0.10", "4.0.12", "4.0.16", "4.0.18", "4.0.19", "4.0.20", "4.1.0")
val AVAILABLE_VERSION = arrayListOf("4.0.2", "4.0.6", "4.0.8", "4.0.10", "4.0.12", "4.0.16", "4.0.18", "4.0.19", "4.0.20", "4.1.0", "4.1.2")
const val PACKAGE_NAMES = "com.tencent.wework"
const val WEWORK_NOTIFY = "wework_notify"
const val LONG_INTERVAL = 5000L

View File

@@ -51,7 +51,7 @@ class MyApplication : Application() {
if (SPUtils.getInstance().getString("uminit", "1") == "1") {
UMConfigure.init(this, key, channel, UMConfigure.DEVICE_TYPE_PHONE, "")
}
TalkingDataSDK.init(this, "80E9C84E39904DAFB28562910FF7C86C", "worktool_master", Constant.robotId);
TalkingDataSDK.init(this, "80E9C84E39904DAFB28562910FF7C86C", "worktool_master", Constant.robotId)
//初始化企业微信sdk
IWWAPIUtil.init(this)
//初始化自动更新

View File

@@ -24,6 +24,8 @@ import org.yameida.worktool.utils.envcheck.CheckRoot
class ListenActivity : AppCompatActivity() {
var riskRetry: Int = 0
companion object {
/**
* @param type 0=游客登录
@@ -132,8 +134,17 @@ class ListenActivity : AppCompatActivity() {
ToastUtils.showLong("请先填写并保存链接号~")
} else if (!PermissionHelper.isAccessibilitySettingOn()) {
if (SPUtils.getInstance().getBoolean("risk", false)) {
if (riskRetry > 10) {
ToastUtils.showLong("再点${20 - riskRetry}次 允许本次运行")
} else {
ToastUtils.showLong("新号请勿使用模拟器/云手机!")
}
if (++riskRetry > 20) {
SPUtils.getInstance().put("risk", false)
startActivity(Intent(this, AccessibilityGuideActivity::class.java))
ToastUtils.showLong("风险提示:临时允许本次运行")
}
sw_accessibility.isChecked = false
ToastUtils.showLong("新号请勿使用模拟器/云手机!")
} else {
startActivity(Intent(this, AccessibilityGuideActivity::class.java))
}

View File

@@ -39,6 +39,7 @@ public class WeworkMessageBean {
* 切换企业 SWITCH_CORP
* 推送链接 PUSH_LINK
* 修改群成员信息 MODIFY_GROUP_MEMBER_INFO
* 撤回消息 RECALL_MESSAGE
* <p>
* 非操作类型 300
* 机器人普通日志记录 ROBOT_LOG
@@ -83,6 +84,7 @@ public class WeworkMessageBean {
public static final int SWITCH_CORP = 223;
public static final int PUSH_LINK = 224;
public static final int MODIFY_GROUP_MEMBER_INFO = 225;
public static final int RECALL_MESSAGE = 226;
public static final int ROBOT_LOG = 301;
public static final int ROBOT_ERROR_LOG = 302;
@@ -298,7 +300,6 @@ public class WeworkMessageBean {
//我的信息
public static class MyInfo {
//{姓名=企微RPA机器人, 工作签名=添加工作签名…, 手机=17326101105, 别名=企微RPA机器人, 对外信息显示=企微RPA机器人擎盾数据, 职务=企微RPA机器人, 所在企业=TEST 擎盾数据, 性别=男}
public String name;
public String alias;
public String gender;
@@ -362,12 +363,12 @@ public class WeworkMessageBean {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
WeworkMessageBean that = (WeworkMessageBean) o;
return Objects.equals(messageId, that.messageId) && Objects.equals(titleList, that.titleList) && Objects.equals(messageList, that.messageList) && Objects.equals(log, that.log) && Objects.equals(roomType, that.roomType) && Objects.equals(receivedName, that.receivedName) && Objects.equals(receivedContent, that.receivedContent) && Objects.equals(at, that.at) && Objects.equals(atList, that.atList) && Objects.equals(originalContent, that.originalContent) && Objects.equals(nameList, that.nameList) && Objects.equals(extraText, that.extraText) && Objects.equals(textType, that.textType) && Objects.equals(groupName, that.groupName) && Objects.equals(groupOwner, that.groupOwner) && Objects.equals(selectList, that.selectList) && Objects.equals(groupNumber, that.groupNumber) && Objects.equals(groupAnnouncement, that.groupAnnouncement) && Objects.equals(groupRemark, that.groupRemark) && Objects.equals(groupTemplate, that.groupTemplate) && Objects.equals(newGroupName, that.newGroupName) && Objects.equals(newGroupAnnouncement, that.newGroupAnnouncement) && Objects.equals(removeList, that.removeList) && Objects.equals(showMessageHistory, that.showMessageHistory) && Objects.equals(myInfo, that.myInfo) && Objects.equals(objectName, that.objectName) && Objects.equals(qrcode, that.qrcode) && Objects.equals(friend, that.friend) && Objects.equals(fileBase64, that.fileBase64) && Objects.equals(fileUrl, that.fileUrl) && Objects.equals(fileType, that.fileType) && Objects.equals(type, that.type);
return Objects.equals(titleList, that.titleList) && Objects.equals(messageList, that.messageList) && Objects.equals(log, that.log) && Objects.equals(roomType, that.roomType) && Objects.equals(receivedName, that.receivedName) && Objects.equals(receivedContent, that.receivedContent) && Objects.equals(at, that.at) && Objects.equals(atList, that.atList) && Objects.equals(originalContent, that.originalContent) && Objects.equals(nameList, that.nameList) && Objects.equals(extraText, that.extraText) && Objects.equals(textType, that.textType) && Objects.equals(groupName, that.groupName) && Objects.equals(groupOwner, that.groupOwner) && Objects.equals(selectList, that.selectList) && Objects.equals(groupNumber, that.groupNumber) && Objects.equals(groupAnnouncement, that.groupAnnouncement) && Objects.equals(groupRemark, that.groupRemark) && Objects.equals(groupTemplate, that.groupTemplate) && Objects.equals(newGroupName, that.newGroupName) && Objects.equals(newGroupAnnouncement, that.newGroupAnnouncement) && Objects.equals(removeList, that.removeList) && Objects.equals(showMessageHistory, that.showMessageHistory) && Objects.equals(myInfo, that.myInfo) && Objects.equals(objectName, that.objectName) && Objects.equals(qrcode, that.qrcode) && Objects.equals(friend, that.friend) && Objects.equals(fileBase64, that.fileBase64) && Objects.equals(fileUrl, that.fileUrl) && Objects.equals(fileType, that.fileType) && Objects.equals(type, that.type);
}
@Override
public int hashCode() {
return Objects.hash(messageId, titleList, messageList, log, roomType, receivedName, receivedContent, at, atList, originalContent, nameList, extraText, textType, groupName, groupOwner, selectList, groupNumber, groupAnnouncement, groupRemark, groupTemplate, newGroupName, newGroupAnnouncement, removeList, showMessageHistory, myInfo, objectName, qrcode, friend, fileBase64, fileUrl, fileType, type);
return Objects.hash(titleList, messageList, log, roomType, receivedName, receivedContent, at, atList, originalContent, nameList, extraText, textType, groupName, groupOwner, selectList, groupNumber, groupAnnouncement, groupRemark, groupTemplate, newGroupName, newGroupAnnouncement, removeList, showMessageHistory, myInfo, objectName, qrcode, friend, fileBase64, fileUrl, fileType, type);
}
@Override

View File

@@ -99,8 +99,9 @@ object MyLooper {
} else {
WeworkController.mainLoopRunning = false
LogUtils.v("加入指令到执行队列", if (message.fileBase64.isNullOrEmpty()) GsonUtils.toJson(message) else message.type)
getInstance().removeMessages(message.type * message.hashCode() + (System.currentTimeMillis() / 10000).toInt())
getInstance().sendMessage(Message.obtain().apply {
what = message.type * message.hashCode()
what = message.type * message.hashCode() + (System.currentTimeMillis() / 10000).toInt()
obj = message.apply {
messageId = messageList.messageId
apiSend = messageList.apiSend
@@ -165,6 +166,9 @@ object MyLooper {
WeworkMessageBean.PUSH_LINK -> {
WeworkController.pushLink(message)
}
WeworkMessageBean.RECALL_MESSAGE -> {
WeworkController.recallMessage(message)
}
WeworkMessageBean.DISMISS_GROUP -> {
WeworkController.dismissGroup(message)
}

View File

@@ -379,6 +379,25 @@ object WeworkController {
)
}
/**
* 撤回消息
* @see WeworkMessageBean.RECALL_MESSAGE
* @param message#titleList 房间名称
* @param message#originalContent 原始消息的内容
* @param message#textType 原始消息的消息类型
* @see WeworkMessageBean.TEXT_TYPE
*/
@RequestMapping
fun recallMessage(message: WeworkMessageBean): Boolean {
LogUtils.d("recallMessage(): ${message.titleList} ${message.originalContent} ${message.textType}")
return WeworkOperationImpl.recallMessage(
message,
message.titleList,
message.originalContent,
message.textType
)
}
/**
* 按手机号添加好友
* @see WeworkMessageBean.ADD_FRIEND_BY_PHONE

View File

@@ -285,6 +285,13 @@ object WeworkGetImpl {
}
}
}
val tvCountFlag = AccessibilityUtil.findOnceByText(getRoot(), "查看全部群成员", exact = true)
val tvCount = AccessibilityUtil.findBackNode(tvCountFlag)
if (tvCount != null && tvCount.text != null) {
LogUtils.d("群成员: " + tvCount.text)
val count = tvCount.text.toString().replace("", "")
weworkMessageBean.groupNumber = count.toIntOrNull()
}
val gridView = AccessibilityUtil.findOneByClazz(getRoot(), Views.GridView)
if (gridView != null && gridView.childCount >= 2) {
val tvOwnerName = AccessibilityUtil.findOnceByClazz(gridView.getChild(0), Views.TextView)
@@ -292,13 +299,17 @@ object WeworkGetImpl {
LogUtils.d("群主: " + tvOwnerName.text)
weworkMessageBean.groupOwner = tvOwnerName.text.toString()
}
}
val tvCountFlag = AccessibilityUtil.findOnceByText(getRoot(), "查看全部群成员", exact = true)
val tvCount = AccessibilityUtil.findBackNode(tvCountFlag)
if (tvCount != null && tvCount.text != null) {
LogUtils.d("群成员: " + tvCount.text)
val count = tvCount.text.toString().replace("", "")
weworkMessageBean.groupNumber = count.toIntOrNull()
if (!saveMembers && weworkMessageBean.groupNumber ?: 0 <= 8) {
val set = linkedSetOf<String>()
for (i in 0 until gridView.childCount) {
val item = gridView.getChild(i)
val name = AccessibilityUtil.findOnceByClazz(item, Views.TextView)?.text?.toString()
?: continue
set.add(name)
}
LogUtils.d("群成员: ${set.joinToString()}")
weworkMessageBean.nameList = set.toList()
}
}
val tvAnnouncementFlag = AccessibilityUtil.findOnceByText(getRoot(), "群公告", exact = true)
val tvAnnouncement = AccessibilityUtil.findBackNode(tvAnnouncementFlag)

View File

@@ -764,6 +764,46 @@ object WeworkOperationImpl {
}
}
/**
* 撤回消息
* @see WeworkMessageBean.RECALL_MESSAGE
* @param titleList 房间名称
* @param originalContent 原始消息的内容
* @param textType 原始消息的消息类型
* @see WeworkMessageBean.TEXT_TYPE
*/
fun recallMessage(
message: WeworkMessageBean,
titleList: List<String>,
originalContent: String,
textType: Int
): Boolean {
val startTime = System.currentTimeMillis()
for (title in titleList) {
if (WeworkRoomUtil.intoRoom(title)) {
if (WeworkTextUtil.longClickMyMessageItem(
//聊天消息列表 1ListView 0RecycleView xViewGroup
AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView),
textType,
originalContent,
"撤回"
)
) {
LogUtils.d("撤回成功")
uploadCommandResult(message, ExecCallbackBean.SUCCESS, "", startTime, titleList, listOf())
return true
} else {
LogUtils.e("撤回失败 未找到目标消息")
uploadCommandResult(message, ExecCallbackBean.ERROR_TARGET, "撤回失败 未找到目标消息", startTime, listOf(), titleList)
return false
}
}
}
LogUtils.e("撤回失败 未找到房间")
uploadCommandResult(message, ExecCallbackBean.ERROR_TARGET, "撤回失败 未找到房间", startTime, listOf(), titleList)
return false
}
/**
* 手机号添加好友或修改好友信息
* @see WeworkMessageBean.ADD_FRIEND_BY_PHONE
@@ -1787,8 +1827,20 @@ object WeworkOperationImpl {
AccessibilityUtil.performClickWithSon(AccessibilityUtil.findFrontNode(voiceFlag))
}
var atFailed = false
val atList = if (!at.isNullOrEmpty()) listOf(at) else atList
val atList = if (!at.isNullOrEmpty()) arrayListOf(at) else atList?.toMutableList()
if (!atList.isNullOrEmpty() && (roomType == WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP || roomType == WeworkMessageBean.ROOM_TYPE_EXTERNAL_GROUP)) {
val nameList = arrayListOf<String>()
if (atList.count { it.startsWith("#regex#") } > 0 && WeworkRoomUtil.intoGroupManager()) {
val groupInfo = WeworkGetImpl.getGroupInfoDetail(saveAddress = false, saveMembers = false)
nameList.addAll(groupInfo.nameList)
val regex = atList.first { it.startsWith("#regex#") }.split("#regex#").last().toRegex()
for (name in groupInfo.nameList) {
if (name != Constant.myName && name.matches(regex)) {
atList.add(name)
}
}
}
atList.removeIf { it.startsWith("#regex#") }
atList.forEachIndexed { index, at ->
if (index == 0) {
AccessibilityUtil.findTextInput(getRoot(), "@")

View File

@@ -176,14 +176,11 @@ object WeworkTextUtil {
------------------ depth: 6 className: android.view.View
------------------ depth: 6 className: android.widget.RelativeLayout
--------------------- depth: 7 className: android.widget.TextView
--------------------- depth: 7 text: 企微RPA机器人
--------------------- depth: 7 className: android.widget.LinearLayout
------------------------ depth: 8 className: android.widget.RelativeLayout
--------------------------- depth: 9 className: android.widget.RelativeLayout
------------------------------ depth: 10 className: android.widget.TextView
------------------------------ depth: 10 text: 新公告
--------------- depth: 5 className: android.widget.TextView
--------------- depth: 5 text: 111
------------------------------总结------------------------------
图片 0tv 1iv (图片)
@@ -283,6 +280,15 @@ object WeworkTextUtil {
LogUtils.v("textType: $textType")
return textType
}
} else if (Views.ImageView.equals(relativeLayoutItem.getChild(1).className)) {
LogUtils.v("头像在右边 本条消息发送者为自己")
var textType = WeworkMessageBean.TEXT_TYPE_UNKNOWN
val subLayout = relativeLayoutItem.getChild(0)
if (subLayout.childCount > 0) {
textType = WeworkTextUtil.getTextType(subLayout)
LogUtils.v("textType: $textType")
return textType
}
}
}
return WeworkMessageBean.TEXT_TYPE_UNKNOWN
@@ -329,8 +335,9 @@ object WeworkTextUtil {
}
/**
* 群聊 长按消息条目
* 长按消息条目
* 复制、转发、回复、收藏、置顶、多选、日程、待办、翻译、删除
* 适用左侧发言者
* @param node 消息列表节点
* @param replyTextType 带回复消息类型
* @param replyNick 待回复人姓名
@@ -403,6 +410,50 @@ object WeworkTextUtil {
return false
}
/**
* 长按消息条目
* 复制、转发、回复、收藏、置顶、多选、日程、待办、翻译、删除、撤回
* 适用自己发言者
* @param node 消息列表节点
* @param replyTextType 带回复消息类型
* @param replyContent 待回复内容
* @param key 复制、转发、回复、收藏、多选
* @return true 进行了长按 否则 false
*/
fun longClickMyMessageItem(
node: AccessibilityNodeInfo?,
replyTextType: Int,
replyContent: String,
key: String
): Boolean {
if (node == null) return false
for (i in 0 until node.childCount) {
val item = node.getChild(node.childCount - 1 - i) ?: continue
val frontNode = getMyMessageListNode(item)
if (frontNode != null) {
val textType = getTextTypeFromItem(item)
if (replyTextType == WeworkMessageBean.TEXT_TYPE_UNKNOWN || replyTextType == textType) {
if (replyTextType == WeworkMessageBean.TEXT_TYPE_IMAGE) {
return longClickMyMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT, key)
}
if ((replyTextType == WeworkMessageBean.TEXT_TYPE_FILE || replyTextType == WeworkMessageBean.TEXT_TYPE_VIDEO)
&& replyContent.contains("###")) {
val replyContentList = replyContent.split("###")
if (AccessibilityUtil.findOnceByText(frontNode, replyContentList[0]) != null
&& AccessibilityUtil.findOnceByText(frontNode, replyContentList[1]) != null) {
return longClickMyMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP, key)
}
}
val textNode = AccessibilityUtil.findOnceByText(frontNode, replyContent, exact = true)
if (textNode != null && replyContent.isNotEmpty()) {
return longClickMyMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT, key)
}
}
}
}
return false
}
private fun longClickMessageItem(item: AccessibilityNodeInfo, roomType: Int, key: String): Boolean {
val backNode = getMessageListNode(item, roomType)
AccessibilityUtil.performLongClickWithSon(backNode)
@@ -418,8 +469,26 @@ object WeworkTextUtil {
return false
}
private fun longClickMyMessageItem(item: AccessibilityNodeInfo, roomType: Int, key: String): Boolean {
val frontNode = getMyMessageListNode(item)
AccessibilityUtil.performLongClickWithSon(frontNode)
sleep(Constant.POP_WINDOW_INTERVAL)
val optionRvList = findAllByClazz(getRoot(), Views.RecyclerView, Views.ViewGroup)
for (optionRv in optionRvList) {
val keyTv = AccessibilityUtil.findOnceByText(optionRv, key, exact = true)
if (keyTv != null) {
AccessibilityUtil.performClick(keyTv)
if (AccessibilityExtraUtil.loadingPage("CustomDialog", timeout = Constant.POP_WINDOW_INTERVAL)) {
AccessibilityUtil.findTextAndClick(getRoot(), "确定", exact = true)
}
return true
}
}
return false
}
/**
* 群聊 提取消息主体框节点(昵称下面的气泡框)
* 提取消息主体框节点(昵称下面的气泡框)
* 适用于左侧发言者
* @param item 消息item节点
*/
@@ -437,4 +506,17 @@ object WeworkTextUtil {
}
return null
}
/**
* 提取消息主体框节点(昵称下面的气泡框)
* 适用于自己发言者
* @param item 消息item节点
*/
private fun getMyMessageListNode(item: AccessibilityNodeInfo): AccessibilityNodeInfo? {
val node = AccessibilityUtil.findAllOnceByClazz(item, Views.ImageView).lastOrNull()
if (node?.parent?.getChild(0) != node) {
return AccessibilityUtil.findFrontNode(node)
}
return null
}
}

BIN
images/chatgpt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB