feat(qqpush): 私聊推送接口与抽卡API token读取兼容

This commit is contained in:
2026-08-15 20:50:12 +08:00
parent d2187e5545
commit cb94794e38
7 changed files with 292 additions and 33 deletions

View File

@@ -53,6 +53,28 @@ class PushRequest(BaseModel):
"""公告发布时间"""
class PrivatePushRequest(BaseModel):
"""私聊推送请求模型"""
user_id: int
"""接收私聊消息的好友 QQ 号"""
text: Optional[str] = None
"""通知文本(# 表示换行)"""
text_only: bool = False
"""True=发送纯文本False默认=渲染为图片发送"""
html: Optional[str] = None
"""可选的完整公告 HTML传入时使用虚拟浏览器截图"""
title: Optional[str] = None
"""公告标题"""
published_at: Optional[str] = None
"""公告发布时间"""
# 响应模型
class PushResponse(BaseModel):
"""推送响应模型"""
@@ -169,4 +191,91 @@ def create_routes(token: str, config: Config):
detail="服务器内部错误"
)
@router.post("/danding/private/{request_token}", response_model=PushResponse)
async def private_push(request_token: str, request: Request, data: PrivatePushRequest):
"""
好友私聊推送接口
仅可向好友发送私聊消息;目标不在好友列表或发送失败时返回错误。
"""
try:
if not validate_token(request_token, token):
raise HTTPException(status_code=403, detail="Token 验证失败")
# 1. 验证参数
if not data.user_id:
raise HTTPException(status_code=400, detail="user_id 不能为空")
if not (data.text or data.html):
raise HTTPException(status_code=400, detail="text 和 html 不能同时为空")
# 2. 检查 Bot 是否在线
bot = sender.get_bot()
if not bot:
logger.error("Bot 实例未设置,无法发送消息")
raise HTTPException(status_code=500, detail="Bot 未连接,请检查机器人状态")
# 3. 检查目标是否为好友
try:
is_friend = await sender.is_friend(data.user_id)
except Exception as e:
logger.error(f"查询好友列表失败: {e}")
raise HTTPException(status_code=500, detail=f"查询好友列表失败: {e}")
if not is_friend:
logger.warning(f"目标用户不是好友: {data.user_id}")
raise HTTPException(
status_code=400,
detail=f"用户 {data.user_id} 不在好友列表中,无法发送私聊消息"
)
# 4. 发送消息
if data.text_only and data.text:
text_parser = TextParser(max_length=config.MaxTextLength)
if not text_parser.validate_text(data.text):
raise HTTPException(status_code=400, detail="文本内容无效")
parsed_text = text_parser.parse(data.text)
send_result = await sender.send_text_to_private(data.user_id, parsed_text)
elif data.html:
image_base64 = await render_article_to_base64(
data.html,
data.title or "蛋定助手通知",
data.published_at,
)
send_result = await sender.send_to_private(data.user_id, image_base64)
else:
text_parser = TextParser(max_length=config.MaxTextLength)
if not text_parser.validate_text(data.text):
raise HTTPException(status_code=400, detail="文本内容无效")
parsed_text = text_parser.parse(data.text)
image_base64 = await asyncio.to_thread(_get_renderer(config).render_to_base64, parsed_text)
send_result = await sender.send_to_private(data.user_id, image_base64)
logger.info("私聊文本已准备发送" if data.text_only else "私聊图片生成成功")
if not send_result["success"]:
logger.error(f"私聊消息发送失败: {send_result.get('error')}")
raise HTTPException(status_code=500, detail=send_result["message"])
logger.info(f"私聊消息发送成功 - 好友: {data.user_id}")
return PushResponse(
success=True,
message="私聊推送成功",
data={
"user_id": data.user_id,
"message_id": send_result["data"].get("message_id")
}
)
except HTTPException:
raise
except Exception as e:
logger.exception(f"私聊推送接口异常: {e}")
raise HTTPException(
status_code=500,
detail="服务器内部错误"
)
return router