diff --git a/_conf_schema.json b/_conf_schema.json index 2b05a01..7254c12 100644 --- a/_conf_schema.json +++ b/_conf_schema.json @@ -117,6 +117,12 @@ "default": "", "hint": "自定义服务所使用的模型名称,例如 gpt-4 、deepseek/deepseek-r1:free 或自定义模型标识,由于自定义情况复杂,无法给出有效的参数参考,需要根据实际情况例如日志报错判断。留空则使用 Astrbot 统一内置提供商。" }, + "bot_qq_id": { + "type": "string", + "description": "机器人QQ号", + "default": "", + "hint": "用于自动分析的机器人QQ号,填写后可启用自动分析功能" + }, "pdf_output_dir": { "type": "string", "description": "PDF输出目录", diff --git a/main.py b/main.py index 493f749..207d619 100644 --- a/main.py +++ b/main.py @@ -18,6 +18,7 @@ from astrbot.core.star.filter.permission import PermissionType # 导入重构后的模块 from .src.core.config import ConfigManager +from .src.core.bot_manager import BotManager from .src.reports.generators import ReportGenerator from .src.scheduler.auto_scheduler import AutoScheduler from .src.utils.pdf_utils import PDFInstaller @@ -26,6 +27,7 @@ from .src.utils.helpers import MessageAnalyzer # 全局变量 config_manager = None +bot_manager = None message_analyzer = None report_generator = None auto_scheduler = None @@ -37,16 +39,19 @@ class QQGroupDailyAnalysis(Star): self.config = config # 初始化模块化组件 - global config_manager, message_analyzer, report_generator, auto_scheduler + global config_manager, bot_manager, message_analyzer, report_generator, auto_scheduler config_manager = ConfigManager(config) - message_analyzer = MessageAnalyzer(context, config_manager) + bot_manager = BotManager(config_manager) + bot_manager.set_context(context) + message_analyzer = MessageAnalyzer(context, config_manager, bot_manager) report_generator = ReportGenerator(config_manager) auto_scheduler = AutoScheduler( config_manager, message_analyzer.message_handler, message_analyzer, report_generator, + bot_manager, self.html_render # 传入html_render函数 ) @@ -62,44 +67,22 @@ class QQGroupDailyAnalysis(Star): # 等待10秒让系统完全初始化 await asyncio.sleep(10) - # 尝试获取bot实例 - bot_instance = await self._get_bot_instance() - if bot_instance: - auto_scheduler.set_bot_instance(bot_instance) - logger.info("已为自动调度器设置bot实例") - else: - logger.info("暂时未获取到bot实例,定时任务仍会启动") + # 初始化bot管理器 + if await bot_manager.initialize_from_config(): + logger.info("Bot管理器初始化成功,启用自动分析功能") - # 启动调度器 - await auto_scheduler.start_scheduler() + # 启动调度器 + await auto_scheduler.start_scheduler() + else: + logger.warning("Bot管理器初始化失败,无法启用自动分析功能") + status = bot_manager.get_status_info() + logger.info(f"Bot管理器状态: {status}") except Exception as e: logger.error(f"延迟启动调度器失败: {e}") - async def _get_bot_instance(self): - """从Context获取bot实例""" - try: - # 简化的获取逻辑,尝试常见的几种方式 - if hasattr(self.context, 'get_platforms') and callable(self.context.get_platforms): - platforms = self.context.get_platforms() - for platform in platforms: - if hasattr(platform, 'bot') and platform.bot: - logger.info(f"从平台获取到bot实例") - return platform.bot - # 尝试从context的platforms属性获取 - if hasattr(self.context, 'platforms') and self.context.platforms: - for platform in self.context.platforms: - if hasattr(platform, 'bot') and platform.bot: - logger.info(f"从平台列表获取到bot实例") - return platform.bot - logger.info("暂时无法获取bot实例") - return None - - except Exception as e: - logger.error(f"获取bot实例失败: {e}") - return None async def _reload_config_and_restart_scheduler(self): """重新加载配置并重启调度器""" @@ -131,9 +114,8 @@ class QQGroupDailyAnalysis(Star): yield event.plain_result("❌ 请在群聊中使用此命令") return - # 设置bot实例 - auto_scheduler.set_bot_instance(event.bot) - await message_analyzer.set_bot_instance(event.bot) + # 更新bot实例(用于手动命令) + bot_manager.update_from_event(event) # 检查群组权限 enabled_groups = config_manager.get_enabled_groups() @@ -151,7 +133,7 @@ class QQGroupDailyAnalysis(Star): try: # 获取群聊消息 - messages = await message_analyzer.message_handler.fetch_group_messages(event.bot, group_id, analysis_days) + messages = await message_analyzer.message_handler.fetch_group_messages(bot_manager.get_bot_instance(), group_id, analysis_days) if not messages: yield event.plain_result("❌ 未找到足够的群聊记录,请确保群内有足够的消息历史") return @@ -338,8 +320,8 @@ class QQGroupDailyAnalysis(Star): yield event.plain_result("🧪 开始测试自动分析功能...") - # 设置bot实例 - auto_scheduler.set_bot_instance(event.bot) + # 更新bot实例(用于测试) + bot_manager.update_from_event(event) # 执行自动分析 try: diff --git a/src/core/bot_manager.py b/src/core/bot_manager.py new file mode 100644 index 0000000..1445a2a --- /dev/null +++ b/src/core/bot_manager.py @@ -0,0 +1,164 @@ +""" +Bot实例管理模块 +统一管理bot实例的获取、设置和使用 +""" + +from typing import Optional, Dict, Any +from astrbot.api import logger + +class BotManager: + """Bot实例管理器 - 统一管理所有bot相关操作""" + + def __init__(self, config_manager): + self.config_manager = config_manager + self._bot_instance = None + self._bot_qq_id = None + self._context = None + self._is_initialized = False + + def set_context(self, context): + """设置AstrBot上下文""" + self._context = context + + def set_bot_instance(self, bot_instance): + """设置bot实例""" + if bot_instance: + self._bot_instance = bot_instance + try: + logger.info(f"Bot实例已设置: {type(bot_instance).__name__}") + except ImportError: + print(f"Bot实例已设置: {type(bot_instance).__name__}") + else: + try: + logger.warning("尝试设置空的bot实例") + except ImportError: + print("尝试设置空的bot实例") + + def set_bot_qq_id(self, bot_qq_id: str): + """设置bot QQ号""" + if bot_qq_id: + self._bot_qq_id = str(bot_qq_id) + try: + logger.info(f"Bot QQ号已设置: {self._bot_qq_id}") + except ImportError: + print(f"Bot QQ号已设置: {self._bot_qq_id}") + else: + try: + logger.warning("尝试设置空的bot QQ号") + except ImportError: + print("尝试设置空的bot QQ号") + + def get_bot_instance(self): + """获取当前bot实例""" + return self._bot_instance + + def has_bot_instance(self) -> bool: + """检查是否有可用的bot实例""" + return self._bot_instance is not None + + def has_bot_qq_id(self) -> bool: + """检查是否有配置的bot QQ号""" + return self._bot_qq_id is not None + + def is_ready_for_auto_analysis(self) -> bool: + """检查是否准备好进行自动分析""" + return self.has_bot_instance() and self.has_bot_qq_id() + + def is_ready_for_manual_analysis(self) -> bool: + """检查是否准备好进行手动分析""" + return self.has_bot_instance() + + async def auto_discover_bot_instance(self) -> Optional[Any]: + """自动发现可用的bot实例""" + try: + if not self._context: + logger.warning("未设置AstrBot上下文,无法自动发现bot实例") + return None + + # 通过platform_manager获取平台实例 + if hasattr(self._context, 'platform_manager') and hasattr(self._context.platform_manager, 'platform_insts'): + platforms = self._context.platform_manager.platform_insts + for platform in platforms: + # 对于aiocqhttp适配器,bot实例在get_client()方法中 + if hasattr(platform, 'get_client'): + bot_client = platform.get_client() + if bot_client: + logger.info(f"自动发现bot实例: {type(bot_client).__name__}") + self.set_bot_instance(bot_client) + return bot_client + # 也检查是否直接有bot属性 + elif hasattr(platform, 'bot') and platform.bot: + logger.info(f"自动发现bot实例: {type(platform.bot).__name__}") + self.set_bot_instance(platform.bot) + return platform.bot + + logger.warning("未找到可用的bot实例") + return None + + except Exception as e: + logger.error(f"自动发现bot实例失败: {e}") + return None + + async def initialize_from_config(self) -> bool: + """从配置初始化bot管理器""" + try: + # 获取配置的bot QQ号 + bot_qq_id = self.config_manager.get_bot_qq_id() + if bot_qq_id: + self.set_bot_qq_id(bot_qq_id) + else: + logger.warning("配置中未找到bot QQ号") + + # 自动发现bot实例 + await self.auto_discover_bot_instance() + + self._is_initialized = True + + if self.is_ready_for_auto_analysis(): + logger.info("Bot管理器初始化完成,可进行自动分析") + return True + elif self.has_bot_instance(): + logger.info("Bot管理器初始化完成,可进行手动分析") + return True + else: + logger.warning("Bot管理器初始化完成,但功能受限") + return False + + except Exception as e: + logger.error(f"Bot管理器初始化失败: {e}") + return False + + def get_status_info(self) -> Dict[str, Any]: + """获取bot管理器状态信息""" + return { + "has_bot_instance": self.has_bot_instance(), + "has_bot_qq_id": self.has_bot_qq_id(), + "bot_qq_id": self._bot_qq_id, + "bot_instance_type": type(self._bot_instance).__name__ if self._bot_instance else None, + "ready_for_auto_analysis": self.is_ready_for_auto_analysis(), + "ready_for_manual_analysis": self.is_ready_for_manual_analysis(), + "is_initialized": self._is_initialized + } + + def update_from_event(self, event): + """从事件更新bot实例(用于手动命令)""" + if hasattr(event, 'bot') and event.bot: + self.set_bot_instance(event.bot) + return True + return False + + def validate_for_message_fetching(self, group_id: str) -> tuple[bool, str]: + """验证是否可以进行消息获取""" + if not self.has_bot_instance(): + return False, f"群 {group_id}: 没有可用的bot实例" + + if not group_id: + return False, "无效的群组ID" + + return True, "验证通过" + + def should_filter_bot_message(self, sender_id: str) -> bool: + """判断是否应该过滤bot自己的消息""" + if not self._bot_qq_id: + return False + return str(sender_id) == self._bot_qq_id diff --git a/src/core/config.py b/src/core/config.py index 7bda27e..bb9b64a 100644 --- a/src/core/config.py +++ b/src/core/config.py @@ -97,6 +97,10 @@ class ConfigManager: def get_pdf_output_dir(self) -> str: """获取PDF输出目录""" return self.config.get("pdf_output_dir", "data/plugins/astrbot-qq-group-daily-analysis/reports") + + def get_bot_qq_id(self) -> str: + """获取bot QQ号""" + return str(self.config.get("bot_qq_id", "")) def get_pdf_filename_format(self) -> str: """获取PDF文件名格式""" diff --git a/src/core/message_handler.py b/src/core/message_handler.py index 0d51d82..8e61fb6 100644 --- a/src/core/message_handler.py +++ b/src/core/message_handler.py @@ -15,27 +15,40 @@ from ...src.visualization.activity_charts import ActivityVisualizer class MessageHandler: """消息处理器""" - def __init__(self, config_manager): + def __init__(self, config_manager, bot_manager=None): self.config_manager = config_manager self.activity_visualizer = ActivityVisualizer() - self.bot_qq_id = None + self.bot_manager = bot_manager - async def set_bot_qq_id(self, bot_instance): - """设置机器人QQ号""" + async def set_bot_qq_id(self, bot_qq_id: str): + """设置机器人QQ号(保持向后兼容)""" try: - if bot_instance and not self.bot_qq_id: - login_info = await bot_instance.api.call_action("get_login_info") - self.bot_qq_id = str(login_info.get("user_id", "")) - logger.info(f"获取到机器人QQ号: {self.bot_qq_id}") + if self.bot_manager: + self.bot_manager.set_bot_qq_id(bot_qq_id) + logger.info(f"设置机器人QQ号: {bot_qq_id}") except Exception as e: - logger.error(f"获取机器人QQ号失败: {e}") + logger.error(f"设置机器人QQ号失败: {e}") + + def set_bot_manager(self, bot_manager): + """设置bot管理器""" + self.bot_manager = bot_manager async def fetch_group_messages(self, bot_instance, group_id: str, days: int) -> List[Dict]: """获取群聊消息记录""" try: - if not bot_instance or not group_id: - logger.error(f"群 {group_id} 无效的客户端或群组ID") - return [] + # 验证参数 + if self.bot_manager: + is_valid, error_msg = self.bot_manager.validate_for_message_fetching(group_id) + if not is_valid: + logger.error(error_msg) + return [] + else: + if not group_id: + logger.error(f"群 {group_id} 无效的群组ID") + return [] + if not bot_instance: + logger.info(f"群 {group_id} 自动分析未获取到 bot 实例,跳过 Bot 消息获取") + return [] # 计算时间范围 end_time = datetime.now() @@ -90,7 +103,7 @@ class MessageHandler: # 过滤掉机器人自己的消息 sender_id = str(msg.get("sender", {}).get("user_id", "")) - if self.bot_qq_id and sender_id == self.bot_qq_id: + if self.bot_manager and self.bot_manager.should_filter_bot_message(sender_id): continue if msg_time >= start_time and msg_time <= end_time: diff --git a/src/scheduler/auto_scheduler.py b/src/scheduler/auto_scheduler.py index b8e52fb..c8fd2ee 100644 --- a/src/scheduler/auto_scheduler.py +++ b/src/scheduler/auto_scheduler.py @@ -12,21 +12,23 @@ from astrbot.api import logger class AutoScheduler: """自动调度器""" - def __init__(self, config_manager, message_handler, analyzer, report_generator, html_render_func=None): + def __init__(self, config_manager, message_handler, analyzer, report_generator, bot_manager, html_render_func=None): self.config_manager = config_manager self.message_handler = message_handler self.analyzer = analyzer self.report_generator = report_generator + self.bot_manager = bot_manager self.html_render_func = html_render_func self.scheduler_task = None - self.bot_instance = None self.last_execution_date = None # 记录上次执行日期,防止重复执行 def set_bot_instance(self, bot_instance): - """设置bot实例""" - self.bot_instance = bot_instance - # 同时设置消息处理器的bot实例 - asyncio.create_task(self.message_handler.set_bot_qq_id(bot_instance)) + """设置bot实例(保持向后兼容)""" + self.bot_manager.set_bot_instance(bot_instance) + + def set_bot_qq_id(self, bot_qq_id: str): + """设置bot QQ号(保持向后兼容)""" + self.bot_manager.set_bot_qq_id(bot_qq_id) async def start_scheduler(self): """启动定时任务调度器""" @@ -116,15 +118,20 @@ class AutoScheduler: async def _perform_auto_analysis_for_group(self, group_id: str): """为指定群执行自动分析""" try: - if not self.bot_instance: - logger.warning(f"群 {group_id} 自动分析跳过:未获取到bot实例") + # 检查bot管理器状态 + if not self.bot_manager.is_ready_for_auto_analysis(): + status = self.bot_manager.get_status_info() + logger.warning(f"群 {group_id} 自动分析跳过:bot管理器未就绪 - {status}") return logger.info(f"开始为群 {group_id} 执行自动分析") # 获取群聊消息 analysis_days = self.config_manager.get_analysis_days() - messages = await self.message_handler.fetch_group_messages(self.bot_instance, group_id, analysis_days) + bot_instance = self.bot_manager.get_bot_instance() + + messages = await self.message_handler.fetch_group_messages(bot_instance, group_id, analysis_days) + if not messages: logger.warning(f"群 {group_id} 未获取到足够的消息记录") return diff --git a/src/utils/helpers.py b/src/utils/helpers.py index 4901a55..eba4272 100644 --- a/src/utils/helpers.py +++ b/src/utils/helpers.py @@ -13,16 +13,20 @@ from ...src.analysis.statistics import UserAnalyzer class MessageAnalyzer: """消息分析器 - 整合所有分析功能""" - def __init__(self, context, config_manager): + def __init__(self, context, config_manager, bot_manager=None): self.context = context self.config_manager = config_manager - self.message_handler = MessageHandler(config_manager) + self.bot_manager = bot_manager + self.message_handler = MessageHandler(config_manager, bot_manager) self.llm_analyzer = LLMAnalyzer(context, config_manager) self.user_analyzer = UserAnalyzer(config_manager) async def set_bot_instance(self, bot_instance): - """设置bot实例""" - await self.message_handler.set_bot_qq_id(bot_instance) + """设置bot实例(保持向后兼容)""" + if self.bot_manager: + self.bot_manager.set_bot_instance(bot_instance) + else: + await self.message_handler.set_bot_qq_id(bot_instance) async def analyze_messages(self, messages: List[Dict], group_id: str) -> Dict: """完整的消息分析流程"""