diff --git a/README.fr.md b/README.fr.md
index 21913f6ba..d49edc5ee 100644
--- a/README.fr.md
+++ b/README.fr.md
@@ -262,7 +262,7 @@ Et voilà ! Vous avez un assistant IA fonctionnel en 2 minutes.
## 💬 Applications de Chat
-Discutez avec votre PicoClaw via Telegram, Discord, DingTalk ou LINE
+Discutez avec votre PicoClaw via Telegram, Discord, DingTalk, LINE ou WeCom
| Canal | Configuration |
| ------------ | -------------------------------------- |
@@ -271,6 +271,7 @@ Discutez avec votre PicoClaw via Telegram, Discord, DingTalk ou LINE
| **QQ** | Facile (AppID + AppSecret) |
| **DingTalk** | Moyen (identifiants de l'application) |
| **LINE** | Moyen (identifiants + URL de webhook) |
+| **WeCom** | Moyen (CorpID + configuration webhook) |
Telegram (Recommandé)
@@ -470,6 +471,87 @@ picoclaw gateway
+
+WeCom (WeChat Work)
+
+PicoClaw prend en charge deux types d'intégration WeCom :
+
+**Option 1 : WeCom Bot (Robot Intelligent)** - Configuration plus facile, prend en charge les discussions de groupe
+**Option 2 : WeCom App (Application Personnalisée)** - Plus de fonctionnalités, messagerie proactive
+
+Voir le [Guide de Configuration WeCom App](docs/wecom-app-configuration.md) pour des instructions détaillées.
+
+**Configuration Rapide - WeCom Bot :**
+
+**1. Créer un bot**
+
+* Accédez à la Console d'Administration WeCom → Discussion de Groupe → Ajouter un Bot de Groupe
+* Copiez l'URL du webhook (format : `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx`)
+
+**2. Configurer**
+
+```json
+{
+ "channels": {
+ "wecom": {
+ "enabled": true,
+ "token": "YOUR_TOKEN",
+ "encoding_aes_key": "YOUR_ENCODING_AES_KEY",
+ "webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
+ "webhook_host": "0.0.0.0",
+ "webhook_port": 18793,
+ "webhook_path": "/webhook/wecom",
+ "allow_from": []
+ }
+ }
+}
+```
+
+**Configuration Rapide - WeCom App :**
+
+**1. Créer une application**
+
+* Accédez à la Console d'Administration WeCom → Gestion des Applications → Créer une Application
+* Copiez l'**AgentId** et le **Secret**
+* Accédez à la page "Mon Entreprise", copiez le **CorpID**
+
+**2. Configurer la réception des messages**
+
+* Dans les détails de l'application, cliquez sur "Recevoir les Messages" → "Configurer l'API"
+* Définissez l'URL sur `http://your-server:18792/webhook/wecom-app`
+* Générez le **Token** et l'**EncodingAESKey**
+
+**3. Configurer**
+
+```json
+{
+ "channels": {
+ "wecom_app": {
+ "enabled": true,
+ "corp_id": "wwxxxxxxxxxxxxxxxx",
+ "corp_secret": "YOUR_CORP_SECRET",
+ "agent_id": 1000002,
+ "token": "YOUR_TOKEN",
+ "encoding_aes_key": "YOUR_ENCODING_AES_KEY",
+ "webhook_host": "0.0.0.0",
+ "webhook_port": 18792,
+ "webhook_path": "/webhook/wecom-app",
+ "allow_from": []
+ }
+ }
+}
+```
+
+**4. Lancer**
+
+```bash
+picoclaw gateway
+```
+
+> **Note** : WeCom App nécessite l'ouverture du port 18792 pour les callbacks webhook. Utilisez un proxy inverse pour HTTPS en production.
+
+
+
##
Rejoignez le Réseau Social d'Agents
Connectez PicoClaw au Réseau Social d'Agents simplement en envoyant un seul message via le CLI ou n'importe quelle application de chat intégrée.
diff --git a/README.ja.md b/README.ja.md
index c0e40883d..793a51101 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -226,7 +226,7 @@ picoclaw agent -m "What is 2+2?"
## 💬 チャットアプリ
-Telegram、Discord、QQ、DingTalk、LINE で PicoClaw と会話できます
+Telegram、Discord、QQ、DingTalk、LINE、WeCom で PicoClaw と会話できます
| チャネル | セットアップ |
|---------|------------|
@@ -235,6 +235,7 @@ Telegram、Discord、QQ、DingTalk、LINE で PicoClaw と会話できます
| **QQ** | 簡単(AppID + AppSecret) |
| **DingTalk** | 普通(アプリ認証情報) |
| **LINE** | 普通(認証情報 + Webhook URL) |
+| **WeCom** | 普通(CorpID + Webhook設定) |
Telegram(推奨)
@@ -430,6 +431,87 @@ picoclaw gateway
+
+WeCom (企業微信)
+
+PicoClaw は2種類の WeCom 統合をサポートしています:
+
+**オプション1: WeCom Bot (智能ロボット)** - 簡単な設定、グループチャット対応
+**オプション2: WeCom App (自作アプリ)** - より多機能、アクティブメッセージング対応
+
+詳細な設定手順は [WeCom App Configuration Guide](docs/wecom-app-configuration.md) を参照してください。
+
+**クイックセットアップ - WeCom Bot:**
+
+**1. ボットを作成**
+
+* WeCom 管理コンソール → グループチャット → グループボットを追加
+* Webhook URL をコピー(形式: `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx`)
+
+**2. 設定**
+
+```json
+{
+ "channels": {
+ "wecom": {
+ "enabled": true,
+ "token": "YOUR_TOKEN",
+ "encoding_aes_key": "YOUR_ENCODING_AES_KEY",
+ "webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
+ "webhook_host": "0.0.0.0",
+ "webhook_port": 18793,
+ "webhook_path": "/webhook/wecom",
+ "allow_from": []
+ }
+ }
+}
+```
+
+**クイックセットアップ - WeCom App:**
+
+**1. アプリを作成**
+
+* WeCom 管理コンソール → アプリ管理 → アプリを作成
+* **AgentId** と **Secret** をコピー
+* "マイ会社" ページで **CorpID** をコピー
+
+**2. メッセージ受信を設定**
+
+* アプリ詳細で "メッセージを受信" → "APIを設定" をクリック
+* URL を `http://your-server:18792/webhook/wecom-app` に設定
+* **Token** と **EncodingAESKey** を生成
+
+**3. 設定**
+
+```json
+{
+ "channels": {
+ "wecom_app": {
+ "enabled": true,
+ "corp_id": "wwxxxxxxxxxxxxxxxx",
+ "corp_secret": "YOUR_CORP_SECRET",
+ "agent_id": 1000002,
+ "token": "YOUR_TOKEN",
+ "encoding_aes_key": "YOUR_ENCODING_AES_KEY",
+ "webhook_host": "0.0.0.0",
+ "webhook_port": 18792,
+ "webhook_path": "/webhook/wecom-app",
+ "allow_from": []
+ }
+ }
+}
+```
+
+**4. 起動**
+
+```bash
+picoclaw gateway
+```
+
+> **注意**: WeCom App は Webhook コールバック用にポート 18792 を開放する必要があります。本番環境では HTTPS 用のリバースプロキシを使用してください。
+
+
+
## ⚙️ 設定
設定ファイル: `~/.picoclaw/config.json`
diff --git a/README.md b/README.md
index 468350409..321e8f60c 100644
--- a/README.md
+++ b/README.md
@@ -264,7 +264,7 @@ That's it! You have a working AI assistant in 2 minutes.
## 💬 Chat Apps
-Talk to your picoclaw through Telegram, Discord, DingTalk, or LINE
+Talk to your picoclaw through Telegram, Discord, DingTalk, LINE, or WeCom
| Channel | Setup |
| ------------ | ---------------------------------- |
@@ -273,6 +273,7 @@ Talk to your picoclaw through Telegram, Discord, DingTalk, or LINE
| **QQ** | Easy (AppID + AppSecret) |
| **DingTalk** | Medium (app credentials) |
| **LINE** | Medium (credentials + webhook URL) |
+| **WeCom** | Medium (CorpID + webhook setup) |
Telegram (Recommended)
@@ -472,6 +473,87 @@ picoclaw gateway
+
+WeCom (企业微信)
+
+PicoClaw supports two types of WeCom integration:
+
+**Option 1: WeCom Bot (智能机器人)** - Easier setup, supports group chats
+**Option 2: WeCom App (自建应用)** - More features, proactive messaging
+
+See [WeCom App Configuration Guide](docs/wecom-app-configuration.md) for detailed setup instructions.
+
+**Quick Setup - WeCom Bot:**
+
+**1. Create a bot**
+
+* Go to WeCom Admin Console → Group Chat → Add Group Bot
+* Copy the webhook URL (format: `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx`)
+
+**2. Configure**
+
+```json
+{
+ "channels": {
+ "wecom": {
+ "enabled": true,
+ "token": "YOUR_TOKEN",
+ "encoding_aes_key": "YOUR_ENCODING_AES_KEY",
+ "webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
+ "webhook_host": "0.0.0.0",
+ "webhook_port": 18793,
+ "webhook_path": "/webhook/wecom",
+ "allow_from": []
+ }
+ }
+}
+```
+
+**Quick Setup - WeCom App:**
+
+**1. Create an app**
+
+* Go to WeCom Admin Console → App Management → Create App
+* Copy **AgentId** and **Secret**
+* Go to "My Company" page, copy **CorpID**
+
+**2. Configure receive message**
+
+* In App details, click "Receive Message" → "Set API"
+* Set URL to `http://your-server:18792/webhook/wecom-app`
+* Generate **Token** and **EncodingAESKey**
+
+**3. Configure**
+
+```json
+{
+ "channels": {
+ "wecom_app": {
+ "enabled": true,
+ "corp_id": "wwxxxxxxxxxxxxxxxx",
+ "corp_secret": "YOUR_CORP_SECRET",
+ "agent_id": 1000002,
+ "token": "YOUR_TOKEN",
+ "encoding_aes_key": "YOUR_ENCODING_AES_KEY",
+ "webhook_host": "0.0.0.0",
+ "webhook_port": 18792,
+ "webhook_path": "/webhook/wecom-app",
+ "allow_from": []
+ }
+ }
+}
+```
+
+**4. Run**
+
+```bash
+picoclaw gateway
+```
+
+> **Note**: WeCom App requires opening port 18792 for webhook callbacks. Use a reverse proxy for HTTPS.
+
+
+
##
Join the Agent Social Network
Connect Picoclaw to the Agent Social Network simply by sending a single message via the CLI or any integrated Chat App.
diff --git a/README.pt-br.md b/README.pt-br.md
index 44f27813c..a1788d119 100644
--- a/README.pt-br.md
+++ b/README.pt-br.md
@@ -263,7 +263,7 @@ Pronto! Você tem um assistente de IA funcionando em 2 minutos.
## 💬 Integração com Apps de Chat
-Converse com seu PicoClaw via Telegram, Discord, DingTalk ou LINE.
+Converse com seu PicoClaw via Telegram, Discord, DingTalk, LINE ou WeCom.
| Canal | Nível de Configuração |
| --- | --- |
@@ -272,6 +272,7 @@ Converse com seu PicoClaw via Telegram, Discord, DingTalk ou LINE.
| **QQ** | Fácil (AppID + AppSecret) |
| **DingTalk** | Médio (credenciais do app) |
| **LINE** | Médio (credenciais + webhook URL) |
+| **WeCom** | Médio (CorpID + configuração webhook) |
Telegram (Recomendado)
@@ -471,6 +472,87 @@ picoclaw gateway
+
+WeCom (WeChat Work)
+
+O PicoClaw suporta dois tipos de integração WeCom:
+
+**Opção 1: WeCom Bot (Robô Inteligente)** - Configuração mais fácil, suporta chats em grupo
+**Opção 2: WeCom App (Aplicativo Personalizado)** - Mais recursos, mensagens proativas
+
+Veja o [Guia de Configuração WeCom App](docs/wecom-app-configuration.md) para instruções detalhadas.
+
+**Configuração Rápida - WeCom Bot:**
+
+**1. Criar um bot**
+
+* Acesse o Console de Administração WeCom → Chat em Grupo → Adicionar Bot de Grupo
+* Copie a URL do webhook (formato: `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx`)
+
+**2. Configurar**
+
+```json
+{
+ "channels": {
+ "wecom": {
+ "enabled": true,
+ "token": "YOUR_TOKEN",
+ "encoding_aes_key": "YOUR_ENCODING_AES_KEY",
+ "webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
+ "webhook_host": "0.0.0.0",
+ "webhook_port": 18793,
+ "webhook_path": "/webhook/wecom",
+ "allow_from": []
+ }
+ }
+}
+```
+
+**Configuração Rápida - WeCom App:**
+
+**1. Criar um aplicativo**
+
+* Acesse o Console de Administração WeCom → Gerenciamento de Aplicativos → Criar Aplicativo
+* Copie o **AgentId** e o **Secret**
+* Acesse a página "Minha Empresa", copie o **CorpID**
+
+**2. Configurar recebimento de mensagens**
+
+* Nos detalhes do aplicativo, clique em "Receber Mensagens" → "Configurar API"
+* Defina a URL como `http://your-server:18792/webhook/wecom-app`
+* Gere o **Token** e o **EncodingAESKey**
+
+**3. Configurar**
+
+```json
+{
+ "channels": {
+ "wecom_app": {
+ "enabled": true,
+ "corp_id": "wwxxxxxxxxxxxxxxxx",
+ "corp_secret": "YOUR_CORP_SECRET",
+ "agent_id": 1000002,
+ "token": "YOUR_TOKEN",
+ "encoding_aes_key": "YOUR_ENCODING_AES_KEY",
+ "webhook_host": "0.0.0.0",
+ "webhook_port": 18792,
+ "webhook_path": "/webhook/wecom-app",
+ "allow_from": []
+ }
+ }
+}
+```
+
+**4. Executar**
+
+```bash
+picoclaw gateway
+```
+
+> **Nota**: O WeCom App requer a abertura da porta 18792 para callbacks de webhook. Use um proxy reverso para HTTPS em produção.
+
+
+
##
Junte-se a Rede Social de Agentes
Conecte o PicoClaw a Rede Social de Agentes simplesmente enviando uma única mensagem via CLI ou qualquer App de Chat integrado.
diff --git a/README.vi.md b/README.vi.md
index 08fa3dccd..5548f88a4 100644
--- a/README.vi.md
+++ b/README.vi.md
@@ -243,7 +243,7 @@ Vậy là xong! Bạn đã có một trợ lý AI hoạt động chỉ trong 2 p
## 💬 Tích hợp ứng dụng Chat
-Trò chuyện với PicoClaw qua Telegram, Discord, DingTalk hoặc LINE.
+Trò chuyện với PicoClaw qua Telegram, Discord, DingTalk, LINE hoặc WeCom.
| Kênh | Mức độ thiết lập |
| --- | --- |
@@ -252,6 +252,7 @@ Trò chuyện với PicoClaw qua Telegram, Discord, DingTalk hoặc LINE.
| **QQ** | Dễ (AppID + AppSecret) |
| **DingTalk** | Trung bình (app credentials) |
| **LINE** | Trung bình (credentials + webhook URL) |
+| **WeCom** | Trung bình (CorpID + cấu hình webhook) |
Telegram (Khuyên dùng)
@@ -451,6 +452,87 @@ picoclaw gateway
+
+WeCom (WeChat Work)
+
+PicoClaw hỗ trợ hai loại tích hợp WeCom:
+
+**Tùy chọn 1: WeCom Bot (Robot Thông minh)** - Thiết lập dễ dàng hơn, hỗ trợ chat nhóm
+**Tùy chọn 2: WeCom App (Ứng dụng Tự xây dựng)** - Nhiều tính năng hơn, nhắn tin chủ động
+
+Xem [Hướng dẫn Cấu hình WeCom App](docs/wecom-app-configuration.md) để biết hướng dẫn chi tiết.
+
+**Thiết lập Nhanh - WeCom Bot:**
+
+**1. Tạo bot**
+
+* Truy cập Bảng điều khiển Quản trị WeCom → Chat Nhóm → Thêm Bot Nhóm
+* Sao chép URL webhook (định dạng: `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx`)
+
+**2. Cấu hình**
+
+```json
+{
+ "channels": {
+ "wecom": {
+ "enabled": true,
+ "token": "YOUR_TOKEN",
+ "encoding_aes_key": "YOUR_ENCODING_AES_KEY",
+ "webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
+ "webhook_host": "0.0.0.0",
+ "webhook_port": 18793,
+ "webhook_path": "/webhook/wecom",
+ "allow_from": []
+ }
+ }
+}
+```
+
+**Thiết lập Nhanh - WeCom App:**
+
+**1. Tạo ứng dụng**
+
+* Truy cập Bảng điều khiển Quản trị WeCom → Quản lý Ứng dụng → Tạo Ứng dụng
+* Sao chép **AgentId** và **Secret**
+* Truy cập trang "Công ty của tôi", sao chép **CorpID**
+
+**2. Cấu hình nhận tin nhắn**
+
+* Trong chi tiết ứng dụng, nhấp vào "Nhận Tin nhắn" → "Thiết lập API"
+* Đặt URL thành `http://your-server:18792/webhook/wecom-app`
+* Tạo **Token** và **EncodingAESKey**
+
+**3. Cấu hình**
+
+```json
+{
+ "channels": {
+ "wecom_app": {
+ "enabled": true,
+ "corp_id": "wwxxxxxxxxxxxxxxxx",
+ "corp_secret": "YOUR_CORP_SECRET",
+ "agent_id": 1000002,
+ "token": "YOUR_TOKEN",
+ "encoding_aes_key": "YOUR_ENCODING_AES_KEY",
+ "webhook_host": "0.0.0.0",
+ "webhook_port": 18792,
+ "webhook_path": "/webhook/wecom-app",
+ "allow_from": []
+ }
+ }
+}
+```
+
+**4. Chạy**
+
+```bash
+picoclaw gateway
+```
+
+> **Lưu ý**: WeCom App yêu cầu mở cổng 18792 cho callback webhook. Sử dụng proxy ngược cho HTTPS trong môi trường sản xuất.
+
+
+
##
Tham gia Mạng xã hội Agent
Kết nối PicoClaw với Mạng xã hội Agent chỉ bằng cách gửi một tin nhắn qua CLI hoặc bất kỳ ứng dụng Chat nào đã tích hợp.
diff --git a/README.zh.md b/README.zh.md
index 4827e66ea..d470db033 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -273,14 +273,15 @@ picoclaw agent -m "2+2 等于几?"
## 💬 聊天应用集成 (Chat Apps)
-通过 Telegram, Discord 或钉钉与您的 PicoClaw 对话。
+通过 Telegram, Discord, 钉钉或企业微信与您的 PicoClaw 对话。
| 渠道 | 设置难度 |
| --- | --- |
| **Telegram** | 简单 (仅需 token) |
| **Discord** | 简单 (bot token + intents) |
| **QQ** | 简单 (AppID + AppSecret) |
-| **钉钉 (DingTalk)** | 中等 (app credentials) |
+| **钉钉 (DingTalk)** | 中等 (应用凭证) |
+| **企业微信 (WeCom)** | 中等 (企业ID + Webhook配置) |
Telegram (推荐)
@@ -438,6 +439,88 @@ picoclaw gateway
+
+企业微信 (WeCom)
+
+PicoClaw 支持两种企业微信集成方式:
+
+**选项1: 智能机器人 (WeCom Bot)** - 设置更简单,支持群聊
+**选项2: 自建应用 (WeCom App)** - 功能更丰富,支持主动推送消息
+
+详见 [企业微信自建应用配置指南](docs/wecom-app-configuration.md)。
+
+**快速设置 - 智能机器人:**
+
+**1. 创建机器人**
+
+* 前往企业微信管理后台 → 群聊 → 添加群机器人
+* 复制 Webhook URL (格式: `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx`)
+
+**2. 配置**
+
+```json
+{
+ "channels": {
+ "wecom": {
+ "enabled": true,
+ "token": "YOUR_TOKEN",
+ "encoding_aes_key": "YOUR_ENCODING_AES_KEY",
+ "webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
+ "webhook_host": "0.0.0.0",
+ "webhook_port": 18793,
+ "webhook_path": "/webhook/wecom",
+ "allow_from": []
+ }
+ }
+}
+```
+
+**快速设置 - 自建应用:**
+
+**1. 创建应用**
+
+* 前往企业微信管理后台 → 应用管理 → 创建应用
+* 复制 **AgentId** 和 **Secret**
+* 前往"我的企业"页面,复制 **CorpID**
+
+**2. 配置接收消息**
+
+* 在应用详情页,点击"接收消息" → "设置API"
+* 设置 URL 为 `http://your-server:18792/webhook/wecom-app`
+* 生成 **Token** 和 **EncodingAESKey**
+
+**3. 配置**
+
+```json
+{
+ "channels": {
+ "wecom_app": {
+ "enabled": true,
+ "corp_id": "wwxxxxxxxxxxxxxxxx",
+ "corp_secret": "YOUR_CORP_SECRET",
+ "agent_id": 1000002,
+ "token": "YOUR_TOKEN",
+ "encoding_aes_key": "YOUR_ENCODING_AES_KEY",
+ "webhook_host": "0.0.0.0",
+ "webhook_port": 18792,
+ "webhook_path": "/webhook/wecom-app",
+ "allow_from": []
+ }
+ }
+}
+```
+
+**4. 运行**
+
+```bash
+picoclaw gateway
+
+```
+
+> **注意**: 自建应用需要开放 18792 端口用于接收 Webhook 回调。生产环境建议使用反向代理配置 HTTPS。
+
+
+
##
加入 Agent 社交网络
只需通过 CLI 或任何集成的聊天应用发送一条消息,即可将 PicoClaw 连接到 Agent 社交网络。
diff --git a/config/config.example.json b/config/config.example.json
index f0c82c2bc..67819688c 100644
--- a/config/config.example.json
+++ b/config/config.example.json
@@ -108,6 +108,7 @@
"allow_from": []
},
"wecom": {
+ "_comment": "WeCom Bot (智能机器人) - Easier setup, supports group chats",
"enabled": false,
"token": "YOUR_TOKEN",
"encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY",
@@ -119,6 +120,7 @@
"reply_timeout": 5
},
"wecom_app": {
+ "_comment": "WeCom App (自建应用) - More features, proactive messaging, private chat only. See docs/wecom-app-configuration.md",
"enabled": false,
"corp_id": "YOUR_CORP_ID",
"corp_secret": "YOUR_CORP_SECRET",
diff --git a/docs/wecom-app-configuration.md b/docs/wecom-app-configuration.md
new file mode 100644
index 000000000..3b17d37a7
--- /dev/null
+++ b/docs/wecom-app-configuration.md
@@ -0,0 +1,117 @@
+# 企业微信自建应用 (WeCom App) 配置指南
+
+本文档介绍如何在 PicoClaw 中配置企业微信自建应用 (wecom-app) 通道。
+
+## 功能特性
+
+| 功能 | 支持状态 |
+|------|---------|
+| 被动接收消息 | ✅ |
+| 主动发送消息 | ✅ |
+| 私聊 | ✅ |
+| 群聊 | ❌ |
+
+## 配置步骤
+
+### 1. 企业微信后台配置
+
+1. 登录 [企业微信管理后台](https://work.weixin.qq.com/wework_admin)
+2. 进入"应用管理" → 选择自建应用
+3. 记录以下信息:
+ - **AgentId**: 应用详情页显示
+ - **Secret**: 点击"查看"获取
+4. 进入"我的企业"页面,记录 **企业ID** (CorpID)
+
+### 2. 接收消息配置
+
+1. 在应用详情页,点击"接收消息"的"设置API接收"
+2. 填写以下信息:
+ - **URL**: `http://your-server:18792/webhook/wecom-app`
+ - **Token**: 随机生成或自定义(用于签名验证)
+ - **EncodingAESKey**: 点击"随机生成"生成43字符的密钥
+3. 点击"保存"时,企业微信会发送验证请求
+
+### 3. PicoClaw 配置
+
+在 `config.json` 中添加以下配置:
+
+```json
+{
+ "channels": {
+ "wecom_app": {
+ "enabled": true,
+ "corp_id": "wwxxxxxxxxxxxxxxxx", // 企业ID
+ "corp_secret": "xxxxxxxxxxxxxxxxxxxxxxxx", // 应用Secret
+ "agent_id": 1000002, // 应用AgentId
+ "token": "your_token", // 接收消息配置的Token
+ "encoding_aes_key": "your_encoding_aes_key", // 接收消息配置的EncodingAESKey
+ "webhook_host": "0.0.0.0",
+ "webhook_port": 18792,
+ "webhook_path": "/webhook/wecom-app",
+ "allow_from": [],
+ "reply_timeout": 5
+ }
+ }
+}
+```
+
+## 常见问题
+
+### 1. 回调URL验证失败
+
+**症状**: 企业微信保存API接收消息时提示验证失败
+
+**检查项**:
+- 确认服务器防火墙已开放 18792 端口
+- 确认 `corp_id`、`token`、`encoding_aes_key` 配置正确
+- 查看 PicoClaw 日志是否有请求到达
+
+### 2. 中文消息解密失败
+
+**症状**: 发送中文消息时出现 `invalid padding size` 错误
+
+**原因**: 企业微信使用非标准的 PKCS7 填充(32字节块大小)
+
+**解决**: 确保使用最新版本的 PicoClaw,已修复此问题。
+
+### 3. 端口冲突
+
+**症状**: 启动时提示端口已被占用
+
+**解决**: 修改 `webhook_port` 为其他端口,如 18794
+
+## 技术细节
+
+### 加密算法
+
+- **算法**: AES-256-CBC
+- **密钥**: EncodingAESKey Base64解码后的32字节
+- **IV**: AESKey的前16字节
+- **填充**: PKCS7(块大小为32字节,非标准16字节)
+- **消息格式**: XML
+
+### 消息结构
+
+解密后的消息格式:
+```
+random(16B) + msg_len(4B) + msg + receiveid
+```
+
+其中 `receiveid` 对于自建应用是 `corp_id`。
+
+## 调试
+
+启用调试模式查看详细日志:
+
+```bash
+picoclaw gateway --debug
+```
+
+关键日志标识:
+- `wecom_app`: WeCom App 通道相关日志
+- `wecom_common`: 加密解密相关日志
+
+## 参考文档
+
+- [企业微信官方文档 - 接收消息](https://developer.work.weixin.qq.com/document/path/96211)
+- [企业微信官方加解密库](https://github.com/sbzhu/weworkapi_golang)
diff --git a/pkg/channels/wecom.go b/pkg/channels/wecom.go
index 404949e07..064568243 100644
--- a/pkg/channels/wecom.go
+++ b/pkg/channels/wecom.go
@@ -7,11 +7,17 @@ package channels
import (
"bytes"
"context"
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/sha1"
+ "encoding/base64"
+ "encoding/binary"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"net/http"
+ "sort"
"strings"
"sync"
"time"
@@ -470,3 +476,129 @@ func (c *WeComBotChannel) handleHealth(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(status)
}
+
+// WeCom common utilities for both WeCom Bot and WeCom App
+// The following functions were moved from wecom_common.go
+
+// WeComVerifySignature verifies the message signature for WeCom
+// This is a common function used by both WeCom Bot and WeCom App
+func WeComVerifySignature(token, msgSignature, timestamp, nonce, msgEncrypt string) bool {
+ if token == "" {
+ return true // Skip verification if token is not set
+ }
+
+ // Sort parameters
+ params := []string{token, timestamp, nonce, msgEncrypt}
+ sort.Strings(params)
+
+ // Concatenate
+ str := strings.Join(params, "")
+
+ // SHA1 hash
+ hash := sha1.Sum([]byte(str))
+ expectedSignature := fmt.Sprintf("%x", hash)
+
+ return expectedSignature == msgSignature
+}
+
+// WeComDecryptMessage decrypts the encrypted message using AES
+// This is a common function used by both WeCom Bot and WeCom App
+// For AIBOT, receiveid should be the aibotid; for other apps, it should be corp_id
+func WeComDecryptMessage(encryptedMsg, encodingAESKey string) (string, error) {
+ return WeComDecryptMessageWithVerify(encryptedMsg, encodingAESKey, "")
+}
+
+// WeComDecryptMessageWithVerify decrypts the encrypted message and optionally verifies receiveid
+// receiveid: for AIBOT use aibotid, for WeCom App use corp_id. If empty, skip verification.
+func WeComDecryptMessageWithVerify(encryptedMsg, encodingAESKey, receiveid string) (string, error) {
+ if encodingAESKey == "" {
+ // No encryption, return as is (base64 decode)
+ decoded, err := base64.StdEncoding.DecodeString(encryptedMsg)
+ if err != nil {
+ return "", err
+ }
+ return string(decoded), nil
+ }
+
+ // Decode AES key (base64)
+ aesKey, err := base64.StdEncoding.DecodeString(encodingAESKey + "=")
+ if err != nil {
+ return "", fmt.Errorf("failed to decode AES key: %w", err)
+ }
+
+ // Decode encrypted message
+ cipherText, err := base64.StdEncoding.DecodeString(encryptedMsg)
+ if err != nil {
+ return "", fmt.Errorf("failed to decode message: %w", err)
+ }
+
+ // AES decrypt
+ block, err := aes.NewCipher(aesKey)
+ if err != nil {
+ return "", fmt.Errorf("failed to create cipher: %w", err)
+ }
+
+ if len(cipherText) < aes.BlockSize {
+ return "", fmt.Errorf("ciphertext too short")
+ }
+
+ // IV is the first 16 bytes of AESKey
+ iv := aesKey[:aes.BlockSize]
+ mode := cipher.NewCBCDecrypter(block, iv)
+ plainText := make([]byte, len(cipherText))
+ mode.CryptBlocks(plainText, cipherText)
+
+ // Remove PKCS7 padding
+ plainText, err = pkcs7UnpadWeCom(plainText)
+ if err != nil {
+ return "", fmt.Errorf("failed to unpad: %w", err)
+ }
+
+ // Parse message structure
+ // Format: random(16) + msg_len(4) + msg + receiveid
+ if len(plainText) < 20 {
+ return "", fmt.Errorf("decrypted message too short")
+ }
+
+ msgLen := binary.BigEndian.Uint32(plainText[16:20])
+ if int(msgLen) > len(plainText)-20 {
+ return "", fmt.Errorf("invalid message length")
+ }
+
+ msg := plainText[20 : 20+msgLen]
+
+ // Verify receiveid if provided
+ if receiveid != "" && len(plainText) > 20+int(msgLen) {
+ actualReceiveID := string(plainText[20+msgLen:])
+ if actualReceiveID != receiveid {
+ return "", fmt.Errorf("receiveid mismatch: expected %s, got %s", receiveid, actualReceiveID)
+ }
+ }
+
+ return string(msg), nil
+}
+
+// pkcs7UnpadWeCom removes PKCS7 padding with validation
+// WeCom uses block size of 32 (not standard AES block size of 16)
+const wecomBlockSize = 32
+
+func pkcs7UnpadWeCom(data []byte) ([]byte, error) {
+ if len(data) == 0 {
+ return data, nil
+ }
+ padding := int(data[len(data)-1])
+ // WeCom uses 32-byte block size for PKCS7 padding
+ if padding == 0 || padding > wecomBlockSize {
+ return nil, fmt.Errorf("invalid padding size: %d", padding)
+ }
+ if padding > len(data) {
+ return nil, fmt.Errorf("padding size larger than data")
+ }
+ // Verify all padding bytes
+ for i := 0; i < padding; i++ {
+ if data[len(data)-1-i] != byte(padding) {
+ return nil, fmt.Errorf("invalid padding byte at position %d", i)
+ }
+ }
+ return data[:len(data)-padding], nil
+}
diff --git a/pkg/channels/wecom_common.go b/pkg/channels/wecom_common.go
deleted file mode 100644
index 3e3908622..000000000
--- a/pkg/channels/wecom_common.go
+++ /dev/null
@@ -1,182 +0,0 @@
-// PicoClaw - Ultra-lightweight personal AI agent
-// WeCom common utilities for both WeCom Bot and WeCom App
-
-package channels
-
-import (
- "crypto/aes"
- "crypto/cipher"
- "crypto/sha1"
- "encoding/base64"
- "encoding/binary"
- "fmt"
- "sort"
- "strings"
-
- "github.com/sipeed/picoclaw/pkg/logger"
-)
-
-// WeComVerifySignature verifies the message signature for WeCom
-// This is a common function used by both WeCom Bot and WeCom App
-func WeComVerifySignature(token, msgSignature, timestamp, nonce, msgEncrypt string) bool {
- if token == "" {
- return true // Skip verification if token is not set
- }
-
- // Sort parameters
- params := []string{token, timestamp, nonce, msgEncrypt}
- sort.Strings(params)
-
- // Concatenate
- str := strings.Join(params, "")
-
- // SHA1 hash
- hash := sha1.Sum([]byte(str))
- expectedSignature := fmt.Sprintf("%x", hash)
-
- return expectedSignature == msgSignature
-}
-
-// WeComDecryptMessage decrypts the encrypted message using AES
-// This is a common function used by both WeCom Bot and WeCom App
-// For AIBOT, receiveid should be the aibotid; for other apps, it should be corp_id
-func WeComDecryptMessage(encryptedMsg, encodingAESKey string) (string, error) {
- return WeComDecryptMessageWithVerify(encryptedMsg, encodingAESKey, "")
-}
-
-// WeComDecryptMessageWithVerify decrypts the encrypted message and optionally verifies receiveid
-// receiveid: for AIBOT use aibotid, for WeCom App use corp_id. If empty, skip verification.
-func WeComDecryptMessageWithVerify(encryptedMsg, encodingAESKey, receiveid string) (string, error) {
- logger.DebugCF("wecom_common", "Starting decryption", map[string]interface{}{
- "encodingAESKey_len": len(encodingAESKey),
- "receiveid": receiveid,
- "encryptedMsg_len": len(encryptedMsg),
- })
-
- if encodingAESKey == "" {
- // No encryption, return as is (base64 decode)
- decoded, err := base64.StdEncoding.DecodeString(encryptedMsg)
- if err != nil {
- return "", err
- }
- return string(decoded), nil
- }
-
- // Decode AES key (base64)
- aesKey, err := base64.StdEncoding.DecodeString(encodingAESKey + "=")
- if err != nil {
- logger.ErrorCF("wecom_common", "Failed to decode AES key", map[string]interface{}{
- "error": err.Error(),
- "key": encodingAESKey,
- })
- return "", fmt.Errorf("failed to decode AES key: %w", err)
- }
- logger.DebugCF("wecom_common", "AES key decoded", map[string]interface{}{
- "key_len": len(aesKey),
- })
-
- // Decode encrypted message
- cipherText, err := base64.StdEncoding.DecodeString(encryptedMsg)
- if err != nil {
- logger.ErrorCF("wecom_common", "Failed to decode message", map[string]interface{}{
- "error": err.Error(),
- })
- return "", fmt.Errorf("failed to decode message: %w", err)
- }
- logger.DebugCF("wecom_common", "Message decoded", map[string]interface{}{
- "cipher_len": len(cipherText),
- })
-
- // AES decrypt
- block, err := aes.NewCipher(aesKey)
- if err != nil {
- return "", fmt.Errorf("failed to create cipher: %w", err)
- }
-
- if len(cipherText) < aes.BlockSize {
- return "", fmt.Errorf("ciphertext too short: %d < %d", len(cipherText), aes.BlockSize)
- }
-
- // IV is the first 16 bytes of AESKey
- iv := aesKey[:aes.BlockSize]
- mode := cipher.NewCBCDecrypter(block, iv)
- plainText := make([]byte, len(cipherText))
- mode.CryptBlocks(plainText, cipherText)
-
- // Remove PKCS7 padding
- unpaddedText, err := pkcs7UnpadWeCom(plainText)
- if err != nil {
- lastByte := -1
- if len(plainText) > 0 {
- lastByte = int(plainText[len(plainText)-1])
- }
- logger.ErrorCF("wecom_common", "PKCS7 unpad failed", map[string]interface{}{
- "error": err.Error(),
- "plain_len": len(plainText),
- "last_byte": lastByte,
- })
- return "", fmt.Errorf("failed to unpad: %w", err)
- }
- plainText = unpaddedText
-
- // Parse message structure
- // Format: random(16) + msg_len(4) + msg + receiveid
- if len(plainText) < 20 {
- return "", fmt.Errorf("decrypted message too short")
- }
-
- msgLen := binary.BigEndian.Uint32(plainText[16:20])
- logger.DebugCF("wecom_common", "Message structure parsed", map[string]interface{}{
- "msg_len": msgLen,
- "plain_len": len(plainText),
- "total_expected": 20 + int(msgLen),
- })
-
- if int(msgLen) > len(plainText)-20 {
- return "", fmt.Errorf("invalid message length: %d > %d", msgLen, len(plainText)-20)
- }
-
- msg := plainText[20 : 20+msgLen]
-
- // Verify receiveid if provided
- if receiveid != "" && len(plainText) > 20+int(msgLen) {
- actualReceiveID := string(plainText[20+msgLen:])
- logger.DebugCF("wecom_common", "ReceiveID verification", map[string]interface{}{
- "expected": receiveid,
- "actual": actualReceiveID,
- })
- if actualReceiveID != receiveid {
- return "", fmt.Errorf("receiveid mismatch: expected %s, got %s", receiveid, actualReceiveID)
- }
- }
-
- logger.DebugCF("wecom_common", "Decryption successful", map[string]interface{}{
- "msg_len": len(msg),
- })
- return string(msg), nil
-}
-
-// pkcs7UnpadWeCom removes PKCS7 padding with validation
-// WeCom uses block size of 32 (not standard AES block size of 16)
-const wecomBlockSize = 32
-
-func pkcs7UnpadWeCom(data []byte) ([]byte, error) {
- if len(data) == 0 {
- return data, nil
- }
- padding := int(data[len(data)-1])
- // WeCom uses 32-byte block size for PKCS7 padding
- if padding == 0 || padding > wecomBlockSize {
- return nil, fmt.Errorf("invalid padding size: %d", padding)
- }
- if padding > len(data) {
- return nil, fmt.Errorf("padding size larger than data")
- }
- // Verify all padding bytes
- for i := 0; i < padding; i++ {
- if data[len(data)-1-i] != byte(padding) {
- return nil, fmt.Errorf("invalid padding byte at position %d", i)
- }
- }
- return data[:len(data)-padding], nil
-}