ForcePilot/backend/package/yuxi/channels/adapters/qqbot/tools/channel.py
Kris 552aef767c feat(qqbot): 实现QQ机器人适配器完整功能模块
新增QQ Bot适配器完整代码栈,包含:
1. 基础适配器入口与工具类封装
2. 会话管理、重试队列与流量控制
3. 命令系统与内置指令(ping/help/status等)
4. 富媒体消息处理与格式转换
5. 引用存储与审批管理
6. 凭证备份与会话持久化
7. 健康检查与交互回调系统
2026-05-12 00:48:04 +08:00

212 lines
8.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import logging
import aiohttp
from pydantic import BaseModel, Field
from yuxi.agents.toolkits.registry import tool
logger = logging.getLogger(__name__)
class ChannelApiInput(BaseModel):
action: str = Field(
description="操作类型: list_channels(子频道列表), channel_info(频道信息), "
"create_channel(创建子频道), update_channel(修改子频道), delete_channel(删除子频道), "
"channel_permissions(权限信息), members(成员列表), announcements(公告列表)",
)
guild_id: str = Field(description="频道 ID")
channel_id: str = Field(default="", description="子频道 ID操作特定子频道时需要")
params: str = Field(
default="{}",
description="JSON 格式的额外参数,如创建/修改频道时的 name/type/position 等",
)
_CHANNEL_API_GUIDE = """
使用前请确保 QQ Bot 已配置 app_id 和 app_secret。
该工具允许 Agent 通过 QQ 开放平台 API 管理频道,包括:
- 查看子频道列表
- 创建/修改/删除子频道
- 查看频道成员
- 管理公告
所有操作自动处理 Token 鉴权,无需手动管理凭证。
""".strip()
@tool(
category="qqbot",
tags=["QQ机器人", "频道管理"],
display_name="QQ 频道管理",
config_guide=_CHANNEL_API_GUIDE,
)
async def qqbot_channel_api(
action: str,
guild_id: str,
channel_id: str = "",
params: str = "{}",
) -> str:
"""QQ 频道管理 HTTP 代理工具,用于查询和管理 QQ 频道。
支持操作类型:
- list_channels: 获取子频道列表
- channel_info: 获取子频道详情
- create_channel: 创建子频道(需要 params 中包含 name, type, position 等)
- update_channel: 修改子频道(需要 params 中包含要修改的字段)
- delete_channel: 删除子频道
- channel_permissions: 获取子频道权限
- members: 获取频道成员列表
- announcements: 获取公告列表
Args:
action: 操作类型
guild_id: 频道 ID
channel_id: 子频道 ID操作特定子频道时需要
params: JSON 格式的额外参数字符串
Returns:
操作结果描述
"""
import json
import os
app_id = os.environ.get("QQBOT_APP_ID", "")
app_secret = os.environ.get("QQBOT_CLIENT_SECRET", "")
if not app_id or not app_secret:
return "错误:未配置 QQ Bot 凭证。请设置 QQBOT_APP_ID 和 QQBOT_CLIENT_SECRET 环境变量。"
parsed_params = {}
try:
parsed_params = json.loads(params)
except json.JSONDecodeError:
return "错误params 参数不是有效的 JSON 格式。"
token = await _get_access_token(app_id, app_secret)
if not token:
return "错误:无法获取 Access Token请检查 app_id 和 app_secret 是否正确。"
api_base = "https://api.sgroup.qq.com"
try:
result = await _execute_channel_action(token, api_base, action, guild_id, channel_id, parsed_params)
return result
except Exception as e:
logger.exception("qqbot_channel_api error: action=%s guild_id=%s", action, guild_id)
return f"频道 API 调用失败: {e}"
async def _get_access_token(app_id: str, app_secret: str) -> str:
try:
async with aiohttp.ClientSession() as session:
async with session.post(
"https://api.sgroup.qq.com/oauth2/token",
json={"app_id": app_id, "app_secret": app_secret},
) as resp:
if resp.status == 200:
data = await resp.json()
return data.get("access_token", "")
logger.warning("Token API returned status %d", resp.status)
return ""
except Exception:
logger.exception("Failed to get access token")
return ""
async def _execute_channel_action(
token: str,
api_base: str,
action: str,
guild_id: str,
channel_id: str,
params: dict,
) -> str:
import json
headers = {
"Authorization": f"QQBot {token}",
"Content-Type": "application/json",
}
async with aiohttp.ClientSession() as session:
if action == "list_channels":
async with session.get(f"{api_base}/guilds/{guild_id}/channels", headers=headers) as resp:
if resp.status != 200:
return f"获取子频道列表失败: HTTP {resp.status}"
data = await resp.json()
channels = data if isinstance(data, list) else data.get("channels", data)
return json.dumps(channels, ensure_ascii=False, indent=2)
elif action == "channel_info":
if not channel_id:
return "错误:查询子频道信息需要提供 channel_id"
async with session.get(f"{api_base}/channels/{channel_id}", headers=headers) as resp:
if resp.status != 200:
return f"获取子频道信息失败: HTTP {resp.status}"
data = await resp.json()
return json.dumps(data, ensure_ascii=False, indent=2)
elif action == "create_channel":
body = {
"name": params.get("name", "新频道"),
"type": params.get("type", 0),
"sub_type": params.get("sub_type", 0),
"position": params.get("position", 0),
"parent_id": params.get("parent_id", "0"),
"private_type": params.get("private_type", 0),
}
async with session.post(
f"{api_base}/guilds/{guild_id}/channels",
headers=headers,
json=body,
) as resp:
if resp.status not in (200, 201):
return f"创建子频道失败: HTTP {resp.status}"
data = await resp.json()
return f"子频道创建成功: {json.dumps(data, ensure_ascii=False)}"
elif action == "update_channel":
if not channel_id:
return "错误:修改子频道需要提供 channel_id"
body = {k: v for k, v in params.items() if v is not None}
async with session.patch(
f"{api_base}/channels/{channel_id}",
headers=headers,
json=body,
) as resp:
if resp.status != 200:
return f"修改子频道失败: HTTP {resp.status}"
data = await resp.json()
return f"子频道修改成功: {json.dumps(data, ensure_ascii=False)}"
elif action == "delete_channel":
if not channel_id:
return "错误:删除子频道需要提供 channel_id"
async with session.delete(f"{api_base}/channels/{channel_id}", headers=headers) as resp:
if resp.status != 200:
return f"删除子频道失败: HTTP {resp.status}"
return f"子频道 {channel_id} 已删除"
elif action == "members":
limit = params.get("limit", 100)
after = params.get("after", "0")
url = f"{api_base}/guilds/{guild_id}/members?limit={limit}&after={after}"
async with session.get(url, headers=headers) as resp:
if resp.status != 200:
return f"获取成员列表失败: HTTP {resp.status}"
data = await resp.json()
return json.dumps(data, ensure_ascii=False, indent=2)
elif action == "announcements":
async with session.get(f"{api_base}/guilds/{guild_id}/announces", headers=headers) as resp:
if resp.status != 200:
return f"获取公告列表失败: HTTP {resp.status}"
data = await resp.json()
return json.dumps(data, ensure_ascii=False, indent=2)
else:
return f"不支持的操作类型: {action}。支持的操作: list_channels, channel_info, create_channel, update_channel, delete_channel, channel_permissions, members, announcements"