--- sidebar_position: 15 title: "微信(Weixin)" description: "通过 iLink Bot API 将 Hermes Agent 连接到个人微信账号" --- # 微信(Weixin / WeChat) 将 Hermes 连接到 [微信](https://weixin.qq.com/)(WeChat),腾讯的个人即时通讯平台。该适配器使用腾讯的 **iLink Bot API** 对接个人微信账号——与企业微信(WeCom)不同。消息通过长轮询(long-polling)方式传递,无需公网端点或 webhook。 :::info 本适配器适用于**个人微信账号**(微信)。如需对接企业微信,请参阅 [WeCom 适配器](./wecom.md)。 ::: :::warning iLink bot 身份——普通微信群可能无法使用 扫码登录后,Hermes 连接的是一个 **iLink bot 身份**(例如 `a5ace6fd482e@im.bot`),**而非**可完全脚本化的普通个人微信账号。具体影响如下: - iLink bot 身份通常**无法像普通联系人一样被邀请进入普通微信群**。 - 对于大多数 bot 类型账号,iLink 通常**不会将普通微信群事件**(包括对扫码登录所用个人账号的 `@` 提及)推送到网关。 - `@` 提及用于扫码的个人微信账号,**不等同于** `@` 提及 iLink bot——两者是独立身份。 - 下方的 `WEIXIN_GROUP_POLICY` / `WEIXIN_GROUP_ALLOWED_USERS` 设置仅在 iLink 实际为你的账号类型返回群事件时才生效。若 iLink 不返回群事件,无论策略如何配置,群消息都不会到达 Hermes。 实际部署中,大多数情况下只有发送给 iLink bot 的私信(DM)能可靠工作。若配置完成后群消息仍无法送达,限制来自 iLink 侧,而非 Hermes。只要 `WEIXIN_GROUP_POLICY` 设置为 `disabled` 以外的值,网关在启动时会记录一条 `WARNING`。 ::: ## 前置条件 - 一个个人微信账号 - Python 包:`aiohttp` 和 `cryptography` - 使用 `messaging` 扩展安装 Hermes 时已内置终端二维码渲染功能 安装所需依赖: ```bash pip install aiohttp cryptography # 可选:用于终端二维码显示 pip install hermes-agent[messaging] ``` ## 配置步骤 ### 1. 运行配置向导 连接微信账号最简便的方式是通过交互式配置向导: ```bash hermes gateway setup ``` 在提示中选择 **Weixin**。向导将执行以下步骤: 1. 向 iLink Bot API 请求二维码 2. 在终端中显示二维码(或提供 URL) 3. 等待你用微信手机端扫描二维码 4. 提示你在手机上确认登录 5. 自动将账号凭据保存至 `~/.hermes/weixin/accounts/` 确认后,你将看到如下消息: ``` 微信连接成功,account_id=your-account-id ``` 向导会保存 `account_id`、`token` 和 `base_url`,无需手动配置。 ### 2. 配置环境变量 完成首次扫码登录后,在 `~/.hermes/.env` 中至少设置账号 ID: ```bash WEIXIN_ACCOUNT_ID=your-account-id # 可选:覆盖 token(通常由扫码登录自动保存) # WEIXIN_TOKEN=your-bot-token # 可选:限制访问权限 WEIXIN_DM_POLICY=open WEIXIN_ALLOWED_USERS=user_id_1,user_id_2 # 可选:恢复旧版多行拆分行为 # WEIXIN_SPLIT_MULTILINE_MESSAGES=true # 可选:cron/通知的默认频道 WEIXIN_HOME_CHANNEL=chat_id WEIXIN_HOME_CHANNEL_NAME=Home ``` ### 3. 启动网关 ```bash hermes gateway ``` 适配器将恢复已保存的凭据,连接到 iLink API,并开始长轮询消息。 ## 功能特性 - **长轮询传输** — 无需公网端点、webhook 或 WebSocket - **扫码登录** — 通过 `hermes gateway setup` 扫码连接 - **私信(DM)消息** — 可配置访问策略;群消息功能取决于 iLink 是否实际为所连接身份推送群事件(iLink bot 账号通常不推送,详见上方警告) - **媒体支持** — 图片、视频、文件和语音消息 - **AES-128-ECB 加密 CDN** — 所有媒体传输自动加解密 - **上下文 token 持久化** — 基于磁盘的回复连续性,重启后仍可保持 - **Markdown 格式化** — 保留 Markdown 格式(包括标题、表格和代码块),支持 Markdown 的微信客户端可原生渲染 - **智能消息分块** — 未超出长度限制时保持单条消息气泡;仅超长内容在逻辑边界处拆分 - **正在输入提示** — 代理处理消息时在微信客户端显示"正在输入…"状态 - **SSRF 防护** — 下载前验证外发媒体 URL - **消息去重** — 5 分钟滑动窗口防止重复处理 - **自动重试与退避** — 从瞬时 API 错误中自动恢复 ## 配置选项 在 `config.yaml` 的 `platforms.weixin.extra` 下设置: | 键 | 默认值 | 说明 | |-----|---------|-------------| | `account_id` | — | iLink Bot 账号 ID(必填) | | `token` | — | iLink Bot token(必填,由扫码登录自动保存) | | `base_url` | `https://ilinkai.weixin.qq.com` | iLink API 基础 URL | | `cdn_base_url` | `https://novac2c.cdn.weixin.qq.com/c2c` | 媒体传输 CDN 基础 URL | | `dm_policy` | `open` | 私信访问策略:`open`、`allowlist`、`disabled`、`pairing` | | `group_policy` | `disabled` | 群组访问策略:`open`、`allowlist`、`disabled` | | `allow_from` | `[]` | 允许发送私信的用户 ID(当 dm_policy=allowlist 时生效) | | `group_allow_from` | `[]` | 允许的群组 ID(当 group_policy=allowlist 时生效) | | `split_multiline_messages` | `false` | 为 `true` 时,将多行回复拆分为多条消息(旧版行为);为 `false` 时,多行回复保持为单条消息,除非超出长度限制。 | ## 访问策略 ### 私信策略 控制哪些用户可以向 bot 发送私信: | 值 | 行为 | |-------|----------| | `open` | 任何人均可向 bot 发送私信(默认) | | `allowlist` | 仅 `allow_from` 中的用户 ID 可发送私信 | | `disabled` | 忽略所有私信 | | `pairing` | 配对模式(用于初始设置) | ```bash WEIXIN_DM_POLICY=allowlist WEIXIN_ALLOWED_USERS=user_id_1,user_id_2 ``` ### 群组策略 控制 bot 在哪些群组中响应消息,**前提是 iLink 为所连接身份推送了群事件**。对于扫码登录的 iLink bot 身份(例如 `...@im.bot`),群事件通常根本不会被推送,因此该策略可能不起作用——详见页面顶部的 iLink bot 限制警告。 | 值 | 行为 | |-------|----------| | `open` | bot 在所有群组中响应(如果事件被推送) | | `allowlist` | bot 仅在 `group_allow_from` 中列出的群组 ID 中响应(如果事件被推送) | | `disabled` | 忽略所有群消息(默认) | ```bash WEIXIN_GROUP_POLICY=allowlist # 注意:这是以逗号分隔的群聊 ID 列表,而非成员用户 ID, # 尽管变量名中包含"USERS"。配置时请注意区分。 WEIXIN_GROUP_ALLOWED_USERS=group_id_1,group_id_2 ``` :::note 微信的默认群组策略为 `disabled`(与企业微信默认为 `open` 不同)。这是有意为之——个人微信账号可能加入了很多群,且 iLink bot 身份通常根本无法接收普通微信群消息。若将 `WEIXIN_GROUP_POLICY` 设置为 `disabled` 以外的值,网关在启动时会记录一条 `WARNING`。 ::: ## 媒体支持 ### 入站(接收) 适配器接收用户发送的媒体附件,从微信 CDN 下载并解密,然后在本地缓存供代理处理: | 类型 | 处理方式 | |------|-----------------| | **图片** | 下载、AES 解密后缓存为 JPEG。 | | **视频** | 下载、AES 解密后缓存为 MP4。 | | **文件** | 下载、AES 解密后缓存,保留原始文件名。 | | **语音** | 若有文字转录,则提取为文本;否则下载音频(SILK 格式)并缓存。 | **引用消息:** 引用(回复)消息中的媒体也会被提取,以便代理了解用户回复的上下文。 ### AES-128-ECB 加密 CDN 微信媒体文件通过加密 CDN 传输。适配器透明处理加解密: - **入站:** 使用 `encrypted_query_param` URL 从 CDN 下载加密媒体,再使用消息载荷中提供的每文件密钥进行 AES-128-ECB 解密。 - **出站:** 使用随机 AES-128-ECB 密钥在本地加密文件,上传至 CDN,并在出站消息中包含加密引用。 - AES 密钥为 16 字节(128 位)。密钥可能以原始 base64 或十六进制编码形式到达——适配器两种格式均支持。 - 需要安装 `cryptography` Python 包。 无需任何配置——加解密自动完成。 ### 出站(发送) | 方法 | 发送内容 | |--------|--------------| | `send` | 带 Markdown 格式的文本消息 | | `send_image` / `send_image_file` | 原生图片消息(通过 CDN 上传) | | `send_document` | 文件附件(通过 CDN 上传) | | `send_video` | 视频消息(通过 CDN 上传) | 所有出站媒体均通过加密 CDN 上传流程处理: 1. 生成随机 AES-128 密钥 2. 使用 AES-128-ECB + PKCS#7 填充加密文件 3. 向 iLink API 请求上传 URL(`getuploadurl`) 4. 将密文上传至 CDN 5. 发送包含加密媒体引用的消息 ## 上下文 Token 持久化 iLink Bot API 要求在每条出站消息中回传 `context_token`(针对特定对话方)。适配器维护一个基于磁盘的上下文 token 存储: - Token 按账号+对话方保存至 `~/.hermes/weixin/accounts/.context-tokens.json` - 启动时恢复之前保存的 token - 每条入站消息都会更新该发送方的已存储 token - 出站消息自动包含最新的上下文 token 这确保了即使网关重启后,回复连续性也不会中断。 ## Markdown 格式化 通过 iLink Bot API 连接的微信客户端可以直接渲染 Markdown,因此适配器保留 Markdown 而不对其进行改写: - **标题** 保持为 Markdown 标题格式(`#`、`##` 等) - **表格** 保持为 Markdown 表格 - **代码围栏** 保持为围栏代码块 - **多余空行** 在围栏代码块外折叠为双换行 ## 消息分块 消息在不超出平台限制时以单条消息发送。仅超长内容才会被拆分发送: - 最大消息长度:**4000 个字符** - 未超出限制的消息保持完整,即使包含多个段落或换行 - 超长消息在逻辑边界处拆分(段落、空行、代码围栏) - 代码围栏尽可能保持完整(除非围栏本身超出限制,否则不在块中间拆分) - 超长的单个块回退到基础适配器的截断逻辑 - 发送多个分块时,块间延迟 0.3 秒,防止触发微信频率限制 ## 正在输入提示 适配器在微信客户端中显示输入状态: 1. 消息到达时,适配器通过 `getconfig` API 获取 `typing_ticket` 2. 输入票据(typing ticket)按用户缓存 10 分钟 3. `send_typing` 发送开始输入信号;`stop_typing` 发送停止输入信号 4. 网关在代理处理消息期间自动触发输入提示 ## 长轮询连接 适配器使用 HTTP 长轮询(而非 WebSocket)接收消息: ### 工作原理 1. **连接:** 验证凭据并启动轮询循环 2. **轮询:** 以 35 秒超时调用 `getupdates`;服务器保持请求直到消息到达或超时 3. **分发:** 入站消息通过 `asyncio.create_task` 并发分发 4. **同步缓冲区:** 持久化同步游标(`get_updates_buf`)保存至磁盘,确保重启后从正确位置恢复 ### 重试行为 发生 API 错误时,适配器采用简单的重试策略: | 条件 | 行为 | |-----------|----------| | 瞬时错误(第 1–2 次) | 2 秒后重试 | | 持续错误(第 3 次及以上) | 退避 30 秒后重置计数器 | | 会话过期(`errcode=-14`) | 暂停 10 分钟(可能需要重新登录) | | 超时 | 立即重新轮询(正常长轮询行为) | ### 去重 入站消息使用消息 ID 在 5 分钟窗口内去重,防止网络抖动或轮询响应重叠时重复处理。 ### Token 锁 同一时间只有一个微信网关实例可以使用给定的 token。适配器在启动时获取作用域锁,关闭时释放。若另一个网关已在使用相同 token,启动将失败并显示详细错误信息。 ## 所有环境变量 | 变量 | 必填 | 默认值 | 说明 | |----------|----------|---------|-------------| | `WEIXIN_ACCOUNT_ID` | ✅ | — | iLink Bot 账号 ID(来自扫码登录) | | `WEIXIN_TOKEN` | ✅ | — | iLink Bot token(由扫码登录自动保存) | | `WEIXIN_BASE_URL` | — | `https://ilinkai.weixin.qq.com` | iLink API 基础 URL | | `WEIXIN_CDN_BASE_URL` | — | `https://novac2c.cdn.weixin.qq.com/c2c` | 媒体传输 CDN 基础 URL | | `WEIXIN_DM_POLICY` | — | `open` | 私信访问策略:`open`、`allowlist`、`disabled`、`pairing` | | `WEIXIN_GROUP_POLICY` | — | `disabled` | 群组访问策略:`open`、`allowlist`、`disabled` | | `WEIXIN_ALLOWED_USERS` | — | _(空)_ | 私信白名单的逗号分隔用户 ID | | `WEIXIN_GROUP_ALLOWED_USERS` | — | _(空)_ | 群组白名单的逗号分隔**群聊 ID**(非成员用户 ID)。变量名为历史遗留,实际填写的是群 ID 而非用户 ID。 | | `WEIXIN_HOME_CHANNEL` | — | — | cron/通知输出的聊天 ID | | `WEIXIN_HOME_CHANNEL_NAME` | — | `Home` | 默认频道的显示名称 | | `WEIXIN_ALLOW_ALL_USERS` | — | — | 网关级别的允许所有用户标志(由配置向导使用) | ## 故障排查 | 问题 | 解决方法 | |---------|-----| | `Weixin startup failed: aiohttp and cryptography are required` | 安装两者:`pip install aiohttp cryptography` | | `Weixin startup failed: WEIXIN_TOKEN is required` | 运行 `hermes gateway setup` 完成扫码登录,或手动设置 `WEIXIN_TOKEN` | | `Weixin startup failed: WEIXIN_ACCOUNT_ID is required` | 在 `.env` 中设置 `WEIXIN_ACCOUNT_ID`,或运行 `hermes gateway setup` | | `Another local Hermes gateway is already using this Weixin token` | 先停止另一个网关实例——每个 token 只允许一个轮询器 | | 会话过期(`errcode=-14`) | 登录会话已过期。重新运行 `hermes gateway setup` 扫描新二维码 | | 配置过程中二维码过期 | 二维码最多自动刷新 3 次。若持续过期,请检查网络连接 | | Bot 不响应私信 | 检查 `WEIXIN_DM_POLICY`——若设置为 `allowlist`,发送方必须在 `WEIXIN_ALLOWED_USERS` 中 | | Bot 忽略群消息 | 群组策略默认为 `disabled`。设置 `WEIXIN_GROUP_POLICY=open` 或 `allowlist`——但请注意,扫码登录的 iLink bot 身份(`...@im.bot`)通常根本无法接收普通微信群消息。若网关日志中没有群消息的原始入站事件,限制来自 iLink 侧,而非 Hermes。 | | 媒体下载/上传失败 | 确保已安装 `cryptography`。检查对 `novac2c.cdn.weixin.qq.com` 的网络访问 | | `Blocked unsafe URL (SSRF protection)` | 出站媒体 URL 指向私有/内部地址,仅允许公网 URL | | 语音消息显示为文本 | 若微信提供了转录文本,适配器会使用文本内容,这是预期行为 | | 消息出现重复 | 适配器通过消息 ID 去重。若仍出现重复,检查是否有多个网关实例在运行 | | `iLink POST ... HTTP 4xx/5xx` | iLink 服务返回 API 错误。检查 token 有效性和网络连通性 | | 终端二维码无法渲染 | 使用 messaging 扩展重新安装:`pip install hermes-agent[messaging]`。或者,打开二维码上方打印的 URL |