mirror of
https://github.com/LiteyukiStudio/LiteyukiBot.git
synced 2024-11-14 13:47:24 +08:00
✨ 预渲染数据,发送更快
This commit is contained in:
parent
ae19113141
commit
bd5f6c5205
@ -27,13 +27,14 @@ superusers: [ "1919810" ] # 超级用户列表
|
|||||||
```yaml
|
```yaml
|
||||||
onebot_access_token: "" # 访问令牌,对公开放时建议设置
|
onebot_access_token: "" # 访问令牌,对公开放时建议设置
|
||||||
default_language: "zh-CN" # 默认语言
|
default_language: "zh-CN" # 默认语言
|
||||||
|
alconna_auto_completion: false # alconna是否自动补全指令,默认false,建议开启
|
||||||
|
# 开发者选项
|
||||||
log_level: "INFO" # 日志等级
|
log_level: "INFO" # 日志等级
|
||||||
log_icon: true # 是否显示日志等级图标(某些控制台字体不可用)
|
log_icon: true # 是否显示日志等级图标(某些控制台字体不可用)
|
||||||
auto_report: true # 是否自动上报问题给轻雪服务器
|
auto_report: true # 是否自动上报问题给轻雪服务器
|
||||||
auto_update: true # 是否自动更新轻雪,每天4点检查更新
|
auto_update: true # 是否自动更新轻雪,每天4点检查更新
|
||||||
alconna_auto_completion: false # alconna是否自动补全指令,默认false,建议开启
|
liteyuki_reload: false # 轻雪内置的重载选项,开启后在调试时修改代码或资源会自动重载相应内容
|
||||||
safe_mode: false # 安全模式,开启后将不会加载任何第三方插件
|
safe_mode: false # 安全模式,开启后将不会加载任何第三方插件
|
||||||
|
|
||||||
# 其他Nonebot插件的配置项
|
# 其他Nonebot插件的配置项
|
||||||
custom_config_1: "custom_value1"
|
custom_config_1: "custom_value1"
|
||||||
custom_config_2: "custom_value2"
|
custom_config_2: "custom_value2"
|
||||||
|
@ -3,6 +3,7 @@ from nonebot.plugin import PluginMetadata
|
|||||||
from .core import *
|
from .core import *
|
||||||
from .loader import *
|
from .loader import *
|
||||||
from .runtime import *
|
from .runtime import *
|
||||||
|
from .dev_tools import *
|
||||||
|
|
||||||
__author__ = "snowykami"
|
__author__ = "snowykami"
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
|
43
liteyuki/liteyuki_main/dev_tools.py
Normal file
43
liteyuki/liteyuki_main/dev_tools.py
Normal file
@ -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()
|
@ -3,18 +3,21 @@ import platform
|
|||||||
import nonebot
|
import nonebot
|
||||||
import psutil
|
import psutil
|
||||||
from cpuinfo import get_cpu_info
|
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.adapters.onebot.v11 import MessageSegment
|
||||||
from nonebot.permission import SUPERUSER
|
from nonebot.permission import SUPERUSER
|
||||||
from liteyuki.utils import __NAME__, __VERSION__, load_from_yaml
|
from liteyuki.utils import __NAME__, __VERSION__, load_from_yaml
|
||||||
from liteyuki.utils.message.html_tool import template2image
|
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.ly_typing import T_Bot, T_MessageEvent
|
||||||
from liteyuki.utils.base.resource import get_path
|
from liteyuki.utils.base.resource import get_path
|
||||||
from liteyuki.utils.message.tools import convert_size
|
from liteyuki.utils.message.tools import convert_size
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
require("nonebot_plugin_apscheduler")
|
||||||
|
from nonebot_plugin_apscheduler import scheduler
|
||||||
|
|
||||||
stats = on_command("status", aliases={"状态"}, priority=5, permission=SUPERUSER)
|
stats = on_command("status", aliases={"状态"}, priority=5, permission=SUPERUSER)
|
||||||
|
|
||||||
config = load_from_yaml("config.yml")
|
config = load_from_yaml("config.yml")
|
||||||
@ -28,18 +31,52 @@ protocol_names = {
|
|||||||
6: "Android Pad",
|
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"),
|
||||||
|
}
|
||||||
|
|
||||||
@stats.handle()
|
|
||||||
async def _(bot: T_Bot, event: T_MessageEvent):
|
@scheduler.scheduled_job("cron", second="*/20")
|
||||||
ulang = get_user_lang(str(event.user_id))
|
async def _():
|
||||||
image = await template2image(
|
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),
|
get_path("templates/stats.html", abs_path=True),
|
||||||
{
|
{
|
||||||
"data": await get_stats_data(bot.self_id, ulang.lang_code)
|
"data": stats_data[lang_code]
|
||||||
},
|
},
|
||||||
wait=1
|
wait=1
|
||||||
)
|
)
|
||||||
image = await png_to_jpg(image)
|
|
||||||
|
|
||||||
|
@stats.handle()
|
||||||
|
async def _(bot: T_Bot, event: T_MessageEvent):
|
||||||
|
global stats_data
|
||||||
|
ulang = get_user_lang(str(event.user_id))
|
||||||
|
|
||||||
|
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))
|
await stats.finish(MessageSegment.image(image))
|
||||||
|
|
||||||
|
|
||||||
@ -238,13 +275,6 @@ async def get_stats_data(self_id: str = None, lang: str = None) -> dict:
|
|||||||
f"{ulang.get('main.monitor.free')} {convert_size(swap_info.free, 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)}",
|
f"{ulang.get('main.monitor.total')} {convert_size(swap_info.total, 1)}",
|
||||||
],
|
],
|
||||||
"cpu_trans" : ulang.get("main.monitor.cpu"),
|
"localization": localization[ulang.lang_code]
|
||||||
"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"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return templ
|
return templ
|
||||||
|
15
liteyuki/plugins/liteyuki_crt_utils/__init__.py
Normal file
15
liteyuki/plugins/liteyuki_crt_utils/__init__.py
Normal file
@ -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,
|
||||||
|
}
|
||||||
|
)
|
575
liteyuki/plugins/liteyuki_crt_utils/canvas.py
Normal file
575
liteyuki/plugins/liteyuki_crt_utils/canvas.py
Normal file
@ -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")
|
0
liteyuki/plugins/liteyuki_crt_utils/crt.py
Normal file
0
liteyuki/plugins/liteyuki_crt_utils/crt.py
Normal file
419
liteyuki/plugins/liteyuki_crt_utils/rt_guide.py
Normal file
419
liteyuki/plugins/liteyuki_crt_utils/rt_guide.py
Normal file
@ -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()
|
@ -6,10 +6,10 @@ from liteyuki.utils.base.config import get_config
|
|||||||
from liteyuki.utils.base.ly_typing import T_MessageEvent
|
from liteyuki.utils.base.ly_typing import T_MessageEvent
|
||||||
|
|
||||||
from .qw_api import *
|
from .qw_api import *
|
||||||
from ...utils.base.data_manager import User, user_db
|
from liteyuki.utils.base.data_manager import User, user_db
|
||||||
from ...utils.base.language import get_user_lang
|
from liteyuki.utils.base.language import get_user_lang
|
||||||
from ...utils.base.resource import get_path
|
from liteyuki.utils.base.resource import get_path
|
||||||
from ...utils.message.html_tool import template2image
|
from liteyuki.utils.message.html_tool import template2image
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
from nonebot_plugin_alconna import on_alconna, Alconna, Args, MultiVar, Arparma
|
from nonebot_plugin_alconna import on_alconna, Alconna, Args, MultiVar, Arparma
|
||||||
|
@ -132,6 +132,4 @@ rpm.move_top=置顶
|
|||||||
|
|
||||||
weather.city_not_found=未找到城市 {CITY}
|
weather.city_not_found=未找到城市 {CITY}
|
||||||
weather.weather_not_found=未找到城市 {CITY} 的天气信息
|
weather.weather_not_found=未找到城市 {CITY} 的天气信息
|
||||||
weather.no_key=未设置天气api key,请在配置文件添加weather-key
|
weather.no_key=未设置天气api key,请在配置文件添加weather_key
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,9 +25,9 @@
|
|||||||
infoDiv.appendChild(tagSpan);
|
infoDiv.appendChild(tagSpan);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
cpuInfo.setOption(getPieOption(data.cpu_trans, cpuData));
|
cpuInfo.setOption(getPieOption(data.localization.cpu, cpuData));
|
||||||
memInfo.setOption(getPieOption(data.mem_trans, memData));
|
memInfo.setOption(getPieOption(data.localization.mem, memData));
|
||||||
swapInfo.setOption(getPieOption(data.swap_trans, swapData));
|
swapInfo.setOption(getPieOption(data.localization.swap, swapData));
|
||||||
|
|
||||||
|
|
||||||
// 在disks-info中插入每个disk的div,用横向柱状图表示用量,每一行div显示一个disk,不加info-box
|
// 在disks-info中插入每个disk的div,用横向柱状图表示用量,每一行div显示一个disk,不加info-box
|
||||||
@ -40,7 +40,7 @@
|
|||||||
diskDiv.appendChild(diskChart);
|
diskDiv.appendChild(diskChart);
|
||||||
let diskInfo = echarts.init(diskChart);
|
let diskInfo = echarts.init(diskChart);
|
||||||
// let diskTitle = disk.name + ' {{ FREE }} ' + disk.free + ' {{ TOTAL }} ' + disk.total;
|
// 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));
|
diskInfo.setOption(getBarOption(diskTitle, disk.percent));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ def init():
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
# 检测python版本是否高于3.10
|
# 检测python版本是否高于3.10
|
||||||
|
init_log()
|
||||||
if sys.version_info < (3, 10):
|
if sys.version_info < (3, 10):
|
||||||
nonebot.logger.error("This project requires Python3.10+ to run, please upgrade your Python Environment.")
|
nonebot.logger.error("This project requires Python3.10+ to run, please upgrade your Python Environment.")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
@ -6,6 +6,7 @@ import aiohttp
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from ..base.config import get_config
|
from ..base.config import get_config
|
||||||
|
from ..base.data import LiteModel
|
||||||
from ..base.ly_typing import T_Bot
|
from ..base.ly_typing import T_Bot
|
||||||
|
|
||||||
|
|
||||||
@ -195,3 +196,14 @@ class Mqqapi:
|
|||||||
# 若命令前缀不为空,则使用配置的第一个命令前缀
|
# 若命令前缀不为空,则使用配置的第一个命令前缀
|
||||||
cmd = f"{command_start[0]}{cmd}"
|
cmd = f"{command_start[0]}{cmd}"
|
||||||
return f"[{text}](mqqapi://aio/inlinecmd?command={quote(cmd)}&reply={str(reply).lower()}&enter={str(enter).lower()})"
|
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
|
||||||
|
@ -64,28 +64,6 @@ class MarkdownMessage:
|
|||||||
message_type = event.message_type
|
message_type = event.message_type
|
||||||
session_id = event.user_id if event.message_type == "private" else event.group_id
|
session_id = event.user_id if event.message_type == "private" else event.group_id
|
||||||
try:
|
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(
|
data = await bot.send_msg(
|
||||||
user_id=session_id,
|
user_id=session_id,
|
||||||
group_id=session_id,
|
group_id=session_id,
|
||||||
@ -94,27 +72,39 @@ class MarkdownMessage:
|
|||||||
{
|
{
|
||||||
"type": "markdown",
|
"type": "markdown",
|
||||||
"data": {
|
"data": {
|
||||||
"content": "{\"content\":\"%s\"}" % formatted_md
|
"content": "{\"content\":\"%s\"}" % formatted_md,
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
],
|
|
||||||
# messages=[
|
|
||||||
# v11.MessageSegment(
|
|
||||||
# type="node",
|
|
||||||
# data={
|
|
||||||
# "name": "Liteyuki.OneBot",
|
|
||||||
# "uin": bot.self_id,
|
|
||||||
# "content": [
|
|
||||||
# {
|
# {
|
||||||
# "type": "markdown",
|
# "type": "keyboard",
|
||||||
# "data": {
|
# "data": {
|
||||||
# "content": '{"content":"%s"}' % formatted_md
|
# "content": {
|
||||||
|
# "rows": [
|
||||||
|
# {
|
||||||
|
# "buttons": [
|
||||||
|
# {
|
||||||
|
# "render_data": {
|
||||||
|
# "label" : "NPM",
|
||||||
|
# "visited_label": "NPM已点击",
|
||||||
|
# "style" : 1
|
||||||
|
# },
|
||||||
|
# "action" : {
|
||||||
|
# "type" : 2,
|
||||||
|
# "enter" : True,
|
||||||
|
# "permission": {
|
||||||
|
# "type": 2
|
||||||
|
# },
|
||||||
|
# "data" : "npm"
|
||||||
|
#
|
||||||
# }
|
# }
|
||||||
# }
|
# }
|
||||||
# ]
|
# ]
|
||||||
# }
|
# }
|
||||||
# ),
|
# ]
|
||||||
# ],
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
],
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
|
Loading…
Reference in New Issue
Block a user