weixin-bot-plugin
March 26, 2026 · View on GitHub
微信 Bot SDK — 通过 iLink Bot API 实现微信消息收发,基于 EventEmitter 的通用库。
来源
vendor/ 目录保存了 @tencent-weixin/openclaw-weixin 插件的原始源码(含 tgz 包)。
src/ 基于 vendor 代码泛化而来,核心逻辑(API、CDN、认证、媒体、消息类型)几乎一致,
主要差异在集成层:
- 移除 OpenClaw 框架依赖(plugin-sdk、PluginRuntime、Zod 配置验证)
- 移除 框架功能:slash-commands、debug-mode、error-notice、pairing、log-upload、monitor
- 新增
WeixinBotClient(EventEmitter 门面),替代 OpenClaw 的 ChannelPlugin 回调 - 新增
poll-loop.ts回调驱动轮询,替代 vendor 的monitor.ts框架轮询 - 自实现
stripMarkdown(),原版从openclaw/plugin-sdk导入
修改 src 时可参考 vendor 对应文件了解原始意图。
命令
pnpm build # tsup 打包(ESM,minified,含 .d.ts → dist/)
pnpm typecheck # tsc --noEmit 类型检查(使用 tsconfig.build.json,排除测试文件)
pnpm test # vitest run 运行所有测试
pnpm test:watch # vitest watch 模式
pnpm dev # tsup --watch 开发模式
架构
src/
├── index.ts # barrel 导出
├── client.ts # WeixinBotClient 门面类(EventEmitter)
├── poll/
│ └── poll-loop.ts # getUpdates long-poll 循环(回调驱动)
├── api/ # iLink Bot API 通信层(HTTP POST/GET)
│ ├── config-cache.ts # getConfig 缓存(TTL)
│ └── session-guard.ts # session 过期追踪(errcode -14)
├── auth/ # 账号存储 + 扫码登录
├── cdn/ # 微信 CDN 加解密上传下载
│ ├── cdn-upload.ts # CDN 上传参数构建
│ └── upload.ts # 文件加密上传
├── media/ # 媒体下载解密、MIME 判断、SILK 语音转码
├── messaging/ # 消息解析(inbound)、发送(send/send-media)
├── storage/ # 持久化存储
│ ├── state-dir.ts # 统一状态目录管理(module-level setter)
│ └── sync-buf.ts # getUpdates 同步断点
└── util/ # 日志(stderr)、ID 生成、脱敏
关键设计
WeixinBotClient是主入口,封装所有微信功能,通过 EventEmitter 推送事件- 不依赖 MCP SDK 或任何 Claude Code 概念,可被任何 JS/TS 项目使用
- 存储路径通过
WeixinBotConfig.stateDir配置,默认~/.weixin-bot/ - 临时媒体文件通过
WeixinBotConfig.tempDir配置 sendText()默认自动 Markdown → 纯文本转换,raw: true跳过- Typing 手动管理:调用方自行控制
startTyping/stopTyping时机 - 登录两阶段:
login()返回 QR 码,后台轮询成功触发loginSuccess事件
注意事项
- 包管理器 pnpm,构建工具 tsup,target node22,ESM only
- 模块级 setter(
setStateDir)意味着同一进程只能有一个WeixinBotClient实例 contextToken由库内部自动管理(poll-loop 提取 → 内存缓存 → 磁盘持久化 → 发送时携带),缺失时 warn 而非抛错- session 过期(errcode -14)触发
sessionExpired事件,需调用方重新 login silk-wasm是可选依赖,SILK 转码失败时优雅降级为原始格式
存储文件布局
stateDir 默认 ~/.weixin-bot/,内部结构:
<stateDir>/
├── accounts.json # 已注册账户 ID 列表
├── accounts/
│ ├── <accountId>.json # 账户凭证(chmod 0o600)
│ └── <accountId>.context-tokens.json # 每个 chatId 的 contextToken 缓存
└── sync/
└── <accountId>.sync.json # getUpdates 同步断点(buf)
代码风格
- ESM 相对导入带
.js后缀:import { logger } from "../util/logger.js" - Node 内置模块用
node:前缀:import os from "node:os" - 私有变量
_前缀,类型用Params/Options/Result后缀 - 测试框架 Vitest,测试文件与源码同级放置(
*.test.ts) - 测试中显式
import { describe, it, expect } from "vitest",不使用全局注入 tsconfig.build.json排除测试文件,tsconfig.json保留(IDE 支持)
测试
- 依赖通过
vi.mock("../path/to/module.js")在文件顶层 mock,mock 路径需带.js后缀 beforeEach中调用vi.clearAllMocks()重置状态- 常用 fixture 命名:
acc-1(accountId)、tok-1(token)、user-1(userId)、chat-1(chatId) - client.test.ts 通过 mock 整个
poll-loop.ts模块来测试 client 生命周期,用mockStartPollLoop.mock.calls[0][0]取回调后手动触发
环境变量
LOG_LEVEL— debug/info/warn/error,默认 info
事件
5 个事件:message、loginSuccess、qrRefresh、sessionExpired、error,详见 README。