* 滚木 * 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: 更新依赖项 * refactor(handler): 移除TYPE_CHECKING并直接导入Bot类 简化类型注解,直接导入Bot类而非使用TYPE_CHECKING条件导入,提高代码可读性和维护性 * fix(command_manager): 修复插件卸载时元信息移除不精确的问题 修复 CommandManager 中 unload_plugin 方法移除插件元信息时使用 startswith 导致可能误删其他插件的问题,改为精确匹配 同时调整相关测试用例验证精确匹配行为 * refactor: 清理未使用的导入和更新文档结构 docs: 添加config_models.py到项目结构文档 docs: 调整数据目录位置到core/data下 docs: 更新权限管理器文档描述 * 文档更新 * 更新thpic插件 支持一次返回多张图 * feat: 添加测试覆盖率并修复相关问题 refactor(redis_manager): 移除冗余的ConnectionError处理 refactor(event_handler): 优化Bot类型注解 refactor(factory): 移除未使用的GroupCardNoticeEvent test: 添加全面的单元测试覆盖 - 添加test_import.py测试模块导入 - 添加test_debug.py测试插件加载调试 - 添加test_plugin_error.py测试错误处理 - 添加test_config_loader.py测试配置加载 - 添加test_redis_manager.py测试Redis管理 - 添加test_bot.py测试Bot功能 - 扩展test_models.py测试消息模型 - 添加test_plugin_manager_coverage.py测试插件管理 - 添加test_executor.py测试代码执行器 - 添加test_ws.py测试WebSocket - 添加test_api.py测试API接口 - 添加test_core_managers.py测试核心管理模块 fix(plugin_manager): 修复插件加载日志变量问题 覆盖率已到达86%(忽略插件) * 更新/help指令,现在会发送图片 * feat(help): 重构帮助系统为图片渲染模式 添加浏览器管理器和图片管理器,用于通过 Playwright 渲染帮助菜单为图片 重构命令管理器以支持图片缓存和同步功能 添加 HTML 模板用于帮助菜单渲染 * build: 更新依赖文件 requirements.txt * build: 更新依赖文件 * feat: 添加性能优化和架构文档,更新依赖和核心模块 refactor(browser_manager): 实现页面池机制以提升性能 refactor(image_manager): 添加模板缓存并集成页面池 refactor(bili_parser): 迁移到异步HTTP请求并实现会话复用 docs: 新增性能优化、架构设计和最佳实践文档 chore: 更新requirements.txt添加新依赖 * docs: 更新文档内容并优化语言风格 重构所有文档内容,使用更简洁直接的语言风格 更新架构、插件开发、部署等核心文档 优化代码示例和图表说明 统一术语和格式规范 * docs: 更新文档内容,简化语言并修正格式 - 简化插件开发指南中的描述,移除冗余内容 - 调整部署文档中的Python版本说明 - 优化最佳实践文档的措辞和格式 - 更新性能优化文档,删除不准确的数据 - 重构核心概念文档,使用更简洁的语言 - 修正README中的项目描述和技术栈说明 - 更新快速上手文档,简化安装步骤 - 调整事件流转文档的描述方式 - 简化架构文档内容 - 更新指令处理文档,添加参数注入示例 - 优化单例管理器文档的表述 --------- Co-authored-by: baby20162016 <2185823427@qq.com>
178 lines
5.8 KiB
Python
178 lines
5.8 KiB
Python
"""
|
||
NEO Bot 主程序入口
|
||
|
||
负责启动 WebSocket 连接,初始化插件系统,并提供热重载功能。
|
||
"""
|
||
import asyncio
|
||
import os
|
||
import sys
|
||
import time
|
||
from watchdog.observers import Observer
|
||
from watchdog.events import FileSystemEventHandler
|
||
|
||
# 尝试使用高性能事件循环
|
||
try:
|
||
if sys.platform == 'win32':
|
||
# winloop 与 Playwright 存在兼容性问题 (不支持 startupinfo),暂时禁用
|
||
# import winloop
|
||
# asyncio.set_event_loop_policy(winloop.EventLoopPolicy())
|
||
# print("已启用 winloop 高性能事件循环")
|
||
print("Windows 平台检测到 Playwright,已自动禁用 winloop 以确保兼容性")
|
||
else:
|
||
import uvloop
|
||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||
print("已启用 uvloop 高性能事件循环")
|
||
except ImportError:
|
||
print("未检测到高性能事件循环库 (uvloop/winloop),将使用默认事件循环")
|
||
|
||
# 初始化日志系统,必须在其他 core 模块导入之前执行
|
||
from core.utils.logger import logger
|
||
|
||
from core.managers.admin_manager import admin_manager
|
||
from core.ws import WS
|
||
from core.managers import plugin_manager, matcher
|
||
from core.managers.redis_manager import redis_manager
|
||
from core.managers.browser_manager import browser_manager
|
||
from core.utils.executor import run_in_thread_pool, initialize_executor
|
||
from core.config_loader import global_config as config
|
||
|
||
# 将项目根目录添加到 sys.path
|
||
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||
sys.path.insert(0, ROOT_DIR)
|
||
|
||
|
||
# 获取插件目录的绝对路径
|
||
PLUGIN_DIR = os.path.join(ROOT_DIR, "plugins")
|
||
|
||
|
||
async def reload_plugin_and_sync_help(module_name: str):
|
||
"""
|
||
重载插件并同步帮助图片
|
||
"""
|
||
await run_in_thread_pool(plugin_manager.reload_plugin, module_name)
|
||
# 插件重载后,重新生成帮助图片
|
||
await matcher.sync_help_pic()
|
||
|
||
|
||
class PluginReloadHandler(FileSystemEventHandler):
|
||
"""
|
||
文件变更处理器,用于热重载插件
|
||
|
||
继承自 watchdog.events.FileSystemEventHandler,
|
||
监听 plugins 目录下的文件变化,并触发插件重载。
|
||
"""
|
||
def __init__(self, loop: asyncio.AbstractEventLoop):
|
||
"""
|
||
初始化处理器
|
||
|
||
设置冷却时间,并保存主事件循环的引用。
|
||
"""
|
||
self.loop = loop
|
||
self.last_reload_time = 0
|
||
self.cooldown = 1.0 # 冷却时间,防止短时间内多次重载
|
||
|
||
def on_any_event(self, file_system_event):
|
||
"""
|
||
处理所有文件事件
|
||
|
||
:param file_system_event: watchdog 事件对象
|
||
"""
|
||
if file_system_event.is_directory:
|
||
return
|
||
|
||
src_path = file_system_event.src_path
|
||
|
||
# 只监控 py 文件
|
||
if not src_path.endswith(".py"):
|
||
return
|
||
|
||
# 过滤掉一些临时文件
|
||
if "__pycache__" in src_path or not src_path.startswith(PLUGIN_DIR):
|
||
return
|
||
|
||
# 简单的防抖动
|
||
current_time = time.time()
|
||
if current_time - self.last_reload_time < self.cooldown:
|
||
return
|
||
|
||
self.last_reload_time = current_time
|
||
|
||
# 从文件路径解析出模块名
|
||
# 例如: C:\path\to\project\plugins\bili_parser.py -> plugins.bili_parser
|
||
relative_path = os.path.relpath(src_path, ROOT_DIR)
|
||
module_name = os.path.splitext(relative_path.replace(os.sep, '.'))[0]
|
||
|
||
logger.info(f"检测到文件变更: {src_path}")
|
||
logger.info(f"准备重载插件: {module_name}...")
|
||
|
||
try:
|
||
# 使用线程安全的方式在主事件循环中运行异步的插件重载函数
|
||
asyncio.run_coroutine_threadsafe(run_in_thread_pool(plugin_manager.reload_plugin, module_name), self.loop)
|
||
logger.success(f"插件 {module_name} 重载任务已提交")
|
||
except Exception as e:
|
||
logger.exception(f"重载失败: {e}")
|
||
|
||
|
||
@logger.catch
|
||
async def main():
|
||
"""
|
||
主函数
|
||
|
||
1. 启动文件监控(热重载)
|
||
2. 初始化 WebSocket 客户端
|
||
3. 建立连接并保持运行
|
||
"""
|
||
# 插件加载已移至 core.managers.__init__.py 中自动执行
|
||
|
||
# 初始化 Redis 连接
|
||
await redis_manager.initialize()
|
||
|
||
# 同步帮助图片
|
||
await matcher.sync_help_pic()
|
||
|
||
# 初始化管理员管理器
|
||
await admin_manager.initialize()
|
||
|
||
# 初始化浏览器管理器 (使用页面池)
|
||
await browser_manager.init_pool(size=3)
|
||
|
||
# 启动文件监控
|
||
# 监控 plugins 目录
|
||
plugin_path = os.path.join(os.path.dirname(__file__), "plugins")
|
||
|
||
# 获取当前事件循环并传递给处理器
|
||
loop = asyncio.get_running_loop()
|
||
event_handler = PluginReloadHandler(loop)
|
||
observer = Observer()
|
||
|
||
if os.path.exists(plugin_path):
|
||
observer.schedule(event_handler, plugin_path, recursive=True)
|
||
observer.start()
|
||
logger.info(f"已启动插件热重载监控: {plugin_path}")
|
||
else:
|
||
logger.warning(f"插件目录不存在 {plugin_path}")
|
||
|
||
try:
|
||
# 初始化代码执行器
|
||
code_executor = initialize_executor(config)
|
||
|
||
websocket_client = WS(code_executor=code_executor)
|
||
|
||
# 启动代码执行器的后台 worker
|
||
logger.debug("[Main] 检查是否需要启动代码执行 Worker...")
|
||
if code_executor and code_executor.docker_client:
|
||
logger.info("[Main] Docker 连接成功,正在启动代码执行 Worker...")
|
||
asyncio.create_task(code_executor.worker())
|
||
else:
|
||
logger.warning("[Main] 未启动代码执行 Worker,因为 Docker 客户端未初始化或连接失败。")
|
||
|
||
await websocket_client.connect()
|
||
finally:
|
||
if observer.is_alive():
|
||
observer.stop()
|
||
observer.join()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
asyncio.run(main())
|