新增QQ Bot适配器完整代码栈,包含: 1. 基础适配器入口与工具类封装 2. 会话管理、重试队列与流量控制 3. 命令系统与内置指令(ping/help/status等) 4. 富媒体消息处理与格式转换 5. 引用存储与审批管理 6. 凭证备份与会话持久化 7. 健康检查与交互回调系统
178 lines
5.5 KiB
Python
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"
|