父级插件加载

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

View File

@ -2,9 +2,9 @@ import nonebot
import aiofiles
import ujson as json
from ruamel.yaml import YAML
from nonebot.plugin import Plugin
from nonebot.drivers import Driver
from nonebot import get_loaded_plugins
from nonebot.plugin import Plugin, PluginMetadata
from zhenxun.services.log import logger
from zhenxun.models.task_info import TaskInfo
@ -41,64 +41,68 @@ async def _handle_setting(
plugin_list: 插件列表
limit_list: 插件限制列表
"""
if metadata := plugin.metadata:
extra = metadata.extra
extra_data = PluginExtraData(**extra)
logger.debug(f"{metadata.name}:{plugin.name} -> {extra}", "初始化插件数据")
setting = extra_data.setting or PluginSetting()
if metadata.type == "library":
extra_data.plugin_type = PluginType.HIDDEN
if (
extra_data.plugin_type
== PluginType.HIDDEN
# and extra_data.plugin_type != "功能"
):
extra_data.menu_type = ""
plugin_list.append(
PluginInfo(
metadata = plugin.metadata
if not metadata:
if not plugin.sub_plugins:
return
"""父插件"""
metadata = PluginMetadata(name=plugin.name, description="", usage="")
extra = metadata.extra
extra_data = PluginExtraData(**extra)
logger.debug(f"{metadata.name}:{plugin.name} -> {extra}", "初始化插件数据")
setting = extra_data.setting or PluginSetting()
if metadata.type == "library":
extra_data.plugin_type = PluginType.HIDDEN
if extra_data.plugin_type == PluginType.HIDDEN:
extra_data.menu_type = ""
if plugin.sub_plugins:
extra_data.plugin_type = PluginType.PARENT
plugin_list.append(
PluginInfo(
module=plugin.name,
module_path=plugin.module_name,
name=metadata.name,
author=extra_data.author,
version=extra_data.version,
level=setting.level,
default_status=setting.default_status,
limit_superuser=setting.limit_superuser,
menu_type=extra_data.menu_type,
cost_gold=setting.cost_gold,
plugin_type=extra_data.plugin_type,
admin_level=extra_data.admin_level,
parent=(plugin.parent_plugin.module_name if plugin.parent_plugin else None),
)
)
if extra_data.limits:
limit_list.extend(
PluginLimit(
module=plugin.name,
module_path=plugin.module_name,
name=metadata.name,
author=extra_data.author,
version=extra_data.version,
level=setting.level,
default_status=setting.default_status,
limit_superuser=setting.limit_superuser,
menu_type=extra_data.menu_type,
cost_gold=setting.cost_gold,
plugin_type=extra_data.plugin_type,
admin_level=extra_data.admin_level,
limit_type=limit._type,
watch_type=limit.watch_type,
status=limit.status,
check_type=limit.check_type,
result=limit.result,
cd=getattr(limit, "cd", None),
max_count=getattr(limit, "max_count", None),
)
for limit in extra_data.limits
)
if extra_data.limits:
limit_list.extend(
PluginLimit(
module=plugin.name,
module_path=plugin.module_name,
limit_type=limit._type,
watch_type=limit.watch_type,
status=limit.status,
check_type=limit.check_type,
result=limit.result,
cd=getattr(limit, "cd", None),
max_count=getattr(limit, "max_count", None),
)
for limit in extra_data.limits
)
if extra_data.tasks:
task_list.extend(
(
task.create_status,
TaskInfo(
module=task.module,
name=task.name,
status=task.status,
run_time=task.run_time,
default_status=task.default_status,
),
)
for task in extra_data.tasks
if extra_data.tasks:
task_list.extend(
(
task.create_status,
TaskInfo(
module=task.module,
name=task.name,
status=task.status,
run_time=task.run_time,
default_status=task.default_status,
),
)
for task in extra_data.tasks
)
@driver.on_startup
@ -115,8 +119,7 @@ async def _():
module2id = {m["module_path"]: m["id"] for m in module_list}
for plugin in get_loaded_plugins():
load_plugin.append(plugin.module_name)
if plugin.metadata:
await _handle_setting(plugin, plugin_list, limit_list, task_list)
await _handle_setting(plugin, plugin_list, limit_list, task_list)
create_list = []
update_list = []
for plugin in plugin_list:

View File

@ -1,12 +1,12 @@
from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import Alconna, Args, Subcommand, on_alconna
from nonebot_plugin_session import EventSession
from nonebot_plugin_alconna import Args, Alconna, Subcommand, on_alconna
from zhenxun.configs.utils import PluginExtraData
from zhenxun.services.log import logger
from zhenxun.utils.enum import PluginType
from zhenxun.utils.message import MessageUtils
from zhenxun.configs.utils import PluginExtraData
from .data_source import ShopManage
@ -68,6 +68,7 @@ _matcher.shortcut(
prefix=True,
)
@_matcher.assign("$main")
async def _(session: EventSession):
try:
@ -82,9 +83,7 @@ async def _(session: EventSession):
@_matcher.assign("add")
async def _(session: EventSession, plugin_id: int):
try:
await MessageUtils.build_message(
f"正在添加插件 Id: {plugin_id}"
).send()
await MessageUtils.build_message(f"正在添加插件 Id: {plugin_id}").send()
result = await ShopManage.add_plugin(plugin_id)
except Exception as e:
logger.error(f"添加插件 Id: {plugin_id}失败", "插件商店", session=session, e=e)
@ -107,24 +106,29 @@ async def _(session: EventSession, plugin_id: int):
logger.info(f"移除插件 Id: {plugin_id}", "插件商店", session=session)
await MessageUtils.build_message(result).send()
@_matcher.assign("search")
async def _(session: EventSession, plugin_name_or_author: str):
try:
result = await ShopManage.search_plugin(plugin_name_or_author)
except Exception as e:
logger.error(f"搜索插件 name: {plugin_name_or_author}失败", "插件商店", session=session, e=e)
logger.error(
f"搜索插件 name: {plugin_name_or_author}失败",
"插件商店",
session=session,
e=e,
)
await MessageUtils.build_message(
f"搜索插件 name: {plugin_name_or_author} 失败 e: {e}"
).finish()
logger.info(f"搜索插件 name: {plugin_name_or_author}", "插件商店", session=session)
await MessageUtils.build_message(result).send()
@_matcher.assign("update")
async def _(session: EventSession, plugin_id: int):
try:
await MessageUtils.build_message(
f"正在更新插件 Id: {plugin_id}"
).send()
await MessageUtils.build_message(f"正在更新插件 Id: {plugin_id}").send()
result = await ShopManage.update_plugin(plugin_id)
except Exception as e:
logger.error(f"更新插件 Id: {plugin_id}失败", "插件商店", session=session, e=e)

View File

@ -211,7 +211,7 @@ class ShopManage:
plugin_list = await PluginInfo.filter(load_status=True).values_list(
"module", "version"
)
suc_plugin = {p[0]: p[1] for p in plugin_list if p[1]}
suc_plugin = {p[0]: (p[1] or "0.1") for p in plugin_list}
data_list = [
[
"已安装" if plugin_info[1]["module"] in suc_plugin else "",
@ -226,7 +226,7 @@ class ShopManage:
]
return await ImageTemplate.table_page(
"插件列表",
"通过安装/卸载插件 ID 来管理插件",
"通过添加/移除插件 ID 来管理插件",
column_name,
data_list,
text_style=row_style,

View File

@ -356,7 +356,7 @@ class ConfigsManager:
value = default
logger.debug(
f"获取配置 MODULE: [<u><y>{module}</y></u>] | "
" KEY: [<u><y>{key}</y></u>] -> [<u><c>{value}</c></u>]"
f" KEY: [<u><y>{key}</y></u>] -> [<u><c>{value}</c></u>]"
)
return value

View File

@ -2,8 +2,7 @@ from tortoise import fields
from zhenxun.services.db_context import Model
from zhenxun.utils.enum import BlockType, PluginType
from .plugin_limit import PluginLimit
from zhenxun.models.plugin_limit import PluginLimit # noqa: F401
class PluginInfo(Model):
@ -45,7 +44,15 @@ class PluginInfo(Model):
"""调用所需权限等级"""
is_delete = fields.BooleanField(default=False, description="是否删除")
"""是否删除"""
parent = fields.CharField(max_length=255, null=True, description="父插件")
"""父插件"""
class Meta:
class Meta: # type: ignore
table = "plugin_info"
table_description = "插件基本信息"
@classmethod
async def _run_script(cls):
return [
"ALTER TABLE plugin_info ADD COLUMN parent character varying(255);",
]

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]

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

View File

@ -42,6 +42,8 @@ class PluginType(StrEnum):
"""依赖插件,一般为没有主动触发命令的插件,受权限控制"""
HIDDEN = "HIDDEN"
"""隐藏插件,一般为没有主动触发命令的插件,不受权限控制,如消息统计"""
PARENT = "PARENT"
"""父插件,仅仅标记"""
class BlockType(StrEnum):

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)