✨ 父级插件加载

This commit is contained in:
HibiKier
2024-08-30 23:50:45 +08:00
parent 0074483280
commit a3681216bd
9 changed files with 177 additions and 153 deletions
+57 -59
View File
@@ -1,15 +1,17 @@
import base64
import math
import uuid
import base64
import itertools
import contextlib
from io import BytesIO
from pathlib import Path
from typing import Literal, Tuple, TypeAlias, overload
from typing_extensions import Self
from typing import Literal, TypeAlias, overload
from nonebot.utils import run_sync
from PIL import Image, ImageDraw, ImageFilter, ImageFont
from PIL.Image import Image as tImage
from PIL.ImageFont import FreeTypeFont
from typing_extensions import Self
from PIL import Image, ImageDraw, ImageFont, ImageFilter
from zhenxun.configs.path_config import FONT_PATH
@@ -18,7 +20,7 @@ ModeType = Literal[
]
"""图片类型"""
ColorAlias: TypeAlias = str | Tuple[int, int, int] | Tuple[int, int, int, int] | None
ColorAlias: TypeAlias = str | tuple[int, int, int] | tuple[int, int, int, int] | None
CenterType = Literal["center", "height", "width"]
"""
@@ -52,9 +54,7 @@ class BuildImage:
self.height = height
self.color = color
self.font = (
self.load_font(font, font_size)
if not isinstance(font, FreeTypeFont)
else font
font if isinstance(font, FreeTypeFont) else self.load_font(font, font_size)
)
if background:
if isinstance(background, bytes):
@@ -66,14 +66,14 @@ class BuildImage:
else:
self.width = self.markImg.width
self.height = self.markImg.height
else:
if not width or not height:
raise ValueError("长度和宽度不能为空...")
elif width and height:
self.markImg = Image.new(mode, (width, height), color) # type: ignore
else:
raise ValueError("长度和宽度不能为空...")
self.draw = ImageDraw.Draw(self.markImg)
@property
def size(self) -> Tuple[int, int]:
def size(self) -> tuple[int, int]:
return self.markImg.size
@classmethod
@@ -94,9 +94,9 @@ class BuildImage:
text: str,
font: str | FreeTypeFont | Path = "HYWenHei-85W.ttf",
size: int = 10,
font_color: str | Tuple[int, int, int] = (0, 0, 0),
font_color: str | tuple[int, int, int] = (0, 0, 0),
color: ColorAlias = None,
padding: int | Tuple[int, int, int, int] | None = None,
padding: int | tuple[int, int, int, int] | None = None,
) -> Self:
"""构建文本图片
@@ -116,7 +116,7 @@ class BuildImage:
_font = None
if isinstance(font, FreeTypeFont):
_font = font
elif isinstance(font, (str, Path)):
elif isinstance(font, str | Path):
_font = cls.load_font(font, size)
width, height = cls.get_text_size(text, _font)
if isinstance(padding, int):
@@ -161,7 +161,7 @@ class BuildImage:
row_count = math.ceil(len(img_list) / row)
if row_count == 1:
background_width = (
sum([img.width for img in img_list]) + space * (row - 1) + padding * 2
sum(img.width for img in img_list) + space * (row - 1) + padding * 2
)
background_height = height * row_count + space * (row_count - 1) + padding * 2
background_image = cls(
@@ -189,20 +189,20 @@ class BuildImage:
返回:
FreeTypeFont: 字体
"""
path = FONT_PATH / font if type(font) == str else font
path = FONT_PATH / font if type(font) is str else font
return ImageFont.truetype(str(path), font_size)
@overload
@classmethod
def get_text_size(
cls, text: str, font: FreeTypeFont | None = None
) -> Tuple[int, int]: ...
) -> tuple[int, int]: ...
@overload
@classmethod
def get_text_size(
cls, text: str, font: str | None = None, font_size: int = 10
) -> Tuple[int, int]: ...
) -> tuple[int, int]: ...
@classmethod
def get_text_size(
@@ -210,7 +210,7 @@ class BuildImage:
text: str,
font: str | FreeTypeFont | None = "HYWenHei-85W.ttf",
font_size: int = 10,
) -> Tuple[int, int]:
) -> tuple[int, int]: # sourcery skip: remove-unnecessary-cast
"""获取该字体下文本需要的长宽
参数:
@@ -219,10 +219,10 @@ class BuildImage:
font_size: 字体大小
返回:
Tuple[int, int]: 长宽
tuple[int, int]: 长宽
"""
_font = font
if font and type(font) == str:
if font and type(font) is str:
_font = cls.load_font(font, font_size)
temp_image = Image.new("RGB", (1, 1), (255, 255, 255))
draw = ImageDraw.Draw(temp_image)
@@ -232,7 +232,8 @@ class BuildImage:
return text_width, text_height + 10
# return _font.getsize(str(text)) # type: ignore
def getsize(self, msg: str) -> Tuple[int, int]:
def getsize(self, msg: str) -> tuple[int, int]:
# sourcery skip: remove-unnecessary-cast
"""
获取文字在该图片 font_size 下所需要的空间
@@ -240,7 +241,7 @@ class BuildImage:
msg: 文本
返回:
Tuple[int, int]: 长宽
tuple[int, int]: 长宽
"""
temp_image = Image.new("RGB", (1, 1), (255, 255, 255))
draw = ImageDraw.Draw(temp_image)
@@ -252,11 +253,11 @@ class BuildImage:
def __center_xy(
self,
pos: Tuple[int, int],
pos: tuple[int, int],
width: int,
height: int,
center_type: CenterType | None,
) -> Tuple[int, int]:
) -> tuple[int, int]:
"""
根据居中类型定位xy
@@ -266,7 +267,7 @@ class BuildImage:
center_type: 居中类型
返回:
Tuple[int, int]: 定位
tuple[int, int]: 定位
"""
# _width, _height = pos
if self.width and self.height:
@@ -285,7 +286,7 @@ class BuildImage:
def paste(
self,
image: Self | tImage,
pos: Tuple[int, int] = (0, 0),
pos: tuple[int, int] = (0, 0),
center_type: CenterType | None = None,
) -> Self:
"""贴图
@@ -303,7 +304,6 @@ class BuildImage:
"""
if center_type and center_type not in ["center", "height", "width"]:
raise ValueError("center_type must be 'center', 'width' or 'height'")
width, height = 0, 0
_image = image
if isinstance(image, BuildImage):
_image = image.markImg
@@ -317,7 +317,7 @@ class BuildImage:
@run_sync
def point(
self, pos: Tuple[int, int], fill: Tuple[int, int, int] | None = None
self, pos: tuple[int, int], fill: tuple[int, int, int] | None = None
) -> Self:
"""
绘制多个或单独的像素
@@ -335,9 +335,9 @@ class BuildImage:
@run_sync
def ellipse(
self,
pos: Tuple[int, int, int, int],
fill: Tuple[int, int, int] | None = None,
outline: Tuple[int, int, int] | None = None,
pos: tuple[int, int, int, int],
fill: tuple[int, int, int] | None = None,
outline: tuple[int, int, int] | None = None,
width: int = 1,
) -> Self:
"""
@@ -358,13 +358,13 @@ class BuildImage:
@run_sync
def text(
self,
pos: Tuple[int, int],
pos: tuple[int, int],
text: str,
fill: str | Tuple[int, int, int] = (0, 0, 0),
fill: str | tuple[int, int, int] = (0, 0, 0),
center_type: CenterType | None = None,
font: FreeTypeFont | str | Path | None = None,
font_size: int = 10,
) -> Self:
) -> Self: # sourcery skip: remove-unnecessary-cast
"""
在图片上添加文字
@@ -382,11 +382,10 @@ class BuildImage:
异常:
ValueError: 居中类型错误
"""
text = str(text)
if center_type and center_type not in ["center", "height", "width"]:
raise ValueError("center_type must be 'center', 'width' or 'height'")
max_length_text = ""
sentence = text.split("\n")
sentence = str(text).split("\n")
for x in sentence:
max_length_text = x if len(x) > len(max_length_text) else max_length_text
if font:
@@ -398,7 +397,7 @@ class BuildImage:
ttf_w, ttf_h = self.getsize(max_length_text) # type: ignore
# ttf_h = ttf_h * len(sentence)
pos = self.__center_xy(pos, ttf_w, ttf_h, center_type)
self.draw.text(pos, text, fill=fill, font=font)
self.draw.text(pos, str(text), fill=fill, font=font)
return self
@run_sync
@@ -437,7 +436,7 @@ class BuildImage:
if not width and not height and not ratio:
raise ValueError("缺少参数...")
if self.width and self.height:
if not width and not height and ratio:
if not width and not height:
width = int(self.width * ratio)
height = int(self.height * ratio)
self.markImg = self.markImg.resize((width, height), Image.LANCZOS) # type: ignore
@@ -446,7 +445,7 @@ class BuildImage:
return self
@run_sync
def crop(self, box: Tuple[int, int, int, int]) -> Self:
def crop(self, box: tuple[int, int, int, int]) -> Self:
"""
裁剪图片
@@ -475,11 +474,10 @@ class BuildImage:
"""
self.markImg = self.markImg.convert("RGBA")
x, y = self.markImg.size
for i in range(n, x - n):
for k in range(n, y - n):
color = self.markImg.getpixel((i, k))
color = color[:-1] + (int(100 * alpha_ratio),)
self.markImg.putpixel((i, k), color)
for i, k in itertools.product(range(n, x - n), range(n, y - n)):
color = self.markImg.getpixel((i, k))
color = color[:-1] + (int(100 * alpha_ratio),)
self.markImg.putpixel((i, k), color)
self.draw = ImageDraw.Draw(self.markImg)
return self
@@ -492,7 +490,7 @@ class BuildImage:
buf = BytesIO()
self.markImg.save(buf, format="PNG")
base64_str = base64.b64encode(buf.getvalue()).decode()
return "base64://" + base64_str
return f"base64://{base64_str}"
def pic2bytes(self) -> bytes:
"""获取bytes
@@ -520,8 +518,8 @@ class BuildImage:
@run_sync
def rectangle(
self,
xy: Tuple[int, int, int, int],
fill: Tuple[int, int, int] | None = None,
xy: tuple[int, int, int, int],
fill: tuple[int, int, int] | None = None,
outline: str | None = None,
width: int = 1,
) -> Self:
@@ -543,8 +541,8 @@ class BuildImage:
@run_sync
def polygon(
self,
xy: list[Tuple[int, int]],
fill: Tuple[int, int, int] = (0, 0, 0),
xy: list[tuple[int, int]],
fill: tuple[int, int, int] = (0, 0, 0),
outline: int = 1,
) -> Self:
"""
@@ -564,8 +562,8 @@ class BuildImage:
@run_sync
def line(
self,
xy: Tuple[int, int, int, int],
fill: Tuple[int, int, int] | str = "#D8DEE4",
xy: tuple[int, int, int, int],
fill: tuple[int, int, int] | str = "#D8DEE4",
width: int = 1,
) -> Self:
"""
@@ -605,21 +603,19 @@ class BuildImage:
)
draw = ImageDraw.Draw(mask)
for offset, fill in (width / -2.0, "black"), (width / 2.0, "white"):
left, top = [(value + offset) * antialias for value in ellipse_box[:2]]
right, bottom = [(value - offset) * antialias for value in ellipse_box[2:]]
left, top = ((value + offset) * antialias for value in ellipse_box[:2])
right, bottom = ((value - offset) * antialias for value in ellipse_box[2:])
draw.ellipse([left, top, right, bottom], fill=fill)
mask = mask.resize(self.markImg.size, Image.LANCZOS)
try:
with contextlib.suppress(ValueError):
self.markImg.putalpha(mask)
except ValueError:
pass
return self
@run_sync
def circle_corner(
self,
radii: int = 30,
point_list: list[Literal["lt", "rt", "lb", "rb"]] = ["lt", "rt", "lb", "rb"],
point_list: list[Literal["lt", "rt", "lb", "rb"]] | None = None,
) -> Self:
"""
矩形四角变圆
@@ -631,6 +627,8 @@ class BuildImage:
返回:
BuildImage: Self
"""
if point_list is None:
point_list = ["lt", "rt", "lb", "rb"]
# 画圆(用于分离4个角)
img = self.markImg.convert("RGBA")
alpha = img.split()[-1]
+9 -11
View File
@@ -1,11 +1,10 @@
import random
from io import BytesIO
from pathlib import Path
from typing import Any, Callable, Dict
from collections.abc import Callable
from fastapi import background
from PIL.ImageFont import FreeTypeFont
from pydantic import BaseModel
from PIL.ImageFont import FreeTypeFont
from ._build_image import BuildImage
@@ -25,13 +24,13 @@ class RowStyle(BaseModel):
class ImageTemplate:
color_list = ["#C2CEFE", "#FFA94C", "#3FE6A0", "#D1D4F5"]
color_list = ["#C2CEFE", "#FFA94C", "#3FE6A0", "#D1D4F5"] # noqa: RUF012
@classmethod
async def hl_page(
cls,
head_text: str,
items: Dict[str, str],
items: dict[str, str],
row_space: int = 10,
padding: int = 30,
) -> BuildImage:
@@ -162,9 +161,9 @@ class ImageTemplate:
column_data = []
for i in range(len(column_name)):
c = []
for l in data_list:
if len(l) > i:
c.append(l[i])
for lst in data_list:
if len(lst) > i:
c.append(lst[i])
else:
c.append("")
column_data.append(c)
@@ -188,7 +187,7 @@ class ImageTemplate:
column_name[i], font, 12, "#C8CCCF"
)
column_name_image_list.append(column_name_image)
max_h = max([c.height for c in column_name_image_list])
max_h = max(c.height for c in column_name_image_list)
for i, data in enumerate(build_data_list):
width = data["width"] + padding * 2
height = (base_h + row_space) * (len(data["data"]) + 1) + padding * 2
@@ -280,10 +279,9 @@ class ImageTemplate:
width = 0
height = 0
_, h = BuildImage.get_text_size("A", font)
image_list = []
for s in text.split("\n"):
s = s.strip() or "A"
w, _ = BuildImage.get_text_size(s, font)
width = width if width > w else w
width = max(width, w)
height += h
return width, height
+2
View File
@@ -42,6 +42,8 @@ class PluginType(StrEnum):
"""依赖插件,一般为没有主动触发命令的插件,受权限控制"""
HIDDEN = "HIDDEN"
"""隐藏插件,一般为没有主动触发命令的插件,不受权限控制,如消息统计"""
PARENT = "PARENT"
"""父插件,仅仅标记"""
class BlockType(StrEnum):
+24 -12
View File
@@ -2,20 +2,36 @@ from io import BytesIO
from pathlib import Path
import nonebot
from pydantic import BaseModel
from nonebot.adapters.onebot.v11 import Message, MessageSegment
from nonebot_plugin_alconna import At, Image, Text, UniMessage
from nonebot_plugin_alconna import At, Text, Image, Video, Voice, UniMessage
from zhenxun.configs.config import BotConfig
from zhenxun.services.log import logger
from zhenxun.configs.config import BotConfig
from zhenxun.utils._build_image import BuildImage
driver = nonebot.get_driver()
MESSAGE_TYPE = (
str | int | float | Path | bytes | BytesIO | BuildImage | At | Image | Text
str
| int
| float
| Path
| bytes
| BytesIO
| BuildImage
| At
| Image
| Text
| Voice
| Video
)
class Config(BaseModel):
is_bytes: bool = False
class MessageUtils:
@classmethod
@@ -28,20 +44,16 @@ class MessageUtils:
返回:
list[Text | Text]: 构造完成的消息列表
"""
is_bytes = False
try:
is_bytes = driver.config.image_to_bytes in ["True", "true"]
except AttributeError:
pass
config = nonebot.get_plugin_config(Config)
message_list = []
for msg in msg_list:
if isinstance(msg, (Image, Text, At)):
if isinstance(msg, Image | Text | At | Video | Voice):
message_list.append(msg)
elif isinstance(msg, (str, int, float)):
elif isinstance(msg, str | int | float):
message_list.append(Text(str(msg)))
elif isinstance(msg, Path):
if msg.exists():
if is_bytes:
if config.is_bytes:
image = BuildImage.open(msg)
message_list.append(Image(raw=image.pic2bytes()))
else:
@@ -120,7 +132,7 @@ class MessageUtils:
forward_data = []
for r_list in msg_list:
s = ""
if isinstance(r_list, (UniMessage, list)):
if isinstance(r_list, UniMessage | list):
for r in r_list:
if isinstance(r, Text):
s += str(r)