From a0b0c3e0d1ea7864c0fba95d16398a2bf139cf6a Mon Sep 17 00:00:00 2001 From: SXP-Simon Date: Sun, 18 Jan 2026 00:52:08 +0800 Subject: [PATCH] =?UTF-8?q?fix(RetryManager):=20=E9=87=8D=E8=AF=95?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=99=A8=E7=9B=B4=E6=8E=A5=E4=BB=8E=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E5=99=A8=E8=AF=B7=E6=B1=82=E5=8E=9F=E5=A7=8B=E5=9B=BE?= =?UTF-8?q?=E5=83=8F=E5=AD=97=E8=8A=82=EF=BC=8CQQ=20=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=E4=B8=8D=E5=86=8D=E9=9C=80=E8=A6=81=E4=BB=8E=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E6=9C=8D=E5=8A=A1=E5=99=A8=E4=B8=8B=E8=BD=BD=E5=9B=BE?= =?UTF-8?q?=E7=89=87=EF=BC=8C=E4=BB=8E=E8=80=8C=E7=BB=95=E8=BF=87=E4=BA=86?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E8=B6=85=E6=97=B6=E7=9A=84=E7=BD=91=E7=BB=9C?= =?UTF-8?q?=E4=BC=A0=E8=BE=93=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 直接字节获取(Direct Byte Retrieval): 重试管理器现在直接从渲染器请求原始图像字节(Bytes),而不是通过 URL。这使得 QQ 客户端不再需要从本地服务器下载图片,从而绕过了导致超时的网络传输层。Base64 编码: 图像字节现在被编码为 Base64 字符串。协议兼容性: 消息负载(Payload)现在使用 base64:// 前缀构建,这是 OneBot/AstrBot 协议中发送 Base64 图片的标准方式。 --- src/scheduler/retry.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/scheduler/retry.py b/src/scheduler/retry.py index 93bac06..1204d47 100644 --- a/src/scheduler/retry.py +++ b/src/scheduler/retry.py @@ -1,6 +1,7 @@ import asyncio import random import time +import base64 from collections.abc import Callable from dataclasses import dataclass @@ -148,19 +149,33 @@ class RetryManager: "quality": 85, } logger.debug(f"[RetryManager] 正在重新渲染群 {task.group_id} 的图片...") - image_url = await self.html_render_func( + + # 修改:return_url=False 获取二进制数据而不是URL + # 这对于解决 NTQQ "Timeout" 错误至关重要,因为它避免了 QQ 客户端下载本地/内网 URL 的网络问题 + image_data = await self.html_render_func( task.html_content, {}, - True, # 返回 URL + False, # return_url=False, 获取 bytes image_options, ) - if not image_url: + if not image_data: logger.warning( - f"[RetryManager] 重新渲染失败(返回空 URL){task.group_id}" + f"[RetryManager] 重新渲染失败(返回空数据){task.group_id}" ) return False + # 将 bytes 转换为 base64 字符串 + try: + base64_str = base64.b64encode(image_data).decode("utf-8") + image_file_str = f"base64://{base64_str}" + logger.debug( + f"[RetryManager] 图片转Base64成功,长度: {len(base64_str)}" + ) + except Exception as e: + logger.error(f"[RetryManager] Base64编码失败: {e}") + return False + # 2. 获取 Bot 实例 bot = self.bot_manager.get_bot_instance(task.platform_id) if not bot: @@ -170,7 +185,9 @@ class RetryManager: return False # 无法重试,因为 Bot 已离线 # 3. 发送图片 - logger.info(f"[RetryManager] 正在向群 {task.group_id} 发送重试图片...") + logger.info( + f"[RetryManager] 正在向群 {task.group_id} 发送重试图片 (Base64模式)..." + ) # 使用 OneBot v11 标准 API if hasattr(bot, "api") and hasattr(bot.api, "call_action"): @@ -182,7 +199,7 @@ class RetryManager: "type": "text", "data": {"text": "📊 每日群聊分析报告(重试发送):\n"}, }, - {"type": "image", "data": {"file": image_url}}, + {"type": "image", "data": {"file": image_file_str}}, ] result = await bot.api.call_action( @@ -195,8 +212,9 @@ class RetryManager: if retcode == 0: return True elif retcode == 1200: + # 即使是 Base64 也可能超时,但概率小很多 logger.warning( - "[RetryManager] 发送失败 (retcode=1200): 可能是Bot被禁言或不在群内,稍后重试" + "[RetryManager] 发送失败 (retcode=1200): 消息可能过大或Bot连接不稳定" ) return False else: @@ -215,7 +233,7 @@ class RetryManager: elif hasattr(bot, "send_msg"): # 尝试 AstrBot 抽象接口 try: # 尝试直接发送 - await bot.send_msg(image_url, group_id=task.group_id) + await bot.send_msg(image_file_str, group_id=task.group_id) return True except Exception as e: logger.error(f"[RetryManager] 抽象接口发送失败: {e}")