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 和等待下一条 OTPAPI 127.0.0.1:8787
whatsapp-wa-app-dataSQLite、登录态和协议身份密钥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,需要同时满足:

  1. 注册状态为 REGISTERED。
  2. 登录状态为 ACTIVE。
  3. 长连接保持在线。
  4. 新收到的一条授权测试消息可以正常解密。

常见返回与处理

返回原因真实含义正确处理
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 消息使用端到端加密。账号转移到新的协议设备后,新设备会生成自己的身份密钥、签名预密钥和一次性预密钥。转移过程中已经发给旧设备密钥的历史消息,新设备没有相应私钥,可能永久无法解密。

排查顺序:

  1. 确认注册状态和登录状态已经正常。
  2. 判断待解密消息是否产生在设备转移或身份变化之前。
  3. 从脱敏日志确认是否为 session/prekey 不匹配,绝不公开真实 key ID。
  4. 在长连接建立后接收一条新的授权测试消息。
  5. 如果新消息能解密,保留当前身份状态,把旧消息标记为历史会话遗留;不要反复重置账号。
  6. 如果新消息也不能解密,再检查 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.go
  • upstream/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”等问题时,它会提供本项目的路径、排查顺序、测试和数据保护约束。