Files
yunke/docs/superpowers/specs/phase0-findings.md
T
2026-08-01 13:29:14 +08:00

5.6 KiB
Raw Blame History

Phase 0 侦察结论(实现期实测补充)

  • 日期:2026-08-01
  • 方式:浏览器实地登录云客 CRM,在页内 fetch 真实接口 + 抓包
  • 配套:数据字典、设计文档
  • 状态:关键项全部确认 ✅;少量次要项标注 ⏳

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 即可(自定义头可选)