diff --git a/README.md b/README.md index 05074fe..4513ddd 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ * **OneBot 11 标准支持**:完整支持 OneBot 11 的消息、通知、请求和元事件。 * **类型安全**:基于 `dataclasses` 的强类型事件模型,开发体验更佳。 * **插件系统**:轻量级的装饰器风格插件系统,支持指令 (`@matcher.command`) 和事件监听 (`@matcher.on_notice`, `@matcher.on_request`)。 +* **🔥 热重载支持**:内置文件监控,修改 `base_plugins` 下的代码自动重载,无需重启,极大提升调试效率。 * **异步核心**:基于 `asyncio` 和 `websockets` 的高性能异步核心。 * **自动重连**:内置 WebSocket 断线重连机制。 @@ -14,7 +15,7 @@ ``` NEO/ -├── base_plugins/ # 基础插件目录,新建插件文件即可自动加载 +├── base_plugins/ # 基础插件目录,新建插件文件即可自动加载(支持热重载) │ └── echo.py # 示例插件:实现 /echo 指令 ├── core/ # 核心框架代码 │ ├── bot.py # Bot API 封装,提供 send_group_msg 等方法 @@ -26,7 +27,7 @@ NEO/ │ ├── message.py # 消息段定义 (MessageSegment) │ └── sender.py # 发送者定义 (Sender) ├── config.toml # 配置文件 -├── main.py # 启动入口 +├── main.py # 启动入口(包含热重载监控) └── requirements.txt # 项目依赖 ``` @@ -65,6 +66,14 @@ python main.py ## 🛠️ 开发指南 +### 🔥 热重载调试 + +项目集成了 `watchdog` 文件监控。在开发过程中,你只需要: +1. 保持 `main.py` 运行。 +2. 修改或新建 `base_plugins` 目录下的 `.py` 插件文件。 +3. 保存文件。 +4. 控制台会自动提示 `[HotReload] 插件重载完成`,新的逻辑立即生效。 + ### 创建新插件 在 `base_plugins` 目录下创建一个新的 `.py` 文件(例如 `my_plugin.py`),框架会自动加载它。 diff --git a/base_plugins/__init__.py b/base_plugins/__init__.py index 3937c7a..c0098c3 100644 --- a/base_plugins/__init__.py +++ b/base_plugins/__init__.py @@ -1,6 +1,7 @@ import importlib import os import pkgutil +import sys def load_all_plugins(): @@ -14,11 +15,17 @@ def load_all_plugins(): full_module_name = f"{package_name}.{module_name}" try: - importlib.import_module(full_module_name) + if full_module_name in sys.modules: + importlib.reload(sys.modules[full_module_name]) + action = "重载" + else: + importlib.import_module(full_module_name) + action = "加载" + type_str = "包" if is_pkg else "文件" - print(f" [{type_str}] 成功加载: {module_name}") + print(f" [{type_str}] 成功{action}: {module_name}") except Exception as e: - print(f" 加载插件 {module_name} 失败: {e}") + print(f" {action if 'action' in locals() else '加载'}插件 {module_name} 失败: {e}") load_all_plugins() diff --git a/base_plugins/echo.py b/base_plugins/echo.py index c38aada..a91551d 100644 --- a/base_plugins/echo.py +++ b/base_plugins/echo.py @@ -23,3 +23,18 @@ async def handle_echo(bot: Bot, event: MessageEvent, args: list[str]): reply_msg = " ".join(args) await event.reply(reply_msg) + +@matcher.command("poke") +async def handle_poke(bot: Bot, event: MessageEvent, args: list[str]): + """ + 处理 echo 指令,原样回复用户输入的内容 + + :param bot: Bot 实例 + :param event: 消息事件对象 + :param args: 指令参数列表 + """ + + await bot.call_api("group_poke", { + "group_id": event.group_id, + "user_id": event.user_id + }) \ No newline at end of file diff --git a/main.py b/main.py index 11b79ca..0e12c54 100644 --- a/main.py +++ b/main.py @@ -2,17 +2,82 @@ NEO Bot 主程序入口 """ import asyncio +import os +import time + +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler import base_plugins # noqa: F401 别动这里是加载插件的 from core import WS +class PluginReloadHandler(FileSystemEventHandler): + """ + 文件变更处理器,用于热重载插件 + """ + def __init__(self): + self.last_reload_time = 0 + self.cooldown = 1.0 # 冷却时间,防止短时间内多次重载 + + def on_any_event(self, event): + """ + 处理所有文件事件 + """ + if event.is_directory: + return + + # 只监控 py 文件 + if not event.src_path.endswith(".py"): + return + + # 过滤掉一些临时文件 + if "__pycache__" in event.src_path: + return + + # 简单的防抖动 + current_time = time.time() + if current_time - self.last_reload_time < self.cooldown: + return + + self.last_reload_time = current_time + + print(f"\n[HotReload] 检测到文件变更: {event.src_path}") + print("[HotReload] 正在重载插件...") + + try: + # 重新扫描并加载插件 + base_plugins.load_all_plugins() + print("[HotReload] 插件重载完成") + except Exception as e: + print(f"[HotReload] 重载失败: {e}") + + async def main(): """ 主函数,启动 WebSocket 连接 """ - bot = WS() - await bot.connect() + # 启动文件监控 + # 监控 base_plugins 目录 + plugin_path = os.path.join(os.path.dirname(__file__), "base_plugins") + + event_handler = PluginReloadHandler() + observer = Observer() + + if os.path.exists(plugin_path): + observer.schedule(event_handler, plugin_path, recursive=True) + observer.start() + print(f"[HotReload] 已启动插件热重载监控: {plugin_path}") + else: + print(f"[HotReload] 警告: 插件目录不存在 {plugin_path}") + + try: + bot = WS() + await bot.connect() + finally: + if observer.is_alive(): + observer.stop() + observer.join() if __name__ == "__main__": diff --git a/requirements.txt b/requirements.txt index 7278891..5fff61d 100644 Binary files a/requirements.txt and b/requirements.txt differ