diff --git a/BACKEND_PROTOCOL.md b/BACKEND_PROTOCOL.md new file mode 100644 index 0000000..e4b8090 --- /dev/null +++ b/BACKEND_PROTOCOL.md @@ -0,0 +1,168 @@ +# WorkTool 后端通信协议文档 + +本文档详细描述了 WorkTool 机器人(客户端)与后台服务器(服务端)之间的通信协议。 + +## 1. 连接方式 + +WorkTool 使用 WebSocket 协议与服务端进行长连接通信。 + +* **WebSocket URL**: `ws://<服务器IP>:<端口>/webserver/wework/<链接号>` + * `<链接号>` (robotId): 机器人的唯一标识,由用户在 APP 端设置。 +* **通信格式**: JSON +* **心跳机制**: 客户端每 5 秒发送一次心跳包 `{"type": 11}`,服务端可忽略或回复。 + +## 2. 数据包结构 + +所有的通信数据(发送和接收)都包裹在 `WeworkMessageListBean` 结构中。 + +### 2.1 外层包装 (WeworkMessageListBean) + +服务端发送给客户端的 JSON 必须符合以下结构: + +```json +{ + "socketType": 2, // 消息类型:2 表示下发指令列表 + "list": [ // 指令列表,可包含多个指令 + { + "type": 203, // 具体指令类型 (例如 203=发送消息) + "messageId": "uuid", // 消息ID (可选,用于回调对应) + ... // 指令参数 (详见下文) + } + ], + "encryptType": 0 // 加密类型:0=不加密, 1=AES加密 (默认0) +} +``` + +### 2.2 消息确认 + +当客户端收到服务端消息后,会回复一个确认包(通常服务端不需要处理): +```json +{ + "socketType": 1, // SOCKET_TYPE_MESSAGE_CONFIRM + "messageId": "..." // 收到的消息ID +} +``` + +## 3. 常用指令详解 + +以下列出常用的指令及其 JSON 参数示例。参数放在 `list` 数组的对象中。 + +### 3.1 发送文本消息 (SEND_MESSAGE) +* **Type**: `203` +* **说明**: 在指定房间(群或好友)发送文本。 + +```json +{ + "type": 203, + "titleList": ["张三", "技术交流群"], // 接收者名称列表 (群名或好友名) + "receivedContent": "你好,这是测试消息", // 消息内容 + "at": "李四" // (可选) 需要 @ 的人昵称,仅在群聊有效 +} +``` + +### 3.2 转发消息 (RELAY_MESSAGE) +* **Type**: `205` +* **说明**: 将某人的消息转发给其他人。 + +```json +{ + "type": 205, + "titleList": ["来源群名"], // 消息来源房间名 + "receivedName": "张三", // 消息发送者昵称 + "originalContent": "原消息内容", // 用于匹配消息内容 + "textType": 1, // 消息类型 (1=文本, 2=图片, 详见附录) + "nameList": ["目标群1", "李四"], // 转发目标列表 + "extraText": "这是转发的消息" // (可选) 转发时的附加留言 +} +``` + +### 3.3 推送文件/图片 (PUSH_FILE) +* **Type**: `218` +* **说明**: 发送网络文件或图片。 + +```json +{ + "type": 218, + "titleList": ["张三"], + "objectName": "文件名.jpg", // 保存的文件名 + "fileUrl": "http://example.com/a.jpg", // 文件下载地址 + "fileType": "image", // 文件类型 (image, video, file) + "extraText": "请查收图片" // (可选) 附加留言 +} +``` + +### 3.4 推送链接 (PUSH_LINK) +* **Type**: `224` +* **说明**: 发送卡片链接。 + +```json +{ + "type": 224, + "titleList": ["张三"], + "objectName": "百度一下", // 链接标题 + "receivedContent": "你就知道", // 链接描述/副标题 + "originalContent": "https://www.baidu.com", // 跳转链接 + "fileUrl": "http://example.com/icon.png" // 链接图标地址 +} +``` + +### 3.5 获取最近聊天列表 (GET_RECENT_LIST) +* **Type**: `505` +* **说明**: 获取消息列表首页的最近聊天记录。 + +```json +{ + "type": 505 +} +``` + +### 3.6 获取群信息 (GET_GROUP_INFO) +* **Type**: `501` +* **说明**: 获取指定群的成员列表等信息。 + +```json +{ + "type": 501, + "selectList": ["技术交流群"] // 要查询的群名列表 +} +``` + +## 4. 执行结果回调 + +当机器人执行完指令后,会向服务端发送执行结果。 + +* **SocketType**: `3` (SOCKET_TYPE_RAW_CONFIRM) +* **数据结构**: + +```json +{ + "socketType": 3, + "messageId": "原始指令的messageId", + "list": [ + { + "errorCode": 0, // 0 表示成功,非 0 表示失败 + "errorReason": "", // 失败原因 + "successList": ["张三"], // 执行成功的对象列表 + "failList": [], // 执行失败的对象列表 + "rawMsg": "{...}", // 原始指令 JSON + "runTime": 1670000000000, // 开始执行时间戳 + "timeCost": 1.5 // 耗时 (秒) + } + ] +} +``` + +### 常见错误码 (ErrorCode) +* `0`: 成功 +* `201102`: 发送消息失败 +* `201104`: 目标寻找失败 (未找到群或联系人) +* `501000`: 其他未知错误 + +## 附录:消息类型定义 (textType) +* 1: 文本 (TEXT_TYPE_PLAIN) +* 2: 图片 (TEXT_TYPE_IMAGE) +* 3: 语音 (TEXT_TYPE_VOICE) +* 5: 视频 (TEXT_TYPE_VIDEO) +* 8: 链接 (TEXT_TYPE_LINK) +* 9: 文件 (TEXT_TYPE_FILE) +* 15: 引用回复 (TEXT_TYPE_REPLY) diff --git a/app/src/main/java/org/yameida/worktool/Constant.kt b/app/src/main/java/org/yameida/worktool/Constant.kt index fcba8ac..efab941 100644 --- a/app/src/main/java/org/yameida/worktool/Constant.kt +++ b/app/src/main/java/org/yameida/worktool/Constant.kt @@ -5,7 +5,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", "4.1.2", "4.1.3", "4.1.6", "4.1.7", "4.1.8", "4.1.9", "4.1.10") - val AVAILABLE_VERSION_MAP = mapOf(Pair("4.0.2", 40002), Pair("4.0.6", 40006), Pair("4.0.8", 40008), Pair("4.0.10", 40010), Pair("4.0.12", 40012), Pair("4.0.16", 40016), Pair("4.0.18", 40018), Pair("4.0.19", 40019), Pair("4.0.20", 40020), Pair("4.1.0", 40100), Pair("4.1.2", 40102), Pair("4.1.3", 40103), Pair("4.1.6", 40106), Pair("4.1.7", 40107), Pair("4.1.8", 40108), Pair("4.1.9", 40109), Pair("4.1.9", 40110)) + val AVAILABLE_VERSION_MAP = mapOf(Pair("4.0.2", 40002), Pair("4.0.6", 40006), Pair("4.0.8", 40008), Pair("4.0.10", 40010), Pair("4.0.12", 40012), Pair("4.0.16", 40016), Pair("4.0.18", 40018), Pair("4.0.19", 40019), Pair("4.0.20", 40020), Pair("4.1.0", 40100), Pair("4.1.2", 40102), Pair("4.1.3", 40103), Pair("4.1.6", 40106), Pair("4.1.7", 40107), Pair("4.1.8", 40108), Pair("4.1.9", 40109), Pair("4.1.10", 40110)) const val PACKAGE_NAMES = "com.tencent.wework" const val WEWORK_NOTIFY = "wework_notify" const val BASE_LONG_INTERVAL = 5000L @@ -14,7 +14,7 @@ object Constant { var LONG_INTERVAL = BASE_LONG_INTERVAL var CHANGE_PAGE_INTERVAL = BASE_CHANGE_PAGE_INTERVAL var POP_WINDOW_INTERVAL = BASE_POP_WINDOW_INTERVAL - private const val DEFAULT_HOST = "wss://api.worktool.ymdyes.cn" + private const val DEFAULT_HOST = "ws://192.168.13.252:8080" var version = Int.MAX_VALUE var myName = "" @@ -28,9 +28,9 @@ object Constant { val transformation = "AES/CBC/PKCS7Padding" val wssRegex = "^wss".toRegex() val wsRegex = "^ws".toRegex() - val suffixString = "(-.*)?(…)?(\\(\\d+\\))?$" - val suffixRegex = "(-.*)?(…)?(\\(\\d+\\))?$".toRegex() - val groupSuffixRegex = "(…)?(\\(\\d+\\))?$".toRegex() + val suffixString = "(\\s*-.*)?(…)?(\\(\\d+\\))?(\\s*[\\(\uff08].*?[\\)\uff09])?$" + val suffixRegex = "(\\s*-.*)?(…)?(\\(\\d+\\))?(\\s*[\\(\uff08].*?[\\)\uff09])?$".toRegex() + val groupSuffixRegex = "(…)?(\\(\\d+\\))?(\\s*[\\(\uff08].*?[\\)\uff09])?$".toRegex() val digitalRegex = "\\(\\d+\\)\$".toRegex() var weworkCorpName: String get() = SPUtils.getInstance().getString("weworkCorpName", "") diff --git a/app/src/main/java/org/yameida/worktool/activity/ListenActivity.kt b/app/src/main/java/org/yameida/worktool/activity/ListenActivity.kt index 65fe7c4..c2e10ab 100644 --- a/app/src/main/java/org/yameida/worktool/activity/ListenActivity.kt +++ b/app/src/main/java/org/yameida/worktool/activity/ListenActivity.kt @@ -185,7 +185,9 @@ class ListenActivity : AppCompatActivity() { } private fun initData() { - HttpUtil.checkUpdate() + Constant.robotId = "111" + Constant.host = "ws://192.168.13.252:8080" + // HttpUtil.checkUpdate() HttpUtil.getMyConfig(toast = false) CacheUtil.autoDelete() } diff --git a/baselibrary/build.gradle b/baselibrary/build.gradle index f42b4e0..eefd3d3 100644 --- a/baselibrary/build.gradle +++ b/baselibrary/build.gradle @@ -34,7 +34,7 @@ dependencies { //工具集 api 'com.blankj:utilcodex:1.31.0' //toast - api 'com.github.getActivity:ToastUtils:10.5' + api(name: 'ToastUtils-10.5', ext: 'aar') //Gson api 'com.google.code.gson:gson:2.8.5' //网络 @@ -53,9 +53,9 @@ dependencies { api 'com.github.bumptech.glide:glide:4.9.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0' //photoview - api 'com.github.chrisbanes:PhotoView:2.1.3' + api(name: 'PhotoView-2.1.3', ext: 'aar') //悬浮窗框架 - api 'com.github.princekin-f:EasyFloat:1.3.4' + api(name: 'EasyFloat-1.3.4', ext: 'aar') //leak // debugApi 'com.squareup.leakcanary:leakcanary-android:1.5.4' @@ -64,4 +64,7 @@ dependencies { } repositories { mavenCentral() + flatDir { + dirs 'libs' + } } diff --git a/baselibrary/libs/EasyFloat-1.3.4.aar b/baselibrary/libs/EasyFloat-1.3.4.aar new file mode 100644 index 0000000..f818843 Binary files /dev/null and b/baselibrary/libs/EasyFloat-1.3.4.aar differ diff --git a/baselibrary/libs/PhotoView-2.1.3.aar b/baselibrary/libs/PhotoView-2.1.3.aar new file mode 100644 index 0000000..597d399 Binary files /dev/null and b/baselibrary/libs/PhotoView-2.1.3.aar differ diff --git a/baselibrary/libs/ToastUtils-10.5.aar b/baselibrary/libs/ToastUtils-10.5.aar new file mode 100644 index 0000000..531bcdf Binary files /dev/null and b/baselibrary/libs/ToastUtils-10.5.aar differ diff --git a/build.gradle b/build.gradle index 3c0d8e9..339a7d7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,15 +1,16 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = "1.3.72" + ext.kotlin_version = "1.7.10" repositories { + maven { url 'https://maven.aliyun.com/repository/public' } + maven { url 'https://maven.aliyun.com/repository/google' } + maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } google() - jcenter() - maven { url 'https://maven.fabric.io/public' } - maven { url 'https://repo1.maven.org/maven2/' } - maven { url 'https://jitpack.io' } + mavenCentral() + maven { url 'https://www.jitpack.io' } } dependencies { - classpath "com.android.tools.build:gradle:4.0.0" + classpath "com.android.tools.build:gradle:7.2.2" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong @@ -19,11 +20,14 @@ buildscript { allprojects { repositories { + maven { url 'https://maven.aliyun.com/repository/public' } + maven { url 'https://maven.aliyun.com/repository/google' } google() - jcenter() - maven { url 'https://maven.fabric.io/public' } - maven { url 'https://repo1.maven.org/maven2/' } - maven { url 'https://jitpack.io' } + mavenCentral() + maven { url 'https://www.jitpack.io' } + flatDir { + dirs "$rootProject.projectDir/baselibrary/libs" + } } } diff --git a/gradle.properties b/gradle.properties index 4d15d01..9180328 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,4 +18,4 @@ android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official \ No newline at end of file +systemProp.http.agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 93c1a47..a227fba 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/gradlew b/gradlew old mode 100644 new mode 100755