From e24449cac119167d33e976e69f697cc1c16a6c0b Mon Sep 17 00:00:00 2001 From: SXP-Simon Date: Sun, 8 Feb 2026 20:09:56 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AE=8C=E6=88=90=E5=A4=9A=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E9=80=BB=E8=BE=91=E8=A7=A3=E8=80=A6=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 配置模式 (_conf_schema.json): 将所有 "QQ号" 描述更名为通用的 "ID" 或 "用户ID"。 更新了 话题分析、用户称号分析、金句分析 的默认 Prompt 模板,将示例 JSON 中的 qq 字段改为 user_id。 保留 bot_qq_ids 配置项名称以维持向后兼容性,但其描述已更新。 数据模型 (data_models.py): UserTitle 和 GoldenQuote 类中的 qq 字段重命名为 user_id (类型 str)。 为了兼容旧代码,添加了 qq 属性(@property),自动将 user_id 转为 int 返回。 分析逻辑 (analyzers/*.py): TopicAnalyzer: 通用化了 @ 消息的处理,支持读取 qq、id 或 user_id 字段。 UserTitleAnalyzer & GoldenQuoteAnalyzer: 更新了 Prompt 构建和结果提取逻辑,优先使用 user_id,不再强制依赖 QQ 号格式。 ReportGenerator: 更新了报告生成逻辑,使用 user_id 获取头像。 结果: 插件现在更具通用性,配置和提示词不再默认绑定 QQ 平台术语,能更好地适应 Discord 等其他平台。容器已重启,新配置生效。 --- _conf_schema.json | 6 +- .../analyzers/golden_quote_analyzer.py | 87 +++++++++---------- src/analysis/analyzers/topic_analyzer.py | 12 ++- src/analysis/analyzers/user_title_analyzer.py | 32 ++++--- src/models/data_models.py | 20 ++++- src/reports/generators.py | 6 +- 6 files changed, 96 insertions(+), 67 deletions(-) diff --git a/_conf_schema.json b/_conf_schema.json index bc2aa65..eb09e4b 100644 --- a/_conf_schema.json +++ b/_conf_schema.json @@ -50,9 +50,9 @@ }, "bot_qq_ids": { "type": "list", - "description": "群分析时屏蔽的QQ号列表", + "description": "群分析时屏蔽的用户ID列表", "default": [], - "hint": "填写后可启用自动分析功能。可以填写用于自动分析的机器人QQ号、多消息平台QQ号、不希望出现于群分析中的其他人的机器人QQ号等,这种群聊中出现但是不希望分析的QQ号。", + "hint": "填写后可启用自动分析功能。可以填写用于自动分析的机器人ID、多消息平台ID、不希望出现于群分析中的其他人的机器人ID等,这种群聊中出现但是不希望分析的ID。", "items": { "type": "string" } @@ -218,7 +218,7 @@ "type": "text", "editor_mode": true, "editor_language": "markdown", - "default": "请为以下群友分配合适的称号和 MBTI 类型。\n\n## 规则:\n\n- 每个人只能有一个称号\n- 每个称号只能给一个人\n\n## 可选称号:\n\n- **龙王**: 发言频繁但内容轻松的人\n- **技术专家**: 经常讨论技术话题的人\n- **夜猫子**: 经常在深夜发言的人\n- **表情包军火库**: 经常发表情的人\n- **沉默终结者**: 经常开启话题的人\n- **评论家**: 平均发言长度很长的人\n- **阳角**: 在群里很有影响力的人\n- **互动达人**: 经常回复别人的人\n- *...(你可以自行进行拓展添加)*\n\n## 用户数据:\n\n{users_text}\n\n---\n\n### 返回格式示例:\n\n```json\n[\n {{\n \"name\": \"用户名\",\n \"qq\": 123456789,\n \"title\": \"称号\",\n \"mbti\": \"MBTI类型\",\n \"reason\": \"获得此称号的原因\"\n }}\n]\n```\n\n**注意**:请以纯 JSON 格式返回,不要包含 markdown 代码块标记。" + "default": "请为以下群友分配合适的称号和 MBTI 类型。\n\n## 规则:\n\n- 每个人只能有一个称号\n- 每个称号只能给一个人\n\n## 可选称号:\n\n- **龙王**: 发言频繁但内容轻松的人\n- **技术专家**: 经常讨论技术话题的人\n- **夜猫子**: 经常在深夜发言的人\n- **表情包军火库**: 经常发表情的人\n- **沉默终结者**: 经常开启话题的人\n- **评论家**: 平均发言长度很长的人\n- **阳角**: 在群里很有影响力的人\n- **互动达人**: 经常回复别人的人\n- *...(你可以自行进行拓展添加)*\n\n## 用户数据:\n\n{users_text}\n\n---\n\n### 返回格式示例:\n\n```json\n[\n {{\n \"name\": \"用户名\",\n \"user_id\": \"123456789\",\n \"title\": \"称号\",\n \"mbti\": \"MBTI类型\",\n \"reason\": \"获得此称号的原因\"\n }}\n]\n```\n\n**注意**:请以纯 JSON 格式返回,不要包含 markdown 代码块标记。" } } }, diff --git a/src/analysis/analyzers/golden_quote_analyzer.py b/src/analysis/analyzers/golden_quote_analyzer.py index f717a3b..38b52fd 100644 --- a/src/analysis/analyzers/golden_quote_analyzer.py +++ b/src/analysis/analyzers/golden_quote_analyzer.py @@ -126,6 +126,48 @@ class GoldenQuoteAnalyzer(BaseAnalyzer): logger.error(f"创建金句对象失败: {e}") return [] + async def analyze_golden_quotes( + self, messages: list[dict], umo: str = None + ) -> tuple[list[GoldenQuote], TokenUsage]: + """ + 分析群聊金句 + + Args: + messages: 群聊消息列表 + umo: 模型唯一标识符 + + Returns: + (金句列表, Token使用统计) + """ + try: + # 提取圣经的文本消息 + interesting_messages = self.extract_interesting_messages(messages) + + if not interesting_messages: + logger.info("没有符合条件的圣经消息,返回空结果") + return [], TokenUsage() + + logger.info(f"开始从 {len(interesting_messages)} 条圣经消息中提取金句") + quotes, usage = await self.analyze(interesting_messages, umo) + + # 回填 User ID + for quote in quotes: + for msg in interesting_messages: + # 尝试匹配内容和发送者 + # 注意:LLM 可能会微调内容,这里使用包含匹配或精确匹配 + if ( + quote.content in msg["content"] + or msg["content"] in quote.content + ) and quote.sender == msg["sender"]: + quote.user_id = str(msg.get("user_id", "")) + break + + return quotes, usage + + except Exception as e: + logger.error(f"金句分析失败: {e}") + return [], TokenUsage() + def extract_interesting_messages(self, messages: list[dict]) -> list[dict]: """ 提取圣经的文本消息 @@ -156,7 +198,7 @@ class GoldenQuoteAnalyzer(BaseAnalyzer): "sender": nickname, "time": msg_time, "content": text, - "qq": sender.get("user_id", 0), + "user_id": str(sender.get("user_id", "")), } ) @@ -165,46 +207,3 @@ class GoldenQuoteAnalyzer(BaseAnalyzer): except Exception as e: logger.error(f"提取圣经消息失败: {e}") return [] - - async def analyze_golden_quotes( - self, messages: list[dict], umo: str = None - ) -> tuple[list[GoldenQuote], TokenUsage]: - """ - 分析群聊金句 - - Args: - messages: 群聊消息列表 - umo: 模型唯一标识符 - - Returns: - (金句列表, Token使用统计) - """ - try: - # 提取圣经的文本消息 - interesting_messages = self.extract_interesting_messages(messages) - - if not interesting_messages: - logger.info("没有符合条件的圣经消息,返回空结果") - return [], TokenUsage() - - logger.info(f"开始从 {len(interesting_messages)} 条圣经消息中提取金句") - logger.info(f"开始从 {len(interesting_messages)} 条圣经消息中提取金句") - quotes, usage = await self.analyze(interesting_messages, umo) - - # 回填QQ号 - for quote in quotes: - for msg in interesting_messages: - # 尝试匹配内容和发送者 - # 注意:LLM 可能会微调内容,这里使用包含匹配或精确匹配 - if ( - quote.content in msg["content"] - or msg["content"] in quote.content - ) and quote.sender == msg["sender"]: - quote.qq = msg.get("qq", 0) - break - - return quotes, usage - - except Exception as e: - logger.error(f"金句分析失败: {e}") - return [], TokenUsage() diff --git a/src/analysis/analyzers/topic_analyzer.py b/src/analysis/analyzers/topic_analyzer.py index fb8d395..8788a2b 100644 --- a/src/analysis/analyzers/topic_analyzer.py +++ b/src/analysis/analyzers/topic_analyzer.py @@ -100,9 +100,15 @@ class TopicAnalyzer(BaseAnalyzer): text_parts.append(text) elif content_type == "at": # 处理 @ 消息,转换为文本 - at_qq = content.get("data", {}).get("qq", "") - if at_qq: - at_text = f"@{at_qq}" + at_data = content.get("data", {}) + # 兼容不同平台的 ID 字段 + at_id = ( + at_data.get("qq") + or at_data.get("id") + or at_data.get("user_id") + ) + if at_id: + at_text = f"@{at_id}" text_parts.append(at_text) elif content_type == "reply": # 处理回复消息,添加标记 diff --git a/src/analysis/analyzers/user_title_analyzer.py b/src/analysis/analyzers/user_title_analyzer.py index 8a0a46b..e82b042 100644 --- a/src/analysis/analyzers/user_title_analyzer.py +++ b/src/analysis/analyzers/user_title_analyzer.py @@ -54,7 +54,7 @@ class UserTitleAnalyzer(BaseAnalyzer): # 构建用户数据文本 users_text = "\n".join( [ - f"- {user['name']} (QQ:{user['qq']}): " + f"- {user['name']} (ID:{user['user_id']}): " f"发言{user['message_count']}条, 平均{user['avg_chars']}字, " f"表情比例{user['emoji_ratio']}, 夜间发言比例{user['night_ratio']}, " f"回复比例{user['reply_ratio']}" @@ -109,7 +109,8 @@ class UserTitleAnalyzer(BaseAnalyzer): for title_data in titles_data[:max_titles]: # 确保数据格式正确 name = title_data.get("name", "").strip() - qq = title_data.get("qq") + # 兼容 LLM 返回 qq 或 user_id 的情况 + user_id = title_data.get("user_id") or title_data.get("qq") title = title_data.get("title", "").strip() mbti = title_data.get("mbti", "").strip() reason = title_data.get("reason", "").strip() @@ -119,15 +120,21 @@ class UserTitleAnalyzer(BaseAnalyzer): logger.warning(f"用户称号数据格式不完整,跳过: {title_data}") continue - # 验证QQ号格式(单个用户QQ号) - try: - qq = int(qq) - except (ValueError, TypeError): - logger.warning(f"QQ号格式无效,跳过: {qq}") + # 确保 user_id 是字符串 + if user_id is not None: + user_id = str(user_id) + else: + logger.warning(f"未找到用户ID (user_id/qq),跳过: {title_data}") continue titles.append( - UserTitle(name=name, qq=qq, title=title, mbti=mbti, reason=reason) + UserTitle( + name=name, + user_id=user_id, + title=title, + mbti=mbti, + reason=reason, + ) ) return titles @@ -172,13 +179,14 @@ class UserTitleAnalyzer(BaseAnalyzer): } for user_id, stats in user_analysis.items(): + user_id_str = str(user_id) # 过滤机器人自己的消息 - if bot_qq_ids and str(user_id) in [str(qq) for qq in bot_qq_ids]: - logger.debug(f"过滤掉机器人QQ号: {user_id}") + if bot_qq_ids and user_id_str in [str(qq) for qq in bot_qq_ids]: + logger.debug(f"过滤掉机器人ID: {user_id}") continue # 只处理活跃用户 - if user_id not in target_user_ids: + if user_id_str not in target_user_ids: continue # 分析用户特征 @@ -192,7 +200,7 @@ class UserTitleAnalyzer(BaseAnalyzer): user_summaries.append( { "name": stats["nickname"], - "qq": int(user_id), + "user_id": user_id_str, # 使用 user_id "message_count": stats["message_count"], "avg_chars": round(avg_chars, 1), "emoji_ratio": round( diff --git a/src/models/data_models.py b/src/models/data_models.py index 0625be9..9d47f85 100644 --- a/src/models/data_models.py +++ b/src/models/data_models.py @@ -20,11 +20,19 @@ class UserTitle: """用户称号数据结构""" name: str - qq: int + user_id: str # 原 qq 字段 title: str mbti: str reason: str + @property + def qq(self) -> int: + """兼容旧字段""" + try: + return int(self.user_id) if self.user_id else 0 + except ValueError: + return 0 + @dataclass class GoldenQuote: @@ -33,7 +41,15 @@ class GoldenQuote: content: str sender: str reason: str - qq: int = 0 + user_id: str = "" # 原 qq 字段 + + @property + def qq(self) -> int: + """兼容旧字段""" + try: + return int(self.user_id) if self.user_id else 0 + except ValueError: + return 0 @dataclass diff --git a/src/reports/generators.py b/src/reports/generators.py index 31649dc..8ad589f 100644 --- a/src/reports/generators.py +++ b/src/reports/generators.py @@ -256,7 +256,7 @@ class ReportGenerator: titles_list = [] for title in user_titles[:max_user_titles]: # 获取用户头像 - avatar_data = await self._get_user_avatar(str(title.qq), avatar_getter) + avatar_data = await self._get_user_avatar(str(title.user_id), avatar_getter) title_data = { "name": title.name, "title": title.title, @@ -276,8 +276,8 @@ class ReportGenerator: quotes_list = [] for quote in stats.golden_quotes[:max_golden_quotes]: avatar_url = ( - await self._get_user_avatar(str(quote.qq), avatar_getter) - if quote.qq + await self._get_user_avatar(str(quote.user_id), avatar_getter) + if quote.user_id else None ) quotes_list.append(