Merge branch 'dev' of https://github.com/MoFox-Studio/MoFox_Bot into dev
This commit is contained in:
@@ -122,6 +122,13 @@ class ChatterManager:
|
|||||||
actions_count = result.get("actions_count", 0)
|
actions_count = result.get("actions_count", 0)
|
||||||
logger.debug(f"流 {stream_id} 处理完成: 成功={success}, 动作数={actions_count}")
|
logger.debug(f"流 {stream_id} 处理完成: 成功={success}, 动作数={actions_count}")
|
||||||
|
|
||||||
|
# 在处理完成后,清除该流的未读消息
|
||||||
|
try:
|
||||||
|
from src.chat.message_manager.message_manager import message_manager
|
||||||
|
await message_manager.clear_stream_unread_messages(stream_id)
|
||||||
|
except Exception as clear_e:
|
||||||
|
logger.error(f"清除流 {stream_id} 未读消息时发生错误: {clear_e}")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.stats["failed_executions"] += 1
|
self.stats["failed_executions"] += 1
|
||||||
|
|||||||
@@ -354,6 +354,25 @@ class MessageManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"清除未读消息时发生错误: {e}")
|
logger.error(f"清除未读消息时发生错误: {e}")
|
||||||
|
|
||||||
|
async def clear_stream_unread_messages(self, stream_id: str):
|
||||||
|
"""清除指定聊天流的所有未读消息"""
|
||||||
|
try:
|
||||||
|
chat_manager = get_chat_manager()
|
||||||
|
chat_stream = chat_manager.get_stream(stream_id)
|
||||||
|
if not chat_stream:
|
||||||
|
logger.warning(f"clear_stream_unread_messages: 聊天流 {stream_id} 不存在")
|
||||||
|
return
|
||||||
|
|
||||||
|
context = chat_stream.context_manager.context
|
||||||
|
if hasattr(context, 'unread_messages') and context.unread_messages:
|
||||||
|
logger.debug(f"正在为流 {stream_id} 清除 {len(context.unread_messages)} 条未读消息")
|
||||||
|
context.unread_messages.clear()
|
||||||
|
else:
|
||||||
|
logger.debug(f"流 {stream_id} 没有需要清除的未读消息")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"清除流 {stream_id} 的未读消息时发生错误: {e}")
|
||||||
|
|
||||||
|
|
||||||
# 创建全局消息管理器实例
|
# 创建全局消息管理器实例
|
||||||
message_manager = MessageManager()
|
message_manager = MessageManager()
|
||||||
|
|||||||
@@ -828,6 +828,63 @@ class MessageHandler:
|
|||||||
data=f"这是一条小程序分享消息,可以根据来源,考虑使用对应解析工具\n{formatted_content}",
|
data=f"这是一条小程序分享消息,可以根据来源,考虑使用对应解析工具\n{formatted_content}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 检查是否是音乐分享 (QQ音乐类型)
|
||||||
|
if nested_data.get("view") == "music" and "com.tencent.music" in str(nested_data.get("app", "")):
|
||||||
|
meta = nested_data.get("meta", {})
|
||||||
|
music = meta.get("music", {})
|
||||||
|
if music:
|
||||||
|
tag = music.get("tag", "未知来源")
|
||||||
|
logger.debug(f"检测到【{tag}】音乐分享消息 (music view),开始提取信息")
|
||||||
|
|
||||||
|
title = music.get("title", "未知歌曲")
|
||||||
|
desc = music.get("desc", "未知艺术家")
|
||||||
|
jump_url = music.get("jumpUrl", "")
|
||||||
|
preview_url = music.get("preview", "")
|
||||||
|
|
||||||
|
artist = "未知艺术家"
|
||||||
|
song_title = title
|
||||||
|
|
||||||
|
if "网易云音乐" in tag:
|
||||||
|
artist = desc
|
||||||
|
elif "QQ音乐" in tag:
|
||||||
|
if " - " in title:
|
||||||
|
parts = title.split(" - ", 1)
|
||||||
|
song_title = parts[0]
|
||||||
|
artist = parts[1]
|
||||||
|
else:
|
||||||
|
artist = desc
|
||||||
|
|
||||||
|
formatted_content = (
|
||||||
|
f"这是一张来自【{tag}】的音乐分享卡片:\n"
|
||||||
|
f"歌曲: {song_title}\n"
|
||||||
|
f"艺术家: {artist}\n"
|
||||||
|
f"跳转链接: {jump_url}\n"
|
||||||
|
f"封面图: {preview_url}"
|
||||||
|
)
|
||||||
|
return Seg(type="text", data=formatted_content)
|
||||||
|
|
||||||
|
# 检查是否是新闻/图文分享 (网易云音乐可能伪装成这种)
|
||||||
|
elif nested_data.get("view") == "news" and "com.tencent.tuwen" in str(nested_data.get("app", "")):
|
||||||
|
meta = nested_data.get("meta", {})
|
||||||
|
news = meta.get("news", {})
|
||||||
|
if news and "网易云音乐" in news.get("tag", ""):
|
||||||
|
tag = news.get("tag")
|
||||||
|
logger.debug(f"检测到【{tag}】音乐分享消息 (news view),开始提取信息")
|
||||||
|
|
||||||
|
title = news.get("title", "未知歌曲")
|
||||||
|
desc = news.get("desc", "未知艺术家")
|
||||||
|
jump_url = news.get("jumpUrl", "")
|
||||||
|
preview_url = news.get("preview", "")
|
||||||
|
|
||||||
|
formatted_content = (
|
||||||
|
f"这是一张来自【{tag}】的音乐分享卡片:\n"
|
||||||
|
f"标题: {title}\n"
|
||||||
|
f"描述: {desc}\n"
|
||||||
|
f"跳转链接: {jump_url}\n"
|
||||||
|
f"封面图: {preview_url}"
|
||||||
|
)
|
||||||
|
return Seg(type="text", data=formatted_content)
|
||||||
|
|
||||||
# 如果没有提取到关键信息,返回None
|
# 如果没有提取到关键信息,返回None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -191,13 +191,21 @@ class PokeAction(BaseAction):
|
|||||||
|
|
||||||
display_name = user_name or user_id
|
display_name = user_name or user_id
|
||||||
|
|
||||||
|
# 构建戳一戳的参数
|
||||||
|
poke_args = {"user_id": str(user_id)}
|
||||||
|
if self.is_group and self.chat_stream.group_info:
|
||||||
|
poke_args["group_id"] = self.chat_stream.group_info.group_id
|
||||||
|
logger.info(f"在群聊 {poke_args['group_id']} 中执行戳一戳")
|
||||||
|
else:
|
||||||
|
logger.info("在私聊中执行戳一戳")
|
||||||
|
|
||||||
for i in range(times):
|
for i in range(times):
|
||||||
logger.info(f"正在向 {display_name} ({user_id}) 发送第 {i + 1}/{times} 次戳一戳...")
|
logger.info(f"正在向 {display_name} ({user_id}) 发送第 {i + 1}/{times} 次戳一戳...")
|
||||||
await self.send_command(
|
await self.send_command(
|
||||||
"SEND_POKE", args={"qq_id": user_id}, display_message=f"戳了戳 {display_name} ({i + 1}/{times})"
|
"send_poke", args=poke_args, display_message=f"戳了戳 {display_name} ({i + 1}/{times})"
|
||||||
)
|
)
|
||||||
# 添加一个小的延迟,以避免发送过快
|
# 添加一个延迟,避免因发送过快导致后续戳一戳失败
|
||||||
await asyncio.sleep(0.5)
|
await asyncio.sleep(1.5)
|
||||||
|
|
||||||
success_message = f"已向 {display_name} 发送 {times} 次戳一戳。"
|
success_message = f"已向 {display_name} 发送 {times} 次戳一戳。"
|
||||||
await self.store_action_info(
|
await self.store_action_info(
|
||||||
@@ -212,138 +220,126 @@ class SetEmojiLikeAction(BaseAction):
|
|||||||
# === 基本信息(必须填写)===
|
# === 基本信息(必须填写)===
|
||||||
action_name = "set_emoji_like"
|
action_name = "set_emoji_like"
|
||||||
action_description = "为某条已经存在的消息添加‘贴表情’回应(类似点赞),而不是发送新消息。可以在觉得某条消息非常有趣、值得赞同或者需要特殊情感回应时主动使用。"
|
action_description = "为某条已经存在的消息添加‘贴表情’回应(类似点赞),而不是发送新消息。可以在觉得某条消息非常有趣、值得赞同或者需要特殊情感回应时主动使用。"
|
||||||
activation_type = ActionActivationType.ALWAYS # 消息接收时激活(?)
|
activation_type = ActionActivationType.ALWAYS
|
||||||
chat_type_allow = ChatType.GROUP
|
chat_type_allow = ChatType.GROUP
|
||||||
parallel_action = True
|
parallel_action = True
|
||||||
|
|
||||||
# === 功能描述(必须填写)===
|
# === 功能描述(必须填写)===
|
||||||
# 从 qq_face 字典中提取所有表情名称用于提示
|
|
||||||
emoji_options = []
|
|
||||||
for name in qq_face.values():
|
|
||||||
match = re.search(r"\[表情:(.+?)\]", name)
|
|
||||||
if match:
|
|
||||||
emoji_options.append(match.group(1))
|
|
||||||
|
|
||||||
action_parameters = {
|
action_parameters = {
|
||||||
"set": "是否设置回应 (True/False)",
|
"set": "是否设置回应 (True/False)",
|
||||||
}
|
}
|
||||||
action_require = [
|
action_require = [
|
||||||
"当需要对一个已存在消息进行‘贴表情’回应时使用",
|
"当需要对一个已存在消息进行‘贴表情’回应时使用",
|
||||||
"这是一个对旧消息的操作,而不是发送新消息",
|
"这是一个对旧消息的操作,而不是发送新消息",
|
||||||
"如果你想发送一个新的表情包消息,请使用 'emoji' 动作",
|
|
||||||
]
|
]
|
||||||
llm_judge_prompt = """
|
llm_judge_prompt = """
|
||||||
判定是否需要使用贴表情动作的条件:
|
判定是否需要使用贴表情动作的条件:
|
||||||
1. 用户明确要求使用贴表情包
|
1. 这是一个适合表达强烈情绪的场合,例如非常有趣、赞同、惊讶等。
|
||||||
2. 这是一个适合表达强烈情绪的场合
|
2. 不要发送太多表情包,如果最近已经发送过表情包,请回答"否"。
|
||||||
3. 不要发送太多表情包,如果你已经发送过多个表情包则回答"否"
|
3. 仅在群聊中使用。
|
||||||
|
|
||||||
请回答"是"或"否"。
|
请回答"是"或"否"。
|
||||||
"""
|
"""
|
||||||
associated_types = ["text"]
|
associated_types = ["text"]
|
||||||
|
|
||||||
|
# 重新启用完整的表情库
|
||||||
|
emoji_options = []
|
||||||
|
for name in qq_face.values():
|
||||||
|
match = re.search(r"\[表情:(.+?)\]", name)
|
||||||
|
if match:
|
||||||
|
emoji_options.append(match.group(1))
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, str]:
|
async def execute(self) -> Tuple[bool, str]:
|
||||||
"""执行设置表情回应的动作"""
|
"""执行设置表情回应的动作"""
|
||||||
message_id = None
|
message_id = None
|
||||||
set_like = self.action_data.get("set", True)
|
set_like = self.action_data.get("set", True)
|
||||||
if self.has_action_message:
|
|
||||||
logger.debug(str(self.action_message))
|
if self.has_action_message and isinstance(self.action_message, dict):
|
||||||
if isinstance(self.action_message, dict):
|
message_id = self.action_message.get("message_id")
|
||||||
message_id = self.action_message.get("message_id")
|
|
||||||
logger.info(f"获取到的消息ID: {message_id}")
|
logger.info(f"获取到的消息ID: {message_id}")
|
||||||
else:
|
else:
|
||||||
logger.error("未提供消息ID")
|
logger.error("未提供有效的消息或消息ID")
|
||||||
await self.store_action_info(
|
await self.store_action_info(action_prompt_display="贴表情失败: 未提供消息ID", action_done=False)
|
||||||
action_build_into_prompt=True,
|
|
||||||
action_prompt_display=f"执行了set_emoji_like动作:{self.action_name},失败: 未提供消息ID",
|
|
||||||
action_done=False,
|
|
||||||
)
|
|
||||||
return False, "未提供消息ID"
|
return False, "未提供消息ID"
|
||||||
|
|
||||||
|
if not message_id:
|
||||||
|
logger.error("消息ID为空")
|
||||||
|
await self.store_action_info(action_prompt_display="贴表情失败: 消息ID为空", action_done=False)
|
||||||
|
return False, "消息ID为空"
|
||||||
|
|
||||||
available_models = llm_api.get_available_models()
|
available_models = llm_api.get_available_models()
|
||||||
if "utils_small" not in available_models:
|
if "utils_small" not in available_models:
|
||||||
logger.error("未找到 'utils_small' 模型配置,无法选择表情")
|
logger.error("未找到 'utils_small' 模型配置,无法选择表情")
|
||||||
return False, "表情选择功能配置错误"
|
return False, "表情选择功能配置错误"
|
||||||
|
|
||||||
model_to_use = available_models["utils_small"]
|
model_to_use = available_models["utils_small"]
|
||||||
|
|
||||||
# 获取最近的对话历史作为上下文
|
context_text = self.action_message.get("processed_plain_text", "")
|
||||||
context_text = ""
|
if not context_text:
|
||||||
if self.action_message:
|
logger.error("无法找到动作选择的原始消息文本")
|
||||||
context_text = self.action_message.get("processed_plain_text", "")
|
return False, "无法找到动作选择的原始消息文本"
|
||||||
else:
|
|
||||||
logger.error("无法找到动作选择的原始消息")
|
|
||||||
return False, "无法找到动作选择的原始消息"
|
|
||||||
|
|
||||||
prompt = (
|
prompt = (
|
||||||
f"根据以下这条消息,从列表中选择一个最合适的表情名称来回应这条消息。\n"
|
f"**任务:**\n"
|
||||||
f"消息内容: '{context_text}'\n"
|
f"根据以下消息,从“可用表情列表”中选择一个最合适的表情名称来回应。\n\n"
|
||||||
f"可用表情列表: {', '.join(self.emoji_options)}\n"
|
f"**规则(必须严格遵守):**\n"
|
||||||
f"你的任务是:只输出你选择的表情的名称,不要包含任何其他文字或标点。\n"
|
f"1. **只能**从下面的“可用表情列表”中选择一个表情名称。\n"
|
||||||
f"例如,如果觉得应该用'赞',就只输出'赞'。"
|
f"2. 你的回答**必须**只包含你选择的表情名称,**不能**有任何其他文字、标点、解释或空格。\n"
|
||||||
)
|
f"3. 你的回答**不能**包含 `[表情:]` 或 `[]` 等符号。\n\n"
|
||||||
|
f"**消息内容:**\n"
|
||||||
|
f"'{context_text}'\n\n"
|
||||||
|
f"**可用表情列表:**\n"
|
||||||
|
f"{', '.join(self.emoji_options)}\n\n"
|
||||||
|
f"**示例:**\n"
|
||||||
|
f"- 如果认为“赞”最合适,你的回答**必须**是:`赞`\n"
|
||||||
|
f"- 如果认为“笑哭”最合适,你的回答**必须**是:`笑哭`\n\n"
|
||||||
|
f"**你的回答:**"
|
||||||
|
)
|
||||||
|
|
||||||
success, response, _, _ = await llm_api.generate_with_model(
|
success, response, _, _ = await llm_api.generate_with_model(
|
||||||
prompt, model_config=model_to_use, request_type="plugin.set_emoji_like.select_emoji"
|
prompt, model_config=model_to_use, request_type="plugin.set_emoji_like.select_emoji"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not success or not response:
|
if not success or not response:
|
||||||
logger.error("二级LLM未能选择有效的表情。")
|
logger.error("表情选择模型未能返回有效的表情名称。")
|
||||||
return False, "无法找到合适的表情。"
|
return False, "无法选择合适的表情。"
|
||||||
|
|
||||||
chosen_emoji_name = response.strip()
|
chosen_emoji_name = response.strip()
|
||||||
logger.info(f"二级LLM选择的表情是: '{chosen_emoji_name}'")
|
logger.info(f"模型选择的表情是: '{chosen_emoji_name}'")
|
||||||
|
|
||||||
emoji_id = get_emoji_id(chosen_emoji_name)
|
emoji_id = get_emoji_id(chosen_emoji_name)
|
||||||
|
|
||||||
if not emoji_id:
|
if not emoji_id:
|
||||||
logger.error(f"二级LLM选择的表情 '{chosen_emoji_name}' 仍然无法匹配到有效的表情ID。")
|
logger.error(f"模型选择的表情 '{chosen_emoji_name}' 无法匹配到有效的表情ID。可能是模型违反了规则。")
|
||||||
await self.store_action_info(
|
|
||||||
action_build_into_prompt=True,
|
|
||||||
action_prompt_display=f"执行了set_emoji_like动作:{self.action_name},失败: 找不到表情: '{chosen_emoji_name}'",
|
|
||||||
action_done=False,
|
|
||||||
)
|
|
||||||
return False, f"找不到表情: '{chosen_emoji_name}'。"
|
|
||||||
|
|
||||||
# 4. 使用适配器API发送命令
|
|
||||||
if not message_id:
|
|
||||||
logger.error("未提供消息ID")
|
|
||||||
await self.store_action_info(
|
await self.store_action_info(
|
||||||
action_build_into_prompt=True,
|
action_prompt_display=f"贴表情失败: 找不到表情 '{chosen_emoji_name}'",
|
||||||
action_prompt_display=f"执行了set_emoji_like动作:{self.action_name},失败: 未提供消息ID",
|
|
||||||
action_done=False,
|
action_done=False,
|
||||||
)
|
)
|
||||||
return False, "未提供消息ID"
|
return False, f"找不到表情: '{chosen_emoji_name}'"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 使用适配器API发送贴表情命令
|
|
||||||
success = await self.send_command(
|
success = await self.send_command(
|
||||||
command_name="set_msg_emoji_like",
|
command_name="set_msg_emoji_like",
|
||||||
args={"message_id": message_id, "emoji_id": emoji_id, "set": set_like},
|
args={"message_id": message_id, "emoji_id": emoji_id, "set": set_like},
|
||||||
storage_message=False,
|
storage_message=False,
|
||||||
)
|
)
|
||||||
if success:
|
if success:
|
||||||
logger.info("设置表情回应成功")
|
display_message = f"贴上了表情: {chosen_emoji_name}"
|
||||||
|
logger.info(display_message)
|
||||||
await self.store_action_info(
|
await self.store_action_info(
|
||||||
action_build_into_prompt=True,
|
action_build_into_prompt=True,
|
||||||
action_prompt_display=f"执行了set_emoji_like动作,{chosen_emoji_name},设置表情回应: {emoji_id}, 是否设置: {set_like}",
|
action_prompt_display=display_message,
|
||||||
action_done=True,
|
action_done=True,
|
||||||
)
|
)
|
||||||
return True, "成功设置表情回应"
|
return True, "成功设置表情回应"
|
||||||
else:
|
else:
|
||||||
logger.error("设置表情回应失败")
|
logger.error("通过适配器设置表情回应失败")
|
||||||
await self.store_action_info(
|
await self.store_action_info(action_prompt_display="贴表情失败: 适配器返回失败", action_done=False)
|
||||||
action_build_into_prompt=True,
|
|
||||||
action_prompt_display=f"执行了set_emoji_like动作:{self.action_name},失败",
|
|
||||||
action_done=False,
|
|
||||||
)
|
|
||||||
return False, "设置表情回应失败"
|
return False, "设置表情回应失败"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"设置表情回应失败: {e}")
|
logger.error(f"设置表情回应时发生异常: {e}", exc_info=True)
|
||||||
await self.store_action_info(
|
await self.store_action_info(action_prompt_display=f"贴表情失败: {e}", action_done=False)
|
||||||
action_build_into_prompt=True,
|
|
||||||
action_prompt_display=f"执行了set_emoji_like动作:{self.action_name},失败: {e}",
|
|
||||||
action_done=False,
|
|
||||||
)
|
|
||||||
return False, f"设置表情回应失败: {e}"
|
return False, f"设置表情回应失败: {e}"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user