mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-03-18 04:25:14 +08:00
feat: complete the QQ channel and supplement the docs
This commit is contained in:
26
README.md
26
README.md
@@ -7,7 +7,7 @@
|
|||||||
[中文] | [<a href="docs/en/README.md">English</a>]
|
[中文] | [<a href="docs/en/README.md">English</a>]
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
**CowAgent** 是基于大模型的超级AI助理,能够主动思考和任务规划、操作计算机和外部资源、创造和执行Skills、拥有长期记忆并不断成长。CowAgent 支持灵活切换多种模型,能处理文本、语音、图片、文件等多模态消息,可接入网页、飞书、钉钉、企微智能机器人、企业微信应用、微信公众号中使用,7*24小时运行于你的个人电脑或服务器中。
|
**CowAgent** 是基于大模型的超级AI助理,能够主动思考和任务规划、操作计算机和外部资源、创造和执行Skills、拥有长期记忆并不断成长。CowAgent 支持灵活切换多种模型,能处理文本、语音、图片、文件等多模态消息,可接入网页、飞书、钉钉、企微智能机器人、QQ、企微自建应用、微信公众号中使用,7*24小时运行于你的个人电脑或服务器中。
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://cowagent.ai/">🌐 官网</a> ·
|
<a href="https://cowagent.ai/">🌐 官网</a> ·
|
||||||
@@ -143,7 +143,7 @@ pip3 install -r requirements-optional.txt
|
|||||||
```bash
|
```bash
|
||||||
# config.json 文件内容示例
|
# config.json 文件内容示例
|
||||||
{
|
{
|
||||||
"channel_type": "web", # 接入渠道类型,默认为web,支持修改为:feishu,dingtalk,wecom_bot,wechatcom_app,wechatmp_service,wechatmp,terminal
|
"channel_type": "web", # 接入渠道类型,默认为web,支持修改为:feishu,dingtalk,wecom_bot,qq,wechatcom_app,wechatmp_service,wechatmp,terminal
|
||||||
"model": "MiniMax-M2.5", # 模型名称
|
"model": "MiniMax-M2.5", # 模型名称
|
||||||
"minimax_api_key": "", # MiniMax API Key
|
"minimax_api_key": "", # MiniMax API Key
|
||||||
"zhipu_ai_api_key": "", # 智谱GLM API Key
|
"zhipu_ai_api_key": "", # 智谱GLM API Key
|
||||||
@@ -702,7 +702,23 @@ API Key创建:在 [控制台](https://aistudio.google.com/app/apikey?hl=zh-cn)
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>5. WeCom App - 企业微信应用</summary>
|
<summary>5. QQ - QQ 机器人</summary>
|
||||||
|
|
||||||
|
QQ 机器人使用 WebSocket 长连接模式,无需公网 IP 和域名,支持 QQ 单聊、群聊和频道消息:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"channel_type": "qq",
|
||||||
|
"qq_app_id": "YOUR_APP_ID",
|
||||||
|
"qq_app_secret": "YOUR_APP_SECRET"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
详细步骤和参数说明参考 [QQ 机器人接入](https://docs.cowagent.ai/channels/qq)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>6. WeCom App - 企业微信应用</summary>
|
||||||
|
|
||||||
企业微信自建应用接入需在后台创建应用并启用消息回调,配置示例:
|
企业微信自建应用接入需在后台创建应用并启用消息回调,配置示例:
|
||||||
|
|
||||||
@@ -722,7 +738,7 @@ API Key创建:在 [控制台](https://aistudio.google.com/app/apikey?hl=zh-cn)
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>6. WeChat MP - 微信公众号</summary>
|
<summary>7. WeChat MP - 微信公众号</summary>
|
||||||
|
|
||||||
本项目支持订阅号和服务号两种公众号,通过服务号(`wechatmp_service`)体验更佳。
|
本项目支持订阅号和服务号两种公众号,通过服务号(`wechatmp_service`)体验更佳。
|
||||||
|
|
||||||
@@ -757,7 +773,7 @@ API Key创建:在 [控制台](https://aistudio.google.com/app/apikey?hl=zh-cn)
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>7. Terminal - 终端</summary>
|
<summary>8. Terminal - 终端</summary>
|
||||||
|
|
||||||
修改 `config.json` 中的 `channel_type` 字段:
|
修改 `config.json` 中的 `channel_type` 字段:
|
||||||
|
|
||||||
|
|||||||
@@ -237,6 +237,8 @@ def _execute_send_message(task: dict, agent_bridge):
|
|||||||
logger.warning(f"[Scheduler] Task {task['id']}: DingTalk single chat message missing sender_staff_id")
|
logger.warning(f"[Scheduler] Task {task['id']}: DingTalk single chat message missing sender_staff_id")
|
||||||
elif channel_type == "wecom_bot":
|
elif channel_type == "wecom_bot":
|
||||||
context["msg"] = None
|
context["msg"] = None
|
||||||
|
elif channel_type == "qq":
|
||||||
|
context["msg"] = None
|
||||||
|
|
||||||
# Create reply
|
# Create reply
|
||||||
reply = Reply(ReplyType.TEXT, content)
|
reply = Reply(ReplyType.TEXT, content)
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class QQChannel(ChatChannel):
|
|||||||
self._access_token = data.get("access_token", "")
|
self._access_token = data.get("access_token", "")
|
||||||
expires_in = int(data.get("expires_in", 7200))
|
expires_in = int(data.get("expires_in", 7200))
|
||||||
self._token_expires_at = time.time() + expires_in - 60
|
self._token_expires_at = time.time() + expires_in - 60
|
||||||
logger.info(f"[QQ] Access token refreshed, expires_in={expires_in}s")
|
logger.debug(f"[QQ] Access token refreshed, expires_in={expires_in}s")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[QQ] Failed to refresh access_token: {e}")
|
logger.error(f"[QQ] Failed to refresh access_token: {e}")
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ class QQChannel(ChatChannel):
|
|||||||
)
|
)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
url = resp.json().get("url", "")
|
url = resp.json().get("url", "")
|
||||||
logger.info(f"[QQ] Gateway URL: {url}")
|
logger.debug(f"[QQ] Gateway URL: {url}")
|
||||||
return url
|
return url
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[QQ] Failed to get gateway URL: {e}")
|
logger.error(f"[QQ] Failed to get gateway URL: {e}")
|
||||||
@@ -173,7 +173,7 @@ class QQChannel(ChatChannel):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _on_open(ws):
|
def _on_open(ws):
|
||||||
logger.info("[QQ] WebSocket connected, waiting for Hello...")
|
logger.debug("[QQ] WebSocket connected, waiting for Hello...")
|
||||||
|
|
||||||
def _on_message(ws, raw):
|
def _on_message(ws, raw):
|
||||||
try:
|
try:
|
||||||
@@ -242,7 +242,7 @@ class QQChannel(ChatChannel):
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
logger.info(f"[QQ] Identify sent with intents={DEFAULT_INTENTS}")
|
logger.debug(f"[QQ] Identify sent with intents={DEFAULT_INTENTS}")
|
||||||
|
|
||||||
def _send_resume(self):
|
def _send_resume(self):
|
||||||
self._ws_send({
|
self._ws_send({
|
||||||
@@ -253,7 +253,7 @@ class QQChannel(ChatChannel):
|
|||||||
"seq": self._last_seq,
|
"seq": self._last_seq,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
logger.info(f"[QQ] Resume sent: session_id={self._session_id}, seq={self._last_seq}")
|
logger.debug(f"[QQ] Resume sent: session_id={self._session_id}, seq={self._last_seq}")
|
||||||
|
|
||||||
def _start_heartbeat(self, interval_ms: int):
|
def _start_heartbeat(self, interval_ms: int):
|
||||||
if self._heartbeat_thread and self._heartbeat_thread.is_alive():
|
if self._heartbeat_thread and self._heartbeat_thread.is_alive():
|
||||||
@@ -291,7 +291,7 @@ class QQChannel(ChatChannel):
|
|||||||
|
|
||||||
if op == OP_HELLO:
|
if op == OP_HELLO:
|
||||||
heartbeat_interval = d.get("heartbeat_interval", 45000) if d else 45000
|
heartbeat_interval = d.get("heartbeat_interval", 45000) if d else 45000
|
||||||
logger.info(f"[QQ] Received Hello, heartbeat_interval={heartbeat_interval}ms")
|
logger.debug(f"[QQ] Received Hello, heartbeat_interval={heartbeat_interval}ms")
|
||||||
self._heartbeat_interval = heartbeat_interval
|
self._heartbeat_interval = heartbeat_interval
|
||||||
if self._can_resume and self._session_id:
|
if self._can_resume and self._session_id:
|
||||||
self._send_resume()
|
self._send_resume()
|
||||||
@@ -321,8 +321,8 @@ class QQChannel(ChatChannel):
|
|||||||
if t == "READY":
|
if t == "READY":
|
||||||
self._session_id = d.get("session_id", "")
|
self._session_id = d.get("session_id", "")
|
||||||
user = d.get("user", {})
|
user = d.get("user", {})
|
||||||
logger.info(f"[QQ] Ready: session_id={self._session_id}, "
|
bot_name = user.get('username', '')
|
||||||
f"bot={user.get('username', '')}")
|
logger.info(f"[QQ] ✅ Connected successfully (bot={bot_name})")
|
||||||
self._connected = True
|
self._connected = True
|
||||||
self._can_resume = False
|
self._can_resume = False
|
||||||
self._start_heartbeat(self._heartbeat_interval)
|
self._start_heartbeat(self._heartbeat_interval)
|
||||||
@@ -445,8 +445,13 @@ class QQChannel(ChatChannel):
|
|||||||
|
|
||||||
def send(self, reply: Reply, context: Context):
|
def send(self, reply: Reply, context: Context):
|
||||||
msg = context.get("msg")
|
msg = context.get("msg")
|
||||||
|
is_group = context.get("isgroup", False)
|
||||||
|
receiver = context.get("receiver", "")
|
||||||
|
|
||||||
if not msg:
|
if not msg:
|
||||||
logger.warning("[QQ] No msg in context, cannot send reply")
|
# Active send (e.g. scheduled tasks), no original message to reply to
|
||||||
|
self._active_send_text(reply.content if reply.type == ReplyType.TEXT else str(reply.content),
|
||||||
|
receiver, is_group)
|
||||||
return
|
return
|
||||||
|
|
||||||
event_type = getattr(msg, "event_type", "")
|
event_type = getattr(msg, "event_type", "")
|
||||||
@@ -521,6 +526,26 @@ class QQChannel(ChatChannel):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[QQ] Send message error: {e}")
|
logger.error(f"[QQ] Send message error: {e}")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Active send (no original message, e.g. scheduled tasks)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _active_send_text(self, content: str, receiver: str, is_group: bool):
|
||||||
|
"""Send text without an original message (active push). QQ limits active messages to 4/month per user."""
|
||||||
|
if not receiver:
|
||||||
|
logger.warning("[QQ] No receiver for active send")
|
||||||
|
return
|
||||||
|
if is_group:
|
||||||
|
url = f"{QQ_API_BASE}/v2/groups/{receiver}/messages"
|
||||||
|
else:
|
||||||
|
url = f"{QQ_API_BASE}/v2/users/{receiver}/messages"
|
||||||
|
body = {
|
||||||
|
"content": content,
|
||||||
|
"msg_type": 0,
|
||||||
|
}
|
||||||
|
event_label = "GROUP_ACTIVE" if is_group else "C2C_ACTIVE"
|
||||||
|
self._post_message(url, body, event_label)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Send text
|
# Send text
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|||||||
@@ -615,6 +615,15 @@ class ChannelsHandler:
|
|||||||
{"key": "wecom_bot_secret", "label": "Secret", "type": "secret"},
|
{"key": "wecom_bot_secret", "label": "Secret", "type": "secret"},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
("qq", {
|
||||||
|
"label": {"zh": "QQ 机器人", "en": "QQ Bot"},
|
||||||
|
"icon": "fa-comment",
|
||||||
|
"color": "blue",
|
||||||
|
"fields": [
|
||||||
|
{"key": "qq_app_id", "label": "App ID", "type": "text"},
|
||||||
|
{"key": "qq_app_secret", "label": "App Secret", "type": "secret"},
|
||||||
|
],
|
||||||
|
}),
|
||||||
("wechatcom_app", {
|
("wechatcom_app", {
|
||||||
"label": {"zh": "企微自建应用", "en": "WeCom App"},
|
"label": {"zh": "企微自建应用", "en": "WeCom App"},
|
||||||
"icon": "fa-building",
|
"icon": "fa-building",
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ CHANNEL_ACTIONS = {"channel_create", "channel_update", "channel_delete"}
|
|||||||
CREDENTIAL_MAP = {
|
CREDENTIAL_MAP = {
|
||||||
"feishu": ("feishu_app_id", "feishu_app_secret"),
|
"feishu": ("feishu_app_id", "feishu_app_secret"),
|
||||||
"dingtalk": ("dingtalk_client_id", "dingtalk_client_secret"),
|
"dingtalk": ("dingtalk_client_id", "dingtalk_client_secret"),
|
||||||
|
"wecom_bot": ("wecom_bot_id", "wecom_bot_secret"),
|
||||||
|
"qq": ("qq_app_id", "qq_app_secret"),
|
||||||
"wechatmp": ("wechatmp_app_id", "wechatmp_app_secret"),
|
"wechatmp": ("wechatmp_app_id", "wechatmp_app_secret"),
|
||||||
"wechatmp_service": ("wechatmp_app_id", "wechatmp_app_secret"),
|
"wechatmp_service": ("wechatmp_app_id", "wechatmp_app_secret"),
|
||||||
"wechatcom_app": ("wechatcomapp_agent_id", "wechatcomapp_secret"),
|
"wechatcom_app": ("wechatcomapp_agent_id", "wechatcomapp_secret"),
|
||||||
@@ -669,6 +671,12 @@ def _build_config():
|
|||||||
elif current_channel_type in ("wechatmp", "wechatmp_service"):
|
elif current_channel_type in ("wechatmp", "wechatmp_service"):
|
||||||
config["app_id"] = local_conf.get("wechatmp_app_id")
|
config["app_id"] = local_conf.get("wechatmp_app_id")
|
||||||
config["app_secret"] = local_conf.get("wechatmp_app_secret")
|
config["app_secret"] = local_conf.get("wechatmp_app_secret")
|
||||||
|
elif current_channel_type == "wecom_bot":
|
||||||
|
config["app_id"] = local_conf.get("wecom_bot_id")
|
||||||
|
config["app_secret"] = local_conf.get("wecom_bot_secret")
|
||||||
|
elif current_channel_type == "qq":
|
||||||
|
config["app_id"] = local_conf.get("qq_app_id")
|
||||||
|
config["app_secret"] = local_conf.get("qq_app_secret")
|
||||||
elif current_channel_type == "wechatcom_app":
|
elif current_channel_type == "wechatcom_app":
|
||||||
config["app_id"] = local_conf.get("wechatcomapp_agent_id")
|
config["app_id"] = local_conf.get("wechatcomapp_agent_id")
|
||||||
config["app_secret"] = local_conf.get("wechatcomapp_secret")
|
config["app_secret"] = local_conf.get("wechatcomapp_secret")
|
||||||
|
|||||||
@@ -381,6 +381,8 @@ def load_config():
|
|||||||
"wechatmp_app_secret": "WECHATMP_APP_SECRET",
|
"wechatmp_app_secret": "WECHATMP_APP_SECRET",
|
||||||
"wechatcomapp_agent_id": "WECHATCOMAPP_AGENT_ID",
|
"wechatcomapp_agent_id": "WECHATCOMAPP_AGENT_ID",
|
||||||
"wechatcomapp_secret": "WECHATCOMAPP_SECRET",
|
"wechatcomapp_secret": "WECHATCOMAPP_SECRET",
|
||||||
|
"qq_app_id": "QQ_APP_ID",
|
||||||
|
"qq_app_secret": "QQ_APP_SECRET"
|
||||||
}
|
}
|
||||||
injected = 0
|
injected = 0
|
||||||
for conf_key, env_key in _CONFIG_TO_ENV.items():
|
for conf_key, env_key in _CONFIG_TO_ENV.items():
|
||||||
|
|||||||
@@ -179,5 +179,7 @@ Agent支持在多种渠道中使用,只需修改 `config.json` 中的 `channel
|
|||||||
- **飞书接入**:[飞书接入文档](https://docs.link-ai.tech/cow/multi-platform/feishu)
|
- **飞书接入**:[飞书接入文档](https://docs.link-ai.tech/cow/multi-platform/feishu)
|
||||||
- **钉钉接入**:[钉钉接入文档](https://docs.link-ai.tech/cow/multi-platform/dingtalk)
|
- **钉钉接入**:[钉钉接入文档](https://docs.link-ai.tech/cow/multi-platform/dingtalk)
|
||||||
- **企业微信应用接入**:[企微应用文档](https://docs.link-ai.tech/cow/multi-platform/wechat-com)
|
- **企业微信应用接入**:[企微应用文档](https://docs.link-ai.tech/cow/multi-platform/wechat-com)
|
||||||
|
- **企微智能机器人**:[企微智能机器人文档](https://docs.link-ai.tech/cow/multi-platform/wecom-bot)
|
||||||
|
- **QQ机器人**:[QQ机器人文档](https://docs.link-ai.tech/cow/multi-platform/qq)
|
||||||
|
|
||||||
更多渠道配置参考:[通道说明](../README.md#通道说明)
|
更多渠道配置参考:[通道说明](../README.md#通道说明)
|
||||||
|
|||||||
88
docs/channels/qq.mdx
Normal file
88
docs/channels/qq.mdx
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
---
|
||||||
|
title: QQ 机器人
|
||||||
|
description: 将 CowAgent 接入 QQ 机器人(WebSocket 长连接模式)
|
||||||
|
---
|
||||||
|
|
||||||
|
> 通过 QQ 开放平台的机器人接口接入 CowAgent,支持 QQ 单聊、QQ 群聊(@机器人)、频道消息和频道私信,无需公网 IP,使用 WebSocket 长连接模式。
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
QQ 机器人通过 QQ 开放平台创建,使用 WebSocket 长连接接收消息,通过 OpenAPI 发送消息,无需公网 IP 和域名。
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
## 一、创建 QQ 机器人
|
||||||
|
|
||||||
|
> 进入[QQ 开放平台](https://q.qq.com),QQ扫码登录,如果未注册开放平台账号,请先完成[账号注册](https://q.qq.com/#/register)。
|
||||||
|
|
||||||
|
1.在 [QQ开放平台-机器人列表页](https://q.qq.com/#/apps),点击创建机器人:
|
||||||
|
|
||||||
|
<img src="https://cdn.link-ai.tech/doc/20260317162900.png" width="800"/>
|
||||||
|
|
||||||
|
2.填写机器人名称、头像等基本信息,完成创建:
|
||||||
|
|
||||||
|
<img src="https://cdn.link-ai.tech/doc/20260317163005.png" width="800"/>
|
||||||
|
|
||||||
|
3.点击进入机器人配置页面,选择**开发管理**菜单,完成以下步骤:
|
||||||
|
|
||||||
|
- 复制并记录 **AppID**(机器人ID)
|
||||||
|
- 生成并记录 **AppSecret**(机器人秘钥)
|
||||||
|
|
||||||
|
<img src="https://cdn.link-ai.tech/doc/20260317164955.png" width="800"/>
|
||||||
|
|
||||||
|
## 二、配置和运行
|
||||||
|
|
||||||
|
### 方式一:Web 控制台接入
|
||||||
|
|
||||||
|
启动 Cow项目后打开 Web 控制台 (本地链接为: http://127.0.0.1:9899/ ),选择 **通道** 菜单,点击 **接入通道**,选择 **QQ 机器人**,填写上一步保存的 AppID 和 AppSecret,点击接入即可。
|
||||||
|
|
||||||
|
<img src="https://cdn.link-ai.tech/doc/20260317165425.png" width="800"/>
|
||||||
|
|
||||||
|
### 方式二:配置文件接入
|
||||||
|
|
||||||
|
在 `config.json` 中添加以下配置:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"channel_type": "qq",
|
||||||
|
"qq_app_id": "YOUR_APP_ID",
|
||||||
|
"qq_app_secret": "YOUR_APP_SECRET"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 参数 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| `qq_app_id` | QQ 机器人的 AppID,在开放平台开发管理中获取 |
|
||||||
|
| `qq_app_secret` | QQ 机器人的 AppSecret,在开放平台开发管理中获取 |
|
||||||
|
|
||||||
|
配置完成后启动程序,日志显示 `[QQ] ✅ Connected successfully` 即表示连接成功。
|
||||||
|
|
||||||
|
|
||||||
|
## 三、使用
|
||||||
|
|
||||||
|
在 QQ开放平台 - 管理 - **使用范围和人员** 菜单中,使用QQ客户端扫描 "添加到群和消息列表" 的二维码,即可开始与QQ机器人的聊天:
|
||||||
|
|
||||||
|
<img src="https://cdn.link-ai.tech/doc/20260317165947.png" width="800"/>
|
||||||
|
|
||||||
|
对话效果:
|
||||||
|
<img src="https://cdn.link-ai.tech/doc/20260317171508.png" width="800"/>
|
||||||
|
|
||||||
|
## 四、功能说明
|
||||||
|
|
||||||
|
> 注意:若需在群聊及频道中使用QQ机器人,需完成发布上架审核并在使用范围配置权限使用范围。
|
||||||
|
|
||||||
|
| 功能 | 支持情况 |
|
||||||
|
| --- | --- |
|
||||||
|
| QQ 单聊 | ✅ |
|
||||||
|
| QQ 群聊(@机器人) | ✅ |
|
||||||
|
| 频道消息(@机器人) | ✅ |
|
||||||
|
| 频道私信 | ✅ |
|
||||||
|
| 文本消息 | ✅ 收发 |
|
||||||
|
| 图片消息 | ✅ 收发(群聊和单聊) |
|
||||||
|
| 文件消息 | ✅ 发送(群聊和单聊) |
|
||||||
|
| 定时任务 | ✅ 主动推送(每月每用户限 4 条) |
|
||||||
|
|
||||||
|
|
||||||
|
## 五、注意事项
|
||||||
|
|
||||||
|
- **被动消息限制**:QQ 单聊被动消息有效期为 60 分钟,每条消息最多回复 5 次;QQ 群聊被动消息有效期为 5 分钟。
|
||||||
|
- **主动消息限制**:单聊和群聊每月主动消息上限为 4 条,在使用定时任务功能时需要注意这个限制
|
||||||
|
- **事件权限**:默认订阅 `GROUP_AND_C2C_EVENT`(QQ群/单聊)和 `PUBLIC_GUILD_MESSAGES`(频道公域消息),如需其他事件类型请在开放平台申请权限。
|
||||||
@@ -29,7 +29,7 @@ description: 将 CowAgent 接入企业微信智能机器人(长连接模式)
|
|||||||
|
|
||||||
### 方式一:Web 控制台接入
|
### 方式一:Web 控制台接入
|
||||||
|
|
||||||
启动程序后打开 Web 控制台 (本地连接为: http://127.0.0.1:9899/ ),选择 **通道** 菜单,点击 **接入通道**,选择 **企微智能机器人**,填写上一步保存的 Bot ID 和 Secret,点击接入即可。
|
启动Cow项目后打开 Web 控制台 (本地链接为: http://127.0.0.1:9899/ ),选择 **通道** 菜单,点击 **接入通道**,选择 **企微智能机器人**,填写上一步保存的 Bot ID 和 Secret,点击接入即可。
|
||||||
|
|
||||||
<img src="https://cdn.link-ai.tech/doc/20260316181711.png" width="800"/>
|
<img src="https://cdn.link-ai.tech/doc/20260316181711.png" width="800"/>
|
||||||
|
|
||||||
|
|||||||
@@ -157,6 +157,7 @@
|
|||||||
"channels/feishu",
|
"channels/feishu",
|
||||||
"channels/dingtalk",
|
"channels/dingtalk",
|
||||||
"channels/wecom-bot",
|
"channels/wecom-bot",
|
||||||
|
"channels/qq",
|
||||||
"channels/wecom",
|
"channels/wecom",
|
||||||
"channels/wechatmp"
|
"channels/wechatmp"
|
||||||
]
|
]
|
||||||
@@ -300,6 +301,7 @@
|
|||||||
"en/channels/feishu",
|
"en/channels/feishu",
|
||||||
"en/channels/dingtalk",
|
"en/channels/dingtalk",
|
||||||
"en/channels/wecom-bot",
|
"en/channels/wecom-bot",
|
||||||
|
"en/channels/qq",
|
||||||
"en/channels/wecom",
|
"en/channels/wecom",
|
||||||
"en/channels/wechatmp"
|
"en/channels/wechatmp"
|
||||||
]
|
]
|
||||||
|
|||||||
88
docs/en/channels/qq.mdx
Normal file
88
docs/en/channels/qq.mdx
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
---
|
||||||
|
title: QQ Bot
|
||||||
|
description: Connect CowAgent to QQ Bot (WebSocket long connection)
|
||||||
|
---
|
||||||
|
|
||||||
|
> Connect CowAgent via QQ Open Platform's bot API, supporting QQ direct messages, group chats (@bot), guild channel messages, and guild DMs. No public IP required — uses WebSocket long connection.
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
QQ Bot is created through the QQ Open Platform. It uses WebSocket long connection to receive messages and OpenAPI to send messages. No public IP or domain is required.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
## 1. Create a QQ Bot
|
||||||
|
|
||||||
|
> Visit the [QQ Open Platform](https://q.qq.com), sign in with QQ. If you haven't registered, please complete [account registration](https://q.qq.com/#/register) first.
|
||||||
|
|
||||||
|
1.Go to the [QQ Open Platform - Bot List](https://q.qq.com/#/apps), and click **Create Bot**:
|
||||||
|
|
||||||
|
<img src="https://cdn.link-ai.tech/doc/20260317162900.png" width="800"/>
|
||||||
|
|
||||||
|
2.Fill in the bot name, avatar, and other basic information to complete the creation:
|
||||||
|
|
||||||
|
<img src="https://cdn.link-ai.tech/doc/20260317163005.png" width="800"/>
|
||||||
|
|
||||||
|
3.Enter the bot configuration page, go to **Development Management**, and complete the following steps:
|
||||||
|
|
||||||
|
- Copy and save the **AppID** (Bot ID)
|
||||||
|
- Generate and save the **AppSecret** (Bot Secret)
|
||||||
|
|
||||||
|
<img src="https://cdn.link-ai.tech/doc/20260317164955.png" width="800"/>
|
||||||
|
|
||||||
|
## 2. Configuration and Running
|
||||||
|
|
||||||
|
### Option A: Web Console
|
||||||
|
|
||||||
|
Start the program and open the Web console (local access: http://127.0.0.1:9899/). Go to the **Channels** tab, click **Connect Channel**, select **QQ Bot**, fill in the AppID and AppSecret from the previous step, and click Connect.
|
||||||
|
|
||||||
|
<img src="https://cdn.link-ai.tech/doc/20260317165425.png" width="800"/>
|
||||||
|
|
||||||
|
### Option B: Config File
|
||||||
|
|
||||||
|
Add the following to your `config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"channel_type": "qq",
|
||||||
|
"qq_app_id": "YOUR_APP_ID",
|
||||||
|
"qq_app_secret": "YOUR_APP_SECRET"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| `qq_app_id` | AppID of the QQ Bot, found in Development Management on the open platform |
|
||||||
|
| `qq_app_secret` | AppSecret of the QQ Bot, found in Development Management on the open platform |
|
||||||
|
|
||||||
|
After configuration, start the program. The log message `[QQ] ✅ Connected successfully` indicates a successful connection.
|
||||||
|
|
||||||
|
|
||||||
|
## 3. Usage
|
||||||
|
|
||||||
|
In the QQ Open Platform, go to **Management → Usage Scope & Members**, scan the "Add to group and message list" QR code with your QQ client to start chatting with the bot:
|
||||||
|
|
||||||
|
<img src="https://cdn.link-ai.tech/doc/20260317165947.png" width="800"/>
|
||||||
|
|
||||||
|
Chat example:
|
||||||
|
<img src="https://cdn.link-ai.tech/doc/20260317171508.png" width="800"/>
|
||||||
|
|
||||||
|
## 4. Supported Features
|
||||||
|
|
||||||
|
> Note: To use the QQ bot in group chats and guild channels, you need to complete the publishing review and configure usage scope permissions.
|
||||||
|
|
||||||
|
| Feature | Status |
|
||||||
|
| --- | --- |
|
||||||
|
| QQ Direct Messages | ✅ |
|
||||||
|
| QQ Group Chat (@bot) | ✅ |
|
||||||
|
| Guild Channel (@bot) | ✅ |
|
||||||
|
| Guild DM | ✅ |
|
||||||
|
| Text Messages | ✅ Send & Receive |
|
||||||
|
| Image Messages | ✅ Send & Receive (group & direct) |
|
||||||
|
| File Messages | ✅ Send (group & direct) |
|
||||||
|
| Scheduled Tasks | ✅ Active push (4 per user per month) |
|
||||||
|
|
||||||
|
|
||||||
|
## 5. Notes
|
||||||
|
|
||||||
|
- **Passive message limits**: QQ direct message replies are valid for 60 minutes (max 5 replies per message); group chat replies are valid for 5 minutes.
|
||||||
|
- **Active message limits**: Both direct and group chats have a monthly limit of 4 active messages. Keep this in mind when using the scheduled tasks feature.
|
||||||
|
- **Event permissions**: By default, `GROUP_AND_C2C_EVENT` (QQ group/direct) and `PUBLIC_GUILD_MESSAGES` (guild public messages) are subscribed. Apply for additional permissions on the open platform if needed.
|
||||||
106
run.sh
106
run.sh
@@ -409,19 +409,21 @@ select_channel() {
|
|||||||
echo -e "${CYAN}${BOLD}=========================================${NC}"
|
echo -e "${CYAN}${BOLD}=========================================${NC}"
|
||||||
echo -e "${YELLOW}1) Feishu (飞书)${NC}"
|
echo -e "${YELLOW}1) Feishu (飞书)${NC}"
|
||||||
echo -e "${YELLOW}2) DingTalk (钉钉)${NC}"
|
echo -e "${YELLOW}2) DingTalk (钉钉)${NC}"
|
||||||
echo -e "${YELLOW}3) WeCom (企微应用)${NC}"
|
echo -e "${YELLOW}3) WeCom Bot (企微智能机器人)${NC}"
|
||||||
echo -e "${YELLOW}4) Web (网页)${NC}"
|
echo -e "${YELLOW}4) QQ (QQ 机器人)${NC}"
|
||||||
|
echo -e "${YELLOW}5) WeCom App (企微自建应用)${NC}"
|
||||||
|
echo -e "${YELLOW}6) Web (网页)${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
read -p "Enter your choice [press Enter for default: 1 - Feishu]: " channel_choice
|
read -p "Enter your choice [press Enter for default: 1 - Feishu]: " channel_choice
|
||||||
channel_choice=${channel_choice:-1}
|
channel_choice=${channel_choice:-1}
|
||||||
case "$channel_choice" in
|
case "$channel_choice" in
|
||||||
1|2|3|4)
|
1|2|3|4|5|6)
|
||||||
break
|
break
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo -e "${RED}Invalid choice. Please enter 1-4.${NC}"
|
echo -e "${RED}Invalid choice. Please enter 1-6.${NC}"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
@@ -456,9 +458,31 @@ configure_channel() {
|
|||||||
ACCESS_INFO="DingTalk channel configured"
|
ACCESS_INFO="DingTalk channel configured"
|
||||||
;;
|
;;
|
||||||
3)
|
3)
|
||||||
# WeCom
|
# WeCom Bot
|
||||||
|
CHANNEL_TYPE="wecom_bot"
|
||||||
|
echo -e "${GREEN}Configure WeCom Bot...${NC}"
|
||||||
|
read -p "Enter WeCom Bot ID: " wecom_bot_id
|
||||||
|
read -p "Enter WeCom Bot Secret: " wecom_bot_secret
|
||||||
|
|
||||||
|
WECOM_BOT_ID="$wecom_bot_id"
|
||||||
|
WECOM_BOT_SECRET="$wecom_bot_secret"
|
||||||
|
ACCESS_INFO="WeCom Bot channel configured"
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
# QQ
|
||||||
|
CHANNEL_TYPE="qq"
|
||||||
|
echo -e "${GREEN}Configure QQ Bot...${NC}"
|
||||||
|
read -p "Enter QQ App ID: " qq_app_id
|
||||||
|
read -p "Enter QQ App Secret: " qq_app_secret
|
||||||
|
|
||||||
|
QQ_APP_ID="$qq_app_id"
|
||||||
|
QQ_APP_SECRET="$qq_app_secret"
|
||||||
|
ACCESS_INFO="QQ Bot channel configured"
|
||||||
|
;;
|
||||||
|
5)
|
||||||
|
# WeCom App
|
||||||
CHANNEL_TYPE="wechatcom_app"
|
CHANNEL_TYPE="wechatcom_app"
|
||||||
echo -e "${GREEN}Configure WeCom...${NC}"
|
echo -e "${GREEN}Configure WeCom App...${NC}"
|
||||||
read -p "Enter WeChat Corp ID: " corp_id
|
read -p "Enter WeChat Corp ID: " corp_id
|
||||||
read -p "Enter WeChat Com App Token: " com_token
|
read -p "Enter WeChat Com App Token: " com_token
|
||||||
read -p "Enter WeChat Com App Secret: " com_secret
|
read -p "Enter WeChat Com App Secret: " com_secret
|
||||||
@@ -473,9 +497,9 @@ configure_channel() {
|
|||||||
WECHATCOM_AGENT_ID="$com_agent_id"
|
WECHATCOM_AGENT_ID="$com_agent_id"
|
||||||
WECHATCOM_AES_KEY="$com_aes_key"
|
WECHATCOM_AES_KEY="$com_aes_key"
|
||||||
WECHATCOM_PORT="$com_port"
|
WECHATCOM_PORT="$com_port"
|
||||||
ACCESS_INFO="WeCom channel configured on port ${com_port}"
|
ACCESS_INFO="WeCom App channel configured on port ${com_port}"
|
||||||
;;
|
;;
|
||||||
4)
|
6)
|
||||||
# Web
|
# Web
|
||||||
CHANNEL_TYPE="web"
|
CHANNEL_TYPE="web"
|
||||||
read -p "Enter web port [press Enter for default: 9899]: " web_port
|
read -p "Enter web port [press Enter for default: 9899]: " web_port
|
||||||
@@ -600,6 +624,72 @@ EOF
|
|||||||
"agent_max_context_turns": 30,
|
"agent_max_context_turns": 30,
|
||||||
"agent_max_steps": 15
|
"agent_max_steps": 15
|
||||||
}
|
}
|
||||||
|
EOF
|
||||||
|
;;
|
||||||
|
wecom_bot)
|
||||||
|
cat > config.json <<EOF
|
||||||
|
{
|
||||||
|
"channel_type": "wecom_bot",
|
||||||
|
"wecom_bot_id": "${WECOM_BOT_ID}",
|
||||||
|
"wecom_bot_secret": "${WECOM_BOT_SECRET}",
|
||||||
|
"model": "${MODEL_NAME}",
|
||||||
|
"open_ai_api_key": "${OPENAI_KEY:-}",
|
||||||
|
"open_ai_api_base": "${OPENAI_BASE:-https://api.openai.com/v1}",
|
||||||
|
"claude_api_key": "${CLAUDE_KEY:-}",
|
||||||
|
"claude_api_base": "${CLAUDE_BASE:-https://api.anthropic.com/v1}",
|
||||||
|
"gemini_api_key": "${GEMINI_KEY:-}",
|
||||||
|
"gemini_api_base": "${GEMINI_BASE:-https://generativelanguage.googleapis.com}",
|
||||||
|
"zhipu_ai_api_key": "${ZHIPU_KEY:-}",
|
||||||
|
"moonshot_api_key": "${MOONSHOT_KEY:-}",
|
||||||
|
"ark_api_key": "${ARK_KEY:-}",
|
||||||
|
"dashscope_api_key": "${DASHSCOPE_KEY:-}",
|
||||||
|
"minimax_api_key": "${MINIMAX_KEY:-}",
|
||||||
|
"voice_to_text": "openai",
|
||||||
|
"text_to_voice": "openai",
|
||||||
|
"voice_reply_voice": false,
|
||||||
|
"speech_recognition": true,
|
||||||
|
"group_speech_recognition": false,
|
||||||
|
"use_linkai": ${USE_LINKAI:-false},
|
||||||
|
"linkai_api_key": "${LINKAI_KEY:-}",
|
||||||
|
"linkai_app_code": "",
|
||||||
|
"agent": true,
|
||||||
|
"agent_max_context_tokens": 40000,
|
||||||
|
"agent_max_context_turns": 30,
|
||||||
|
"agent_max_steps": 15
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
;;
|
||||||
|
qq)
|
||||||
|
cat > config.json <<EOF
|
||||||
|
{
|
||||||
|
"channel_type": "qq",
|
||||||
|
"qq_app_id": "${QQ_APP_ID}",
|
||||||
|
"qq_app_secret": "${QQ_APP_SECRET}",
|
||||||
|
"model": "${MODEL_NAME}",
|
||||||
|
"open_ai_api_key": "${OPENAI_KEY:-}",
|
||||||
|
"open_ai_api_base": "${OPENAI_BASE:-https://api.openai.com/v1}",
|
||||||
|
"claude_api_key": "${CLAUDE_KEY:-}",
|
||||||
|
"claude_api_base": "${CLAUDE_BASE:-https://api.anthropic.com/v1}",
|
||||||
|
"gemini_api_key": "${GEMINI_KEY:-}",
|
||||||
|
"gemini_api_base": "${GEMINI_BASE:-https://generativelanguage.googleapis.com}",
|
||||||
|
"zhipu_ai_api_key": "${ZHIPU_KEY:-}",
|
||||||
|
"moonshot_api_key": "${MOONSHOT_KEY:-}",
|
||||||
|
"ark_api_key": "${ARK_KEY:-}",
|
||||||
|
"dashscope_api_key": "${DASHSCOPE_KEY:-}",
|
||||||
|
"minimax_api_key": "${MINIMAX_KEY:-}",
|
||||||
|
"voice_to_text": "openai",
|
||||||
|
"text_to_voice": "openai",
|
||||||
|
"voice_reply_voice": false,
|
||||||
|
"speech_recognition": true,
|
||||||
|
"group_speech_recognition": false,
|
||||||
|
"use_linkai": ${USE_LINKAI:-false},
|
||||||
|
"linkai_api_key": "${LINKAI_KEY:-}",
|
||||||
|
"linkai_app_code": "",
|
||||||
|
"agent": true,
|
||||||
|
"agent_max_context_tokens": 40000,
|
||||||
|
"agent_max_context_turns": 30,
|
||||||
|
"agent_max_steps": 15
|
||||||
|
}
|
||||||
EOF
|
EOF
|
||||||
;;
|
;;
|
||||||
wechatcom_app)
|
wechatcom_app)
|
||||||
|
|||||||
Reference in New Issue
Block a user