diff --git a/.gitignore b/.gitignore index f0dce34..94878a6 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,7 @@ src/plugins/trimo_plugin_msctconverter/MusicPreview/assets/wav # vuepress .github -pyproject.toml +# pyproject.toml test.py line_count.py diff --git a/liteyuki/__init__.py b/liteyuki/__init__.py new file mode 100644 index 0000000..c97c271 --- /dev/null +++ b/liteyuki/__init__.py @@ -0,0 +1,12 @@ +from liteyuki.bot import ( + LiteyukiBot, + get_bot +) + +# def get_bot_instance() -> LiteyukiBot | None: +# """ +# 获取轻雪实例 +# Returns: +# LiteyukiBot: 当前的轻雪实例 +# """ +# return _BOT_INSTANCE diff --git a/liteyuki/bot/__init__.py b/liteyuki/bot/__init__.py new file mode 100644 index 0000000..04c7650 --- /dev/null +++ b/liteyuki/bot/__init__.py @@ -0,0 +1,270 @@ +import asyncio +import multiprocessing +from typing import Any, Coroutine, Optional + +import nonebot + +from liteyuki.plugin.load import load_plugin, load_plugins +from src.utils import ( + adapter_manager, + driver_manager, +) +from src.utils.base.log import logger +from liteyuki.bot.lifespan import ( + Lifespan, + LIFESPAN_FUNC, +) +from liteyuki.core.spawn_process import nb_run, ProcessingManager + +__all__ = [ + "LiteyukiBot", + "get_bot" +] + +_MAIN_PROCESS = multiprocessing.current_process().name == "MainProcess" + + +class LiteyukiBot: + def __init__(self, *args, **kwargs): + + global _BOT_INSTANCE + _BOT_INSTANCE = self # 引用 + self.running = False + self.config: dict[str, Any] = kwargs + self.lifespan: Lifespan = Lifespan() + self.init(**self.config) # 初始化 + + print("\033[34m" + r""" + ▅▅▅▅▅▅▅▅▅▅▅▅▅▅██ ▅▅▅▅▅▅▅▅▅▅▅▅▅▅██ ██ ▅▅▅▅▅▅▅▅▅▅██ + ▛ ██ ██ ▛ ██ ███ ██ ██ + ██ ██ ███████████████ ██ ████████▅ ██ + ███████████████ ██ ███ ██ ██ + ██ ██ ▅██████████████▛ ██ ████████████ + ██ ██ ███ ███ + ████████████████ ██▅ ███ ██ ▅▅▅▅▅▅▅▅▅▅▅██ + ███ █ ▜███████ ██ ███ ██ ██ ██ ██ + ███ ███ █████▛ ██ ██ ██ ██ ██ + ███ ██ ███ █ ██ ██ ██ ██ ██ + ███ █████ ██████ ███ ██████████████ +""" + "\033[0m") + + def run(self, *args, **kwargs): + + if _MAIN_PROCESS: + load_plugins("liteyuki/plugins") + asyncio.run(self.lifespan.before_start()) + self._run_nb_in_spawn_process(*args, **kwargs) + else: + # 子进程启动 + + driver_manager.init(config=self.config) + adapter_manager.init(self.config) + adapter_manager.register() + nonebot.load_plugin("src.liteyuki_main") + + def _run_nb_in_spawn_process(self, *args, **kwargs): + """ + 在新的进程中运行nonebot.run方法 + Args: + *args: + **kwargs: + + Returns: + """ + + timeout_limit: int = 20 + should_exit = False + + while not should_exit: + ctx = multiprocessing.get_context("spawn") + event = ctx.Event() + ProcessingManager.event = event + process = ctx.Process( + target=nb_run, + args=(event,) + args, + kwargs=kwargs, + ) + process.start() # 启动进程 + + asyncio.run(self.lifespan.after_start()) + + while not should_exit: + if ProcessingManager.event.wait(1): + logger.info("接收到重启活动信息") + process.terminate() + process.join(timeout_limit) + if process.is_alive(): + logger.warning( + f"进程 {process.pid} 在 {timeout_limit} 秒后依旧存在,强制清灭。" + ) + process.kill() + break + elif process.is_alive(): + continue + else: + should_exit = True + + @staticmethod + def _run_coroutine(*coro: Coroutine): + """ + 运行协程 + Args: + coro: + + Returns: + + """ + # 检测是否有现有的事件循环 + new_loop = False + try: + loop = asyncio.get_event_loop() + except RuntimeError: + new_loop = True + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + if new_loop: + for c in coro: + loop.run_until_complete(c) + loop.close() + + else: + for c in coro: + loop.create_task(c) + + @property + def status(self) -> int: + """ + 获取轻雪状态 + Returns: + int: 0:未启动 1:运行中 + """ + return 1 if self.running else 0 + + def restart(self): + """ + 停止轻雪 + Returns: + + """ + logger.info("正在停止灵温活动…") + + logger.debug("正在启动 before_restart 的函数…") + self._run_coroutine(self.lifespan.before_restart()) + logger.debug("正在启动 before_shutdown 的函数…") + self._run_coroutine(self.lifespan.before_shutdown()) + + ProcessingManager.restart() + self.running = False + + def init(self, *args, **kwargs): + """ + 初始化轻雪, 自动调用 + Returns: + + """ + self.init_config() + self.init_logger() + if not _MAIN_PROCESS: + nonebot.init(**kwargs) + asyncio.run(self.lifespan.after_nonebot_init()) + + def init_logger(self): + from src.utils.base.log import init_log + init_log() + + def init_config(self): + pass + + def register_adapters(self, *args): + pass + + def on_before_start(self, func: LIFESPAN_FUNC): + """ + 注册启动前的函数 + Args: + func: + + Returns: + + """ + return self.lifespan.on_before_start(func) + + def on_after_start(self, func: LIFESPAN_FUNC): + """ + 注册启动后的函数 + Args: + func: + + Returns: + + """ + return self.lifespan.on_after_start(func) + + def on_before_shutdown(self, func: LIFESPAN_FUNC): + """ + 注册停止前的函数 + Args: + func: + + Returns: + + """ + return self.lifespan.on_before_shutdown(func) + + def on_after_shutdown(self, func: LIFESPAN_FUNC): + """ + 注册停止后的函数:未实现 + Args: + func: + + Returns: + + """ + return self.lifespan.on_after_shutdown(func) + + def on_before_restart(self, func: LIFESPAN_FUNC): + """ + 注册重启前的函数 + Args: + func: + + Returns: + + """ + + return self.lifespan.on_before_restart(func) + + def on_after_restart(self, func: LIFESPAN_FUNC): + """ + 注册重启后的函数:未实现 + Args: + func: + + Returns: + + """ + return self.lifespan.on_after_restart(func) + + def on_after_nonebot_init(self, func: LIFESPAN_FUNC): + """ + 注册nonebot初始化后的函数 + Args: + func: + + Returns: + + """ + return self.lifespan.on_after_nonebot_init(func) + + +_BOT_INSTANCE: Optional[LiteyukiBot] = None + + +def get_bot() -> Optional[LiteyukiBot]: + """ + 获取轻雪实例 + Returns: + LiteyukiBot: 当前的轻雪实例 + """ + return _BOT_INSTANCE diff --git a/liteyuki/bot/lifespan.py b/liteyuki/bot/lifespan.py new file mode 100644 index 0000000..eec6949 --- /dev/null +++ b/liteyuki/bot/lifespan.py @@ -0,0 +1,181 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/7/23 下午8:24 +@Author : snowykami +@Email : snowykami@outlook.com +@File : lifespan.py +@Software: PyCharm +""" +from typing import Any, Awaitable, Callable, TypeAlias + +from liteyuki.utils import is_coroutine_callable + +SYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Any] +ASYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Awaitable[Any]] +LIFESPAN_FUNC: TypeAlias = SYNC_LIFESPAN_FUNC | ASYNC_LIFESPAN_FUNC + + +class Lifespan: + def __init__(self) -> None: + """ + 轻雪生命周期管理,启动、停止、重启 + """ + + self.life_flag: int = 0 # 0: 启动前,1: 启动后,2: 停止前,3: 停止后 + + self._before_start_funcs: list[LIFESPAN_FUNC] = [] + self._after_start_funcs: list[LIFESPAN_FUNC] = [] + + self._before_shutdown_funcs: list[LIFESPAN_FUNC] = [] + self._after_shutdown_funcs: list[LIFESPAN_FUNC] = [] + + self._before_restart_funcs: list[LIFESPAN_FUNC] = [] + self._after_restart_funcs: list[LIFESPAN_FUNC] = [] + + self._after_nonebot_init_funcs: list[LIFESPAN_FUNC] = [] + + @staticmethod + async def _run_funcs(funcs: list[LIFESPAN_FUNC]) -> None: + """ + 运行函数 + Args: + funcs: + Returns: + """ + for func in funcs: + if is_coroutine_callable(func): + await func() + else: + func() + + def on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: + """ + 注册启动时的函数 + Args: + func: + Returns: + LIFESPAN_FUNC: + """ + self._before_start_funcs.append(func) + return func + + def on_after_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: + """ + 注册启动时的函数 + Args: + func: + Returns: + LIFESPAN_FUNC: + """ + self._after_start_funcs.append(func) + return func + + def on_before_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: + """ + 注册停止前的函数 + Args: + func: + Returns: + LIFESPAN_FUNC: + """ + self._before_shutdown_funcs.append(func) + return func + + def on_after_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: + """ + 注册停止后的函数 + Args: + func: + + Returns: + LIFESPAN_FUNC: + + """ + self._after_shutdown_funcs.append(func) + return func + + def on_before_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: + """ + 注册重启时的函数 + Args: + func: + Returns: + LIFESPAN_FUNC: + """ + self._before_restart_funcs.append(func) + return func + + def on_after_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: + """ + 注册重启后的函数 + Args: + func: + Returns: + LIFESPAN_FUNC: + """ + self._after_restart_funcs.append(func) + return func + + def on_after_nonebot_init(self, func): + """ + 注册 NoneBot 初始化后的函数 + Args: + func: + + Returns: + + """ + self._after_nonebot_init_funcs.append(func) + return func + + async def before_start(self) -> None: + """ + 启动前 + Returns: + """ + await self._run_funcs(self._before_start_funcs) + + async def after_start(self) -> None: + """ + 启动后 + Returns: + """ + await self._run_funcs(self._after_start_funcs) + + async def before_shutdown(self) -> None: + """ + 停止前 + Returns: + """ + await self._run_funcs(self._before_shutdown_funcs) + + async def after_shutdown(self) -> None: + """ + 停止后 + Returns: + """ + await self._run_funcs(self._after_shutdown_funcs) + + async def before_restart(self) -> None: + """ + 重启前 + Returns: + """ + await self._run_funcs(self._before_restart_funcs) + + async def after_restart(self) -> None: + """ + 重启后 + Returns: + + """ + await self._run_funcs(self._after_restart_funcs) + + async def after_nonebot_init(self) -> None: + """ + NoneBot 初始化后 + Returns: + """ + await self._run_funcs(self._after_nonebot_init_funcs) diff --git a/src/liteyuki/__init__.py b/liteyuki/config.py similarity index 100% rename from src/liteyuki/__init__.py rename to liteyuki/config.py diff --git a/liteyuki/core/__init__.py b/liteyuki/core/__init__.py new file mode 100644 index 0000000..e8f45b2 --- /dev/null +++ b/liteyuki/core/__init__.py @@ -0,0 +1,3 @@ +from .spawn_process import * + + diff --git a/liteyuki/core/spawn_process.py b/liteyuki/core/spawn_process.py new file mode 100644 index 0000000..62c065e --- /dev/null +++ b/liteyuki/core/spawn_process.py @@ -0,0 +1,37 @@ +import threading +from multiprocessing import get_context, Event + +import nonebot +from nonebot import logger + +from liteyuki.plugin.load import load_plugins + +timeout_limit: int = 20 +__all__ = [ + "ProcessingManager", + "nb_run", +] + + +class ProcessingManager: + event: Event = None + + @classmethod + def restart(cls, delay: int = 0): + """ + 发送终止信号 + Args: + delay: 延迟时间,默认为0,单位秒 + Returns: + """ + if cls.event is None: + raise RuntimeError("ProcessingManager 未初始化。") + if delay > 0: + threading.Timer(delay, function=cls.event.set).start() + return + cls.event.set() + + +def nb_run(event, *args, **kwargs): + ProcessingManager.event = event + nonebot.run(*args, **kwargs) diff --git a/src/liteyuki/exception.py b/liteyuki/exception.py similarity index 100% rename from src/liteyuki/exception.py rename to liteyuki/exception.py diff --git a/liteyuki/plugin/__init__.py b/liteyuki/plugin/__init__.py new file mode 100644 index 0000000..485f28b --- /dev/null +++ b/liteyuki/plugin/__init__.py @@ -0,0 +1,17 @@ +from liteyuki.plugin.model import Plugin, PluginMetadata +from liteyuki.plugin.load import load_plugin, _plugins + +__all__ = [ + "PluginMetadata", + "Plugin", + "load_plugin", +] + + +def get_loaded_plugins() -> dict[str, Plugin]: + """ + 获取已加载的插件 + Returns: + dict[str, Plugin]: 插件字典 + """ + return _plugins diff --git a/liteyuki/plugin/load.py b/liteyuki/plugin/load.py new file mode 100644 index 0000000..a34b4ad --- /dev/null +++ b/liteyuki/plugin/load.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/7/23 下午11:59 +@Author : snowykami +@Email : snowykami@outlook.com +@File : load.py +@Software: PyCharm +""" +import os +import traceback +from pathlib import Path +from typing import Optional + +from nonebot import logger + +from liteyuki.plugin.model import Plugin, PluginMetadata +from importlib import import_module + +from liteyuki.utils import path_to_module_name + +_plugins: dict[str, Plugin] = {} + + +def load_plugin(module_path: str | Path) -> Optional[Plugin]: + """加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。 + + 参数: + module_path: 插件名称 `path.to.your.plugin` + 或插件路径 `pathlib.Path(path/to/your/plugin)` + """ + module_path = path_to_module_name(Path(module_path)) if isinstance(module_path, Path) else module_path + try: + module = import_module(module_path) + _plugins[module.__name__] = Plugin( + name=module.__name__, + module=module, + module_name=module_path, + metadata=module.__dict__.get("__plugin_metadata__", None) + ) + logger.opt(colors=True).success( + f'成功加载 轻雪插件 "{module.__name__.split(".")[-1]}"' + ) + return _plugins[module.__name__] + + except Exception as e: + logger.opt(colors=True).success( + f'未能加载 轻雪插件 "{module_path}"' + ) + traceback.print_exc() + return None + + +def load_plugins(*plugin_dir: str) -> set[Plugin]: + """导入文件夹下多个插件 + + 参数: + plugin_dir: 文件夹路径 + """ + plugins = set() + for dir_path in plugin_dir: + # 遍历每一个文件夹下的py文件和包含__init__.py的文件夹,不递归 + for f in os.listdir(dir_path): + path = Path(os.path.join(dir_path, f)) + + module_name = None + if os.path.isfile(path) and f.endswith('.py') and f != '__init__.py': + module_name = f"{path_to_module_name(Path(dir_path))}.{f[:-3]}" + + elif os.path.isdir(path) and os.path.exists(os.path.join(path, '__init__.py')): + module_name = path_to_module_name(path) + + if module_name: + load_plugin(module_name) + if _plugins.get(module_name): + plugins.add(_plugins[module_name]) + return plugins diff --git a/liteyuki/plugin/manager.py b/liteyuki/plugin/manager.py new file mode 100644 index 0000000..9107775 --- /dev/null +++ b/liteyuki/plugin/manager.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/7/23 下午11:59 +@Author : snowykami +@Email : snowykami@outlook.com +@File : manager.py +@Software: PyCharm +""" diff --git a/liteyuki/plugin/model.py b/liteyuki/plugin/model.py new file mode 100644 index 0000000..70f8dab --- /dev/null +++ b/liteyuki/plugin/model.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/7/24 上午12:02 +@Author : snowykami +@Email : snowykami@outlook.com +@File : model.py +@Software: PyCharm +""" +from types import ModuleType +from typing import Optional + +from pydantic import BaseModel + + +class PluginMetadata(BaseModel): + """ + 轻雪插件元数据,由插件编写者提供 + """ + name: str + description: str + usage: str = "" + type: str = "" + homepage: str = "" + running_in_main: bool = True # 是否在主进程运行 + + +class Plugin(BaseModel): + """ + 存储插件信息 + """ + model_config = { + 'arbitrary_types_allowed': True + } + name: str + """插件名称 例如plugin_loader""" + module: ModuleType + """插件模块对象""" + module_name: str + """点分割模块路径 例如a.b.c""" + metadata: Optional[PluginMetadata] = None + + def __hash__(self): + return hash(self.module_name) diff --git a/liteyuki/plugins/plugin_loader/__init__.py b/liteyuki/plugins/plugin_loader/__init__.py new file mode 100644 index 0000000..f674a93 --- /dev/null +++ b/liteyuki/plugins/plugin_loader/__init__.py @@ -0,0 +1,41 @@ +import multiprocessing +import time + +import nonebot +from nonebot import get_driver + +from liteyuki.plugin import PluginMetadata +from liteyuki import get_bot + +__plugin_metadata__ = PluginMetadata( + name="plugin_loader", + description="轻雪插件加载器", + usage="", + type="", + homepage="" +) + +from src.utils import TempConfig, common_db + +liteyuki = get_bot() + + +@liteyuki.on_after_start +def _(): + temp_data = common_db.where_one(TempConfig(), default=TempConfig()) + # 储存重启计时信息 + if temp_data.data.get("reload", False): + delta_time = time.time() - temp_data.data.get("reload_time", 0) + temp_data.data["delta_time"] = delta_time + common_db.save(temp_data) # 更新数据 + + +@liteyuki.on_before_start +def _(): + print("灵温正在启动") + + +@liteyuki.on_after_nonebot_init +async def _(): + print("NoneBot初始化完成") + nonebot.load_plugin("src.liteyuki_main") diff --git a/liteyuki/plugins/plugin_loader/data_source.py b/liteyuki/plugins/plugin_loader/data_source.py new file mode 100644 index 0000000..e744bd8 --- /dev/null +++ b/liteyuki/plugins/plugin_loader/data_source.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/7/23 下午11:21 +@Author : snowykami +@Email : snowykami@outlook.com +@File : data_source.py +@Software: PyCharm +""" diff --git a/src/liteyuki/core/__init__.py b/liteyuki/plugins/process_manager/__init__.py similarity index 100% rename from src/liteyuki/core/__init__.py rename to liteyuki/plugins/process_manager/__init__.py diff --git a/src/liteyuki/plugin/__init__.py b/liteyuki/plugins/resource_loader/__init__.py similarity index 100% rename from src/liteyuki/plugin/__init__.py rename to liteyuki/plugins/resource_loader/__init__.py diff --git a/liteyuki/utils.py b/liteyuki/utils.py new file mode 100644 index 0000000..af95af9 --- /dev/null +++ b/liteyuki/utils.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +""" +一些常用的工具类,部分来源于 nonebot 并遵循其许可进行修改 +""" +import inspect +from pathlib import Path +from typing import Any, Callable + + +def is_coroutine_callable(call: Callable[..., Any]) -> bool: + """ + 判断是否为协程可调用对象 + Args: + call: 可调用对象 + Returns: + bool: 是否为协程可调用对象 + """ + if inspect.isroutine(call): + return inspect.iscoroutinefunction(call) + if inspect.isclass(call): + return False + func_ = getattr(call, "__call__", None) + return inspect.iscoroutinefunction(func_) + + +def path_to_module_name(path: Path) -> str: + """ + 转换路径为模块名 + Args: + path: 路径a/b/c/d -> a.b.c.d + Returns: + str: 模块名 + """ + rel_path = path.resolve().relative_to(Path.cwd().resolve()) + if rel_path.stem == "__init__": + return ".".join(rel_path.parts[:-1]) + else: + return ".".join(rel_path.parts[:-1] + (rel_path.stem,)) diff --git a/main.py b/main.py index 23b557b..44eb1a2 100644 --- a/main.py +++ b/main.py @@ -1,28 +1,6 @@ -import nonebot -from src.utils import adapter_manager, driver_manager, init -from src.utils.base.config import load_from_yaml -from src.utils.base.data_manager import StoredConfig, common_db -from src.utils.base.ly_api import liteyuki_api +from liteyuki import LiteyukiBot +from src.utils import load_from_yaml -if __name__ == "__mp_main__": - # Start as multiprocessing - init() - store_config: dict = common_db.where_one(StoredConfig(), default=StoredConfig()).config - static_config = load_from_yaml("config.yml") - store_config.update(static_config) - driver_manager.init(config=store_config) - adapter_manager.init(store_config) - nonebot.init(**store_config) - adapter_manager.register() - try: - nonebot.load_plugin("src.liteyuki_main") - nonebot.load_from_toml("pyproject.toml") - except BaseException as e: - if not isinstance(e, KeyboardInterrupt): - nonebot.logger.error(f"An error occurred: {e}, Bug will be reported automatically.") - liteyuki_api.bug_report(str(e.__repr__())) - -if __name__ == "__main__": - # Start as __main__ - from src.utils.base.reloader import Reloader - nonebot.run() +if __name__ in ("__main__", "__mp_main__"): + bot = LiteyukiBot(**load_from_yaml("config.yml")) + bot.run() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e65327c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,58 @@ +# PEP 621 project metadata +# See https://www.python.org/dev/peps/pep-0621/ +# This file is for project use, but don`t use with nb-cli +# 此文件为项目所用,请不要和nb-cli一起使用以防被修改 +[tool.poetry] +name = "ryoun-trim" +version = "0" +description = "based on liteyuki6" +authors = ["金羿Eilles"] +license = "MIT & LSO" +package-mode = false + + +[tool.poetry.dependencies] +python = "^3.10" +aiofiles = "~23.2.1" +aiohttp = "~3.9.3" +aiosqlite3 = "~0.3.0" +colored = "~2.2.4" +fastapi = "~0.110.0" +GitPython = "~3.1.42" +httpx = "~0.27.0" +importlib_metadata = "~7.0.2" +jieba = "~0.42.1" +loguru = "~0.7.2" +nb-cli = "~1.4.1" +nonebot-adapter-onebot = "~2.4.3" +nonebot-adapter-satori = "~0.11.5" +nonebot-plugin-alconna = "~0.46.3" +nonebot-plugin-apscheduler = "~0.4.0" +nonebot-plugin-htmlrender = "~0.3.1" +nonebot2 = { version = "~2.3.0", extras = ["fastapi", "httpx", "websockets"] } +numpy = "<2.0.0" +packaging = "~23.1" +psutil = "~5.9.8" +py-cpuinfo = "~9.0.0" +pydantic = "~2.7.0" +Pygments = "~2.17.2" +python-dotenv = "~1.0.1" +pytest = "~8.3.1" +pytz = "~2024.1" +PyYAML = "~6.0.1" +requests = "~2.31.0" +starlette = "~0.36.3" +watchdog = "~4.0.0" + + +[[tool.poetry.source]] +name = "tuna" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" + +[tool.nonebot] + +[project.urls] +homepage = "https://bot.liteyuki.icu" +repository = "https://gitee.com/TriM-Organization/LiteyukiBot-TriM" +documentation = "https://bot.liteyuki.icu" + diff --git a/requirements.txt b/requirements.txt index de0b93f..da4db6d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,9 +23,9 @@ loguru~=0.7.2 importlib_metadata~=7.0.2 requests~=2.31.0 watchdog~=4.0.0 -pillow~=10.2.0 +pillow~=10.0.0 jieba~=0.42.1 -pip~=23.2.1 +aiosqlite3~=0.3.0 fastapi~=0.110.0 python-dotenv~=1.0.1 nonebot_plugin_session diff --git a/src/liteyuki/bot/__init__.py b/src/liteyuki/bot/__init__.py deleted file mode 100644 index e8e8744..0000000 --- a/src/liteyuki/bot/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -import abc - - -class Bot(abc.ABC): - def __init__(self): - pass \ No newline at end of file diff --git a/src/liteyuki/plugins/process_manager/__init__.py b/src/liteyuki/plugins/process_manager/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/liteyuki/plugins/resource_loader/__init__.py b/src/liteyuki/plugins/resource_loader/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/liteyuki_main/__init__.py b/src/liteyuki_main/__init__.py index b54fb04..20e9f3a 100644 --- a/src/liteyuki_main/__init__.py +++ b/src/liteyuki_main/__init__.py @@ -11,24 +11,15 @@ __plugin_meta__ = PluginMetadata( usage="", homepage="https://github.com/snowykami/LiteyukiBot", extra={ - "liteyuki" : True, - "toggleable": False, - } + "liteyuki": True, + "toggleable": False, + }, ) from ..utils.base.language import Language, get_default_lang_code -print("\033[34m" + r""" - __ ______ ________ ________ __ __ __ __ __ __ ______ -/ | / |/ |/ |/ \ / |/ | / |/ | / |/ | -$$ | $$$$$$/ $$$$$$$$/ $$$$$$$$/ $$ \ /$$/ $$ | $$ |$$ | /$$/ $$$$$$/ -$$ | $$ | $$ | $$ |__ $$ \/$$/ $$ | $$ |$$ |/$$/ $$ | -$$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |$$ $$< $$ | -$$ | $$ | $$ | $$$$$/ $$$$/ $$ | $$ |$$$$$ \ $$ | -$$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \__$$ |$$ |$$ \ _$$ |_ -$$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ | -$$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/ -""" + "\033[0m") sys_lang = Language(get_default_lang_code()) -nonebot.logger.info(sys_lang.get("main.current_language", LANG=sys_lang.get("language.name"))) +nonebot.logger.info( + sys_lang.get("main.current_language", LANG=sys_lang.get("language.name")) +) diff --git a/src/liteyuki_main/core.py b/src/liteyuki_main/core.py index 1619560..addec68 100644 --- a/src/liteyuki_main/core.py +++ b/src/liteyuki_main/core.py @@ -16,9 +16,12 @@ from src.utils.base.data_manager import StoredConfig, TempConfig, common_db from src.utils.base.language import get_user_lang from src.utils.base.ly_typing import T_Bot, T_MessageEvent from src.utils.message.message import MarkdownMessage as md, broadcast_to_superusers -from src.utils.base.reloader import Reloader + +# from src.liteyuki.core import Reloader from src.utils import event as event_utils, satori_utils +from liteyuki.core import ProcessingManager from .api import update_liteyuki +from liteyuki.bot import get_bot from ..utils.base.ly_function import get_function require("nonebot_plugin_alconna") @@ -115,7 +118,9 @@ async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent): ) common_db.save(temp_data) - Reloader.reload(0) + # Reloader.reload(0) + bot = get_bot() + bot.restart() @on_alconna( @@ -314,7 +319,6 @@ async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher result = str(e) args_show = "\n".join("- %s: %s" % (k, v) for k, v in args_dict.items()) - print(f"API: {api_name}\n\nArgs: \n{args_show}\n\nResult: {result}") await matcher.finish(f"API: {api_name}\n\nArgs: \n{args_show}\n\nResult: {result}") @@ -376,12 +380,19 @@ async def test_for_md_image(bot: T_Bot, api: str, data: dict): @driver.on_startup async def on_startup(): - temp_data = common_db.where_one(TempConfig(), default=TempConfig()) - # 储存重启信息 - if temp_data.data.get("reload", False): - delta_time = time.time() - temp_data.data.get("reload_time", 0) - temp_data.data["delta_time"] = delta_time - common_db.save(temp_data) # 更新数据 + # temp_data = common_db.where_one(TempConfig(), default=TempConfig()) + # # 储存重启信息 + # if temp_data.data.get("reload", False): + # delta_time = time.time() - temp_data.data.get("reload_time", 0) + # temp_data.data["delta_time"] = delta_time + # common_db.save(temp_data) # 更新数据 + """ + 该部分迁移至轻雪生命周期 + Returns: + + """ + + pass @driver.on_shutdown @@ -407,7 +418,7 @@ async def _(bot: T_Bot): if isinstance(bot, satori.Bot): await bot.send_message( channel_id=reload_session_id, - message="灵温 重载耗时 %.2f 秒" % delta_time, + message="轻雪核心 重载耗时 {:.2f} 秒\n*此数据仅作参考,具体计时请以实际为准\n灵温 预计体感重载耗时 {:.2f} 秒".format(delta_time,time.time() - temp_data.data.get("reload_time", 0)), ) else: await bot.call_api( @@ -415,7 +426,7 @@ async def _(bot: T_Bot): message_type=reload_session_type, user_id=reload_session_id, group_id=reload_session_id, - message="灵温 重载耗时 %.2f 秒" % delta_time, + message="轻雪核心 重载耗时 {:.2f} 秒\n*此数据仅作参考,具体计时请以实际为准\n灵温 预计体感重载耗时 {:.2f} 秒".format(delta_time,time.time() - temp_data.data.get("reload_time", 0)), ) @@ -426,9 +437,9 @@ async def every_day_update(): result, logs = update_liteyuki() pip.main(["install", "-r", "requirements.txt"]) if result: - await broadcast_to_superusers(f"灵温已更新: ```\n{logs}\n```") - nonebot.logger.info(f"灵温已更新: {logs}") - Reloader.reload(5) + await broadcast_to_superusers(f"灵温已更新:```\n{logs}\n```") + nonebot.logger.info(f"灵温已更新:{logs}") + ProcessingManager.restart(3) else: nonebot.logger.info(logs) diff --git a/src/liteyuki_main/dev.py b/src/liteyuki_main/dev.py index 72eb330..052ea56 100644 --- a/src/liteyuki_main/dev.py +++ b/src/liteyuki_main/dev.py @@ -2,28 +2,28 @@ import nonebot from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler +from liteyuki.bot import get_bot from src.utils.base.config import get_config -from src.utils.base.reloader import Reloader +from liteyuki.core import ProcessingManager from src.utils.base.resource import load_resources if get_config("debug", False): + liteyuki_bot = get_bot() + src_directories = ( "src/liteyuki_main", "src/plugins", "src/utils", ) - src_excludes_extensions = ( - "pyc", - ) + src_excludes_extensions = ("pyc",) res_directories = ( "src/resources", "resources", ) - nonebot.logger.info("Liteyuki Reload enabled, watching for file changes...") - + nonebot.logger.info("已启用 Liteyuki Reload ,正在监测文件变动。") class CodeModifiedHandler(FileSystemEventHandler): """ @@ -31,12 +31,14 @@ if get_config("debug", False): """ def on_modified(self, event): - if event.src_path.endswith( - src_excludes_extensions) or event.is_directory or "__pycache__" in event.src_path: + if ( + event.src_path.endswith(src_excludes_extensions) + or event.is_directory + or "__pycache__" in event.src_path + ): return - nonebot.logger.info(f"{event.src_path} modified, reloading bot...") - Reloader.reload() - + nonebot.logger.info(f"文件 {event.src_path} 变更,正在重载…") + liteyuki_bot.restart() class ResourceModifiedHandler(FileSystemEventHandler): """ @@ -44,10 +46,9 @@ if get_config("debug", False): """ def on_modified(self, event): - nonebot.logger.info(f"{event.src_path} modified, reloading resource...") + nonebot.logger.info(f"资源 {event.src_path} 变更,重载资源包…") load_resources() - code_modified_handler = CodeModifiedHandler() resource_modified_handle = ResourceModifiedHandler() diff --git a/src/liteyuki_main/loader.py b/src/liteyuki_main/loader.py index a09393c..eb644d4 100644 --- a/src/liteyuki_main/loader.py +++ b/src/liteyuki_main/loader.py @@ -6,10 +6,13 @@ from src.utils.base.data_manager import InstalledPlugin, plugin_db from src.utils.base.resource import load_resources from src.utils.message.tools import check_for_package +from liteyuki import get_bot + load_resources() init_log() driver = get_driver() +liteyuki_bot = get_bot() @driver.on_startup @@ -18,14 +21,47 @@ async def load_plugins(): # 从数据库读取已安装的插件 if not get_config("safe_mode", False): # 安全模式下,不加载插件 - installed_plugins: list[InstalledPlugin] = plugin_db.where_all(InstalledPlugin()) + installed_plugins: list[InstalledPlugin] = plugin_db.where_all( + InstalledPlugin() + ) if installed_plugins: for installed_plugin in installed_plugins: if not check_for_package(installed_plugin.module_name): nonebot.logger.error( - f"{installed_plugin.module_name} not installed, but in loading database. please run `npm fixup` in chat to reinstall it.") + f"插件 {installed_plugin.module_name} 在加载列表中但未安装。请使用超管账户对机器人发送 `npm fixup` 以重新安装。" + ) else: nonebot.load_plugin(installed_plugin.module_name) nonebot.plugin.load_plugins("plugins") else: - nonebot.logger.info("Safe mode is on, no plugin loaded.") + nonebot.logger.info("安全模式已启动,未加载任何插件。") + + +@liteyuki_bot.on_before_start +async def _(): + print("启动前") + + +@liteyuki_bot.on_after_start +async def _(): + print("启动后") + + +@liteyuki_bot.on_before_shutdown +async def _(): + print("停止前") + + +@liteyuki_bot.on_after_shutdown +async def _(): + print("停止后") + + +@liteyuki_bot.on_before_restart +async def _(): + print("重启前") + + +@liteyuki_bot.on_after_restart +async def _(): + print("重启后") diff --git a/src/plugins/liteyuki_crt_utils/__init__.py b/src/plugins/liteyuki_crt_utils/__init__.py index 21c8066..4ef30f3 100644 --- a/src/plugins/liteyuki_crt_utils/__init__.py +++ b/src/plugins/liteyuki_crt_utils/__init__.py @@ -1,4 +1,7 @@ +import multiprocessing + from nonebot.plugin import PluginMetadata +from liteyuki.plugin import get_loaded_plugins from .rt_guide import * from .crt_matchers import * @@ -14,3 +17,5 @@ __plugin_meta__ = PluginMetadata( "default_enable": True, } ) + +print("已加载插件:", len(get_loaded_plugins())) \ No newline at end of file diff --git a/src/plugins/liteyuki_pacman/npm.py b/src/plugins/liteyuki_pacman/npm.py index eb251e2..c844da8 100644 --- a/src/plugins/liteyuki_pacman/npm.py +++ b/src/plugins/liteyuki_pacman/npm.py @@ -554,7 +554,7 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot, npm: Matcher): Subcommand( disable, Args["group_id", str, None], - alias=["d", "停用"], + alias=["d", "停用","禁用"], ), ), permission=SUPERUSER | GROUP_OWNER | GROUP_ADMIN, diff --git a/src/plugins/liteyuki_webdash/__init__.py b/src/plugins/webdash/__init__.py similarity index 97% rename from src/plugins/liteyuki_webdash/__init__.py rename to src/plugins/webdash/__init__.py index 6f46e9a..faab14b 100644 --- a/src/plugins/liteyuki_webdash/__init__.py +++ b/src/plugins/webdash/__init__.py @@ -1,21 +1,21 @@ -from nonebot.plugin import PluginMetadata - -from .main import * - -__author__ = "snowykami" -__plugin_meta__ = PluginMetadata( - name="网页监控面板", - description="网页监控面板,用于查看机器人的状态和信息", - usage=( - "访问 127.0.0.1:port 查看机器人的状态信息\n" - "stat msg -g|--group [group_id] 查看群的统计信息,不带参数为全群\n" - "配置项:custom_domain,自定义域名,通常对外用,内网无需" - ), - type="application", - homepage="https://github.com/snowykami/LiteyukiBot", - extra={ - "liteyuki" : True, - "toggleable" : False, - "default_enable": True, - } -) +from nonebot.plugin import PluginMetadata + +from .main import * + +__author__ = "snowykami" +__plugin_meta__ = PluginMetadata( + name="网页监控面板", + description="网页监控面板,用于查看机器人的状态和信息", + usage=( + "访问 127.0.0.1:port 查看机器人的状态信息\n" + "stat msg -g|--group [group_id] 查看群的统计信息,不带参数为全群\n" + "配置项:custom_domain,自定义域名,通常对外用,内网无需" + ), + type="application", + homepage="https://github.com/snowykami/LiteyukiBot", + extra={ + "liteyuki" : True, + "toggleable" : False, + "default_enable": True, + } +) diff --git a/src/plugins/liteyuki_webdash/common.py b/src/plugins/webdash/common.py similarity index 96% rename from src/plugins/liteyuki_webdash/common.py rename to src/plugins/webdash/common.py index 29b4ebf..b9abd37 100644 --- a/src/plugins/liteyuki_webdash/common.py +++ b/src/plugins/webdash/common.py @@ -1,4 +1,4 @@ -from fastapi import FastAPI -from nonebot import get_app - +from fastapi import FastAPI +from nonebot import get_app + app: FastAPI = get_app() \ No newline at end of file diff --git a/src/plugins/liteyuki_webdash/main.py b/src/plugins/webdash/main.py similarity index 94% rename from src/plugins/liteyuki_webdash/main.py rename to src/plugins/webdash/main.py index 3be44c2..40506b1 100644 --- a/src/plugins/liteyuki_webdash/main.py +++ b/src/plugins/webdash/main.py @@ -1,10 +1,10 @@ -from fastapi import FastAPI -from nonebot import get_app -from .restful_api import * - - -@app.get("/ping") -async def root(): - return { - "message": "pong" - } +from fastapi import FastAPI +from nonebot import get_app +from .restful_api import * + + +@app.get("/ping") +async def root(): + return { + "message": "pong" + } diff --git a/src/plugins/liteyuki_webdash/restful_api.py b/src/plugins/webdash/restful_api.py similarity index 95% rename from src/plugins/liteyuki_webdash/restful_api.py rename to src/plugins/webdash/restful_api.py index 3235b38..873cb69 100644 --- a/src/plugins/liteyuki_webdash/restful_api.py +++ b/src/plugins/webdash/restful_api.py @@ -1,24 +1,24 @@ -from fastapi import FastAPI, APIRouter -from .common import * - -device_info_router = APIRouter(prefix="/api/device-info") -bot_info_router = APIRouter(prefix="/api/bot-info") - - -@device_info_router.get("/") -async def device_info(): - print("Hello Device Info") - return { - "message": "Hello Device Info" - } - - -@bot_info_router.get("/") -async def bot_info(): - return { - "message": "Hello Bot Info" - } - - -app.include_router(device_info_router) -app.include_router(bot_info_router) +from fastapi import FastAPI, APIRouter +from .common import * + +device_info_router = APIRouter(prefix="/api/device-info") +bot_info_router = APIRouter(prefix="/api/bot-info") + + +@device_info_router.get("/") +async def device_info(): + print("Hello Device Info") + return { + "message": "Hello Device Info" + } + + +@bot_info_router.get("/") +async def bot_info(): + return { + "message": "Hello Bot Info" + } + + +app.include_router(device_info_router) +app.include_router(bot_info_router) diff --git a/src/test/action_test.py b/src/test/action_test.py deleted file mode 100644 index 74d7c54..0000000 --- a/src/test/action_test.py +++ /dev/null @@ -1,29 +0,0 @@ -import nonebot -from src.utils import adapter_manager, driver_manager, init -from src.utils.base.config import load_from_yaml -from src.utils.base.data_manager import StoredConfig, common_db -from src.utils.base.ly_api import liteyuki_api - -if __name__ == "__mp_main__": - # Start as multiprocessing - init() - store_config: dict = common_db.where_one(StoredConfig(), default=StoredConfig()).config - static_config = load_from_yaml("config.yml") - store_config.update(static_config) - driver_manager.init(config=store_config) - adapter_manager.init(store_config) - nonebot.init(**store_config) - adapter_manager.register() - try: - nonebot.load_plugin("liteyuki.liteyuki_main") - nonebot.load_from_toml("pyproject.toml") - except BaseException as e: - if not isinstance(e, KeyboardInterrupt): - nonebot.logger.error(f"An error occurred: {e}, Bug will be reported automatically.") - liteyuki_api.bug_report(str(e.__repr__())) - -if __name__ == "__main__": - # Start as __main__ - from src.utils.base.reloader import Reloader - - nonebot.run() diff --git a/src/utils/__init__.py b/src/utils/__init__.py index b8502b3..a7e5e06 100644 --- a/src/utils/__init__.py +++ b/src/utils/__init__.py @@ -6,8 +6,8 @@ import time import nonebot -__NAME__ = "LiteyukiBot-TriMO" -__VERSION__ = "6.3.3" # 60201 +__NAME__ = "尹灵温|轻雪-睿乐" +__VERSION__ = "6.3.4" # 60201 # __VERSION_I__ = 99060303 import requests diff --git a/src/utils/adapter_manager/satori.py b/src/utils/adapter_manager/satori.py index c5edc80..6ddef50 100644 --- a/src/utils/adapter_manager/satori.py +++ b/src/utils/adapter_manager/satori.py @@ -7,14 +7,14 @@ from nonebot.adapters import satori def init(config: dict): if config.get("satori", None) is None: - nonebot.logger.info("Satori config not found, skip Satori init.") + nonebot.logger.info("未查见 Satori 的配置文档,将跳过 Satori 初始化") return None satori_config = config.get("satori") if not satori_config.get("enable", False): - nonebot.logger.info("Satori not enabled, skip Satori init.") + nonebot.logger.info("未启用 Satori ,将跳过 Satori 初始化") return None if os.getenv("SATORI_CLIENTS", None) is not None: - nonebot.logger.info("Satori clients already set in environment variable, skip.") + nonebot.logger.info("Satori 客户端已设入环境变量,跳过此步。") os.environ["SATORI_CLIENTS"] = json.dumps(satori_config.get("hosts", []), ensure_ascii=False) config['satori_clients'] = satori_config.get("hosts", []) return diff --git a/src/utils/base/config.py b/src/utils/base/config.py index a53f8f9..84045a9 100644 --- a/src/utils/base/config.py +++ b/src/utils/base/config.py @@ -20,25 +20,26 @@ class SatoriNodeConfig(BaseModel): class SatoriConfig(BaseModel): - comment: str = "These features are still in development. Do not enable in production environment." + comment: str = "此皆正处于开发之中,切勿在生产环境中启用。" enable: bool = False hosts: List[SatoriNodeConfig] = [SatoriNodeConfig()] class BasicConfig(BaseModel): host: str = "127.0.0.1" - port: int = 20216 + port: int = 20247 superusers: list[str] = [] command_start: list[str] = ["/", ""] - nickname: list[str] = [f"LiteyukiBot-{random_hex_string(6)}"] + nickname: list[str] = [f"灵温-{random_hex_string(6)}"] satori: SatoriConfig = SatoriConfig() + 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") + nonebot.logger.warning(f"未找到配置文件 {file} ,已创建默认配置,请修改后重启。") with open(file, "w", encoding="utf-8") as f: yaml.dump(BasicConfig().dict(), f, default_flow_style=False) @@ -46,7 +47,7 @@ def load_from_yaml(file: str) -> dict: conf = init_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") + nonebot.logger.warning(f"配置文件 {file} 为空,已创建默认配置,请修改后重启。") conf = BasicConfig().dict() return conf @@ -95,6 +96,8 @@ def init_conf(conf: dict) -> dict: """ # 若command_start中无"",则添加必要命令头,开启alconna_use_command_start防止冲突 - if "" not in conf.get("command_start", []): - conf["alconna_use_command_start"] = True + # 以下内容由于issue #53 被注释 + # if "" not in conf.get("command_start", []): + # conf["alconna_use_command_start"] = True return conf + pass diff --git a/src/utils/base/ly_api.py b/src/utils/base/ly_api.py index 9b8d16d..ffbf18a 100644 --- a/src/utils/base/ly_api.py +++ b/src/utils/base/ly_api.py @@ -20,8 +20,9 @@ class LiteyukiAPI: self.data = json.loads(f.read()) self.liteyuki_id = self.data.get("liteyuki_id") self.report = load_from_yaml("config.yml").get("auto_report", True) + if self.report: - nonebot.logger.info("Auto bug report is enabled") + nonebot.logger.info("已启用自动上报") @property def device_info(self) -> dict: @@ -37,10 +38,10 @@ class LiteyukiAPI: "python" : f"{platform.python_implementation()} {platform.python_version()}", "os" : f"{platform.system()} {platform.version()} {platform.machine()}", "cpu" : f"{psutil.cpu_count(logical=False)}c{psutil.cpu_count()}t{psutil.cpu_freq().current}MHz", - "memory_total": f"{psutil.virtual_memory().total / 1024 / 1024 / 1024:.2f}GB", - "memory_used" : f"{psutil.virtual_memory().used / 1024 / 1024 / 1024:.2f}GB", - "memory_bot" : f"{psutil.Process(os.getpid()).memory_info().rss / 1024 / 1024:.2f}MB", - "disk" : f"{psutil.disk_usage('/').total / 1024 / 1024 / 1024:.2f}GB" + "memory_total": f"{psutil.virtual_memory().total / 1024 ** 3:.2f}吉字节", + "memory_used" : f"{psutil.virtual_memory().used / 1024 ** 3:.2f}吉字节", + "memory_bot" : f"{psutil.Process(os.getpid()).memory_info().rss / 1024 ** 2:.2f}兆字节", + "disk" : f"{psutil.disk_usage('/').total / 1024 ** 3:.2f}吉字节" } def bug_report(self, content: str): @@ -53,7 +54,7 @@ class LiteyukiAPI: """ if self.report: - nonebot.logger.warning(f"Reporting bug...: {content}") + nonebot.logger.warning(f"正在上报查误:{content}") url = "https://api.liteyuki.icu/bug_report" data = { "liteyuki_id": self.liteyuki_id, @@ -62,11 +63,11 @@ class LiteyukiAPI: } resp = requests.post(url, json=data) if resp.status_code == 200: - nonebot.logger.success(f"Bug report sent successfully, report_id: {resp.json().get('report_id')}") + nonebot.logger.success(f"成功上报差误信息,报文ID为:{resp.json().get('report_id')}") else: - nonebot.logger.error(f"Bug report failed: {resp.text}") + nonebot.logger.error(f"差误上报错误:{resp.text}") else: - nonebot.logger.warning(f"Bug report is disabled: {content}") + nonebot.logger.warning(f"已禁用自动上报:{content}") async def heartbeat_report(self): """ @@ -77,14 +78,11 @@ class LiteyukiAPI: url = "https://api.liteyuki.icu/heartbeat" data = { "liteyuki_id": self.liteyuki_id, - "version": __VERSION__, + "version" : __VERSION__, } async with aiohttp.ClientSession() as session: async with session.post(url, json=data) as resp: if resp.status == 200: - nonebot.logger.success("Heartbeat sent successfully") + nonebot.logger.success("心跳成功送达。") else: - nonebot.logger.error(f"Heartbeat failed: {await resp.text()}") - - -liteyuki_api = LiteyukiAPI() + nonebot.logger.error(f"休克:{await resp.text()}") \ No newline at end of file diff --git a/src/utils/driver_manager/auto_set_env.py b/src/utils/driver_manager/auto_set_env.py index f3b2f20..a069580 100644 --- a/src/utils/driver_manager/auto_set_env.py +++ b/src/utils/driver_manager/auto_set_env.py @@ -9,13 +9,12 @@ from .defines import * def auto_set_env(config: dict): dotenv.load_dotenv(".env") if os.getenv("DRIVER", None) is not None: - print(os.getenv("DRIVER")) - nonebot.logger.info("Driver already set in environment variable, skip auto configure.") + nonebot.logger.info("Driver 已设入环境变量中,将跳过自动配置环节。") return if config.get("satori", {'enable': False}).get("enable", False): os.environ["DRIVER"] = get_driver_string(ASGI_DRIVER, HTTPX_DRIVER, WEBSOCKETS_DRIVER) - nonebot.logger.info("Enable Satori, set driver to ASGI+HTTPX+WEBSOCKETS") + nonebot.logger.info("已启用 Satori,将 driver 设为 ASGI+HTTPX+WEBSOCKETS") else: os.environ["DRIVER"] = get_driver_string(ASGI_DRIVER) - nonebot.logger.info("Disable Satori, set driver to ASGI") + nonebot.logger.info("已禁用 Satori,将 driver 设为 ASGI") return diff --git a/src/utils/network/__init__.py b/src/utils/io/__init__.py similarity index 87% rename from src/utils/network/__init__.py rename to src/utils/io/__init__.py index 62c4eac..bc30c16 100644 --- a/src/utils/network/__init__.py +++ b/src/utils/io/__init__.py @@ -1,15 +1,17 @@ -from aiohttp import ClientSession - - -async def simple_get(url: str) -> str: - """ - 简单异步get请求 - Args: - url: - - Returns: - - """ - async with ClientSession() as session: - async with session.get(url) as resp: - return await resp.text() +from aiohttp import ClientSession + +from .net import * +from .file import * + + +async def simple_get(url: str) -> str: + """ + 简单异步get请求 + Args: + url: + + Returns: + """ + async with ClientSession() as session: + async with session.get(url) as resp: + return await resp.text() diff --git a/src/utils/io/file.py b/src/utils/io/file.py new file mode 100644 index 0000000..c25a9c7 --- /dev/null +++ b/src/utils/io/file.py @@ -0,0 +1,29 @@ +import aiofiles + + +async def write_file( + file_path: str, + content: str | bytes, + mode: str = "w" +): + """ + 写入文件 + Args: + mode: 写入模式 + file_path: 文件路径 + content: 内容 + """ + async with aiofiles.open(file_path, mode) as f: + await f.write(content) + + +async def read_file(file_path: str, mode: str = "r") -> str: + """ + 读取文件 + Args: + file_path: 文件路径 + mode: 读取模式 + Returns: + """ + async with aiofiles.open(file_path, mode) as f: + return await f.read() diff --git a/src/utils/io/net.py b/src/utils/io/net.py new file mode 100644 index 0000000..c346aa4 --- /dev/null +++ b/src/utils/io/net.py @@ -0,0 +1,12 @@ +async def fetch(url: str) -> str: + """ + 异步get请求 + Args: + url: + + Returns: + + """ + async with ClientSession() as session: + async with session.get(url) as resp: + return await resp.text() diff --git a/test/test_core.py b/test/test_core.py new file mode 100644 index 0000000..81da20e --- /dev/null +++ b/test/test_core.py @@ -0,0 +1,5 @@ +from src.liteyuki import LiteyukiBot + +if __name__ == "__main__": + lyb = LiteyukiBot() + lyb.run() \ No newline at end of file diff --git a/src/liteyuki/plugins/__init__.py b/test/test_lyapi.py similarity index 100% rename from src/liteyuki/plugins/__init__.py rename to test/test_lyapi.py diff --git a/src/liteyuki/plugins/plugin_loader/__init__.py b/test/test_lyfunc.py similarity index 100% rename from src/liteyuki/plugins/plugin_loader/__init__.py rename to test/test_lyfunc.py