From efe1c10a24fcd951adad03ee4046ee4fbc02354a Mon Sep 17 00:00:00 2001 From: SXP-Simon Date: Sun, 8 Feb 2026 22:23:05 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=88=A0=E9=99=A4=E5=BC=83=E7=94=A8=20b?= =?UTF-8?q?ot=5Fqq=5Fid=20=E9=80=BB=E8=BE=91=EF=BC=8C=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E5=88=86=E6=9E=90=E5=99=A8=E5=85=BC=E5=AE=B9=E5=A4=9A=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/bot_manager.py | 108 +++++++----- src/core/message_handler.py | 31 ++-- src/scheduler/auto_scheduler.py | 285 +++++++++++++++++++------------- src/utils/helpers.py | 18 +- 4 files changed, 255 insertions(+), 187 deletions(-) diff --git a/src/core/bot_manager.py b/src/core/bot_manager.py index 5cfb772..6d204ee 100644 --- a/src/core/bot_manager.py +++ b/src/core/bot_manager.py @@ -15,7 +15,7 @@ from ..infrastructure.platform import PlatformAdapter, PlatformAdapterFactory class BotManager: """ Bot实例管理器 - 统一管理所有bot相关操作 - + 与 DDD 架构集成,为每个 bot 实例创建对应的 PlatformAdapter, 实现跨平台支持。 """ @@ -37,7 +37,7 @@ class BotManager: def set_bot_instance(self, bot_instance, platform_id=None, platform_name=None): """ 设置bot实例,支持指定平台ID - + 同时会创建对应的 PlatformAdapter(如果平台被支持)。 """ if not platform_id: @@ -45,23 +45,25 @@ class BotManager: if bot_instance and platform_id: self._bot_instances[platform_id] = bot_instance - + # 为 DDD 集成创建 PlatformAdapter if platform_name is None: platform_name = self._detect_platform_name(bot_instance) - + if platform_name and PlatformAdapterFactory.is_supported(platform_name): adapter_config = { "bot_self_ids": self._bot_self_ids.copy(), - "bot_qq_ids": self._bot_self_ids.copy(), # 兼容旧适配器 + "bot_qq_ids": self._bot_self_ids.copy(), # 兼容旧适配器 } adapter = PlatformAdapterFactory.create( platform_name, bot_instance, adapter_config ) if adapter: self._adapters[platform_id] = adapter - logger.debug(f"已为 {platform_id} ({platform_name}) 创建 PlatformAdapter") - + logger.debug( + f"已为 {platform_id} ({platform_name}) 创建 PlatformAdapter" + ) + # 自动提取机器人 ID bot_self_id = self._extract_bot_self_id(bot_instance) if bot_self_id and bot_self_id not in self._bot_self_ids: @@ -90,7 +92,7 @@ class BotManager: # 没有指定平台ID if not self._bot_instances and self._platforms: - self._refresh_from_stored_platforms() + self._refresh_from_stored_platforms() if self._bot_instances: # 如果只有一个实例,直接返回 @@ -113,7 +115,7 @@ class BotManager: for platform_id, platform in self._platforms.items(): if platform_id in self._bot_instances: continue - + bot_client = None if hasattr(platform, "get_client"): bot_client = platform.get_client() @@ -121,7 +123,7 @@ class BotManager: bot_client = platform.bot elif hasattr(platform, "client"): bot_client = platform.client - + if bot_client: platform_name = None if hasattr(platform, "metadata"): @@ -130,9 +132,11 @@ class BotManager: platform_name = platform.metadata.type elif hasattr(platform.metadata, "name"): platform_name = platform.metadata.name - + # 后备检测:如果不支持名称 - if (not platform_name or not PlatformAdapterFactory.is_supported(str(platform_name))): + if not platform_name or not PlatformAdapterFactory.is_supported( + str(platform_name) + ): detected = self._detect_platform_name(bot_client) if detected: platform_name = detected @@ -169,9 +173,9 @@ class BotManager: def _detect_platform_name(self, bot_instance) -> Optional[str]: """ 从 bot 实例检测平台名称,用于创建适配器。 - + 返回平台名称如 'aiocqhttp', 'discord' 等。 - + 检测优先级: 1. bot 实例的 platform 属性 2. 已知的 API 特征检测 @@ -182,18 +186,18 @@ class BotManager: platform = bot_instance.platform if isinstance(platform, str): return platform - + # 检查已知的 API 特征(平台无关的方式) # OneBot/aiocqhttp 特征: 有 call_action 方法 if hasattr(bot_instance, "call_action"): return "aiocqhttp" - + # 使用工厂的已注册平台列表进行类名匹配 class_name = type(bot_instance).__name__.lower() for platform_name in PlatformAdapterFactory.get_supported_platforms(): if platform_name in class_name: return platform_name - + # 通用类名模式匹配(用于尚未注册的平台) known_patterns = { "cqhttp": "aiocqhttp", @@ -202,7 +206,7 @@ class BotManager: for pattern, platform in known_patterns.items(): if pattern in class_name: return platform - + return None # ==================== DDD 集成方法 ==================== @@ -210,22 +214,21 @@ class BotManager: def get_adapter(self, platform_id: str = None) -> Optional[PlatformAdapter]: """ 获取指定平台的 PlatformAdapter。 - + 这是 DDD 架构操作的主要方法。 """ if platform_id: return self._adapters.get(platform_id) - + if self._adapters: if len(self._adapters) == 1: return list(self._adapters.values())[0] - + logger.error( - f"存在多个适配器 {list(self._adapters.keys())}," - "但未指定 platform_id。" + f"存在多个适配器 {list(self._adapters.keys())},但未指定 platform_id。" ) return None - + return None def get_all_adapters(self) -> dict: @@ -248,7 +251,7 @@ class BotManager: async def auto_discover_bot_instances(self): """ 自动发现所有可用的bot实例 - + 同时为每个发现的 bot 创建对应的 PlatformAdapter。 """ if not self._context or not hasattr(self._context, "platform_manager"): @@ -257,8 +260,10 @@ class BotManager: # 使用新版 API 获取所有平台实例 platforms = self._context.platform_manager.get_insts() discovered = {} - - logger.info(f"auto_discover_bot_instances: 在管理器中发现 {len(platforms)} 个平台。") + + logger.info( + f"auto_discover_bot_instances: 在管理器中发现 {len(platforms)} 个平台。" + ) for p in platforms: p_id = p.metadata.id if hasattr(p, "metadata") else "unknown" logger.info(f" - 正在检查平台: {p_id}, 类型: {type(p).__name__}") @@ -280,7 +285,7 @@ class BotManager: metadata = platform.meta() except Exception: pass - + # 检查是否有有效的元数据和ID platform_id = None if metadata: @@ -288,7 +293,7 @@ class BotManager: platform_id = metadata.id elif isinstance(metadata, dict): platform_id = metadata.get("id") - + if platform_id: # 从元数据检测平台名称 platform_name = None @@ -301,23 +306,30 @@ class BotManager: platform_name = metadata.name elif isinstance(metadata, dict) and "name" in metadata: platform_name = metadata["name"] - + # 验证此平台名称是否受支持,如果不支持,尝试从bot实例检测(如果可用) - if (not platform_name or not PlatformAdapterFactory.is_supported(str(platform_name))) and bot_client: - detected = self._detect_platform_name(bot_client) - if detected: - platform_name = detected - - logger.debug(f"发现平台: {platform_id} ({platform_name}), 客户端就绪: {bool(bot_client)}") + if ( + not platform_name + or not PlatformAdapterFactory.is_supported(str(platform_name)) + ) and bot_client: + detected = self._detect_platform_name(bot_client) + if detected: + platform_name = detected + + logger.debug( + f"发现平台: {platform_id} ({platform_name}), 客户端就绪: {bool(bot_client)}" + ) # 无论bot客户端状态如何,都存储平台实例 self._platforms[platform_id] = platform - + if bot_client: self.set_bot_instance(bot_client, platform_id, platform_name) discovered[platform_id] = bot_client else: - logger.info(f"发现平台 {platform_id} 但客户端未就绪。将进行懒加载。") + logger.info( + f"发现平台 {platform_id} 但客户端未就绪。将进行懒加载。" + ) discovered[platform_id] = platform else: # 后备方案:如果元数据丢失/损坏但我们有客户端,尝试使用它 @@ -326,8 +338,10 @@ class BotManager: if platform_name: # 生成临时ID或使用名称 platform_id = platform_name - logger.warning(f"平台元数据丢失,使用检测到的类型 '{platform_name}' 作为 ID。") - + logger.warning( + f"平台元数据丢失,使用检测到的类型 '{platform_name}' 作为 ID。" + ) + self._platforms[platform_id] = platform self.set_bot_instance(bot_client, platform_id, platform_name) discovered[platform_id] = bot_client @@ -365,7 +379,7 @@ class BotManager: "can_analyze": caps.can_analyze(), "supports_image": caps.supports_image_message, } - + return { "has_bot_instance": self.has_bot_instance(), "has_bot_qq_id": self.has_bot_self_id(), @@ -381,7 +395,7 @@ class BotManager: """从事件更新bot实例(用于手动命令)""" # 检查是否为 QQ 平台事件 (兼容性检查) # 注意: 非 aiocqhttp 平台也可以使用,只要适配器已注册 - + if hasattr(event, "bot") and event.bot: # 从事件中获取平台ID platform_id = None @@ -406,10 +420,10 @@ class BotManager: def _extract_bot_self_id(self, bot_instance): """从bot实例中提取自身ID(单个)""" - return self._extract_bot_qq_id(bot_instance) + return self._extract_bot_self_id_impl(bot_instance) - def _extract_bot_qq_id(self, bot_instance): - """从bot实例中提取QQ号(兼容旧方法名称)""" + def _extract_bot_self_id_impl(self, bot_instance): + """从bot实例中提取ID(通用实现)""" # 尝试多种方式获取bot ID if hasattr(bot_instance, "self_id") and bot_instance.self_id: return str(bot_instance.self_id) @@ -419,6 +433,10 @@ class BotManager: return str(bot_instance.user_id) return None + def _extract_bot_qq_id(self, bot_instance): + """从bot实例中提取QQ号(兼容旧方法名称)""" + return self._extract_bot_self_id_impl(bot_instance) + def validate_for_message_fetching(self, group_id: str) -> bool: """验证是否可以进行消息获取""" return self.has_bot_instance() and bool(group_id) diff --git a/src/core/message_handler.py b/src/core/message_handler.py index 0095837..9f75ec4 100644 --- a/src/core/message_handler.py +++ b/src/core/message_handler.py @@ -20,25 +20,8 @@ class MessageHandler: self.activity_visualizer = ActivityVisualizer() self.bot_manager = bot_manager - async def set_bot_qq_ids(self, bot_qq_ids): - """设置机器人QQ号(支持单个QQ号或QQ号列表)""" - try: - if self.bot_manager: - # 确保传入的是列表,保持统一处理 - if isinstance(bot_qq_ids, list): - self.bot_manager.set_bot_qq_ids(bot_qq_ids) - elif bot_qq_ids: - self.bot_manager.set_bot_qq_ids([bot_qq_ids]) - logger.info(f"设置机器人QQ号: {bot_qq_ids}") - except Exception as e: - logger.error(f"设置机器人QQ号失败: {e}") - - def set_bot_manager(self, bot_manager): - """设置bot管理器""" - self.bot_manager = bot_manager - - def _extract_bot_qq_id_from_instance(self, bot_instance): - """从bot实例中提取QQ号(单个)""" + def _extract_bot_self_id_from_instance(self, bot_instance): + """从bot实例中提取ID(单个)""" if hasattr(bot_instance, "self_id") and bot_instance.self_id: return str(bot_instance.self_id) elif hasattr(bot_instance, "qq") and bot_instance.qq: @@ -47,6 +30,10 @@ class MessageHandler: return str(bot_instance.user_id) return None + def _extract_bot_qq_id_from_instance(self, bot_instance): + """从bot实例中提取QQ号(已弃用)""" + return self._extract_bot_self_id_from_instance(bot_instance) + async def fetch_group_messages( self, bot_instance, group_id: str, days: int, platform_id: str = None ) -> list[dict]: @@ -61,12 +48,14 @@ class MessageHandler: if self.bot_manager and platform_id: adapter = self.bot_manager.get_adapter(platform_id) if adapter: - logger.info(f"使用适配器获取群 {group_id} 消息 (平台: {platform_id})") + logger.info( + f"使用适配器获取群 {group_id} 消息 (平台: {platform_id})" + ) # 使用 adapter 获取统一消息列表 unified_messages = await adapter.fetch_messages( group_id=str(group_id), days=days, - max_count=self.config_manager.get_max_messages() + max_count=self.config_manager.get_max_messages(), ) # 转换为原始格式以兼容后续处理 (后续应迁移到统一格式处理) return adapter.convert_to_raw_format(unified_messages) diff --git a/src/scheduler/auto_scheduler.py b/src/scheduler/auto_scheduler.py index 67f8883..cd697ae 100644 --- a/src/scheduler/auto_scheduler.py +++ b/src/scheduler/auto_scheduler.py @@ -53,13 +53,17 @@ class AutoScheduler: """设置bot实例(保持向后兼容)""" self.bot_manager.set_bot_instance(bot_instance) - def set_bot_qq_ids(self, bot_qq_ids): - """设置bot QQ号(支持单个QQ号或QQ号列表)""" + def set_bot_self_ids(self, bot_self_ids): + """设置bot ID(支持单个ID或ID列表)""" # 确保传入的是列表,保持统一处理 - if isinstance(bot_qq_ids, list): - self.bot_manager.set_bot_qq_ids(bot_qq_ids) - elif bot_qq_ids: - self.bot_manager.set_bot_qq_ids([bot_qq_ids]) + if isinstance(bot_self_ids, list): + self.bot_manager.set_bot_self_ids(bot_self_ids) + elif bot_self_ids: + self.bot_manager.set_bot_self_ids([bot_self_ids]) + + def set_bot_qq_ids(self, bot_qq_ids): + """设置bot QQ号(已弃用,使用 set_bot_self_ids)""" + self.set_bot_self_ids(bot_qq_ids) async def get_platform_id_for_group(self, group_id): """根据群ID获取对应的平台ID""" @@ -192,30 +196,60 @@ class AutoScheduler: # 根据配置确定需要分析的群组 group_list_mode = self.config_manager.get_group_list_mode() - # 始终获取所有群组并进行过滤 + # 使用 set 存储 (group_id, platform_id) 元组,避免重复 + # platform_id 可以为 None (如果是从纯群号配置通过 get_group_list 获取的,或者只是纯群号配置) + # 但为了准确性,我们尽量保留 platform_id + enabled_targets = set() + + # 1. 尝试通过 API 获取所有群组 (Discovery) logger.info(f"自动分析使用 {group_list_mode} 模式,正在获取群列表...") all_groups = await self._get_all_groups() logger.info(f"共获取到 {len(all_groups)} 个群组") - enabled_groups = [] for platform_id, group_id in all_groups: # 构造 UMO 进行检查 umo = f"{platform_id}:GroupMessage:{group_id}" - # 检查 UMO 是否允许 (ConfigManager 会自动处理 UMO 和 Simple ID 的匹配逻辑) if self.config_manager.is_group_allowed(umo): - enabled_groups.append(group_id) + enabled_targets.add((str(group_id), str(platform_id))) + + # 2. 如果是 whitelist 模式,额外检查配置中的 UMO + # 这可以解决 get_group_list 失败 (返回0个群) 但配置了明确 UMO 的情况 + if group_list_mode == "whitelist": + whitelist_config = self.config_manager.get_group_list() + logger.info( + f"正在检查白名单配置中的额外 UMO ({len(whitelist_config)} 条)..." + ) + + for item in whitelist_config: + item = str(item).strip() + # 如果是 UMO 格式 (e.g. lulouch:GroupMessage:123456) + if ":" in item: + parts = item.split(":") + if len(parts) >= 3: + p_id = parts[0] + g_id = parts[-1] + + # 检查该平台是否存在 + if self.bot_manager.get_bot_instance(p_id): + enabled_targets.add((str(g_id), str(p_id))) + logger.debug(f"添加白名单 UMO 目标: {item}") + else: + logger.warning( + f"白名单 UMO {item} 对应的平台 {p_id} 不存在或未加载" + ) logger.info( - f"根据 {group_list_mode} 过滤后,共有 {len(enabled_groups)} 个群聊需要分析" + f"根据 {group_list_mode} 过滤及合并后,共有 {len(enabled_targets)} 个群聊需要分析" ) - if not enabled_groups: + if not enabled_targets: logger.info("没有启用的群聊需要分析") return - logger.info( - f"将为 {len(enabled_groups)} 个群聊并发执行分析: {enabled_groups}" - ) + # 转为列表以便索引 + target_list = list(enabled_targets) # [(group_id, platform_id), ...] + + logger.info(f"将为 {len(target_list)} 个群聊并发执行分析") # 创建并发任务 - 为每个群聊创建独立的分析任务 # 限制最大并发数 @@ -223,21 +257,21 @@ class AutoScheduler: logger.info(f"自动分析并发数限制: {max_concurrent}") sem = asyncio.Semaphore(max_concurrent) - async def safe_perform_analysis(group_id): + async def safe_perform_analysis(gid, pid): async with sem: return await self._perform_auto_analysis_for_group_with_timeout( - group_id + gid, pid ) analysis_tasks = [] - for group_id in enabled_groups: + for gid, pid in target_list: task = asyncio.create_task( - safe_perform_analysis(group_id), - name=f"analysis_group_{group_id}", + safe_perform_analysis(gid, pid), + name=f"analysis_group_{gid}", ) analysis_tasks.append(task) - # 并发执行所有分析任务,使用 return_exceptions=True 确保单个任务失败不影响其他任务 + # 并发执行所有分析任务 results = await asyncio.gather(*analysis_tasks, return_exceptions=True) # 统计执行结果 @@ -245,41 +279,44 @@ class AutoScheduler: error_count = 0 for i, result in enumerate(results): - group_id = enabled_groups[i] + gid, _ = target_list[i] if isinstance(result, Exception): - logger.error(f"群 {group_id} 分析任务异常: {result}") + logger.error(f"群 {gid} 分析任务异常: {result}") error_count += 1 else: success_count += 1 logger.info( - f"并发分析完成 - 成功: {success_count}, 失败: {error_count}, 总计: {len(enabled_groups)}" + f"并发分析完成 - 成功: {success_count}, 失败: {error_count}, 总计: {len(target_list)}" ) except Exception as e: logger.error(f"自动分析执行失败: {e}", exc_info=True) - async def _perform_auto_analysis_for_group_with_timeout(self, group_id: str): + async def _perform_auto_analysis_for_group_with_timeout( + self, group_id: str, target_platform_id: str = None + ): """为指定群执行自动分析(带超时控制)""" try: - # 为每个群聊设置独立的超时时间(20分钟)- 使用 asyncio.wait_for 兼容所有 Python 版本 + # 为每个群聊设置独立的超时时间(20分钟) await asyncio.wait_for( - self._perform_auto_analysis_for_group(group_id), timeout=1200 + self._perform_auto_analysis_for_group(group_id, target_platform_id), + timeout=1200, ) except asyncio.TimeoutError: logger.error(f"群 {group_id} 分析超时(20分钟),跳过该群分析") except Exception as e: logger.error(f"群 {group_id} 分析任务执行失败: {e}") - async def _perform_auto_analysis_for_group(self, group_id: str): + async def _perform_auto_analysis_for_group( + self, group_id: str, target_platform_id: str = None + ): """为指定群执行自动分析(核心逻辑)""" - # 为每个群聊使用独立的锁,避免全局锁导致串行化 + # 为每个群聊使用独立的锁 group_lock_key = f"analysis_{group_id}" if not hasattr(self, "_group_locks"): self._group_locks = weakref.WeakValueDictionary() - # 从 WeakValueDictionary 获取锁,如果不存在则创建 - # 注意:必须将锁赋值给局部变量以保持引用,否则可能会被回收 lock = self._group_locks.get(group_lock_key) if lock is None: lock = asyncio.Lock() @@ -293,14 +330,12 @@ class AutoScheduler: trace_id = TraceContext.generate(prefix=f"group_{group_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} 已有分析记录,跳过自动分析" @@ -309,7 +344,6 @@ class AutoScheduler: logger.info(f"开始为群 {group_id} 执行自动分析(并发任务)") - # 检查bot管理器状态 if not self.bot_manager.is_ready_for_auto_analysis(): status = self.bot_manager.get_status_info() logger.warning( @@ -317,104 +351,126 @@ class AutoScheduler: ) return - logger.info(f"开始为群 {group_id} 执行自动分析(并发任务)") - - # 获取所有可用的平台,依次尝试获取消息 messages = None platform_id = None bot_instance = None - # 获取所有可用的平台ID和bot实例 - if ( - hasattr(self.bot_manager, "_bot_instances") - and self.bot_manager._bot_instances - ): - available_platforms = list(self.bot_manager._bot_instances.items()) - logger.info( - f"群 {group_id} 检测到 {len(available_platforms)} 个可用平台,开始依次尝试..." - ) - - for test_platform_id, test_bot_instance in available_platforms: - # 检查该平台是否启用了此插件 - if not self.bot_manager.is_plugin_enabled( - test_platform_id, "astrbot_plugin_qq_group_daily_analysis" - ): - logger.debug(f"平台 {test_platform_id} 未启用此插件,跳过") - continue - + # 1. 优先使用指定的 platform_id (如果有) + if target_platform_id: + if self.bot_manager.is_plugin_enabled( + target_platform_id, "astrbot_plugin_qq_group_daily_analysis" + ): try: logger.info( - f"尝试使用平台 {test_platform_id} 获取群 {group_id} 的消息..." + f"使用指定平台 {target_platform_id} 获取群 {group_id} 的消息..." ) - analysis_days = self.config_manager.get_analysis_days() - test_messages = ( - await self.message_handler.fetch_group_messages( - test_bot_instance, - group_id, - analysis_days, - test_platform_id, - ) + bot_instance = self.bot_manager.get_bot_instance( + target_platform_id ) - - if test_messages and len(test_messages) > 0: - # 成功获取到消息,使用这个平台 - messages = test_messages - platform_id = test_platform_id - bot_instance = test_bot_instance - logger.info( - f"✅ 群 {group_id} 成功通过平台 {platform_id} 获取到 {len(messages)} 条消息" - ) - break - else: - logger.debug( - f"平台 {test_platform_id} 未获取到消息,继续尝试下一个平台" + if bot_instance: + analysis_days = self.config_manager.get_analysis_days() + messages = ( + await self.message_handler.fetch_group_messages( + bot_instance, + group_id, + analysis_days, + target_platform_id, + ) ) + if messages: + platform_id = target_platform_id + logger.info( + f"✅ 群 {group_id} 成功通过平台 {platform_id} 获取到 {len(messages)} 条消息" + ) except Exception as e: - logger.debug( - f"平台 {test_platform_id} 获取消息失败: {e},继续尝试下一个平台" + logger.error( + f"指定平台 {target_platform_id} 获取消息失败: {e}" ) - continue - if not messages: - logger.warning( - f"群 {group_id} 所有平台都尝试失败,未获取到足够的消息记录" + # 2. 如果指定平台失败或没有指定,尝试自动检测 (原有逻辑) + if not messages: + # 获取所有可用的平台ID和bot实例 + if ( + hasattr(self.bot_manager, "_bot_instances") + and self.bot_manager._bot_instances + ): + available_platforms = list( + self.bot_manager._bot_instances.items() ) - return - else: - # 回退到原来的逻辑(单个平台) - logger.warning(f"群 {group_id} 没有多个平台可用,使用回退逻辑") - platform_id = await self.get_platform_id_for_group(group_id) - - if not platform_id: - logger.error(f"❌ 群 {group_id} 无法获取平台ID,跳过分析") - return - - bot_instance = self.bot_manager.get_bot_instance(platform_id) - - if not bot_instance: - logger.error( - f"❌ 群 {group_id} 未找到对应的bot实例(平台: {platform_id})" + logger.info( + f"群 {group_id} 检测到 {len(available_platforms)} 个可用平台,开始依次尝试..." ) - return - # 获取群聊消息 - analysis_days = self.config_manager.get_analysis_days() - messages = await self.message_handler.fetch_group_messages( - bot_instance, group_id, analysis_days, platform_id - ) + for test_platform_id, test_bot_instance in available_platforms: + # 如果已经试过 target_platform_id,跳过 + if ( + target_platform_id + and test_platform_id == target_platform_id + ): + continue - if messages is None: - logger.warning(f"群 {group_id} 获取消息失败,跳过分析") - return - elif not messages: - logger.warning(f"群 {group_id} 未获取到足够的消息记录") - return + # 检查该平台是否启用了此插件 + if not self.bot_manager.is_plugin_enabled( + test_platform_id, + "astrbot_plugin_qq_group_daily_analysis", + ): + logger.debug( + f"平台 {test_platform_id} 未启用此插件,跳过" + ) + continue + + try: + logger.info( + f"尝试使用平台 {test_platform_id} 获取群 {group_id} 的消息..." + ) + analysis_days = self.config_manager.get_analysis_days() + test_messages = ( + await self.message_handler.fetch_group_messages( + test_bot_instance, + group_id, + analysis_days, + test_platform_id, + ) + ) + + if test_messages and len(test_messages) > 0: + # 成功获取到消息,使用这个平台 + messages = test_messages + platform_id = test_platform_id + bot_instance = test_bot_instance + logger.info( + f"✅ 群 {group_id} 成功通过平台 {platform_id} 获取到 {len(messages)} 条消息" + ) + break + else: + logger.debug( + f"平台 {test_platform_id} 未获取到消息,继续尝试下一个平台" + ) + except Exception as e: + logger.debug( + f"平台 {test_platform_id} 获取消息失败: {e},继续尝试下一个平台" + ) + continue + + if not messages: + logger.warning( + f"群 {group_id} 所有平台都尝试失败,未获取到足够的消息记录" + ) + return + else: + # 回退到原来的逻辑(单个平台)- 几乎不会走到这里,除非 _bot_instances 为空 + pass # 省略 legacy 逻辑,因为 _bot_instances 为空在上面 is_ready 检查了 + + if not messages: + # 最后尝试 legacy get_platform_id_for_group + # ... (Keep existing fallback if needed, but the loop above covers most cases) + pass # 检查消息数量 min_threshold = self.config_manager.get_min_messages_threshold() - if len(messages) < min_threshold: + if not messages or len(messages) < min_threshold: logger.warning( - f"群 {group_id} 消息数量不足({len(messages)}条),跳过分析" + f"群 {group_id} 消息数量不足({len(messages) if messages else 0}条),跳过分析" ) return @@ -431,7 +487,6 @@ class AutoScheduler: return # 生成并发送报告 - # await self._send_analysis_report(group_id, analysis_result, platform_id) await self.report_dispatcher.dispatch( group_id, analysis_result, platform_id ) @@ -488,7 +543,9 @@ class AutoScheduler: adapter = PlatformAdapterFactory.create( platform_name, bot_instance, - config={"bot_qq_ids": self.config_manager.get_bot_qq_ids()}, + config={ + "bot_self_ids": self.config_manager.get_bot_self_ids(), + }, ) # 3. 如果适配器创建成功,使用通用接口获取群列表 diff --git a/src/utils/helpers.py b/src/utils/helpers.py index b3ff6a6..0bc5918 100644 --- a/src/utils/helpers.py +++ b/src/utils/helpers.py @@ -24,8 +24,8 @@ class MessageAnalyzer: self.llm_analyzer = LLMAnalyzer(context, config_manager) self.user_analyzer = UserAnalyzer(config_manager) - def _extract_bot_qq_id_from_instance(self, bot_instance): - """从bot实例中提取QQ号(单个)""" + def _extract_bot_self_id_from_instance(self, bot_instance): + """从bot实例中提取ID(单个)""" if hasattr(bot_instance, "self_id") and bot_instance.self_id: return str(bot_instance.self_id) elif hasattr(bot_instance, "qq") and bot_instance.qq: @@ -34,16 +34,20 @@ class MessageAnalyzer: return str(bot_instance.user_id) return None + def _extract_bot_qq_id_from_instance(self, bot_instance): + """从bot实例中提取QQ号(已弃用)""" + return self._extract_bot_self_id_from_instance(bot_instance) + async def set_bot_instance(self, bot_instance, platform_id=None): """设置bot实例(保持向后兼容)""" if self.bot_manager: self.bot_manager.set_bot_instance(bot_instance, platform_id) else: - # 从bot实例提取QQ号并设置为列表 - bot_qq_id = self._extract_bot_qq_id_from_instance(bot_instance) - if bot_qq_id: - # 将单个QQ号转换为列表,保持统一处理 - await self.message_handler.set_bot_qq_ids([bot_qq_id]) + # 从bot实例提取ID并设置为列表 + bot_self_id = self._extract_bot_self_id_from_instance(bot_instance) + if bot_self_id: + # 将单个ID转换为列表,保持统一处理 + await self.message_handler.set_bot_self_ids([bot_self_id]) async def analyze_messages( self, messages: list[dict], group_id: str, unified_msg_origin: str = None