diff --git a/BACKEND_PROTOCOL.md b/BACKEND_PROTOCOL.md index e4b8090..30a7a1f 100644 --- a/BACKEND_PROTOCOL.md +++ b/BACKEND_PROTOCOL.md @@ -166,3 +166,133 @@ WorkTool 使用 WebSocket 协议与服务端进行长连接通信。 * 8: 链接 (TEXT_TYPE_LINK) * 9: 文件 (TEXT_TYPE_FILE) * 15: 引用回复 (TEXT_TYPE_REPLY) + +## 附录:自动通过好友请求 + +当 APP 开启"自动通过好友请求"开关后,有新好友添加成功时会主动上传好友信息到服务端。 + +### 上传时机 +* 自动通过好友请求成功后 +* 手动通过好友请求成功后 + +### 消息类型 +* **Type**: `502` (GET_FRIEND_INFO) + +### 数据结构 +```json +{ + "type": 502, + "friend": { + "name": "张三", // 好友昵称 + "newFriend": true, // 标记为新好友 + "wechatId": "zhangsan123", // 微信号 + "phone": "13800138000", // 手机号 + "corpName": "XX科技有限公司", // 企业名称 + "department": "技术部", // 部门 + "position": "工程师", // 职位 + "email": "zhangsan@example.com", // 邮箱 + "markName": "张工", // 备注名 + "gender": 1, // 性别: 1男 2女 0未知 + "leavingMsg": "请求添加好友时的留言" // 留言/验证消息 + } +} +``` + +### 服务端接收示例 +服务端只需监听 WebSocket 消息,当 `type=502` 且 `friend.newFriend=true` 时,表示有新好友添加成功。 + +--- + +## 附录:接收消息列表 (101) + +客户端监听到新消息时,会主动上传消息列表到服务端。 + +### 消息类型 +* **Type**: `101` (TYPE_RECEIVE_MESSAGE_LIST) + +### 数据结构(群聊) +```json +{ + "type": 101, + "roomType": 1, + "titleList": ["技术交流群"], + "messageList": [ + { + "sender": 0, + "textType": 1, + "nameList": ["张三"], + "itemMessageList": [ + {"feature": 2, "text": "你好,这是消息内容"} + ] + }, + { + "sender": 1, + "textType": 1, + "itemMessageList": [ + {"feature": 2, "text": "收到"} + ] + } + ] +} +``` + +### 数据结构(单聊) +```json +{ + "type": 101, + "roomType": 2, + "titleList": ["张三"], + "contact": { + "name": "张三", + "wechatId": "zhangsan123", + "phone": "13800138000", + "corpName": "XX科技有限公司", + "department": "技术部", + "position": "工程师", + "email": "zhangsan@example.com", + "markName": "张工" + }, + "messageList": [ + { + "sender": 0, + "textType": 1, + "itemMessageList": [ + {"feature": 2, "text": "你好"} + ] + } + ] +} +``` + +### 字段说明 + +| 字段 | 类型 | 说明 | +|------|------|------| +| type | int | 消息类型:101=接收的消息列表 | +| roomType | int | 房间类型:1=外部群, 2=外部联系人, 3=内部群, 4=内部联系人 | +| titleList | Array | 聊天对象名称(群名或好友名) | +| contact | Object | 联系人信息(仅单聊时有效) | +| messageList | Array | 消息列表 | + +### messageList 每条消息 + +| 字段 | 类型 | 说明 | +|------|------|------| +| sender | int | 发送者:0=其他人, 1=自己, 2=系统消息 | +| textType | int | 消息类型:1=文本, 2=图片, 3=语音, 5=视频, 8=链接, 9=文件 | +| nameList | Array | 发送者名称列表(群聊时有效) | +| itemMessageList | Array | 消息内容列表 | + +### contact 联系人信息 + +| 字段 | 类型 | 说明 | +|------|------|------| +| name | String | 好友昵称 | +| wechatId | String | 微信号 | +| phone | String | 手机号 | +| corpName | String | 企业名称 | +| department | String | 部门 | +| position | String | 职位 | +| email | String | 邮箱 | +| markName | String | 备注名 | +| gender | int | 性别:1=男, 2=女, 0=未知 | diff --git a/app/src/main/java/org/yameida/worktool/model/WeworkMessageBean.java b/app/src/main/java/org/yameida/worktool/model/WeworkMessageBean.java index e0eef54..2f3e238 100644 --- a/app/src/main/java/org/yameida/worktool/model/WeworkMessageBean.java +++ b/app/src/main/java/org/yameida/worktool/model/WeworkMessageBean.java @@ -246,6 +246,9 @@ public class WeworkMessageBean { //添加好友 public Friend friend; + //联系人信息(单聊时上传) + public Friend contact; + //网络文件 public String fileBase64; public String fileUrl; @@ -376,18 +379,32 @@ public class WeworkMessageBean { public Boolean newFriend; //留言 public String leavingMsg; + //微信号 + public String wechatId; + //企业名称 + public String corpName; + //部门 + public String department; + //职位 + public String position; + //邮箱 + public String email; + //性别: 1男 2女 0未知 + public Integer gender; + //头像URL + public String avatarUrl; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Friend friend = (Friend) o; - return Objects.equals(phone, friend.phone) && Objects.equals(name, friend.name) && Objects.equals(markName, friend.markName) && Objects.equals(markCorp, friend.markCorp) && Objects.equals(markExtra, friend.markExtra) && Objects.equals(tagList, friend.tagList) && Objects.equals(newFriend, friend.newFriend) && Objects.equals(leavingMsg, friend.leavingMsg); + return Objects.equals(phone, friend.phone) && Objects.equals(name, friend.name) && Objects.equals(markName, friend.markName) && Objects.equals(markCorp, friend.markCorp) && Objects.equals(markExtra, friend.markExtra) && Objects.equals(tagList, friend.tagList) && Objects.equals(newFriend, friend.newFriend) && Objects.equals(leavingMsg, friend.leavingMsg) && Objects.equals(wechatId, friend.wechatId) && Objects.equals(corpName, friend.corpName) && Objects.equals(department, friend.department) && Objects.equals(position, friend.position) && Objects.equals(email, friend.email) && Objects.equals(gender, friend.gender) && Objects.equals(avatarUrl, friend.avatarUrl); } @Override public int hashCode() { - return Objects.hash(phone, name, markName, markCorp, markExtra, tagList, newFriend, leavingMsg); + return Objects.hash(phone, name, markName, markCorp, markExtra, tagList, newFriend, leavingMsg, wechatId, corpName, department, position, email, gender, avatarUrl); } @Override @@ -401,6 +418,13 @@ public class WeworkMessageBean { ", tagList=" + tagList + ", newFriend=" + newFriend + ", leavingMsg='" + leavingMsg + '\'' + + ", wechatId='" + wechatId + '\'' + + ", corpName='" + corpName + '\'' + + ", department='" + department + '\'' + + ", position='" + position + '\'' + + ", email='" + email + '\'' + + ", gender=" + gender + + ", avatarUrl='" + avatarUrl + '\'' + '}'; } } diff --git a/app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt b/app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt index 0df5a9a..3b6a4dd 100644 --- a/app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt +++ b/app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt @@ -280,16 +280,26 @@ object WeworkLoopImpl { } messageList.removeIf { it.textType == WeworkMessageBean.TEXT_TYPE_IMAGE } } - WeworkController.weworkService.webSocketManager.send( - WeworkMessageBean( - null, null, - WeworkMessageBean.TYPE_RECEIVE_MESSAGE_LIST, - roomType, - titleList, - messageList, - null - ) + // 获取单聊联系人详细信息 + var contactInfo: WeworkMessageBean.Friend? = null + if (roomType == WeworkMessageBean.ROOM_TYPE_EXTERNAL_CONTACT + || roomType == WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT) { + val friendName = titleList.firstOrNull() ?: "" + if (friendName.isNotEmpty()) { + contactInfo = getChatContactInfo(friendName) + } + } + + val messageBean = WeworkMessageBean( + null, null, + WeworkMessageBean.TYPE_RECEIVE_MESSAGE_LIST, + roomType, + titleList, + messageList, + null ) + messageBean.contact = contactInfo + WeworkController.weworkService.webSocketManager.send(messageBean) //推测是否回复并在房间等待指令 if (needInfer) { val lastMessage = messageList.lastOrNull() @@ -415,7 +425,8 @@ object WeworkLoopImpl { val filter = textViewList.filter { it.text != null && it.text.toString() != "微信" } if (filter.isNotEmpty()) { val tvNick = filter[0] - LogUtils.d("好友请求: " + tvNick.text) + val friendName = tvNick.text.toString() + LogUtils.d("好友请求: $friendName") //设置标签 if (AccessibilityUtil.findTextAndClick(getRoot(), "标签")) { WeworkOperationImpl.setFriendTags(arrayListOf("worktool自动通过")) @@ -429,14 +440,13 @@ object WeworkLoopImpl { if (textNode?.text?.toString() == "添加请求已过期,添加失败") { LogUtils.d("添加好友失败") } else { + // 获取完整的好友信息 + val friendInfo = getFriendDetailInfo(friendName) val weworkMessageBean = WeworkMessageBean() weworkMessageBean.type = WeworkMessageBean.GET_FRIEND_INFO - weworkMessageBean.friend = WeworkMessageBean.Friend().apply { - name = tvNick.text.toString() - newFriend = true - } + weworkMessageBean.friend = friendInfo WeworkController.weworkService.webSocketManager.send(weworkMessageBean) - nameList.add(tvNick.text.toString()) + nameList.add(friendName) } //回到上一页 var retry = 5 @@ -451,6 +461,107 @@ object WeworkLoopImpl { return nameList } + /** + * 获取好友详细信息 + * 通过好友后进入详情页抓取完整信息 + */ + private fun getFriendDetailInfo(friendName: String): WeworkMessageBean.Friend { + val friend = WeworkMessageBean.Friend().apply { + name = friendName + newFriend = true + } + try { + // 点击发消息进入聊天页面 + val sendMsgNode = AccessibilityUtil.findOneByText(getRoot(), "发消息", exact = true) + if (sendMsgNode != null) { + AccessibilityUtil.performClick(sendMsgNode) + sleep(Constant.CHANGE_PAGE_INTERVAL) + } + + // 进入好友详情页 + if (WeworkRoomUtil.intoFriendDetail()) { + sleep(Constant.CHANGE_PAGE_INTERVAL) + // 解析详情页信息 + val listView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) + if (listView != null) { + // 遍历列表获取各字段信息 + for (i in 0 until listView.childCount) { + val item = listView.getChild(i) ?: continue + val textViews = AccessibilityUtil.findAllOnceByClazz(item, Views.TextView) + if (textViews.size >= 2) { + val label = textViews[0].text?.toString() ?: "" + val value = textViews[1].text?.toString() ?: "" + when { + label.contains("微信号") || label.contains("微信ID") -> friend.wechatId = value + label.contains("手机") -> friend.phone = value + label.contains("企业") -> friend.corpName = value + label.contains("部门") -> friend.department = value + label.contains("职位") || label.contains("职务") -> friend.position = value + label.contains("邮箱") -> friend.email = value + label.contains("备注") -> friend.markName = value + } + LogUtils.d("好友详情 - $label: $value") + } + } + } + // 尝试获取头像(如果有) + val avatarView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ImageView) + if (avatarView != null) { + // 头像通常没有直接URL,这里留空或可通过其他方式获取 + // friend.avatarUrl = "" + } + // 返回聊天页面 + backPress() + sleep(Constant.POP_WINDOW_INTERVAL) + } + } catch (e: Exception) { + LogUtils.e("获取好友详情失败: ${e.message}") + } + LogUtils.d("好友信息: $friend") + return friend + } + + /** + * 获取单聊联系人详细信息 + */ + private fun getChatContactInfo(friendName: String): WeworkMessageBean.Friend? { + val friend = WeworkMessageBean.Friend().apply { + name = friendName + } + try { + // 进入好友详情页 + if (WeworkRoomUtil.intoFriendDetail()) { + sleep(Constant.CHANGE_PAGE_INTERVAL) + // 解析详情页信息 + val listView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) + if (listView != null) { + for (i in 0 until listView.childCount) { + val item = listView.getChild(i) ?: continue + val textViews = AccessibilityUtil.findAllOnceByClazz(item, Views.TextView) + if (textViews.size >= 2) { + val label = textViews[0].text?.toString() ?: "" + val value = textViews[1].text?.toString() ?: "" + when { + label.contains("微信号") || label.contains("微信ID") -> friend.wechatId = value + label.contains("手机") -> friend.phone = value + label.contains("企业") -> friend.corpName = value + label.contains("部门") -> friend.department = value + label.contains("职位") || label.contains("职务") -> friend.position = value + label.contains("邮箱") -> friend.email = value + label.contains("备注") -> friend.markName = value + } + } + } + } + backPress() + sleep(Constant.POP_WINDOW_INTERVAL) + } + } catch (e: Exception) { + LogUtils.e("获取联系人详情失败: ${e.message}") + } + return friend + } + /** * 读取聊天列表 */ diff --git a/app/src/main/res/layout/activity_accessibility_guide.xml b/app/src/main/res/layout/activity_accessibility_guide.xml index a1c17a2..c2aaeb1 100644 --- a/app/src/main/res/layout/activity_accessibility_guide.xml +++ b/app/src/main/res/layout/activity_accessibility_guide.xml @@ -23,7 +23,7 @@ android:layout_marginTop="18dp" android:layout_marginStart="18dp" android:layout_marginEnd="18dp" - android:text="请开启无障碍服务,以使用Awin WorkTool主功能" + android:text="请开启无障碍服务,以使用AwinWorkTool主功能" android:textColor="@color/color_333333" android:textSize="14sp" /> @@ -150,7 +150,7 @@ android:layout_gravity="center_vertical" android:textSize="17sp" android:textColor="@color/qmui_config_color_black" - android:text="使用Awin WorkTool" + android:text="使用AwinWorkTool" /> diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 0c66a70..229f569 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -73,7 +73,7 @@ android:layout_centerHorizontal="true" android:layout_marginTop="4dp" android:gravity="center" - android:text="Awin WorkTool 极致的自动化体验" + android:text="AwinWorkTool 智能自动化体验" android:textColor="#9affffff" android:textSize="10sp" tools:ignore="SmallSp" /> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 438294e..5da3342 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,8 +1,47 @@ + + #00BCD4 #0097A7 - #E0F7FA + #00BCD4 + #00BCD4 - #96ffffff + + #00BCD4 + #0097A7 + #26C6DA + #00E5FF + + + #FFFFFF + #F5F9FA + #FFFFFF + + + #212121 + #424242 + #757575 + #FFFFFF + #C8C8C8 + + + #00BCD4 + #00838F + #00BCD4 + #E0E0E0 + + + #FAFAFA + #F5F5F5 + #E0E0E0 + #00000000 + #E6E6E6 + #D9D9D9 + #000000 + + + #4CAF50 + #FF9800 + #F44336 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4479448..1fcd906 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,7 +1,7 @@ - Awin WorkTool + AwinWorkTool - Awin WorkTool + AwinWorkTool 如果需要使用或停止该应用,应用需打开 无障碍服务 进行权限设置