mirror of
https://github.com/Nezumi-2711/astrbot_plugin_qq_group_daily_analysis.git
synced 2026-09-22 20:01:04 +00:00
fix: 删除弃用 bot_qq_id 逻辑,定时分析器兼容多平台
This commit is contained in:
+63
-45
@@ -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)
|
||||
|
||||
+10
-21
@@ -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)
|
||||
|
||||
+171
-114
@@ -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. 如果适配器创建成功,使用通用接口获取群列表
|
||||
|
||||
+11
-7
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user