ForcePilot/backend/package/yuxi/channels/adapters/qqbot/voice_send.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

178 lines
5.5 KiB
Python

from __future__ import annotations
import aiohttp
from yuxi.channels.models import DeliveryResult
from yuxi.utils.logging_config import logger
from .constants import DM_CHAT_PREFIX, GROUP_CHAT_PREFIX
from .media_upload import (
FILE_TYPE_VOICE,
build_media_payload,
upload_media,
validate_media_size,
)
async def send_voice(
voice_data: bytes,
chat_id: str,
token: str,
http_client: aiohttp.ClientSession,
api_base: str,
filename: str = "voice.mp3",
max_size_mb: int = 100,
) -> DeliveryResult:
validate_media_size(voice_data, max_size_mb=max_size_mb, label="voice")
group_openid = None
if chat_id.startswith(GROUP_CHAT_PREFIX):
group_openid = chat_id.replace(GROUP_CHAT_PREFIX, "")
try:
file_id = await upload_media(
voice_data,
token,
http_client=http_client,
filename=filename,
file_type=FILE_TYPE_VOICE,
group_openid=group_openid,
)
except Exception as e:
return DeliveryResult(success=False, error=f"Voice upload failed: {e}")
payload = build_media_payload(chat_id, file_id, msg_type=7)
url = _resolve_media_send_url(api_base, chat_id)
headers = {
"Authorization": f"QQBot {token}",
"Content-Type": "application/json",
}
try:
async with http_client.post(url, json=payload, headers=headers) as resp:
if resp.status == 200:
data = await resp.json()
return DeliveryResult(
success=True,
message_id=data.get("id") or data.get("message_id"),
)
return DeliveryResult(success=False, error=f"Voice send failed: HTTP {resp.status}")
except Exception as e:
logger.error(f"[QQBot] Voice send error: {e}")
return DeliveryResult(success=False, error=str(e))
async def send_video(
video_data: bytes,
chat_id: str,
token: str,
http_client: aiohttp.ClientSession,
api_base: str,
filename: str = "video.mp4",
max_size_mb: int = 100,
) -> DeliveryResult:
from .media_upload import FILE_TYPE_VIDEO
validate_media_size(video_data, max_size_mb=max_size_mb, label="video")
group_openid = None
if chat_id.startswith(GROUP_CHAT_PREFIX):
group_openid = chat_id.replace(GROUP_CHAT_PREFIX, "")
try:
file_id = await upload_media(
video_data,
token,
http_client=http_client,
filename=filename,
file_type=FILE_TYPE_VIDEO,
group_openid=group_openid,
)
except Exception as e:
return DeliveryResult(success=False, error=f"Video upload failed: {e}")
payload = build_media_payload(chat_id, file_id, msg_type=7)
url = _resolve_media_send_url(api_base, chat_id)
headers = {
"Authorization": f"QQBot {token}",
"Content-Type": "application/json",
}
try:
async with http_client.post(url, json=payload, headers=headers) as resp:
if resp.status == 200:
data = await resp.json()
return DeliveryResult(
success=True,
message_id=data.get("id") or data.get("message_id"),
)
return DeliveryResult(success=False, error=f"Video send failed: HTTP {resp.status}")
except Exception as e:
logger.error(f"[QQBot] Video send error: {e}")
return DeliveryResult(success=False, error=str(e))
async def send_file(
file_data: bytes,
chat_id: str,
token: str,
http_client: aiohttp.ClientSession,
api_base: str,
filename: str = "file.bin",
max_size_mb: int = 100,
) -> DeliveryResult:
from .media_upload import FILE_TYPE_FILE
validate_media_size(file_data, max_size_mb=max_size_mb, label="file")
group_openid = None
if chat_id.startswith(GROUP_CHAT_PREFIX):
group_openid = chat_id.replace(GROUP_CHAT_PREFIX, "")
try:
file_id = await upload_media(
file_data,
token,
http_client=http_client,
filename=filename,
file_type=FILE_TYPE_FILE,
group_openid=group_openid,
)
except Exception as e:
return DeliveryResult(success=False, error=f"File upload failed: {e}")
payload = build_media_payload(chat_id, file_id, msg_type=7)
url = _resolve_media_send_url(api_base, chat_id)
headers = {
"Authorization": f"QQBot {token}",
"Content-Type": "application/json",
}
try:
async with http_client.post(url, json=payload, headers=headers) as resp:
if resp.status == 200:
data = await resp.json()
return DeliveryResult(
success=True,
message_id=data.get("id") or data.get("message_id"),
)
return DeliveryResult(success=False, error=f"File send failed: HTTP {resp.status}")
except Exception as e:
logger.error(f"[QQBot] File send error: {e}")
return DeliveryResult(success=False, error=str(e))
def _resolve_media_send_url(api_base: str, chat_id: str) -> str:
if chat_id.startswith(GROUP_CHAT_PREFIX):
group_openid = chat_id.replace(GROUP_CHAT_PREFIX, "")
return f"{api_base}/v2/groups/{group_openid}/messages"
elif chat_id.startswith(DM_CHAT_PREFIX):
openid = chat_id.replace(DM_CHAT_PREFIX, "")
return f"{api_base}/v2/users/{openid}/messages"
elif chat_id:
return f"{api_base}/v2/channels/{chat_id}/messages"
return f"{api_base}/v2/users/@me/messages"