diff --git a/.gitignore b/.gitignore index fc9e590e..afb5c743 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ _config.yml config.yml config.example.yml compile.bat +liteyuki/resources/templates/latest-debug.html # vuepress .github diff --git a/docs/usage/basic_command.md b/docs/usage/basic_command.md index 9672d126..6b66ad96 100644 --- a/docs/usage/basic_command.md +++ b/docs/usage/basic_command.md @@ -39,7 +39,7 @@ category: 使用手册 [SOA]disable # 禁用插件 [S]enable-global # 全局启用插件 [S]disable-global # 全局禁用插件 -list-plugin # 列出所有插件 +list-plugin [page] [num] # 列出所有插件 page为页数,num为每页显示数量 # 受限于Nonebot的钩子函数,目前只能阻断消息事件的传入,对于主动推送消息的插件,无法将其阻止 ------ 别名: enable 启用, disable 停用, enable-global 全局启用, disable-global 全局停用, list-plugin 列出插件/插件列表 diff --git a/liteyuki/__init__.py b/liteyuki/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/liteyuki/bin/libhello.dll b/liteyuki/bin/libhello.dll deleted file mode 100644 index 5b10e00b..00000000 Binary files a/liteyuki/bin/libhello.dll and /dev/null differ diff --git a/liteyuki/liteyuki_main/__init__.py b/liteyuki/liteyuki_main/__init__.py index 1b20d380..470b1298 100644 --- a/liteyuki/liteyuki_main/__init__.py +++ b/liteyuki/liteyuki_main/__init__.py @@ -17,6 +17,18 @@ __plugin_meta__ = PluginMetadata( } ) +print("\033[34m" + r""" + __ ______ ________ ________ __ __ __ __ __ __ ______ +/ | / |/ |/ |/ \ / |/ | / |/ | / |/ | +$$ | $$$$$$/ $$$$$$$$/ $$$$$$$$/ $$ \ /$$/ $$ | $$ |$$ | /$$/ $$$$$$/ +$$ | $$ | $$ | $$ |__ $$ \/$$/ $$ | $$ |$$ |/$$/ $$ | +$$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |$$ $$< $$ | +$$ | $$ | $$ | $$$$$/ $$$$/ $$ | $$ |$$$$$ \ $$ | +$$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \__$$ |$$ |$$ \ _$$ |_ +$$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ | +$$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/ +""" + "\033[0m") + sys_lang = get_default_lang() nonebot.logger.info(sys_lang.get("main.current_language", LANG=sys_lang.get("language.name"))) nonebot.logger.info(sys_lang.get("main.enable_webdash", URL=f"http://127.0.0.1:{config.get('port', 20216)}")) diff --git a/liteyuki/liteyuki_main/core.py b/liteyuki/liteyuki_main/core.py index bfd2929c..4fec7de9 100644 --- a/liteyuki/liteyuki_main/core.py +++ b/liteyuki/liteyuki_main/core.py @@ -1,7 +1,6 @@ import base64 from typing import Any -import nonebot import pip from git import Repo from nonebot import Bot, require, get_driver @@ -13,12 +12,10 @@ from liteyuki.utils.data_manager import StoredConfig, common_db from liteyuki.utils.language import get_user_lang from liteyuki.utils.ly_typing import T_Bot, T_MessageEvent from liteyuki.utils.message import Markdown as md -from .reloader import Reloader -from liteyuki.utils import htmlrender +from liteyuki.utils.reloader import Reloader require("nonebot_plugin_alconna"), require("nonebot_plugin_htmlrender") from nonebot_plugin_alconna import on_alconna, Alconna, Args, Subcommand, Arparma -from nonebot_plugin_htmlrender import html_to_pic driver = get_driver() diff --git a/liteyuki/liteyuki_main/runtime.py b/liteyuki/liteyuki_main/runtime.py index eed840b1..62723cb0 100644 --- a/liteyuki/liteyuki_main/runtime.py +++ b/liteyuki/liteyuki_main/runtime.py @@ -127,7 +127,8 @@ async def _(bot: T_Bot, event: T_MessageEvent): image_bytes = await template2image( template=get_path("templates/stats.html", abs_path=True), templates=templ, - scale_factor=4, + scale_factor=1, + debug=True ) # await md.send_image(image_bytes, bot, event=event) await stats.finish(MessageSegment.image(image_bytes)) diff --git a/liteyuki/plugins/liteyuki_npm/manager.py b/liteyuki/plugins/liteyuki_npm/manager.py index 15c1ab68..7e2bf576 100644 --- a/liteyuki/plugins/liteyuki_npm/manager.py +++ b/liteyuki/plugins/liteyuki_npm/manager.py @@ -4,8 +4,7 @@ import nonebot.plugin from nonebot import require from nonebot.exception import FinishedException, IgnoredException from nonebot.internal.adapter import Event -from nonebot.internal.matcher import Matcher, current_matcher -from nonebot.adapters import Bot +from nonebot.internal.matcher import Matcher from nonebot.message import run_preprocessor from nonebot.permission import SUPERUSER from nonebot.plugin import Plugin @@ -17,6 +16,7 @@ from liteyuki.utils.message import Markdown as md from liteyuki.utils.permission import GROUP_ADMIN, GROUP_OWNER from .common import get_plugin_can_be_toggle, get_plugin_default_enable, get_plugin_global_enable, get_plugin_session_enable from .installer import get_store_plugin, npm_update +from ...utils.tools import clamp require("nonebot_plugin_alconna") from nonebot_plugin_alconna import on_alconna, Alconna, Args, Arparma @@ -24,8 +24,9 @@ from nonebot_plugin_alconna import on_alconna, Alconna, Args, Arparma list_plugins = on_alconna( Alconna( "list-plugin", + Args["page", int, 1]["num", int, 10], ), - aliases={"列出插件", "插件列表", "菜单"} + aliases={"列出插件", "插件列表"} ) toggle_plugin = on_alconna( @@ -47,42 +48,48 @@ toggle_plugin_global = on_alconna( @list_plugins.handle() -async def _(event: T_MessageEvent, bot: T_Bot): +async def _(event: T_MessageEvent, bot: T_Bot, result: Arparma): + lang = get_user_lang(str(event.user_id)) if not os.path.exists("data/liteyuki/plugins.json"): await npm_update() - lang = get_user_lang(str(event.user_id)) - reply = f"# {lang.get('npm.loaded_plugins')} | {lang.get('npm.total', TOTAL=len(nonebot.get_loaded_plugins()))} \n***\n" - for plugin in nonebot.get_loaded_plugins(): + + loaded_plugin_list = sorted(nonebot.get_loaded_plugins(), key=lambda x: x.module_name) + num_per_page = result.args.get("num") + total = len(loaded_plugin_list) // num_per_page + (1 if len(loaded_plugin_list) % num_per_page else 0) + + page = clamp(result.args.get("page"), 1, total) + + # 已加载插件 | 总计10 | 第1/3页 + reply = (f"# {lang.get('npm.loaded_plugins')} | " + f"{lang.get('npm.total', TOTAL=len(nonebot.get_loaded_plugins()))} | " + f"{lang.get('npm.page', PAGE=page, TOTAL=total)} \n***\n") + + for plugin in loaded_plugin_list[(page - 1) * num_per_page: min(page * num_per_page, len(loaded_plugin_list))]: # 检查是否有 metadata 属性 # 添加帮助按钮 btn_usage = md.button(lang.get("npm.usage"), f"help {plugin.module_name}", False) store_plugin = await get_store_plugin(plugin.module_name) session_enable = get_plugin_session_enable(event, plugin.module_name) - default_enable = get_plugin_default_enable(plugin.module_name) if store_plugin: btn_homepage = md.link(lang.get("npm.homepage"), store_plugin.homepage) show_name = store_plugin.name - show_desc = store_plugin.desc elif plugin.metadata: if plugin.metadata.extra.get("liteyuki"): btn_homepage = md.link(lang.get("npm.homepage"), "https://github.com/snowykami/LiteyukiBot") else: btn_homepage = lang.get("npm.homepage") show_name = plugin.metadata.name - show_desc = plugin.metadata.description else: btn_homepage = lang.get("npm.homepage") show_name = plugin.name - show_desc = lang.get("npm.no_description") + lang.get("npm.no_description") if plugin.metadata: - reply += (f"\n**{md.escape(show_name)}**\n" - f"\n > {md.escape(show_desc)}\n") + reply += f"\n**{md.escape(show_name)}**\n" else: - reply += (f"**{md.escape(show_name)}**\n" - f"\n > {md.escape(show_desc)}\n") + reply += f"**{md.escape(show_name)}**\n" reply += f"\n > {btn_usage} {btn_homepage}" @@ -121,7 +128,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") # 支持对自定义command_start的判断 - toggle = result.header_result in [prefix+header for prefix in bot.config.command_start for header in ["enable-plugin", "启用"]] # 判断是启用还是停用 + toggle = result.header_result in [prefix + header for prefix in bot.config.command_start for header in ["enable-plugin", "启用"]] # 判断是启用还是停用 session_enable = get_plugin_session_enable(event, plugin_module_name) # 获取插件当前状态 diff --git a/liteyuki/resources/lang/de.lang b/liteyuki/resources/lang/de.lang index 8164a1b0..43559e5e 100644 --- a/liteyuki/resources/lang/de.lang +++ b/liteyuki/resources/lang/de.lang @@ -93,3 +93,8 @@ user.profile.nickname.desc=Spitzname des Bots für den Benutzer festlegen user.profile.input_value=Bitte geben Sie {ATTR} ein user.profile.set_success={ATTR} erfolgreich auf {VALUE} festgelegt user.profile.set_failed={ATTR} festlegen fehlgeschlagen. Bitte überprüfen Sie, ob die Eingabe gültig ist. + +liteyuki.image_mode_on=Markdown-Bild +liteyuki.image_mode_off=Markdown-Link + +npm.page=Page {PAGE}/{TOTAL} \ No newline at end of file diff --git a/liteyuki/resources/lang/en.lang b/liteyuki/resources/lang/en.lang index 264ffb1a..ff5c223c 100644 --- a/liteyuki/resources/lang/en.lang +++ b/liteyuki/resources/lang/en.lang @@ -93,3 +93,8 @@ user.profile.nickname.desc=Set Bot's nickname for the user user.profile.input_value=Please enter {ATTR} user.profile.set_success={ATTR} set successfully to {VALUE} user.profile.set_failed=Setting {ATTR} failed. Please check if the input is valid. + +liteyuki.image_mode_on=Enable markdown image +liteyuki.image_mode_off=Closed markdown image + +npm.page=Page {PAGE}/{TOTAL} \ No newline at end of file diff --git a/liteyuki/resources/lang/zh-CN.lang b/liteyuki/resources/lang/zh-CN.lang index c4e74cde..ce32624f 100644 --- a/liteyuki/resources/lang/zh-CN.lang +++ b/liteyuki/resources/lang/zh-CN.lang @@ -95,4 +95,6 @@ user.profile.set_success=成功将 {ATTR} 设置为 {VALUE} user.profile.set_failed=设置 {ATTR} 失败,请检查输入是否合法 liteyuki.image_mode_on=开启Markdown图片模式 -liteyuki.image_mode_off=关闭Markdown图片模式 \ No newline at end of file +liteyuki.image_mode_off=关闭Markdown图片模式 + +npm.page=第{PAGE}/{TOTAL}页 \ No newline at end of file diff --git a/liteyuki/resources/templates/stats.html b/liteyuki/resources/templates/stats.html index 0dfdea40..ede0fc03 100644 --- a/liteyuki/resources/templates/stats.html +++ b/liteyuki/resources/templates/stats.html @@ -16,40 +16,44 @@ background-image: url('img/bg1.jpg'); color: white; // 上10px,左右10px,下0px - margin: 10px 10px 0; + margin: 24px; } .info-box { - border-radius: 10px; - padding: 15px; + border-radius: 30px; + padding: 30px; backdrop-filter: blur(30px); background-color: rgba(0, 0, 0, 0.3); display: flex; - margin-bottom: 10px; + margin: 0 24px 24px; } #cpu-chart, #mem-chart, #swap-chart { - height: 150px; - width: 100px; - margin: -10px 15px; + height: 240px; + width: 240px; + margin-bottom: 20px; + } + + #cpu-info, #mem-info, #swap-info { + margin: 0 40px; + align-items: center; } #bot-info { + margin-top: 24px; align-items: center; } #hardware-info { justify-content: center; text-align: center; - align-items: center; } #bot-icon { border-radius: 50%; - width: 100px; - height: 100px; + height: 200px; } #bot-name, #bot-tag { @@ -57,28 +61,22 @@ } #bot-name { - font-size: 22px; + font-size: 42px; font-weight: bold; } - #bot-id { - margin-left: 10px; - font-size: 18px; - font-weight: normal; - } #bot-tag { margin-top: 10px; - display: flex; - flex-wrap: wrap; } .chart-label { - font-size: 15px; + font-size: 30px; + max-width: 240px; } .tag { - font-size: 15px; + font-size: 27px; } .tag::after { @@ -178,7 +176,7 @@ textStyle: { //文字颜色 color: '#fff', - fontSize: 15 + fontSize: 30 } }, tooltip: { @@ -207,7 +205,7 @@ label: { show: true, textStyle: { - fontSize: '25', + fontSize: '50', fontWeight: 'bold' } } diff --git a/liteyuki/utils/__init__.py b/liteyuki/utils/__init__.py index 4f3ef4a3..0ae44cb4 100644 --- a/liteyuki/utils/__init__.py +++ b/liteyuki/utils/__init__.py @@ -61,17 +61,6 @@ def init(): if not os.path.exists("data/liteyuki/liteyuki.json"): register_bot() - print("\033[34m" + r""" - __ ______ ________ ________ __ __ __ __ __ __ ______ -/ | / |/ |/ |/ \ / |/ | / |/ | / |/ | -$$ | $$$$$$/ $$$$$$$$/ $$$$$$$$/ $$ \ /$$/ $$ | $$ |$$ | /$$/ $$$$$$/ -$$ | $$ | $$ | $$ |__ $$ \/$$/ $$ | $$ |$$ |/$$/ $$ | -$$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |$$ $$< $$ | -$$ | $$ | $$ | $$$$$/ $$$$/ $$ | $$ |$$$$$ \ $$ | -$$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \__$$ |$$ |$$ \ _$$ |_ -$$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ | -$$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/ -""" + "\033[0m") nonebot.logger.info( f"Run Liteyuki with Python{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro} " f"at {sys.executable}" diff --git a/liteyuki/utils/htmlrender.py b/liteyuki/utils/htmlrender.py index e8bfcf54..60f7b5c6 100644 --- a/liteyuki/utils/htmlrender.py +++ b/liteyuki/utils/htmlrender.py @@ -1,5 +1,8 @@ import os.path +import time +from os import getcwd +import aiofiles from nonebot import require require("nonebot_plugin_htmlrender") @@ -31,25 +34,44 @@ from nonebot_plugin_htmlrender import * async def template2image( template: str, templates: dict, - pages: dict | None = None, + pages=None, wait: int = 0, - scale_factor: float = 2, - **kwargs + scale_factor: float = 1, + debug: bool = False, ) -> bytes: """ template -> html -> image Args: + debug: 输入渲染好的 html wait: 等待时间,单位秒 pages: 页面参数 template: str: 模板文件 templates: dict: 模板参数 scale_factor: 缩放因子,越高越清晰 - **kwargs: page 参数 Returns: 图片二进制数据 """ + if pages is None: + pages = { + "viewport": { + "width" : 1080, + "height": 10 + }, + "base_url": f"file://{getcwd()}", + } template_path = os.path.dirname(template) template_name = os.path.basename(template) + + if debug: + raw_html = await template_to_html( + template_name=template_name, + template_path=template_path, + **templates, + ) + async with aiofiles.open(os.path.join(template_path, "latest-debug.html"), "w", encoding="utf-8") as f: + await f.write(raw_html) + nonebot.logger.info("Debug HTML: %s" % "latest-debug.html") + return await template_to_pic( template_name=template_name, template_path=template_path, diff --git a/liteyuki/utils/language.py b/liteyuki/utils/language.py index 4aa874f7..37952d91 100644 --- a/liteyuki/utils/language.py +++ b/liteyuki/utils/language.py @@ -100,12 +100,15 @@ def load_from_dict(data: dict, lang_code: str): class Language: - def __init__(self, lang_code: str = None, fallback_lang_code: str = "en"): + def __init__(self, lang_code: str = None, fallback_lang_code: str = None): if lang_code is None: - lang_code = get_system_lang_code() + lang_code = config.get("default_language", get_default_lang()) self.lang_code = lang_code self.fallback_lang_code = fallback_lang_code + if self.fallback_lang_code is None: + self.fallback_lang_code = config.get("default_language", get_system_lang_code()) + def get(self, item: str, *args, **kwargs) -> str | Any: """ 获取当前语言文本 @@ -154,7 +157,7 @@ def get_user_lang(user_id: str) -> Language: username="Unknown" )) - return Language(user.profile.get("lang", config.get("default_language", get_system_lang_code()))) + return Language(user.profile.get("lang", get_default_lang())) def get_system_lang_code() -> str: diff --git a/liteyuki/utils/log.py b/liteyuki/utils/log.py index e6c2bbbd..9e1db87c 100644 --- a/liteyuki/utils/log.py +++ b/liteyuki/utils/log.py @@ -2,7 +2,7 @@ import sys import loguru from typing import TYPE_CHECKING from .config import load_from_yaml -from .language import Language, get_default_lang, get_system_lang_code +from .language import Language, get_default_lang logger = loguru.logger if TYPE_CHECKING: @@ -59,7 +59,7 @@ def init_log(): format=get_format(config.get("log_level", "INFO")), ) show_icon = config.get("log_icon", True) - lang = Language(config.get("default_language", get_system_lang_code())) + lang = get_default_lang() debug = lang.get("log.debug", default="==DEBUG") info = lang.get("log.info", default="===INFO") diff --git a/liteyuki/utils/plugin.py b/liteyuki/utils/plugin.py new file mode 100644 index 00000000..e69de29b diff --git a/liteyuki/liteyuki_main/reloader.py b/liteyuki/utils/reloader.py similarity index 90% rename from liteyuki/liteyuki_main/reloader.py rename to liteyuki/utils/reloader.py index 093ff098..2b90dd94 100644 --- a/liteyuki/liteyuki_main/reloader.py +++ b/liteyuki/utils/reloader.py @@ -3,11 +3,6 @@ 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 @@ -60,7 +55,6 @@ def run(*args, **kwargs): elif process.is_alive(): continue else: - # Process stoped without setting event should_exit = True diff --git a/liteyuki/utils/tools.py b/liteyuki/utils/tools.py index b088ee23..4b1ef66a 100644 --- a/liteyuki/utils/tools.py +++ b/liteyuki/utils/tools.py @@ -1,5 +1,18 @@ from importlib.metadata import PackageNotFoundError, version -from urllib.parse import quote + + +def clamp(value: float, min_value: float, max_value: float) -> float | int: + """将值限制在最小值和最大值之间 + + Args: + value (float): 要限制的值 + min_value (float): 最小值 + max_value (float): 最大值 + + Returns: + float: 限制后的值 + """ + return max(min(value, max_value), min_value) def convert_size(size: int, precision: int = 2, add_unit: bool = True, suffix: str = "iB") -> str: