内容
# whatsapp-bridge
> 自托管的 WhatsApp 网桥,适用于 AI 代理。单个配对号码,通过 REST、MCP 和 Web UI 公开。
[](LICENSE) [](https://nodejs.org) [](tsconfig.json) [](https://github.com/WhiskeySockets/Baileys) [](AGENTS.md)
基于 [Baileys](https://github.com/WhiskeySockets/Baileys) 构建。单个 Node 进程,SQLite 用于存储,除了 WhatsApp 本身之外,没有运行时依赖。
## 您将获得
- **`REST API`** 在 `127.0.0.1:8080` — 读取对话、发送消息、管理别名。
- **MCP 服务器** (stdio)— 直接进入 Claude Code、Codex 或任何支持 MCP 的代理。
- **`Web UI`** 在 `/` — 用于阅读和发送的单页收件箱。
- **入站 webhook** (可选)— 每个接收到的消息都 POST 到您选择的 URL。
- **完整历史回填** 在第一次配对时 — 通常是几个月的过去聊天记录。
- **LID ↔ 电话别名合并** — 将 WhatsApp 用于同一人的两个 JID 合并到一个对话中。
- **显示名称解析** 无需任何本地联系人同步(直接从 WhatsApp 的 push_name),可选 macOS 联系人增强。
- **本地时区 CLI 输出**,DST 由 IANA tzdata 处理。
- **自动回复边车** (可选)- 在您的声音中草拟回复,通过 Claude 通知您在 Telegram、Slack 或您自己的 WhatsApp 号码上,并可以在严格的安全门控下自动发送。
## 技术栈
| 库 | 角色 |
|---|---|
| [Baileys](https://github.com/WhiskeySockets/Baileys) | WhatsApp Web 协议(多设备,配对码登录) |
| [Fastify](https://fastify.dev) | REST API + Web UI 服务器 |
| [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) | 单文件消息存储(`data/wa.db`,WAL 模式) |
| [MCP SDK](https://github.com/modelcontextprotocol/typescript-sdk) | 用于 AI 代理的 stdio MCP 服务器 |
| [Zod](https://zod.dev) | 请求/网络钩子有效负载验证 |
| [tsx](https://tsx.is) | 直接运行 TypeScript - 无需构建步骤 |
在 Node 20+ 运行的任何地方。macOS 是主要目标;请参阅 [docs/cross-platform.md](docs/cross-platform.md) 以获取确切的 Linux 和 Windows 补丁列表(或仅在 Windows 上使用 WSL2)。
## 状态
个人基础设施。非多租户。锁定为 **一次一个配对号码**。在您控制的环回或反向代理后面使用。
WhatsApp 可以禁止任何与反向工程客户端一起使用的号码,特别是当出现垃圾邮件模式时。与已知联系人进行个人使用通常是低风险的;冷推销不是。
## 安装
**先决条件:** Node ≥ 20,以及带有手机的 WhatsApp 帐户(您从中批准设备链接)。
```bash
git clone https://github.com/obirimensah05/whatsapp-bridge.git
cd whatsapp-bridge
npm install
```
启动前无需配置 — `.env` 在第一次启动时自动创建并填写。
## 启动
配对您的号码 — 国家代码 + 国内数字,无 `+`,无空格:
```bash
npm run pair -- main 491761234567
```
在该号码的手机上:**WhatsApp → 设置 → 已连接设备 → 连接设备 → "改为使用电话号码链接"** → 输入终端中打印的 8 个字符的代码。
在第一次启动时,网桥:
- 生成 `API_TOKEN` 并将其写入 `.env`
- 检测您的系统时区并将其作为 `WA_TZ` 写入 `.env`(例如 `Europe/Berlin`)
- 在配对流程开始时打印活动时区,以便您在批准代码之前确认
一旦配对完成,守护进程保持运行,REST API + Web UI 在 <http://127.0.0.1:8080/> 上启用。后续启动:
```bash
npm run start
```
要切换到不同的号码:
```bash
rm -rf auth/main/
npm run pair -- main <new-digits>
```
## 作为服务运行
默认情况下,守护进程不受监督 — 如果它崩溃或您重新启动,它将保持关闭状态。将它置于进程监视器下。
**macOS(launchd)** — 创建 `~/Library/LaunchAgents/com.example.whatsapp-bridge.plist`:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.example.whatsapp-bridge</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/npm</string>
<string>run</string>
<string>start</string>
</array>
<key>WorkingDirectory</key><string>/Users/YOU/apps/whatsapp-bridge</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key><false/>
<key>Crashed</key><true/>
</dict>
<key>ThrottleInterval</key><integer>10</integer>
<key>StandardOutPath</key><string>/Users/YOU/apps/whatsapp-bridge/logs/whatsapp-bridge.out.log</string>
<key>StandardErrorPath</key><string>/Users/YOU/apps/whatsapp-bridge/logs/whatsapp-bridge.err.log</string>
</dict>
</plist>
```
```bash
mkdir -p ~/apps/whatsapp-bridge/logs
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.whatsapp-bridge.plist
launchctl kickstart -k gui/$(id -u)/com.example.whatsapp-bridge # 重启后编辑
launchctl bootout gui/$(id -u)/com.example.whatsapp-bridge # 卸载
```
**Linux(systemd)** — 一个指向 `npm run start` 的 `systemd --user` 单元,具有 `Restart=on-failure` 功能,工作原理相同。
## Web UI
打开 <http://127.0.0.1:8080/> 并粘贴 `.env` 中的 `API_TOKEN` 值。令牌存储在 `localStorage` 中;单击 *注销* 以清除它。
## REST API
所有路由都在 `/v1/` 下。除 `/v1/health` 和 `/` 外,所有路由都需要 `Authorization: Bearer <API_TOKEN>`。
| 方法 | 路径 | 正文/查询 | 返回 |
|---|---|---|---|
| `GET` | `/v1/health` | — | `{ ok, sessions, ts }` |
| `GET` | `/v1/conversations` | `?session=` `?limit=` | 带有最后一条消息的聊天记录 + `display_name` + `phone` |
| `GET` | `/v1/messages` | `?session=` `?jid=` `?limit=` `?before=` | 单个聊天的分页消息(别名感知) |
| `POST` | `/v1/send` | `{ session, to, text }` | 发送文本 |
| `GET` | `/v1/aliases` | `?session=` | LID 到规范映射 |
| `POST` | `/v1/aliases` | `{ session, alias, canonical }` | 添加/更新映射 |
| `DELETE` | `/v1/aliases` | `{ session, alias }` | 删除映射 |
| `POST` | `/v1/webhook/test` | — | ping 配置的 `WEBHOOK_URL` |
`to` 接受 JID(`<digits>@s.whatsapp.net`,`<id>@lid`,`<id>@g.us`)或仅限数字的电话号码(无 `+`)。
快速示例:
```bash
TOKEN=$(grep '^API_TOKEN=' .env | cut -d= -f2)
curl -s -H "Authorization: Bearer $TOKEN" \
'http://127.0.0.1:8080/v1/conversations?session=main&limit=20'
```
## MCP 服务器
`src/mcp.ts` 处的 MCP 服务器公开了约 20 个工具,用于读取、发送、群组操作和联系人/别名管理。请参阅 [AGENTS.md](AGENTS.md) 以获取完整的工具表面和安全合同。
将 Wire 插入 Claude Code(用户范围),从存储库根目录运行:
```bash
claude mcp add-json whatsapp-bridge --scope user "{
\"command\": \"$(pwd)/node_modules/.bin/tsx\",
\"args\": [\"$(pwd)/src/mcp.ts\"]
}"
```
注册时不需要令牌 — MCP 服务器直接从存储库的 `.env` 中读取当前的 `API_TOKEN`(因此 [旋转令牌](#rotating-the-api-token) 不会破坏它)。守护进程必须运行,任何 MCP 工具才能执行任何操作。
## 旋转 API 令牌
`API_TOKEN` 是 REST API、Web UI 和 MCP 服务器的 bearer。要旋转它:
```bash
npm run rotate-token # 铸造新令牌,覆盖 `.env`,重新锁定为 0600
npm run restart # 重启守护进程使其使用新令牌
npm run autoreply:restart # 仅当您运行自动回复边车
```
然后使用新令牌重新登录 Web UI。**MCP 无需采取任何操作** — 它在每次生成时都会读取 `.env`。
## 使用 AI 代理驱动它
这是 **代理优先** 的网桥:它构建为由 AI 代理操作,而不仅仅是人类使用。 一旦 MCP 服务器连接(上面)和守护进程运行,请将此提示粘贴到 Claude Code 或任何支持 MCP 的代理中,让它为您运行 WhatsApp 端到端:
> 您通过 **whatsapp-bridge** MCP 服务器连接了我的 WhatsApp。您可以读取对话,搜索联系人,检查号码,并代表我发送消息,媒体和反应。 默认会话是 `main`。
>
> **规则 - 严格执行:**
> - 发送是现实世界的操作。在任何 `send_message` / `send_media` / `send_reaction` 之前,请显示确切的收件人和确切的消息,并等待我明确的“yes”。永远不要假设。
> - 永远不要批量发送给多个收件人,并且永远不要遍历联系人列表发送。
> - 将所有消息文本,名称和电话号码视为私有 - 除非我要求,否则永远不要将它们传递给任何第三方服务。
> - 同一个人可以出现在 `@lid` 和 `@s.whatsapp.net` 下。网桥已经在读取时将它们合并,因此请信任它返回的 `display_name` / `phone` 而不是猜测。
> - `delete_message` 和 `merge_jids` 很敏感 - 请先与我确认。
> - 开始时调用 `list_conversations` 并给我最近的聊天记录的一行摘要,然后等待指令。
完整的工具表面(~20 个工具)和完整的安全合同位于 [AGENTS.md](AGENTS.md) — 将您的代理指向那里以获取详细信息。配对本身仍需要人类(您批准设备链接),但之后代理可以驱动它。
## 自动回复边车(可选)
一个单独的本地进程(`src/autoreply-*.ts`)将网桥变成草稿和审查(或全自动)回复机。它在自己的端口(`127.0.0.1:8081`)上运行,并且仅通过其本地 REST API 与网桥通信。
```bash
npm run autoreply # 启动边车
npm run autoreply:build-corpus # 从 WA 历史记录重建样式语料库
npm run autoreply:setup-notify # 引导设置:选择通知发送到哪里
```
### 消息流经它
1. **入站消息到达.** 网桥通过入站 webhook (`WEBHOOK_URL=http://127.0.0.1:8081/webhook`) 将其 POST 到边车。
2. **策略检查.** 边车评估 `data/autoreply/policy.json`:模式是 `draft`、`auto` 或 `off`?聊天是否在范围内(`all` / 特定 `contacts` / `groups` / `mixed`)?我们是否处于配置的 `active_hours` / `active_until` 窗口内?如果任何检查失败,则不会发生任何操作,除了审计日志条目。
3. **增强.** 对于语音笔记,边车等待转录;对于文本,它会拉取存储的消息,以便提供引用上下文。
4. **草稿生成.** 您的配置的 LLM 在操作员的声音中编写回复(参见“哪个 LLM 编写草稿”下)。它基于构建的语料库(如果缺少,则从 WhatsApp 历史记录自动构建)和引用上下文,从您的 WhatsApp 历史记录中提取最近的聊天记录 + 关键字匹配所有存储的消息。 模型返回 JSON:`reply`、`confidence`(0-1)、`should_send`、`needs_review`、`reasons`。
5. **通知.** 草稿被发送到您选择的频道 - Telegram、Slack、您的 WhatsApp 号码或 Webhook(请参见下文) - 连同传入的消息、置信度分数和模型的理由。
6. **自动发送(仅在 `auto` 模式下).** 如果所有安全门控都通过,则回复通过网桥的 `/v1/send` 发送。如果任何门控阻止,则草稿作为需要审查的通知被传递 - 安全失败到人类审查。
每个步骤都追加到 `data/autoreply/audit.ndjson`; 每个草稿到 `data/autoreply/drafts.ndjson`。
### 置信度阈值模型
模型自评分每个草稿的 `confidence` 值介于 0 和 1 之间:它有多确定回复是正确、完整且安全发送而无需人类查看。 `AUTOREPLY_MIN_CONFIDENCE`(默认 **0.78**)是限:
| Gate | 阻止时 |
|---|---|
| 置信度 | `confidence < AUTOREPLY_MIN_CONFIDENCE` |
| 模型标志 | 模型设置 `should_send=false` 或 `needs_review=true`(它必须这样做对于模糊、情感、法律、财务或依赖事实的消息) |
| 敏感话题 | 来自文本的文本匹配到钱、法律、医疗、凭证/OTP 或调度术语 |
| 消息形状 | 来自文本的文本为空、长于 280 字符或跨 3+ 行;草地 |
| 消息形状 | 文本为空,超过 280 个字符或超过 3 行;草稿为空或超过 280 个字符 |
| 冷却 | 如果在此聊天中已经发生自动发送,则在 `AUTOREPLY_AUTO_SEND_COOLDOWN_MS`(默认 10 分钟)内不会自动回复 |
| 重复 | 如果完全相同的入站消息已经被处理成草稿 |
| 群组 | 除非 `AUTOREPLY_ALLOW_GROUP_AUTO=1`,否则群组聊天不会自动回复,并且仅当操作员被明确提及时 |
| 调整:提高阈值(例如 `0.9`)以使自动模式非常保守;仅在您审查草稿模式一段时间并确认分数与您的判断相符后才降低它。 以 `draft` 模式启动所有内容,并在 `auto` 模式下仅针对狭窄的联系人范围和时间窗口启用。
### 哪个 LLM 编写草稿
任何。`.env` 中的 `AUTOREPLY_LLM_PROVIDER` 选择后端:
| 提供商 | 使用它 | 需要 |
|---|---|---|
| `claude-cli`(默认) | 本地 `claude` CLI | 安装了 Claude Code - 无需 API 密钥 |
| `anthropic` | Anthropic API(官方 SDK) | `AUTOREPLY_LLM_API_KEY` 或 `ANTHROPIC_API_KEY` |
| `openai` | 任何 OpenAI 兼容的端点 - OpenAI、OpenRouter、Groq、Mistral、LLama、LM Studio | `AUTOREPLY_LLM_BASE_URL` + `AUTOREPLY_LLM_MODEL` |
完整的配置示例: [docs/autoreply.md](docs/autoreply.md).
### 哪里发送草稿:通知渠道
选择 **一个** 渠道;向导引导您完成每个渠道的凭证:
```bash
npm run autoreply:setup-notify
```
| 渠道 | 它是什么样子 | 需要 |
|---|---|---|
| **Telegram** | 机器人给您发送每个草稿 | 来自 @BotFather 的免费机器人(2 分钟) |
| **Slack** | 草稿发布到一个频道或 DM | 来自 webhook 或机器人令牌 + 频道 |
| **您的 WhatsApp 号码** | 网桥给您发送消息,您给自己发送消息 - 无需额外应用 | 仅需该号码 |
| **通用 webhook** | 原始 JSON POST 到任何 URL | 您的端点 |
选择的存储为 `.env` 中的 `AUTOREPLY_NOTIFY_CHANNEL`; 设置后,仅使用该渠道。 WhatsApp 渠道具有内置的循环保护:通知聊天中的消息被排除在自动回复处理之外,因此通知永远不会生成关于它自己的草稿。
步骤指南: [docs/sop-notifications-human.md](docs/sop-notifications-human.md)(适用于人)和 [docs/sop-notifications-agent.md](docs/sop-notifications-agent.md)(适用于执行设置的 AI 代理)。
### 控制策略
```bash
TOKEN=$(grep '^AUTOREPLY_TOKEN=' .env | cut -d= -f2)
# see current policy
curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8081/policy
# everything in draft mode (the safe default)
curl -s -X PUT -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
http://127.0.0.1:8081/policy \
-d '{"mode":"draft","scope":"all","contacts":[],"groups":[],"active_until":null,"active_hours":null}'
# kill switch
npm run autoreply:off
```
完整的引用(环境变量、路由、策略模式、安全门控): [docs/autoreply.md](docs/autoreply.md).
## 入站 webhook(可选)
设置 `.env` 中的 `WEBHOOK_URL` 并重启。每个入站消息都会 POST:
```json
{
"event": "message",
"session": "main",
"message": {
"id": "main:ABC123",
"ts": 1735689600000,
"direction": "in",
"chat_jid": "12025550100@s.whatsapp.net",
"from_jid": "12025550100@s.whatsapp.net",
"type": "text",
"body": "hi",
"from_display_name": "Jane Doe",
"from_phone": "+12025550100",
"chat_phone": "+12025550100"
}
}
```
如果设置了 `WEBHOOK_TOKEN`,它将作为 `Authorization: Bearer <token>` 到达,因此接收者可以验证来源。 三次尝试,1秒/3秒延迟,5秒尝试超时。 出站发送 **不** 触发 webhook。
## 配置
所有变量在启动时从 `.env` 读取;所有变量都是可选的,具有合理的默认值。
| 变量 | 默认值 | 备注 |
|---|---|---|
| `API_TOKEN` | 自动生成 | 32 字节十六进制令牌,写入 `.env` 启动时如果不存在。 |
| `WA_TZ` | 系统时区 | 在第一次启动时自动检测并持久化。 使用 IANA 名称 (`Europe/Berlin`, `America/New_York`) - DST 由 tzdata 处理。 |
| `HOST` | `127.0.0.1` | 仅在反向代理后面使用 `0.0.0.0`。 设置 `WA_ALLOW_PUBLIC=1` 以覆盖环回保护。 |
| `PORT` | `8080` | |
| `WEBHOOK_URL` | (禁用) | 如果设置,每个入站消息都会 POST 到此 URL。 |
| `WEBHOOK_TOKEN` | (无) | `WEBHOOK_URL` 的可选 bearer 令牌。 |
| `OPENAI_API_KEY` | (无) | 由 `npm run transcribe-backlog` 用于转录过去的音频消息。 |
## NPM 脚本
| 命令 | 用途 |
|---|---|
| `npm run start` | 启动守护进程。 |
| `npm run dev` | 同上,但启用 `tsx --watch`。 |
| `npm run pair -- main <数字>` | 配对一个号码。如果已有会话存在,则拒绝。 |
| `npm run history` | CLI 转储最近的消息,显示名称,本地时间。 |
| `npm run import-contacts -- --session=main` | 从 macOS Contacts.app 批量丰富联系人( `--refresh` , `--limit=N` )。 |
| `npm run transcribe-backlog` | 对过去无转录的音频消息运行 Whisper。 |
| `npm run backup` | 将 `data/wa.db` SQLite 数据库备份到 `data/backups/`。 |
| `npm run mcp` | 启动 MCP stdio 服务器(Claude Code 自动启动此服务器)。 |
| `npm run autoreply` | 启动本地自动回复 sidecar(独立进程)。参见 [docs/autoreply.md](docs/autoreply.md)。 |
| `npm run autoreply:dev` | 同上,但启用 `tsx --watch`。 |
| `npm run autoreply:build-corpus` | 从 WhatsApp 历史记录和第二大脑笔记中重建样式语料库。 |
| `npm run typecheck` | `tsc --noEmit` — 严格模式已启用。 |
请参阅 [COMMANDS.md](COMMANDS.md) 以获取完整的参考资料,包括 REST 示例和常见操作。
## 布局
```
src/
index.ts 启动 — 恢复配对会话 + 启动 API
wa.ts WaManager — 套接字,发送,历史同步,群组元数据刷新
db.ts SQLite 架构 + 查询(SQL 的单一真实来源)
api.ts Fastify REST + 静态 UI 路由
mcp.ts MCP stdio 服务器(与 api.ts 通信 HTTP)
webhook.ts 入站调度器,带有重试机制
env.ts .env 加载器 + API_TOKEN/WA_TZ 自动持久化
time.ts 格式化本地时间戳(ts)用于 CLI/日志
history.ts CLI:打印最近的消息,显示名称
web/index.html 单页 Web UI
auth/<名称>/ Baileys 会话密钥(gitignored)
auth_backups/ 自动备份 auth/(gitignored)
data/wa.db SQLite — 整个消息存储(gitignored)
data/media/ 下载的媒体(gitignored)
logs/ launchd/systemd 输出(gitignored)
.env 机密 + 配置(gitignored)
```
## 历史记录如何回填
`syncFullHistory: true` 在 `wa.ts` 中启用。 WhatsApp 在配对后通过 `messaging-history.set` 事件以批次方式提供历史记录转储。 网桥将聊天记录、联系人和消息导入 SQLite。 现有行通过主键进行去重。
强制刷新转储:
1. 在手机上,**设置 → 已连接设备** → 注销 whatsapp-bridge 条目。
2. `rm -rf auth/main/` 并重新运行 `npm run pair -- main <数字>`。
3. SQLite 消息和别名在擦除后存活(它们位于 `data/`,而不是 `auth/`)。
WhatsApp 决定向连接设备发送多少历史记录 — 通常是最近几个月的记录。 没有 API 可以请求 “从时间之初以来的一切”。
## LID 与手机 — 合并层
来自非联系人的入站消息通常带有 `<id>@lid` JID 而不是 `<phone>@s.whatsapp.net`。 WhatsApp 这样做是为了隐私。 同一个人可能因此显示为两个对话,直到合并。
```bash
TOKEN=$(grep '^API_TOKEN=' .env | cut -d= -f2)
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
http://127.0.0.1:8080/v1/aliases \
-d '{"session":"main","alias":"123456789@lid","canonical":"491761234567@s.whatsapp.net"}'
```
之后,`listConversations` 和 `listMessages` 在读取时将它们折叠。 电话和显示名称丰富然后在整个别名组中解析。 MCP 等效的是 `merge_jids` / `unmerge_jid`。
## 风险和限制
- **WhatsApp 可以封禁该号码。** 封禁率取决于您的使用情况看起来有多像垃圾邮件。 个人使用已知联系人通常是安全的; 冷推销则不然。
- **Baileys 故障。** WhatsApp 偶尔会发布协议更改; 预计每几个月更新一次 `@whiskeysockets/baileys`。 网桥具有内置的更新通知程序。
- **配对代码标识符。** 某些 `browser` 标识符会导致 WhatsApp 拒绝配对代码。 网桥使用 `Browsers.macOS('Safari')` 因为它已知是有效的 — 请勿在未经测试的情况下更改它。
- **历史同步大小。** 初始同步可以拉取数千条消息。 通常第一批消息在几秒钟内到达。
- **单个设备。** WhatsApp 对每个帐户的连接设备数量有限制。 配对 whatsapp-bridge 会占用一个插槽。
## 许可证
[MIT](LICENSE)。 您可以随心所欲地使用它; 请保留版权声明。 不提供任何保证。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器