Revert "feat(chat): 通过动作参数实现专用的 @用户 功能"
This reverts commit e5117720c6.
This commit is contained in:
@@ -30,16 +30,16 @@ from __future__ import annotations
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
from typing import TYPE_CHECKING, Any, cast
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from mofox_wire import MessageEnvelope
|
from mofox_wire import MessageEnvelope, MessageRuntime
|
||||||
|
|
||||||
from src.chat.message_manager import message_manager
|
from src.chat.message_manager import message_manager
|
||||||
from src.chat.message_receive.storage import MessageStorage
|
from src.chat.message_receive.storage import MessageStorage
|
||||||
from src.chat.utils.utils import is_mentioned_bot_in_message
|
from src.chat.utils.utils import is_mentioned_bot_in_message
|
||||||
from src.common.data_models.database_data_model import DatabaseGroupInfo, DatabaseMessages, DatabaseUserInfo
|
from src.common.data_models.database_data_model import DatabaseGroupInfo, DatabaseMessages, DatabaseUserInfo
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
from src.config.config import Config, global_config
|
from src.config.config import global_config
|
||||||
from src.mood.mood_manager import mood_manager
|
from src.mood.mood_manager import mood_manager
|
||||||
from src.plugin_system.base import BaseCommand, EventType
|
from src.plugin_system.base import BaseCommand, EventType
|
||||||
from src.plugin_system.core import component_registry, event_manager, global_announcement_manager
|
from src.plugin_system.core import component_registry, event_manager, global_announcement_manager
|
||||||
@@ -48,10 +48,6 @@ if TYPE_CHECKING:
|
|||||||
from src.chat.message_receive.chat_stream import ChatStream
|
from src.chat.message_receive.chat_stream import ChatStream
|
||||||
from src.common.core_sink_manager import CoreSinkManager
|
from src.common.core_sink_manager import CoreSinkManager
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from mofox_wire import MessageRuntime
|
|
||||||
global_config: Config | None
|
|
||||||
|
|
||||||
logger = get_logger("message_handler")
|
logger = get_logger("message_handler")
|
||||||
|
|
||||||
# 项目根目录
|
# 项目根目录
|
||||||
@@ -59,13 +55,7 @@ PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
|
|||||||
|
|
||||||
def _check_ban_words(text: str, chat: "ChatStream", userinfo) -> bool:
|
def _check_ban_words(text: str, chat: "ChatStream", userinfo) -> bool:
|
||||||
"""检查消息是否包含过滤词"""
|
"""检查消息是否包含过滤词"""
|
||||||
if global_config:
|
for word in global_config.message_receive.ban_words:
|
||||||
for word in global_config.message_receive.ban_words:
|
|
||||||
if word in text:
|
|
||||||
chat_name = chat.group_info.group_name if chat.group_info else "私聊"
|
|
||||||
logger.info(f"[{chat_name}]{userinfo.user_nickname}:{text}")
|
|
||||||
logger.info(f"[过滤词识别]消息中含有{word},filtered")
|
|
||||||
return True
|
|
||||||
if word in text:
|
if word in text:
|
||||||
chat_name = chat.group_info.group_name if chat.group_info else "私聊"
|
chat_name = chat.group_info.group_name if chat.group_info else "私聊"
|
||||||
logger.info(f"[{chat_name}]{userinfo.user_nickname}:{text}")
|
logger.info(f"[{chat_name}]{userinfo.user_nickname}:{text}")
|
||||||
@@ -76,13 +66,7 @@ def _check_ban_words(text: str, chat: "ChatStream", userinfo) -> bool:
|
|||||||
|
|
||||||
def _check_ban_regex(text: str, chat: "ChatStream", userinfo) -> bool:
|
def _check_ban_regex(text: str, chat: "ChatStream", userinfo) -> bool:
|
||||||
"""检查消息是否匹配过滤正则表达式"""
|
"""检查消息是否匹配过滤正则表达式"""
|
||||||
if global_config:
|
for pattern in global_config.message_receive.ban_msgs_regex:
|
||||||
for pattern in global_config.message_receive.ban_msgs_regex:
|
|
||||||
if re.search(pattern, text):
|
|
||||||
chat_name = chat.group_info.group_name if chat.group_info else "私聊"
|
|
||||||
logger.info(f"[{chat_name}]{userinfo.user_nickname}:{text}")
|
|
||||||
logger.info(f"[正则表达式过滤]消息匹配到{pattern},filtered")
|
|
||||||
return True
|
|
||||||
if re.search(pattern, text):
|
if re.search(pattern, text):
|
||||||
chat_name = chat.group_info.group_name if chat.group_info else "私聊"
|
chat_name = chat.group_info.group_name if chat.group_info else "私聊"
|
||||||
logger.info(f"[{chat_name}]{userinfo.user_nickname}:{text}")
|
logger.info(f"[{chat_name}]{userinfo.user_nickname}:{text}")
|
||||||
@@ -116,13 +100,13 @@ class MessageHandler:
|
|||||||
self._message_manager_started = False
|
self._message_manager_started = False
|
||||||
self._core_sink_manager: CoreSinkManager | None = None
|
self._core_sink_manager: CoreSinkManager | None = None
|
||||||
self._shutting_down = False
|
self._shutting_down = False
|
||||||
self._runtime: "MessageRuntime | None" = None
|
self._runtime: MessageRuntime | None = None
|
||||||
|
|
||||||
def set_core_sink_manager(self, manager: "CoreSinkManager") -> None:
|
def set_core_sink_manager(self, manager: "CoreSinkManager") -> None:
|
||||||
"""设置 CoreSinkManager 引用"""
|
"""设置 CoreSinkManager 引用"""
|
||||||
self._core_sink_manager = manager
|
self._core_sink_manager = manager
|
||||||
|
|
||||||
def register_handlers(self, runtime: "MessageRuntime") -> None:
|
def register_handlers(self, runtime: MessageRuntime) -> None:
|
||||||
"""
|
"""
|
||||||
向 MessageRuntime 注册消息处理器和钩子
|
向 MessageRuntime 注册消息处理器和钩子
|
||||||
|
|
||||||
@@ -297,7 +281,7 @@ class MessageHandler:
|
|||||||
chat = await get_chat_manager().get_or_create_stream(
|
chat = await get_chat_manager().get_or_create_stream(
|
||||||
platform=platform,
|
platform=platform,
|
||||||
user_info=DatabaseUserInfo.from_dict(user_info) if user_info else None, # type: ignore
|
user_info=DatabaseUserInfo.from_dict(user_info) if user_info else None, # type: ignore
|
||||||
group_info=DatabaseGroupInfo.from_dict(cast(dict, group_info)) if group_info else None,
|
group_info=DatabaseGroupInfo.from_dict(group_info) if group_info else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 将消息信封转换为 DatabaseMessages
|
# 将消息信封转换为 DatabaseMessages
|
||||||
@@ -447,7 +431,7 @@ class MessageHandler:
|
|||||||
chat = await get_chat_manager().get_or_create_stream(
|
chat = await get_chat_manager().get_or_create_stream(
|
||||||
platform=platform,
|
platform=platform,
|
||||||
user_info=DatabaseUserInfo.from_dict(user_info) if user_info else None, # type: ignore
|
user_info=DatabaseUserInfo.from_dict(user_info) if user_info else None, # type: ignore
|
||||||
group_info=DatabaseGroupInfo.from_dict(cast(dict, group_info)) if group_info else None,
|
group_info=DatabaseGroupInfo.from_dict(group_info) if group_info else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 将消息信封转换为 DatabaseMessages
|
# 将消息信封转换为 DatabaseMessages
|
||||||
@@ -551,7 +535,7 @@ class MessageHandler:
|
|||||||
text = message.processed_plain_text or ""
|
text = message.processed_plain_text or ""
|
||||||
|
|
||||||
# 获取配置的命令前缀
|
# 获取配置的命令前缀
|
||||||
prefixes = global_config.command.command_prefixes if global_config else []
|
prefixes = global_config.command.command_prefixes
|
||||||
|
|
||||||
# 检查是否以任何前缀开头
|
# 检查是否以任何前缀开头
|
||||||
matched_prefix = None
|
matched_prefix = None
|
||||||
@@ -723,7 +707,7 @@ class MessageHandler:
|
|||||||
|
|
||||||
# 检查是否需要处理消息
|
# 检查是否需要处理消息
|
||||||
should_process_in_manager = True
|
should_process_in_manager = True
|
||||||
if global_config and group_info and str(group_info.group_id) in global_config.message_receive.mute_group_list:
|
if group_info and str(group_info.group_id) in global_config.message_receive.mute_group_list:
|
||||||
is_image_or_emoji = message.is_picid or message.is_emoji
|
is_image_or_emoji = message.is_picid or message.is_emoji
|
||||||
if not message.is_mentioned and not is_image_or_emoji:
|
if not message.is_mentioned and not is_image_or_emoji:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
@@ -747,7 +731,7 @@ class MessageHandler:
|
|||||||
|
|
||||||
# 情绪系统更新
|
# 情绪系统更新
|
||||||
try:
|
try:
|
||||||
if global_config and global_config.mood.enable_mood:
|
if global_config.mood.enable_mood:
|
||||||
interest_rate = message.interest_value or 0.0
|
interest_rate = message.interest_value or 0.0
|
||||||
logger.debug(f"开始更新情绪状态,兴趣度: {interest_rate:.2f}")
|
logger.debug(f"开始更新情绪状态,兴趣度: {interest_rate:.2f}")
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ class ChatterActionManager:
|
|||||||
log_prefix=log_prefix,
|
log_prefix=log_prefix,
|
||||||
shutting_down=shutting_down,
|
shutting_down=shutting_down,
|
||||||
plugin_config=plugin_config,
|
plugin_config=plugin_config,
|
||||||
action_message=action_message.flatten() if action_message else None,
|
action_message=action_message,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(f"创建Action实例成功: {action_name}")
|
logger.debug(f"创建Action实例成功: {action_name}")
|
||||||
@@ -252,7 +252,7 @@ class ChatterActionManager:
|
|||||||
# 检查目标消息是否为表情包消息以及配置是否允许回复表情包
|
# 检查目标消息是否为表情包消息以及配置是否允许回复表情包
|
||||||
if target_message and getattr(target_message, "is_emoji", False):
|
if target_message and getattr(target_message, "is_emoji", False):
|
||||||
# 如果是表情包消息且配置不允许回复表情包,则跳过回复
|
# 如果是表情包消息且配置不允许回复表情包,则跳过回复
|
||||||
if global_config and not getattr(global_config.chat, "allow_reply_to_emoji", True):
|
if not getattr(global_config.chat, "allow_reply_to_emoji", True):
|
||||||
logger.info(f"{log_prefix} 目标消息为表情包且配置不允许回复表情包,跳过回复")
|
logger.info(f"{log_prefix} 目标消息为表情包且配置不允许回复表情包,跳过回复")
|
||||||
return {"action_type": action_name, "success": True, "reply_text": "", "skip_reason": "emoji_not_allowed"}
|
return {"action_type": action_name, "success": True, "reply_text": "", "skip_reason": "emoji_not_allowed"}
|
||||||
|
|
||||||
@@ -288,7 +288,7 @@ class ChatterActionManager:
|
|||||||
reply_message=target_message,
|
reply_message=target_message,
|
||||||
action_data=action_data_with_mode,
|
action_data=action_data_with_mode,
|
||||||
available_actions=current_actions, # type: ignore
|
available_actions=current_actions, # type: ignore
|
||||||
enable_tool=global_config.tool.enable_tool if global_config else False,
|
enable_tool=global_config.tool.enable_tool,
|
||||||
request_type="chat.replyer",
|
request_type="chat.replyer",
|
||||||
from_plugin=False,
|
from_plugin=False,
|
||||||
)
|
)
|
||||||
@@ -325,7 +325,6 @@ class ChatterActionManager:
|
|||||||
thinking_id,
|
thinking_id,
|
||||||
[], # actions
|
[], # actions
|
||||||
should_quote_reply, # 传递should_quote_reply参数
|
should_quote_reply, # 传递should_quote_reply参数
|
||||||
action_data=action_data or {}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# 记录回复动作到目标消息
|
# 记录回复动作到目标消息
|
||||||
@@ -493,7 +492,6 @@ class ChatterActionManager:
|
|||||||
thinking_id,
|
thinking_id,
|
||||||
actions,
|
actions,
|
||||||
should_quote_reply: bool | None = None,
|
should_quote_reply: bool | None = None,
|
||||||
action_data: dict | None = None
|
|
||||||
) -> tuple[str, dict[str, float]]:
|
) -> tuple[str, dict[str, float]]:
|
||||||
"""
|
"""
|
||||||
发送并存储回复信息
|
发送并存储回复信息
|
||||||
@@ -511,39 +509,11 @@ class ChatterActionManager:
|
|||||||
Returns:
|
Returns:
|
||||||
Tuple[Dict[str, Any], str, Dict[str, float]]: 循环信息, 回复文本, 循环计时器
|
Tuple[Dict[str, Any], str, Dict[str, float]]: 循环信息, 回复文本, 循环计时器
|
||||||
"""
|
"""
|
||||||
# 提取回复文本
|
# 发送回复
|
||||||
reply_text = ""
|
with Timer("回复发送", cycle_timers):
|
||||||
for reply_seg in response_set:
|
reply_text = await self.send_response(
|
||||||
if isinstance(reply_seg, tuple) and len(reply_seg) >= 2:
|
chat_stream, response_set, loop_start_time, action_message, should_quote_reply
|
||||||
_, data = reply_seg
|
|
||||||
else:
|
|
||||||
data = str(reply_seg)
|
|
||||||
if isinstance(data, list):
|
|
||||||
data = "".join(map(str, data))
|
|
||||||
reply_text += data
|
|
||||||
|
|
||||||
# 检查是否需要@用户
|
|
||||||
at_user_id = action_data.get("at_user_id") if action_data else None
|
|
||||||
if at_user_id and chat_stream.group_info:
|
|
||||||
logger.info(f"检测到需要@用户: {at_user_id},将使用 SEND_AT_MESSAGE 命令发送")
|
|
||||||
from src.plugins.built_in.napcat_adapter.src.event_models import CommandType
|
|
||||||
command_payload = {
|
|
||||||
"name": CommandType.SEND_AT_MESSAGE.name,
|
|
||||||
"args": {
|
|
||||||
"qq_id": str(at_user_id),
|
|
||||||
"text": reply_text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await send_api.command_to_stream(
|
|
||||||
command=command_payload,
|
|
||||||
stream_id=chat_stream.stream_id
|
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
# 正常发送回复
|
|
||||||
with Timer("回复发送", cycle_timers):
|
|
||||||
reply_text = await self.send_response(
|
|
||||||
chat_stream, response_set, loop_start_time, action_message, should_quote_reply, action_data
|
|
||||||
)
|
|
||||||
|
|
||||||
# 存储reply action信息
|
# 存储reply action信息
|
||||||
person_info_manager = get_person_info_manager()
|
person_info_manager = get_person_info_manager()
|
||||||
@@ -588,7 +558,7 @@ class ChatterActionManager:
|
|||||||
return reply_text, cycle_timers
|
return reply_text, cycle_timers
|
||||||
|
|
||||||
async def send_response(
|
async def send_response(
|
||||||
self, chat_stream, reply_set, thinking_start_time, message_data, should_quote_reply: bool | None = None, action_data: dict | None = None
|
self, chat_stream, reply_set, thinking_start_time, message_data, should_quote_reply: bool | None = None
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
发送回复内容的具体实现
|
发送回复内容的具体实现
|
||||||
@@ -599,7 +569,6 @@ class ChatterActionManager:
|
|||||||
thinking_start_time: 思考开始时间
|
thinking_start_time: 思考开始时间
|
||||||
message_data: 消息数据
|
message_data: 消息数据
|
||||||
should_quote_reply: 是否应该引用回复原消息,None表示自动决定
|
should_quote_reply: 是否应该引用回复原消息,None表示自动决定
|
||||||
action_data: 动作数据,用于检查是否需要@
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: 完整的回复文本
|
str: 完整的回复文本
|
||||||
@@ -628,7 +597,6 @@ class ChatterActionManager:
|
|||||||
logger.debug(f"[send_response] message_data: {message_data}")
|
logger.debug(f"[send_response] message_data: {message_data}")
|
||||||
|
|
||||||
first_replied = False
|
first_replied = False
|
||||||
|
|
||||||
for reply_seg in reply_set:
|
for reply_seg in reply_set:
|
||||||
# 调试日志:验证reply_seg的格式
|
# 调试日志:验证reply_seg的格式
|
||||||
logger.debug(f"Processing reply_seg type: {type(reply_seg)}, content: {reply_seg}")
|
logger.debug(f"Processing reply_seg type: {type(reply_seg)}, content: {reply_seg}")
|
||||||
|
|||||||
@@ -38,10 +38,6 @@ from src.plugin_system.base.component_types import ActionInfo, EventType
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from src.chat.message_receive.chat_stream import ChatStream
|
from src.chat.message_receive.chat_stream import ChatStream
|
||||||
from src.config.config import APIAdapterConfig, Config
|
|
||||||
|
|
||||||
global_config: "Config"
|
|
||||||
model_config: "APIAdapterConfig"
|
|
||||||
|
|
||||||
logger = get_logger("replyer")
|
logger = get_logger("replyer")
|
||||||
|
|
||||||
@@ -123,10 +119,6 @@ def init_prompt():
|
|||||||
|
|
||||||
{action_descriptions}
|
{action_descriptions}
|
||||||
|
|
||||||
- **关于@功能的重要说明**:
|
|
||||||
- 如果你需要在一个回复中`@`某个用户,**请不要**在你的回复内容中直接输出`@`符号或`艾特`等文字。
|
|
||||||
- 你应该使用`reply`或`respond`动作中的`at_user_id`参数。只需要将目标的QQ号填入该参数,系统就会自动为你完成`@`操作。
|
|
||||||
|
|
||||||
## 任务
|
## 任务
|
||||||
|
|
||||||
*{chat_scene}*
|
*{chat_scene}*
|
||||||
|
|||||||
@@ -92,11 +92,10 @@ import traceback
|
|||||||
import uuid
|
import uuid
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from mofox_wire import MessageEnvelope, MessageInfoPayload, SegPayload
|
from mofox_wire import MessageEnvelope
|
||||||
from src.common.data_models.database_data_model import DatabaseUserInfo
|
from src.common.data_models.database_data_model import DatabaseUserInfo
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from src.common.data_models.database_data_model import DatabaseMessages
|
from src.common.data_models.database_data_model import DatabaseMessages
|
||||||
from src.config.config import Config
|
|
||||||
|
|
||||||
# 导入依赖
|
# 导入依赖
|
||||||
from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager
|
from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager
|
||||||
@@ -104,9 +103,6 @@ from src.chat.message_receive.uni_message_sender import HeartFCSender
|
|||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
assert global_config is not None
|
|
||||||
|
|
||||||
# 日志记录器
|
# 日志记录器
|
||||||
logger = get_logger("send_api")
|
logger = get_logger("send_api")
|
||||||
|
|
||||||
@@ -197,44 +193,32 @@ def _build_message_envelope(
|
|||||||
) -> MessageEnvelope:
|
) -> MessageEnvelope:
|
||||||
"""构建发送的 MessageEnvelope 数据结构"""
|
"""构建发送的 MessageEnvelope 数据结构"""
|
||||||
target_user_info = target_stream.user_info or bot_user_info
|
target_user_info = target_stream.user_info or bot_user_info
|
||||||
message_info: MessageInfoPayload = {
|
message_info: dict[str, Any] = {
|
||||||
"message_id": message_id,
|
"message_id": message_id,
|
||||||
"time": timestamp,
|
"time": timestamp,
|
||||||
"platform": target_stream.platform,
|
"platform": target_stream.platform,
|
||||||
"user_info": {
|
"user_info": {
|
||||||
"user_id": target_user_info.user_id or "",
|
"user_id": target_user_info.user_id,
|
||||||
"user_nickname": target_user_info.user_nickname or "",
|
"user_nickname": target_user_info.user_nickname,
|
||||||
"user_cardname": getattr(target_user_info, "user_cardname", "") or "",
|
"user_cardname": getattr(target_user_info, "user_cardname", None),
|
||||||
"platform": target_user_info.platform or "",
|
"platform": target_user_info.platform,
|
||||||
},
|
},
|
||||||
"group_info": None, # type: ignore
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if target_stream.group_info:
|
if target_stream.group_info:
|
||||||
message_info["group_info"] = {
|
message_info["group_info"] = {
|
||||||
"group_id": target_stream.group_info.group_id or "",
|
"group_id": target_stream.group_info.group_id,
|
||||||
"group_name": target_stream.group_info.group_name or "",
|
"group_name": target_stream.group_info.group_name,
|
||||||
"platform": target_stream.group_info.platform or "",
|
"platform": target_stream.group_info.platform,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Ensure message_segment is of the correct type
|
return {
|
||||||
seg_payload: SegPayload
|
"id": str(uuid.uuid4()),
|
||||||
if isinstance(message_segment, list):
|
|
||||||
seg_payload = {"type": "seglist", "data": message_segment}
|
|
||||||
elif isinstance(message_segment, dict) and "type" in message_segment and "data" in message_segment:
|
|
||||||
seg_payload = message_segment # type: ignore
|
|
||||||
else:
|
|
||||||
# Fallback for simple string content or other unexpected formats
|
|
||||||
seg_payload = {"type": "text", "data": str(message_segment)}
|
|
||||||
|
|
||||||
|
|
||||||
envelope: MessageEnvelope = {
|
|
||||||
"direction": "outgoing",
|
"direction": "outgoing",
|
||||||
"platform": target_stream.platform,
|
"platform": target_stream.platform,
|
||||||
"message_info": message_info,
|
"message_info": message_info,
|
||||||
"message_segment": seg_payload,
|
"message_segment": message_segment,
|
||||||
}
|
}
|
||||||
return envelope
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -273,18 +257,9 @@ async def _send_to_target(
|
|||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
message_id = f"send_api_{int(current_time * 1000)}"
|
message_id = f"send_api_{int(current_time * 1000)}"
|
||||||
|
|
||||||
# Use a safer way to get bot config
|
|
||||||
if not global_config:
|
|
||||||
logger.error("[SendAPI] Global config is not initialized!")
|
|
||||||
return False
|
|
||||||
bot_config = global_config.bot
|
|
||||||
if not bot_config:
|
|
||||||
logger.error("[SendAPI] Bot configuration not found!")
|
|
||||||
return False
|
|
||||||
|
|
||||||
bot_user_info = DatabaseUserInfo(
|
bot_user_info = DatabaseUserInfo(
|
||||||
user_id=str(bot_config.qq_account),
|
user_id=str(global_config.bot.qq_account),
|
||||||
user_nickname=bot_config.nickname,
|
user_nickname=global_config.bot.nickname,
|
||||||
platform=target_stream.platform,
|
platform=target_stream.platform,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -437,30 +412,6 @@ async def image_to_stream(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def at_user_to_stream(
|
|
||||||
user_id: str,
|
|
||||||
stream_id: str,
|
|
||||||
display_text: str = "",
|
|
||||||
storage_message: bool = False,
|
|
||||||
) -> bool:
|
|
||||||
"""向指定流发送@用户消息
|
|
||||||
这是一个特殊的、独立的段,通常用于在发送文本之前先发送@信息
|
|
||||||
|
|
||||||
Args:
|
|
||||||
user_id: 要@的用户的ID
|
|
||||||
stream_id: 聊天流ID
|
|
||||||
display_text: 在数据库中存储的显示文本
|
|
||||||
storage_message: 是否存储此消息段到数据库
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 是否发送成功
|
|
||||||
"""
|
|
||||||
at_segment = {"qq": user_id}
|
|
||||||
return await _send_to_target(
|
|
||||||
"at", at_segment, stream_id, display_text, typing=False, storage_message=storage_message, set_reply=False
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def command_to_stream(
|
async def command_to_stream(
|
||||||
command: str | dict,
|
command: str | dict,
|
||||||
stream_id: str,
|
stream_id: str,
|
||||||
@@ -567,18 +518,9 @@ async def adapter_command_to_stream(
|
|||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
message_id = f"adapter_cmd_{int(current_time * 1000)}"
|
message_id = f"adapter_cmd_{int(current_time * 1000)}"
|
||||||
|
|
||||||
# Use a safer way to get bot config
|
|
||||||
if not global_config:
|
|
||||||
logger.error("[SendAPI] Global config is not initialized!")
|
|
||||||
return {"status": "error", "message": "Global config is not initialized"}
|
|
||||||
bot_config = global_config.bot
|
|
||||||
if not bot_config:
|
|
||||||
logger.error("[SendAPI] Bot configuration not found!")
|
|
||||||
return {"status": "error", "message": "Bot configuration not found"}
|
|
||||||
|
|
||||||
bot_user_info = DatabaseUserInfo(
|
bot_user_info = DatabaseUserInfo(
|
||||||
user_id=str(bot_config.qq_account),
|
user_id=str(global_config.bot.qq_account),
|
||||||
user_nickname=bot_config.nickname,
|
user_nickname=global_config.bot.nickname,
|
||||||
platform=target_stream.platform,
|
platform=target_stream.platform,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class CoreActionsPlugin(BasePlugin):
|
|||||||
"""返回插件包含的组件列表"""
|
"""返回插件包含的组件列表"""
|
||||||
|
|
||||||
# --- 根据配置注册组件 ---
|
# --- 根据配置注册组件 ---
|
||||||
components = []
|
components: ClassVar = []
|
||||||
|
|
||||||
# 注册 reply 动作
|
# 注册 reply 动作
|
||||||
if self.get_config("components.enable_reply", True):
|
if self.get_config("components.enable_reply", True):
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ class ReplyAction(BaseAction):
|
|||||||
"target_message_id": "要回复的目标消息ID(必需,来自未读消息的 <m...> 标签)",
|
"target_message_id": "要回复的目标消息ID(必需,来自未读消息的 <m...> 标签)",
|
||||||
"content": "回复的具体内容(可选,由LLM生成)",
|
"content": "回复的具体内容(可选,由LLM生成)",
|
||||||
"should_quote_reply": "是否引用原消息(可选,true/false,默认false。群聊中回复较早消息或需要明确指向时使用true)",
|
"should_quote_reply": "是否引用原消息(可选,true/false,默认false。群聊中回复较早消息或需要明确指向时使用true)",
|
||||||
"at_user_id": "需要@的用户的QQ号(可选,string)。如果需要在回复中@某个用户,请提供此参数。",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 动作使用场景
|
# 动作使用场景
|
||||||
@@ -48,7 +47,6 @@ class ReplyAction(BaseAction):
|
|||||||
"私聊场景必须使用此动作(不支持 respond)",
|
"私聊场景必须使用此动作(不支持 respond)",
|
||||||
"群聊中需要明确回应某个特定用户或问题时使用",
|
"群聊中需要明确回应某个特定用户或问题时使用",
|
||||||
"关注单条消息的具体内容和上下文细节",
|
"关注单条消息的具体内容和上下文细节",
|
||||||
"如果回复时需要@某个用户,请在参数中提供'at_user_id'。",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# 关联类型
|
# 关联类型
|
||||||
@@ -85,7 +83,6 @@ class RespondAction(BaseAction):
|
|||||||
# 动作参数定义
|
# 动作参数定义
|
||||||
action_parameters: ClassVar = {
|
action_parameters: ClassVar = {
|
||||||
"content": "回复的具体内容(可选,由LLM生成)",
|
"content": "回复的具体内容(可选,由LLM生成)",
|
||||||
"at_user_id": "需要@的用户的QQ号(可选,string)。如果需要在回复中@某个用户,请提供此参数。",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 动作使用场景
|
# 动作使用场景
|
||||||
@@ -96,7 +93,6 @@ class RespondAction(BaseAction):
|
|||||||
"关注对话流程、话题走向和整体氛围",
|
"关注对话流程、话题走向和整体氛围",
|
||||||
"适合群聊中的自然对话流,无需精确指向特定消息",
|
"适合群聊中的自然对话流,无需精确指向特定消息",
|
||||||
"可以同时回应多个话题或参与者",
|
"可以同时回应多个话题或参与者",
|
||||||
"如果回复时需要@某个用户,请在参数中提供'at_user_id'。",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# 关联类型
|
# 关联类型
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
import base64
|
import base64
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, cast
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from mofox_wire import MessageBuilder
|
from mofox_wire import MessageBuilder
|
||||||
@@ -224,7 +224,7 @@ class MessageHandler:
|
|||||||
if not messages:
|
if not messages:
|
||||||
logger.warning("转发消息内容为空或获取失败")
|
logger.warning("转发消息内容为空或获取失败")
|
||||||
return None
|
return None
|
||||||
return await self.handle_forward_message(cast(list, messages))
|
return await self.handle_forward_message(messages)
|
||||||
case RealMessageType.json:
|
case RealMessageType.json:
|
||||||
return await self._handle_json_message(segment)
|
return await self._handle_json_message(segment)
|
||||||
case RealMessageType.file:
|
case RealMessageType.file:
|
||||||
@@ -331,13 +331,10 @@ class MessageHandler:
|
|||||||
{"type": seg.get("type", "text"), "data": seg.get("data", "")} for seg in reply_segments
|
{"type": seg.get("type", "text"), "data": seg.get("data", "")} for seg in reply_segments
|
||||||
] or [{"type": "text", "data": "[无法获取被引用的消息]"}]
|
] or [{"type": "text", "data": "[无法获取被引用的消息]"}]
|
||||||
|
|
||||||
return cast(
|
return {
|
||||||
SegPayload,
|
"type": "seglist",
|
||||||
{
|
"data": [{"type": "text", "data": prefix_text}, *brief_segments, {"type": "text", "data": suffix_text}],
|
||||||
"type": "seglist",
|
}
|
||||||
"data": [{"type": "text", "data": prefix_text}, *brief_segments, {"type": "text", "data": suffix_text}],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _handle_record_message(self, segment: dict) -> SegPayload | None:
|
async def _handle_record_message(self, segment: dict) -> SegPayload | None:
|
||||||
"""处理语音消息"""
|
"""处理语音消息"""
|
||||||
@@ -383,17 +380,14 @@ class MessageHandler:
|
|||||||
video_base64 = base64.b64encode(video_data).decode("utf-8")
|
video_base64 = base64.b64encode(video_data).decode("utf-8")
|
||||||
logger.debug(f"视频文件大小: {len(video_data) / (1024 * 1024):.2f} MB")
|
logger.debug(f"视频文件大小: {len(video_data) / (1024 * 1024):.2f} MB")
|
||||||
|
|
||||||
return cast(
|
return {
|
||||||
SegPayload,
|
"type": "video",
|
||||||
{
|
"data": {
|
||||||
"type": "video",
|
"base64": video_base64,
|
||||||
"data": {
|
"filename": Path(file_path).name,
|
||||||
"base64": video_base64,
|
"size_mb": len(video_data) / (1024 * 1024),
|
||||||
"filename": Path(file_path).name,
|
|
||||||
"size_mb": len(video_data) / (1024 * 1024),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
}
|
||||||
elif video_url:
|
elif video_url:
|
||||||
# URL下载处理
|
# URL下载处理
|
||||||
from ..video_handler import get_video_downloader
|
from ..video_handler import get_video_downloader
|
||||||
@@ -407,18 +401,15 @@ class MessageHandler:
|
|||||||
video_base64 = base64.b64encode(download_result["data"]).decode("utf-8")
|
video_base64 = base64.b64encode(download_result["data"]).decode("utf-8")
|
||||||
logger.debug(f"视频下载成功,大小: {len(download_result['data']) / (1024 * 1024):.2f} MB")
|
logger.debug(f"视频下载成功,大小: {len(download_result['data']) / (1024 * 1024):.2f} MB")
|
||||||
|
|
||||||
return cast(
|
return {
|
||||||
SegPayload,
|
"type": "video",
|
||||||
{
|
"data": {
|
||||||
"type": "video",
|
"base64": video_base64,
|
||||||
"data": {
|
"filename": download_result.get("filename", "video.mp4"),
|
||||||
"base64": video_base64,
|
"size_mb": len(download_result["data"]) / (1024 * 1024),
|
||||||
"filename": download_result.get("filename", "video.mp4"),
|
"url": video_url,
|
||||||
"size_mb": len(download_result["data"]) / (1024 * 1024),
|
|
||||||
"url": video_url,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
}
|
||||||
else:
|
else:
|
||||||
logger.warning("既没有有效的本地文件路径,也没有有效的视频URL")
|
logger.warning("既没有有效的本地文件路径,也没有有效的视频URL")
|
||||||
return None
|
return None
|
||||||
@@ -463,14 +454,14 @@ class MessageHandler:
|
|||||||
processed_message = handled_message
|
processed_message = handled_message
|
||||||
|
|
||||||
forward_hint = {"type": "text", "data": "这是一条转发消息:\n"}
|
forward_hint = {"type": "text", "data": "这是一条转发消息:\n"}
|
||||||
return cast(SegPayload, {"type": "seglist", "data": [forward_hint, processed_message]})
|
return {"type": "seglist", "data": [forward_hint, processed_message]}
|
||||||
|
|
||||||
async def _recursive_parse_image_seg(self, seg_data: SegPayload, to_image: bool) -> SegPayload:
|
async def _recursive_parse_image_seg(self, seg_data: SegPayload, to_image: bool) -> SegPayload:
|
||||||
# sourcery skip: merge-else-if-into-elif
|
# sourcery skip: merge-else-if-into-elif
|
||||||
if seg_data.get("type") == "seglist":
|
if seg_data.get("type") == "seglist":
|
||||||
new_seg_list = []
|
new_seg_list = []
|
||||||
for i_seg in seg_data.get("data", []):
|
for i_seg in seg_data.get("data", []):
|
||||||
parsed_seg = await self._recursive_parse_image_seg(cast(SegPayload, i_seg), to_image)
|
parsed_seg = await self._recursive_parse_image_seg(i_seg, to_image)
|
||||||
new_seg_list.append(parsed_seg)
|
new_seg_list.append(parsed_seg)
|
||||||
return {"type": "seglist", "data": new_seg_list}
|
return {"type": "seglist", "data": new_seg_list}
|
||||||
|
|
||||||
@@ -478,7 +469,7 @@ class MessageHandler:
|
|||||||
if seg_data.get("type") == "image":
|
if seg_data.get("type") == "image":
|
||||||
image_url = seg_data.get("data")
|
image_url = seg_data.get("data")
|
||||||
try:
|
try:
|
||||||
encoded_image = await get_image_base64(cast(str, image_url))
|
encoded_image = await get_image_base64(image_url)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"图片处理失败: {str(e)}")
|
logger.error(f"图片处理失败: {str(e)}")
|
||||||
return {"type": "text", "data": "[图片]"}
|
return {"type": "text", "data": "[图片]"}
|
||||||
@@ -486,7 +477,7 @@ class MessageHandler:
|
|||||||
if seg_data.get("type") == "emoji":
|
if seg_data.get("type") == "emoji":
|
||||||
image_url = seg_data.get("data")
|
image_url = seg_data.get("data")
|
||||||
try:
|
try:
|
||||||
encoded_image = await get_image_base64(cast(str, image_url))
|
encoded_image = await get_image_base64(image_url)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"图片处理失败: {str(e)}")
|
logger.error(f"图片处理失败: {str(e)}")
|
||||||
return {"type": "text", "data": "[表情包]"}
|
return {"type": "text", "data": "[表情包]"}
|
||||||
@@ -501,7 +492,7 @@ class MessageHandler:
|
|||||||
logger.debug(f"不处理类型: {seg_data.get('type')}")
|
logger.debug(f"不处理类型: {seg_data.get('type')}")
|
||||||
return seg_data
|
return seg_data
|
||||||
|
|
||||||
async def _handle_forward_message(self, message_list: list, layer: int) -> Tuple[Optional[SegPayload], int]:
|
async def _handle_forward_message(self, message_list: list, layer: int) -> Tuple[SegPayload | None, int]:
|
||||||
# sourcery skip: low-code-quality
|
# sourcery skip: low-code-quality
|
||||||
"""
|
"""
|
||||||
递归处理实际转发消息
|
递归处理实际转发消息
|
||||||
@@ -539,7 +530,7 @@ class MessageHandler:
|
|||||||
continue
|
continue
|
||||||
contents = sub_message_data.get("content")
|
contents = sub_message_data.get("content")
|
||||||
seg_data, count = await self._handle_forward_message(contents, layer + 1)
|
seg_data, count = await self._handle_forward_message(contents, layer + 1)
|
||||||
if not seg_data:
|
if seg_data is None:
|
||||||
continue
|
continue
|
||||||
image_count += count
|
image_count += count
|
||||||
head_tip: SegPayload = {
|
head_tip: SegPayload = {
|
||||||
@@ -604,7 +595,7 @@ class MessageHandler:
|
|||||||
"id": file_id,
|
"id": file_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
return cast(SegPayload, {"type": "file", "data": file_data})
|
return {"type": "file", "data": file_data}
|
||||||
|
|
||||||
async def _handle_json_message(self, segment: dict) -> SegPayload | None:
|
async def _handle_json_message(self, segment: dict) -> SegPayload | None:
|
||||||
"""
|
"""
|
||||||
@@ -632,7 +623,7 @@ class MessageHandler:
|
|||||||
# 从回声消息中提取文件信息
|
# 从回声消息中提取文件信息
|
||||||
file_info = self._extract_file_info_from_echo(nested_data)
|
file_info = self._extract_file_info_from_echo(nested_data)
|
||||||
if file_info:
|
if file_info:
|
||||||
return cast(SegPayload, {"type": "file", "data": file_info})
|
return {"type": "file", "data": file_info}
|
||||||
|
|
||||||
# 检查是否是QQ小程序分享消息
|
# 检查是否是QQ小程序分享消息
|
||||||
if "app" in nested_data and "com.tencent.miniapp" in str(nested_data.get("app", "")):
|
if "app" in nested_data and "com.tencent.miniapp" in str(nested_data.get("app", "")):
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast, TypedDict
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
||||||
|
|
||||||
from mofox_wire import MessageEnvelope, SegPayload, GroupInfoPayload, UserInfoPayload, MessageInfoPayload
|
from mofox_wire import MessageEnvelope, SegPayload, GroupInfoPayload, UserInfoPayload, MessageInfoPayload
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
@@ -19,12 +19,6 @@ if TYPE_CHECKING:
|
|||||||
from ....plugin import NapcatAdapter
|
from ....plugin import NapcatAdapter
|
||||||
|
|
||||||
|
|
||||||
class AtPayload(TypedDict, total=False):
|
|
||||||
"""@ 消息段数据"""
|
|
||||||
|
|
||||||
user_id: str
|
|
||||||
|
|
||||||
|
|
||||||
class SendHandler:
|
class SendHandler:
|
||||||
"""负责向 Napcat 发送消息"""
|
"""负责向 Napcat 发送消息"""
|
||||||
|
|
||||||
@@ -47,11 +41,10 @@ class SendHandler:
|
|||||||
return
|
return
|
||||||
|
|
||||||
message_segment = envelope.get("message_segment")
|
message_segment = envelope.get("message_segment")
|
||||||
segment: SegPayload
|
|
||||||
if isinstance(message_segment, list):
|
if isinstance(message_segment, list):
|
||||||
segment = {"type": "seglist", "data": message_segment}
|
segment: SegPayload = {"type": "seglist", "data": message_segment}
|
||||||
else:
|
else:
|
||||||
segment = message_segment or {} # type: ignore
|
segment = message_segment or {}
|
||||||
|
|
||||||
if segment:
|
if segment:
|
||||||
seg_type = segment.get("type")
|
seg_type = segment.get("type")
|
||||||
@@ -73,12 +66,11 @@ class SendHandler:
|
|||||||
处理普通消息发送
|
处理普通消息发送
|
||||||
"""
|
"""
|
||||||
logger.info("处理普通信息中")
|
logger.info("处理普通信息中")
|
||||||
message_info: MessageInfoPayload = envelope.get("message_info", {}) or {}
|
message_info: MessageInfoPayload = envelope.get("message_info", {})
|
||||||
message_segment: Union[SegPayload, List[SegPayload]] = envelope.get("message_segment") or cast(SegPayload, {})
|
message_segment: SegPayload = envelope.get("message_segment", {}) # type: ignore[assignment]
|
||||||
|
|
||||||
seg_data: SegPayload
|
|
||||||
if isinstance(message_segment, list):
|
if isinstance(message_segment, list):
|
||||||
seg_data = {"type": "seglist", "data": message_segment}
|
seg_data: SegPayload = {"type": "seglist", "data": message_segment}
|
||||||
else:
|
else:
|
||||||
seg_data = message_segment
|
seg_data = message_segment
|
||||||
|
|
||||||
@@ -89,9 +81,7 @@ class SendHandler:
|
|||||||
id_name: Optional[str] = None
|
id_name: Optional[str] = None
|
||||||
processed_message: list = []
|
processed_message: list = []
|
||||||
try:
|
try:
|
||||||
processed_message = await self.handle_seg_recursive(
|
processed_message = await self.handle_seg_recursive(seg_data, user_info or {})
|
||||||
seg_data, cast(UserInfoPayload, user_info if user_info is not None else {})
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"处理消息时发生错误: {e}")
|
logger.error(f"处理消息时发生错误: {e}")
|
||||||
return None
|
return None
|
||||||
@@ -133,10 +123,10 @@ class SendHandler:
|
|||||||
处理命令类
|
处理命令类
|
||||||
"""
|
"""
|
||||||
logger.debug("处理命令中")
|
logger.debug("处理命令中")
|
||||||
message_info: MessageInfoPayload = envelope.get("message_info", {}) or {}
|
message_info: Dict[str, Any] = envelope.get("message_info", {})
|
||||||
group_info: Optional[GroupInfoPayload] = message_info.get("group_info")
|
group_info: Optional[Dict[str, Any]] = message_info.get("group_info")
|
||||||
segment: SegPayload = envelope.get("message_segment", {}) # type: ignore
|
segment: SegPayload = envelope.get("message_segment", {}) # type: ignore[assignment]
|
||||||
seg_data: Dict[str, Any] = segment.get("data", {}) if isinstance(segment, dict) else {} # type: ignore
|
seg_data: Dict[str, Any] = segment.get("data", {}) if isinstance(segment, dict) else {}
|
||||||
command_name: Optional[str] = seg_data.get("name")
|
command_name: Optional[str] = seg_data.get("name")
|
||||||
try:
|
try:
|
||||||
args = seg_data.get("args", {})
|
args = seg_data.get("args", {})
|
||||||
@@ -157,10 +147,10 @@ class SendHandler:
|
|||||||
command, args_dict = self.handle_ai_voice_send_command(args, group_info)
|
command, args_dict = self.handle_ai_voice_send_command(args, group_info)
|
||||||
elif command_name == CommandType.SET_EMOJI_LIKE.name:
|
elif command_name == CommandType.SET_EMOJI_LIKE.name:
|
||||||
command, args_dict = self.handle_set_emoji_like_command(args)
|
command, args_dict = self.handle_set_emoji_like_command(args)
|
||||||
elif command_name == CommandType.SEND_LIKE.name:
|
|
||||||
command, args_dict = self.handle_send_like_command(args)
|
|
||||||
elif command_name == CommandType.SEND_AT_MESSAGE.name:
|
elif command_name == CommandType.SEND_AT_MESSAGE.name:
|
||||||
command, args_dict = self.handle_at_message_command(args, group_info)
|
command, args_dict = self.handle_at_message_command(args, group_info)
|
||||||
|
elif command_name == CommandType.SEND_LIKE.name:
|
||||||
|
command, args_dict = self.handle_send_like_command(args)
|
||||||
else:
|
else:
|
||||||
logger.error(f"未知命令: {command_name}")
|
logger.error(f"未知命令: {command_name}")
|
||||||
return
|
return
|
||||||
@@ -186,8 +176,8 @@ class SendHandler:
|
|||||||
处理适配器命令类 - 用于直接向Napcat发送命令并返回结果
|
处理适配器命令类 - 用于直接向Napcat发送命令并返回结果
|
||||||
"""
|
"""
|
||||||
logger.info("处理适配器命令中")
|
logger.info("处理适配器命令中")
|
||||||
segment: SegPayload = envelope.get("message_segment", {}) # type: ignore
|
segment: SegPayload = envelope.get("message_segment", {}) # type: ignore[assignment]
|
||||||
seg_data: Dict[str, Any] = segment.get("data", {}) if isinstance(segment, dict) else {} # type: ignore
|
seg_data: Dict[str, Any] = segment.get("data", {}) if isinstance(segment, dict) else {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
action = seg_data.get("action")
|
action = seg_data.get("action")
|
||||||
@@ -255,9 +245,6 @@ class SendHandler:
|
|||||||
if not text:
|
if not text:
|
||||||
return payload
|
return payload
|
||||||
new_payload = self.build_payload(payload, self.handle_text_message(str(text)), False)
|
new_payload = self.build_payload(payload, self.handle_text_message(str(text)), False)
|
||||||
elif seg_type == "at":
|
|
||||||
at_data: AtPayload = seg.get("data", {}) # type: ignore
|
|
||||||
new_payload = self.build_payload(payload, self.handle_at_message(at_data), False)
|
|
||||||
elif seg_type == "face":
|
elif seg_type == "face":
|
||||||
logger.warning("MoFox-Bot 发送了qq原生表情,暂时不支持")
|
logger.warning("MoFox-Bot 发送了qq原生表情,暂时不支持")
|
||||||
elif seg_type == "image":
|
elif seg_type == "image":
|
||||||
@@ -312,21 +299,50 @@ class SendHandler:
|
|||||||
payload.append(addon)
|
payload.append(addon)
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
async def handle_reply_message(self, message_id: str, user_info: UserInfoPayload) -> dict:
|
async def handle_reply_message(self, message_id: str, user_info: UserInfoPayload) -> dict | list:
|
||||||
"""处理回复消息"""
|
"""处理回复消息"""
|
||||||
logger.debug(f"开始处理回复消息,消息ID: {message_id}")
|
logger.debug(f"开始处理回复消息,消息ID: {message_id}")
|
||||||
reply_seg = {"type": "reply", "data": {"id": message_id}}
|
reply_seg = {"type": "reply", "data": {"id": message_id}}
|
||||||
|
|
||||||
|
# 检查是否启用引用艾特功能
|
||||||
|
if not config_api.get_plugin_config(self.plugin_config, "features.enable_reply_at", False):
|
||||||
|
logger.info("引用艾特功能未启用,仅发送普通回复")
|
||||||
|
return reply_seg
|
||||||
|
|
||||||
|
try:
|
||||||
|
msg_info_response = await self.send_message_to_napcat("get_msg", {"message_id": message_id})
|
||||||
|
logger.debug(f"获取消息 {message_id} 的详情响应: {msg_info_response}")
|
||||||
|
|
||||||
|
replied_user_id = None
|
||||||
|
if msg_info_response and msg_info_response.get("status") == "ok":
|
||||||
|
sender_info = msg_info_response.get("data", {}).get("sender")
|
||||||
|
if sender_info:
|
||||||
|
replied_user_id = sender_info.get("user_id")
|
||||||
|
|
||||||
|
if not replied_user_id:
|
||||||
|
logger.warning(f"无法获取消息 {message_id} 的发送者信息,跳过 @")
|
||||||
|
logger.debug(f"最终返回的回复段: {reply_seg}")
|
||||||
|
return reply_seg
|
||||||
|
|
||||||
|
if random.random() < config_api.get_plugin_config(self.plugin_config, "features.reply_at_rate", 0.5):
|
||||||
|
at_seg = {"type": "at", "data": {"qq": str(replied_user_id)}}
|
||||||
|
text_seg = {"type": "text", "data": {"text": " "}}
|
||||||
|
result_seg = [reply_seg, at_seg, text_seg]
|
||||||
|
logger.debug(f"最终返回的回复段: {result_seg}")
|
||||||
|
return result_seg
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"处理引用回复并尝试@时出错: {e}")
|
||||||
|
logger.debug(f"最终返回的回复段: {reply_seg}")
|
||||||
|
return reply_seg
|
||||||
|
|
||||||
|
logger.debug(f"最终返回的回复段: {reply_seg}")
|
||||||
return reply_seg
|
return reply_seg
|
||||||
|
|
||||||
def handle_text_message(self, message: str) -> dict:
|
def handle_text_message(self, message: str) -> dict:
|
||||||
"""处理文本消息"""
|
"""处理文本消息"""
|
||||||
return {"type": "text", "data": {"text": message}}
|
return {"type": "text", "data": {"text": message}}
|
||||||
|
|
||||||
def handle_at_message(self, at_data: AtPayload) -> dict:
|
|
||||||
"""处理@消息"""
|
|
||||||
user_id = at_data.get("user_id")
|
|
||||||
return {"type": "at", "data": {"qq": str(user_id)}}
|
|
||||||
|
|
||||||
def handle_image_message(self, encoded_image: str) -> dict:
|
def handle_image_message(self, encoded_image: str) -> dict:
|
||||||
"""处理图片消息"""
|
"""处理图片消息"""
|
||||||
return {
|
return {
|
||||||
@@ -354,8 +370,14 @@ class SendHandler:
|
|||||||
|
|
||||||
def handle_voice_message(self, encoded_voice: str) -> dict:
|
def handle_voice_message(self, encoded_voice: str) -> dict:
|
||||||
"""处理语音消息"""
|
"""处理语音消息"""
|
||||||
|
use_tts = False
|
||||||
|
if self.plugin_config:
|
||||||
|
use_tts = config_api.get_plugin_config(self.plugin_config, "voice.use_tts", False)
|
||||||
|
|
||||||
|
if not use_tts:
|
||||||
|
logger.warning("未启用语音消息处理")
|
||||||
|
return {}
|
||||||
if not encoded_voice:
|
if not encoded_voice:
|
||||||
logger.warning("接收到空的语音消息,跳过处理")
|
|
||||||
return {}
|
return {}
|
||||||
return {
|
return {
|
||||||
"type": "record",
|
"type": "record",
|
||||||
@@ -394,7 +416,7 @@ class SendHandler:
|
|||||||
"""处理删除消息命令"""
|
"""处理删除消息命令"""
|
||||||
return "delete_msg", {"message_id": args["message_id"]}
|
return "delete_msg", {"message_id": args["message_id"]}
|
||||||
|
|
||||||
def handle_ban_command(self, args: Dict[str, Any], group_info: Optional[GroupInfoPayload]) -> tuple[str, Dict[str, Any]]:
|
def handle_ban_command(self, args: Dict[str, Any], group_info: Optional[Dict[str, Any]]) -> tuple[str, Dict[str, Any]]:
|
||||||
"""处理封禁命令"""
|
"""处理封禁命令"""
|
||||||
duration: int = int(args["duration"])
|
duration: int = int(args["duration"])
|
||||||
user_id: int = int(args["qq_id"])
|
user_id: int = int(args["qq_id"])
|
||||||
@@ -414,7 +436,7 @@ class SendHandler:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_whole_ban_command(self, args: Dict[str, Any], group_info: Optional[GroupInfoPayload]) -> tuple[str, Dict[str, Any]]:
|
def handle_whole_ban_command(self, args: Dict[str, Any], group_info: Optional[Dict[str, Any]]) -> tuple[str, Dict[str, Any]]:
|
||||||
"""处理全体禁言命令"""
|
"""处理全体禁言命令"""
|
||||||
enable = args["enable"]
|
enable = args["enable"]
|
||||||
assert isinstance(enable, bool), "enable参数必须是布尔值"
|
assert isinstance(enable, bool), "enable参数必须是布尔值"
|
||||||
@@ -429,7 +451,7 @@ class SendHandler:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_kick_command(self, args: Dict[str, Any], group_info: Optional[GroupInfoPayload]) -> tuple[str, Dict[str, Any]]:
|
def handle_kick_command(self, args: Dict[str, Any], group_info: Optional[Dict[str, Any]]) -> tuple[str, Dict[str, Any]]:
|
||||||
"""处理群成员踢出命令"""
|
"""处理群成员踢出命令"""
|
||||||
user_id: int = int(args["qq_id"])
|
user_id: int = int(args["qq_id"])
|
||||||
group_id: int = int(group_info["group_id"]) if group_info and group_info.get("group_id") else 0
|
group_id: int = int(group_info["group_id"]) if group_info and group_info.get("group_id") else 0
|
||||||
@@ -446,7 +468,7 @@ class SendHandler:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_poke_command(self, args: Dict[str, Any], group_info: Optional[GroupInfoPayload]) -> tuple[str, Dict[str, Any]]:
|
def handle_poke_command(self, args: Dict[str, Any], group_info: Optional[Dict[str, Any]]) -> tuple[str, Dict[str, Any]]:
|
||||||
"""处理戳一戳命令"""
|
"""处理戳一戳命令"""
|
||||||
user_id: int = int(args["qq_id"])
|
user_id: int = int(args["qq_id"])
|
||||||
group_id: Optional[int] = None
|
group_id: Optional[int] = None
|
||||||
@@ -493,7 +515,31 @@ class SendHandler:
|
|||||||
{"user_id": user_id, "times": times},
|
{"user_id": user_id, "times": times},
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_ai_voice_send_command(self, args: Dict[str, Any], group_info: Optional[GroupInfoPayload]) -> tuple[str, Dict[str, Any]]:
|
def handle_at_message_command(self, args: Dict[str, Any], group_info: Optional[Dict[str, Any]]) -> tuple[str, Dict[str, Any]]:
|
||||||
|
"""处理艾特并发送消息命令"""
|
||||||
|
at_user_id = args.get("qq_id")
|
||||||
|
text = args.get("text")
|
||||||
|
|
||||||
|
if not at_user_id or not text:
|
||||||
|
raise ValueError("艾特消息命令缺少 qq_id 或 text 参数")
|
||||||
|
|
||||||
|
if not group_info or not group_info.get("group_id"):
|
||||||
|
raise ValueError("艾特消息命令必须在群聊上下文中使用")
|
||||||
|
|
||||||
|
message_payload = [
|
||||||
|
{"type": "at", "data": {"qq": str(at_user_id)}},
|
||||||
|
{"type": "text", "data": {"text": " " + str(text)}},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
"send_group_msg",
|
||||||
|
{
|
||||||
|
"group_id": group_info["group_id"],
|
||||||
|
"message": message_payload,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle_ai_voice_send_command(self, args: Dict[str, Any], group_info: Optional[Dict[str, Any]]) -> tuple[str, Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
处理AI语音发送命令的逻辑。
|
处理AI语音发送命令的逻辑。
|
||||||
并返回 NapCat 兼容的 (action, params) 元组。
|
并返回 NapCat 兼容的 (action, params) 元组。
|
||||||
@@ -519,30 +565,6 @@ class SendHandler:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_at_message_command(self, args: Dict[str, Any], group_info: Optional[GroupInfoPayload]) -> tuple[str, Dict[str, Any]]:
|
|
||||||
"""处理艾特并发送消息命令"""
|
|
||||||
at_user_id = args.get("qq_id")
|
|
||||||
text = args.get("text")
|
|
||||||
|
|
||||||
if not at_user_id or not text:
|
|
||||||
raise ValueError("艾特消息命令缺少 qq_id 或 text 参数")
|
|
||||||
|
|
||||||
if not group_info or not group_info.get("group_id"):
|
|
||||||
raise ValueError("艾特消息命令必须在群聊上下文中使用")
|
|
||||||
|
|
||||||
message_payload = [
|
|
||||||
{"type": "at", "data": {"qq": str(at_user_id)}},
|
|
||||||
{"type": "text", "data": {"text": " " + str(text)}},
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
"send_group_msg",
|
|
||||||
{
|
|
||||||
"group_id": group_info["group_id"],
|
|
||||||
"message": message_payload,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
async def send_message_to_napcat(self, action: str, params: dict, timeout: float = 20.0) -> dict:
|
async def send_message_to_napcat(self, action: str, params: dict, timeout: float = 20.0) -> dict:
|
||||||
"""通过 adapter API 发送到 napcat"""
|
"""通过 adapter API 发送到 napcat"""
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user