mirror of
https://github.com/Nezumi-2711/astrbot_plugin_qq_group_daily_analysis.git
synced 2026-09-22 13:38:43 +00:00
feat: implement sub-daily history tracking for analysis results
This commit is contained in:
@@ -20,6 +20,7 @@ from .src.core.bot_manager import BotManager
|
|||||||
|
|
||||||
# 导入重构后的模块
|
# 导入重构后的模块
|
||||||
from .src.core.config import ConfigManager
|
from .src.core.config import ConfigManager
|
||||||
|
from .src.core.history_manager import HistoryManager
|
||||||
from .src.reports.generators import ReportGenerator
|
from .src.reports.generators import ReportGenerator
|
||||||
from .src.scheduler.auto_scheduler import AutoScheduler
|
from .src.scheduler.auto_scheduler import AutoScheduler
|
||||||
from .src.scheduler.retry import RetryManager
|
from .src.scheduler.retry import RetryManager
|
||||||
@@ -40,6 +41,7 @@ class QQGroupDailyAnalysis(Star):
|
|||||||
context, self.config_manager, self.bot_manager
|
context, self.config_manager, self.bot_manager
|
||||||
)
|
)
|
||||||
self.report_generator = ReportGenerator(self.config_manager)
|
self.report_generator = ReportGenerator(self.config_manager)
|
||||||
|
self.history_manager = HistoryManager(self)
|
||||||
self.retry_manager = RetryManager(
|
self.retry_manager = RetryManager(
|
||||||
self.bot_manager, self.html_render, self.report_generator
|
self.bot_manager, self.html_render, self.report_generator
|
||||||
)
|
)
|
||||||
@@ -50,6 +52,7 @@ class QQGroupDailyAnalysis(Star):
|
|||||||
self.report_generator,
|
self.report_generator,
|
||||||
self.bot_manager,
|
self.bot_manager,
|
||||||
self.retry_manager,
|
self.retry_manager,
|
||||||
|
self.history_manager,
|
||||||
self.html_render, # 传入html_render函数
|
self.html_render, # 传入html_render函数
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -187,11 +190,13 @@ class QQGroupDailyAnalysis(Star):
|
|||||||
messages, group_id, event.unified_msg_origin
|
messages, group_id, event.unified_msg_origin
|
||||||
)
|
)
|
||||||
|
|
||||||
# 检查分析结果
|
|
||||||
if not analysis_result or not analysis_result.get("statistics"):
|
if not analysis_result or not analysis_result.get("statistics"):
|
||||||
yield event.plain_result("❌ 分析过程中出现错误,请稍后重试")
|
yield event.plain_result("❌ 分析过程中出现错误,请稍后重试")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# 保存到历史记录
|
||||||
|
await self.history_manager.save_analysis(group_id, analysis_result)
|
||||||
|
|
||||||
# 生成报告
|
# 生成报告
|
||||||
output_format = self.config_manager.get_output_format()
|
output_format = self.config_manager.get_output_format()
|
||||||
if output_format == "image":
|
if output_format == "image":
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
"""
|
||||||
|
历史记录管理器模块
|
||||||
|
负责存储和查询群聊分析报告的摘要信息
|
||||||
|
使用 AstrBot 的 put_kv_data/get_kv_data 实现
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from astrbot.api import logger
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryManager:
|
||||||
|
"""历史分析记录管理器"""
|
||||||
|
|
||||||
|
def __init__(self, star_instance):
|
||||||
|
"""
|
||||||
|
初始化历史记录管理器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
star_instance: Star 插件实例,用于访问 put_kv_data/get_kv_data
|
||||||
|
"""
|
||||||
|
self.plugin = star_instance
|
||||||
|
|
||||||
|
async def save_analysis(
|
||||||
|
self,
|
||||||
|
group_id: str,
|
||||||
|
analysis_result: dict[str, Any],
|
||||||
|
date_str: str | None = None,
|
||||||
|
time_str: str | None = None,
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
保存分析结果摘要到历史记录
|
||||||
|
|
||||||
|
Args:
|
||||||
|
group_id: 群组ID
|
||||||
|
analysis_result: 分析结果对象
|
||||||
|
date_str: 日期字符串 (格式: YYYY-MM-DD),如果不提供则使用当前日期
|
||||||
|
time_str: 时间字符串 (格式: HH-MM),如果不提供则使用当前时间
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
if not date_str:
|
||||||
|
date_str = now.strftime("%Y-%m-%d")
|
||||||
|
if not time_str:
|
||||||
|
time_str = now.strftime("%H-%M")
|
||||||
|
|
||||||
|
# 格式化 time_str,确保文件名/Key 安全 (替换 : 为 -)
|
||||||
|
time_str = time_str.replace(":", "-")
|
||||||
|
|
||||||
|
# 提取摘要数据
|
||||||
|
stats = analysis_result.get("statistics")
|
||||||
|
topics = analysis_result.get("topics", [])
|
||||||
|
user_titles = analysis_result.get("user_titles", [])
|
||||||
|
|
||||||
|
summary = {
|
||||||
|
"message_count": getattr(stats, "message_count", 0) if stats else 0,
|
||||||
|
"participant_count": getattr(stats, "participant_count", 0)
|
||||||
|
if stats
|
||||||
|
else 0,
|
||||||
|
"topics": [{"topic": t.topic, "detail": t.detail} for t in topics],
|
||||||
|
"user_titles_count": len(user_titles),
|
||||||
|
"generated_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
}
|
||||||
|
|
||||||
|
key = f"analysis_{group_id}_{date_str}_{time_str}"
|
||||||
|
await self.plugin.put_kv_data(key, summary)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"已保存群 {group_id} 在 {date_str} {time_str} 的分析摘要到历史记录 (Key: {key})"
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"保存历史分析记录失败: {e}", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def get_history(
|
||||||
|
self, group_id: str, date_str: str, time_str: str
|
||||||
|
) -> dict[str, Any] | None:
|
||||||
|
"""
|
||||||
|
获取指定日期、时间点和群组的分析摘要
|
||||||
|
|
||||||
|
Args:
|
||||||
|
group_id: 群组ID
|
||||||
|
date_str: 日期字符串 (YYYY-MM-DD)
|
||||||
|
time_str: 时间字符串 (HH-MM)
|
||||||
|
"""
|
||||||
|
# 确保格式统一
|
||||||
|
time_str = time_str.replace(":", "-")
|
||||||
|
key = f"analysis_{group_id}_{date_str}_{time_str}"
|
||||||
|
return await self.plugin.get_kv_data(key, None)
|
||||||
|
|
||||||
|
async def has_history(self, group_id: str, date_str: str, time_str: str) -> bool:
|
||||||
|
"""
|
||||||
|
检查指定日期、时间点和群组是否已有分析记录
|
||||||
|
|
||||||
|
Args:
|
||||||
|
group_id: 群组ID
|
||||||
|
date_str: 日期字符串 (YYYY-MM-DD)
|
||||||
|
time_str: 时间字符串 (HH-MM)
|
||||||
|
"""
|
||||||
|
history = await self.get_history(group_id, date_str, time_str)
|
||||||
|
return history is not None
|
||||||
@@ -25,7 +25,8 @@ class AutoScheduler:
|
|||||||
analyzer,
|
analyzer,
|
||||||
report_generator,
|
report_generator,
|
||||||
bot_manager,
|
bot_manager,
|
||||||
retry_manager, # 新增
|
retry_manager,
|
||||||
|
history_manager,
|
||||||
html_render_func=None,
|
html_render_func=None,
|
||||||
):
|
):
|
||||||
self.config_manager = config_manager
|
self.config_manager = config_manager
|
||||||
@@ -34,6 +35,7 @@ class AutoScheduler:
|
|||||||
self.report_generator = report_generator
|
self.report_generator = report_generator
|
||||||
self.bot_manager = bot_manager
|
self.bot_manager = bot_manager
|
||||||
self.retry_manager = retry_manager # 保存引用
|
self.retry_manager = retry_manager # 保存引用
|
||||||
|
self.history_manager = history_manager
|
||||||
self.html_render_func = html_render_func
|
self.html_render_func = html_render_func
|
||||||
|
|
||||||
# Initialize Core Components
|
# Initialize Core Components
|
||||||
@@ -282,6 +284,21 @@ class AutoScheduler:
|
|||||||
# 设置 TraceID
|
# 设置 TraceID
|
||||||
trace_id = TraceContext.generate(prefix=f"group_{group_id}")
|
trace_id = TraceContext.generate(prefix=f"group_{group_id}")
|
||||||
TraceContext.set(trace_id)
|
TraceContext.set(trace_id)
|
||||||
|
|
||||||
|
# 获取当前日期和时间槽 (HH-MM)
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
date_str = now.strftime("%Y-%m-%d")
|
||||||
|
time_str = now.strftime("%H-%M")
|
||||||
|
|
||||||
|
# 检查是否已有该时间段分析记录
|
||||||
|
if await self.history_manager.has_history(group_id, date_str, time_str):
|
||||||
|
logger.info(
|
||||||
|
f"群 {group_id} 在 {date_str} {time_str} 已有分析记录,跳过自动分析"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
logger.info(f"开始为群 {group_id} 执行自动分析(并发任务)")
|
logger.info(f"开始为群 {group_id} 执行自动分析(并发任务)")
|
||||||
|
|
||||||
# 检查bot管理器状态
|
# 检查bot管理器状态
|
||||||
@@ -411,6 +428,11 @@ class AutoScheduler:
|
|||||||
group_id, analysis_result, platform_id
|
group_id, analysis_result, platform_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 保存到历史记录
|
||||||
|
await self.history_manager.save_analysis(
|
||||||
|
group_id, analysis_result, date_str, time_str
|
||||||
|
)
|
||||||
|
|
||||||
# 记录执行时间
|
# 记录执行时间
|
||||||
end_time = asyncio.get_event_loop().time()
|
end_time = asyncio.get_event_loop().time()
|
||||||
execution_time = end_time - start_time
|
execution_time = end_time - start_time
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import asyncio
|
||||||
|
import sys
|
||||||
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# 模拟环境
|
||||||
|
sys.modules['astrbot'] = MagicMock()
|
||||||
|
sys.modules['astrbot.api'] = MagicMock()
|
||||||
|
|
||||||
|
async def verify_history():
|
||||||
|
print("开始验证 HistoryManager...")
|
||||||
|
|
||||||
|
# 模拟 Star 实例
|
||||||
|
mock_star = MagicMock()
|
||||||
|
mock_star.put_kv_data = AsyncMock()
|
||||||
|
mock_star.get_kv_data = AsyncMock()
|
||||||
|
|
||||||
|
# 导入 HistoryManager (使用相对路径或动态导入)
|
||||||
|
from src.core.history_manager import HistoryManager
|
||||||
|
|
||||||
|
hm = HistoryManager(mock_star)
|
||||||
|
|
||||||
|
# 1. 测试保存分析
|
||||||
|
group_id = "123456"
|
||||||
|
mock_stats = MagicMock()
|
||||||
|
mock_stats.message_count = 100
|
||||||
|
mock_stats.participant_count = 10
|
||||||
|
|
||||||
|
analysis_result = {
|
||||||
|
"statistics": mock_stats,
|
||||||
|
"topics": [],
|
||||||
|
"user_titles": []
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"测试保存群 {group_id} 的分析摘要 (带时间槽)...")
|
||||||
|
await hm.save_analysis(group_id, analysis_result, "2026-02-08", "12-00")
|
||||||
|
|
||||||
|
# 验证 put_kv_data 被调用,且 key 正确
|
||||||
|
expected_key = f"analysis_{group_id}_2026-02-08_12-00"
|
||||||
|
mock_star.put_kv_data.assert_called_once()
|
||||||
|
actual_key = mock_star.put_kv_data.call_args[0][0]
|
||||||
|
actual_data = mock_star.put_kv_data.call_args[0][1]
|
||||||
|
|
||||||
|
assert actual_key == expected_key
|
||||||
|
assert actual_data["message_count"] == 100
|
||||||
|
print(f"✅ 保存验证成功: Key={actual_key}")
|
||||||
|
|
||||||
|
# 2. 测试检查历史 (不同时间点)
|
||||||
|
mock_star.get_kv_data.side_effect = lambda type, pid, key, default: {"message_count": 100} if "12-00" in key else None
|
||||||
|
|
||||||
|
has_history_12 = await hm.has_history(group_id, "2026-02-08", "12-00")
|
||||||
|
assert has_history_12 is True
|
||||||
|
print(f"✅ 12:00 历史检查成功")
|
||||||
|
|
||||||
|
has_history_13 = await hm.has_history(group_id, "2026-02-08", "13-00")
|
||||||
|
assert has_history_13 is False
|
||||||
|
print(f"✅ 13:00 (无历史) 检查成功")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(verify_history())
|
||||||
Reference in New Issue
Block a user