mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2025-12-15 06:12:53 +08:00
* 添加全局cache * ✨ 构建缓存,hook使用缓存 * ✨ 新增数据库Model方法监控 * ✨ 数据库添加semaphore锁 * 🩹 优化webapi返回数据 * ✨ 添加增量缓存与缓存过期 * 🎨 优化检测代码结构 * ⚡ 优化hook权限检测性能 * 🐛 添加新异常判断跳过权限检测 * ✨ 添加插件limit缓存 * 🎨 代码格式优化 * 🐛 修复代码导入 * 🐛 修复刷新时检查 * 👽 Rename exception for missing database URL in initialization * ♿ Update default database URL to SQLite in configuration * 🔧 Update tortoise-orm and aiocache dependencies restrictions; add optional redis and asyncpg support * 🐛 修复ban检测 * 🐛 修复所有插件关闭时缓存更新 * 🐛 尝试迁移至aiocache * 🐛 完善aiocache缓存 * ⚡ 代码性能优化 * 🐛 移除获取封禁缓存时的日志记录 * 🐛 修复缓存类型声明,优化封禁用户处理逻辑 * 🐛 优化LevelUser权限更新逻辑及数据库迁移 * ✨ cache支持redis连接 * 🚨 auto fix by pre-commit hooks * ⚡ :增强获取群组的安全性和准确性。同时,优化了缓存管理中的相关逻辑,确保缓存操作的一致性。 * ✨ feat(auth_limit): 将插件初始化逻辑的启动装饰器更改为优先级管理器 * 🔧 修复日志记录级别 * 🔧 更新数据库连接字符串 * 🔧 更新数据库连接字符串为内存数据库,并优化权限检查逻辑 * ✨ feat(cache): 增加缓存功能配置项,并新增数据访问层以支持缓存逻辑 * ♻️ 重构cache * ✨ feat(cache): 增强缓存管理,新增缓存字典和缓存列表功能,支持过期时间管理 * 🔧 修复Notebook类中的viewport高度设置,将其从1000调整为10 * ✨ 更新插件管理逻辑,替换缓存服务为CacheRoot并优化缓存失效处理 * ✨ 更新RegisterConfig类中的type字段 * ✨ 修复清理重复记录逻辑,确保检查记录的id属性有效性 * ⚡ 超级无敌大优化,解决延迟与卡死问题 * ✨ 更新封禁功能,增加封禁时长参数和描述,优化插件信息返回结构 * ✨ 更新zhenxun_help.py中的viewport高度,将其从453调整为10,以优化页面显示效果 * ✨ 优化插件分类逻辑,增加插件ID排序,并更新插件信息返回结构 --------- Co-authored-by: BalconyJH <balconyjh@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
297 lines
7.3 KiB
Python
297 lines
7.3 KiB
Python
from collections import defaultdict
|
||
from dataclasses import dataclass
|
||
from datetime import date, datetime
|
||
import os
|
||
from pathlib import Path
|
||
import time
|
||
from typing import Any, ClassVar
|
||
|
||
import httpx
|
||
from nonebot_plugin_uninfo import Uninfo
|
||
import pypinyin
|
||
import pytz
|
||
|
||
from zhenxun.configs.config import Config
|
||
from zhenxun.services.log import logger
|
||
|
||
|
||
@dataclass
|
||
class EntityIDs:
|
||
user_id: str
|
||
"""用户id"""
|
||
group_id: str | None
|
||
"""群组id"""
|
||
channel_id: str | None
|
||
"""频道id"""
|
||
|
||
|
||
class ResourceDirManager:
|
||
"""
|
||
临时文件管理器
|
||
"""
|
||
|
||
temp_path: ClassVar[set[Path]] = set()
|
||
|
||
@classmethod
|
||
def __tree_append(cls, path: Path, deep: int = 1, current: int = 0):
|
||
"""递归添加文件夹"""
|
||
if current >= deep and deep != -1:
|
||
return
|
||
path = path.resolve() # 标准化路径
|
||
for f in os.listdir(path):
|
||
file = (path / f).resolve() # 标准化子路径
|
||
if file.is_dir():
|
||
if file not in cls.temp_path:
|
||
cls.temp_path.add(file)
|
||
logger.debug(f"添加临时文件夹: {file}")
|
||
cls.__tree_append(file, deep, current + 1)
|
||
|
||
@classmethod
|
||
def add_temp_dir(cls, path: str | Path, tree: bool = False, deep: int = 1):
|
||
"""添加临时清理文件夹,这些文件夹会被自动清理
|
||
|
||
参数:
|
||
path: 文件夹路径
|
||
tree: 是否递归添加文件夹
|
||
deep: 深度, -1 为无限深度
|
||
"""
|
||
if isinstance(path, str):
|
||
path = Path(path)
|
||
if path not in cls.temp_path:
|
||
cls.temp_path.add(path)
|
||
logger.debug(f"添加临时文件夹: {path}")
|
||
if tree:
|
||
cls.__tree_append(path, deep)
|
||
|
||
|
||
class CountLimiter:
|
||
"""
|
||
每日调用命令次数限制
|
||
"""
|
||
|
||
tz = pytz.timezone("Asia/Shanghai")
|
||
|
||
def __init__(self, max_num):
|
||
self.today = -1
|
||
self.count = defaultdict(int)
|
||
self.max = max_num
|
||
|
||
def check(self, key) -> bool:
|
||
day = datetime.now(self.tz).day
|
||
if day != self.today:
|
||
self.today = day
|
||
self.count.clear()
|
||
return self.count[key] < self.max
|
||
|
||
def get_num(self, key):
|
||
return self.count[key]
|
||
|
||
def increase(self, key, num=1):
|
||
self.count[key] += num
|
||
|
||
def reset(self, key):
|
||
self.count[key] = 0
|
||
|
||
|
||
class UserBlockLimiter:
|
||
"""
|
||
检测用户是否正在调用命令
|
||
"""
|
||
|
||
def __init__(self):
|
||
self.flag_data = defaultdict(bool)
|
||
self.time = time.time()
|
||
|
||
def set_true(self, key: Any):
|
||
self.time = time.time()
|
||
self.flag_data[key] = True
|
||
|
||
def set_false(self, key: Any):
|
||
self.flag_data[key] = False
|
||
|
||
def check(self, key: Any) -> bool:
|
||
if time.time() - self.time > 30:
|
||
self.set_false(key)
|
||
return not self.flag_data[key]
|
||
|
||
|
||
class FreqLimiter:
|
||
"""
|
||
命令冷却,检测用户是否处于冷却状态
|
||
"""
|
||
|
||
def __init__(self, default_cd_seconds: int):
|
||
self.next_time = defaultdict(float)
|
||
self.default_cd = default_cd_seconds
|
||
|
||
def check(self, key: Any) -> bool:
|
||
return time.time() >= self.next_time[key]
|
||
|
||
def start_cd(self, key: Any, 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: Any) -> float:
|
||
return self.next_time[key] - time.time()
|
||
|
||
|
||
def cn2py(word: str) -> str:
|
||
"""将字符串转化为拼音
|
||
|
||
参数:
|
||
word: 文本
|
||
"""
|
||
return "".join("".join(i) for i in pypinyin.pinyin(word, style=pypinyin.NORMAL))
|
||
|
||
|
||
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:
|
||
logger.error("获取用户头像错误", "Util", target=uid)
|
||
return None
|
||
|
||
|
||
async def get_group_avatar(gid: int | str) -> bytes | None:
|
||
"""快捷获取用群头像
|
||
|
||
参数:
|
||
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:
|
||
logger.error("获取群头像错误", "Util", target=gid)
|
||
return None
|
||
|
||
|
||
def change_pixiv_image_links(
|
||
url: str, size: str | None = None, nginx_url: str | None = None
|
||
) -> str:
|
||
"""根据配置改变图片大小和反代链接
|
||
|
||
参数:
|
||
url: 图片原图链接
|
||
size: 模式
|
||
nginx_url: 反代
|
||
|
||
返回:
|
||
str: url
|
||
"""
|
||
if size == "master":
|
||
img_sp = url.rsplit(".", maxsplit=1)
|
||
url = img_sp[0]
|
||
img_type = img_sp[1]
|
||
url = url.replace("original", "master") + f"_master1200.{img_type}"
|
||
if not nginx_url:
|
||
nginx_url = Config.get_config("pixiv", "PIXIV_NGINX_URL")
|
||
if nginx_url:
|
||
url = (
|
||
url.replace("i.pximg.net", nginx_url)
|
||
.replace("i.pixiv.cat", nginx_url)
|
||
.replace("i.pixiv.re", nginx_url)
|
||
.replace("_webp", "")
|
||
)
|
||
return url
|
||
|
||
|
||
def change_img_md5(path_file: str | Path) -> bool:
|
||
"""改变图片MD5
|
||
|
||
参数:
|
||
path_file: 图片路径
|
||
|
||
返还:
|
||
bool: 是否修改成功
|
||
"""
|
||
try:
|
||
with open(path_file, "a") as f:
|
||
f.write(str(int(time.time() * 1000)))
|
||
return True
|
||
except Exception as e:
|
||
logger.warning(f"改变图片MD5错误 Path:{path_file}", e=e)
|
||
return False
|
||
|
||
|
||
def is_valid_date(date_text: str, separator: str = "-") -> bool:
|
||
"""日期是否合法
|
||
|
||
参数:
|
||
date_text: 日期
|
||
separator: 分隔符
|
||
|
||
返回:
|
||
bool: 日期是否合法
|
||
"""
|
||
try:
|
||
datetime.strptime(date_text, f"%Y{separator}%m{separator}%d")
|
||
return True
|
||
except ValueError:
|
||
return False
|
||
|
||
|
||
def get_entity_ids(session: Uninfo) -> EntityIDs:
|
||
"""获取用户id,群组id,频道id
|
||
|
||
参数:
|
||
session: Uninfo
|
||
|
||
返回:
|
||
EntityIDs: 用户id,群组id,频道id
|
||
"""
|
||
user_id = session.user.id
|
||
group_id = None
|
||
channel_id = None
|
||
if session.group:
|
||
if session.group.parent:
|
||
group_id = session.group.parent.id
|
||
channel_id = session.group.id
|
||
else:
|
||
group_id = session.group.id
|
||
return EntityIDs(user_id=user_id, group_id=group_id, channel_id=channel_id)
|
||
|
||
|
||
def is_number(text: str) -> bool:
|
||
"""是否为数字
|
||
|
||
参数:
|
||
text: 文本
|
||
|
||
返回:
|
||
bool: 是否为数字
|
||
"""
|
||
try:
|
||
float(text)
|
||
return True
|
||
except ValueError:
|
||
return False
|
||
|
||
|
||
class TimeUtils:
|
||
@classmethod
|
||
def get_day_start(cls, target_date: date | datetime | None = None) -> datetime:
|
||
"""获取某天的0点时间
|
||
|
||
返回:
|
||
datetime: 今天某天的0点时间
|
||
"""
|
||
if not target_date:
|
||
target_date = datetime.now()
|
||
return (
|
||
target_date.replace(hour=0, minute=0, second=0, microsecond=0)
|
||
if isinstance(target_date, datetime)
|
||
else datetime.combine(target_date, datetime.min.time())
|
||
)
|