* 滚木 * feat: 重构核心架构,增强类型安全与插件管理 本次提交对核心模块进行了深度重构,引入 Pydantic 增强配置管理的类型安全性,并全面优化了插件管理系统。 主要变更详情: 1. 核心架构与配置 - 重构配置加载模块:引入 Pydantic 模型 (`core/config_models.py`),提供严格的配置项类型检查、验证及默认值管理。 - 统一模块结构:规范化模块导入路径,移除冗余的 `__init__.py` 文件,提升项目结构的清晰度。 - 性能优化:集成 Redis 缓存支持 (`RedisManager`),有效降低高频 API 调用开销,提升响应速度。 2. 插件系统升级 - 实现热重载机制:新增插件文件变更监听功能,支持开发过程中自动重载插件,提升开发效率。 - 优化生命周期管理:改进插件加载与卸载逻辑,支持精确卸载指定插件及其关联的命令、事件处理器和定时任务。 3. 功能特性增强 - 新增媒体 API:引入 `MediaAPI` 模块,封装图片、语音等富媒体资源的获取与处理接口。 - 完善权限体系:重构权限管理系统,实现管理员与操作员的分级控制,支持更细粒度的命令权限校验。 4. 代码质量与稳定性 - 全面类型修复:解决 `mypy` 静态类型检查发现的大量类型错误(包括 `CommandManager`、`EventFactory` 及 `Bot` API 签名不匹配问题)。 - 增强错误处理:优化消息处理管道的异常捕获机制,完善关键路径的日志记录,提升系统运行稳定性。 * feat: 添加测试用例并优化代码结构 refactor(permission_manager): 调整初始化顺序和逻辑 fix(admin_manager): 修复初始化逻辑和目录创建问题 feat(ws): 优化Bot实例初始化条件 feat(message): 增强MessageSegment功能并添加测试 feat(events): 支持字符串格式的消息解析 test: 添加核心功能测试用例 refactor(plugin_manager): 改进插件路径处理 style: 清理无用导入和代码 chore: 更新依赖项
203 lines
7.3 KiB
Python
203 lines
7.3 KiB
Python
"""
|
|
消息相关 API 模块
|
|
|
|
该模块定义了 `MessageAPI` Mixin 类,提供了所有与消息发送、撤回、
|
|
转发等相关的 OneBot v11 API 封装。
|
|
"""
|
|
from typing import Union, List, Dict, Any, TYPE_CHECKING
|
|
from .base import BaseAPI
|
|
|
|
if TYPE_CHECKING:
|
|
from models.message import MessageSegment
|
|
from models.events.base import OneBotEvent
|
|
|
|
|
|
class MessageAPI(BaseAPI):
|
|
"""
|
|
`MessageAPI` Mixin 类,提供了所有与消息操作相关的 API 方法。
|
|
"""
|
|
|
|
async def send_group_msg(self, group_id: int, message: Union[str, "MessageSegment", List["MessageSegment"]], auto_escape: bool = False) -> Dict[str, Any]:
|
|
"""
|
|
发送群消息。
|
|
|
|
Args:
|
|
group_id (int): 目标群组的群号。
|
|
message (Union[str, MessageSegment, List[MessageSegment]]): 要发送的消息内容。
|
|
可以是纯文本字符串、单个消息段对象或消息段列表。
|
|
auto_escape (bool, optional): 仅当 `message` 为字符串时有效,
|
|
是否对消息内容进行 CQ 码转义。Defaults to False.
|
|
|
|
Returns:
|
|
Dict[str, Any]: OneBot API 的响应数据。
|
|
"""
|
|
return await self.call_api(
|
|
"send_group_msg", {"group_id": group_id, "message": self._process_message(message), "auto_escape": auto_escape}
|
|
)
|
|
|
|
async def send_private_msg(self, user_id: int, message: Union[str, "MessageSegment", List["MessageSegment"]], auto_escape: bool = False) -> Dict[str, Any]:
|
|
"""
|
|
发送私聊消息。
|
|
|
|
Args:
|
|
user_id (int): 目标用户的 QQ 号。
|
|
message (Union[str, MessageSegment, List[MessageSegment]]): 要发送的消息内容。
|
|
auto_escape (bool, optional): 是否对消息内容进行 CQ 码转义。Defaults to False.
|
|
|
|
Returns:
|
|
Dict[str, Any]: OneBot API 的响应数据。
|
|
"""
|
|
return await self.call_api(
|
|
"send_private_msg", {"user_id": user_id, "message": self._process_message(message), "auto_escape": auto_escape}
|
|
)
|
|
|
|
async def send(self, event: "OneBotEvent", message: Union[str, "MessageSegment", List["MessageSegment"]], auto_escape: bool = False) -> Dict[str, Any]:
|
|
"""
|
|
智能发送消息。
|
|
|
|
该方法会根据传入的事件对象 `event` 自动判断是私聊还是群聊,
|
|
并调用相应的发送函数。如果事件是消息事件,则优先使用 `reply` 方法。
|
|
|
|
Args:
|
|
event (OneBotEvent): 触发该发送行为的事件对象。
|
|
message (Union[str, MessageSegment, List[MessageSegment]]): 要发送的消息内容。
|
|
auto_escape (bool, optional): 是否对消息内容进行 CQ 码转义。Defaults to False.
|
|
|
|
Returns:
|
|
Dict[str, Any]: OneBot API 的响应数据。
|
|
"""
|
|
# 如果是消息事件,直接调用 reply
|
|
if hasattr(event, "reply"):
|
|
await event.reply(message, auto_escape)
|
|
return {"status": "ok", "msg": "Replied via event.reply()"}
|
|
|
|
# 尝试从事件中获取 user_id 或 group_id
|
|
user_id = getattr(event, "user_id", None)
|
|
group_id = getattr(event, "group_id", None)
|
|
|
|
if group_id:
|
|
return await self.send_group_msg(group_id, message, auto_escape)
|
|
elif user_id:
|
|
return await self.send_private_msg(user_id, message, auto_escape)
|
|
|
|
return {"status": "failed", "msg": "Unknown message target"}
|
|
|
|
async def delete_msg(self, message_id: int) -> Dict[str, Any]:
|
|
"""
|
|
撤回一条消息。
|
|
|
|
Args:
|
|
message_id (int): 要撤回的消息的 ID。
|
|
|
|
Returns:
|
|
Dict[str, Any]: OneBot API 的响应数据。
|
|
"""
|
|
return await self.call_api("delete_msg", {"message_id": message_id})
|
|
|
|
async def get_msg(self, message_id: int) -> Dict[str, Any]:
|
|
"""
|
|
获取一条消息的详细信息。
|
|
|
|
Args:
|
|
message_id (int): 要获取的消息的 ID。
|
|
|
|
Returns:
|
|
Dict[str, Any]: OneBot API 的响应数据,包含消息详情。
|
|
"""
|
|
return await self.call_api("get_msg", {"message_id": message_id})
|
|
|
|
async def get_forward_msg(self, id: str) -> List[Dict[str, Any]]:
|
|
"""
|
|
获取合并转发消息的内容。
|
|
|
|
Args:
|
|
id (str): 合并转发消息的 ID。
|
|
|
|
Returns:
|
|
List[Dict[str, Any]]: 转发消息的节点列表。
|
|
"""
|
|
forward_data = await self.call_api("get_forward_msg", {"id": id})
|
|
nodes = forward_data.get("data")
|
|
|
|
if not isinstance(nodes, list):
|
|
# 兼容某些实现可能将节点放在 'messages' 键下
|
|
data = forward_data.get('data', {})
|
|
if isinstance(data, dict):
|
|
nodes = data.get('messages')
|
|
|
|
if not isinstance(nodes, list):
|
|
raise ValueError("在 get_forward_msg 响应中找不到消息节点列表")
|
|
|
|
return nodes
|
|
|
|
async def send_group_forward_msg(self, group_id: int, messages: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
"""
|
|
发送群聊合并转发消息。
|
|
|
|
Args:
|
|
group_id (int): 目标群组的群号。
|
|
messages (List[Dict[str, Any]]): 消息节点列表。
|
|
推荐使用 `bot.build_forward_node` 来构建节点。
|
|
|
|
Returns:
|
|
Dict[str, Any]: OneBot API 的响应数据。
|
|
"""
|
|
return await self.call_api("send_group_forward_msg", {"group_id": group_id, "messages": messages})
|
|
|
|
async def send_private_forward_msg(self, user_id: int, messages: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
"""
|
|
发送私聊合并转发消息。
|
|
|
|
Args:
|
|
user_id (int): 目标用户的 QQ 号。
|
|
messages (List[Dict[str, Any]]): 消息节点列表。
|
|
|
|
Returns:
|
|
Dict[str, Any]: OneBot API 的响应数据。
|
|
"""
|
|
return await self.call_api("send_private_forward_msg", {"user_id": user_id, "messages": messages})
|
|
|
|
def _process_message(self, message: Union[str, "MessageSegment", List["MessageSegment"]]) -> Union[str, List[Dict[str, Any]]]:
|
|
"""
|
|
内部方法:将消息内容处理成 OneBot API 可接受的格式。
|
|
|
|
- `str` -> `str`
|
|
- `MessageSegment` -> `List[Dict]`
|
|
- `List[MessageSegment]` -> `List[Dict]`
|
|
|
|
Args:
|
|
message: 原始消息内容。
|
|
|
|
Returns:
|
|
处理后的消息内容。
|
|
"""
|
|
if isinstance(message, str):
|
|
return message
|
|
|
|
# 避免循环导入,在运行时导入
|
|
from models.message import MessageSegment
|
|
|
|
if isinstance(message, MessageSegment):
|
|
return [self._segment_to_dict(message)]
|
|
|
|
if isinstance(message, list):
|
|
return [self._segment_to_dict(m) for m in message if isinstance(m, MessageSegment)]
|
|
|
|
return str(message)
|
|
|
|
def _segment_to_dict(self, segment: "MessageSegment") -> Dict[str, Any]:
|
|
"""
|
|
内部方法:将 `MessageSegment` 对象转换为字典。
|
|
|
|
Args:
|
|
segment (MessageSegment): 消息段对象。
|
|
|
|
Returns:
|
|
Dict[str, Any]: 符合 OneBot 规范的消息段字典。
|
|
"""
|
|
return {
|
|
"type": segment.type,
|
|
"data": segment.data
|
|
}
|
|
|