From 7e70a6532c37dca22ff43daefb42d597425f0abc Mon Sep 17 00:00:00 2001 From: SXP-Simon Date: Sat, 17 Jan 2026 23:54:39 +0800 Subject: [PATCH] =?UTF-8?q?[v4.6.2]=20=E6=96=B0=E5=A2=9E=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E6=8A=A5=E5=91=8A=E9=87=8D=E8=AF=95=E6=9C=BA=E5=88=B6=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=B0=83=E5=BA=A6=E7=A8=B3=E5=AE=9A=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 11 +++- README.md | 2 +- main.py | 6 +- metadata.yaml | 2 +- src/scheduler/auto_scheduler.py | 2 +- src/scheduler/retry.py | 97 ++++++++++++++++++++++++++++----- 6 files changed, 97 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b55794..2ed6ef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,20 @@ # 更新日志 (CHANGELOG) -## [v4.6.1] - Linux 下的 `/安装PDF` 指令简易修复,纠正头像获取的处理;PDF 格式在 Linux 环境下占用过大,原因还在排查中,暂时不推荐使用。 -* **🐛 Bug 修复**: 修复 generators.py 中 _get_user_avatar 的 TypeError。将 aiohttp 请求重构为异步上下文模式,确保 response.read() 被正确等待,并增加了网络连接失败处理。 -* **🐛 Bug 修复**: Linux 环境下的安装 PDF 命令修复,调整 PDF 格式排版情况。PDF 格式在 Linux 环境下占用过大,原因还在排查中,暂时不推荐使用。 +## [v4.6.2] - 新增图片报告重试机制并优化调度稳定性 + +* **✨ 新功能**: 新增图片报告重试机制并优化调度稳定性,实现延迟队列与死信队列,支持指数退避 + 随机抖动 (Jitter) 重试策略。 ---
📋 点击查看历史更新日志 +## [v4.6.1] - Linux 下的 `/安装PDF` 指令简易修复,纠正头像获取的处理;PDF 格式在 Linux 环境下占用过大,原因还在排查中,暂时不推荐使用。 + +* **🐛 Bug 修复**: 修复 generators.py 中 _get_user_avatar 的 TypeError。将 aiohttp 请求重构为异步上下文模式,确保 response.read() 被正确等待,并增加了网络连接失败处理。 +* **🐛 Bug 修复**: Linux 环境下的安装 PDF 命令修复,调整 PDF 格式排版情况。PDF 格式在 Linux 环境下占用过大,原因还在排查中,暂时不推荐使用。 + ## [v4.6.0] - 通过 AstrBot 配置文件来判断账号启用插件 * **🐛 Bug 修复**: 检查该平台是否包含该插件的配置文件 plugin_set,通过 AstrBot 配置文件来判断账号启用插件。 diff --git a/README.md b/README.md index 770cd93..07a7fcf 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # QQ群日常分析插件 -[![Plugin Version](https://img.shields.io/badge/Latest_Version-v4.6.1-blue.svg?style=for-the-badge&color=76bad9)](https://github.com/SXP-Simon/astrbot-qq-group-daily-analysis) +[![Plugin Version](https://img.shields.io/badge/Latest_Version-v4.6.2-blue.svg?style=for-the-badge&color=76bad9)](https://github.com/SXP-Simon/astrbot-qq-group-daily-analysis) [![AstrBot](https://img.shields.io/badge/AstrBot-Plugin-ff69b4?style=for-the-badge)](https://github.com/AstrBotDevs/AstrBot) [![License](https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge)](LICENSE) [![](https://img.shields.io/badge/五彩斑斓的Bug群-Bug反馈群&水群-white?style=for-the-badge&color=76bad9&logo=qq&logoColor=76bad9)](https://qm.qq.com/q/oTzIrdDBIc) diff --git a/main.py b/main.py index 8958cf8..135d9a9 100644 --- a/main.py +++ b/main.py @@ -40,7 +40,9 @@ class QQGroupDailyAnalysis(Star): context, self.config_manager, self.bot_manager ) self.report_generator = ReportGenerator(self.config_manager) - self.retry_manager = RetryManager(self.bot_manager, self.html_render) + self.retry_manager = RetryManager( + self.bot_manager, self.html_render, self.report_generator + ) self.auto_scheduler = AutoScheduler( self.config_manager, self.message_analyzer.message_handler, @@ -213,7 +215,7 @@ class QQGroupDailyAnalysis(Star): group_id ) await self.retry_manager.add_task( - html_content, group_id, platform_id + html_content, analysis_result, group_id, platform_id ) else: # 如果图片生成失败且无HTML,回退到文本报告 diff --git a/metadata.yaml b/metadata.yaml index bf89c5d..ad5de26 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -14,6 +14,6 @@ help: | # 插件的帮助信息 命令: /群分析 [天数] - 分析群聊活动 /分析设置 [操作] - 管理设置(enable/disable/status/test) -version: v4.6.1 # 插件版本号。格式:v1.1.1 或者 v1.1 +version: v4.6.2 # 插件版本号。格式:v1.1.1 或者 v1.1 author: SXP-Simon # 作者 repo: https://github.com/SXP-Simon/astrbot_plugin_qq_group_daily_analysis # 插件的仓库地址 diff --git a/src/scheduler/auto_scheduler.py b/src/scheduler/auto_scheduler.py index a305da0..0db7d8b 100644 --- a/src/scheduler/auto_scheduler.py +++ b/src/scheduler/auto_scheduler.py @@ -565,7 +565,7 @@ class AutoScheduler: f"群 {group_id} 图片生成失败,已静默加入重试队列" ) await self.retry_manager.add_task( - html_content, group_id, platform_id + html_content, analysis_result, group_id, platform_id ) else: logger.error( diff --git a/src/scheduler/retry.py b/src/scheduler/retry.py index f09305b..93bac06 100644 --- a/src/scheduler/retry.py +++ b/src/scheduler/retry.py @@ -1,8 +1,8 @@ import asyncio import random import time -from dataclasses import dataclass from collections.abc import Callable +from dataclasses import dataclass from astrbot.api import logger @@ -12,6 +12,7 @@ class RetryTask: """重试任务数据类""" html_content: str + analysis_result: dict # 保存原始分析结果,用于文本回退 group_id: str platform_id: str # 需要保存 platform_id 以便找回 Bot retry_count: int = 0 @@ -34,9 +35,10 @@ class RetryManager: 4. 超过最大重试次数放入死信队列 """ - def __init__(self, bot_manager, html_render_func: Callable): + def __init__(self, bot_manager, html_render_func: Callable, report_generator=None): self.bot_manager = bot_manager self.html_render_func = html_render_func + self.report_generator = report_generator # 用于生成文本报告 self.queue = asyncio.Queue() self.running = False self.worker_task = None @@ -69,7 +71,9 @@ class RetryManager: logger.info("[RetryManager] 图片重试管理器已停止") - async def add_task(self, html_content: str, group_id: str, platform_id: str): + async def add_task( + self, html_content: str, analysis_result: dict, group_id: str, platform_id: str + ): """添加重试任务""" if not self.running: logger.warning( @@ -79,6 +83,7 @@ class RetryManager: task = RetryTask( html_content=html_content, + analysis_result=analysis_result, group_id=group_id, platform_id=platform_id, created_at=time.time(), @@ -115,10 +120,12 @@ class RetryManager: self.queue.task_done() else: logger.error( - f"[RetryManager] 群 {task.group_id} 超过最大重试次数,移入死信队列" + f"[RetryManager] 群 {task.group_id} 超过最大重试次数,移入死信队列并尝试文本回退" ) self._dlq.append(task) self.queue.task_done() + # 尝试发送文本回退 + await self._send_fallback_text(task) await self._notify_failure(task) except asyncio.CancelledError: @@ -189,7 +196,7 @@ class RetryManager: return True elif retcode == 1200: logger.warning( - f"[RetryManager] 发送失败 (retcode=1200): 可能是Bot被禁言或不在群内,稍后重试" + "[RetryManager] 发送失败 (retcode=1200): 可能是Bot被禁言或不在群内,稍后重试" ) return False else: @@ -224,15 +231,75 @@ class RetryManager: logger.error(f"[RetryManager] 处理任务时发生意外错误: {e}", exc_info=True) return False - async def _notify_failure(self, task: RetryTask): - """通知最终失败""" - try: - bot = self.bot_manager.get_bot_instance(task.platform_id) - if bot and hasattr(bot, "api") and hasattr(bot.api, "call_action"): - await bot.api.call_action( - "send_group_msg", - group_id=int(task.group_id), - message=f"[AstrBot QQ群日常分析总结插件] 报告生成/发送多次失败 (Group: {task.group_id}),请检查服务器日志。", - ) except Exception: pass + + async def _send_fallback_text(self, task: RetryTask): + """发送文本回退报告(使用合并转发)""" + if not self.report_generator: + logger.warning("[RetryManager] 未配置 ReportGenerator,无法发送文本回退") + return + + try: + logger.info(f"[RetryManager] 正在为群 {task.group_id} 生成文本回退报告...") + text_report = self.report_generator.generate_text_report( + task.analysis_result + ) + + bot = self.bot_manager.get_bot_instance(task.platform_id) + if not bot: + return + + # 构造合并转发节点 + # 注意:这里需要构造符合 OneBot v11 标准的节点列表 + # 即使没有 self_id,我们也可以尝试发送 + + # 获取 bot self_id (如果能获取到) + bot_id = "10000" # fallback id + if hasattr(bot, "self_id"): + bot_id = str(bot.self_id) + + nickname = "AstrBot日常分析" + + nodes = [ + { + "type": "node", + "data": { + "name": nickname, + "uin": bot_id, + "content": "⚠️ 图片报告多次生成失败,为您呈现文本版报告:", + }, + }, + { + "type": "node", + "data": {"name": nickname, "uin": bot_id, "content": text_report}, + }, + ] + + if hasattr(bot, "api") and hasattr(bot.api, "call_action"): + # 尝试发送群合并转发消息 + # 一般使用 send_group_forward_msg 或 send_group_msg (带 nodes) + try: + await bot.api.call_action( + "send_group_forward_msg", + group_id=int(task.group_id), + messages=nodes, + ) + logger.info( + f"[RetryManager] 群 {task.group_id} 文本回退报告发送成功 (合并转发)" + ) + except Exception as e: + logger.warning( + f"[RetryManager] 合并转发失败,尝试直接发送文本: {e}" + ) + # 回退到直接发送宽文本 + await bot.api.call_action( + "send_group_msg", + group_id=int(task.group_id), + message=f"⚠️ 图片报告生成失败,文本报告:\n{text_report}"[ + :4500 + ], # 截断防止过长 + ) + + except Exception as e: + logger.error(f"[RetryManager] 文本回退发送失败: {e}", exc_info=True)