8.5 KiB
8.5 KiB
按 UID 获取私信历史与发送文本(不打开目标主页)
结论与交付
- 脚本:
src/douyin_im.py,分别用history和send子命令独立执行。 - 离线检查:
src/test_douyin_im.py。 - 输入数字 UID,不需要主页 URL、sec_uid、固定会话 ID、Cookie 文件或当前账号 ID。
- Python 通过
browser-harness在已有、已登录的抖音非他人主页标签中调用官方 IM SDK。脚本不导航、不点击私信按钮、不打开聊天窗口、不调用标记已读接口。 - 仍依赖浏览器/CDP 与页面中的 SDK;不是脱离浏览器的纯 HTTP 实现。 本次解决“不打开目标用户主页”,没有实现无浏览器认证、Protobuf 编解码、签名及令牌刷新。
- 获取的是“指定 UID 对应会话的一页消息历史”,不是账号所有会话列表,也不是持续监听。
调研过程与证据
- 检查现有
get_current_user.py、follow_user.py,复用validate_uid,没有修改这些脚本。 - 浏览器原先已有个人页和目标用户主页标签。本次切换到个人页调研,没有导航打开目标主页。登录检查接口
/aweme/v1/web/user/profile/self/?device_platform=webapp&aid=6383返回status_code=0和有效用户 UID。 - 页面暴露
openImConversation,但该入口会显示聊天 UI,因此没有将它用于产出。 - 发现全局 chunk 数组
@pc-im/im:<版本>。利用 Webpack 的 runtime 回调获得模块缓存,在已加载模块中定位getOrCreatePrivateConversationByUid特征,找到导出实例的imSdkService。 - 调研时数字模块 ID 可用,但最终脚本不固定这些 ID,也不固定版本号。依赖的管理器和 SDK 方法仍是私有接口,网站升级可能使其失效。
- 找到
conversationManager、imSdkManager、sendMessageManager。底层发送需要会话 ticket、消息类型、客户端 ID 等,交由原站 SDK 处理,不从网络资源中复制或硬编码令牌。 - UID 查询优先匹配
sdk.getConversationList()的toParticipantUserId。历史缺少本地会话时调用getConversationByUidOrShortIdIfOnline({participantId: uid}),其内部可调用fetchConversation;实际发送时才允许getOrCreatePrivateConversationByUid(uid)创建会话。 - 实测目标 UID
4432868004606158:getMessagesByConversation({conversation,cursor,limit})可直接拉取历史,起始游标为 int64 最大值字符串9223372036854775807。 - 最初历史中只有一条系统限制提示:对方回复或关注之前只能发送一条文字消息。先执行预览,再询问用户,不消耗发送机会。
- 用户明确授权发送
HI。只调用一次发送命令,SDK 返回success=true、statusCode=0、checkCode=0,服务器消息 ID 为7682092588350670385。 - 随后重新请求历史,确认
HI和同一服务器消息 ID 存在,server_status=0。这是服务端接受并可回读的证据,不代表收件人已读。 - 中途一次过宽的 bundle 源码扫描导致浏览器连接异常,一次 JS 表达式括号错误;均改为有界的模块/方法检查。没有因此重复发送。
- 查询在线安装文档遭遇网络代理限制,
browser-harness --help超时;通过本机包元数据核实安装名、版本与 Python 要求。空白环境安装步骤如下,未另行在空白机器执行。
消息接口约定
const sdk = service.imSdkManager.getImSdkInstance();
const result = await sdk.getMessagesByConversation({
conversation,
cursor: '9223372036854775807',
limit: 20
});
const message = await sdk.createMessage({
conversation,
type: 7,
content: JSON.stringify({
aweType: 700,
type: 0,
richTextInfos: [],
text: '用户指定的文本'
})
});
const sent = await sdk.sendMessage({message});
- SDK 文本类型为
7,业务AWEME_TEXT为700。 - 历史输出包含原始
content字符串,普通文本可进一步 JSON 解析;系统消息不能一概当作普通文本。 - 输出字符串化服务端 ID、发送者 ID、游标,避免 Python 调用方再将长整型转换为 JavaScript Number 后损失精度。
has_more和next_cursor用于下一页;保留接口返回顺序,不猜测时间排序、不自动循环拉取。- 发送默认预览。预览只检查账号、SDK 和已有会话,不创建会话或消息。
空白环境安装
需要 Python 3.11+、Chrome/Chromium、已登录抖音账号。验证环境:Python 3.13.5、browser-harness 0.1.9、Node.js 22.21.0(仅离线测试需要 Node.js)。
# 在项目根目录;Windows 激活命令改为 .venv\Scripts\activate
python3 -m venv .venv
. .venv/bin/activate
python -m pip install 'browser-harness==0.1.9'
# 或者已有 uv 时,独立安装 CLI,不需要给项目再装 Python 包:
# uv tool install --python 3.12 'browser-harness==0.1.9'
保留项目 src/:本脚本复用 follow_user.py 的 UID 校验,该模块还导入 get_current_user.py;不要只复制单个文件。这些导入不会执行关注操作。
连接已有 CDP,地址可以换成自己的,不在脚本中绑定:
export BU_CDP_URL=http://localhost:9222
browser-harness <<'PY'
print(list_tabs())
PY
若尚未启用 CDP,按本机 Chrome 版本启用远程调试,或使用独立浏览器配置目录启动,例如:
# 可执行文件名称按系统修改;仅在需要新浏览器配置时执行
chromium --remote-debugging-address=127.0.0.1 --remote-debugging-port=9222 \
--user-data-dir="$HOME/.douyin-im-browser"
手动在该浏览器登录并保留 https://www.douyin.com/ 首页,等待官方 IM SDK 加载。不需要打开任何目标用户主页。若启动的是新的配置目录,原浏览器的登录态不会自动转移。CDP 只绑定本机,不向公网暴露。登录丢失时脚本停止,不尝试登录。
使用
# 一页消息历史
python src/douyin_im.py history 4432868004606158 --limit 20
# 下一页:仅在 has_more 为真时,将输出的 next_cursor 原样传入
python src/douyin_im.py history 4432868004606158 --cursor '<next_cursor>' --limit 20
# 默认只预览,可核对 sender_uid、目标 UID 与文本
python src/douyin_im.py send 4432868004606158 --text '待发送的文本'
# 明确确认后实际发送;会占用平台允许的陌生人消息额度
python src/douyin_im.py send 4432868004606158 --text '待发送的文本' --confirm
实际发送返回 success=true 才以退出码 0 表示成功;明确失败或未知结果以非零退出。不要复制文档发送命令反复测试本次 UID,HI 已成功发送。
history 限制 limit 为 1..100,游标必须是非负 int64 字符串。UID 必须是无前导零的正整数字符串。空白消息被拒绝;其他平台长度、风控和关系限制由 SDK/服务端决定,不进行绕过。
失败处理与范围
LOGIN_REQUIRED/LOGIN_CHECK_FAILED:停止,由用户检查登录状态。- 无适合的标签页:请手动打开并登录抖音首页;脚本不导航补建。
IM_SDK_NOT_READY:当前页面尚未加载 SDK,或网站结构已变化;当前实现不通过点击 UI 初始化。CONVERSATION_NOT_FOUND:在线查询没有得到会话;历史功能不为此主动创建会话。TARGET_MISMATCH:会话类型或目标 UID 不符,拒绝继续。SDK_REQUEST_FAILED:私有 SDK 请求异常;仅输出固定错误码,避免错误对象含令牌时泄露。- 超时:发送可能已被服务端接受。先查历史/人工核实,禁止自动重发。Python 不重试,但不能保证底层官方 SDK 不做自身重试,也不承诺跨进程 exactly-once。
- 共享 browser-harness 会话存在并发切换标签风险,当前只支持串行调用。
- 换账号或全新会话的创建路径尚未实测,本次验证限于当前已登录账号和此测试 UID。
- 未实现:群聊、图片/语音/文件、撤回、已读上报、全部会话遍历、持续消息监听、绕过陌生人发送限制。
- 不将聊天内容、Cookie、token 保存为研究附件;执行者若重定向 stdout,应自行保护该文件。
验证
python -m unittest discover -s src -p 'test_douyin_im.py'
两个离线测试通过:SDK 模拟覆盖预览无副作用、文本字段、单次发送、分页、登录失效拒绝发送及特殊字符安全序列化;Python 模拟覆盖非法参数及超时不重试。测试不会连接抖音或发出真实消息。两个 Python 文件主语言服务诊断均无问题。真实验证为一次 HI 发送成功及独立历史回读。