* 滚木

* 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: 更新依赖项
This commit is contained in:
镀铬酸钾
2026-01-09 00:20:56 +08:00
committed by GitHub
parent 6d7dfc179d
commit fa81229f6f
42 changed files with 1461 additions and 697 deletions

View File

@@ -6,7 +6,7 @@
"""
from dataclasses import dataclass
from typing import Any, Dict
from typing import Any, Dict, Optional, List
@dataclass(slots=True)
@@ -23,7 +23,7 @@ class MessageSegment:
data: Dict[str, Any]
@property
def text(self) -> str:
def plain_text(self) -> str:
"""
当消息段类型为 'text' 时,快速获取其文本内容。
@@ -32,6 +32,19 @@ class MessageSegment:
"""
return self.data.get("text", "") if self.type == "text" else ""
@staticmethod
def text(text: str) -> "MessageSegment":
"""
创建一个文本消息段。
Args:
text (str): 文本内容。
Returns:
MessageSegment: 一个类型为 'text' 的消息段对象。
"""
return MessageSegment(type="text", data={"text": text})
@property
def image_url(self) -> str:
"""
@@ -76,7 +89,7 @@ class MessageSegment:
return self.data.get("file", "")
return ""
def is_at(self, user_id: int = None) -> bool:
def is_at(self, user_id: Optional[int] = None) -> bool:
"""
检查当前消息段是否是一个 'at' (提及) 消息段。
@@ -93,16 +106,52 @@ class MessageSegment:
return True
return str(self.data.get("qq")) == str(user_id)
def __str__(self):
"""
返回消息段的 CQ 码字符串表示。
"""
if self.type == "text":
return self.data.get("text", "")
params = ",".join([f"{k}={v}" for k, v in self.data.items()])
if params:
return f"[CQ:{self.type},{params}]"
return f"[CQ:{self.type}]"
def __repr__(self):
"""
返回消息段对象的字符串表示形式,便于调试。
"""
return f"[MS:{self.type}:{self.data}]"
def __add__(self, other: Any) -> "List[MessageSegment]":
"""
支持消息段相加,返回消息段列表。
"""
if isinstance(other, MessageSegment):
return [self, other]
elif isinstance(other, str):
return [self, MessageSegment.text(other)]
elif isinstance(other, list):
return [self] + other
return NotImplemented
def __radd__(self, other: Any) -> "List[MessageSegment]":
"""
支持反向相加。
"""
if isinstance(other, MessageSegment):
return [other, self]
elif isinstance(other, str):
return [MessageSegment.text(other), self]
elif isinstance(other, list):
return other + [self]
return NotImplemented
# --- 快捷构造方法 ---
@staticmethod
def text(text: str) -> "MessageSegment": # noqa: F811
def from_text(text: str) -> "MessageSegment":
"""
创建一个文本消息段。
@@ -115,7 +164,7 @@ class MessageSegment:
return MessageSegment(type="text", data={"text": text})
@staticmethod
def at(user_id: int | str, name: str = None) -> "MessageSegment":
def at(user_id: int | str, name: Optional[str] = None) -> "MessageSegment":
"""
创建一个 @某人 的消息段。
@@ -132,7 +181,7 @@ class MessageSegment:
return MessageSegment(type="at", data=data)
@staticmethod
def image(file: str, image_type: str = None, cache: bool = True, proxy: bool = True, timeout: int = None, sub_type: int = None) -> "MessageSegment":
def image(file: str, image_type: Optional[str] = None, cache: bool = True, proxy: bool = True, timeout: Optional[int] = None, sub_type: Optional[int] = None) -> "MessageSegment":
"""
创建一个图片消息段。
@@ -194,7 +243,7 @@ class MessageSegment:
"""
return MessageSegment(type="xml", data={"data": data})
@staticmethod
def share(url: str, title: str, content: str = None, image: str = None) -> "MessageSegment":
def share(url: str, title: str, content: Optional[str] = None, image: Optional[str] = None) -> "MessageSegment":
"""
创建一个分享消息段。
@@ -227,7 +276,7 @@ class MessageSegment:
"""
return MessageSegment(type="music", data={"type": type, "id": id})
@staticmethod
def music_custom(url: str, audio: str, title: str, content: str = None, image: str = None) -> "MessageSegment":
def music_custom(url: str, audio: str, title: str, content: Optional[str] = None, image: Optional[str] = None) -> "MessageSegment":
"""
创建一个自定义音乐消息段。
@@ -248,7 +297,7 @@ class MessageSegment:
data["image"] = image
return MessageSegment(type="music", data={"type": "custom", **data})
@staticmethod
def record(file: str, magic: bool = False, cache: bool = True, proxy: bool = True, timeout: int = None) -> "MessageSegment":
def record(file: str, magic: bool = False, cache: bool = True, proxy: bool = True, timeout: Optional[int] = None) -> "MessageSegment":
"""
创建一个语音消息段。
@@ -267,7 +316,7 @@ class MessageSegment:
data["timeout"] = str(timeout)
return MessageSegment(type="record", data=data)
@staticmethod
def video(file: str, cover: str = None, c: int = 2) -> "MessageSegment":
def video(file: str, cover: Optional[str] = None, c: int = 2) -> "MessageSegment":
"""
创建一个视频消息段。
@@ -297,17 +346,17 @@ class MessageSegment:
return MessageSegment(type="file", data={"file": file})
@staticmethod
def reply(message_id: str) -> "MessageSegment":
def reply(message_id: str | int) -> "MessageSegment":
"""
创建一个回复消息段。
Args:
message_id (str): 被回复的消息 ID。
message_id (str | int): 被回复的消息 ID。
Returns:
MessageSegment: 一个类型为 'reply' 的消息段对象。
"""
return MessageSegment(type="reply", data={"id": message_id})
return MessageSegment(type="reply", data={"id": str(message_id)})
@staticmethod
def rps() -> "MessageSegment":