docs: phase 0 reconnaissance findings

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
浅念
2026-08-01 13:29:14 +08:00
parent 8dc8caa749
commit 7f19b12e74
+83
View File
@@ -0,0 +1,83 @@
# Phase 0 侦察结论(实现期实测补充)
- **日期**:2026-08-01
- **方式**:浏览器实地登录云客 CRM,在页内 `fetch` 真实接口 + 抓包
- **配套**:[数据字典](2026-08-01-yunke-data-dictionary.md)、[设计文档](2026-08-01-yunke-data-mirror-design.md)
- **状态**:关键项全部确认 ✅;少量次要项标注 ⏳
---
## 1. 登录与鉴权 ✅
- **`GET /newuc/login/getSecureKey`**(公开接口,无需登录)返回 RSA 公钥:
```
{"code":10000,"data":{"public_exponent":"10001","modulus":"9a370c33...(hex)"}}
```
→ **密码用 RSA 加密**(PKCS1 v1.5,exponent=0x10001=65537,modulus 为返回的 hex)。
- **`POST /usercenter/login/pcLogin`**:请求体 `{account, password}`(`password`=RSA 加密后的 hex/base64)—— 字段名为推断,实现时若失败尝试 `{username, password}` / `{account, pwd}` 等变体。登录成功返回会话 cookie + `userid`/`company`。
- **数据接口鉴权 = 会话 cookie**:实测在登录后的页内用 `fetch('/wechatweb/...', {credentials:'include'})`(**不带** `userid`/`company` 等自定义头)即可 200 返回数据。自定义请求头(`userid`/`company`/`source:yunkepc`/`channel:4`)是**信息性的,非必需**。
- **影响**:`YunkeClient` 只需持有会话 cookie 即可调所有数据接口;自定义头可继续带(无害)。
- **会话可从 localStorage 重建**:`userid`、`company`、`h5Token`、`userList` 都在 localStorage(冒烟脚本 existing-session 模式可据此 + cookie 运行)。
## 2. 微信消息类型码 ✅(实测枚举)
| type | 含义 | 处理 |
|---|---|---|
| `1` | 文字 | 存 `text` |
| `-2` | 日期分隔符 | **丢弃**(非真实消息) |
| `2` | 图片/表情(webp) | `file`/`fileTh` 为**直接 URL** + `md5` → 下载去重 |
| `3` | 图片(旧格式) | `file` 为**不透明 ID**(如 `07215407292664565911ce5105`)→ 需另解析/下载接口 ⏳ |
| `4` | 视频(mp4) | `file` 直接 URL + `md5` → 下载 |
| `10` | 系统/链接/名片/邀请 | `file` 为 `support.weixin.qq.com/...` URL → **非媒体,跳过下载** |
| `6` `9` `13` `15` `21` `22` | 其他(语音/文件/转账/红包/引用等疑似的码) | ⏳ 实现期按需枚举确认(`raw_json` 已留底) |
> `normalize_wechat_type` 已映射 `1→text`、`-2→divider`;需补 `2→image`、`3→image_legacy`、`4→video`、`10→system` 等。
## 3. 媒体下载机制 ✅
**聊天媒体**(图片/视频/文件):
- 主机:`wx.oss.yunkecn.com`
- 路径:`/wechatMsgFile/{companyCode}/{userId}/ossCallback/{aa}/{bb}/{cc}/{md5}.{ext}`
- 例:`https://wx.oss.yunkecn.com/wechatMsgFile/8m0atd/{userId}/ossCallback/ea/8f/74/ea8f74e7797e63505731e8c51222c554.webp`
- 消息里给 `file`(原图)/`fileTh`(缩略图)/`fileHd`(高清) + `md5`(去重键)
- **直接 GET 即可下载**(公开 OSS,无需鉴权);按 `md5` 去重存储
- 例外:type=`3` 图片的 `file` 是不透明 ID(非 URL)→ 需另找下载接口 ⏳
**通话录音 / 语音通话录音**:
- 主机:`yunke-pcfile.oss-cn-beijing.aliyuncs.com`
- 电话录音:`/callFile/{company}/{userId}/YYYY/MMDD/{callNumber}_{ts}.mp3`(`PhoneCall.recordFile`)
- 微信语音通话录音:`/wechat-call/{company}/{imei}/ossCallback/{ts}_{ts}.mp3`(`voiceCall.ossFileName`)
**头像**:`wx.qlogo.cn`、`mmhead.c2c.wechat.com`、`pc.oss.yunkecn.com`(外链,抓到即下载)
## 4. 语音通话 ✅
- `POST /wechatweb/callRecord/queryWeChatVoiceList`,total=**333**
- 字段:`id`、`userId`/`userName`/`userPhone`/`userWeChatNickName`/`userWeChatHeadImg`(员工)、`talker`/`talkerNickName`/`talkerAlias`/`talkerHeadImg`(客户)、`callType`、`isSend`(0呼入/1呼出)、`callStatus`(1接通有录音/2无应答)、**`ossFileName`(录音URL)**、`ossFileExist`、`duration`(秒,字符串)/`durationFile`(秒,int)、`startTime`/`endTime`、`weChatId`、`remark`(如"通话时长02:31"/"对方无应答")、`isRoom`(群通话?)、`callFileId`
- 归一到 `PhoneCall` 表,`channel='voice_call'`,`record_file`←`ossFileName`
## 5. WhatsApp 联系人 ✅
- `POST /qqweb/WhatsAppUser/businessFriendList`,total=**11292**
- 字段:`uid`(员工 `phone@s.whatsapp.net`)、`userId`/`userName`/`salePhone`/`salesNickName`(员工)、`friendUid`(客户→`friend_id`)、`nickName`、`phone`、`createTime`/`lastUpdate`、`groupNum`/`contactsNum`/`talkerCount`/`messageCount`/`sensitiveWordNum`
- 归一到 `Customer` 表,`channel='whatsapp'`
## 6. WhatsApp 媒体 ⏳
- 消息里给 `mediaName`(文件名,如 `{uid}_{keyId}.jpg`),**非 URL** → 需专门下载接口
- ⏳ 实现期:读 `whatsapp-service-*.js` 或触发一次真实图片加载,找到 `mediaName → 文件` 的下载接口(很可能也是 `wx.oss.yunkecn.com` 或同系 OSS)
## 7. 群成员昵称 ⏳
- `chatroom/getChatroomList` 的 `memberIds` 给的是**分号分隔的 wxid 列表**(不含昵称/头像)
- ⏳ 群内成员的昵称/头像可能需另调接口(实现期确认);本期可先存 wxid,昵称后补
---
## 对实现的影响(解锁项)
- **登录**:`app/session/login.py::encrypt_password` 改为真实 RSA(用 `cryptography` 从 modulus/exponent 构造公钥加密)→ 解锁自动登录 + 真实端到端冒烟
- **媒体下载(M2.6)**:微信 type 2/4 直接 URL 下载、按 md5 去重;type 3 标记待解析;type 10 跳过
- **语音通话(M2.4)**:`queryWeChatVoiceList` 字段已确认,录音直链下载
- **WhatsApp 联系人(M3.3)**:`businessFriendList` 字段已确认
- **鉴权简化**:`YunkeClient` cookie-only 即可(自定义头可选)