From bd5f6c5205f3cd404a93bfcd2a89170258a66ee3 Mon Sep 17 00:00:00 2001 From: snowy Date: Mon, 22 Apr 2024 21:05:35 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E9=A2=84=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=EF=BC=8C=E5=8F=91=E9=80=81=E6=9B=B4=E5=BF=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/deployment/config.md | 5 +- liteyuki/liteyuki_main/__init__.py | 1 + liteyuki/liteyuki_main/dev_tools.py | 43 ++ liteyuki/liteyuki_main/runtime.py | 108 ++-- .../plugins/liteyuki_crt_utils/__init__.py | 15 + liteyuki/plugins/liteyuki_crt_utils/canvas.py | 575 ++++++++++++++++++ liteyuki/plugins/liteyuki_crt_utils/crt.py | 0 .../plugins/liteyuki_crt_utils/rt_guide.py | 419 +++++++++++++ liteyuki/plugins/liteyuki_weather/qweather.py | 8 +- liteyuki/resources/lang/zh-CN.lang | 4 +- liteyuki/resources/templates/js/style.js | 8 +- liteyuki/utils/__init__.py | 1 + liteyuki/utils/message/markdown.py | 12 + liteyuki/utils/message/message.py | 72 +-- 14 files changed, 1178 insertions(+), 93 deletions(-) create mode 100644 liteyuki/liteyuki_main/dev_tools.py create mode 100644 liteyuki/plugins/liteyuki_crt_utils/__init__.py create mode 100644 liteyuki/plugins/liteyuki_crt_utils/canvas.py create mode 100644 liteyuki/plugins/liteyuki_crt_utils/crt.py create mode 100644 liteyuki/plugins/liteyuki_crt_utils/rt_guide.py diff --git a/docs/deployment/config.md b/docs/deployment/config.md index 3c7cbe8..5e5ce53 100644 --- a/docs/deployment/config.md +++ b/docs/deployment/config.md @@ -27,13 +27,14 @@ superusers: [ "1919810" ] # 超级用户列表 ```yaml onebot_access_token: "" # 访问令牌,对公开放时建议设置 default_language: "zh-CN" # 默认语言 +alconna_auto_completion: false # alconna是否自动补全指令,默认false,建议开启 +# 开发者选项 log_level: "INFO" # 日志等级 log_icon: true # 是否显示日志等级图标(某些控制台字体不可用) auto_report: true # 是否自动上报问题给轻雪服务器 auto_update: true # 是否自动更新轻雪,每天4点检查更新 -alconna_auto_completion: false # alconna是否自动补全指令,默认false,建议开启 +liteyuki_reload: false # 轻雪内置的重载选项,开启后在调试时修改代码或资源会自动重载相应内容 safe_mode: false # 安全模式,开启后将不会加载任何第三方插件 - # 其他Nonebot插件的配置项 custom_config_1: "custom_value1" custom_config_2: "custom_value2" diff --git a/liteyuki/liteyuki_main/__init__.py b/liteyuki/liteyuki_main/__init__.py index a76d106..b4c4918 100644 --- a/liteyuki/liteyuki_main/__init__.py +++ b/liteyuki/liteyuki_main/__init__.py @@ -3,6 +3,7 @@ from nonebot.plugin import PluginMetadata from .core import * from .loader import * from .runtime import * +from .dev_tools import * __author__ = "snowykami" __plugin_meta__ = PluginMetadata( diff --git a/liteyuki/liteyuki_main/dev_tools.py b/liteyuki/liteyuki_main/dev_tools.py new file mode 100644 index 0000000..de9c058 --- /dev/null +++ b/liteyuki/liteyuki_main/dev_tools.py @@ -0,0 +1,43 @@ +import time + +import nonebot +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler + +from liteyuki.utils.base.config import get_config +from liteyuki.utils.base.reloader import Reloader +from liteyuki.utils.base.resource import load_resources + +if get_config("liteyuki_reload", False): + nonebot.logger.info("Liteyuki Reload is enable, watching for file changes...") + + + class CodeModifiedHandler(FileSystemEventHandler): + def on_modified(self, event): + if "liteyuki/resources" not in event.src_path.replace("\\", "/"): + nonebot.logger.debug(f"{event.src_path} has been modified, reloading bot...") + Reloader.reload() + + + class ResourceModifiedHandler(FileSystemEventHandler): + def on_modified(self, event): + if not event.is_directory: + nonebot.logger.debug(f"{event.src_path} has been modified, reloading resource...") + load_resources() + + + code_modified_handler = CodeModifiedHandler() + resource_modified_handle = ResourceModifiedHandler() + + observer = Observer() + observer.schedule(resource_modified_handle, path="liteyuki/resources", recursive=True) + observer.schedule(resource_modified_handle, path="resources", recursive=True) + observer.schedule(code_modified_handler, path="liteyuki", recursive=True) + observer.start() + +# try: +# while True: +# time.sleep(1) +# except KeyboardInterrupt: +# observer.stop() +# observer.join() diff --git a/liteyuki/liteyuki_main/runtime.py b/liteyuki/liteyuki_main/runtime.py index e6cc34e..be3366e 100644 --- a/liteyuki/liteyuki_main/runtime.py +++ b/liteyuki/liteyuki_main/runtime.py @@ -3,17 +3,20 @@ import platform import nonebot import psutil from cpuinfo import get_cpu_info -from nonebot import on_command +from nonebot import on_command, require from nonebot.adapters.onebot.v11 import MessageSegment from nonebot.permission import SUPERUSER from liteyuki.utils import __NAME__, __VERSION__, load_from_yaml from liteyuki.utils.message.html_tool import template2image -from liteyuki.utils.base.language import Language, get_default_lang_code, get_user_lang +from liteyuki.utils.base.language import Language, get_all_lang, get_default_lang_code, get_user_lang from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent from liteyuki.utils.base.resource import get_path from liteyuki.utils.message.tools import convert_size -from PIL import Image -from io import BytesIO +from PIL import Image +from io import BytesIO + +require("nonebot_plugin_apscheduler") +from nonebot_plugin_apscheduler import scheduler stats = on_command("status", aliases={"状态"}, priority=5, permission=SUPERUSER) @@ -28,29 +31,63 @@ protocol_names = { 6: "Android Pad", } +# 预存数据区 +stats_data = {} # lang -> dict +image_data = {} # lang -> bytes +localization = {} +for lang in get_all_lang(): + ulang = Language(lang) + localization[lang] = { + "cpu" : ulang.get("main.monitor.cpu"), + "mem" : ulang.get("main.monitor.memory"), + "swap" : ulang.get("main.monitor.swap"), + "disk" : ulang.get("main.monitor.disk"), + "used" : ulang.get("main.monitor.used"), + "free" : ulang.get("main.monitor.free"), + "total": ulang.get("main.monitor.total"), + } + + +@scheduler.scheduled_job("cron", second="*/20") +async def _(): + nonebot.logger.info("数据已刷新") + for lang_code in get_all_lang(): + stats_data[lang_code] = await get_stats_data(lang=lang_code) + image_data[lang_code] = await template2image( + get_path("templates/stats.html", abs_path=True), + { + "data": stats_data[lang_code] + }, + wait=1 + ) + @stats.handle() async def _(bot: T_Bot, event: T_MessageEvent): + global stats_data ulang = get_user_lang(str(event.user_id)) - image = await template2image( - get_path("templates/stats.html", abs_path=True), - { - "data": await get_stats_data(bot.self_id, ulang.lang_code) - }, - wait=1 - ) - image = await png_to_jpg(image) + + if ulang.lang_code not in image_data: + stats_data[ulang.lang_code] = await get_stats_data(lang=ulang.lang_code) + image_data[ulang.lang_code] = await template2image( + get_path("templates/stats.html", abs_path=True), + { + "data": stats_data[ulang.lang_code] + }, + wait=1 + ) + image = await png_to_jpg(image_data[ulang.lang_code]) await stats.finish(MessageSegment.image(image)) -async def png_to_jpg(image): - image_stream = BytesIO(image) - img = Image.open(image_stream) - rgb_img = img.convert('RGB') - output_stream = BytesIO() - rgb_img.save(output_stream, format='JPEG') - jpg_bytes = output_stream.getvalue() - return jpg_bytes +async def png_to_jpg(image): + image_stream = BytesIO(image) + img = Image.open(image_stream) + rgb_img = img.convert('RGB') + output_stream = BytesIO() + rgb_img.save(output_stream, format='JPEG') + jpg_bytes = output_stream.getvalue() + return jpg_bytes async def get_bots_data(ulang: Language, self_id) -> list: @@ -181,10 +218,10 @@ async def get_stats_data(self_id: str = None, lang: str = None) -> dict: cpu_info = get_cpu_info() templ = { - "plugin" : len(nonebot.get_loaded_plugins()), - "version" : __VERSION__, - "system" : platform.platform(), - "cpu" : [ + "plugin" : len(nonebot.get_loaded_plugins()), + "version" : __VERSION__, + "system" : platform.platform(), + "cpu" : [ { "name" : "USED", "value": psutil.cpu_percent() @@ -194,7 +231,7 @@ async def get_stats_data(self_id: str = None, lang: str = None) -> dict: "value": 100 - psutil.cpu_percent() } ], - "mem" : [ + "mem" : [ { "name" : "OTHER", @@ -209,7 +246,7 @@ async def get_stats_data(self_id: str = None, lang: str = None) -> dict: "value": mem_used_bot }, ], - "swap" : [ + "swap" : [ { "name" : "USED", "value": psutil.swap_memory().used @@ -219,32 +256,25 @@ async def get_stats_data(self_id: str = None, lang: str = None) -> dict: "value": psutil.swap_memory().free } ], - "disk" : disk_data, # list[{"name":"C", "total":100, "used":50, "free":50}] - "bot" : await get_bots_data(ulang, self_id), - "cpuTags" : [ + "disk" : disk_data, # list[{"name":"C", "total":100, "used":50, "free":50}] + "bot" : await get_bots_data(ulang, self_id), + "cpuTags" : [ f"{brand} {cpu_info.get('arch', 'Unknown')}", f"{fake_device_info.get('cpu', {}).get('cores', psutil.cpu_count(logical=False))}C " f"{fake_device_info.get('cpu', {}).get('logical_cores', psutil.cpu_count(logical=True))}T", f"{'%.2f' % (fake_device_info.get('cpu', {}).get('frequency', psutil.cpu_freq().current) / 1000)}GHz" ], - "memTags" : [ + "memTags" : [ f"Bot {mem_used_bot_show}", f"{ulang.get('main.monitor.used')} {mem_used_show}", f"{ulang.get('main.monitor.free')} {convert_size(mem_free, 1)}", f"{ulang.get('main.monitor.total')} {mem_total_show}", ], - "swapTags" : [ + "swapTags" : [ f"{ulang.get('main.monitor.used')} {convert_size(swap_info.used, 1)}", f"{ulang.get('main.monitor.free')} {convert_size(swap_info.free, 1)}", f"{ulang.get('main.monitor.total')} {convert_size(swap_info.total, 1)}", ], - "cpu_trans" : ulang.get("main.monitor.cpu"), - "mem_trans" : ulang.get("main.monitor.memory"), - "swap_trans" : ulang.get("main.monitor.swap"), - "disk_trans" : ulang.get("main.monitor.disk"), - "used_trans" : ulang.get("main.monitor.used"), - "free_trans" : ulang.get("main.monitor.free"), - "total_trans": ulang.get("main.monitor.total"), + "localization": localization[ulang.lang_code] } - return templ diff --git a/liteyuki/plugins/liteyuki_crt_utils/__init__.py b/liteyuki/plugins/liteyuki_crt_utils/__init__.py new file mode 100644 index 0000000..a8d2b75 --- /dev/null +++ b/liteyuki/plugins/liteyuki_crt_utils/__init__.py @@ -0,0 +1,15 @@ +from nonebot.plugin import PluginMetadata +from .rt_guide import * + +__plugin_meta__ = PluginMetadata( + name="CRT生成工具", + description="一些CRT牌子生成器", + usage="我觉得你应该会用", + type="application", + homepage="https://github.com/snowykami/LiteyukiBot", + extra={ + "liteyuki" : True, + "toggleable" : True, + "default_enable": True, + } +) diff --git a/liteyuki/plugins/liteyuki_crt_utils/canvas.py b/liteyuki/plugins/liteyuki_crt_utils/canvas.py new file mode 100644 index 0000000..02e6e3d --- /dev/null +++ b/liteyuki/plugins/liteyuki_crt_utils/canvas.py @@ -0,0 +1,575 @@ +import os +import uuid +from typing import Tuple, Union, List + +import nonebot +from PIL import Image, ImageFont, ImageDraw + +default_color = (255, 255, 255, 255) +default_font = "resources/fonts/MiSans-Semibold.ttf" + + +def render_canvas_from_json(file: str, background: Image) -> "Canvas": + pass + + +class BasePanel: + def __init__(self, + uv_size: Tuple[Union[int, float], Union[int, float]] = (1.0, 1.0), + box_size: Tuple[Union[int, float], Union[int, float]] = (1.0, 1.0), + parent_point: Tuple[float, float] = (0.5, 0.5), + point: Tuple[float, float] = (0.5, 0.5)): + """ + :param uv_size: 底面板大小 + :param box_size: 子(自身)面板大小 + :param parent_point: 底面板锚点 + :param point: 子(自身)面板锚点 + """ + self.canvas: Canvas | None = None + self.uv_size = uv_size + self.box_size = box_size + self.parent_point = parent_point + self.point = point + self.parent: BasePanel | None = None + self.canvas_box: Tuple[float, float, float, float] = (0.0, 0.0, 1.0, 1.0) + # 此节点在父节点上的盒子 + self.box = ( + self.parent_point[0] - self.point[0] * self.box_size[0] / self.uv_size[0], + self.parent_point[1] - self.point[1] * self.box_size[1] / self.uv_size[1], + self.parent_point[0] + (1 - self.point[0]) * self.box_size[0] / self.uv_size[0], + self.parent_point[1] + (1 - self.point[1]) * self.box_size[1] / self.uv_size[1] + ) + + def load(self, only_calculate=False): + """ + 将对象写入画布 + 此处仅作声明 + 由各子类重写 + + :return: + """ + self.actual_pos = self.canvas_box + + def save_as(self, canvas_box, only_calculate=False): + """ + 此函数执行时间较长,建议异步运行 + :param only_calculate: + :param canvas_box 此节点在画布上的盒子,并不是在父节点上的盒子 + :return: + """ + for name, child in self.__dict__.items(): + # 此节点在画布上的盒子 + if isinstance(child, BasePanel) and name not in ["canvas", "parent"]: + child.parent = self + if isinstance(self, Canvas): + child.canvas = self + else: + child.canvas = self.canvas + dxc = canvas_box[2] - canvas_box[0] + dyc = canvas_box[3] - canvas_box[1] + child.canvas_box = ( + canvas_box[0] + dxc * child.box[0], + canvas_box[1] + dyc * child.box[1], + canvas_box[0] + dxc * child.box[2], + canvas_box[1] + dyc * child.box[3] + ) + child.load(only_calculate) + child.save_as(child.canvas_box, only_calculate) + + +class Canvas(BasePanel): + def __init__(self, base_img: Image.Image): + self.base_img = base_img + self.canvas = self + super(Canvas, self).__init__() + self.draw_line_list = [] + + def export(self, file, alpha=False): + self.base_img = self.base_img.convert("RGBA") + self.save_as((0, 0, 1, 1)) + draw = ImageDraw.Draw(self.base_img) + for line in self.draw_line_list: + draw.line(*line) + if not alpha: + self.base_img = self.base_img.convert("RGB") + self.base_img.save(file) + + def delete(self): + os.remove(self.file) + + def get_actual_box(self, path: str) -> Union[None, Tuple[float, float, float, float]]: + """ + 获取控件实际相对大小 + 函数执行时间较长 + + :param path: 控件路径 + :return: + """ + sub_obj = self + self.save_as((0, 0, 1, 1), True) + control_path = "" + for i, seq in enumerate(path.split(".")): + if seq not in sub_obj.__dict__: + raise KeyError(f"在{control_path}中找不到控件:{seq}") + control_path += f".{seq}" + sub_obj = sub_obj.__dict__[seq] + return sub_obj.actual_pos + + def get_actual_pixel_size(self, path: str) -> Union[None, Tuple[int, int]]: + """ + 获取控件实际像素长宽 + 函数执行时间较长 + :param path: 控件路径 + :return: + """ + sub_obj = self + self.save_as((0, 0, 1, 1), True) + control_path = "" + for i, seq in enumerate(path.split(".")): + if seq not in sub_obj.__dict__: + raise KeyError(f"在{control_path}中找不到控件:{seq}") + control_path += f".{seq}" + sub_obj = sub_obj.__dict__[seq] + dx = int(sub_obj.canvas.base_img.size[0] * (sub_obj.actual_pos[2] - sub_obj.actual_pos[0])) + dy = int(sub_obj.canvas.base_img.size[1] * (sub_obj.actual_pos[3] - sub_obj.actual_pos[1])) + return dx, dy + + def get_actual_pixel_box(self, path: str) -> Union[None, Tuple[int, int, int, int]]: + """ + 获取控件实际像素大小盒子 + 函数执行时间较长 + :param path: 控件路径 + :return: + """ + sub_obj = self + self.save_as((0, 0, 1, 1), True) + control_path = "" + for i, seq in enumerate(path.split(".")): + if seq not in sub_obj.__dict__: + raise KeyError(f"在{control_path}中找不到控件:{seq}") + control_path += f".{seq}" + sub_obj = sub_obj.__dict__[seq] + x1 = int(sub_obj.canvas.base_img.size[0] * sub_obj.actual_pos[0]) + y1 = int(sub_obj.canvas.base_img.size[1] * sub_obj.actual_pos[1]) + x2 = int(sub_obj.canvas.base_img.size[2] * sub_obj.actual_pos[2]) + y2 = int(sub_obj.canvas.base_img.size[3] * sub_obj.actual_pos[3]) + return x1, y1, x2, y2 + + def get_parent_box(self, path: str) -> Union[None, Tuple[float, float, float, float]]: + """ + 获取控件在父节点的大小 + 函数执行时间较长 + + :param path: 控件路径 + :return: + """ + sub_obj = self.get_control_by_path(path) + on_parent_pos = ( + (sub_obj.actual_pos[0] - sub_obj.parent.actual_pos[0]) / (sub_obj.parent.actual_pos[2] - sub_obj.parent.actual_pos[0]), + (sub_obj.actual_pos[1] - sub_obj.parent.actual_pos[1]) / (sub_obj.parent.actual_pos[3] - sub_obj.parent.actual_pos[1]), + (sub_obj.actual_pos[2] - sub_obj.parent.actual_pos[0]) / (sub_obj.parent.actual_pos[2] - sub_obj.parent.actual_pos[0]), + (sub_obj.actual_pos[3] - sub_obj.parent.actual_pos[1]) / (sub_obj.parent.actual_pos[3] - sub_obj.parent.actual_pos[1]) + ) + return on_parent_pos + + def get_control_by_path(self, path: str) -> Union[BasePanel, "Img", "Rectangle", "Text"]: + sub_obj = self + self.save_as((0, 0, 1, 1), True) + control_path = "" + for i, seq in enumerate(path.split(".")): + if seq not in sub_obj.__dict__: + raise KeyError(f"在{control_path}中找不到控件:{seq}") + control_path += f".{seq}" + sub_obj = sub_obj.__dict__[seq] + return sub_obj + + def draw_line(self, path: str, p1: Tuple[float, float], p2: Tuple[float, float], color, width): + """ + 画线 + + :param color: + :param width: + :param path: + :param p1: + :param p2: + :return: + """ + ac_pos = self.get_actual_box(path) + control = self.get_control_by_path(path) + dx = ac_pos[2] - ac_pos[0] + dy = ac_pos[3] - ac_pos[1] + xy_box = int((ac_pos[0] + dx * p1[0]) * control.canvas.base_img.size[0]), int((ac_pos[1] + dy * p1[1]) * control.canvas.base_img.size[1]), int( + (ac_pos[0] + dx * p2[0]) * control.canvas.base_img.size[0]), int((ac_pos[1] + dy * p2[1]) * control.canvas.base_img.size[1]) + self.draw_line_list.append((xy_box, color, width)) + + +class Panel(BasePanel): + def __init__(self, uv_size, box_size, parent_point, point): + super(Panel, self).__init__(uv_size, box_size, parent_point, point) + + +class TextSegment: + def __init__(self, text, **kwargs): + if not isinstance(text, str): + raise TypeError("请输入字符串") + self.text = text + self.color = kwargs.get("color", None) + self.font = kwargs.get("font", None) + + @staticmethod + def text2text_segment_list(text: str): + """ + 暂时没写好 + + :param text: %FFFFFFFF%1123%FFFFFFFF%21323 + :return: + """ + pass + + +class Text(BasePanel): + def __init__(self, uv_size, box_size, parent_point, point, text: Union[str, list], font=default_font, color=(255, 255, 255, 255), vertical=False, + line_feed=False, force_size=False, fill=(0, 0, 0, 0), fillet=0, outline=(0, 0, 0, 0), outline_width=0, rectangle_side=0, font_size=None, dp: int = 5, + anchor: str = "la"): + """ + :param uv_size: + :param box_size: + :param parent_point: + :param point: + :param text: list[TextSegment] | str + :param font: + :param color: + :param vertical: 是否竖直 + :param line_feed: 是否换行 + :param force_size: 强制大小 + :param dp: 字体大小递减精度 + :param anchor : https://www.zhihu.com/question/474216280 + :param fill: 底部填充颜色 + :param fillet: 填充圆角 + :param rectangle_side: 边框宽度 + :param outline: 填充矩形边框颜色 + :param outline_width: 填充矩形边框宽度 + """ + self.actual_pos = None + self.outline_width = outline_width + self.outline = outline + self.fill = fill + self.fillet = fillet + self.font = font + self.text = text + self.color = color + self.force_size = force_size + self.vertical = vertical + self.line_feed = line_feed + self.dp = dp + self.font_size = font_size + self.rectangle_side = rectangle_side + self.anchor = anchor + super(Text, self).__init__(uv_size, box_size, parent_point, point) + + def load(self, only_calculate=False): + """限制区域像素大小""" + if isinstance(self.text, str): + self.text = [ + TextSegment(text=self.text, color=self.color, font=self.font) + ] + all_text = str() + for text in self.text: + all_text += text.text + limited_size = int((self.canvas_box[2] - self.canvas_box[0]) * self.canvas.base_img.size[0]), int((self.canvas_box[3] - self.canvas_box[1]) * self.canvas.base_img.size[1]) + font_size = limited_size[1] if self.font_size is None else self.font_size + image_font = ImageFont.truetype(self.font, font_size) + actual_size = image_font.getsize(all_text) + while (actual_size[0] > limited_size[0] or actual_size[1] > limited_size[1]) and not self.force_size: + font_size -= self.dp + image_font = ImageFont.truetype(self.font, font_size) + actual_size = image_font.getsize(all_text) + draw = ImageDraw.Draw(self.canvas.base_img) + if isinstance(self.parent, Img) or isinstance(self.parent, Text): + self.parent.canvas_box = self.parent.actual_pos + dx0 = self.parent.canvas_box[2] - self.parent.canvas_box[0] + dy0 = self.parent.canvas_box[3] - self.parent.canvas_box[1] + dx1 = actual_size[0] / self.canvas.base_img.size[0] + dy1 = actual_size[1] / self.canvas.base_img.size[1] + start_point = [ + int((self.parent.canvas_box[0] + dx0 * self.parent_point[0] - dx1 * self.point[0]) * self.canvas.base_img.size[0]), + int((self.parent.canvas_box[1] + dy0 * self.parent_point[1] - dy1 * self.point[1]) * self.canvas.base_img.size[1]) + ] + self.actual_pos = ( + start_point[0] / self.canvas.base_img.size[0], + start_point[1] / self.canvas.base_img.size[1], + (start_point[0] + actual_size[0]) / self.canvas.base_img.size[0], + (start_point[1] + actual_size[1]) / self.canvas.base_img.size[1], + ) + self.font_size = font_size + if not only_calculate: + for text_segment in self.text: + if text_segment.color is None: + text_segment.color = self.color + if text_segment.font is None: + text_segment.font = self.font + image_font = ImageFont.truetype(font=text_segment.font, size=font_size) + if self.fill[-1] > 0: + rectangle = Shape.rectangle(size=(actual_size[0] + 2 * self.rectangle_side, actual_size[1] + 2 * self.rectangle_side), fillet=self.fillet, fill=self.fill, + width=self.outline_width, outline=self.outline) + self.canvas.base_img.paste(im=rectangle, box=(start_point[0] - self.rectangle_side, + start_point[1] - self.rectangle_side, + start_point[0] + actual_size[0] + self.rectangle_side, + start_point[1] + actual_size[1] + self.rectangle_side), + mask=rectangle.split()[-1]) + draw.text((start_point[0] - self.rectangle_side, start_point[1] - self.rectangle_side), + text_segment.text, text_segment.color, font=image_font, anchor=self.anchor) + text_width = image_font.getsize(text_segment.text) + start_point[0] += text_width[0] + + +class Img(BasePanel): + def __init__(self, uv_size, box_size, parent_point, point, img: Image.Image, keep_ratio=True): + self.img_base_img = img + self.keep_ratio = keep_ratio + super(Img, self).__init__(uv_size, box_size, parent_point, point) + + def load(self, only_calculate=False): + self.preprocess() + self.img_base_img = self.img_base_img.convert("RGBA") + limited_size = int((self.canvas_box[2] - self.canvas_box[0]) * self.canvas.base_img.size[0]), \ + int((self.canvas_box[3] - self.canvas_box[1]) * self.canvas.base_img.size[1]) + + if self.keep_ratio: + """保持比例""" + actual_ratio = self.img_base_img.size[0] / self.img_base_img.size[1] + limited_ratio = limited_size[0] / limited_size[1] + if actual_ratio >= limited_ratio: + # 图片过长 + self.img_base_img = self.img_base_img.resize( + (int(self.img_base_img.size[0] * limited_size[0] / self.img_base_img.size[0]), + int(self.img_base_img.size[1] * limited_size[0] / self.img_base_img.size[0])) + ) + else: + self.img_base_img = self.img_base_img.resize( + (int(self.img_base_img.size[0] * limited_size[1] / self.img_base_img.size[1]), + int(self.img_base_img.size[1] * limited_size[1] / self.img_base_img.size[1])) + ) + + else: + """不保持比例""" + self.img_base_img = self.img_base_img.resize(limited_size) + + # 占比长度 + if isinstance(self.parent, Img) or isinstance(self.parent, Text): + self.parent.canvas_box = self.parent.actual_pos + + dx0 = self.parent.canvas_box[2] - self.parent.canvas_box[0] + dy0 = self.parent.canvas_box[3] - self.parent.canvas_box[1] + + dx1 = self.img_base_img.size[0] / self.canvas.base_img.size[0] + dy1 = self.img_base_img.size[1] / self.canvas.base_img.size[1] + start_point = ( + int((self.parent.canvas_box[0] + dx0 * self.parent_point[0] - dx1 * self.point[0]) * self.canvas.base_img.size[0]), + int((self.parent.canvas_box[1] + dy0 * self.parent_point[1] - dy1 * self.point[1]) * self.canvas.base_img.size[1]) + ) + alpha = self.img_base_img.split()[3] + self.actual_pos = ( + start_point[0] / self.canvas.base_img.size[0], + start_point[1] / self.canvas.base_img.size[1], + (start_point[0] + self.img_base_img.size[0]) / self.canvas.base_img.size[0], + (start_point[1] + self.img_base_img.size[1]) / self.canvas.base_img.size[1], + ) + if not only_calculate: + self.canvas.base_img.paste(self.img_base_img, start_point, alpha) + + def preprocess(self): + pass + + +class Rectangle(Img): + def __init__(self, uv_size, box_size, parent_point, point, fillet: Union[int, float] = 0, img: Union[Image.Image] = None, keep_ratio=True, + color=default_color, outline_width=0, outline_color=default_color): + """ + 圆角图 + :param uv_size: + :param box_size: + :param parent_point: + :param point: + :param fillet: 圆角半径浮点或整数 + :param img: + :param keep_ratio: + """ + self.fillet = fillet + self.color = color + self.outline_width = outline_width + self.outline_color = outline_color + super(Rectangle, self).__init__(uv_size, box_size, parent_point, point, img, keep_ratio) + + def preprocess(self): + limited_size = (int(self.canvas.base_img.size[0] * (self.canvas_box[2] - self.canvas_box[0])), + int(self.canvas.base_img.size[1] * (self.canvas_box[3] - self.canvas_box[1]))) + if not self.keep_ratio and self.img_base_img is not None and self.img_base_img.size[0] / self.img_base_img.size[1] != limited_size[0] / limited_size[1]: + self.img_base_img = self.img_base_img.resize(limited_size) + self.img_base_img = Shape.rectangle(size=limited_size, fillet=self.fillet, fill=self.color, width=self.outline_width, outline=self.outline_color) + + +class Color: + GREY = (128, 128, 128, 255) + RED = (255, 0, 0, 255) + GREEN = (0, 255, 0, 255) + BLUE = (0, 0, 255, 255) + YELLOW = (255, 255, 0, 255) + PURPLE = (255, 0, 255, 255) + CYAN = (0, 255, 255, 255) + WHITE = (255, 255, 255, 255) + BLACK = (0, 0, 0, 255) + + @staticmethod + def hex2dec(colorHex: str) -> Tuple[int, int, int, int]: + """ + :param colorHex: FFFFFFFF (ARGB)-> (R, G, B, A) + :return: + """ + return int(colorHex[2:4], 16), int(colorHex[4:6], 16), int(colorHex[6:8], 16), int(colorHex[0:2], 16) + + +class Shape: + @staticmethod + def circular(radius: int, fill: tuple, width: int = 0, outline: tuple = Color.BLACK) -> Image.Image: + """ + :param radius: 半径(像素) + :param fill: 填充颜色 + :param width: 轮廓粗细(像素) + :param outline: 轮廓颜色 + :return: 圆形Image对象 + """ + img = Image.new("RGBA", (radius * 2, radius * 2), color=radius) + draw = ImageDraw.Draw(img) + draw.ellipse(xy=(0, 0, radius * 2, radius * 2), fill=fill, outline=outline, width=width) + return img + + @staticmethod + def rectangle(size: Tuple[int, int], fill: tuple, width: int = 0, outline: tuple = Color.BLACK, fillet: int = 0) -> Image.Image: + """ + :param fillet: 圆角半径(像素) + :param size: 长宽(像素) + :param fill: 填充颜色 + :param width: 轮廓粗细(像素) + :param outline: 轮廓颜色 + :return: 矩形Image对象 + """ + img = Image.new("RGBA", size, color=fill) + draw = ImageDraw.Draw(img) + draw.rounded_rectangle(xy=(0, 0, size[0], size[1]), fill=fill, outline=outline, width=width, radius=fillet) + return img + + @staticmethod + def ellipse(size: Tuple[int, int], fill: tuple, outline: int = 0, outline_color: tuple = Color.BLACK) -> Image.Image: + """ + :param size: 长宽(像素) + :param fill: 填充颜色 + :param outline: 轮廓粗细(像素) + :param outline_color: 轮廓颜色 + :return: 椭圆Image对象 + """ + img = Image.new("RGBA", size, color=fill) + draw = ImageDraw.Draw(img) + draw.ellipse(xy=(0, 0, size[0], size[1]), fill=fill, outline=outline_color, width=outline) + return img + + @staticmethod + def polygon(points: List[Tuple[int, int]], fill: tuple, outline: int, outline_color: tuple) -> Image.Image: + """ + :param points: 多边形顶点列表 + :param fill: 填充颜色 + :param outline: 轮廓粗细(像素) + :param outline_color: 轮廓颜色 + :return: 多边形Image对象 + """ + img = Image.new("RGBA", (max(points)[0], max(points)[1]), color=fill) + draw = ImageDraw.Draw(img) + draw.polygon(xy=points, fill=fill, outline=outline_color, width=outline) + return img + + @staticmethod + def line(points: List[Tuple[int, int]], fill: tuple, width: int) -> Image: + """ + :param points: 线段顶点列表 + :param fill: 填充颜色 + :param width: 线段粗细(像素) + :return: 线段Image对象 + """ + img = Image.new("RGBA", (max(points)[0], max(points)[1]), color=fill) + draw = ImageDraw.Draw(img) + draw.line(xy=points, fill=fill, width=width) + return img + + +class Utils: + + @staticmethod + def central_clip_by_ratio(img: Image.Image, size: Tuple, use_cache=True): + """ + :param use_cache: 是否使用缓存,剪切过一次后默认生成缓存 + :param img: + :param size: 仅为比例,满填充裁剪 + :return: + """ + cache_file_path = str() + if use_cache: + filename_without_end = ".".join(os.path.basename(img.fp.name).split(".")[0:-1]) + f"_{size[0]}x{size[1]}" + ".png" + cache_file_path = os.path.join(".cache", filename_without_end) + if os.path.exists(cache_file_path): + nonebot.logger.info("本次使用缓存加载图片,不裁剪") + return Image.open(os.path.join(".cache", filename_without_end)) + img_ratio = img.size[0] / img.size[1] + limited_ratio = size[0] / size[1] + if limited_ratio > img_ratio: + actual_size = ( + img.size[0], + img.size[0] / size[0] * size[1] + ) + box = ( + 0, (img.size[1] - actual_size[1]) // 2, + img.size[0], img.size[1] - (img.size[1] - actual_size[1]) // 2 + ) + else: + actual_size = ( + img.size[1] / size[1] * size[0], + img.size[1], + ) + box = ( + (img.size[0] - actual_size[0]) // 2, 0, + img.size[0] - (img.size[0] - actual_size[0]) // 2, img.size[1] + ) + img = img.crop(box).resize(size) + if use_cache: + img.save(cache_file_path) + return img + + @staticmethod + def circular_clip(img: Image.Image): + """ + 裁剪为alpha圆形 + + :param img: + :return: + """ + length = min(img.size) + alpha_cover = Image.new("RGBA", (length, length), color=(0, 0, 0, 0)) + if img.size[0] > img.size[1]: + box = ( + (img.size[0] - img[1]) // 2, 0, + (img.size[0] - img[1]) // 2 + img.size[1], img.size[1] + ) + else: + box = ( + 0, (img.size[1] - img.size[0]) // 2, + img.size[0], (img.size[1] - img.size[0]) // 2 + img.size[0] + ) + img = img.crop(box).resize((length, length)) + draw = ImageDraw.Draw(alpha_cover) + draw.ellipse(xy=(0, 0, length, length), fill=(255, 255, 255, 255)) + alpha = alpha_cover.split()[-1] + img.putalpha(alpha) + return img + + @staticmethod + def open_img(path) -> Image.Image: + return Image.open(path, "RGBA") diff --git a/liteyuki/plugins/liteyuki_crt_utils/crt.py b/liteyuki/plugins/liteyuki_crt_utils/crt.py new file mode 100644 index 0000000..e69de29 diff --git a/liteyuki/plugins/liteyuki_crt_utils/rt_guide.py b/liteyuki/plugins/liteyuki_crt_utils/rt_guide.py new file mode 100644 index 0000000..4167cbf --- /dev/null +++ b/liteyuki/plugins/liteyuki_crt_utils/rt_guide.py @@ -0,0 +1,419 @@ +import json +from typing import List, Any + +from PIL import Image +from arclet.alconna import Alconna +from nb_cli import run_sync +from nonebot import on_command +from nonebot_plugin_alconna import on_alconna, Alconna, Subcommand, Args, MultiVar, Arparma, UniMessage +from pydantic import BaseModel + +from .canvas import * +from ...utils.base.resource import get_path + +resolution = 256 + + +class Entrance(BaseModel): + identifier: str + size: tuple[int, int] + dest: List[str] + + +class Station(BaseModel): + identifier: str + chineseName: str + englishName: str + position: tuple[int, int] + + +class Line(BaseModel): + identifier: str + chineseName: str + englishName: str + color: Any + stations: List["Station"] + + +font_light = get_path("templates/fonts/MiSans/MiSans-Light.woff2") +font_bold = get_path("templates/fonts/MiSans/MiSans-Bold.woff2") + +@run_sync +def generate_entrance_sign(name: str, aliases: List[str], lineInfo: List[Line], entranceIdentifier: str, ratio: tuple[int | float, int | float], + reso: int = resolution): + """ + Generates an entrance sign for the ride. + """ + width, height = ratio[0] * reso, ratio[1] * reso + baseCanvas = Canvas(Image.new("RGBA", (width, height), Color.WHITE)) + # 加黑色图框 + baseCanvas.outline = Img( + uv_size=(1, 1), + box_size=(1, 1), + parent_point=(0, 0), + point=(0, 0), + img=Shape.rectangle( + size=(width, height), + fillet=0, + fill=(0, 0, 0, 0), + width=15, + outline=Color.BLACK + ) + ) + + baseCanvas.contentPanel = Panel( + uv_size=(width, height), + box_size=(width - 28, height - 28), + parent_point=(0.5, 0.5), + point=(0.5, 0.5), + ) + + linePanelHeight = 0.7 * ratio[1] + linePanelWidth = linePanelHeight * 1.3 + + # 画线路面板部分 + + for i, line in enumerate(lineInfo): + linePanel = baseCanvas.contentPanel.__dict__[f"Line_{i}_Panel"] = Panel( + uv_size=ratio, + box_size=(linePanelWidth, linePanelHeight), + parent_point=(i * linePanelWidth / ratio[0], 1), + point=(0, 1), + ) + + linePanel.colorCube = Img( + uv_size=(1, 1), + box_size=(0.15, 1), + parent_point=(0.125, 1), + point=(0, 1), + img=Shape.rectangle( + size=(100, 100), + fillet=0, + fill=line.color, + ), + keep_ratio=False + ) + + textPanel = linePanel.TextPanel = Panel( + uv_size=(1, 1), + box_size=(0.625, 1), + parent_point=(1, 1), + point=(1, 1) + ) + + # 中文线路名 + textPanel.namePanel = Panel( + uv_size=(1, 1), + box_size=(1, 2 / 3), + parent_point=(0, 0), + point=(0, 0), + ) + nameSize = baseCanvas.get_actual_pixel_size("contentPanel.Line_{}_Panel.TextPanel.namePanel".format(i)) + textPanel.namePanel.text = Text( + uv_size=(1, 1), + box_size=(1, 1), + parent_point=(0.5, 0.5), + point=(0.5, 0.5), + text=line.chineseName, + color=Color.BLACK, + font_size=int(nameSize[1] * 0.5), + force_size=True, + font=font_bold + + ) + + # 英文线路名 + textPanel.englishNamePanel = Panel( + uv_size=(1, 1), + box_size=(1, 1 / 3), + parent_point=(0, 1), + point=(0, 1), + ) + englishNameSize = baseCanvas.get_actual_pixel_size("contentPanel.Line_{}_Panel.TextPanel.englishNamePanel".format(i)) + textPanel.englishNamePanel.text = Text( + uv_size=(1, 1), + box_size=(1, 1), + parent_point=(0.5, 0.5), + point=(0.5, 0.5), + text=line.englishName, + color=Color.BLACK, + font_size=int(englishNameSize[1] * 0.6), + force_size=True, + font=font_light + ) + + # 画名称部分 + namePanel = baseCanvas.contentPanel.namePanel = Panel( + uv_size=(1, 1), + box_size=(1, 0.4), + parent_point=(0.5, 0), + point=(0.5, 0), + ) + + namePanel.text = Text( + uv_size=(1, 1), + box_size=(1, 1), + parent_point=(0.5, 0.5), + point=(0.5, 0.5), + text=name, + color=Color.BLACK, + font_size=int(height * 0.3), + force_size=True, + font=font_bold + ) + + aliasesPanel = baseCanvas.contentPanel.aliasesPanel = Panel( + uv_size=(1, 1), + box_size=(1, 0.5), + parent_point=(0.5, 1), + point=(0.5, 1), + + ) + for j, alias in enumerate(aliases): + aliasesPanel.__dict__[alias] = Text( + uv_size=(1, 1), + box_size=(0.35, 0.5), + parent_point=(0.5, 0.5 * j), + point=(0.5, 0), + text=alias, + color=Color.BLACK, + font_size=int(height * 0.15), + font=font_light + ) + + # 画入口标识 + entrancePanel = baseCanvas.contentPanel.entrancePanel = Panel( + uv_size=(1, 1), + box_size=(0.2, 1), + parent_point=(1, 0.5), + point=(1, 0.5), + ) + # 中文文本 + entrancePanel.namePanel = Panel( + uv_size=(1, 1), + box_size=(1, 0.5), + parent_point=(1, 0), + point=(1, 0), + ) + entrancePanel.namePanel.text = Text( + uv_size=(1, 1), + box_size=(1, 1), + parent_point=(0, 0.5), + point=(0, 0.5), + text=f"{entranceIdentifier}出入口", + color=Color.BLACK, + font_size=int(height * 0.2), + force_size=True, + font=font_bold + ) + # 英文文本 + entrancePanel.englishNamePanel = Panel( + uv_size=(1, 1), + box_size=(1, 0.5), + parent_point=(1, 1), + point=(1, 1), + ) + entrancePanel.englishNamePanel.text = Text( + uv_size=(1, 1), + box_size=(1, 1), + parent_point=(0, 0.5), + point=(0, 0.5), + text=f"Entrance {entranceIdentifier}", + color=Color.BLACK, + font_size=int(height * 0.15), + force_size=True, + font=font_light + ) + + return baseCanvas.base_img.tobytes() + + +crt_alc = on_alconna( + Alconna( + "crt", + Subcommand( + "entrance", + Args["name", str]["lines", str, ""]["entrance", int, 1], # /crt entrance 璧山&Bishan 1号线&Line1&#ff0000,27号线&Line1&#ff0000 1A + ) + ) +) + + +@crt_alc.assign("entrance") +async def _(result: Arparma): + args = result.subcommands.get("entrance").args + name = args["name"] + lines = args["lines"] + entrance = args["entrance"] + line_info = [] + for line in lines.split(","): + line_args = line.split("&") + line_info.append(Line( + identifier=1, + chineseName=line_args[0], + englishName=line_args[1], + color=line_args[2], + stations=[] + )) + img_bytes = await generate_entrance_sign( + name=name, + aliases=name.split("&"), + lineInfo=line_info, + entranceIdentifier=entrance, + ratio=(8, 1), + reso=256, + ) + await crt_alc.finish( + UniMessage.image(raw=img_bytes) + ) + + +def generate_platform_line_pic(line: Line, station: Station, ratio=None, reso: int = resolution): + """ + 生成站台线路图 + :param line: 线路对象 + :param station: 本站点对象 + :param ratio: 比例 + :param reso: 分辨率,1:reso + :return: 两个方向的站牌 + """ + if ratio is None: + ratio = [4, 1] + width, height = ratio[0] * reso, ratio[1] * reso + baseCanvas = Canvas(Image.new("RGBA", (width, height), Color.YELLOW)) + # 加黑色图框 + baseCanvas.linePanel = Panel( + uv_size=(1, 1), + box_size=(0.8, 0.15), + parent_point=(0.5, 0.5), + point=(0.5, 0.5), + ) + + # 直线块 + baseCanvas.linePanel.recLine = Img( + uv_size=(1, 1), + box_size=(1, 1), + parent_point=(0.5, 0.5), + point=(0.5, 0.5), + img=Shape.rectangle( + size=(10, 10), + fill=line.color, + ), + keep_ratio=False + ) + # 灰色直线块 + baseCanvas.linePanel.recLineGrey = Img( + uv_size=(1, 1), + box_size=(1, 1), + parent_point=(0.5, 0.5), + point=(0.5, 0.5), + img=Shape.rectangle( + size=(10, 10), + fill=Color.GREY, + ), + keep_ratio=False + ) + # 生成各站圆点 + outline_width = 40 + circleForward = Shape.circular( + radius=200, + fill=Color.WHITE, + width=outline_width, + outline=line.color, + ) + + circleThisPanel = Canvas(Image.new("RGBA", (200, 200), (0, 0, 0, 0))) + circleThisPanel.circleOuter = Img( + uv_size=(1, 1), + box_size=(1, 1), + parent_point=(0.5, 0.5), + point=(0.5, 0.5), + img=Shape.circular( + radius=200, + fill=Color.WHITE, + width=outline_width, + outline=line.color, + ), + ) + circleThisPanel.circleOuter.circleInner = Img( + uv_size=(1, 1), + box_size=(0.7, 0.7), + parent_point=(0.5, 0.5), + point=(0.5, 0.5), + img=Shape.circular( + radius=200, + fill=line.color, + width=0, + outline=line.color, + ), + ) + + circleThisPanel.export("a.png", alpha=True) + circleThis = circleThisPanel.base_img + + circlePassed = Shape.circular( + radius=200, + fill=Color.WHITE, + width=outline_width, + outline=Color.GREY, + ) + + arrival = False + distance = 1 / (len(line.stations) - 1) + for i, sta in enumerate(line.stations): + box_size = (1.618, 1.618) + if sta.identifier == station.identifier: + arrival = True + baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img( + uv_size=(1, 1), + box_size=(1.8, 1.8), + parent_point=(distance * i, 0.5), + point=(0.5, 0.5), + img=circleThis, + keep_ratio=True + ) + continue + if arrival: + # 后方站绘制 + baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img( + uv_size=(1, 1), + box_size=box_size, + parent_point=(distance * i, 0.5), + point=(0.5, 0.5), + img=circleForward, + keep_ratio=True + ) + else: + # 前方站绘制 + baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img( + uv_size=(1, 1), + box_size=box_size, + parent_point=(distance * i, 0.5), + point=(0.5, 0.5), + img=circlePassed, + keep_ratio=True + ) + return baseCanvas + + +def generate_platform_sign(name: str, aliases: List[str], lineInfo: List[Line], entranceIdentifier: str, ratio: tuple[int | float, int | float], + reso: int = resolution + ): + pass + +# def main(): +# generate_entrance_sign( +# "璧山", +# aliases=["Bishan"], +# lineInfo=[ +# +# Line(identifier="2", chineseName="1号线", englishName="Line 1", color=Color.RED, stations=[]), +# Line(identifier="3", chineseName="27号线", englishName="Line 27", color="#685bc7", stations=[]), +# Line(identifier="1", chineseName="璧铜线", englishName="BT Line", color="#685BC7", stations=[]), +# ], +# entranceIdentifier="1", +# ratio=(8, 1) +# ) +# +# +# main() diff --git a/liteyuki/plugins/liteyuki_weather/qweather.py b/liteyuki/plugins/liteyuki_weather/qweather.py index 4afffb3..a224d91 100644 --- a/liteyuki/plugins/liteyuki_weather/qweather.py +++ b/liteyuki/plugins/liteyuki_weather/qweather.py @@ -6,10 +6,10 @@ from liteyuki.utils.base.config import get_config from liteyuki.utils.base.ly_typing import T_MessageEvent from .qw_api import * -from ...utils.base.data_manager import User, user_db -from ...utils.base.language import get_user_lang -from ...utils.base.resource import get_path -from ...utils.message.html_tool import template2image +from liteyuki.utils.base.data_manager import User, user_db +from liteyuki.utils.base.language import get_user_lang +from liteyuki.utils.base.resource import get_path +from liteyuki.utils.message.html_tool import template2image require("nonebot_plugin_alconna") from nonebot_plugin_alconna import on_alconna, Alconna, Args, MultiVar, Arparma diff --git a/liteyuki/resources/lang/zh-CN.lang b/liteyuki/resources/lang/zh-CN.lang index 19a8e5a..1a48df3 100644 --- a/liteyuki/resources/lang/zh-CN.lang +++ b/liteyuki/resources/lang/zh-CN.lang @@ -132,6 +132,4 @@ rpm.move_top=置顶 weather.city_not_found=未找到城市 {CITY} weather.weather_not_found=未找到城市 {CITY} 的天气信息 -weather.no_key=未设置天气api key,请在配置文件添加weather-key - - +weather.no_key=未设置天气api key,请在配置文件添加weather_key diff --git a/liteyuki/resources/templates/js/style.js b/liteyuki/resources/templates/js/style.js index 49f98e8..2f53f16 100644 --- a/liteyuki/resources/templates/js/style.js +++ b/liteyuki/resources/templates/js/style.js @@ -25,9 +25,9 @@ infoDiv.appendChild(tagSpan); }); } - cpuInfo.setOption(getPieOption(data.cpu_trans, cpuData)); - memInfo.setOption(getPieOption(data.mem_trans, memData)); - swapInfo.setOption(getPieOption(data.swap_trans, swapData)); + cpuInfo.setOption(getPieOption(data.localization.cpu, cpuData)); + memInfo.setOption(getPieOption(data.localization.mem, memData)); + swapInfo.setOption(getPieOption(data.localization.swap, swapData)); // 在disks-info中插入每个disk的div,用横向柱状图表示用量,每一行div显示一个disk,不加info-box @@ -40,7 +40,7 @@ diskDiv.appendChild(diskChart); let diskInfo = echarts.init(diskChart); // let diskTitle = disk.name + ' {{ FREE }} ' + disk.free + ' {{ TOTAL }} ' + disk.total; - let diskTitle = `${disk.name} ${data.free_trans} ${disk.free} ${data.total_trans} ${disk.total}`; + let diskTitle = `${disk.name} ${data.localization.free} ${disk.free} ${data.localization.total} ${disk.total}`; diskInfo.setOption(getBarOption(diskTitle, disk.percent)); }); diff --git a/liteyuki/utils/__init__.py b/liteyuki/utils/__init__.py index 9ee4e1e..5dbd1fa 100644 --- a/liteyuki/utils/__init__.py +++ b/liteyuki/utils/__init__.py @@ -50,6 +50,7 @@ def init(): """ # 检测python版本是否高于3.10 + init_log() if sys.version_info < (3, 10): nonebot.logger.error("This project requires Python3.10+ to run, please upgrade your Python Environment.") exit(1) diff --git a/liteyuki/utils/message/markdown.py b/liteyuki/utils/message/markdown.py index d85b1d2..919d59d 100644 --- a/liteyuki/utils/message/markdown.py +++ b/liteyuki/utils/message/markdown.py @@ -6,6 +6,7 @@ import aiohttp from PIL import Image from ..base.config import get_config +from ..base.data import LiteModel from ..base.ly_typing import T_Bot @@ -195,3 +196,14 @@ class Mqqapi: # 若命令前缀不为空,则使用配置的第一个命令前缀 cmd = f"{command_start[0]}{cmd}" return f"[{text}](mqqapi://aio/inlinecmd?command={quote(cmd)}&reply={str(reply).lower()}&enter={str(enter).lower()})" + + +class RenderData(LiteModel): + label: str + visited_label: str + style: int + + +class Button(LiteModel): + id: int + render_data: RenderData diff --git a/liteyuki/utils/message/message.py b/liteyuki/utils/message/message.py index 0e8a38e..89b671a 100644 --- a/liteyuki/utils/message/message.py +++ b/liteyuki/utils/message/message.py @@ -64,28 +64,6 @@ class MarkdownMessage: message_type = event.message_type session_id = event.user_id if event.message_type == "private" else event.group_id try: - # 构建Markdown消息并获取转发消息ID - # forward_id = await bot.call_api( - # api="send_forward_msg", - # messages=[ - # v11.MessageSegment( - # type="node", - # data={ - # "name" : "Liteyuki.OneBot", - # "uin" : bot.self_id, - # "content": [ - # { - # "type": "markdown", - # "data": { - # "content": '{"content":"%s"}' % formatted_md - # } - # }, - # ] - # }, - # ) - # ] - # ) - # 发送Markdown longmsg并获取相应数据 data = await bot.send_msg( user_id=session_id, group_id=session_id, @@ -94,27 +72,39 @@ class MarkdownMessage: { "type": "markdown", "data": { - "content": "{\"content\":\"%s\"}" % formatted_md + "content": "{\"content\":\"%s\"}" % formatted_md, } - } + }, + # { + # "type": "keyboard", + # "data": { + # "content": { + # "rows": [ + # { + # "buttons": [ + # { + # "render_data": { + # "label" : "NPM", + # "visited_label": "NPM已点击", + # "style" : 1 + # }, + # "action" : { + # "type" : 2, + # "enter" : True, + # "permission": { + # "type": 2 + # }, + # "data" : "npm" + # + # } + # } + # ] + # } + # ] + # } + # } + # } ], - # messages=[ - # v11.MessageSegment( - # type="node", - # data={ - # "name": "Liteyuki.OneBot", - # "uin": bot.self_id, - # "content": [ - # { - # "type": "markdown", - # "data": { - # "content": '{"content":"%s"}' % formatted_md - # } - # } - # ] - # } - # ), - # ], **kwargs ) except BaseException as e: