1
0
forked from bot/app

feat: 优化了排版和渲染

This commit is contained in:
远野千束 2024-04-03 01:03:25 +08:00
parent 14fb96fec2
commit 955d9f6d62
19 changed files with 121 additions and 72 deletions

1
.gitignore vendored
View File

@ -14,6 +14,7 @@ _config.yml
config.yml config.yml
config.example.yml config.example.yml
compile.bat compile.bat
liteyuki/resources/templates/latest-debug.html
# vuepress # vuepress
.github .github

View File

@ -39,7 +39,7 @@ category: 使用手册
[SOA]disable <plugin_name> # 禁用插件 [SOA]disable <plugin_name> # 禁用插件
[S]enable-global <plugin_name> # 全局启用插件 [S]enable-global <plugin_name> # 全局启用插件
[S]disable-global <plugin_name> # 全局禁用插件 [S]disable-global <plugin_name> # 全局禁用插件
list-plugin # 列出所有插件 list-plugin [page] [num] # 列出所有插件 page为页数num为每页显示数量
# 受限于Nonebot的钩子函数目前只能阻断消息事件的传入对于主动推送消息的插件无法将其阻止 # 受限于Nonebot的钩子函数目前只能阻断消息事件的传入对于主动推送消息的插件无法将其阻止
------ ------
别名: enable 启用, disable 停用, enable-global 全局启用, disable-global 全局停用, list-plugin 列出插件/插件列表 别名: enable 启用, disable 停用, enable-global 全局启用, disable-global 全局停用, list-plugin 列出插件/插件列表

0
liteyuki/__init__.py Normal file
View File

Binary file not shown.

View File

@ -17,6 +17,18 @@ __plugin_meta__ = PluginMetadata(
} }
) )
print("\033[34m" + r"""
__ ______ ________ ________ __ __ __ __ __ __ ______
/ | / |/ |/ |/ \ / |/ | / |/ | / |/ |
$$ | $$$$$$/ $$$$$$$$/ $$$$$$$$/ $$ \ /$$/ $$ | $$ |$$ | /$$/ $$$$$$/
$$ | $$ | $$ | $$ |__ $$ \/$$/ $$ | $$ |$$ |/$$/ $$ |
$$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |$$ $$< $$ |
$$ | $$ | $$ | $$$$$/ $$$$/ $$ | $$ |$$$$$ \ $$ |
$$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \__$$ |$$ |$$ \ _$$ |_
$$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ |
$$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
""" + "\033[0m")
sys_lang = get_default_lang() 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.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)}")) nonebot.logger.info(sys_lang.get("main.enable_webdash", URL=f"http://127.0.0.1:{config.get('port', 20216)}"))

View File

@ -1,7 +1,6 @@
import base64 import base64
from typing import Any from typing import Any
import nonebot
import pip import pip
from git import Repo from git import Repo
from nonebot import Bot, require, get_driver 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.language import get_user_lang
from liteyuki.utils.ly_typing import T_Bot, T_MessageEvent from liteyuki.utils.ly_typing import T_Bot, T_MessageEvent
from liteyuki.utils.message import Markdown as md from liteyuki.utils.message import Markdown as md
from .reloader import Reloader from liteyuki.utils.reloader import Reloader
from liteyuki.utils import htmlrender
require("nonebot_plugin_alconna"), require("nonebot_plugin_htmlrender") require("nonebot_plugin_alconna"), require("nonebot_plugin_htmlrender")
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Subcommand, Arparma from nonebot_plugin_alconna import on_alconna, Alconna, Args, Subcommand, Arparma
from nonebot_plugin_htmlrender import html_to_pic
driver = get_driver() driver = get_driver()

View File

@ -127,7 +127,8 @@ async def _(bot: T_Bot, event: T_MessageEvent):
image_bytes = await template2image( image_bytes = await template2image(
template=get_path("templates/stats.html", abs_path=True), template=get_path("templates/stats.html", abs_path=True),
templates=templ, templates=templ,
scale_factor=4, scale_factor=1,
debug=True
) )
# await md.send_image(image_bytes, bot, event=event) # await md.send_image(image_bytes, bot, event=event)
await stats.finish(MessageSegment.image(image_bytes)) await stats.finish(MessageSegment.image(image_bytes))

