mirror of
https://github.com/Nezumi-2711/astrbot_plugin_qq_group_daily_analysis.git
synced 2026-09-22 05:31:52 +00:00
fix(retry): 尝试增强重试管理器,采用 get_login_info 兜底增强获取 Bot user_id ,避免重试地狱和重复发送
This commit is contained in:
+6
-2
@@ -1,14 +1,18 @@
|
||||
# 更新日志 (CHANGELOG)
|
||||
|
||||
## [v4.7.13] - fix(增量分析)解决增量分析传参错误导致用户称号失效的问题
|
||||
## [v4.7.14] - fix(retry): 尝试增强重试管理器,正确获取 Bot user_id ,避免重试地狱和重复发送
|
||||
* **🛠️ 健壮性增强**: 尝试增强重试管理器,避免重试地狱和重复发送(有待进一步完善)
|
||||
* **🐛 Bug 修复**: 解决增量分析传参错误导致用户称号失效的问题
|
||||
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary>📋 点击查看历史更新日志</summary>
|
||||
|
||||
## [v4.7.13] - fix(增量分析)解决增量分析传参错误导致用户称号失效的问题
|
||||
* **🛠️ 健壮性增强**: 尝试增强重试管理器,避免重试地狱和重复发送(有待进一步完善)
|
||||
* **🐛 Bug 修复**: 解决增量分析传参错误导致用户称号失效的问题
|
||||
|
||||
## [v4.7.11] - feat(base64): 新增可选 Base64 图片传输开关,默认启用
|
||||
* **✨ 新功能**: 新增可选 Base64 图片传输开关,默认启用,兼容更多环境。
|
||||
* **🐛 Bug 修复**: 修复图片路径处理逻辑,处理绝对路径和相对路径的文件路径转换
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
name: astrbot_plugin_qq_group_daily_analysis # 这是你的插件的唯一识别名。
|
||||
display_name: 群分析总结插件 # 插件的显示名称
|
||||
desc: "[多平台接入开发中] 群日常分析总结插件 - 支持 QQ (aiocqhttp)、Telegram、Discord;生成精美的群聊分析报告,支持话题分析、用户形象、群聊圣经等功能" # 插件简短描述
|
||||
version: v4.7.13 # 插件版本号。格式:v1.1.1 或者 v1.1
|
||||
version: v4.7.14 # 插件版本号。格式:v1.1.1 或者 v1.1
|
||||
author: SXP-Simon # 作者
|
||||
astrbot_version: ">=4.16.0"
|
||||
support_platforms:
|
||||
|
||||
@@ -53,15 +53,14 @@ class OneBotAdapter(PlatformAdapter):
|
||||
def __init__(self, bot_instance: Any, config: dict | None = None):
|
||||
"""
|
||||
初始化 OneBot 适配器。
|
||||
|
||||
Args:
|
||||
bot_instance (Any): 外部传入的机器人对象
|
||||
config (dict, optional): 插件配置,用于提取机器人自身的 QQ 号供过滤用
|
||||
"""
|
||||
super().__init__(bot_instance, config)
|
||||
# 支持从多个潜在的配置键中提取机器人 ID
|
||||
self.bot_self_ids = (
|
||||
[str(id) for id in config.get("bot_qq_ids", [])] if config else []
|
||||
[str(id) for id in config.get("bot_self_ids", [])] if config else []
|
||||
)
|
||||
if not self.bot_self_ids and config:
|
||||
self.bot_self_ids = [str(id) for id in config.get("bot_qq_ids", [])]
|
||||
|
||||
def _init_capabilities(self) -> PlatformCapabilities:
|
||||
"""返回预定义的 OneBot v11 能力集。"""
|
||||
@@ -544,7 +543,7 @@ class OneBotAdapter(PlatformAdapter):
|
||||
history = await self.bot.call_action(
|
||||
"get_group_msg_history",
|
||||
group_id=int(group_id),
|
||||
count=50, # 只检查最近 50 条消息,足够覆盖大多数情况
|
||||
count=100, # [针对重复检查优化] 提高扫描深度,覆盖大群高频刷屏的情况
|
||||
)
|
||||
|
||||
if not history or "messages" not in history:
|
||||
@@ -557,7 +556,36 @@ class OneBotAdapter(PlatformAdapter):
|
||||
import time
|
||||
|
||||
now = time.time()
|
||||
self_id = str(getattr(self.bot, "self_id", ""))
|
||||
# 1. 优先从内存缓存中获取机器人 ID
|
||||
self_id = self.bot_self_ids[0] if self.bot_self_ids else ""
|
||||
|
||||
# 2. 如果列表为空,尝试反射实例属性
|
||||
if not self_id:
|
||||
self_id = (
|
||||
str(getattr(self.bot, "self_id", ""))
|
||||
or str(getattr(self.bot, "uin", ""))
|
||||
or str(getattr(self.bot, "user_id", ""))
|
||||
)
|
||||
|
||||
# 3. [兜底方案] 仍未获取到,通过 API 向 OneBot 服务端请求
|
||||
if not self_id:
|
||||
try:
|
||||
login_info = await self.bot.call_action("get_login_info")
|
||||
if login_info and "user_id" in login_info:
|
||||
self_id = str(login_info["user_id"])
|
||||
# 更新缓存,下次无需重复请求
|
||||
if self_id not in self.bot_self_ids:
|
||||
self.bot_self_ids.append(self_id)
|
||||
logger.info(f"[OneBot] 成功通过 API 获取到机器人 ID: {self_id}")
|
||||
except Exception as e:
|
||||
logger.debug(
|
||||
f"[OneBot] was_image_sent_recently: get_login_info API 调用失败: {e}"
|
||||
)
|
||||
|
||||
if not self_id:
|
||||
logger.warning(
|
||||
"[OneBot] was_image_sent_recently: 无法确定机器人 ID,历史回显校验可能不准确"
|
||||
)
|
||||
|
||||
for msg in reversed(messages):
|
||||
msg_time = msg.get("time", 0)
|
||||
|
||||
@@ -113,10 +113,8 @@ class RetryManager:
|
||||
try:
|
||||
task: RetryTask = await self.queue.get()
|
||||
|
||||
# 如果该群已经在处理中,不再重复处理(双重保险)
|
||||
if task.group_id in self._active_groups:
|
||||
self.queue.task_done()
|
||||
continue
|
||||
# 【修复】去掉原本在这里的 group_id in self._active_groups 判断
|
||||
# 因为重新排队的任务本身就在 active_groups 中,会导致任务死在队列里被彻底丢弃
|
||||
|
||||
# 启动非阻塞的延迟执行协程
|
||||
asyncio.create_task(self._run_task_with_delay(task))
|
||||
@@ -130,13 +128,14 @@ class RetryManager:
|
||||
|
||||
async def _run_task_with_delay(self, task: RetryTask):
|
||||
"""异步执行带延迟的单体重试任务"""
|
||||
# 锁定该群,防止其他重试任务进入
|
||||
if task.group_id in self._active_groups:
|
||||
# 锁定该群,防止其他“新”重试任务进入。
|
||||
# 如果是重试任务(retry_count > 0),它已经在队列循环中,之前已经释放过锁。
|
||||
if task.group_id in self._active_groups and task.retry_count == 0:
|
||||
return
|
||||
self._active_groups.add(task.group_id)
|
||||
|
||||
try:
|
||||
# 1. 延迟策略:指数回落 + 观察期 (应对 OneBot 假超时)
|
||||
# 1. 策略计算:指数回落 + 抖动
|
||||
jitter = random.uniform(2, 8)
|
||||
delay = 20 * (2**task.retry_count) + jitter
|
||||
|
||||
@@ -144,19 +143,23 @@ class RetryManager:
|
||||
logger.info(
|
||||
f"[RetryManager] 群 {task.group_id} 启动 {delay:.1f}s 重试观察期..."
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
f"[RetryManager] 群 {task.group_id} 准备第 {task.retry_count + 1} 轮重试,退避 {delay:.1f}s..."
|
||||
)
|
||||
|
||||
await asyncio.sleep(delay)
|
||||
|
||||
if not self.running:
|
||||
return
|
||||
|
||||
# 【核心判定逻辑】:睡醒后先别急着发,去群里看看那张“疑似失败”的图是不是其实已经出来了
|
||||
# 【真相检查 1】:睡醒后先核实群里图片是不是其实已经出来了
|
||||
adapter = self.bot_manager.get_adapter(task.platform_id)
|
||||
if adapter and hasattr(adapter, "was_image_sent_recently"):
|
||||
# 检查过去 3 分钟内的消息回显 (覆盖初发和之前的重试)
|
||||
if await adapter.was_image_sent_recently(task.group_id, seconds=180):
|
||||
# 检查过去 5 分钟内的消息回显 (覆盖初发和之前的重试)
|
||||
if await adapter.was_image_sent_recently(task.group_id, seconds=300):
|
||||
logger.info(
|
||||
f"[RetryManager] 根据消息回显判断,群 {task.group_id} 的图片报告已成功送达。取消后续重试。"
|
||||
f"[RetryManager] [拦截] 根据历史回显,群 {task.group_id} 的图片已成功送达。取消本次重试。"
|
||||
)
|
||||
return
|
||||
|
||||
@@ -164,16 +167,16 @@ class RetryManager:
|
||||
success = await self._process_task(task)
|
||||
|
||||
if success:
|
||||
logger.info(f"[RetryManager] 群 {task.group_id} 重试发送成功")
|
||||
logger.info(f"[RetryManager] 群 {task.group_id} 重试流程圆满完成")
|
||||
else:
|
||||
# 3. 失败后续处理
|
||||
task.retry_count += 1
|
||||
if task.retry_count < task.max_retries:
|
||||
# 释放锁,以便下次取到时能重新进入观察期
|
||||
self._active_groups.discard(task.group_id)
|
||||
# 将任务重新放回队列。
|
||||
# 注意:锁会在 finally 释放,这样下一个 worker 就能拉取到它并进入睡眠。
|
||||
await self.queue.put(task)
|
||||
logger.warning(
|
||||
f"[RetryManager] 群 {task.group_id} 本轮重试失败,准备进入第 {task.retry_count + 1} 轮..."
|
||||
f"[RetryManager] 群 {task.group_id} 本轮调用返回失败,已排期下一轮..."
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
@@ -181,9 +184,9 @@ class RetryManager:
|
||||
)
|
||||
await self._send_fallback_text(task)
|
||||
except Exception as e:
|
||||
logger.error(f"[RetryManager] 重试协程执行异常 (群 {task.group_id}): {e}")
|
||||
logger.error(f"[RetryManager] 重试协程发生意外: {e}", exc_info=True)
|
||||
finally:
|
||||
# 确保最终释放该群的重试锁
|
||||
# 释放群锁
|
||||
if task.group_id in self._active_groups:
|
||||
self._active_groups.discard(task.group_id)
|
||||
|
||||
@@ -281,9 +284,18 @@ class RetryManager:
|
||||
)
|
||||
return False
|
||||
|
||||
# 3. 发送图片 (通过统一适配器接口)
|
||||
# 3. 【临界检查 2】发送图片前最后一次复核 (针对渲染耗时极长产生的盲窗)
|
||||
# 例如渲染 10s 期间图片出来了,这里可以最后贴身拦截一次
|
||||
if adapter and hasattr(adapter, "was_image_sent_recently"):
|
||||
if await adapter.was_image_sent_recently(task.group_id, seconds=120):
|
||||
logger.info(
|
||||
f"[RetryManager] [临界拦截] 渲染完成后检测到群 {task.group_id} 已有报告。拦截重复发送。"
|
||||
)
|
||||
return True
|
||||
|
||||
# 4. 执行实际发送
|
||||
logger.info(
|
||||
f"[RetryManager] 正在向群 {task.group_id} 发送重试图片 (Adapter: {type(adapter).__name__})..."
|
||||
f"[RetryManager] 正在向群 {task.group_id} 发送回补图片 (Adapter: {type(adapter).__name__})..."
|
||||
)
|
||||
|
||||
# 注意:某些适配器可能需要 URL,某些需要 Base64。
|
||||
|
||||
Reference in New Issue
Block a user