企业微信智能机器人 Python 插件
企业微信智能机器人的 Python SDK,基于 WebSocket 长连接实现,功能完整、易于使用。
📋 目录
✨ 特性
核心能力
- ✅ WebSocket 长连接 - 稳定的双向通信,自动心跳保活
- ✅ 消息收发 - 支持文本、图片、视频、文件等多种消息类型
- ✅ 分片上传 - 支持大文件上传(最大 100MB)
- ✅ 媒体下载 - 自动下载并解密媒体文件
- ✅ 访问控制 - 灵活的 DM 和群聊权限管理
- ✅ 自动重连 - 网络断开自动重连(最多 100 次)
- ✅ 消息历史 - 内置消息历史存储
- ✅ 类型安全 - 完整的类型注解
技术特点
- 🔒 线程安全 - 使用锁保护共享资源
- 🎯 响应等待 - 同步等待服务器响应,确保可靠性
- 📝 详细日志 - 完整的调试和错误日志
- 🔄 兼容性 - 与 Node.js 版本协议完全兼容
- 🚀 高性能 - 异步处理,支持高并发
🚀 快速开始
安装依赖
bash
pip install -r requirements.txt
依赖包:
websocket-client>=1.6.0- WebSocket 客户端requests>=2.28.0- HTTP 请求pyyaml>=6.0- YAML 配置解析pycryptodome>=3.15.0- AES 加密解密
基础使用
1. 创建配置文件
复制 config.yaml.example 为 config.yaml:
yaml
channels:
wecom:
botId: "你的机器人ID"
secret: "你的机器人密钥"
dmPolicy: "open"
groupPolicy: "open"
2. 运行示例程序
bash
# 启动主程序(接收消息并自动下载媒体)
python src/main.py --config config.yaml
# 发送文本消息
python test_upload.py --bot-id YOUR_BOT_ID --secret YOUR_SECRET --chat-id CHAT_ID --text "你好"
# 发送图片
python test_upload.py --bot-id YOUR_BOT_ID --secret YOUR_SECRET --chat-id CHAT_ID --file-path image.jpg
3. 代码示例
python
from src import create_plugin, WeComMessage
# 创建插件实例
plugin = create_plugin(
bot_id="your_bot_id",
secret="your_secret"
)
# 设置消息回调
def on_message(msg: WeComMessage):
print(f"收到消息: {msg.content}")
# 回复消息
plugin.reply_message(msg, "收到你的消息了")
plugin.on_message = on_message
# 启动插件
plugin.start()
🏗️ 项目架构
目录结构
wecom-python-plugin/
├── src/ # 核心源代码
│ ├── __init__.py # 主插件实现
│ ├── main.py # 应用入口
│ └── handlers.py # 消息历史存储
├── sender_api.py # API 发送接口
│ ├── text # 文本消息发送
│ ├── image # 图片消息发送
│ ├── vedio # 视频消息发送
│ └── file # 文件消息发送
├── downloader.py # 媒体下载器(图片/视频/文件)
├── config.yaml # 配置文件
├── requirements.txt # Python 依赖
└── download/ # 媒体文件下载目录
核心模块
WeComClient - WebSocket 客户端
负责与企业微信服务器的 WebSocket 通信:
- 连接管理 - 建立连接、认证、心跳、重连
- 消息收发 - 接收推送、发送消息
- 媒体上传 - 分片上传大文件
- 响应等待 - 同步等待服务器响应
MessageParser - 消息解析器
解析企业微信推送的消息:
- 提取消息字段(ID、类型、内容等)
- 处理不同消息类型(文本、图片、视频、文件)
- 创建
WeComMessage对象
AccessController - 访问控制器
管理消息访问权限:
MediaDownloader - 媒体下载器
下载和解密媒体文件:
- HTTP 下载
- AES-256-CBC 解密
- 文件类型检测(Magic Bytes)
- 自动保存
🔧 核心功能
1. 消息接收
支持接收以下类型的消息:
| 消息类型 | 说明 | 字段 |
|---|---|---|
| text | 文本消息 | content, mentioned_list |
| image | 图片消息 | url, aeskey, media_id |
| video | 视频消息 | url, aeskey, media_id |
| voice | 语音消息 | url, aeskey, media_id |
| file | 文件消息 | url, aeskey, media_id, filename |
消息对象结构:
python
@dataclass
class WeComMessage:
msg_id: str # 消息ID
msg_type: str # 消息类型
chat_type: str # 会话类型(single/group)
chat_id: str # 会话ID
from_user_id: str # 发送者ID
from_user_name: str # 发送者名称
content: str # 消息内容
media_url: str # 媒体URL
mentioned_list: List[str] # @成员列表
timestamp: int # 时间戳
raw_body: Dict[str, Any] # 原始消息体
2. 消息发送
发送文本消息
python
# 单聊
plugin.send_message("user_id", "你好")
# 群聊(@成员)
plugin.send_message("group_id", "大家好", mentioned_list=["user1", "user2"])
# 回复消息
plugin.reply_message(msg, "收到你的消息了")
发送图片
python
plugin.client.send_image("chat_id", "path/to/image.jpg")
支持格式: JPG, PNG, GIF, BMP
大小限制: ≤ 10MB
发送视频
python
plugin.client.send_video(
"chat_id",
"path/to/video.mp4",
title="视频标题",
description="视频描述"
)
支持格式: MP4
大小限制: ≤ 100MB
分辨率建议: ≤ 1920×1080
编码要求: H.264 + AAC
发送文件
python
plugin.client.send_file("chat_id", "path/to/document.pdf")
支持格式: PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, ZIP 等
大小限制: ≤ 100MB
3. 媒体上传机制
分片上传流程
1. 初始化上传
├─ 发送文件信息(大小、MD5、分片数)
└─ 获取 upload_id
2. 分片上传
├─ 每片 512KB
├─ Base64 编码
├─ 最多 200 片(约 100MB)
└─ 等待每片确认
3. 完成上传
├─ 发送完成请求
└─ 获取 media_id
4. 发送消息
└─ 使用 media_id 发送
上传参数
python
CHUNK_SIZE = 512 * 1024 # 分片大小:512KB
MAX_CHUNKS = 200 # 最大分片数:200(约100MB)
修改大小限制:
如需支持更大文件,修改 src/__init__.py 中的 MAX_CHUNKS 参数:
python
MAX_CHUNKS = 400 # 支持约 200MB
4. 媒体下载机制
自动下载
主程序会自动下载收到的媒体文件:
python
# 在 src/main.py 中已实现
def _on_message(self, msg: WeComMessage):
if msg.msg_type in ["image", "video", "file"]:
result = self.downloader.download_from_callback(
msg.raw_body,
self.download_dir
)
if result:
print(f"文件已保存: {result['file_path']}")
手动下载
python
from downloader import MediaDownloader
downloader = MediaDownloader()
# 从回调消息下载
result = downloader.download_from_callback(
callback_data=msg.raw_body,
save_dir="./download"
)
# 从URL下载
data = downloader.download_from_url(
url="https://...",
aeskey="base64_encoded_key",
save_path="output.jpg"
)
AES 解密
企业微信的媒体文件使用 AES-256-CBC 加密:
- 算法: AES-256-CBC
- IV: 密钥的前 16 字节
- 密钥格式: URL 安全的 Base64 编码
下载器会自动处理解密过程。
文件类型检测
通过 Magic Bytes 自动识别文件类型:
| 格式 | Magic Bytes | 扩展名 |
|---|---|---|
| JPEG | FF D8 FF |
.jpg |
| PNG | 89 50 4E 47 |
.png |
| MP4 | 00 00 00 ?? 66 74 79 70 |
.mp4 |
25 50 44 46 |
||
| ZIP | 50 4B 03 04 |
.zip |
支持 30+ 种文件格式的自动识别。
5. 访问控制
DM(私聊)策略
yaml
dmPolicy: "open" # 策略类型
allowFrom: ["user1", "user2"] # 白名单(allowlist模式)
策略类型:
pairing- 配对模式(需要用户首次发送消息批准)open- 开放模式(所有用户可发送)allowlist- 白名单模式(只允许白名单用户)disabled- 禁用模式(不接收任何私聊)
群聊策略
yaml
groupPolicy: "open" # 策略类型
groupAllowFrom: ["group1", "group2"] # 白名单
策略类型:
open- 开放模式(所有群聊可用)allowlist- 白名单模式(只允许白名单群聊)disabled- 禁用模式(不接收任何群消息)
6. 连接管理
心跳保活
python
HEARTBEAT_INTERVAL_MS = 30000 # 30秒发送一次心跳
自动发送 ping 帧保持连接活跃。
自动重连
python
MAX_RECONNECT_ATTEMPTS = 100 # 最多重连100次
连接断开时自动重连,使用指数退避策略。
连接状态回调
python
def on_connected():
print("已连接")
def on_disconnected(reason):
print(f"已断开: {reason}")
def on_error(error):
print(f"错误: {error}")
plugin.on_connected = on_connected
plugin.on_disconnected = on_disconnected
plugin.on_error = on_error
📚 API 文档
插件初始化
方式1:使用工厂函数(推荐)
python
from src import create_plugin
plugin = create_plugin(
bot_id="your_bot_id",
secret="your_secret",
dm_policy="open",
group_policy="open"
)
方式2:手动创建
python
from src import WeComPlugin, WeComConfig
config = WeComConfig(
bot_id="your_bot_id",
secret="your_secret",
dm_policy="open",
allow_from=["user1", "user2"],
group_policy="allowlist",
group_allow_from=["group1"],
websocket_url="wss://openws.work.weixin.qq.com"
)
plugin = WeComPlugin(config)
WeComPlugin 类
方法
| 方法 | 说明 | 参数 |
|---|---|---|
start() |
启动插件 | 无 |
stop() |
停止插件 | 无 |
send_message(chat_id, content, mentioned_list) |
发送文本消息 | chat_id, content, mentioned_list=[] |
reply_message(msg, content) |
回复消息 | msg, content |
回调函数
| 回调 | 说明 | 参数 |
|---|---|---|
on_message |
收到消息 | msg: WeComMessage |
on_connected |
连接成功 | 无 |
on_disconnected |
连接断开 | reason: str |
on_error |
发生错误 | error: Exception |
WeComClient 类
方法
| 方法 | 说明 | 返回值 |
|---|---|---|
send_text(chat_id, content, mentioned_list) |
发送文本 | bool |
send_image(chat_id, image_path) |
发送图片 | bool |
send_video(chat_id, video_path, title, description) |
发送视频 | bool |
send_file(chat_id, file_path) |
发送文件 | bool |
upload_media(file_path, media_type) |
上传媒体 | media_id: str |
MediaDownloader 类
方法
| 方法 | 说明 | 返回值 |
|---|---|---|
download_from_callback(callback_data, save_dir) |
从回调下载 | Dict |
download_from_url(url, aeskey, save_path) |
从URL下载 | bytes |
download_image(image_data, save_path) |
下载图片 | bytes |
download_video(video_data, save_path) |
下载视频 | bytes |
download_file(file_data, save_path) |
下载文件 | bytes |
⚙️ 配置说明
完整配置示例
yaml
channels:
wecom:
# 必填:机器人凭证
botId: "aibhpZhzMDvtrBcmnCq0N0FEbCx78CIQOhQ"
secret: "kOtU0A4wKCGfViGin1zjcp6ClwBdlyae1AOmUY2Yjmm"
# 可选:WebSocket地址(默认值)
websocketUrl: "wss://openws.work.weixin.qq.com"
# DM访问策略
dmPolicy: "open" # pairing | open | allowlist | disabled
allowFrom: [] # DM白名单
# 群聊访问策略
groupPolicy: "open" # open | allowlist | disabled
groupAllowFrom: [] # 群聊白名单
# 日志配置
logging:
level: INFO # DEBUG | INFO | WARNING | ERROR
file: "wecom.log"
# 消息存储配置
storage:
history_days: 180 # 历史消息保留天数
type: memory # memory | mysql | postgres
# AI配置(可选)
ai:
enabled: false
api_url: ""
api_key: ""
配置加载优先级
-
命令行参数(最高优先级)
bashpython src/main.py --bot-id XXX --secret XXX -
配置文件
bashpython src/main.py --config config.yaml -
OpenClaw配置 (自动读取
~/.openclaw/config.json)
💡 使用示例
示例1:简单的回声机器人
python
from src import create_plugin, WeComMessage
plugin = create_plugin(
bot_id="your_bot_id",
secret="your_secret"
)
def on_message(msg: WeComMessage):
# 回复相同的内容
plugin.reply_message(msg, f"你说: {msg.content}")
plugin.on_message = on_message
plugin.start()
示例2:自动回复机器人
python
from src import create_plugin, WeComMessage
plugin = create_plugin(
bot_id="your_bot_id",
secret="your_secret"
)
# 关键词回复字典
REPLIES = {
"你好": "你好!有什么可以帮助你的吗?",
"帮助": "我可以回答你的问题,发送图片、视频等。",
"再见": "再见!祝你有美好的一天!"
}
def on_message(msg: WeComMessage):
content = msg.content.strip()
# 查找匹配的关键词
for keyword, reply in REPLIES.items():
if keyword in content:
plugin.reply_message(msg, reply)
return
# 默认回复
plugin.reply_message(msg, "抱歉,我不太明白你的意思。")
plugin.on_message = on_message
plugin.start()
示例3:图片处理机器人
python
from src import create_plugin, WeComMessage
from downloader import MediaDownloader
from PIL import Image
plugin = create_plugin(
bot_id="your_bot_id",
secret="your_secret"
)
downloader = MediaDownloader()
def on_message(msg: WeComMessage):
if msg.msg_type == "image":
# 下载图片
result = downloader.download_from_callback(
msg.raw_body,
"./download"
)
if result:
# 处理图片(例如:转换为灰度)
img = Image.open(result['file_path'])
gray_img = img.convert('L')
gray_path = result['file_path'].replace('.jpg', '_gray.jpg')
gray_img.save(gray_path)
# 发送处理后的图片
plugin.client.send_image(msg.chat_id, gray_path)
plugin.reply_message(msg, "已将图片转换为灰度")
plugin.on_message = on_message
plugin.start()
示例4:群聊管理机器人
python
from src import create_plugin, WeComMessage
plugin = create_plugin(
bot_id="your_bot_id",
secret="your_secret",
group_policy="open"
)
def on_message(msg: WeComMessage):
# 只处理群聊消息
if msg.chat_type != "group":
return
content = msg.content.strip()
# @所有人
if content == "/all":
plugin.send_message(
msg.chat_id,
"大家好!",
mentioned_list=["@all"]
)
# 欢迎新成员
elif content.startswith("/welcome"):
user_id = content.split()[1] if len(content.split()) > 1 else None
if user_id:
plugin.send_message(
msg.chat_id,
f"欢迎新成员!",
mentioned_list=[user_id]
)
plugin.on_message = on_message
plugin.start()
示例5:文件管理机器人
python
from src import create_plugin, WeComMessage
from downloader import MediaDownloader
import os
plugin = create_plugin(
bot_id="your_bot_id",
secret="your_secret"
)
downloader = MediaDownloader()
def on_message(msg: WeComMessage):
if msg.msg_type == "file":
# 下载文件
result = downloader.download_from_callback(
msg.raw_body,
"./files"
)
if result:
file_path = result['file_path']
file_size = os.path.getsize(file_path)
file_ext = result['file_ext']
# 发送文件信息
info = f"文件已保存\n"
info += f"文件名: {os.path.basename(file_path)}\n"
info += f"大小: {file_size / 1024:.2f} KB\n"
info += f"类型: {file_ext}"
plugin.reply_message(msg, info)
plugin.on_message = on_message
plugin.start()
🔍 常见问题
Q1: 如何获取 bot_id 和 secret?
- 登录企业微信管理后台
- 进入"应用管理" → "智能机器人"
- 创建或选择一个机器人
- 查看机器人详情获取 bot_id 和 secret
Q2: 视频上传失败,错误码 40011?
原因: 视频不符合企业微信要求
解决方案:
-
检查视频分辨率(建议 ≤ 1920×1080)
-
检查视频编码(使用 H.264 + AAC)
-
检查视频大小(≤ 50MB)
-
使用 FFmpeg 转换:
bashffmpeg -i input.mp4 -vf scale=1280:720 -c:v libx264 -crf 23 -c:a aac -b:a 128k output.mp4
Q3: 如何支持更大的文件?
修改 src/__init__.py 中的 MAX_CHUNKS 参数:
python
MAX_CHUNKS = 200 # 默认100MB
MAX_CHUNKS = 400 # 支持200MB
Q4: 连接频繁断开怎么办?
检查项:
- 网络连接是否稳定
- 防火墙是否阻止 WebSocket 连接
- bot_id 和 secret 是否正确
- 查看日志中的错误信息
解决方案:
- 启用自动重连(默认已启用)
- 增加心跳间隔
- 检查服务器时间是否同步
Q5: 如何调试?
启用 DEBUG 日志:
python
import logging
logging.basicConfig(level=logging.DEBUG)
或在配置文件中:
yaml
logging:
level: DEBUG
查看详细日志:
- WebSocket 连接状态
- 消息收发详情
- 上传下载进度
- 错误堆栈信息
Q6: 支持哪些消息类型?
| 类型 | 接收 | 发送 | 说明 |
|---|---|---|---|
| 文本 | ✅ | ✅ | 支持 @成员 |
| 图片 | ✅ | ✅ | JPG, PNG, GIF |
| 视频 | ✅ | ✅ | MP4 |
| 语音 | ✅ | ❌ | AMR, MP3 |
| 文件 | ✅ | ✅ | 各类文档 |
| 卡片 | ❌ | ❌ | 暂不支持 |
Q7: 如何处理群聊 @ 消息?
python
def on_message(msg: WeComMessage):
# 检查是否被@
if msg.from_user_id in msg.mentioned_list or "@all" in msg.mentioned_list:
plugin.reply_message(msg, "收到你的@")
Q8: 消息历史如何存储?
默认: 内存存储(重启后丢失)
持久化存储: 可扩展为数据库存储
python
# 在 handlers.py 中实现
class DatabaseMessageStore(MessageHistoryStore):
def add_message(self, chat_id, msg):
# 保存到数据库
pass
Q9: 如何与 AI 集成?
python
import openai
def on_message(msg: WeComMessage):
# 调用 AI API
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": msg.content}]
)
ai_reply = response.choices[0].message.content
plugin.reply_message(msg, ai_reply)
Q10: 性能优化建议?
- 异步处理 - 使用线程池处理耗时操作
- 消息队列 - 使用队列缓冲消息
- 连接池 - 复用 HTTP 连接
- 缓存 - 缓存常用数据
- 日志级别 - 生产环境使用 INFO 或 WARNING