View File

@ -4,8 +4,7 @@ import nonebot.plugin
from nonebot import require from nonebot import require
from nonebot.exception import FinishedException, IgnoredException from nonebot.exception import FinishedException, IgnoredException
from nonebot.internal.adapter import Event from nonebot.internal.adapter import Event
from nonebot.internal.matcher import Matcher, current_matcher from nonebot.internal.matcher import Matcher
from nonebot.adapters import Bot
from nonebot.message import run_preprocessor from nonebot.message import run_preprocessor
from nonebot.permission import SUPERUSER from nonebot.permission import SUPERUSER
from nonebot.plugin import Plugin 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 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 .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 .installer import get_store_plugin, npm_update
from ...utils.tools import clamp
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Arparma 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( list_plugins = on_alconna(
Alconna( Alconna(
"list-plugin", "list-plugin",
Args["page", int, 1]["num", int, 10],
), ),
aliases={"列出插件", "插件列表", "菜单"} aliases={"列出插件", "插件列表"}
) )
toggle_plugin = on_alconna( toggle_plugin = on_alconna(
@ -47,42 +48,48 @@ toggle_plugin_global = on_alconna(
@list_plugins.handle() @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"): if not os.path.exists("data/liteyuki/plugins.json"):
await npm_update() 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" loaded_plugin_list = sorted(nonebot.get_loaded_plugins(), key=lambda x: x.module_name)
for plugin in nonebot.get_loaded_plugins(): 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 属性 # 检查是否有 metadata 属性
# 添加帮助按钮 # 添加帮助按钮
btn_usage = md.button(lang.get("npm.usage"), f"help {plugin.module_name}", False) btn_usage = md.button(lang.get("npm.usage"), f"help {plugin.module_name}", False)
store_plugin = await get_store_plugin(plugin.module_name) store_plugin = await get_store_plugin(plugin.module_name)
session_enable = get_plugin_session_enable(event, 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: if store_plugin:
btn_homepage = md.link(lang.get("npm.homepage"), store_plugin.homepage) btn_homepage = md.link(lang.get("npm.homepage"), store_plugin.homepage)
show_name = store_plugin.name show_name = store_plugin.name
show_desc = store_plugin.desc
elif plugin.metadata: elif plugin.metadata:
if plugin.metadata.extra.get("liteyuki"): if plugin.metadata.extra.get("liteyuki"):
btn_homepage = md.link(lang.get("npm.homepage"), "https://github.com/snowykami/LiteyukiBot") btn_homepage = md.link(lang.get("npm.homepage"), "https://github.com/snowykami/LiteyukiBot")
else: else:
btn_homepage = lang.get("npm.homepage") btn_homepage = lang.get("npm.homepage")
show_name = plugin.metadata.name show_name = plugin.metadata.name
show_desc = plugin.metadata.description
else: else:
btn_homepage = lang.get("npm.homepage") btn_homepage = lang.get("npm.homepage")
show_name = plugin.name show_name = plugin.name
show_desc = lang.get("npm.no_description") lang.get("npm.no_description")
if plugin.metadata: if plugin.metadata:
reply += (f"\n**{md.escape(show_name)}**\n" reply += f"\n**{md.escape(show_name)}**\n"
f"\n > {md.escape(show_desc)}\n")
else: else:
reply += (f"**{md.escape(show_name)}**\n" reply += f"**{md.escape(show_name)}**\n"
f"\n > {md.escape(show_desc)}\n")
reply += f"\n > {btn_usage} {btn_homepage}" 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)) ulang = get_user_lang(str(event.user_id))
plugin_module_name = result.args.get("plugin_name") plugin_module_name = result.args.get("plugin_name")
# 支持对自定义command_start的判断 # 支持对自定义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) # 获取插件当前状态 session_enable = get_plugin_session_enable(event, plugin_module_name) # 获取插件当前状态

View File

@ -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.input_value=Bitte geben Sie {ATTR} ein
user.profile.set_success={ATTR} erfolgreich auf {VALUE} festgelegt 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. 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}

View File

@ -93,3 +93,8 @@ user.profile.nickname.desc=Set Bot's nickname for the user
user.profile.input_value=Please enter {ATTR} user.profile.input_value=Please enter {ATTR}
user.profile.set_success={ATTR} set successfully to {VALUE} user.profile.set_success={ATTR} set successfully to {VALUE}
user.profile.set_failed=Setting {ATTR} failed. Please check if the input is valid. 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}

View File

@ -95,4 +95,6 @@ user.profile.set_success=成功将 {ATTR} 设置为 {VALUE}
user.profile.set_failed=设置 {ATTR} 失败,请检查输入是否合法 user.profile.set_failed=设置 {ATTR} 失败,请检查输入是否合法
liteyuki.image_mode_on=开启Markdown图片模式 liteyuki.image_mode_on=开启Markdown图片模式
liteyuki.image_mode_off=关闭Markdown图片模式 liteyuki.image_mode_off=关闭Markdown图片模式
npm.page=第{PAGE}/{TOTAL}页

View File

@ -16,40 +16,44 @@
background-image: url('img/bg1.jpg'); background-image: url('img/bg1.jpg');
color: white; color: white;
// 上10px左右10px下0px // 上10px左右10px下0px
margin: 10px 10px 0; margin: 24px;
} }
.info-box { .info-box {
border-radius: 10px; border-radius: 30px;
padding: 15px; padding: 30px;
backdrop-filter: blur(30px); backdrop-filter: blur(30px);
background-color: rgba(0, 0, 0, 0.3); background-color: rgba(0, 0, 0, 0.3);
display: flex; display: flex;
margin-bottom: 10px; margin: 0 24px 24px;
} }
#cpu-chart, #mem-chart, #swap-chart { #cpu-chart, #mem-chart, #swap-chart {
height: 150px; height: 240px;
width: 100px; width: 240px;
margin: -10px 15px; margin-bottom: 20px;
}
#cpu-info, #mem-info, #swap-info {
margin: 0 40px;
align-items: center;
} }
#bot-info { #bot-info {
margin-top: 24px;
align-items: center; align-items: center;
} }
#hardware-info { #hardware-info {
justify-content: center; justify-content: center;
text-align: center; text-align: center;
align-items: center;
} }
#bot-icon { #bot-icon {
border-radius: 50%; border-radius: 50%;
width: 100px; height: 200px;
height: 100px;
} }
#bot-name, #bot-tag { #bot-name, #bot-tag {
@ -57,28 +61,22 @@
} }
#bot-name { #bot-name {
font-size: 22px; font-size: 42px;
font-weight: bold; font-weight: bold;
} }
#bot-id {
margin-left: 10px;
font-size: 18px;
font-weight: normal;
}
#bot-tag { #bot-tag {
margin-top: 10px; margin-top: 10px;
display: flex;
flex-wrap: wrap;
} }
.chart-label { .chart-label {
font-size: 15px; font-size: 30px;
max-width: 240px;
} }
.tag { .tag {
font-size: 15px; font-size: 27px;
} }
.tag::after { .tag::after {
@ -178,7 +176,7 @@
textStyle: { textStyle: {
//文字颜色 //文字颜色
color: '#fff', color: '#fff',
fontSize: 15 fontSize: 30
} }
}, },
tooltip: { tooltip: {
@ -207,7 +205,7 @@
label: { label: {
show: true, show: true,
textStyle: { textStyle: {
fontSize: '25', fontSize: '50',
fontWeight: 'bold' fontWeight: 'bold'
} }
} }

View File

@ -61,17 +61,6 @@ def init():
if not os.path.exists("data/liteyuki/liteyuki.json"): if not os.path.exists("data/liteyuki/liteyuki.json"):
register_bot() register_bot()
print("\033[34m" + r"""
__ ______ ________ ________ __ __ __ __ __ __ ______
/ | / |/ |/ |/ \ / |/ | / |/ | / |/ |
$$ | $$$$$$/ $$$$$$$$/ $$$$$$$$/ $$ \ /$$/ $$ | $$ |$$ | /$$/ $$$$$$/
$$ | $$ | $$ | $$ |__ $$ \/$$/ $$ | $$ |$$ |/$$/ $$ |
$$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |$$ $$< $$ |
$$ | $$ | $$ | $$$$$/ $$$$/ $$ | $$ |$$$$$ \ $$ |
$$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \__$$ |$$ |$$ \ _$$ |_
$$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ |
$$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
""" + "\033[0m")
nonebot.logger.info( nonebot.logger.info(
f"Run Liteyuki with Python{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro} " f"Run Liteyuki with Python{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro} "
f"at {sys.executable}" f"at {sys.executable}"

View File

@ -1,5 +1,8 @@
import os.path import os.path
import time
from os import getcwd
import aiofiles
from nonebot import require from nonebot import require
require("nonebot_plugin_htmlrender") require("nonebot_plugin_htmlrender")
@ -31,25 +34,44 @@ from nonebot_plugin_htmlrender import *
async def template2image( async def template2image(
template: str, template: str,
templates: dict, templates: dict,
pages: dict | None = None, pages=None,
wait: int = 0, wait: int = 0,
scale_factor: float = 2, scale_factor: float = 1,
**kwargs debug: bool = False,
) -> bytes: ) -> bytes:
""" """
template -> html -> image template -> html -> image
Args: Args:
debug: 输入渲染好的 html
wait: 等待时间单位秒 wait: 等待时间单位秒
pages: 页面参数 pages: 页面参数
template: str: 模板文件 template: str: 模板文件
templates: dict: 模板参数 templates: dict: 模板参数
scale_factor: 缩放因子越高越清晰 scale_factor: 缩放因子越高越清晰
**kwargs: page 参数
Returns: Returns:
图片二进制数据 图片二进制数据
""" """
if pages is None:
pages = {
"viewport": {
"width" : 1080,
"height": 10
},
"base_url": f"file://{getcwd()}",
}
template_path = os.path.dirname(template) template_path = os.path.dirname(template)
template_name = os.path.basename(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( return await template_to_pic(
template_name=template_name, template_name=template_name,
template_path=template_path, template_path=template_path,

View File

@ -100,12 +100,15 @@ def load_from_dict(data: dict, lang_code: str):
class Language: 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: 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.lang_code = lang_code
self.fallback_lang_code = fallback_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: def get(self, item: str, *args, **kwargs) -> str | Any:
""" """
获取当前语言文本 获取当前语言文本
@ -154,7 +157,7 @@ def get_user_lang(user_id: str) -> Language:
username="Unknown" 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: def get_system_lang_code() -> str:

View File

@ -2,7 +2,7 @@ import sys
import loguru import loguru
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from .config import load_from_yaml 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 logger = loguru.logger
if TYPE_CHECKING: if TYPE_CHECKING:
@ -59,7 +59,7 @@ def init_log():
format=get_format(config.get("log_level", "INFO")), format=get_format(config.get("log_level", "INFO")),
) )
show_icon = config.get("log_icon", True) 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") debug = lang.get("log.debug", default="==DEBUG")
info = lang.get("log.info", default="===INFO") info = lang.get("log.info", default="===INFO")

0
liteyuki/utils/plugin.py Normal file
View File

View File

@ -3,11 +3,6 @@ from multiprocessing import get_context
import nonebot import nonebot
from nonebot import logger from nonebot import logger
from typing import List, Optional
from nonebot import get_driver
from pydantic import BaseSettings
reboot_grace_time_limit: int = 20 reboot_grace_time_limit: int = 20
@ -60,7 +55,6 @@ def run(*args, **kwargs):
elif process.is_alive(): elif process.is_alive():
continue continue
else: else:
# Process stoped without setting event
should_exit = True should_exit = True

View File

@ -1,5 +1,18 @@
from importlib.metadata import PackageNotFoundError, version 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: def convert_size(size: int, precision: int = 2, add_unit: bool = True, suffix: str = "iB") -> str: