1. 目标与边界
本项目在本机运行 pood1e/wa-app,通过非官方 WhatsApp 协议完成自有账号注册、长连接、消息解密和 OTP 提取,并在其上增加一个最小化的本地 OTP API。
仅用于本人拥有或明确获授权的账号。非官方协议可能因 WhatsApp 服务端变更失效,也可能触发账号风控。登录状态、密钥、验证码和消息都属于敏感数据,不能提交 Git、上传公开站点或写入普通日志。
2. 本地架构
自有 WhatsApp 账号
│ 协议长连接
▼
wa-app / whatsapp-protocol
│ 本地 HTTP
▼
otp-gateway / whatsapp-otp-gateway
│ Bearer Token
▼
本地调用程序
| 组件 | 作用 | 本地入口 |
|---|---|---|
whatsapp-protocol | 注册、密钥、长连接、解密和消息存储 | Dashboard 127.0.0.1:18080 |
whatsapp-otp-gateway | 账号发现、最新 OTP 和等待下一条 OTP | API 127.0.0.1:8787 |
whatsapp-wa-app-data | SQLite、登录态和协议身份密钥 | Docker named volume,不可随意删除 |
源码位于 /Users/zhoujiangbin/Documents/whatapp接码。上游代码固定到公开提交 28c8c9de612827625a3a505580575c05aa48e4f5,并保留本地消息时间修复。
3. 下次开机如何启动
cd /Users/zhoujiangbin/Documents/whatapp接码
colima start
docker context inspect colima >/dev/null 2>&1 || \
docker context create colima --docker host=unix:///Users/zhoujiangbin/.colima/default/docker.sock
DOCKER_CONTEXT=colima docker compose up -d
DOCKER_CONTEXT=colima docker compose ps
curl -fsS http://127.0.0.1:18080/healthz
curl -fsS http://127.0.0.1:8787/healthz
平时启动不要带 --build。只有源码、Dockerfile 或依赖变化时才执行:
DOCKER_CONTEXT=colima docker compose up -d --build
Colima 正在运行但 Docker 仍连接 /var/run/docker.sock 时,不要启动 Docker Desktop;先检查 /Users/zhoujiangbin/.colima/default/docker.sock,再补建 colima context。
4. 首次注册判断流程
注册完成不能只看接口 success=true,需要同时满足:
- 注册状态为
REGISTERED。 - 登录状态为
ACTIVE。 - 长连接保持在线。
- 新收到的一条授权测试消息可以正常解密。
常见返回与处理
| 返回原因 | 真实含义 | 正确处理 |
|---|---|---|
security_code | 账号开启了两步验证 PIN 或触发安全校验 | 停止重复提交 OTP;通过官方账号安全流程处理,或在用户确认后走上游支持的旧设备流程 |
mismatch / bad_code | 验证码错误或已过期 | 等允许后只申请一个新码,不反复提交旧码 |
too_recent / too_many | 请求过快或已触发频控 | 按倒计时等待,继续尝试会延长风险 |
| 已注册但登录态不活跃 | 登录态落库或长连接异常 | 查登录态和协议日志,不要立即重复注册 |
security_code 不是普通代理错误。服务端能够返回这个原因,说明注册请求已经到达并被安全规则拒绝。两步验证 PIN 也不能填进短信 OTP 输入框。
当手机号暂时收不到短信、但官方旧设备仍可访问时,可以考虑上游支持的 wa_old 旧设备转移流程;协议代码不能绕过号码所有权验证,也不能绕过 WhatsApp 的冷却期。
5. 代理为什么要这样配
Mac 终端中的:
export https_proxy=http://127.0.0.1:7890
只影响从该终端启动的宿主机命令,不会自动改变已经运行的容器。容器里的 127.0.0.1 指容器自己,不是 Mac。
Colima 容器访问 Mac 上的本地代理,应在私有 .env 中配置容器可达地址:
WA_COMMON_PROXY=http://host.lima.internal:7890
配置变化后重建相关服务:
DOCKER_CONTEXT=colima docker compose up -d --force-recreate wa-app otp-gateway
代理账号和密码不能写进本文或聊天记录。
6. “消息待解密”的根因与判断
WhatsApp 消息使用端到端加密。账号转移到新的协议设备后,新设备会生成自己的身份密钥、签名预密钥和一次性预密钥。转移过程中已经发给旧设备密钥的历史消息,新设备没有相应私钥,可能永久无法解密。
排查顺序:
- 确认注册状态和登录状态已经正常。
- 判断待解密消息是否产生在设备转移或身份变化之前。
- 从脱敏日志确认是否为 session/prekey 不匹配,绝不公开真实 key ID。
- 在长连接建立后接收一条新的授权测试消息。
- 如果新消息能解密,保留当前身份状态,把旧消息标记为历史会话遗留;不要反复重置账号。
- 如果新消息也不能解密,再检查 session 建立、回执和协议兼容,不能一上来就删除身份密钥。
本次验证中,新会话建立后的消息能够正常接收和解密,因此旧的“待解密”属于转移期密钥不匹配,而不是 OTP 网关故障。
7. 刷新后消息时间全部相同
现象
消息第一次展示时各有时间,刷新或重新连接后,同一批消息被显示成相同时间。
根因
需要区分三个时间来源:
provider_timestamp:WhatsApp 提供的消息时间。- SQLite 独立列
received_at:首次持久化接收时间。 - JSON payload 内的
received_at:Dashboard 读取并展示的时间。
重连时服务端会重复投递历史消息。原 SQLite UPSERT 合并逻辑保留了数据库列,却用重放时新生成的 received_at 覆盖整个 JSON payload,导致页面刷新后多条消息显示为同一重放时间。
修复
本地修复在 mergeInboundMessageState 中保留旧消息的 ReceivedAt,使重复消息只更新允许变化的状态,不覆盖首次接收时间。对应文件:
upstream/wa-app/internal/waapp/store/sqlite_store.goupstream/wa-app/internal/waapp/store/sqlite_store_message_merge_test.go
已有错误数据只能在确认 SQLite 独立列仍然正确后,从独立列恢复 payload;修复前必须备份数据库,不能对所有时间做无条件批量覆盖。
回归验证
cd /Users/zhoujiangbin/Documents/whatapp接码/upstream/wa-app
gofmt -w internal/waapp/store/sqlite_store.go internal/waapp/store/sqlite_store_message_merge_test.go
go vet ./...
go test ./internal/waapp/store
cd /Users/zhoujiangbin/Documents/whatapp接码
npm test
DOCKER_CONTEXT=colima docker compose up -d --build
刷新 Dashboard 或触发一次安全重连后,各消息仍应保持各自时间。
8. OTP 网关使用与保密
调用时从本地 .env 加载令牌,不要把令牌直接写入脚本仓库:
cd /Users/zhoujiangbin/Documents/whatapp接码
set -a
. ./.env
set +a
curl -fsS -H "Authorization: Bearer ${GATEWAY_TOKEN}" \
'http://127.0.0.1:8787/api/otp/wait?after=now&timeout=60'
unset GATEWAY_TOKEN WA_APP_AUTH_PASSWORD
成功响应包含验证码,禁止把响应写入公开日志。网关带 Cache-Control: no-store,其日志也不输出 OTP 正文。
9. 数据保护与升级
.env、数据卷、SQLite 备份、session、identity key、prekey 和 OTP 都按凭据管理。- 禁止执行
docker compose down -v或删除whatsapp-wa-app-data;这可能丢失登录身份并要求重新注册。 - 上游升级前记录当前提交和本地补丁,备份数据卷,并检查上游是否已经解决重复消息时间覆盖。
- 升级后依次执行 Go vet、store 回归测试、Node 测试、镜像构建、双健康检查和自有测试账号收消息验证。
- 服务只绑定
127.0.0.1,如需对外访问,必须另行设计认证、TLS、来源限制和日志脱敏。
10. 验收清单
whatsapp-protocol为 healthy,whatsapp-otp-gateway为 running。- Dashboard 和 OTP Gateway 的
/healthz都成功。 - 目标账号注册状态为 registered、登录状态为 active、长连接在线。
- 新消息能够解密,页面刷新后每条消息保持自己的时间。
- OTP 接口未授权时拒绝访问,授权后可读取但日志不包含验证码。
.env、账号信息、Cookie、验证码、密钥和数据库备份均未进入 Git 或公开文档。
11. 可复用 Skill
已建立本地 Skill:/Users/zhoujiangbin/.codex/skills/wa-app-protocol-maintainer。以后提出“启动 WhatsApp 接码服务”“security_code 怎么处理”“消息待解密”“刷新后时间都一样”“升级 wa-app”等问题时,它会提供本项目的路径、排查顺序、测试和数据保护约束。