add Zalo channel support and documentation
This commit is contained in:
parent
f12c09b767
commit
e5b91ae4e6
13 changed files with 775 additions and 5 deletions
52
README.fr.md
52
README.fr.md
|
|
@ -292,11 +292,12 @@ Et voilà ! Vous avez un assistant IA fonctionnel en 2 minutes.
|
||||||
|
|
||||||
## 💬 Applications de Chat
|
## 💬 Applications de Chat
|
||||||
|
|
||||||
Discutez avec votre PicoClaw via Telegram, Discord, DingTalk, LINE ou WeCom
|
Discutez avec votre PicoClaw via Telegram, Zalo, Discord, DingTalk, LINE ou WeCom
|
||||||
|
|
||||||
| Canal | Configuration |
|
| Canal | Configuration |
|
||||||
| ------------ | -------------------------------------- |
|
| ------------ | -------------------------------------- |
|
||||||
| **Telegram** | Facile (juste un token) |
|
| **Telegram** | Facile (juste un token) |
|
||||||
|
| **Zalo** | Moyen (token + secret_token + webhook URL) |
|
||||||
| **Discord** | Facile (token bot + intents) |
|
| **Discord** | Facile (token bot + intents) |
|
||||||
| **QQ** | Facile (AppID + AppSecret) |
|
| **QQ** | Facile (AppID + AppSecret) |
|
||||||
| **DingTalk** | Moyen (identifiants de l'application) |
|
| **DingTalk** | Moyen (identifiants de l'application) |
|
||||||
|
|
@ -336,6 +337,55 @@ picoclaw gateway
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>Zalo</b> (Webhook)</summary>
|
||||||
|
|
||||||
|
**1. Préparer**
|
||||||
|
|
||||||
|
* Récupérez votre `BOT_TOKEN` Zalo
|
||||||
|
* Choisissez un `secret_token` (8–256 caractères). Zalo le renverra dans l'en-tête `X-Bot-Api-Secret-Token`.
|
||||||
|
* Pour plus de détails sur le webhook, voir la documentation officielle [`setWebhook`](https://bot.zapps.me/docs/apis/setWebhook/).
|
||||||
|
|
||||||
|
**2. Configurer**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"channels": {
|
||||||
|
"zalo": {
|
||||||
|
"enabled": true,
|
||||||
|
"token": "YOUR_ZALO_BOT_TOKEN",
|
||||||
|
"secret_token": "YOUR_SECRET_TOKEN",
|
||||||
|
"webhook_path": "/webhook/zalo",
|
||||||
|
"allow_from": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Démarrer**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
picoclaw gateway
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. HTTPS local (ngrok)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ngrok http 18790
|
||||||
|
```
|
||||||
|
|
||||||
|
Webhook URL: `https://<ngrok-domain>/webhook/zalo`
|
||||||
|
|
||||||
|
**5. Enregistrer le webhook**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://bot-api.zaloplatforms.com/bot${BOT_TOKEN}/setWebhook" \
|
||||||
|
-H "Content-Type: "application/json" \
|
||||||
|
-d '{"url":"https://<ngrok-domain>/webhook/zalo","secret_token":"YOUR_SECRET_TOKEN"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><b>Discord</b></summary>
|
<summary><b>Discord</b></summary>
|
||||||
|
|
||||||
|
|
|
||||||
52
README.ja.md
52
README.ja.md
|
|
@ -264,17 +264,67 @@ picoclaw agent -m "What is 2+2?"
|
||||||
|
|
||||||
## 💬 チャットアプリ
|
## 💬 チャットアプリ
|
||||||
|
|
||||||
Telegram、Discord、QQ、DingTalk、LINE、WeCom で PicoClaw と会話できます
|
Telegram、Zalo、Discord、QQ、DingTalk、LINE、WeCom で PicoClaw と会話できます
|
||||||
|
|
||||||
| チャネル | セットアップ |
|
| チャネル | セットアップ |
|
||||||
|---------|------------|
|
|---------|------------|
|
||||||
| **Telegram** | 簡単(トークンのみ) |
|
| **Telegram** | 簡単(トークンのみ) |
|
||||||
|
| **Zalo** | 普通(token + secret_token + webhook URL) |
|
||||||
| **Discord** | 簡単(Bot トークン + Intents) |
|
| **Discord** | 簡単(Bot トークン + Intents) |
|
||||||
| **QQ** | 簡単(AppID + AppSecret) |
|
| **QQ** | 簡単(AppID + AppSecret) |
|
||||||
| **DingTalk** | 普通(アプリ認証情報) |
|
| **DingTalk** | 普通(アプリ認証情報) |
|
||||||
| **LINE** | 普通(認証情報 + Webhook URL) |
|
| **LINE** | 普通(認証情報 + Webhook URL) |
|
||||||
| **WeCom AI Bot** | 普通(Token + AES キー) |
|
| **WeCom AI Bot** | 普通(Token + AES キー) |
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>Zalo</b>(Webhook)</summary>
|
||||||
|
|
||||||
|
**1. 準備**
|
||||||
|
|
||||||
|
- Zalo の `BOT_TOKEN` を用意
|
||||||
|
- `secret_token`(8–256 文字)を決める(Zalo が `X-Bot-Api-Secret-Token` ヘッダーで返します)
|
||||||
|
- Webhook の詳細は公式ドキュメント [`setWebhook`](https://bot.zapps.me/docs/apis/setWebhook/) を参照してください。
|
||||||
|
|
||||||
|
**2. 設定**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"channels": {
|
||||||
|
"zalo": {
|
||||||
|
"enabled": true,
|
||||||
|
"token": "YOUR_ZALO_BOT_TOKEN",
|
||||||
|
"secret_token": "YOUR_SECRET_TOKEN",
|
||||||
|
"webhook_path": "/webhook/zalo",
|
||||||
|
"allow_from": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. 起動**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
picoclaw gateway
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. ローカルで HTTPS(ngrok)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ngrok http 18790
|
||||||
|
```
|
||||||
|
|
||||||
|
Webhook URL: `https://<ngrok-domain>/webhook/zalo`
|
||||||
|
|
||||||
|
**5. Webhook 登録**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://bot-api.zaloplatforms.com/bot${BOT_TOKEN}/setWebhook" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"url":"https://<ngrok-domain>/webhook/zalo","secret_token":"YOUR_SECRET_TOKEN"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><b>Telegram</b>(推奨)</summary>
|
<summary><b>Telegram</b>(推奨)</summary>
|
||||||
|
|
||||||
|
|
|
||||||
63
README.md
63
README.md
|
|
@ -331,13 +331,14 @@ That's it! You have a working AI assistant in 2 minutes.
|
||||||
|
|
||||||
## 💬 Chat Apps
|
## 💬 Chat Apps
|
||||||
|
|
||||||
Talk to your picoclaw through Telegram, Discord, WhatsApp, Matrix, QQ, DingTalk, LINE, or WeCom
|
Talk to your picoclaw through Telegram, Zalo, Discord, WhatsApp, Matrix, QQ, DingTalk, LINE, or WeCom
|
||||||
|
|
||||||
> **Note**: All webhook-based channels (LINE, WeCom, etc.) are served on a single shared Gateway HTTP server (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`). There are no per-channel ports to configure. Note: Feishu uses WebSocket/SDK mode and does not use the shared HTTP webhook server.
|
> **Note**: All webhook-based channels (LINE, WeCom, etc.) are served on a single shared Gateway HTTP server (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`). There are no per-channel ports to configure. Note: Feishu uses WebSocket/SDK mode and does not use the shared HTTP webhook server.
|
||||||
|
|
||||||
| Channel | Setup |
|
| Channel | Setup |
|
||||||
| ------------ | ---------------------------------- |
|
| ------------ | ---------------------------------- |
|
||||||
| **Telegram** | Easy (just a token) |
|
| **Telegram** | Easy (just a token) |
|
||||||
|
| **Zalo** | Medium (token + secret_token + webhook URL) |
|
||||||
| **Discord** | Easy (bot token + intents) |
|
| **Discord** | Easy (bot token + intents) |
|
||||||
| **WhatsApp** | Easy (native: QR scan; or bridge URL) |
|
| **WhatsApp** | Easy (native: QR scan; or bridge URL) |
|
||||||
| **Matrix** | Medium (homeserver + bot access token) |
|
| **Matrix** | Medium (homeserver + bot access token) |
|
||||||
|
|
@ -386,6 +387,66 @@ If command registration fails (network/API transient errors), the channel still
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>Zalo</b> (Webhook)</summary>
|
||||||
|
|
||||||
|
PicoClaw integrates with Zalo Bot via the official Zalo Bot API webhook flow.
|
||||||
|
|
||||||
|
**1. Create a Zalo Bot**
|
||||||
|
|
||||||
|
* Create a bot in Zalo Developer / Bot console
|
||||||
|
* Copy your **Bot Token** (`BOT_TOKEN`)
|
||||||
|
* Choose a strong `secret_token` (8–256 chars). This is your own secret and will be sent back by Zalo in the `X-Bot-Api-Secret-Token` header.
|
||||||
|
* For full webhook options, see Zalo’s official [`setWebhook` docs](https://bot.zapps.me/docs/apis/setWebhook/).
|
||||||
|
|
||||||
|
**2. Configure**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"channels": {
|
||||||
|
"zalo": {
|
||||||
|
"enabled": true,
|
||||||
|
"token": "YOUR_ZALO_BOT_TOKEN",
|
||||||
|
"secret_token": "YOUR_SECRET_TOKEN",
|
||||||
|
"webhook_path": "/webhook/zalo",
|
||||||
|
"allow_from": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Run the Gateway**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
picoclaw gateway
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. Expose HTTPS locally (ngrok)**
|
||||||
|
|
||||||
|
Zalo requires an HTTPS webhook URL. For local development you can use ngrok:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ngrok http 18790
|
||||||
|
```
|
||||||
|
|
||||||
|
Then your webhook URL becomes:
|
||||||
|
|
||||||
|
`https://<your-ngrok-domain>/webhook/zalo`
|
||||||
|
|
||||||
|
**5. Register the webhook with Zalo**
|
||||||
|
|
||||||
|
Call Zalo `setWebhook` with your HTTPS URL and the same `secret_token` you configured above:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://bot-api.zaloplatforms.com/bot${BOT_TOKEN}/setWebhook" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"url":"https://<your-ngrok-domain>/webhook/zalo","secret_token":"YOUR_SECRET_TOKEN"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
After that, send a message to your bot on Zalo and PicoClaw will reply.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><b>Discord</b></summary>
|
<summary><b>Discord</b></summary>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -286,17 +286,67 @@ Pronto! Você tem um assistente de IA funcionando em 2 minutos.
|
||||||
|
|
||||||
## 💬 Integração com Apps de Chat
|
## 💬 Integração com Apps de Chat
|
||||||
|
|
||||||
Converse com seu PicoClaw via Telegram, Discord, DingTalk, LINE ou WeCom.
|
Converse com seu PicoClaw via Telegram, Zalo, Discord, DingTalk, LINE ou WeCom.
|
||||||
|
|
||||||
| Canal | Nível de Configuração |
|
| Canal | Nível de Configuração |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| **Telegram** | Fácil (apenas um token) |
|
| **Telegram** | Fácil (apenas um token) |
|
||||||
|
| **Zalo** | Médio (token + secret_token + webhook URL) |
|
||||||
| **Discord** | Fácil (bot token + intents) |
|
| **Discord** | Fácil (bot token + intents) |
|
||||||
| **QQ** | Fácil (AppID + AppSecret) |
|
| **QQ** | Fácil (AppID + AppSecret) |
|
||||||
| **DingTalk** | Médio (credenciais do app) |
|
| **DingTalk** | Médio (credenciais do app) |
|
||||||
| **LINE** | Médio (credenciais + webhook URL) |
|
| **LINE** | Médio (credenciais + webhook URL) |
|
||||||
| **WeCom AI Bot** | Médio (Token + chave AES) |
|
| **WeCom AI Bot** | Médio (Token + chave AES) |
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>Zalo</b> (Webhook)</summary>
|
||||||
|
|
||||||
|
**1. Preparar**
|
||||||
|
|
||||||
|
* Pegue seu `BOT_TOKEN` do Zalo
|
||||||
|
* Defina um `secret_token` (8–256 chars). O Zalo enviará esse valor no header `X-Bot-Api-Secret-Token`.
|
||||||
|
* Para mais detalhes de webhook, veja a documentação oficial [`setWebhook`](https://bot.zapps.me/docs/apis/setWebhook/).
|
||||||
|
|
||||||
|
**2. Configurar**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"channels": {
|
||||||
|
"zalo": {
|
||||||
|
"enabled": true,
|
||||||
|
"token": "YOUR_ZALO_BOT_TOKEN",
|
||||||
|
"secret_token": "YOUR_SECRET_TOKEN",
|
||||||
|
"webhook_path": "/webhook/zalo",
|
||||||
|
"allow_from": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Executar**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
picoclaw gateway
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. HTTPS local (ngrok)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ngrok http 18790
|
||||||
|
```
|
||||||
|
|
||||||
|
Webhook URL: `https://<ngrok-domain>/webhook/zalo`
|
||||||
|
|
||||||
|
**5. Registrar webhook**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://bot-api.zaloplatforms.com/bot${BOT_TOKEN}/setWebhook" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"url":"https://<ngrok-domain>/webhook/zalo","secret_token":"YOUR_SECRET_TOKEN"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><b>Telegram</b> (Recomendado)</summary>
|
<summary><b>Telegram</b> (Recomendado)</summary>
|
||||||
|
|
||||||
|
|
|
||||||
52
README.vi.md
52
README.vi.md
|
|
@ -257,17 +257,67 @@ 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
|
## 💬 Tích hợp ứng dụng Chat
|
||||||
|
|
||||||
Trò chuyện với PicoClaw qua Telegram, Discord, DingTalk, LINE hoặc WeCom.
|
Trò chuyện với PicoClaw qua Telegram, Zalo, Discord, DingTalk, LINE hoặc WeCom.
|
||||||
|
|
||||||
| Kênh | Mức độ thiết lập |
|
| Kênh | Mức độ thiết lập |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| **Telegram** | Dễ (chỉ cần token) |
|
| **Telegram** | Dễ (chỉ cần token) |
|
||||||
|
| **Zalo** | Trung bình (token + secret_token + webhook URL) |
|
||||||
| **Discord** | Dễ (bot token + intents) |
|
| **Discord** | Dễ (bot token + intents) |
|
||||||
| **QQ** | Dễ (AppID + AppSecret) |
|
| **QQ** | Dễ (AppID + AppSecret) |
|
||||||
| **DingTalk** | Trung bình (app credentials) |
|
| **DingTalk** | Trung bình (app credentials) |
|
||||||
| **LINE** | Trung bình (credentials + webhook URL) |
|
| **LINE** | Trung bình (credentials + webhook URL) |
|
||||||
| **WeCom AI Bot** | Trung bình (Token + khóa AES) |
|
| **WeCom AI Bot** | Trung bình (Token + khóa AES) |
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>Zalo</b> (Webhook)</summary>
|
||||||
|
|
||||||
|
**1. Chuẩn bị**
|
||||||
|
|
||||||
|
* Lấy `BOT_TOKEN` của Zalo Bot
|
||||||
|
* Tạo `secret_token` (8–256 ký tự). Zalo sẽ gửi lại trong header `X-Bot-Api-Secret-Token`.
|
||||||
|
* Xem thêm chi tiết webhook tại tài liệu chính thức [`setWebhook`](https://bot.zapps.me/docs/apis/setWebhook/).
|
||||||
|
|
||||||
|
**2. Cấu hình**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"channels": {
|
||||||
|
"zalo": {
|
||||||
|
"enabled": true,
|
||||||
|
"token": "YOUR_ZALO_BOT_TOKEN",
|
||||||
|
"secret_token": "YOUR_SECRET_TOKEN",
|
||||||
|
"webhook_path": "/webhook/zalo",
|
||||||
|
"allow_from": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Chạy**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
picoclaw gateway
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. HTTPS local (ngrok)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ngrok http 18790
|
||||||
|
```
|
||||||
|
|
||||||
|
Webhook URL: `https://<ngrok-domain>/webhook/zalo`
|
||||||
|
|
||||||
|
**5. Đăng ký webhook**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://bot-api.zaloplatforms.com/bot${BOT_TOKEN}/setWebhook" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"url":"https://<ngrok-domain>/webhook/zalo","secret_token":"YOUR_SECRET_TOKEN"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><b>Telegram</b> (Khuyên dùng)</summary>
|
<summary><b>Telegram</b> (Khuyên dùng)</summary>
|
||||||
|
|
||||||
|
|
|
||||||
61
README.zh.md
61
README.zh.md
|
|
@ -310,6 +310,7 @@ PicoClaw 支持多种聊天平台,使您的 Agent 能够连接到任何地方
|
||||||
| 渠道 | 设置难度 | 特性说明 | 文档链接 |
|
| 渠道 | 设置难度 | 特性说明 | 文档链接 |
|
||||||
| -------------------- | ----------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
| -------------------- | ----------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||||
| **Telegram** | ⭐ 简单 | 推荐,支持语音转文字,长轮询无需公网 | [查看文档](docs/channels/telegram/README.zh.md) |
|
| **Telegram** | ⭐ 简单 | 推荐,支持语音转文字,长轮询无需公网 | [查看文档](docs/channels/telegram/README.zh.md) |
|
||||||
|
| **Zalo** | ⭐⭐ 中等 | Webhook 模式(需要 HTTPS),适合本地 ngrok 调试 | 参见主 README 中的 Zalo(Webhook) 章节 |
|
||||||
| **Discord** | ⭐ 简单 | Socket Mode,支持群组/私信,Bot 生态成熟 | [查看文档](docs/channels/discord/README.zh.md) |
|
| **Discord** | ⭐ 简单 | Socket Mode,支持群组/私信,Bot 生态成熟 | [查看文档](docs/channels/discord/README.zh.md) |
|
||||||
| **Slack** | ⭐ 简单 | **Socket Mode** (无需公网 IP),企业级支持 | [查看文档](docs/channels/slack/README.zh.md) |
|
| **Slack** | ⭐ 简单 | **Socket Mode** (无需公网 IP),企业级支持 | [查看文档](docs/channels/slack/README.zh.md) |
|
||||||
| **Matrix** | ⭐⭐ 中等 | 联邦协议,支持自建 homeserver 与公开服务器 | [查看文档](docs/channels/matrix/README.zh.md) |
|
| **Matrix** | ⭐⭐ 中等 | 联邦协议,支持自建 homeserver 与公开服务器 | [查看文档](docs/channels/matrix/README.zh.md) |
|
||||||
|
|
@ -321,6 +322,66 @@ PicoClaw 支持多种聊天平台,使您的 Agent 能够连接到任何地方
|
||||||
| **OneBot** | ⭐⭐ 中等 | 兼容 NapCat/Go-CQHTTP,社区生态丰富 | [查看文档](docs/channels/onebot/README.zh.md) |
|
| **OneBot** | ⭐⭐ 中等 | 兼容 NapCat/Go-CQHTTP,社区生态丰富 | [查看文档](docs/channels/onebot/README.zh.md) |
|
||||||
| **MaixCam** | ⭐ 简单 | 专为 AI 摄像头设计的硬件集成通道 | [查看文档](docs/channels/maixcam/README.zh.md) |
|
| **MaixCam** | ⭐ 简单 | 专为 AI 摄像头设计的硬件集成通道 | [查看文档](docs/channels/maixcam/README.zh.md) |
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>Zalo</b> (Webhook)</summary>
|
||||||
|
|
||||||
|
PicoClaw 通过官方 Zalo Bot API webhook 流程与 Zalo Bot 集成。
|
||||||
|
|
||||||
|
**1. 创建 Zalo Bot**
|
||||||
|
|
||||||
|
* 在 Zalo Developer / Bot 控制台创建机器人
|
||||||
|
* 复制您的 **Bot Token** (`BOT_TOKEN`)
|
||||||
|
* 选择一个强密码 `secret_token`(8–256 字符)。这是您自己的密钥,Zalo 会在 `X-Bot-Api-Secret-Token` 请求头中返回此值。
|
||||||
|
* 有关完整的 webhook 选项,请参阅 Zalo 官方 [`setWebhook` 文档](https://bot.zapps.me/docs/apis/setWebhook/)。
|
||||||
|
|
||||||
|
**2. 配置**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"channels": {
|
||||||
|
"zalo": {
|
||||||
|
"enabled": true,
|
||||||
|
"token": "YOUR_ZALO_BOT_TOKEN",
|
||||||
|
"secret_token": "YOUR_SECRET_TOKEN",
|
||||||
|
"webhook_path": "/webhook/zalo",
|
||||||
|
"allow_from": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. 运行 Gateway**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
picoclaw gateway
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. 本地暴露 HTTPS (ngrok)**
|
||||||
|
|
||||||
|
Zalo 需要 HTTPS webhook URL。对于本地开发,您可以使用 ngrok:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ngrok http 18790
|
||||||
|
```
|
||||||
|
|
||||||
|
然后您的 webhook URL 变为:
|
||||||
|
|
||||||
|
`https://<your-ngrok-domain>/webhook/zalo`
|
||||||
|
|
||||||
|
**5. 向 Zalo 注册 webhook**
|
||||||
|
|
||||||
|
使用您的 HTTPS URL 和上面配置的相同 `secret_token` 调用 Zalo `setWebhook`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://bot-api.zaloplatforms.com/bot${BOT_TOKEN}/setWebhook" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"url":"https://<your-ngrok-domain>/webhook/zalo","secret_token":"YOUR_SECRET_TOKEN"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
之后,在 Zalo 上向您的机器人发送消息,PicoClaw 将回复。
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
### Telegram 命令注册(启动时自动同步)
|
### Telegram 命令注册(启动时自动同步)
|
||||||
|
|
||||||
PicoClaw 现在使用统一的命令定义来源。启动时会自动将 Telegram 支持的命令(例如 `/start`、`/help`、`/show`、`/list`)注册到 Bot 命令菜单,确保菜单展示与实际行为一致。
|
PicoClaw 现在使用统一的命令定义来源。启动时会自动将 Telegram 支持的命令(例如 `/start`、`/help`、`/show`、`/list`)注册到 Bot 命令菜单,确保菜单展示与实际行为一致。
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,14 @@
|
||||||
},
|
},
|
||||||
"reasoning_channel_id": ""
|
"reasoning_channel_id": ""
|
||||||
},
|
},
|
||||||
|
"zalo": {
|
||||||
|
"enabled": false,
|
||||||
|
"token": "YOUR_ZALO_BOT_TOKEN",
|
||||||
|
"secret_token": "YOUR_SECRET_TOKEN",
|
||||||
|
"webhook_path": "/webhook/zalo",
|
||||||
|
"allow_from": [],
|
||||||
|
"reasoning_channel_id": ""
|
||||||
|
},
|
||||||
"qq": {
|
"qq": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"app_id": "YOUR_QQ_APP_ID",
|
"app_id": "YOUR_QQ_APP_ID",
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ type placeholderEntry struct {
|
||||||
// channelRateConfig maps channel name to per-second rate limit.
|
// channelRateConfig maps channel name to per-second rate limit.
|
||||||
var channelRateConfig = map[string]float64{
|
var channelRateConfig = map[string]float64{
|
||||||
"telegram": 20,
|
"telegram": 20,
|
||||||
|
"zalo": 10,
|
||||||
"discord": 1,
|
"discord": 1,
|
||||||
"slack": 1,
|
"slack": 1,
|
||||||
"matrix": 2,
|
"matrix": 2,
|
||||||
|
|
@ -239,6 +240,11 @@ func (m *Manager) initChannels() error {
|
||||||
m.initChannel("telegram", "Telegram")
|
m.initChannel("telegram", "Telegram")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if m.config.Channels.Zalo.Enabled && m.config.Channels.Zalo.Token != "" &&
|
||||||
|
m.config.Channels.Zalo.SecretToken != "" {
|
||||||
|
m.initChannel("zalo", "Zalo")
|
||||||
|
}
|
||||||
|
|
||||||
if m.config.Channels.WhatsApp.Enabled {
|
if m.config.Channels.WhatsApp.Enabled {
|
||||||
waCfg := m.config.Channels.WhatsApp
|
waCfg := m.config.Channels.WhatsApp
|
||||||
if waCfg.UseNative {
|
if waCfg.UseNative {
|
||||||
|
|
|
||||||
13
pkg/channels/zalo/init.go
Normal file
13
pkg/channels/zalo/init.go
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
package zalo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sipeed/picoclaw/pkg/bus"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/channels"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
channels.RegisterFactory("zalo", func(cfg *config.Config, b *bus.MessageBus) (channels.Channel, error) {
|
||||||
|
return NewZaloChannel(cfg, b)
|
||||||
|
})
|
||||||
|
}
|
||||||
402
pkg/channels/zalo/zalo.go
Normal file
402
pkg/channels/zalo/zalo.go
Normal file
|
|
@ -0,0 +1,402 @@
|
||||||
|
package zalo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sipeed/picoclaw/pkg/bus"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/channels"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/identity"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/logger"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const zaloAPIBase = "https://bot-api.zaloplatforms.com/bot"
|
||||||
|
|
||||||
|
type ZaloChannel struct {
|
||||||
|
*channels.BaseChannel
|
||||||
|
cfg config.ZaloConfig
|
||||||
|
httpClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewZaloChannel(cfg *config.Config, messageBus *bus.MessageBus) (*ZaloChannel, error) {
|
||||||
|
zc := cfg.Channels.Zalo
|
||||||
|
if strings.TrimSpace(zc.Token) == "" {
|
||||||
|
return nil, fmt.Errorf("zalo token is required")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(zc.SecretToken) == "" {
|
||||||
|
return nil, fmt.Errorf("zalo secret_token is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
base := channels.NewBaseChannel(
|
||||||
|
"zalo",
|
||||||
|
zc,
|
||||||
|
messageBus,
|
||||||
|
zc.AllowFrom,
|
||||||
|
channels.WithMaxMessageLength(2000),
|
||||||
|
channels.WithGroupTrigger(zc.GroupTrigger),
|
||||||
|
channels.WithReasoningChannelID(zc.ReasoningChannelID),
|
||||||
|
)
|
||||||
|
|
||||||
|
return &ZaloChannel{
|
||||||
|
BaseChannel: base,
|
||||||
|
cfg: zc,
|
||||||
|
httpClient: &http.Client{Timeout: 30 * time.Second},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ZaloChannel) Start(ctx context.Context) error {
|
||||||
|
if _, err := c.getMe(ctx); err != nil {
|
||||||
|
logger.ErrorCF("zalo", "Failed to start channel", map[string]any{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.SetRunning(true)
|
||||||
|
logger.InfoC("zalo", "Zalo channel started successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ZaloChannel) Stop(ctx context.Context) error {
|
||||||
|
c.SetRunning(false)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ZaloChannel) WebhookPath() string {
|
||||||
|
if p := strings.TrimSpace(c.cfg.WebhookPath); p != "" {
|
||||||
|
if strings.HasPrefix(p, "/") {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
return "/" + p
|
||||||
|
}
|
||||||
|
return "/webhook/zalo"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ZaloChannel) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
logger.ErrorCF("zalo", "Failed to read request body", map[string]any{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
http.Error(w, "Bad request", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secret := r.Header.Get("X-Bot-Api-Secret-Token")
|
||||||
|
if subtle.ConstantTimeCompare([]byte(secret), []byte(c.cfg.SecretToken)) != 1 {
|
||||||
|
logger.WarnC("zalo", "Invalid webhook secret token")
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload map[string]any
|
||||||
|
if err := json.Unmarshal(body, &payload); err != nil {
|
||||||
|
logger.ErrorCF("zalo", "Failed to parse webhook payload", map[string]any{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
http.Error(w, "Bad request", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
c.processPayload(context.Background(), payload)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ZaloChannel) processPayload(ctx context.Context, payload map[string]any) {
|
||||||
|
logger.DebugCF("zalo", "Processing webhook payload", map[string]any{
|
||||||
|
"has_result": payload["result"] != nil,
|
||||||
|
})
|
||||||
|
if v, ok := payload["result"]; ok {
|
||||||
|
if arr, ok := v.([]any); ok {
|
||||||
|
for _, item := range arr {
|
||||||
|
if m, ok := item.(map[string]any); ok {
|
||||||
|
c.processUpdate(ctx, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.processUpdate(ctx, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ZaloChannel) processUpdate(ctx context.Context, upd map[string]any) {
|
||||||
|
msgAny, _ := upd["message"]
|
||||||
|
msg, _ := msgAny.(map[string]any)
|
||||||
|
if msg == nil {
|
||||||
|
msg = upd
|
||||||
|
}
|
||||||
|
|
||||||
|
content := getString(msg, "text")
|
||||||
|
if strings.TrimSpace(content) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
messageID := getString(msg, "message_id")
|
||||||
|
if messageID == "" {
|
||||||
|
messageID = getString(msg, "id")
|
||||||
|
}
|
||||||
|
|
||||||
|
chatID := getString(msg, "chat_id")
|
||||||
|
if chatID == "" {
|
||||||
|
chatID = getStringPath(msg, "chat", "id")
|
||||||
|
}
|
||||||
|
if chatID == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
senderID := getStringPath(msg, "from", "id")
|
||||||
|
if senderID == "" {
|
||||||
|
senderID = getString(msg, "from_id")
|
||||||
|
}
|
||||||
|
if senderID == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peerKind := "direct"
|
||||||
|
if t := getStringPath(msg, "chat", "type"); t != "" {
|
||||||
|
if strings.Contains(strings.ToLower(t), "group") {
|
||||||
|
peerKind = "group"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if peerKind == "group" {
|
||||||
|
should, cleaned := c.ShouldRespondInGroup(false, content)
|
||||||
|
if !should {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content = cleaned
|
||||||
|
}
|
||||||
|
|
||||||
|
sender := bus.SenderInfo{
|
||||||
|
Platform: "zalo",
|
||||||
|
PlatformID: senderID,
|
||||||
|
CanonicalID: identity.BuildCanonicalID("zalo", senderID),
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.IsAllowedSender(sender) {
|
||||||
|
logger.DebugCF("zalo", "Message from disallowed sender", map[string]any{
|
||||||
|
"sender_id": senderID,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peer := bus.Peer{
|
||||||
|
Kind: peerKind,
|
||||||
|
ID: chatID,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.DebugCF("zalo", "Received message", map[string]any{
|
||||||
|
"sender_id": senderID,
|
||||||
|
"chat_id": chatID,
|
||||||
|
"peer_kind": peerKind,
|
||||||
|
"preview": utils.Truncate(content, 50),
|
||||||
|
})
|
||||||
|
|
||||||
|
c.HandleMessage(ctx, peer, messageID, senderID, chatID, content, nil, nil, sender)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ZaloChannel) Send(ctx context.Context, msg bus.OutboundMessage) error {
|
||||||
|
if !c.IsRunning() {
|
||||||
|
return channels.ErrNotRunning
|
||||||
|
}
|
||||||
|
return c.sendMessage(ctx, msg.ChatID, msg.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ZaloChannel) StartTyping(ctx context.Context, chatID string) (func(), error) {
|
||||||
|
if !c.IsRunning() {
|
||||||
|
return func() {}, channels.ErrNotRunning
|
||||||
|
}
|
||||||
|
err := c.sendChatAction(ctx, chatID, "typing")
|
||||||
|
return func() {}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ZaloChannel) SendMedia(ctx context.Context, msg bus.OutboundMediaMessage) error {
|
||||||
|
if !c.IsRunning() {
|
||||||
|
return channels.ErrNotRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, part := range msg.Parts {
|
||||||
|
if part.Type != "image" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ref := strings.TrimSpace(part.Ref)
|
||||||
|
if strings.HasPrefix(ref, "http://") || strings.HasPrefix(ref, "https://") {
|
||||||
|
if err := c.sendPhoto(ctx, msg.ChatID, ref, part.Caption); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
caption := strings.TrimSpace(part.Caption)
|
||||||
|
if caption == "" {
|
||||||
|
caption = part.Filename
|
||||||
|
}
|
||||||
|
if caption != "" {
|
||||||
|
if err := c.sendMessage(ctx, msg.ChatID, caption); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ZaloChannel) apiURL(method string) string {
|
||||||
|
return strings.TrimRight(zaloAPIBase, "/") + c.cfg.Token + "/" + strings.TrimLeft(method, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ZaloChannel) doPOST(ctx context.Context, method string, payload any) (int, []byte, error) {
|
||||||
|
b, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.apiURL(method), bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err := c.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, channels.ClassifyNetError(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return resp.StatusCode, nil, channels.ClassifyNetError(err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
return resp.StatusCode, data, channels.ClassifySendError(
|
||||||
|
resp.StatusCode,
|
||||||
|
fmt.Errorf("http %d", resp.StatusCode),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.StatusCode, data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ZaloChannel) getMe(ctx context.Context) (map[string]any, error) {
|
||||||
|
_, data, err := c.doPOST(ctx, "getMe", map[string]any{})
|
||||||
|
if err != nil {
|
||||||
|
logger.ErrorCF("zalo", "Failed to call getMe API", map[string]any{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var out map[string]any
|
||||||
|
if err := json.Unmarshal(data, &out); err != nil {
|
||||||
|
logger.ErrorCF("zalo", "Failed to parse getMe response", map[string]any{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ok, _ := out["ok"].(bool); !ok {
|
||||||
|
err := fmt.Errorf("zalo getMe returned ok=false")
|
||||||
|
logger.ErrorCF("zalo", "getMe API returned error", map[string]any{
|
||||||
|
"response": out,
|
||||||
|
})
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ZaloChannel) sendMessage(ctx context.Context, chatID, text string) error {
|
||||||
|
_, data, err := c.doPOST(ctx, "sendMessage", map[string]any{
|
||||||
|
"chat_id": chatID,
|
||||||
|
"text": text,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.ErrorCF("zalo", "Failed to send message", map[string]any{
|
||||||
|
"chat_id": chatID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var out map[string]any
|
||||||
|
if err := json.Unmarshal(data, &out); err != nil {
|
||||||
|
logger.ErrorCF("zalo", "Failed to parse sendMessage response", map[string]any{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ok, _ := out["ok"].(bool); !ok {
|
||||||
|
err := fmt.Errorf("zalo sendMessage returned ok=false")
|
||||||
|
logger.ErrorCF("zalo", "sendMessage API returned error", map[string]any{
|
||||||
|
"chat_id": chatID,
|
||||||
|
"response": out,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ZaloChannel) sendPhoto(ctx context.Context, chatID, photoURL, caption string) error {
|
||||||
|
payload := map[string]any{
|
||||||
|
"chat_id": chatID,
|
||||||
|
"photo": photoURL,
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(caption) != "" {
|
||||||
|
payload["caption"] = caption
|
||||||
|
}
|
||||||
|
_, _, err := c.doPOST(ctx, "sendPhoto", payload)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ZaloChannel) sendChatAction(ctx context.Context, chatID, action string) error {
|
||||||
|
_, _, err := c.doPOST(ctx, "sendChatAction", map[string]any{
|
||||||
|
"chat_id": chatID,
|
||||||
|
"action": action,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getString(m map[string]any, key string) string {
|
||||||
|
if m == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
v, ok := m[key]
|
||||||
|
if !ok || v == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
switch t := v.(type) {
|
||||||
|
case string:
|
||||||
|
return t
|
||||||
|
case float64:
|
||||||
|
if t == float64(int64(t)) {
|
||||||
|
return fmt.Sprintf("%.0f", t)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v", t)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%v", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStringPath(m map[string]any, k1, k2 string) string {
|
||||||
|
v, ok := m[k1]
|
||||||
|
if !ok || v == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
m2, ok := v.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return getString(m2, k2)
|
||||||
|
}
|
||||||
|
|
@ -259,6 +259,7 @@ func (d *AgentDefaults) GetModelName() string {
|
||||||
type ChannelsConfig struct {
|
type ChannelsConfig struct {
|
||||||
WhatsApp WhatsAppConfig `json:"whatsapp"`
|
WhatsApp WhatsAppConfig `json:"whatsapp"`
|
||||||
Telegram TelegramConfig `json:"telegram"`
|
Telegram TelegramConfig `json:"telegram"`
|
||||||
|
Zalo ZaloConfig `json:"zalo"`
|
||||||
Feishu FeishuConfig `json:"feishu"`
|
Feishu FeishuConfig `json:"feishu"`
|
||||||
Discord DiscordConfig `json:"discord"`
|
Discord DiscordConfig `json:"discord"`
|
||||||
MaixCam MaixCamConfig `json:"maixcam"`
|
MaixCam MaixCamConfig `json:"maixcam"`
|
||||||
|
|
@ -313,6 +314,16 @@ type TelegramConfig struct {
|
||||||
ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_TELEGRAM_REASONING_CHANNEL_ID"`
|
ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_TELEGRAM_REASONING_CHANNEL_ID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ZaloConfig struct {
|
||||||
|
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_ZALO_ENABLED"`
|
||||||
|
Token string `json:"token" env:"PICOCLAW_CHANNELS_ZALO_TOKEN"`
|
||||||
|
SecretToken string `json:"secret_token" env:"PICOCLAW_CHANNELS_ZALO_SECRET_TOKEN"`
|
||||||
|
WebhookPath string `json:"webhook_path" env:"PICOCLAW_CHANNELS_ZALO_WEBHOOK_PATH"`
|
||||||
|
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_ZALO_ALLOW_FROM"`
|
||||||
|
GroupTrigger GroupTriggerConfig `json:"group_trigger,omitempty"`
|
||||||
|
ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_ZALO_REASONING_CHANNEL_ID"`
|
||||||
|
}
|
||||||
|
|
||||||
type FeishuConfig struct {
|
type FeishuConfig struct {
|
||||||
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_FEISHU_ENABLED"`
|
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_FEISHU_ENABLED"`
|
||||||
AppID string `json:"app_id" env:"PICOCLAW_CHANNELS_FEISHU_APP_ID"`
|
AppID string `json:"app_id" env:"PICOCLAW_CHANNELS_FEISHU_APP_ID"`
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,13 @@ func DefaultConfig() *Config {
|
||||||
Text: "Thinking... 💭",
|
Text: "Thinking... 💭",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Zalo: ZaloConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Token: "",
|
||||||
|
SecretToken: "",
|
||||||
|
WebhookPath: "/webhook/zalo",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
},
|
||||||
Feishu: FeishuConfig{
|
Feishu: FeishuConfig{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
AppID: "",
|
AppID: "",
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import (
|
||||||
_ "github.com/sipeed/picoclaw/pkg/channels/wecom"
|
_ "github.com/sipeed/picoclaw/pkg/channels/wecom"
|
||||||
_ "github.com/sipeed/picoclaw/pkg/channels/whatsapp"
|
_ "github.com/sipeed/picoclaw/pkg/channels/whatsapp"
|
||||||
_ "github.com/sipeed/picoclaw/pkg/channels/whatsapp_native"
|
_ "github.com/sipeed/picoclaw/pkg/channels/whatsapp_native"
|
||||||
|
_ "github.com/sipeed/picoclaw/pkg/channels/zalo"
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
"github.com/sipeed/picoclaw/pkg/cron"
|
"github.com/sipeed/picoclaw/pkg/cron"
|
||||||
"github.com/sipeed/picoclaw/pkg/devices"
|
"github.com/sipeed/picoclaw/pkg/devices"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue