diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 00000000..2b5ac2e1 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,40 @@ +## Plugin Commands + +### 轻雪`liteyuki` + +```shell +reload-liteyuki # 重载轻雪 +update-liteyuki # 更新轻雪 +liteyuki # 查看轻雪信息 +``` + +### 轻雪Nonebot插件管理 `liteyuki_npm` + +```shell +npm update # 更新插件索引 +npm install # 安装插件 +npm uninstall # 卸载插件 +npm search # 搜索插件 +------ +Alias: npm 插件, update 更新, install 安装, uninstall 卸载, search 搜索 +``` + +```shell +enable # 启用插件 +disable # 禁用插件 +enable-global # 全局启用插件 +disable-global # 全局禁用插件 +list-plugin # 列出所有插件 +------ +Alias: enable 启用, disable 停用, enable-global 全局启用, disable-global 全局停用, list-plugin 列出插件/插件列表 +``` + +### 轻雪用户管理`liteyuki_user` + +```shell +profile # 查看用户信息菜单 +profile set [value] # 设置用户信息或打开属性设置菜单 +profile get # 获取用户信息 +------ +Alias: profile 个人信息, set 设置, get 查询 +``` \ No newline at end of file diff --git a/liteyuki/liteyuki_main/core.py b/liteyuki/liteyuki_main/core.py index 5116607f..ec29c45f 100644 --- a/liteyuki/liteyuki_main/core.py +++ b/liteyuki/liteyuki_main/core.py @@ -1,8 +1,12 @@ from nonebot import require from nonebot.permission import SUPERUSER +from git import Repo from liteyuki.utils.config import config -from liteyuki.utils.ly_typing import T_Bot +from liteyuki.utils.ly_typing import T_Bot, T_MessageEvent + +from .reloader import Reloader +from ..utils.message import send_markdown require("nonebot_plugin_alconna") from nonebot_plugin_alconna import on_alconna, Alconna @@ -14,7 +18,46 @@ cmd_liteyuki = on_alconna( permission=SUPERUSER ) +update_liteyuki = on_alconna( + Alconna( + ["update-liteyuki", "更新轻雪"] + ), + permission=SUPERUSER +) + +reload_liteyuki = on_alconna( + Alconna( + ["reload-liteyuki", "重启轻雪"] + ), + permission=SUPERUSER +) + @cmd_liteyuki.handle() async def _(bot: T_Bot): await cmd_liteyuki.finish(f"Hello, Liteyuki!\nBot {bot.self_id}\nLiteyukiID {config.get('liteyuki_id', 'No')}") + + +@update_liteyuki.handle() +async def _(bot: T_Bot, event: T_MessageEvent): + # 使用git pull更新 + origins = ["origin", "origin2"] + repo = Repo(".") + for origin in origins: + try: + repo.remotes[origin].pull() + break + except Exception as e: + print(f"Pull from {origin} failed: {e}") + logs = repo.git.log('--pretty=format:%H %s') + reply = "Liteyuki updated!\n" + reply += f"```\n{logs}\n```" + print(list(repo.iter_commits())) + + await send_markdown(reply, bot, event=event, at_sender=False) + + +@reload_liteyuki.handle() +async def _(): + await reload_liteyuki.send("Liteyuki reloading") + Reloader.reload(3) diff --git a/liteyuki/liteyuki_main/reloader.py b/liteyuki/liteyuki_main/reloader.py new file mode 100644 index 00000000..093ff098 --- /dev/null +++ b/liteyuki/liteyuki_main/reloader.py @@ -0,0 +1,67 @@ +import threading +from multiprocessing import get_context + +import nonebot +from nonebot import logger +from typing import List, Optional + +from nonebot import get_driver +from pydantic import BaseSettings + + +reboot_grace_time_limit: int = 20 + +_nb_run = nonebot.run + + +class Reloader: + event: threading.Event = None + + @classmethod + def reload(cls, delay: int = 0): + if cls.event is None: + raise RuntimeError() + if delay > 0: + threading.Timer(delay, function=cls.event.set).start() + return + cls.event.set() + + +def _run(ev: threading.Event, *args, **kwargs): + Reloader.event = ev + _nb_run(*args, **kwargs) + + +def run(*args, **kwargs): + should_exit = False + ctx = get_context("spawn") + while not should_exit: + event = ctx.Event() + process = ctx.Process( + target=_run, + args=( + event, + *args, + ), + kwargs=kwargs, + ) + process.start() + while not should_exit: + if event.wait(1): + logger.info("Receive reboot event") + process.terminate() + process.join(reboot_grace_time_limit) + if process.is_alive(): + logger.warning( + f"Cannot shutdown gracefully in {reboot_grace_time_limit} second, force kill process." + ) + process.kill() + break + elif process.is_alive(): + continue + else: + # Process stoped without setting event + should_exit = True + + +nonebot.run = run diff --git a/liteyuki/plugins/liteyuki_npm/manager.py b/liteyuki/plugins/liteyuki_npm/manager.py index f286ba75..d38409c9 100644 --- a/liteyuki/plugins/liteyuki_npm/manager.py +++ b/liteyuki/plugins/liteyuki_npm/manager.py @@ -29,22 +29,14 @@ list_plugins = on_alconna( toggle_plugin = on_alconna( Alconna( - ["enable", "disable"], + ["enable", "disable", "启用", "停用"], Args["plugin_name", str], ) ) toggle_plugin_global = on_alconna( Alconna( - ["enable-global", "disable-global"], - Args["plugin_name", str], - ), - permission=SUPERUSER -) - -global_toggle = on_alconna( - Alconna( - ["toggle-global"], + ["enable-global", "disable-global", "全局启用", "全局停用"], Args["plugin_name", str], ), permission=SUPERUSER @@ -126,7 +118,7 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot): ulang = get_user_lang(str(event.user_id)) plugin_module_name = result.args.get("plugin_name") - toggle = result.header_result == "enable-plugin" # 判断是启用还是停用 + toggle = result.header_result in ["enable-plugin", "启用"] # 判断是启用还是停用 session_enable = get_plugin_session_enable(event, plugin_module_name) # 获取插件当前状态 @@ -189,7 +181,7 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot): ulang = get_user_lang(str(event.user_id)) plugin_module_name = result.args.get("plugin_name") - toggle = result.header_result == "enable-global" + toggle = result.header_result in ["enable-global", "全局启用"] can_be_toggled = get_plugin_can_be_toggle(plugin_module_name) if not can_be_toggled: await toggle_plugin_global.finish(ulang.get("npm.plugin_cannot_be_toggled", NAME=plugin_module_name)) @@ -238,6 +230,4 @@ async def pre_handle(event: Event, matcher: Matcher): @Bot.on_calling_api async def _(bot: Bot, api: str, data: dict[str, any]): - # TODO 插件启用/停用检查hook - plugin = current_plugin nonebot.logger.info(f"Plugin Callapi: {api}: {data}") diff --git a/requirements.txt b/requirements.txt index 64fc67cf..5ec308e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ arclet-alconna==1.8.5 arclet-alconna-tools==0.7.0 colored==2.2.4 dash==2.16.1 +GitPython==3.1.42 nonebot2[fastapi]==2.2.1 nonebot-adapter-onebot==2.4.3 nonebot-plugin-alconna==0.41.0