From 37b8d969b12fd510e46ebcf0e926551c63ff52f5 Mon Sep 17 00:00:00 2001 From: snowy Date: Mon, 12 Aug 2024 02:40:51 +0800 Subject: [PATCH] =?UTF-8?q?:bug:=20fix=20=E9=80=9A=E9=81=93=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E5=9C=A8=E8=BF=9B=E7=A8=8B=E5=86=85=E4=BC=A0=E9=80=92?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 13 +- .../__init__.py => config/default.yml | 0 docs/README.md | 6 +- docs/deployment/config.md | 5 +- liteyuki/__init__.py | 3 +- liteyuki/bot/__init__.py | 118 ++++++++-------- liteyuki/comm/channel.py | 37 ++--- liteyuki/config.py | 128 +++++++++++++++--- liteyuki/core/__init__.py | 1 - liteyuki/core/manager.py | 32 +++-- liteyuki/core/spawn_process.py | 56 -------- liteyuki/dev/__init__.py | 4 + liteyuki/dev/observer.py | 5 + liteyuki/plugin/load.py | 6 +- liteyuki/plugins/code_watchdog/__init__.py | 21 +++ liteyuki/plugins/code_watchdog/observer.py | 44 ++++++ liteyuki/plugins/nonebot_launcher/__init__.py | 56 ++++++++ .../nb_utils}/adapter_manager/__init__.py | 0 .../nb_utils}/adapter_manager/onebot.py | 0 .../nb_utils}/adapter_manager/satori.py | 0 .../nb_utils}/driver_manager/__init__.py | 0 .../nb_utils}/driver_manager/auto_set_env.py | 0 .../nb_utils}/driver_manager/defines.py | 0 liteyuki/plugins/plugins_loader/__init__.py | 20 +++ liteyuki/plugins/what_litaco/__init__.py | 19 --- main.py | 7 +- src/liteyuki_plugins/README.md | 3 - .../liteyuki_plugins}/lifespan_monitor.py | 20 +-- .../process_manager/__init__.py | 9 ++ .../liteyuki_plugins/reboot.py | 6 +- .../liteyuki_plugins}/register_service.py | 0 .../resource_loader/__init__.py | 0 .../liteyuki_crt_utils/__init__.py | 10 +- tests/test_config_load.py | 21 +++ tests/test_core.py | 2 +- tests/test_dll.py | 16 --- 36 files changed, 430 insertions(+), 238 deletions(-) rename liteyuki/plugins/process_manager/__init__.py => config/default.yml (100%) delete mode 100644 liteyuki/core/spawn_process.py create mode 100644 liteyuki/dev/__init__.py create mode 100644 liteyuki/dev/observer.py create mode 100644 liteyuki/plugins/code_watchdog/__init__.py create mode 100644 liteyuki/plugins/code_watchdog/observer.py create mode 100644 liteyuki/plugins/nonebot_launcher/__init__.py rename liteyuki/{core/nb => plugins/nonebot_launcher/nb_utils}/adapter_manager/__init__.py (100%) rename liteyuki/{core/nb => plugins/nonebot_launcher/nb_utils}/adapter_manager/onebot.py (100%) rename liteyuki/{core/nb => plugins/nonebot_launcher/nb_utils}/adapter_manager/satori.py (100%) rename liteyuki/{core/nb => plugins/nonebot_launcher/nb_utils}/driver_manager/__init__.py (100%) rename liteyuki/{core/nb => plugins/nonebot_launcher/nb_utils}/driver_manager/auto_set_env.py (100%) rename liteyuki/{core/nb => plugins/nonebot_launcher/nb_utils}/driver_manager/defines.py (100%) create mode 100644 liteyuki/plugins/plugins_loader/__init__.py delete mode 100644 liteyuki/plugins/what_litaco/__init__.py delete mode 100644 src/liteyuki_plugins/README.md rename {liteyuki/plugins => src/liteyuki_plugins}/lifespan_monitor.py (71%) create mode 100644 src/liteyuki_plugins/process_manager/__init__.py rename liteyuki/plugins/reloader_monitor.py => src/liteyuki_plugins/reboot.py (71%) rename {liteyuki/plugins => src/liteyuki_plugins}/register_service.py (100%) rename {liteyuki/plugins => src/liteyuki_plugins}/resource_loader/__init__.py (100%) create mode 100644 tests/test_config_load.py delete mode 100644 tests/test_dll.py diff --git a/.gitignore b/.gitignore index d8bc4873..7a9bad78 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .venv/ .idea/ .cache/ + node_modules/ data/ db/ @@ -11,19 +12,23 @@ __pycache__/ *.pyd *.pyw /plugins/ + +#config +config/ +!config/default.yml _config.yml config.yml config.example.yml -compile.bat -src/resources/templates/latest-debug.html + # vuepress .github -test.py -line_count.py +# mupy mypy.ini # nuitka +compile.bat +src/resources/templates/latest-debug.html main.build/ main.dist/ main.exe diff --git a/liteyuki/plugins/process_manager/__init__.py b/config/default.yml similarity index 100% rename from liteyuki/plugins/process_manager/__init__.py rename to config/default.yml diff --git a/docs/README.md b/docs/README.md index c2f32ee9..c18d881e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,7 +9,7 @@ bgImageDark: bgImageStyle: background-attachment: fixed heroText: LiteyukiBot -tagline: LiteyukiBot 轻雪机器人,基于NoneBot2构建的综合应用型聊天机器人 +tagline: LiteyukiBot 轻雪机器人,综合性的机器人应用及管理框架 actions: - text: 快速部署 @@ -30,9 +30,9 @@ highlights: background-repeat: repeat background-size: initial features: - - title: 基于NoneBot2 + - title: 支持多种框架 icon: robot - details: 拥有良好的生态支持 + details: 兼容nonebot,melobot等,拥有良好的生态支持 link: https://nonebot.dev/ - title: 便捷管理 diff --git a/docs/deployment/config.md b/docs/deployment/config.md index 57a96fe5..385c070d 100644 --- a/docs/deployment/config.md +++ b/docs/deployment/config.md @@ -8,8 +8,11 @@ tag: - 部署 --- -首次运行后生成`config.yml`,你可以修改配置项后重启轻雪,绝大多数情况下,你只需要修改`superusers`及`nickname`字段即可 +轻雪支持`yaml`、`json`和`toml`作为配置文件,取决于你个人的喜好 +首次运行后生成`config.yml`和`config`目录,你可修改配置项后重启轻雪,绝大多数情况下,你只需要修改`superusers`及`nickname`字段即可 + +启动时会加载项目目录下`config.yml/yaml/json/toml`和`config`目录下的所有配置文件,你可在`config`目录下创建多个配置文件,轻雪会自动合并这些配置文件 ## **基础配置项** ```yaml diff --git a/liteyuki/__init__.py b/liteyuki/__init__.py index 4ace66e1..1e195c2e 100644 --- a/liteyuki/__init__.py +++ b/liteyuki/__init__.py @@ -1,6 +1,7 @@ from liteyuki.bot import ( LiteyukiBot, - get_bot + get_bot, + get_config ) from liteyuki.comm import ( diff --git a/liteyuki/bot/__init__.py b/liteyuki/bot/__init__.py index e89d8a3a..63363c92 100644 --- a/liteyuki/bot/__init__.py +++ b/liteyuki/bot/__init__.py @@ -4,21 +4,22 @@ import platform import sys import threading import time -from typing import Any, Optional +from typing import Any, Iterable, Optional from watchdog.events import FileSystemEventHandler from watchdog.observers import Observer from liteyuki.bot.lifespan import (LIFESPAN_FUNC, Lifespan) +from liteyuki.comm import get_channel from liteyuki.core import IS_MAIN_PROCESS from liteyuki.core.manager import ProcessManager -from liteyuki.core.spawn_process import mb_run, nb_run from liteyuki.log import init_log, logger from liteyuki.plugin import load_plugins __all__ = [ "LiteyukiBot", - "get_bot" + "get_bot", + "get_config", ] @@ -26,15 +27,18 @@ class LiteyukiBot: def __init__(self, *args, **kwargs): global _BOT_INSTANCE _BOT_INSTANCE = self # 引用 - self.config: dict[str, Any] = kwargs - self.init(**self.config) # 初始化 - self.lifespan: Lifespan = Lifespan() + self.lifespan = Lifespan() + + self.config: dict[str, Any] = kwargs + + self.init(**self.config) # 初始化 self.process_manager: ProcessManager = ProcessManager(bot=self) self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.loop_thread = threading.Thread(target=self.loop.run_forever, daemon=True) + self.stop_event = threading.Event() self.call_restart_count = 0 print("\033[34m" + r""" @@ -48,58 +52,22 @@ $$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \__$$ |$$ |$$ \ _$$ |_ $$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ | $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/ """ + "\033[0m") + load_plugins("liteyuki/plugins") # 加载轻雪插件 + logger.info("Liteyuki is initializing...") def run(self): - load_plugins("liteyuki/plugins") # 加载轻雪插件 - + """ + 启动逻辑 + """ self.loop_thread.start() # 启动事件循环 asyncio.run(self.lifespan.before_start()) # 启动前钩子 - self.process_manager.add_target("nonebot", nb_run, **self.config) - self.process_manager.start("nonebot") - - self.process_manager.add_target("melobot", mb_run, **self.config) - self.process_manager.start("melobot") - asyncio.run(self.lifespan.after_start()) # 启动后钩子 - - self.start_watcher() # 启动文件监视器 + self.start_watcher() # 启动文件监视器,后续准备插件化 + self.keep_running() def start_watcher(self): - if self.config.get("debug", False): - - src_directories = ( - "liteyuki", - "src/liteyuki_main", - "src/liteyuki_plugins", - "src/nonebot_plugins", - "src/utils", - ) - src_excludes_extensions = ( - "pyc", - ) - - logger.debug("Liteyuki Reload enabled, watching for file changes...") - restart = self.restart_process - - class CodeModifiedHandler(FileSystemEventHandler): - """ - Handler for code file changes - """ - - def on_modified(self, event): - if event.src_path.endswith( - src_excludes_extensions) or event.is_directory or "__pycache__" in event.src_path: - return - logger.info(f"{event.src_path} modified, reloading bot...") - restart() - - code_modified_handler = CodeModifiedHandler() - - observer = Observer() - for directory in src_directories: - observer.schedule(code_modified_handler, directory, recursive=True) - observer.start() + pass def restart(self, delay: int = 0): """ @@ -135,15 +103,22 @@ $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/ Returns: """ - logger.info("Stopping LiteyukiBot...") + logger.info(f"Stopping process {name}...") self.loop.create_task(self.lifespan.before_shutdown()) # 重启前钩子 self.loop.create_task(self.lifespan.before_shutdown()) # 停止前钩子 + # if name: + # self.process_manager.terminate(name) + # else: + # self.process_manager.terminate_all() if name: - self.process_manager.terminate(name) + chan_active = get_channel(f"{name}-active") + chan_active.send(1) else: - self.process_manager.terminate_all() + for process_name in self.process_manager.processes: + chan_active = get_channel(f"{process_name}-active") + chan_active.send(1) def init(self, *args, **kwargs): """ @@ -239,6 +214,9 @@ $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/ """ return self.lifespan.on_after_nonebot_init(func) + def keep_running(self): + self.stop_event.wait() + _BOT_INSTANCE: Optional[LiteyukiBot] = None @@ -254,3 +232,37 @@ def get_bot() -> Optional[LiteyukiBot]: else: # 从多进程上下文中获取 pass + + +def get_config(key: str, default: Any = None) -> Any: + """ + 获取配置 + Args: + key: 配置键 + default: 默认值 + + Returns: + Any: 配置值 + """ + return _BOT_INSTANCE.config.get(key, default) + + +def get_config_with_compat(key: str, compat_keys: tuple[str], default: Any = None) -> Any: + """ + 获取配置,兼容旧版本 + Args: + key: 配置键 + compat_keys: 兼容键 + default: 默认值 + + Returns: + Any: 配置值 + """ + if key in _BOT_INSTANCE.config: + return _BOT_INSTANCE.config[key] + for compat_key in compat_keys: + if compat_key in _BOT_INSTANCE.config: + logger.warning(f"Config key {compat_key} will be deprecated, use {key} instead.") + return _BOT_INSTANCE.config[compat_key] + return default + diff --git a/liteyuki/comm/channel.py b/liteyuki/comm/channel.py index ec22014c..eae83da7 100644 --- a/liteyuki/comm/channel.py +++ b/liteyuki/comm/channel.py @@ -35,13 +35,14 @@ _callback_funcs: dict[str, ON_RECEIVE_FUNC] = {} class Channel: """ - 通道类,用于进程间通信,进程内不可用,仅限主进程和子进程之间通信 + 通道类,可以在进程间和进程内通信,双向但同时只能有一个发送者和一个接收者 有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器 """ def __init__(self, _id: str): - self.main_send_conn, self.sub_receive_conn = Pipe() - self.sub_send_conn, self.main_receive_conn = Pipe() + # self.main_send_conn, self.sub_receive_conn = Pipe() + # self.sub_send_conn, self.main_receive_conn = Pipe() + self.conn_send, self.conn_recv = Pipe() self._closed = False self._on_main_receive_funcs: list[str] = [] self._on_sub_receive_funcs: list[str] = [] @@ -61,12 +62,7 @@ class Channel: """ if self._closed: raise RuntimeError("Cannot send to a closed channel") - if IS_MAIN_PROCESS: - print("主进程发送数据:", data) - self.main_send_conn.send(data) - else: - print("子进程发送数据:", data) - self.sub_send_conn.send(data) + self.conn_send.send(data) def receive(self) -> Any: """ @@ -77,14 +73,7 @@ class Channel: raise RuntimeError("Cannot receive from a closed channel") while True: - # 判断receiver是否为None或者receiver是否等于接收者,是则接收数据,否则不动数据 - if IS_MAIN_PROCESS: - data = self.main_receive_conn.recv() - print("主进程接收数据:", data) - else: - data = self.sub_receive_conn.recv() - print("子进程接收数据:", data) - + data = self.conn_recv.recv() return data def close(self): @@ -92,10 +81,8 @@ class Channel: 关闭通道 """ self._closed = True - self.sub_receive_conn.close() - self.main_send_conn.close() - self.sub_send_conn.close() - self.main_receive_conn.close() + self.conn_send.close() + self.conn_recv.close() def on_receive(self, filter_func: Optional[FILTER_FUNC] = None) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]: """ @@ -158,7 +145,7 @@ class Channel: """ self.is_main_receive_loop_running = True while not self._closed: - data = self.main_receive_conn.recv() + data = self.conn_recv.recv() self._run_on_main_receive_funcs(data) def _start_sub_receive_loop(self): @@ -167,7 +154,7 @@ class Channel: """ self.is_sub_receive_loop_running = True while not self._closed: - data = self.sub_receive_conn.recv() + data = self.conn_recv.recv() self._run_on_sub_receive_funcs(data) def __iter__(self): @@ -188,6 +175,8 @@ def set_channel(name: str, channel: Channel): name: 通道名称 channel: 通道实例 """ + if not isinstance(channel, Channel): + raise TypeError(f"channel must be an instance of Channel, {type(channel)} found") _channel[name] = channel @@ -198,7 +187,7 @@ def set_channels(channels: dict[str, Channel]): channels: 通道名称 """ for name, channel in channels.items(): - _channel[name] = channel + set_channel(name, channel) def get_channel(name: str) -> Optional[Channel]: diff --git a/liteyuki/config.py b/liteyuki/config.py index 4fcb8d44..b50adc5e 100644 --- a/liteyuki/config.py +++ b/liteyuki/config.py @@ -1,12 +1,24 @@ +""" +该模块用于常用配置文件的加载 +多配置文件编写原则: +1.尽量不要冲突: 一个键不要多次出现 +2.分工明确: 每个配置文件给一个或一类服务提供配置 +3.扁平化编写: 配置文件尽量扁平化,不要出现过多的嵌套 +4.注意冲突时的优先级: 项目目录下的配置文件优先级高于config目录下的配置文件 +5.请不要将需要动态加载的内容写入配置文件,你应该使用其他储存方式 +""" +import copy +import json import os -from typing import List +from typing import Any, List -import nonebot +import toml import yaml from pydantic import BaseModel +from liteyuki import logger -config = {} # 主进程全局配置,确保加载后读取 +_SUPPORTED_CONFIG_FORMATS = (".yaml", ".yml", ".json", ".toml") class SatoriNodeConfig(BaseModel): @@ -32,18 +44,100 @@ class BasicConfig(BaseModel): data_path: str = "data/liteyuki" -def load_from_yaml(file: str) -> dict: - global config - nonebot.logger.debug("Loading config from %s" % file) - if not os.path.exists(file): - nonebot.logger.warning(f"Config file {file} not found, created default config, please modify it and restart") - with open(file, "w", encoding="utf-8") as f: - yaml.dump(BasicConfig().dict(), f, default_flow_style=False) +def flat_config(config: dict[str, Any]) -> dict[str, Any]: + """ + 扁平化配置文件 - with open(file, "r", encoding="utf-8") as f: - conf = yaml.load(f, Loader=yaml.FullLoader) - config = conf - if conf is None: - nonebot.logger.warning(f"Config file {file} is empty, use default config. please modify it and restart") - conf = BasicConfig().dict() - return conf + {a:{b:{c:1}}} -> {"a.b.c": 1} + Args: + config: 配置项目 + + Returns: + 扁平化后的配置文件,但也包含原有的键值对 + """ + new_config = copy.deepcopy(config) + for key, value in config.items(): + if isinstance(value, dict): + for k, v in flat_config(value).items(): + new_config[f"{key}.{k}"] = v + return new_config + + +def load_from_yaml(file: str) -> dict[str, Any]: + """ + Load config from yaml file + + """ + logger.debug(f"Loading YAML config from {file}") + config = yaml.safe_load(open(file, "r", encoding="utf-8")) + return flat_config(config if config is not None else {}) + + +def load_from_json(file: str) -> dict[str, Any]: + """ + Load config from json file + """ + logger.debug(f"Loading JSON config from {file}") + config = json.load(open(file, "r", encoding="utf-8")) + return flat_config(config if config is not None else {}) + + +def load_from_toml(file: str) -> dict[str, Any]: + """ + Load config from toml file + """ + logger.debug(f"Loading TOML config from {file}") + config = toml.load(open(file, "r", encoding="utf-8")) + return flat_config(config if config is not None else {}) + + +def load_from_files(*files: str, no_warning: bool = False) -> dict[str, Any]: + """ + 从指定文件加载配置项,会自动识别文件格式 + 默认执行扁平化选项 + """ + config = {} + for file in files: + if os.path.exists(file): + if file.endswith((".yaml", "yml")): + config.update(load_from_yaml(file)) + elif file.endswith(".json"): + config.update(load_from_json(file)) + elif file.endswith(".toml"): + config.update(load_from_toml(file)) + else: + if not no_warning: + logger.warning(f"Unsupported config file format: {file}") + else: + if not no_warning: + logger.warning(f"Config file not found: {file}") + return config + + +def load_configs_from_dirs(*directories: str, no_waring: bool = False) -> dict[str, Any]: + """ + 从目录下加载配置文件,不递归 + 按照读取文件的优先级反向覆盖 + 默认执行扁平化选项 + """ + config = {} + for directory in directories: + if not os.path.exists(directory): + if not no_waring: + logger.warning(f"Directory not found: {directory}") + continue + for file in os.listdir(directory): + if file.endswith(_SUPPORTED_CONFIG_FORMATS): + config.update(load_from_files(os.path.join(directory, file), no_warning=no_waring)) + return config + + +def load_config_in_default(no_waring: bool = False) -> dict[str, Any]: + """ + 从一个标准的轻雪项目加载配置文件 + 项目目录下的config.*和config目录下的所有配置文件 + 项目目录下的配置文件优先 + """ + config = load_configs_from_dirs("config", no_waring=no_waring) + config.update(load_from_files("config.yaml", "config.toml", "config.json", "config.yml", no_warning=no_waring)) + return config diff --git a/liteyuki/core/__init__.py b/liteyuki/core/__init__.py index f95f8146..8f4d9278 100644 --- a/liteyuki/core/__init__.py +++ b/liteyuki/core/__init__.py @@ -1,6 +1,5 @@ import multiprocessing -from .spawn_process import * from .manager import * __all__ = [ diff --git a/liteyuki/core/manager.py b/liteyuki/core/manager.py index 06d62eee..9cb368a8 100644 --- a/liteyuki/core/manager.py +++ b/liteyuki/core/manager.py @@ -36,13 +36,6 @@ class ProcessManager: self.targets: dict[str, tuple[callable, tuple, dict]] = {} self.processes: dict[str, Process] = {} - set_channels({ - "nonebot-active" : Channel(_id="nonebot-active"), - "melobot-active" : Channel(_id="melobot-active"), - "nonebot-passive": Channel(_id="nonebot-passive"), - "melobot-passive": Channel(_id="melobot-passive"), - }) - def start(self, name: str, delay: int = 0): """ 开启后自动监控进程,并添加到进程字典中 @@ -61,7 +54,7 @@ class ProcessManager: while not should_exit: chan_active = get_channel(f"{name}-active") chan_passive = get_channel(f"{name}-passive") - process = Process(target=self.targets[name][0], args=(chan_active, chan_passive, *self.targets[name][1]), + process = Process(target=self.targets[name][0], args=self.targets[name][1], kwargs=self.targets[name][2]) self.processes[name] = process process.start() @@ -88,9 +81,30 @@ class ProcessManager: else: threading.Thread(target=_start).start() - def add_target(self, name: str, target, *args, **kwargs): + def add_target(self, name: str, target, args: tuple = (), kwargs=None): + """ + 添加进程 + Args: + name: 进程名,用于获取和唯一标识 + target: 进程函数 + args: 进程函数参数 + kwargs: 进程函数关键字参数,通常会默认传入chan_active和chan_passive + """ + if kwargs is None: + kwargs = {} + chan_active = Channel(_id=f"{name}-active") + chan_passive = Channel(_id=f"{name}-passive") + kwargs["chan_active"] = chan_active + kwargs["chan_passive"] = chan_passive self.targets[name] = (target, args, kwargs) + set_channels( + { + f"{name}-active" : chan_active, + f"{name}-passive": chan_passive + } + ) + def join(self): for name, process in self.targets: process.join() diff --git a/liteyuki/core/spawn_process.py b/liteyuki/core/spawn_process.py deleted file mode 100644 index 040392aa..00000000 --- a/liteyuki/core/spawn_process.py +++ /dev/null @@ -1,56 +0,0 @@ -from typing import Optional, TYPE_CHECKING - -import nonebot - -from liteyuki.core.nb import adapter_manager, driver_manager -from liteyuki.comm.channel import set_channel - -if TYPE_CHECKING: - from liteyuki.comm.channel import Channel - -timeout_limit: int = 20 - -"""导出对象,用于主进程与nonebot通信""" -_channels = {} - - -def nb_run(chan_active: "Channel", chan_passive: "Channel", *args, **kwargs): - """ - 初始化NoneBot并运行在子进程 - Args: - - chan_active: - chan_passive: - **kwargs: - - Returns: - - """ - set_channel("nonebot-active", chan_active) - set_channel("nonebot-passive", chan_passive) - nonebot.init(**kwargs) - driver_manager.init(config=kwargs) - adapter_manager.init(kwargs) - adapter_manager.register() - nonebot.load_plugin("src.liteyuki_main") - nonebot.run() - - -def mb_run(chan_active: "Channel", chan_passive: "Channel", *args, **kwargs): - """ - 初始化MeloBot并运行在子进程 - Args: - chan_active - chan_passive - *args: - **kwargs: - - Returns: - - """ - set_channel("melobot-active", chan_active) - set_channel("melobot-passive", chan_passive) - - # bot = MeloBot(__name__) - # bot.init(AbstractConnector(cd_time=0)) - # bot.run() diff --git a/liteyuki/dev/__init__.py b/liteyuki/dev/__init__.py new file mode 100644 index 00000000..893c1703 --- /dev/null +++ b/liteyuki/dev/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +""" +该模块用于存放一些开发工具 +""" diff --git a/liteyuki/dev/observer.py b/liteyuki/dev/observer.py new file mode 100644 index 00000000..8143c7fa --- /dev/null +++ b/liteyuki/dev/observer.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +""" + +""" +import watchdog \ No newline at end of file diff --git a/liteyuki/plugin/load.py b/liteyuki/plugin/load.py index e073c19c..a997f1e2 100644 --- a/liteyuki/plugin/load.py +++ b/liteyuki/plugin/load.py @@ -44,8 +44,12 @@ def load_plugin(module_path: str | Path) -> Optional[Plugin]: module_name=module_path, metadata=module.__dict__.get("__plugin_metadata__", None) ) + display_name = module.__name__.split(".")[-1] + if module.__dict__.get("__plugin_meta__"): + metadata: "PluginMetadata" = module.__dict__["__plugin_meta__"] + display_name = f"{metadata.name}({module.__name__.split('.')[-1]})" logger.opt(colors=True).success( - f'Succeeded to load liteyuki plugin "{module.__name__.split(".")[-1]}"' + f'Succeeded to load liteyuki plugin "{display_name}"' ) return _plugins[module.__name__] diff --git a/liteyuki/plugins/code_watchdog/__init__.py b/liteyuki/plugins/code_watchdog/__init__.py new file mode 100644 index 00000000..7500e8ab --- /dev/null +++ b/liteyuki/plugins/code_watchdog/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/11 下午8:50 +@Author : snowykami +@Email : snowykami@outlook.com +@File : __init__.py.py +@Software: PyCharm +""" +from liteyuki.core import IS_MAIN_PROCESS +from liteyuki.plugin import PluginMetadata + +from .observer import * + +__plugin_meta__ = PluginMetadata( + name="代码热重载监视", +) + +if IS_MAIN_PROCESS: + config = get_config("liteyuki.reload") diff --git a/liteyuki/plugins/code_watchdog/observer.py b/liteyuki/plugins/code_watchdog/observer.py new file mode 100644 index 00000000..c081c854 --- /dev/null +++ b/liteyuki/plugins/code_watchdog/observer.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/11 下午10:01 +@Author : snowykami +@Email : snowykami@outlook.com +@File : observer.py +@Software: PyCharm +""" +from watchdog.events import FileSystemEventHandler +from watchdog.observers import Observer +from liteyuki import get_config, logger, get_bot + +liteyuki_bot = get_bot() + +if get_config("debug", False): + + src_directories = ( + "src/nonebot_plugins", + "src/utils", + ) + src_excludes_extensions = ( + "pyc", + ) + logger.debug("Liteyuki Reload enabled, watching for file changes...") + + class CodeModifiedHandler(FileSystemEventHandler): + """ + Handler for code file changes + """ + def on_modified(self, event): + if event.src_path.endswith( + src_excludes_extensions) or event.is_directory or "__pycache__" in event.src_path: + return + logger.info(f"{event.src_path} modified, reloading bot...") + liteyuki_bot.restart_process("nonebot") + + code_modified_handler = CodeModifiedHandler() + + observer = Observer() + for directory in src_directories: + observer.schedule(code_modified_handler, directory, recursive=True) + observer.start() \ No newline at end of file diff --git a/liteyuki/plugins/nonebot_launcher/__init__.py b/liteyuki/plugins/nonebot_launcher/__init__.py new file mode 100644 index 00000000..ee90b158 --- /dev/null +++ b/liteyuki/plugins/nonebot_launcher/__init__.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/11 下午5:24 +@Author : snowykami +@Email : snowykami@outlook.com +@File : __init__.py.py +@Software: PyCharm +""" + +import nonebot + +from liteyuki.plugin import PluginMetadata +from liteyuki import get_bot +from liteyuki.comm import Channel, set_channel +from liteyuki.core import IS_MAIN_PROCESS +from .nb_utils import adapter_manager, driver_manager + +__plugin_meta__ = PluginMetadata( + name="NoneBot2启动器", +) + +liteyuki = get_bot() + + +def nb_run(chan_active: "Channel", chan_passive: "Channel", **kwargs): + """ + 初始化NoneBot并运行在子进程 + Args: + + chan_active: + chan_passive: + **kwargs: + + Returns: + """ + # 给子进程传递通道对象 + set_channel("nonebot-active", chan_active) + set_channel("nonebot-passive", chan_passive) + + kwargs.update(kwargs.get("nonebot", {})) # nonebot配置优先 + nonebot.init(**kwargs) + + driver_manager.init(config=kwargs) + adapter_manager.init(kwargs) + adapter_manager.register() + nonebot.load_plugin("src.liteyuki_main") + nonebot.run() + + +if IS_MAIN_PROCESS: + @liteyuki.on_after_start + def start_run_nonebot(): + liteyuki.process_manager.add_target(name="nonebot", target=nb_run, args=(), kwargs=liteyuki.config) + liteyuki.process_manager.start("nonebot") diff --git a/liteyuki/core/nb/adapter_manager/__init__.py b/liteyuki/plugins/nonebot_launcher/nb_utils/adapter_manager/__init__.py similarity index 100% rename from liteyuki/core/nb/adapter_manager/__init__.py rename to liteyuki/plugins/nonebot_launcher/nb_utils/adapter_manager/__init__.py diff --git a/liteyuki/core/nb/adapter_manager/onebot.py b/liteyuki/plugins/nonebot_launcher/nb_utils/adapter_manager/onebot.py similarity index 100% rename from liteyuki/core/nb/adapter_manager/onebot.py rename to liteyuki/plugins/nonebot_launcher/nb_utils/adapter_manager/onebot.py diff --git a/liteyuki/core/nb/adapter_manager/satori.py b/liteyuki/plugins/nonebot_launcher/nb_utils/adapter_manager/satori.py similarity index 100% rename from liteyuki/core/nb/adapter_manager/satori.py rename to liteyuki/plugins/nonebot_launcher/nb_utils/adapter_manager/satori.py diff --git a/liteyuki/core/nb/driver_manager/__init__.py b/liteyuki/plugins/nonebot_launcher/nb_utils/driver_manager/__init__.py similarity index 100% rename from liteyuki/core/nb/driver_manager/__init__.py rename to liteyuki/plugins/nonebot_launcher/nb_utils/driver_manager/__init__.py diff --git a/liteyuki/core/nb/driver_manager/auto_set_env.py b/liteyuki/plugins/nonebot_launcher/nb_utils/driver_manager/auto_set_env.py similarity index 100% rename from liteyuki/core/nb/driver_manager/auto_set_env.py rename to liteyuki/plugins/nonebot_launcher/nb_utils/driver_manager/auto_set_env.py diff --git a/liteyuki/core/nb/driver_manager/defines.py b/liteyuki/plugins/nonebot_launcher/nb_utils/driver_manager/defines.py similarity index 100% rename from liteyuki/core/nb/driver_manager/defines.py rename to liteyuki/plugins/nonebot_launcher/nb_utils/driver_manager/defines.py diff --git a/liteyuki/plugins/plugins_loader/__init__.py b/liteyuki/plugins/plugins_loader/__init__.py new file mode 100644 index 00000000..e8f7010d --- /dev/null +++ b/liteyuki/plugins/plugins_loader/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/11 下午10:02 +@Author : snowykami +@Email : snowykami@outlook.com +@File : __init__.py.py +@Software: PyCharm +""" +from liteyuki.plugin import PluginMetadata, load_plugins + +__plugin_meta__ = PluginMetadata( + name="外部轻雪插件加载器", + version="0.1.0", + author="snowykami", + description="插件加载器,用于加载轻雪原生插件" +) + +load_plugins("src/liteyuki_plugins") diff --git a/liteyuki/plugins/what_litaco/__init__.py b/liteyuki/plugins/what_litaco/__init__.py deleted file mode 100644 index ffce5f45..00000000 --- a/liteyuki/plugins/what_litaco/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved - -@Time : 2024/7/25 上午2:28 -@Author : snowykami -@Email : snowykami@outlook.com -@File : __init__.py -@Software: PyCharm -""" -# -*- coding: utf-8 -*- -# -# Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved -# -# @Time : 2024/7/22 上午11:25 -# @Author : snowykami -# @Email : snowykami@outlook.com -# @File : asa.py -# @Software: PyCharm \ No newline at end of file diff --git a/main.py b/main.py index 7efe0279..a2a83b1f 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,9 @@ +""" +启动脚本,会执行一些启动的操作,比如加载配置文件,初始化 bot 实例等。 +""" from liteyuki import LiteyukiBot -from liteyuki.config import load_from_yaml +from liteyuki.config import load_config_in_default if __name__ == "__main__": - bot = LiteyukiBot(**load_from_yaml("config.yml")) + bot = LiteyukiBot(**load_config_in_default()) bot.run() diff --git a/src/liteyuki_plugins/README.md b/src/liteyuki_plugins/README.md deleted file mode 100644 index aba0376d..00000000 --- a/src/liteyuki_plugins/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# 说明 - -此目录为**轻雪插件**目录,非其他插件目录。 \ No newline at end of file diff --git a/liteyuki/plugins/lifespan_monitor.py b/src/liteyuki_plugins/lifespan_monitor.py similarity index 71% rename from liteyuki/plugins/lifespan_monitor.py rename to src/liteyuki_plugins/lifespan_monitor.py index f49d5bca..ca222bc8 100644 --- a/liteyuki/plugins/lifespan_monitor.py +++ b/src/liteyuki_plugins/lifespan_monitor.py @@ -8,20 +8,22 @@ # @File : asa.py # @Software: PyCharm import asyncio +import multiprocessing from liteyuki.plugin import PluginMetadata from liteyuki import get_bot, logger from liteyuki.comm.channel import get_channel __plugin_meta__ = PluginMetadata( - name="lifespan_monitor", + name="生命周期日志", ) bot = get_bot() -nbp_chan = get_channel("nonebot-passive") -mbp_chan = get_channel("melobot-passive") +# nbp_chan = get_channel("nonebot-passive") +# mbp_chan = get_channel("melobot-passive") + @bot.on_before_start def _(): logger.info("生命周期监控器:准备启动") @@ -41,15 +43,3 @@ def _(): @bot.on_after_start def _(): logger.info("生命周期监控器:启动完成") - - -@bot.on_after_start -async def _(): - logger.info("生命周期监控器:启动完成") - - - -# @mbp_chan.on_receive() -# @nbp_chan.on_receive() -# async def _(data): -# print("主进程收到数据", data) diff --git a/src/liteyuki_plugins/process_manager/__init__.py b/src/liteyuki_plugins/process_manager/__init__.py new file mode 100644 index 00000000..dcdbd336 --- /dev/null +++ b/src/liteyuki_plugins/process_manager/__init__.py @@ -0,0 +1,9 @@ +from liteyuki.plugin import PluginMetadata + + +__plugin_meta__ = PluginMetadata( + name="进程管理器", + version="0.1.0", + author="snowykami", + description="进程管理器,用于管理子进程" +) \ No newline at end of file diff --git a/liteyuki/plugins/reloader_monitor.py b/src/liteyuki_plugins/reboot.py similarity index 71% rename from liteyuki/plugins/reloader_monitor.py rename to src/liteyuki_plugins/reboot.py index 4900e215..ddb31053 100644 --- a/liteyuki/plugins/reloader_monitor.py +++ b/src/liteyuki_plugins/reboot.py @@ -2,9 +2,9 @@ """ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved -@Time : 2024/8/10 下午5:18 +@Time : 2024/8/11 下午8:22 @Author : snowykami @Email : snowykami@outlook.com -@File : reloader_monitor.py +@File : reloader.py.py @Software: PyCharm -""" \ No newline at end of file +""" diff --git a/liteyuki/plugins/register_service.py b/src/liteyuki_plugins/register_service.py similarity index 100% rename from liteyuki/plugins/register_service.py rename to src/liteyuki_plugins/register_service.py diff --git a/liteyuki/plugins/resource_loader/__init__.py b/src/liteyuki_plugins/resource_loader/__init__.py similarity index 100% rename from liteyuki/plugins/resource_loader/__init__.py rename to src/liteyuki_plugins/resource_loader/__init__.py diff --git a/src/nonebot_plugins/liteyuki_crt_utils/__init__.py b/src/nonebot_plugins/liteyuki_crt_utils/__init__.py index 386f3c63..63e21d69 100644 --- a/src/nonebot_plugins/liteyuki_crt_utils/__init__.py +++ b/src/nonebot_plugins/liteyuki_crt_utils/__init__.py @@ -16,12 +16,4 @@ __plugin_meta__ = PluginMetadata( "toggleable" : True, "default_enable": True, } -) - -# chan = get_channel("nonebot-passive") -# -# -# @chan.on_receive() -# async def _(d): -# print("CRT子进程接收到数据:", d) -# chan.send("CRT子进程已接收到数据") +) \ No newline at end of file diff --git a/tests/test_config_load.py b/tests/test_config_load.py new file mode 100644 index 00000000..d4c30397 --- /dev/null +++ b/tests/test_config_load.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/11 下午11:07 +@Author : snowykami +@Email : snowykami@outlook.com +@File : test_config_load.py +@Software: PyCharm +""" +import json +import os +import sys + +sys.path.insert(0, os.getcwd()) +from liteyuki.config import load_config_in_default + + +def test_default_load(): + config = load_config_in_default() + print(json.dumps(config, indent=4, ensure_ascii=False)) diff --git a/tests/test_core.py b/tests/test_core.py index 81da20e8..b8983b04 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,4 +1,4 @@ -from src.liteyuki import LiteyukiBot +from liteyuki import LiteyukiBot if __name__ == "__main__": lyb = LiteyukiBot() diff --git a/tests/test_dll.py b/tests/test_dll.py deleted file mode 100644 index 8d443788..00000000 --- a/tests/test_dll.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved - -@Time : 2024/8/7 下午11:44 -@Author : snowykami -@Email : snowykami@outlook.com -@File : test_dll.py -@Software: PyCharm -""" -from src.utils.extension import load_lib - - -a = load_lib("src/libs/ly_api") - -a.Register("sss", "sss", 64, "sss", "sss") \ No newline at end of file