mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-10 22:30:02 +08:00
init🎉: 首次提交
This commit is contained in:
@@ -0,0 +1,656 @@
|
||||
import base64
|
||||
import math
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import List, Literal, Tuple, 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 zhenxun.configs.path_config import FONT_PATH
|
||||
|
||||
ModeType = Literal[
|
||||
"1", "CMYK", "F", "HSV", "I", "L", "LAB", "P", "RGB", "RGBA", "RGBX", "YCbCr"
|
||||
]
|
||||
"""图片类型"""
|
||||
|
||||
ColorAlias: TypeAlias = str | Tuple[int, int, int] | Tuple[int, int, int, int] | None
|
||||
|
||||
CenterType = Literal["center", "height", "width"]
|
||||
"""
|
||||
粘贴居中
|
||||
|
||||
center: 水平垂直居中
|
||||
|
||||
height: 垂直居中
|
||||
|
||||
width: 水平居中
|
||||
"""
|
||||
|
||||
|
||||
class BuildImage:
|
||||
"""
|
||||
快捷生成图片与操作图片的工具类
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
width: int = 0,
|
||||
height: int = 0,
|
||||
color: ColorAlias = None,
|
||||
mode: ModeType = "RGBA",
|
||||
font: str | Path | FreeTypeFont = "HYWenHei-85W.ttf",
|
||||
font_size: int = 20,
|
||||
background: str | BytesIO | Path | None = None,
|
||||
) -> None:
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.color = color
|
||||
self.font = (
|
||||
self.load_font(font, font_size)
|
||||
if not isinstance(font, FreeTypeFont)
|
||||
else font
|
||||
)
|
||||
if background:
|
||||
self.markImg = Image.open(background)
|
||||
if width and height:
|
||||
self.markImg = self.markImg.resize((width, height), Image.LANCZOS)
|
||||
else:
|
||||
if not width or not height:
|
||||
raise ValueError("长度和宽度不能为空...")
|
||||
self.markImg = Image.new(mode, (width, height), color) # type: ignore
|
||||
self.draw = ImageDraw.Draw(self.markImg)
|
||||
|
||||
@property
|
||||
def size(self) -> Tuple[int, int]:
|
||||
return self.markImg.size
|
||||
|
||||
@classmethod
|
||||
async def build_text_image(
|
||||
cls,
|
||||
text: str,
|
||||
font: str | Path = "HYWenHei-85W.ttf",
|
||||
size: int = 10,
|
||||
font_color: str | Tuple[int, int, int] = (0, 0, 0),
|
||||
color: ColorAlias = None,
|
||||
padding: int | Tuple[int, int, int, int] | None = None,
|
||||
) -> Self:
|
||||
"""构建文本图片
|
||||
|
||||
参数:
|
||||
text: 文本
|
||||
font: 字体路径
|
||||
size: 字体大小
|
||||
font_color: 字体颜色.
|
||||
color: 背景颜色
|
||||
padding: 外边距
|
||||
|
||||
返回:
|
||||
Self: Self
|
||||
"""
|
||||
_font = cls.load_font(font, size)
|
||||
width, height = cls.get_text_size(text, _font)
|
||||
if type(padding) == int:
|
||||
width += padding * 2
|
||||
height += padding * 2
|
||||
elif type(padding) == tuple:
|
||||
width += padding[1] + padding[3]
|
||||
height += padding[0] + padding[2]
|
||||
markImg = cls(width, height, color)
|
||||
await markImg.text(
|
||||
(0, 0), text, fill=font_color, font=_font, center_type="center"
|
||||
)
|
||||
return markImg
|
||||
|
||||
@classmethod
|
||||
async def auto_paste(
|
||||
cls,
|
||||
img_list: list[Self | tImage],
|
||||
row: int,
|
||||
space: int = 10,
|
||||
padding: int = 50,
|
||||
color: ColorAlias = (255, 255, 255, 0),
|
||||
background: str | BytesIO | Path | None = None,
|
||||
) -> Self | None:
|
||||
"""自动贴图
|
||||
|
||||
参数:
|
||||
img_list: 图片列表
|
||||
row: 一行图片的数量
|
||||
space: 图片之间的间距.
|
||||
padding: 外边距.
|
||||
color: 图片背景颜色.
|
||||
background: 图片背景图片.
|
||||
|
||||
返回:
|
||||
Self: Self
|
||||
"""
|
||||
if not img_list:
|
||||
return None
|
||||
width, height = img_list[0].size
|
||||
background_width = width * row + space * (row - 1) + padding * 2
|
||||
column = math.ceil(len(img_list) / row)
|
||||
background_height = height * column + space * (column - 1) + padding * 2
|
||||
background_image = cls(
|
||||
background_width, background_height, color=color, background=background
|
||||
)
|
||||
_cur_width, _cur_height = padding, padding
|
||||
for img in img_list:
|
||||
await background_image.paste(img, (_cur_width, _cur_height))
|
||||
_cur_width += space + img.width
|
||||
_cur_height += space + img.height
|
||||
if _cur_width + padding >= background_image.width:
|
||||
_cur_width = padding
|
||||
return background_image
|
||||
|
||||
@classmethod
|
||||
def load_font(cls, font: str | Path, font_size: int) -> FreeTypeFont:
|
||||
"""
|
||||
加载字体
|
||||
|
||||
参数:
|
||||
font: 字体名称
|
||||
font_size: 字体大小
|
||||
|
||||
返回:
|
||||
FreeTypeFont: 字体
|
||||
"""
|
||||
path = FONT_PATH / font if type(font) == 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]:
|
||||
...
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
def get_text_size(
|
||||
cls, text: str, font: str | None = None, font_size: int = 10
|
||||
) -> Tuple[int, int]:
|
||||
...
|
||||
|
||||
@classmethod
|
||||
def get_text_size(
|
||||
cls, text: str, font: str | FreeTypeFont | None = None, font_size: int = 10
|
||||
) -> Tuple[int, int]:
|
||||
"""获取该字体下文本需要的长宽
|
||||
|
||||
参数:
|
||||
text: 文本内容
|
||||
font: 字体名称或FreeTypeFont
|
||||
font_size: 字体大小
|
||||
|
||||
返回:
|
||||
Tuple[int, int]: 长宽
|
||||
"""
|
||||
_font = font
|
||||
if font and type(font) == str:
|
||||
_font = cls.load_font(font, font_size)
|
||||
return _font.getsize(text) # type: ignore
|
||||
|
||||
def getsize(self, msg: str) -> Tuple[int, int]:
|
||||
"""
|
||||
获取文字在该图片 font_size 下所需要的空间
|
||||
|
||||
参数:
|
||||
msg: 文本
|
||||
|
||||
返回:
|
||||
Tuple[int, int]: 长宽
|
||||
"""
|
||||
return self.font.getsize(msg) # type: ignore
|
||||
|
||||
def __center_xy(
|
||||
self,
|
||||
pos: Tuple[int, int],
|
||||
width: int,
|
||||
height: int,
|
||||
center_type: CenterType | None,
|
||||
) -> Tuple[int, int]:
|
||||
"""
|
||||
根据居中类型定位xy
|
||||
|
||||
参数:
|
||||
pos: 定位
|
||||
image: image
|
||||
center_type: 居中类型
|
||||
|
||||
返回:
|
||||
Tuple[int, int]: 定位
|
||||
"""
|
||||
# _width, _height = pos
|
||||
if self.width and self.height:
|
||||
if center_type == "center":
|
||||
width = int((self.width - width) / 2)
|
||||
height = int((self.height - height) / 2)
|
||||
elif center_type == "width":
|
||||
width = int((self.width - width) / 2)
|
||||
height = pos[1]
|
||||
elif center_type == "height":
|
||||
width = pos[0]
|
||||
height = int((self.height - height) / 2)
|
||||
return width, height
|
||||
|
||||
@run_sync
|
||||
def paste(
|
||||
self,
|
||||
image: Self | tImage,
|
||||
pos: Tuple[int, int] = (0, 0),
|
||||
center_type: CenterType | None = None,
|
||||
) -> Self:
|
||||
"""贴图
|
||||
|
||||
参数:
|
||||
image: BuildImage 或 Image
|
||||
pos: 定位.
|
||||
center_type: 居中.
|
||||
|
||||
返回:
|
||||
BuildImage: Self
|
||||
|
||||
异常:
|
||||
ValueError: 居中类型错误
|
||||
"""
|
||||
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
|
||||
if _image.width and _image.height and center_type:
|
||||
pos = self.__center_xy(pos, _image.width, _image.height, center_type)
|
||||
self.markImg.paste(_image, pos, _image) # type: ignore
|
||||
return self
|
||||
|
||||
@run_sync
|
||||
def point(
|
||||
self, pos: Tuple[int, int], fill: Tuple[int, int, int] | None = None
|
||||
) -> Self:
|
||||
"""
|
||||
绘制多个或单独的像素
|
||||
|
||||
参数:
|
||||
pos: 坐标
|
||||
fill: 填充颜色.
|
||||
|
||||
返回:
|
||||
BuildImage: Self
|
||||
"""
|
||||
self.draw.point(pos, fill=fill)
|
||||
return self
|
||||
|
||||
@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,
|
||||
width: int = 1,
|
||||
) -> Self:
|
||||
"""
|
||||
绘制圆
|
||||
|
||||
参数:
|
||||
pos: 坐标范围
|
||||
fill: 填充颜色.
|
||||
outline: 描线颜色.
|
||||
width: 描线宽度.
|
||||
|
||||
返回:
|
||||
BuildImage: Self
|
||||
"""
|
||||
self.draw.ellipse(pos, fill, outline, width)
|
||||
return self
|
||||
|
||||
@run_sync
|
||||
def text(
|
||||
self,
|
||||
pos: Tuple[int, int],
|
||||
text: str,
|
||||
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:
|
||||
"""
|
||||
在图片上添加文字
|
||||
|
||||
参数:
|
||||
pos: 文字位置
|
||||
text: 文字内容
|
||||
fill: 文字颜色.
|
||||
center_type: 居中类型.
|
||||
font: 字体.
|
||||
font_size: 字体大小.
|
||||
|
||||
返回:
|
||||
BuildImage: Self
|
||||
|
||||
异常:
|
||||
ValueError: 居中类型错误
|
||||
"""
|
||||
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
|
||||
max_length_text = ""
|
||||
sentence = text.split("\n")
|
||||
for x in sentence:
|
||||
max_length_text = x if len(x) > len(max_length_text) else max_length_text
|
||||
if font:
|
||||
if not isinstance(font, FreeTypeFont):
|
||||
font = self.load_font(font, font_size)
|
||||
else:
|
||||
font = self.font
|
||||
if center_type:
|
||||
ttf_w, ttf_h = font.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)
|
||||
return self
|
||||
|
||||
@run_sync
|
||||
def save(self, path: str | Path):
|
||||
"""
|
||||
保存图片
|
||||
|
||||
参数:
|
||||
path: 图片路径
|
||||
"""
|
||||
self.markImg.save(path) # type: ignore
|
||||
|
||||
def show(self):
|
||||
"""
|
||||
说明:
|
||||
显示图片
|
||||
"""
|
||||
self.markImg.show()
|
||||
|
||||
@run_sync
|
||||
def resize(self, ratio: float = 0, width: int = 0, height: int = 0) -> Self:
|
||||
"""
|
||||
压缩图片
|
||||
|
||||
参数:
|
||||
ratio: 压缩倍率.
|
||||
width: 压缩图片宽度至 width.
|
||||
height: 压缩图片高度至 height.
|
||||
|
||||
返回:
|
||||
BuildImage: Self
|
||||
|
||||
异常:
|
||||
ValueError: 缺少参数
|
||||
"""
|
||||
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:
|
||||
width = int(self.width * ratio)
|
||||
height = int(self.height * ratio)
|
||||
self.markImg = self.markImg.resize((width, height), Image.LANCZOS) # type: ignore
|
||||
self.width, self.height = self.markImg.size
|
||||
self.draw = ImageDraw.Draw(self.markImg)
|
||||
return self
|
||||
|
||||
@run_sync
|
||||
def crop(self, box: Tuple[int, int, int, int]) -> Self:
|
||||
"""
|
||||
裁剪图片
|
||||
|
||||
参数:
|
||||
box: 左上角坐标,右下角坐标 (left, upper, right, lower)
|
||||
|
||||
返回:
|
||||
BuildImage: Self
|
||||
"""
|
||||
self.markImg = self.markImg.crop(box)
|
||||
self.width, self.height = self.markImg.size
|
||||
self.draw = ImageDraw.Draw(self.markImg)
|
||||
return self
|
||||
|
||||
@run_sync
|
||||
def transparent(self, alpha_ratio: float = 1, n: int = 0) -> Self:
|
||||
"""
|
||||
图片透明化
|
||||
|
||||
参数:
|
||||
alpha_ratio: 透明化程度.
|
||||
n: 透明化大小内边距.
|
||||
|
||||
返回:
|
||||
BuildImage: Self
|
||||
"""
|
||||
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)
|
||||
self.draw = ImageDraw.Draw(self.markImg)
|
||||
return self
|
||||
|
||||
def pic2bs4(self) -> str:
|
||||
"""
|
||||
BuildImage 转 base64
|
||||
"""
|
||||
buf = BytesIO()
|
||||
self.markImg.save(buf, format="PNG")
|
||||
base64_str = base64.b64encode(buf.getvalue()).decode()
|
||||
return "base64://" + base64_str
|
||||
|
||||
def convert(self, type_: ModeType) -> Self:
|
||||
"""
|
||||
修改图片类型
|
||||
|
||||
参数:
|
||||
type_: ModeType
|
||||
|
||||
返回:
|
||||
BuildImage: Self
|
||||
"""
|
||||
self.markImg = self.markImg.convert(type_)
|
||||
return self
|
||||
|
||||
@run_sync
|
||||
def rectangle(
|
||||
self,
|
||||
xy: Tuple[int, int, int, int],
|
||||
fill: Tuple[int, int, int] | None = None,
|
||||
outline: str | None = None,
|
||||
width: int = 1,
|
||||
) -> Self:
|
||||
"""
|
||||
画框
|
||||
|
||||
参数:
|
||||
xy: 坐标
|
||||
fill: 填充颜色.
|
||||
outline: 轮廓颜色.
|
||||
width: 线宽.
|
||||
|
||||
返回:
|
||||
BuildImage: Self
|
||||
"""
|
||||
self.draw.rectangle(xy, fill, outline, width)
|
||||
return self
|
||||
|
||||
@run_sync
|
||||
def polygon(
|
||||
self,
|
||||
xy: List[Tuple[int, int]],
|
||||
fill: Tuple[int, int, int] = (0, 0, 0),
|
||||
outline: int = 1,
|
||||
) -> Self:
|
||||
"""
|
||||
画多边形
|
||||
|
||||
参数:
|
||||
xy: 坐标
|
||||
fill: 颜色.
|
||||
outline: 线宽.
|
||||
|
||||
返回:
|
||||
BuildImage: Self
|
||||
"""
|
||||
self.draw.polygon(xy, fill, outline)
|
||||
return self
|
||||
|
||||
@run_sync
|
||||
def line(
|
||||
self,
|
||||
xy: Tuple[int, int, int, int],
|
||||
fill: Tuple[int, int, int] | str = "#D8DEE4",
|
||||
width: int = 1,
|
||||
) -> Self:
|
||||
"""
|
||||
画线
|
||||
|
||||
参数:
|
||||
xy: 坐标
|
||||
fill: 填充.
|
||||
width: 线宽.
|
||||
|
||||
返回:
|
||||
BuildImage: Self
|
||||
"""
|
||||
self.draw.line(xy, fill, width)
|
||||
return self
|
||||
|
||||
@run_sync
|
||||
def circle(self) -> Self:
|
||||
"""
|
||||
图像变圆
|
||||
|
||||
返回:
|
||||
BuildImage: Self
|
||||
"""
|
||||
self.markImg.convert("RGBA")
|
||||
size = self.markImg.size
|
||||
r2 = min(size[0], size[1])
|
||||
if size[0] != size[1]:
|
||||
self.markImg = self.markImg.resize((r2, r2), Image.LANCZOS) # type: ignore
|
||||
width = 1
|
||||
antialias = 4
|
||||
ellipse_box = [0, 0, r2 - 2, r2 - 2]
|
||||
mask = Image.new(
|
||||
size=[int(dim * antialias) for dim in self.markImg.size], # type: ignore
|
||||
mode="L",
|
||||
color="black",
|
||||
)
|
||||
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:]]
|
||||
draw.ellipse([left, top, right, bottom], fill=fill)
|
||||
mask = mask.resize(self.markImg.size, Image.LANCZOS)
|
||||
try:
|
||||
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"],
|
||||
) -> Self:
|
||||
"""
|
||||
矩形四角变圆
|
||||
|
||||
参数:
|
||||
radii: 半径.
|
||||
point_list: 需要变化的角.
|
||||
|
||||
返回:
|
||||
BuildImage: Self
|
||||
"""
|
||||
# 画圆(用于分离4个角)
|
||||
img = self.markImg.convert("RGBA")
|
||||
alpha = img.split()[-1]
|
||||
circle = Image.new("L", (radii * 2, radii * 2), 0)
|
||||
draw = ImageDraw.Draw(circle)
|
||||
draw.ellipse((0, 0, radii * 2, radii * 2), fill=255) # 黑色方形内切白色圆形
|
||||
w, h = img.size
|
||||
if "lt" in point_list:
|
||||
alpha.paste(circle.crop((0, 0, radii, radii)), (0, 0))
|
||||
if "rt" in point_list:
|
||||
alpha.paste(circle.crop((radii, 0, radii * 2, radii)), (w - radii, 0))
|
||||
if "lb" in point_list:
|
||||
alpha.paste(circle.crop((0, radii, radii, radii * 2)), (0, h - radii))
|
||||
if "rb" in point_list:
|
||||
alpha.paste(
|
||||
circle.crop((radii, radii, radii * 2, radii * 2)),
|
||||
(w - radii, h - radii),
|
||||
)
|
||||
img.putalpha(alpha)
|
||||
self.markImg = img
|
||||
self.draw = ImageDraw.Draw(self.markImg)
|
||||
return self
|
||||
|
||||
@run_sync
|
||||
def rotate(self, angle: int, expand: bool = False) -> Self:
|
||||
"""
|
||||
旋转图片
|
||||
|
||||
参数:
|
||||
angle: 角度
|
||||
expand: 放大图片适应角度.
|
||||
|
||||
返回:
|
||||
BuildImage: Self
|
||||
"""
|
||||
self.markImg = self.markImg.rotate(angle, expand=expand)
|
||||
return self
|
||||
|
||||
@run_sync
|
||||
def transpose(self, angle: Literal[0, 1, 2, 3, 4, 5, 6]) -> Self:
|
||||
"""
|
||||
旋转图片(包括边框)
|
||||
|
||||
参数:
|
||||
angle: 角度
|
||||
|
||||
返回:
|
||||
BuildImage: Self
|
||||
"""
|
||||
self.markImg.transpose(angle)
|
||||
return self
|
||||
|
||||
@run_sync
|
||||
def filter(self, filter_: str, aud: int | None = None) -> Self:
|
||||
"""
|
||||
图片变化
|
||||
|
||||
参数:
|
||||
filter_: 变化效果
|
||||
aud: 利率.
|
||||
|
||||
返回:
|
||||
BuildImage: Self
|
||||
"""
|
||||
_type = None
|
||||
if filter_ == "GaussianBlur": # 高斯模糊
|
||||
_type = ImageFilter.GaussianBlur
|
||||
elif filter_ == "EDGE_ENHANCE": # 锐化效果
|
||||
_type = ImageFilter.EDGE_ENHANCE
|
||||
elif filter_ == "BLUR": # 模糊效果
|
||||
_type = ImageFilter.BLUR
|
||||
elif filter_ == "CONTOUR": # 铅笔滤镜
|
||||
_type = ImageFilter.CONTOUR
|
||||
elif filter_ == "FIND_EDGES": # 边缘检测
|
||||
_type = ImageFilter.FIND_EDGES
|
||||
if _type:
|
||||
if aud:
|
||||
self.markImg = self.markImg.filter(_type(aud)) # type: ignore
|
||||
else:
|
||||
self.markImg = self.markImg.filter(_type)
|
||||
self.draw = ImageDraw.Draw(self.markImg)
|
||||
return self
|
||||
@@ -0,0 +1,3 @@
|
||||
class BuildMat:
|
||||
def pic2bs4(self):
|
||||
return ""
|
||||
@@ -0,0 +1,47 @@
|
||||
from typing import Optional
|
||||
|
||||
from nonebot import get_driver
|
||||
from playwright.async_api import Browser, Playwright, async_playwright
|
||||
|
||||
from services.log import logger
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
_playwright: Optional[Playwright] = None
|
||||
_browser: Optional[Browser] = None
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def start_browser():
|
||||
global _playwright
|
||||
global _browser
|
||||
_playwright = await async_playwright().start()
|
||||
_browser = await _playwright.chromium.launch()
|
||||
|
||||
|
||||
@driver.on_shutdown
|
||||
async def shutdown_browser():
|
||||
if _browser:
|
||||
await _browser.close()
|
||||
if _playwright:
|
||||
await _playwright.stop() # type: ignore
|
||||
|
||||
|
||||
def get_browser() -> Browser:
|
||||
if not _browser:
|
||||
raise RuntimeError("playwright is not initalized")
|
||||
return _browser
|
||||
|
||||
|
||||
def install():
|
||||
"""自动安装、更新 Chromium"""
|
||||
logger.info("正在检查 Chromium 更新")
|
||||
import sys
|
||||
|
||||
from playwright.__main__ import main
|
||||
|
||||
sys.argv = ["", "install", "chromium"]
|
||||
try:
|
||||
main()
|
||||
except SystemExit:
|
||||
pass
|
||||
@@ -0,0 +1,73 @@
|
||||
from strenum import StrEnum
|
||||
|
||||
|
||||
class PluginType(StrEnum):
|
||||
"""
|
||||
插件类型
|
||||
"""
|
||||
|
||||
SUPERUSER = "超级管理员插件"
|
||||
ADMIN = "管理员插件"
|
||||
NORMAL = "普通插件"
|
||||
HIDDEN = "被动插件"
|
||||
|
||||
|
||||
class BlockType(StrEnum):
|
||||
"""
|
||||
禁用状态
|
||||
"""
|
||||
|
||||
FRIEND = "PRIVATE"
|
||||
GROUP = "GROUP"
|
||||
ALL = "ALL"
|
||||
|
||||
|
||||
class PluginLimitType(StrEnum):
|
||||
"""
|
||||
插件限制类型
|
||||
"""
|
||||
|
||||
CD = "CD"
|
||||
COUNT = "COUNT"
|
||||
BLOCK = "BLOCK"
|
||||
|
||||
|
||||
class LimitCheckType(StrEnum):
|
||||
"""
|
||||
插件限制类型
|
||||
"""
|
||||
|
||||
PRIVATE = "PRIVATE"
|
||||
GROUP = "GROUP"
|
||||
ALL = "ALL"
|
||||
|
||||
|
||||
class LimitWatchType(StrEnum):
|
||||
"""
|
||||
插件限制监听对象
|
||||
"""
|
||||
|
||||
USER = "USER"
|
||||
GROUP = "GROUP"
|
||||
|
||||
|
||||
class RequestType(StrEnum):
|
||||
"""
|
||||
请求类型
|
||||
"""
|
||||
|
||||
FRIEND = "FRIEND"
|
||||
GROUP = "GROUP"
|
||||
|
||||
|
||||
class RequestHandleType(StrEnum):
|
||||
"""
|
||||
请求类型
|
||||
"""
|
||||
|
||||
APPROVE = "APPROVE"
|
||||
"""同意"""
|
||||
REFUSED = "REFUSED"
|
||||
"""拒绝"""
|
||||
IGNORE = "IGNORE"
|
||||
"""忽略"""
|
||||
@@ -0,0 +1,2 @@
|
||||
class NotFoundError(Exception):
|
||||
pass
|
||||
@@ -0,0 +1,2 @@
|
||||
from ._build_image import BuildImage
|
||||
from ._build_mat import BuildMat
|
||||
@@ -0,0 +1,46 @@
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.internal.rule import Rule
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot_plugin_session import EventSession, SessionLevel
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.models.level_user import LevelUser
|
||||
|
||||
|
||||
def admin_check(a: int | str, key: str | None = None) -> Rule:
|
||||
"""
|
||||
管理员权限等级检查
|
||||
|
||||
参数:
|
||||
a: 权限等级或 配置项 module
|
||||
key: 配置项 key.
|
||||
|
||||
返回:
|
||||
Rule: Rule
|
||||
"""
|
||||
|
||||
async def _rule(bot: Bot, event: Event, session: EventSession) -> bool:
|
||||
if await SUPERUSER(bot, event):
|
||||
return True
|
||||
if session.id1 and session.id2:
|
||||
level = a
|
||||
if type(a) == str and key:
|
||||
level = Config.get_config(a, key)
|
||||
if level is not None:
|
||||
return bool(LevelUser.check_level(session.id1, session.id2, int(level)))
|
||||
return False
|
||||
|
||||
return Rule(_rule)
|
||||
|
||||
|
||||
def ensure_group(session: EventSession) -> bool:
|
||||
"""
|
||||
是否在群聊中
|
||||
|
||||
参数:
|
||||
session: session
|
||||
|
||||
返回:
|
||||
bool: bool
|
||||
"""
|
||||
return session.level in [SessionLevel.LEVEL2, SessionLevel.LEVEL3]
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
|
||||
class ResourceDirManager:
|
||||
"""
|
||||
临时文件管理器
|
||||
"""
|
||||
|
||||
temp_path = []
|
||||
|
||||
@classmethod
|
||||
def __tree_append(cls, path: Path):
|
||||
"""递归添加文件夹
|
||||
|
||||
参数:
|
||||
path: 文件夹路径
|
||||
"""
|
||||
for f in os.listdir(path):
|
||||
file = path / f
|
||||
if file.is_dir():
|
||||
if file not in cls.temp_path:
|
||||
cls.temp_path.append(file)
|
||||
logger.debug(f"添加临时文件夹: {path}")
|
||||
cls.__tree_append(file)
|
||||
|
||||
@classmethod
|
||||
def add_temp_dir(cls, path: str | Path, tree: bool = False):
|
||||
"""添加临时清理文件夹,这些文件夹会被自动清理
|
||||
|
||||
参数:
|
||||
path: 文件夹路径
|
||||
tree: 是否递归添加文件夹
|
||||
"""
|
||||
if isinstance(path, str):
|
||||
path = Path(path)
|
||||
if path not in cls.temp_path:
|
||||
cls.temp_path.append(path)
|
||||
logger.debug(f"添加临时文件夹: {path}")
|
||||
if tree:
|
||||
cls.__tree_append(path)
|
||||
|
||||
|
||||
async def get_user_avatar(uid: int | str) -> bytes | None:
|
||||
"""快捷获取用户头像
|
||||
|
||||
参数:
|
||||
uid: 用户id
|
||||
"""
|
||||
url = f"http://q1.qlogo.cn/g?b=qq&nk={uid}&s=160"
|
||||
async with httpx.AsyncClient() as client:
|
||||
for _ in range(3):
|
||||
try:
|
||||
return (await client.get(url)).content
|
||||
except Exception as e:
|
||||
logger.error("获取用户头像错误", "Util", target=uid)
|
||||
return None
|
||||
|
||||
|
||||
async def get_group_avatar(gid: int | str) -> bytes | None:
|
||||
"""快捷获取用群头像
|
||||
|
||||
参数:
|
||||
:param gid: 群号
|
||||
"""
|
||||
url = f"http://p.qlogo.cn/gh/{gid}/{gid}/640/"
|
||||
async with httpx.AsyncClient() as client:
|
||||
for _ in range(3):
|
||||
try:
|
||||
return (await client.get(url)).content
|
||||
except Exception as e:
|
||||
logger.error("获取群头像错误", "Util", target=gid)
|
||||
return None
|
||||
Reference in New Issue
Block a user