mirror of
https://github.com/Nezumi-2711/astrbot_plugin_qq_group_daily_analysis.git
synced 2026-09-22 20:01:04 +00:00
fix: 完成多平台逻辑解耦优化
配置模式 (_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 等其他平台。容器已重启,新配置生效。
This commit is contained in:
+3
-3
@@ -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 代码块标记。"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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":
|
||||
# 处理回复消息,添加标记
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user