refactor code

This commit is contained in:
hibiki
2021-07-30 21:21:51 +08:00
parent 2ad891aa1e
commit cc24822dca
165 changed files with 7815 additions and 8174 deletions
+3 -18
View File
@@ -1,11 +1,8 @@
from typing import Optional
import shutil
from nonebot.log import logger
from playwright.async_api import Browser, async_playwright
import nonebot
from nonebot import Driver
from appdirs import AppDirs
from pathlib import Path
from services.log import logger
@@ -22,7 +19,7 @@ async def init(**kwargs) -> Optional[Browser]:
_browser = await browser.chromium.launch(**kwargs)
return _browser
except NotImplementedError:
logger.warning('win环境下 初始化playwright失败....请替换环境至linux')
logger.warning("win环境下 初始化playwright失败....请替换环境至linux")
return None
@@ -36,18 +33,6 @@ def install():
logger.info("正在检查 Chromium 更新")
import sys
from playwright.__main__ import main
sys.argv = ['', 'install', 'chromium']
sys.argv = ["", "install", "chromium"]
main()
@driver.on_startup
def delete_pyppeteer():
"""删除 Pyppeteer 遗留的 Chromium"""
dir = Path(AppDirs('pyppeteer').user_data_dir)
if not dir.exists():
return
shutil.rmtree(dir)
logger.info("已清理 Pyppeteer 依赖残留")
+11 -4
View File
@@ -1,9 +1,16 @@
from models.group_member_info import GroupInfoUser
from typing import List
# 生成通用排行榜
async def init_rank(users: list, all_user_data: list, group_id: int):
all_user_id = [user.user_qq for user in users]
async def init_rank(all_user_id: List[int], all_user_data: List[int], group_id: int) -> str:
"""
说明:
初始化通用的数据排行榜
参数:
:param all_user_id: 所有用户的qq号
:param all_user_data: 所有用户需要排行的对应数据
:param group_id: 群号,用于从数据库中获取该用户在此群的昵称
"""
rst = ''
for i in range(len(all_user_id) if len(all_user_id) < 10 else 10):
_max = max(all_user_data)
@@ -11,7 +18,7 @@ async def init_rank(users: list, all_user_data: list, group_id: int):
all_user_id.remove(max_user_id)
all_user_data.remove(_max)
try:
user_name = (await GroupInfoUser.select_member_info(max_user_id, group_id)).user_name
user_name = (await GroupInfoUser.get_member_info(max_user_id, group_id)).user_name
except AttributeError:
user_name = f'{max_user_id}'
rst += f'{user_name}: {_max}\n'
+389
View File
@@ -0,0 +1,389 @@
from configs.path_config import IMAGE_PATH, TTF_PATH
from PIL import Image, ImageFile, ImageDraw, ImageFont
from imagehash import ImageHash
from io import BytesIO
from matplotlib import pyplot as plt
from typing import Tuple, Optional
from pathlib import Path
import cv2
import base64
import imagehash
def compare_image_with_hash(
image_file1: str, image_file2: str, max_dif: int = 1.5
) -> bool:
"""
说明:
比较两张图片的hash值是否相同
参数:
:param image_file1: 图片文件路径
:param image_file2: 图片文件路径
:param max_dif: 允许最大hash差值, 越小越精确,最小为0
"""
ImageFile.LOAD_TRUNCATED_IMAGES = True
hash_1 = get_img_hash(image_file1)
hash_2 = get_img_hash(image_file2)
dif = hash_1 - hash_2
if dif < 0:
dif = -dif
if dif <= max_dif:
return True
else:
return False
def get_img_hash(image_file: str) -> ImageHash:
"""
说明:
获取图片的hash值
参数:
:param image_file: 图片文件路径
"""
with open(image_file, "rb") as fp:
hash_value = imagehash.average_hash(Image.open(fp))
return hash_value
def compressed_image(in_file: str, out_file: str = None, ratio: float = 0.9):
"""
说明:
压缩图片
参数:
:param in_file: 被压缩的文件路径
:param out_file: 压缩后输出的文件路径
:param ratio: 压缩率,宽高 * 压缩率
"""
in_file = Path(IMAGE_PATH) / in_file
out_file = Path(IMAGE_PATH) / out_file if out_file else in_file
h, w, d = cv2.imread(str(in_file.absolute())).shape
img = cv2.resize(cv2.imread(str(in_file.absolute())), (int(w * ratio), int(h * ratio)))
cv2.imwrite(str(out_file.absolute()), img)
def alpha2white_PIL(pic: Image) -> Image:
"""
说明:
将图片透明背景转化为白色
参数:
:param pic: 通过PIL打开的图片文件
"""
img = pic.convert("RGBA")
width, height = img.size
for yh in range(height):
for xw in range(width):
dot = (xw, yh)
color_d = img.getpixel(dot)
if color_d[3] == 0:
color_d = (255, 255, 255, 255)
img.putpixel(dot, color_d)
return img
def pic2b64(pic: Image) -> str:
"""
说明:
PIL图片转base64
参数:
:param pic: 通过PIL打开的图片文件
"""
buf = BytesIO()
pic.save(buf, format="PNG")
base64_str = base64.b64encode(buf.getvalue()).decode()
return "base64://" + base64_str
def fig2b64(plt_: plt) -> str:
"""
说明:
matplotlib图片转base64
参数:
:param plt_: matplotlib生成的图片
"""
buf = BytesIO()
plt_.savefig(buf, format="PNG", dpi=100)
base64_str = base64.b64encode(buf.getvalue()).decode()
return "base64://" + base64_str
def is_valid(file: str) -> bool:
"""
说明:
判断图片是否损坏
参数:
:param file: 图片文件路径
"""
valid = True
try:
Image.open(file).load()
except OSError:
valid = False
return valid
class CreateImg:
"""
快捷生成图片与操作图片的工具类
"""
def __init__(
self,
w,
h,
paste_image_width=0,
paste_image_height=0,
color="white",
image_type="RGBA",
font_size=10,
background="",
ttf="yz.ttf",
ratio=1,
):
"""
参数:
:param w: 自定义图片的宽度,w=0时为图片原本宽度
:param h: 自定义图片的高度,h=0时为图片原本高度
:param paste_image_width: 当图片做为背景图时,设置贴图的宽度,用于贴图自动换行
:param paste_image_height: 当图片做为背景图时,设置贴图的高度,用于贴图自动换行
:param color: 生成图片的颜色
:param image_type: 图片的类型
:param font_size: 文字大小
:param background: 打开图片的路径
:param ttf: 字体,默认在 resource/ttf/ 路径下
:param ratio: 倍率压缩
"""
self.w = int(w)
self.h = int(h)
self.paste_image_width = int(paste_image_width)
self.paste_image_height = int(paste_image_height)
self.current_w = 0
self.current_h = 0
self.ttfont = ImageFont.truetype(TTF_PATH + ttf, int(font_size))
if not background:
self.markImg = Image.new(image_type, (self.w, self.h), color)
else:
if not w and not h:
self.markImg = Image.open(background)
w, h = self.markImg.size
if ratio and ratio > 0 and ratio != 1:
self.w = int(ratio * w)
self.h = int(ratio * h)
self.markImg = self.markImg.resize(
(self.w, self.h), Image.ANTIALIAS
)
else:
self.w = w
self.h = h
else:
self.markImg = Image.open(background).resize(
(self.w, self.h), Image.ANTIALIAS
)
self.draw = ImageDraw.Draw(self.markImg)
self.size = self.w, self.h
def paste(
self,
img: "CreateImg" or Image,
pos: Tuple[int, int] = None,
alpha: bool = False,
):
"""
说明:
贴图
参数:
:param img: 已打开的图片文件,可以为 CreateImg 或 Image
:param pos: 贴图位置(左上角)
:param alpha: 图片背景是否为透明
"""
if isinstance(img, CreateImg):
img = img.markImg
if self.current_w == self.w:
self.current_w = 0
self.current_h += self.paste_image_height
if not pos:
pos = (self.current_w, self.current_h)
if alpha:
try:
self.markImg.paste(img, pos, img)
except ValueError:
img = img.convert("RGBA")
self.markImg.paste(img, pos, img)
else:
self.markImg.paste(img, pos)
self.current_w += self.paste_image_width
def getsize(self, msg: str) -> Tuple[int, int]:
"""
说明:
获取文字在该图片 font_size 下所需要的空间
参数:
:param msg: 文字内容
"""
return self.ttfont.getsize(msg)
def text(
self, pos: Tuple[int, int], text: str, fill: Tuple[int, int, int] = (0, 0, 0)
):
"""
说明:
在图片上添加文字
参数:
:param pos: 文字位置
:param text: 文字内容
:param fill: 文字颜色
"""
self.draw.text(pos, text, fill=fill, font=self.ttfont)
def save(self, path: str):
"""
说明:
保存图片
参数:
:param path: 图片路径
"""
self.markImg.save(path)
def show(self):
"""
说明:
显示图片
"""
self.markImg.show(self.markImg)
def resize(self, ratio: float = 0, w: int = 0, h: int = 0):
"""
说明:
压缩图片
参数:
:param ratio: 压缩倍率
:param w: 压缩图片宽度至 w
:param h: 压缩图片高度至 h
"""
if not w and not h and not ratio:
raise Exception("缺少参数...")
if not w and not h and ratio:
w = int(self.w * ratio)
h = int(self.h * ratio)
self.markImg = self.markImg.resize((w, h), Image.ANTIALIAS)
self.w, self.h = self.markImg.size
self.size = self.w, self.h
self.draw = ImageDraw.Draw(self.markImg)
def crop(self, box: Tuple[int, int, int, int]):
"""
说明:
裁剪图片
参数:
:param box: 左上角坐标,右下角坐标 (left, upper, right, lower)
"""
self.markImg = self.markImg.crop(box)
self.w, self.h = self.markImg.size
self.size = self.w, self.h
self.draw = ImageDraw.Draw(self.markImg)
def check_font_size(self, word: str) -> bool:
"""
说明:
检查文本所需宽度是否大于图片宽度
:param word: 文本内容
"""
return self.ttfont.getsize(word)[0] > self.w
def transparent(self, n: int = 0):
"""
说明:
图片透明化
参数:
:param n: 透明化大小内边距
"""
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] + (100,)
self.markImg.putpixel((i, k), color)
def pic2bs4(self) -> str:
"""
说明:
CreateImg 转 base64
"""
buf = BytesIO()
self.markImg.save(buf, format="PNG")
base64_str = base64.b64encode(buf.getvalue()).decode()
return base64_str
def convert(self, type_: str):
"""
说明:
修改图片类型
参数:
:param type_: 类型
"""
self.markImg = self.markImg.convert(type_)
def rectangle(
self,
xy: Tuple[int, int, int, int],
fill: Optional[Tuple[int, int, int]] = None,
outline: str = None,
width: int = 1,
):
"""
说明:
画框
参数:
:param xy: 坐标
:param fill: 填充颜色
:param outline: 轮廓颜色
:param width: 线宽
"""
self.draw.rectangle(xy, fill, outline, width)
def line(
self,
xy: Tuple[int, int, int, int],
fill: Optional[Tuple[int, int, int]] = None,
width: int = 1,
):
"""
说明:
画线
参数:
:param xy: 坐标
:param fill: 填充
:param width: 线宽
"""
self.draw.line(xy, fill, width)
def circle(self):
"""
说明:
将 CreateImg 图片变为圆形
"""
self.convert("RGBA")
r2 = min(self.w, self.h)
if self.w != self.h:
self.resize(w=r2, h=r2)
r3 = int(r2 / 2)
imb = Image.new("RGBA", (r3 * 2, r3 * 2), (255, 255, 255, 0))
pim_a = self.markImg.load() # 像素的访问对象
pim_b = imb.load()
r = float(r2 / 2)
for i in range(r2):
for j in range(r2):
lx = abs(i - r) # 到圆心距离的横坐标
ly = abs(j - r) # 到圆心距离的纵坐标
l = (pow(lx, 2) + pow(ly, 2)) ** 0.5 # 三角函数 半径
if l < r3:
pim_b[i - (r - r3), j - (r - r3)] = pim_a[i, j]
self.markImg = imb
#
def getchannel(self, itype):
self.markImg = self.markImg.getchannel(itype)
if __name__ == "__main__":
pass
-339
View File
@@ -1,339 +0,0 @@
import os
from configs.path_config import IMAGE_PATH, TXT_PATH, TTF_PATH
from PIL import Image, ImageFile, ImageDraw, ImageFont
import cv2
from typing import Tuple
import imagehash
import base64
from io import BytesIO
from matplotlib import pyplot as plt
# 扫描图库id是否连贯
def scan_img(path):
path = IMAGE_PATH + path
nolist = []
length = len(os.listdir(path))
print(length)
for i in range(length):
if i in nolist:
continue
img_path = path + "{}.jpg".format(i)
if not os.path.exists(img_path):
print("不存在=== " + str(length) + ".jpg -------> " + str(i) + ".jpg")
os.rename(path + "{}.jpg".format(length - 1), img_path)
nolist.append(length)
length -= 1
# 比较hash值
def compare_image_with_hash(image_file1, image_file2, max_dif=1.5):
"""
max_dif: 允许最大hash差值, 越小越精确,最小为0
推荐使用
"""
ImageFile.LOAD_TRUNCATED_IMAGES = True
hash_1 = None
hash_2 = None
hash_1 = get_img_hash(image_file1)
hash_2 = get_img_hash(image_file2)
dif = hash_1 - hash_2
if dif < 0:
dif = -dif
if dif <= max_dif:
return True
else:
return False
# 比较图片与hash值
def compare_one_img_hash(image_file, hash_2, max_dif=1.5):
hash_1 = get_img_hash(image_file)
dif = hash_1 - hash_2
if dif < 0:
dif = -dif
if dif <= max_dif:
return True
else:
return False
def get_img_hash(image_file):
with open(image_file, 'rb') as fp:
hash_value = imagehash.average_hash(Image.open(fp))
return hash_value
# 压缩图片
def rar_imgs(inpath, outpath, ratio=0.9, start=0, end=0, lens=0, maxsize=0.0, in_file_name='', out_file_name='',
itype='jpg'):
in_path = IMAGE_PATH + inpath + '/'
out_path = IMAGE_PATH + outpath + '/'
# scan_img(inpath)
l = []
if in_file_name != '' and out_file_name != '':
filein = in_path + in_file_name + "." + itype
fileout = out_path + out_file_name + "." + itype
h, w, d = cv2.imread(filein).shape
width = int(w * ratio)
height = int(h * ratio)
ResizeImage(filein, fileout, width, height)
else:
if lens == 0:
lens = len(os.listdir(in_path))
if end == 0:
end = lens
for i in range(start, end):
if i in l:
continue
if maxsize != 0:
if os.path.getsize(in_path + str(i) + ".jpg") > maxsize:
print("压缩----->", i, ".jpg")
filein = in_path + str(i) + ".jpg"
fileout = out_path + str(i) + ".jpg"
h, w, d = cv2.imread(filein).shape
width = int(w * ratio)
height = int(h * ratio)
ResizeImage(filein, fileout, width, height)
else:
continue
else:
print("压缩----->", i, ".jpg")
filein = in_path + str(i) + ".jpg"
fileout = out_path + str(i) + ".jpg"
h, w, d = cv2.imread(filein).shape
width = int(w * ratio)
height = int(h * ratio)
ResizeImage(filein, fileout, width, height)
# 压缩
def ResizeImage(filein, fileout, width, height):
img = cv2.resize(cv2.imread(filein), (int(width), int(height)))
cv2.imwrite(fileout, img)
# 保存图片压缩后的hash值
def save_img_hash(path, name):
for file in os.listdir(IMAGE_PATH + path):
if os.path.getsize(IMAGE_PATH + path + file) > 1024 * 1024 * 1.5:
compare_img_hash_in_txt(IMAGE_PATH + 'rar/' + file, name)
else:
compare_img_hash_in_txt(IMAGE_PATH + path + file, name)
# 比较色图hash值
def compare_img_hash_in_txt(file, name, mode=1):
with open(TXT_PATH + name + ".txt", 'a+') as txtfile:
txtfile.seek(0)
hash_list = txtfile.read()[:-1].strip(",")
txtfile.seek(2)
with open(file, 'rb') as fp:
img_hash = str(imagehash.average_hash(Image.open(fp)))
if img_hash not in hash_list:
if mode == 1:
txtfile.write(img_hash + ",")
return False
return True
# 透明背景 -> 白色
def alphabg2white_PIL(img):
img = img.convert('RGBA')
sp = img.size
width = sp[0]
height = sp[1]
for yh in range(height):
for xw in range(width):
dot = (xw, yh)
color_d = img.getpixel(dot)
if color_d[3] == 0:
color_d = (255, 255, 255, 255)
img.putpixel(dot, color_d)
return img
def pic2b64(pic: Image) -> str:
buf = BytesIO()
pic.save(buf, format='PNG')
base64_str = base64.b64encode(buf.getvalue()).decode()
return 'base64://' + base64_str
def fig2b64(plt: plt) -> str:
buf = BytesIO()
plt.savefig(buf, format='PNG', dpi=100)
base64_str = base64.b64encode(buf.getvalue()).decode()
return 'base64://' + base64_str
class CreateImg:
def __init__(self,
w,
h,
img_w=0,
img_h=0,
color='white',
image_type='RGBA',
font_size=10,
background='',
ttf='yz.ttf',
divisor=1):
self.w = int(w)
self.h = int(h)
self.img_w = int(img_w)
self.img_h = int(img_h)
self.current_w = 0
self.current_h = 0
self.ttfont = ImageFont.truetype(TTF_PATH + ttf, int(font_size))
if not background:
self.markImg = Image.new(image_type, (self.w, self.h), color)
else:
if w == 0 and h == 0:
self.markImg = Image.open(background)
w, h = self.markImg.size
if divisor:
self.w = int(divisor * w)
self.h = int(divisor * h)
self.markImg = self.markImg.resize((self.w, self.h), Image.ANTIALIAS)
else:
self.w = w
self.h = h
else:
self.markImg = Image.open(background).resize((self.w, self.h), Image.ANTIALIAS)
self.draw = ImageDraw.Draw(self.markImg)
self.size = self.w, self.h
# 贴图
def paste(self, img, pos=None, alpha=False):
if isinstance(img, CreateImg):
img = img.markImg
if self.current_w == self.w:
self.current_w = 0
self.current_h += self.img_h
if not pos:
pos = (self.current_w, self.current_h)
if alpha:
try:
self.markImg.paste(img, pos, img)
except ValueError:
img = img.convert("RGBA")
self.markImg.paste(img, pos, img)
else:
self.markImg.paste(img, pos)
self.current_w += self.img_w
return self.markImg
def crop(self, box: Tuple[int, int, int, int]):
"""
说明:
裁剪图片
参数:
:param box: 左上角坐标,右下角坐标 (left, upper, right, lower)
"""
self.markImg = self.markImg.crop(box)
self.w, self.h = self.markImg.size
self.size = self.w, self.h
self.draw = ImageDraw.Draw(self.markImg)
# 获取文字大小
def getsize(self, msg):
return self.ttfont.getsize(msg)
# 写字
def text(self, pos, text, fill=(0, 0, 0)):
self.draw.text(pos, text, fill=fill, font=self.ttfont)
return self.markImg
# 饼图
def pieslice(self):
self.draw.pieslice((350, 50, 500, 200), -150, -30, 'pink', 'crimson')
return self.markImg
# 保存
def save(self, path):
self.markImg.save(path)
# 显示
def show(self):
self.markImg.show(self.markImg)
# 压缩
def resize(self, ratio=0, w=0, h=0):
if not w and not h and not ratio:
raise Exception('缺少参数...')
if not w and not h and ratio:
w = int(self.w * ratio)
h = int(self.h * ratio)
self.markImg = self.markImg.resize((w, h), Image.ANTIALIAS)
self.w, self.h = self.markImg.size
self.size = self.w, self.h
self.draw = ImageDraw.Draw(self.markImg)
# 检查字体大小
def check_font_size(self, word):
return self.ttfont.getsize(word)[0] > self.w
# 透明化
def transparent(self, n=0):
self.markImg = self.markImg.convert('RGBA') # 修改颜色通道为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] + (100, )
self.markImg.putpixel((i, k), color)
return self.markImg
# 转bs4:
def pic2bs4(self):
buf = BytesIO()
self.markImg.save(buf, format='PNG')
base64_str = base64.b64encode(buf.getvalue()).decode()
return base64_str
#
def convert(self, itype):
self.markImg = self.markImg.convert(itype)
# 变圆
def circle(self):
self.convert('RGBA')
r2 = min(self.w, self.h)
if self.w != self.h:
self.resize(w=r2, h=r2)
r3 = int(r2 / 2)
imb = Image.new('RGBA', (r3 * 2, r3 * 2), (255, 255, 255, 0))
pima = self.markImg.load() # 像素的访问对象
pimb = imb.load()
r = float(r2 / 2)
for i in range(r2):
for j in range(r2):
lx = abs(i - r) # 到圆心距离的横坐标
ly = abs(j - r) # 到圆心距离的纵坐标
l = (pow(lx, 2) + pow(ly, 2)) ** 0.5 # 三角函数 半径
if l < r3:
pimb[i - (r - r3), j - (r - r3)] = pima[i, j]
self.markImg = imb
#
def getchannel(self, itype):
self.markImg = self.markImg.getchannel(itype)
if __name__ == '__main__':
pass
-88
View File
@@ -1,88 +0,0 @@
from configs.path_config import IMAGE_PATH, VOICE_PATH
from nonebot.adapters.cqhttp.message import MessageSegment
import os
from services.log import logger
import ujson
def image(img_name: str = None, path: str = '', abspath: str = None, b64: str = None):
if abspath:
if os.path.exists(abspath):
return MessageSegment.image("file:///" + abspath)
else:
return ''
elif b64:
if b64.find('base64://') != -1:
return MessageSegment.image(b64)
else:
return MessageSegment.image('base64://' + b64)
else:
img_name = str(img_name)
if img_name.find('http') == -1:
if len(img_name.split('.')) == 1:
img_name += '.jpg'
if os.path.exists(IMAGE_PATH + path + '/' + img_name):
return MessageSegment.image("file:///" + IMAGE_PATH + path + '/' + img_name)
else:
logger.warning(f"图片 {path}/{img_name}缺失.")
return ''
else:
return MessageSegment.image(img_name)
def at(qq):
return MessageSegment.at(qq)
def record(voice_name='', path=''):
if len(voice_name.split('.')) == 1:
voice_name += '.mp3'
if path == "":
name = VOICE_PATH + "{}.".format(voice_name)
else:
name = VOICE_PATH + "{}/{}".format(path, voice_name)
if voice_name.find('http') == -1:
if os.path.exists(name):
result = MessageSegment.record("file:///" + name)
return result
else:
logger.warning(f"语音{path}/{voice_name}缺失...")
return ""
else:
return MessageSegment.record(voice_name)
def text(msg):
return MessageSegment.text(msg)
def contact_user(qq):
return MessageSegment.contact_user(qq)
def share(url, title, content='', image_url=''):
return MessageSegment.share(url, title, content, image_url)
def xml(data):
return MessageSegment.xml(data)
def json(data):
data = ujson.dumps(data)
return MessageSegment.json(data)
def face(id_):
return MessageSegment.face(id_)
def poke(qq):
return MessageSegment('poke', {"qq": qq})
def forward():
return MessageSegment.forward()
# if __name__ == '__main__':
# print(get_record_result("dadada", "", type="amr"))
+151
View File
@@ -0,0 +1,151 @@
from configs.path_config import IMAGE_PATH, VOICE_PATH
from nonebot.adapters.cqhttp.message import MessageSegment
from services.log import logger
from typing import Optional
from pathlib import Path
import os
import ujson
def image(
img_name: str = None, path: str = None, abspath: str = None, b64: str = None
) -> MessageSegment or str:
"""
说明:
生成一个 MessageSegment.image 消息
生成顺序:绝对路径(abspath) > base64(b64) > img_name
参数:
:param img_name: 图片文件名称,默认在 resource/img 目录下
:param path: 图片所在路径,默认在 resource/img 目录下
:param abspath: 图片绝对路径
:param b64: 图片base64
"""
if abspath:
return (
MessageSegment.image("file:///" + abspath)
if os.path.exists(abspath)
else ""
)
elif b64:
return MessageSegment.image(b64 if "base64://" in b64 else "base64://" + b64)
else:
if "http" in img_name:
return MessageSegment.image(img_name)
if len(img_name.split(".")) == 1:
img_name += ".jpg"
file = (
Path(IMAGE_PATH) / path / img_name if path else Path(IMAGE_PATH) / img_name
)
if file.exists():
return MessageSegment.image(f"file:///{file.absolute()}")
else:
logger.warning(f"图片 {file.absolute()}缺失...")
return ""
def at(qq: int) -> MessageSegment:
"""
说明:
生成一个 MessageSegment.at 消息
参数:
:param qq: qq号
"""
return MessageSegment.at(qq)
def record(voice_name: str, path: str = None) -> MessageSegment or str:
"""
说明:
生成一个 MessageSegment.record 消息
参数:
:param voice_name: 音频文件名称,默认在 resource/voice 目录下
:param path: 音频文件路径,默认在 resource/voice 目录下
"""
if len(voice_name.split(".")) == 1:
voice_name += ".mp3"
file = (
Path(VOICE_PATH) / path / voice_name if path else Path(VOICE_PATH) / voice_name
)
if "http" in voice_name:
return MessageSegment.record(voice_name)
if file.exists():
result = MessageSegment.record(f"file:///{file.absolute()}")
return result
else:
logger.warning(f"语音{file.absolute()}缺失...")
return ""
def text(msg: str) -> MessageSegment:
"""
说明:
生成一个 MessageSegment.text 消息
参数:
:param msg: 消息文本
"""
return MessageSegment.text(msg)
def contact_user(qq: int) -> MessageSegment:
"""
说明:
生成一个 MessageSegment.contact_user 消息
参数:
:param qq: qq号
"""
return MessageSegment.contact_user(qq)
def share(
url: str, title: str, content: str = None, image_url: str = None
) -> MessageSegment:
"""
说明:
生成一个 MessageSegment.share 消息
参数:
:param url: 自定义分享的链接
:param title: 自定义分享的包体
:param content: 自定义分享的内容
:param image_url: 自定义分享的展示图片
"""
return MessageSegment.share(url, title, content, image_url)
def xml(data: str) -> MessageSegment:
"""
说明:
生成一个 MessageSegment.xml 消息
参数:
:param data: 数据文本
"""
return MessageSegment.xml(data)
def json(data: str) -> MessageSegment:
"""
说明:
生成一个 MessageSegment.json 消息
参数:
:param data: 消息数据
"""
return MessageSegment.json(data)
def face(id_: int) -> MessageSegment:
"""
说明:
生成一个 MessageSegment.face 消息
参数:
:param id_: 表情id
"""
return MessageSegment.face(id_)
def poke(qq: int) -> MessageSegment:
"""
说明:
生成一个 MessageSegment.poke 消息
参数:
:param qq: qq号
"""
return MessageSegment("poke", {"qq": qq})
+2 -2
View File
@@ -39,9 +39,9 @@ user_agent = [
"Openwave/ UCWEB7.0.2.37/28/999",
"Mozilla/4.0 (compatible; MSIE 6.0; ) Opera/UCWEB7.0.2.37/28/999",
# iPhone 6:
"Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25"
"Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25",
]
def get_user_agent():
return {'User-Agent': random.choice(user_agent)}
return {"User-Agent": random.choice(user_agent)}
+175 -103
View File
@@ -1,104 +1,128 @@
import time
from datetime import datetime, timedelta
from collections import defaultdict
from nonebot import require
import nonebot
import json
import pytz
from configs.path_config import TXT_PATH
from configs.config import system_proxy
from configs.config import SYSTEM_PROXY
from typing import List, Union
from nonebot.adapters import Bot
import nonebot
import pytz
import pypinyin
import aiohttp
import time
try:
import ujson as json
except ModuleNotFoundError:
import json
scheduler = require('nonebot_plugin_apscheduler').scheduler
scheduler = require("nonebot_plugin_apscheduler").scheduler
# 次数检测
class CountLimiter:
def __init__(self, max):
self.count = defaultdict(int)
self.max = max
"""
次数检测工具,检测调用次数是否超过设定值
"""
def add(self, key):
def __init__(self, max_count: int):
self.count = defaultdict(int)
self.max_count = max_count
def add(self, key: Union[str, int, float]):
self.count[key] += 1
def check(self, key) -> bool:
if self.count[key] >= self.max:
def check(self, key: Union[str, int, float]) -> bool:
if self.count[key] >= self.max_count:
self.count[key] = 0
return True
return False
# 用户正在执行此命令
class UserExistLimiter:
"""
检测用户是否正在调用命令
"""
def __init__(self):
self.mbool = defaultdict(bool)
self.flag_data = defaultdict(bool)
self.time = time.time()
def set_True(self, key):
def set_True(self, key: Union[str, int, float]):
self.time = time.time()
self.mbool[key] = True
self.flag_data[key] = True
def set_False(self, key):
self.mbool[key] = False
def set_False(self, key: Union[str, int, float]):
self.flag_data[key] = False
def check(self, key):
def check(self, key: Union[str, int, float]) -> bool:
if time.time() - self.time > 30:
self.set_False(key)
return False
return self.mbool[key]
return self.flag_data[key]
# 命令cd
class FreqLimiter:
def __init__(self, default_cd_seconds):
"""
命令冷却,检测用户是否处于冷却状态
"""
def __init__(self, default_cd_seconds: int):
self.next_time = defaultdict(float)
self.default_cd = default_cd_seconds
def check(self, key) -> bool:
def check(self, key: Union[str, int, float]) -> bool:
return time.time() >= self.next_time[key]
def start_cd(self, key, cd_time=0):
self.next_time[key] = time.time() + (cd_time if cd_time > 0 else self.default_cd)
def start_cd(self, key: Union[str, int, float], cd_time: int = 0):
self.next_time[key] = time.time() + (
cd_time if cd_time > 0 else self.default_cd
)
def left_time(self, key) -> float:
def left_time(self, key: Union[str, int, float]) -> float:
return self.next_time[key] - time.time()
static_flmt = FreqLimiter(15)
# 恶意触发命令检测
class BanCheckLimiter:
"""
恶意命令触发检测
"""
def __init__(self, default_check_time: float = 5, default_count: int = 4):
self.mint = defaultdict(int)
self.mtime = defaultdict(float)
self.default_check_time = default_check_time
self.default_count = default_count
def add(self, key):
def add(self, key: Union[str, int, float]):
if self.mint[key] == 1:
self.mtime[key] = time.time()
self.mint[key] += 1
def check(self, key) -> bool:
# print(self.mint[key])
# print(time.time() - self.mtime[key])
def check(self, key: Union[str, int, float]) -> bool:
if time.time() - self.mtime[key] > self.default_check_time:
self.mtime[key] = time.time()
self.mint[key] = 0
return False
if self.mint[key] >= self.default_count and time.time() - self.mtime[key] < self.default_check_time:
if (
self.mint[key] >= self.default_count
and time.time() - self.mtime[key] < self.default_check_time
):
self.mtime[key] = time.time()
self.mint[key] = 0
return True
return False
# 每日次数
class DailyNumberLimiter:
tz = pytz.timezone('Asia/Shanghai')
"""
每日调用命令次数限制
"""
tz = pytz.timezone("Asia/Shanghai")
def __init__(self, max_num):
self.today = -1
@@ -123,7 +147,13 @@ class DailyNumberLimiter:
self.count[key] = 0
def is_number(s) -> bool:
def is_number(s: str) -> bool:
"""
说明:
检测 s 是否为数字
参数:
:param s: 文本
"""
try:
float(s)
return True
@@ -131,6 +161,7 @@ def is_number(s) -> bool:
pass
try:
import unicodedata
unicodedata.numeric(s)
return True
except (TypeError, ValueError):
@@ -138,115 +169,156 @@ def is_number(s) -> bool:
return False
# 获取bot
def get_bot():
def get_bot() -> Bot:
"""
说明:
获取 bot 对象
"""
return list(nonebot.get_bots().values())[0]
def get_message_at(data: str) -> list:
def get_message_at(data: str) -> List[int]:
"""
说明:
获取消息中所有的 at 对象的 qq
参数:
:param data: event.json()
"""
qq_list = []
data = json.loads(data)
try:
for msg in data['message']:
if msg['type'] == 'at':
qq_list.append(int(msg['data']['qq']))
return qq_list
except Exception:
return []
for msg in data["message"]:
if msg["type"] == "at":
qq_list.append(int(msg["data"]["qq"]))
return qq_list
def get_message_imgs(data: str) -> list:
def get_message_imgs(data: str) -> List[str]:
"""
说明:
获取消息中所有的 图片 的链接
参数:
:param data: event.json()
"""
img_list = []
data = json.loads(data)
try:
for msg in data['message']:
if msg['type'] == 'image':
img_list.append(msg['data']['url'])
return img_list
except Exception:
return []
for msg in data["message"]:
if msg["type"] == "image":
img_list.append(msg["data"]["url"])
return img_list
def get_message_text(data: str) -> str:
"""
说明:
获取消息中 纯文本 的信息
参数:
:param data: event.json()
"""
data = json.loads(data)
result = ''
try:
for msg in data['message']:
if msg['type'] == 'text':
result += msg['data']['text'].strip() + ' '
return result.strip()
except Exception:
return ''
result = ""
for msg in data["message"]:
if msg["type"] == "text":
result += msg["data"]["text"].strip() + " "
return result.strip()
def get_message_type(data: str) -> str:
return json.loads(data)['message_type']
def get_message_record(data: str) -> str:
def get_message_record(data: str) -> List[str]:
"""
说明:
获取消息中所有 语音 的链接
参数:
:param data: event.json()
"""
record_list = []
data = json.loads(data)
try:
for msg in data['message']:
if msg['type'] == 'record':
return msg['data']['url']
return ''
except Exception:
return ''
for msg in data["message"]:
if msg["type"] == "record":
record_list.append(msg["data"]["url"])
return record_list
def get_message_json(data: str) -> dict:
def get_message_json(data: str) -> List[dict]:
"""
说明:
获取消息中所有 json
参数:
:param data: event.json()
"""
json_list = []
data = json.loads(data)
try:
for msg in data['message']:
if msg['type'] == 'json':
return msg['data']
return {}
except Exception:
return {}
def add_to_16(value):
while len(value) % 16 != 0:
value += '\0'
return str.encode(value)
for msg in data["message"]:
if msg["type"] == "json":
json_list.append(msg["data"])
return json_list
# 获取文本加密后的cookie
def get_cookie_text(cookie_name: str) -> str:
with open(TXT_PATH + "cookie/" + cookie_name + ".txt", 'r') as f:
"""
说明:
获取 txt/cookie 目录下指定 cookie 的内容
参数:
:param cookie_name: cookie文件名称
"""
with open(TXT_PATH + "cookie/" + cookie_name + ".txt", "r") as f:
return f.read()
# 获取本地http代理
def get_local_proxy():
return system_proxy if system_proxy else None
"""
说明:
获取 config.py 中设置的代理
"""
return SYSTEM_PROXY if SYSTEM_PROXY else None
# 判断是否为中文
def is_Chinese(word):
def is_Chinese(word: str) -> bool:
"""
说明:
判断字符串是否为纯中文
参数:
:param word: 文本
"""
for ch in word:
if '\u4e00' <= ch <= '\u9fff':
return True
return False
if not "\u4e00" <= ch <= "\u9fff":
return False
return True
async def user_avatar(qq):
url = f'http://q1.qlogo.cn/g?b=qq&nk={qq}&s=160'
async def user_avatar(qq: int) -> bytes:
"""
说明:
快捷获取用户头像
参数:
:param qq: qq号
"""
url = f"http://q1.qlogo.cn/g?b=qq&nk={qq}&s=160"
async with aiohttp.ClientSession() as session:
async with session.get(url, proxy=get_local_proxy(), timeout=5) as response:
return await response.read()
async def group_avatar(group_id):
url = f'http://p.qlogo.cn/gh/{group_id}/{group_id}/640/'
async def group_avatar(group_id: int) -> bytes:
"""
说明:
快捷获取用群头像
参数:
:param group_id: 群号
"""
url = f"http://p.qlogo.cn/gh/{group_id}/{group_id}/640/"
async with aiohttp.ClientSession() as session:
async with session.get(url, proxy=get_local_proxy(), timeout=5) as response:
return await response.read()
def cn2py(word) -> str:
def cn2py(word: str) -> str:
"""
说明:
将字符串转化为拼音
参数:
:param word: 文本
"""
temp = ""
for i in pypinyin.pinyin(word, style=pypinyin.NORMAL):
temp += ''.join(i)
temp += "".join(i)
return temp