mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-08 21:30:01 +08:00
✨ 引入缓存机制 (#1889)
* 添加全局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>
This commit is contained in:
co-authored by
BalconyJH
pre-commit-ci[bot]
parent
6283c3d13d
commit
8649aaaa54
@@ -1,597 +0,0 @@
|
||||
from typing import ClassVar
|
||||
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.adapters.onebot.v11 import PokeNotifyEvent
|
||||
from nonebot.exception import IgnoredException
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot_plugin_alconna import At, UniMsg
|
||||
from nonebot_plugin_session import EventSession
|
||||
from pydantic import BaseModel
|
||||
from tortoise.exceptions import IntegrityError
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.models.bot_console import BotConsole
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.models.level_user import LevelUser
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.plugin_limit import PluginLimit
|
||||
from zhenxun.models.sign_user import SignUser
|
||||
from zhenxun.models.user_console import UserConsole
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import (
|
||||
BlockType,
|
||||
GoldHandle,
|
||||
LimitWatchType,
|
||||
PluginLimitType,
|
||||
PluginType,
|
||||
)
|
||||
from zhenxun.utils.exception import InsufficientGold
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.utils.utils import CountLimiter, FreqLimiter, UserBlockLimiter
|
||||
|
||||
base_config = Config.get("hook")
|
||||
|
||||
|
||||
class Limit(BaseModel):
|
||||
limit: PluginLimit
|
||||
limiter: FreqLimiter | UserBlockLimiter | CountLimiter
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
class LimitManage:
|
||||
add_module: ClassVar[list] = []
|
||||
|
||||
cd_limit: ClassVar[dict[str, Limit]] = {}
|
||||
block_limit: ClassVar[dict[str, Limit]] = {}
|
||||
count_limit: ClassVar[dict[str, Limit]] = {}
|
||||
|
||||
@classmethod
|
||||
def add_limit(cls, limit: PluginLimit):
|
||||
"""添加限制
|
||||
|
||||
参数:
|
||||
limit: PluginLimit
|
||||
"""
|
||||
if limit.module not in cls.add_module:
|
||||
cls.add_module.append(limit.module)
|
||||
if limit.limit_type == PluginLimitType.BLOCK:
|
||||
cls.block_limit[limit.module] = Limit(
|
||||
limit=limit, limiter=UserBlockLimiter()
|
||||
)
|
||||
elif limit.limit_type == PluginLimitType.CD:
|
||||
cls.cd_limit[limit.module] = Limit(
|
||||
limit=limit, limiter=FreqLimiter(limit.cd)
|
||||
)
|
||||
elif limit.limit_type == PluginLimitType.COUNT:
|
||||
cls.count_limit[limit.module] = Limit(
|
||||
limit=limit, limiter=CountLimiter(limit.max_count)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def unblock(
|
||||
cls, module: str, user_id: str, group_id: str | None, channel_id: str | None
|
||||
):
|
||||
"""解除插件block
|
||||
|
||||
参数:
|
||||
module: 模块名
|
||||
user_id: 用户id
|
||||
group_id: 群组id
|
||||
channel_id: 频道id
|
||||
"""
|
||||
if limit_model := cls.block_limit.get(module):
|
||||
limit = limit_model.limit
|
||||
limiter: UserBlockLimiter = limit_model.limiter # type: ignore
|
||||
key_type = user_id
|
||||
if group_id and limit.watch_type == LimitWatchType.GROUP:
|
||||
key_type = channel_id or group_id
|
||||
logger.debug(
|
||||
f"解除对象: {key_type} 的block限制",
|
||||
"AuthChecker",
|
||||
session=user_id,
|
||||
group_id=group_id,
|
||||
)
|
||||
limiter.set_false(key_type)
|
||||
|
||||
@classmethod
|
||||
async def check(
|
||||
cls,
|
||||
module: str,
|
||||
user_id: str,
|
||||
group_id: str | None,
|
||||
channel_id: str | None,
|
||||
session: EventSession,
|
||||
):
|
||||
"""检测限制
|
||||
|
||||
参数:
|
||||
module: 模块名
|
||||
user_id: 用户id
|
||||
group_id: 群组id
|
||||
channel_id: 频道id
|
||||
session: Session
|
||||
|
||||
异常:
|
||||
IgnoredException: IgnoredException
|
||||
"""
|
||||
if limit_model := cls.cd_limit.get(module):
|
||||
await cls.__check(limit_model, user_id, group_id, channel_id, session)
|
||||
if limit_model := cls.block_limit.get(module):
|
||||
await cls.__check(limit_model, user_id, group_id, channel_id, session)
|
||||
if limit_model := cls.count_limit.get(module):
|
||||
await cls.__check(limit_model, user_id, group_id, channel_id, session)
|
||||
|
||||
@classmethod
|
||||
async def __check(
|
||||
cls,
|
||||
limit_model: Limit | None,
|
||||
user_id: str,
|
||||
group_id: str | None,
|
||||
channel_id: str | None,
|
||||
session: EventSession,
|
||||
):
|
||||
"""检测限制
|
||||
|
||||
参数:
|
||||
limit_model: Limit
|
||||
user_id: 用户id
|
||||
group_id: 群组id
|
||||
channel_id: 频道id
|
||||
session: Session
|
||||
|
||||
异常:
|
||||
IgnoredException: IgnoredException
|
||||
"""
|
||||
if not limit_model:
|
||||
return
|
||||
limit = limit_model.limit
|
||||
limiter = limit_model.limiter
|
||||
is_limit = (
|
||||
LimitWatchType.ALL
|
||||
or (group_id and limit.watch_type == LimitWatchType.GROUP)
|
||||
or (not group_id and limit.watch_type == LimitWatchType.USER)
|
||||
)
|
||||
key_type = user_id
|
||||
if group_id and limit.watch_type == LimitWatchType.GROUP:
|
||||
key_type = channel_id or group_id
|
||||
if is_limit and not limiter.check(key_type):
|
||||
if limit.result:
|
||||
await MessageUtils.build_message(limit.result).send()
|
||||
logger.debug(
|
||||
f"{limit.module}({limit.limit_type}) 正在限制中...",
|
||||
"AuthChecker",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException(f"{limit.module} 正在限制中...")
|
||||
else:
|
||||
logger.debug(
|
||||
f"开始进行限制 {limit.module}({limit.limit_type})...",
|
||||
"AuthChecker",
|
||||
session=user_id,
|
||||
group_id=group_id,
|
||||
)
|
||||
if isinstance(limiter, FreqLimiter):
|
||||
limiter.start_cd(key_type)
|
||||
if isinstance(limiter, UserBlockLimiter):
|
||||
limiter.set_true(key_type)
|
||||
if isinstance(limiter, CountLimiter):
|
||||
limiter.increase(key_type)
|
||||
|
||||
|
||||
class IsSuperuserException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AuthChecker:
|
||||
"""
|
||||
权限检查
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
check_notice_info_cd = Config.get_config("hook", "CHECK_NOTICE_INFO_CD")
|
||||
if check_notice_info_cd is None or check_notice_info_cd < 0:
|
||||
raise ValueError("模块: [hook], 配置项: [CHECK_NOTICE_INFO_CD] 为空或小于0")
|
||||
self._flmt = FreqLimiter(check_notice_info_cd)
|
||||
self._flmt_g = FreqLimiter(check_notice_info_cd)
|
||||
self._flmt_s = FreqLimiter(check_notice_info_cd)
|
||||
self._flmt_c = FreqLimiter(check_notice_info_cd)
|
||||
|
||||
def is_send_limit_message(self, plugin: PluginInfo, sid: str) -> bool:
|
||||
"""是否发送提示消息
|
||||
|
||||
参数:
|
||||
plugin: PluginInfo
|
||||
|
||||
返回:
|
||||
bool: 是否发送提示消息
|
||||
"""
|
||||
if not base_config.get("IS_SEND_TIP_MESSAGE"):
|
||||
return False
|
||||
if plugin.plugin_type == PluginType.DEPENDANT:
|
||||
return False
|
||||
if plugin.ignore_prompt:
|
||||
return False
|
||||
return self._flmt_s.check(sid)
|
||||
|
||||
async def auth(
|
||||
self,
|
||||
matcher: Matcher,
|
||||
event: Event,
|
||||
bot: Bot,
|
||||
session: EventSession,
|
||||
message: UniMsg,
|
||||
):
|
||||
"""权限检查
|
||||
|
||||
参数:
|
||||
matcher: matcher
|
||||
bot: bot
|
||||
session: EventSession
|
||||
message: UniMsg
|
||||
"""
|
||||
is_ignore = False
|
||||
cost_gold = 0
|
||||
user_id = session.id1
|
||||
group_id = session.id3
|
||||
channel_id = session.id2
|
||||
if not group_id:
|
||||
group_id = channel_id
|
||||
channel_id = None
|
||||
if matcher.type == "notice" and not isinstance(event, PokeNotifyEvent):
|
||||
"""过滤除poke外的notice"""
|
||||
return
|
||||
if user_id and matcher.plugin and (module_path := matcher.plugin.module_name):
|
||||
try:
|
||||
user = await UserConsole.get_user(user_id, session.platform)
|
||||
except IntegrityError as e:
|
||||
logger.debug(
|
||||
"重复创建用户,已跳过该次权限...",
|
||||
"AuthChecker",
|
||||
session=session,
|
||||
e=e,
|
||||
)
|
||||
return
|
||||
if plugin := await PluginInfo.get_or_none(module_path=module_path):
|
||||
if plugin.plugin_type == PluginType.HIDDEN:
|
||||
logger.debug(
|
||||
f"插件: {plugin.name}:{plugin.module} "
|
||||
"为HIDDEN,已跳过权限检查..."
|
||||
)
|
||||
return
|
||||
try:
|
||||
cost_gold = await self.auth_cost(user, plugin, session)
|
||||
if session.id1 in bot.config.superusers:
|
||||
if plugin.plugin_type == PluginType.SUPERUSER:
|
||||
raise IsSuperuserException()
|
||||
if not plugin.limit_superuser:
|
||||
cost_gold = 0
|
||||
raise IsSuperuserException()
|
||||
await self.auth_bot(plugin, bot.self_id)
|
||||
await self.auth_group(plugin, session, message)
|
||||
await self.auth_admin(plugin, session)
|
||||
await self.auth_plugin(plugin, session, event)
|
||||
await self.auth_limit(plugin, session)
|
||||
except IsSuperuserException:
|
||||
logger.debug(
|
||||
"超级用户或被ban跳过权限检测...", "AuthChecker", session=session
|
||||
)
|
||||
except IgnoredException:
|
||||
is_ignore = True
|
||||
LimitManage.unblock(
|
||||
matcher.plugin.name, user_id, group_id, channel_id
|
||||
)
|
||||
except AssertionError as e:
|
||||
is_ignore = True
|
||||
logger.debug("消息无法发送", session=session, e=e)
|
||||
if cost_gold and user_id:
|
||||
"""花费金币"""
|
||||
try:
|
||||
await UserConsole.reduce_gold(
|
||||
user_id,
|
||||
cost_gold,
|
||||
GoldHandle.PLUGIN,
|
||||
matcher.plugin.name if matcher.plugin else "",
|
||||
session.platform,
|
||||
)
|
||||
except InsufficientGold:
|
||||
if u := await UserConsole.get_user(user_id):
|
||||
u.gold = 0
|
||||
await u.save(update_fields=["gold"])
|
||||
logger.debug(
|
||||
f"调用功能花费金币: {cost_gold}", "AuthChecker", session=session
|
||||
)
|
||||
if is_ignore:
|
||||
raise IgnoredException("权限检测 ignore")
|
||||
|
||||
async def auth_bot(self, plugin: PluginInfo, bot_id: str):
|
||||
"""机器人权限
|
||||
|
||||
参数:
|
||||
plugin: PluginInfo
|
||||
bot_id: bot_id
|
||||
"""
|
||||
if not await BotConsole.get_bot_status(bot_id):
|
||||
logger.debug("Bot休眠中阻断权限检测...", "AuthChecker")
|
||||
raise IgnoredException("BotConsole休眠权限检测 ignore")
|
||||
if await BotConsole.is_block_plugin(bot_id, plugin.module):
|
||||
logger.debug(
|
||||
f"Bot插件 {plugin.name}({plugin.module}) 权限检查结果为关闭...",
|
||||
"AuthChecker",
|
||||
)
|
||||
raise IgnoredException("BotConsole插件权限检测 ignore")
|
||||
|
||||
async def auth_limit(self, plugin: PluginInfo, session: EventSession):
|
||||
"""插件限制
|
||||
|
||||
参数:
|
||||
plugin: PluginInfo
|
||||
session: EventSession
|
||||
"""
|
||||
user_id = session.id1
|
||||
group_id = session.id3
|
||||
channel_id = session.id2
|
||||
if not group_id:
|
||||
group_id = channel_id
|
||||
channel_id = None
|
||||
if plugin.module not in LimitManage.add_module:
|
||||
limit_list: list[PluginLimit] = await plugin.plugin_limit.filter(
|
||||
status=True
|
||||
).all() # type: ignore
|
||||
for limit in limit_list:
|
||||
LimitManage.add_limit(limit)
|
||||
if user_id:
|
||||
await LimitManage.check(
|
||||
plugin.module, user_id, group_id, channel_id, session
|
||||
)
|
||||
|
||||
async def auth_plugin(
|
||||
self, plugin: PluginInfo, session: EventSession, event: Event
|
||||
):
|
||||
"""插件状态
|
||||
|
||||
参数:
|
||||
plugin: PluginInfo
|
||||
session: EventSession
|
||||
"""
|
||||
group_id = session.id3
|
||||
channel_id = session.id2
|
||||
if not group_id:
|
||||
group_id = channel_id
|
||||
channel_id = None
|
||||
if user_id := session.id1:
|
||||
if plugin.impression > 0:
|
||||
sign_user = await SignUser.get_user(user_id)
|
||||
if float(sign_user.impression) < plugin.impression:
|
||||
if self.is_send_limit_message(plugin, user_id):
|
||||
self._flmt_s.start_cd(user_id)
|
||||
await MessageUtils.build_message(
|
||||
f"好感度不足哦,当前功能需要好感度: {plugin.impression},"
|
||||
"请继续签到提升好感度吧!"
|
||||
).send(reply_to=True)
|
||||
logger.debug(
|
||||
f"{plugin.name}({plugin.module}) 用户好感度不足...",
|
||||
"AuthChecker",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException("好感度不足...")
|
||||
if group_id:
|
||||
sid = group_id or user_id
|
||||
if await GroupConsole.is_superuser_block_plugin(
|
||||
group_id, plugin.module
|
||||
):
|
||||
"""超级用户群组插件状态"""
|
||||
if self.is_send_limit_message(plugin, sid):
|
||||
self._flmt_s.start_cd(group_id or user_id)
|
||||
await MessageUtils.build_message(
|
||||
"超级管理员禁用了该群此功能..."
|
||||
).send(reply_to=True)
|
||||
logger.debug(
|
||||
f"{plugin.name}({plugin.module}) 超级管理员禁用了该群此功能...",
|
||||
"AuthChecker",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException("超级管理员禁用了该群此功能...")
|
||||
if await GroupConsole.is_normal_block_plugin(group_id, plugin.module):
|
||||
"""群组插件状态"""
|
||||
if self.is_send_limit_message(plugin, sid):
|
||||
self._flmt_s.start_cd(group_id or user_id)
|
||||
await MessageUtils.build_message("该群未开启此功能...").send(
|
||||
reply_to=True
|
||||
)
|
||||
logger.debug(
|
||||
f"{plugin.name}({plugin.module}) 未开启此功能...",
|
||||
"AuthChecker",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException("该群未开启此功能...")
|
||||
if plugin.block_type == BlockType.GROUP:
|
||||
"""全局群组禁用"""
|
||||
try:
|
||||
if self.is_send_limit_message(plugin, sid):
|
||||
self._flmt_c.start_cd(group_id)
|
||||
await MessageUtils.build_message(
|
||||
"该功能在群组中已被禁用..."
|
||||
).send(reply_to=True)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"auth_plugin 发送消息失败",
|
||||
"AuthChecker",
|
||||
session=session,
|
||||
e=e,
|
||||
)
|
||||
logger.debug(
|
||||
f"{plugin.name}({plugin.module}) 该插件在群组中已被禁用...",
|
||||
"AuthChecker",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException("该插件在群组中已被禁用...")
|
||||
else:
|
||||
sid = user_id
|
||||
if plugin.block_type == BlockType.PRIVATE:
|
||||
"""全局私聊禁用"""
|
||||
try:
|
||||
if self.is_send_limit_message(plugin, sid):
|
||||
self._flmt_c.start_cd(user_id)
|
||||
await MessageUtils.build_message(
|
||||
"该功能在私聊中已被禁用..."
|
||||
).send()
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"auth_admin 发送消息失败",
|
||||
"AuthChecker",
|
||||
session=session,
|
||||
e=e,
|
||||
)
|
||||
logger.debug(
|
||||
f"{plugin.name}({plugin.module}) 该插件在私聊中已被禁用...",
|
||||
"AuthChecker",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException("该插件在私聊中已被禁用...")
|
||||
if not plugin.status and plugin.block_type == BlockType.ALL:
|
||||
"""全局状态"""
|
||||
if group_id and await GroupConsole.is_super_group(group_id):
|
||||
raise IsSuperuserException()
|
||||
logger.debug(
|
||||
f"{plugin.name}({plugin.module}) 全局未开启此功能...",
|
||||
"AuthChecker",
|
||||
session=session,
|
||||
)
|
||||
if self.is_send_limit_message(plugin, sid):
|
||||
self._flmt_s.start_cd(group_id or user_id)
|
||||
await MessageUtils.build_message("全局未开启此功能...").send()
|
||||
raise IgnoredException("全局未开启此功能...")
|
||||
|
||||
async def auth_admin(self, plugin: PluginInfo, session: EventSession):
|
||||
"""管理员命令 个人权限
|
||||
|
||||
参数:
|
||||
plugin: PluginInfo
|
||||
session: EventSession
|
||||
"""
|
||||
user_id = session.id1
|
||||
if user_id and plugin.admin_level:
|
||||
if group_id := session.id3 or session.id2:
|
||||
if not await LevelUser.check_level(
|
||||
user_id, group_id, plugin.admin_level
|
||||
):
|
||||
try:
|
||||
if self._flmt.check(user_id):
|
||||
self._flmt.start_cd(user_id)
|
||||
await MessageUtils.build_message(
|
||||
[
|
||||
At(flag="user", target=user_id),
|
||||
f"你的权限不足喔,"
|
||||
f"该功能需要的权限等级: {plugin.admin_level}",
|
||||
]
|
||||
).send(reply_to=True)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"auth_admin 发送消息失败",
|
||||
"AuthChecker",
|
||||
session=session,
|
||||
e=e,
|
||||
)
|
||||
logger.debug(
|
||||
f"{plugin.name}({plugin.module}) 管理员权限不足...",
|
||||
"AuthChecker",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException("管理员权限不足...")
|
||||
elif not await LevelUser.check_level(user_id, None, plugin.admin_level):
|
||||
try:
|
||||
await MessageUtils.build_message(
|
||||
f"你的权限不足喔,该功能需要的权限等级: {plugin.admin_level}"
|
||||
).send()
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"auth_admin 发送消息失败", "AuthChecker", session=session, e=e
|
||||
)
|
||||
logger.debug(
|
||||
f"{plugin.name}({plugin.module}) 管理员权限不足...",
|
||||
"AuthChecker",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException("权限不足")
|
||||
|
||||
async def auth_group(
|
||||
self, plugin: PluginInfo, session: EventSession, message: UniMsg
|
||||
):
|
||||
"""群黑名单检测 群总开关检测
|
||||
|
||||
参数:
|
||||
plugin: PluginInfo
|
||||
session: EventSession
|
||||
message: UniMsg
|
||||
"""
|
||||
if not (group_id := session.id3 or session.id2):
|
||||
return
|
||||
text = message.extract_plain_text()
|
||||
group = await GroupConsole.get_group(group_id)
|
||||
if not group:
|
||||
"""群不存在"""
|
||||
logger.debug(
|
||||
"群组信息不存在...",
|
||||
"AuthChecker",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException("群不存在")
|
||||
if group.level < 0:
|
||||
"""群权限小于0"""
|
||||
logger.debug(
|
||||
"群黑名单, 群权限-1...",
|
||||
"AuthChecker",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException("群黑名单")
|
||||
if not group.status:
|
||||
"""群休眠"""
|
||||
if text.strip() != "醒来":
|
||||
logger.debug("群休眠状态...", "AuthChecker", session=session)
|
||||
raise IgnoredException("群休眠状态")
|
||||
if plugin.level > group.level:
|
||||
"""插件等级大于群等级"""
|
||||
logger.debug(
|
||||
f"{plugin.name}({plugin.module}) 群等级限制.."
|
||||
f"该功能需要的群等级: {plugin.level}..",
|
||||
"AuthChecker",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException(f"{plugin.name}({plugin.module}) 群等级限制...")
|
||||
|
||||
async def auth_cost(
|
||||
self, user: UserConsole, plugin: PluginInfo, session: EventSession
|
||||
) -> int:
|
||||
"""检测是否满足金币条件
|
||||
|
||||
参数:
|
||||
user: UserConsole
|
||||
plugin: PluginInfo
|
||||
session: EventSession
|
||||
|
||||
返回:
|
||||
int: 需要消耗的金币
|
||||
"""
|
||||
if user.gold < plugin.cost_gold:
|
||||
"""插件消耗金币不足"""
|
||||
try:
|
||||
await MessageUtils.build_message(
|
||||
f"金币不足..该功能需要{plugin.cost_gold}金币.."
|
||||
).send()
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"auth_cost 发送消息失败", "AuthChecker", session=session, e=e
|
||||
)
|
||||
logger.debug(
|
||||
f"{plugin.name}({plugin.module}) 金币限制.."
|
||||
f"该功能需要{plugin.cost_gold}金币..",
|
||||
"AuthChecker",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException(f"{plugin.name}({plugin.module}) 金币限制...")
|
||||
return plugin.cost_gold
|
||||
|
||||
|
||||
checker = AuthChecker()
|
||||
@@ -0,0 +1,99 @@
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from nonebot_plugin_alconna import At
|
||||
from nonebot_plugin_uninfo import Uninfo
|
||||
|
||||
from zhenxun.models.level_user import LevelUser
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.services.data_access import DataAccess
|
||||
from zhenxun.services.db_context import DB_TIMEOUT_SECONDS
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.utils import get_entity_ids
|
||||
|
||||
from .config import LOGGER_COMMAND, WARNING_THRESHOLD
|
||||
from .exception import SkipPluginException
|
||||
from .utils import send_message
|
||||
|
||||
|
||||
async def auth_admin(plugin: PluginInfo, session: Uninfo):
|
||||
"""管理员命令 个人权限
|
||||
|
||||
参数:
|
||||
plugin: PluginInfo
|
||||
session: Uninfo
|
||||
"""
|
||||
start_time = time.time()
|
||||
|
||||
if not plugin.admin_level:
|
||||
return
|
||||
|
||||
try:
|
||||
entity = get_entity_ids(session)
|
||||
level_dao = DataAccess(LevelUser)
|
||||
|
||||
# 并行查询用户权限数据
|
||||
global_user: LevelUser | None = None
|
||||
group_users: LevelUser | None = None
|
||||
|
||||
# 查询全局权限
|
||||
global_user_task = level_dao.safe_get_or_none(
|
||||
user_id=session.user.id, group_id__isnull=True
|
||||
)
|
||||
|
||||
# 如果在群组中,查询群组权限
|
||||
group_users_task = None
|
||||
if entity.group_id:
|
||||
group_users_task = level_dao.safe_get_or_none(
|
||||
user_id=session.user.id, group_id=entity.group_id
|
||||
)
|
||||
|
||||
# 等待查询完成,添加超时控制
|
||||
try:
|
||||
results = await asyncio.wait_for(
|
||||
asyncio.gather(global_user_task, group_users_task or asyncio.sleep(0)),
|
||||
timeout=DB_TIMEOUT_SECONDS,
|
||||
)
|
||||
global_user = results[0]
|
||||
group_users = results[1] if group_users_task else None
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"查询用户权限超时: user_id={session.user.id}", LOGGER_COMMAND)
|
||||
# 超时时不阻塞,继续执行
|
||||
return
|
||||
|
||||
user_level = global_user.user_level if global_user else 0
|
||||
if entity.group_id and group_users:
|
||||
user_level = max(user_level, group_users.user_level)
|
||||
|
||||
if user_level < plugin.admin_level:
|
||||
await send_message(
|
||||
session,
|
||||
[
|
||||
At(flag="user", target=session.user.id),
|
||||
f"你的权限不足喔,该功能需要的权限等级: {plugin.admin_level}",
|
||||
],
|
||||
entity.user_id,
|
||||
)
|
||||
|
||||
raise SkipPluginException(
|
||||
f"{plugin.name}({plugin.module}) 管理员权限不足..."
|
||||
)
|
||||
elif global_user:
|
||||
if global_user.user_level < plugin.admin_level:
|
||||
await send_message(
|
||||
session,
|
||||
f"你的权限不足喔,该功能需要的权限等级: {plugin.admin_level}",
|
||||
)
|
||||
|
||||
raise SkipPluginException(
|
||||
f"{plugin.name}({plugin.module}) 管理员权限不足..."
|
||||
)
|
||||
finally:
|
||||
# 记录执行时间
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed > WARNING_THRESHOLD: # 记录耗时超过500ms的检查
|
||||
logger.warning(
|
||||
f"auth_admin 耗时: {elapsed:.3f}s, plugin={plugin.module}",
|
||||
LOGGER_COMMAND,
|
||||
session=session,
|
||||
)
|
||||
@@ -0,0 +1,303 @@
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot_plugin_alconna import At
|
||||
from nonebot_plugin_uninfo import Uninfo
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.models.ban_console import BanConsole
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.services.data_access import DataAccess
|
||||
from zhenxun.services.db_context import DB_TIMEOUT_SECONDS
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.utils import EntityIDs, get_entity_ids
|
||||
|
||||
from .config import LOGGER_COMMAND, WARNING_THRESHOLD
|
||||
from .exception import SkipPluginException
|
||||
from .utils import freq, send_message
|
||||
|
||||
Config.add_plugin_config(
|
||||
"hook",
|
||||
"BAN_RESULT",
|
||||
"才不会给你发消息.",
|
||||
help="对被ban用户发送的消息",
|
||||
)
|
||||
|
||||
|
||||
def calculate_ban_time(ban_record: BanConsole | None) -> int:
|
||||
"""根据ban记录计算剩余ban时间
|
||||
|
||||
参数:
|
||||
ban_record: BanConsole记录
|
||||
|
||||
返回:
|
||||
int: ban剩余时长,-1时为永久ban,0表示未被ban
|
||||
"""
|
||||
if not ban_record:
|
||||
return 0
|
||||
|
||||
if ban_record.duration == -1:
|
||||
return -1
|
||||
|
||||
_time = time.time() - (ban_record.ban_time + ban_record.duration)
|
||||
return 0 if _time > 0 else int(abs(_time))
|
||||
|
||||
|
||||
async def is_ban(user_id: str | None, group_id: str | None) -> int:
|
||||
"""检查用户或群组是否被ban
|
||||
|
||||
参数:
|
||||
user_id: 用户ID
|
||||
group_id: 群组ID
|
||||
|
||||
返回:
|
||||
int: ban的剩余时间,0表示未被ban
|
||||
"""
|
||||
if not user_id and not group_id:
|
||||
return 0
|
||||
|
||||
start_time = time.time()
|
||||
ban_dao = DataAccess(BanConsole)
|
||||
|
||||
# 分别获取用户在群组中的ban记录和全局ban记录
|
||||
group_user = None
|
||||
user = None
|
||||
|
||||
try:
|
||||
# 并行查询用户和群组的 ban 记录
|
||||
tasks = []
|
||||
if user_id and group_id:
|
||||
tasks.append(ban_dao.safe_get_or_none(user_id=user_id, group_id=group_id))
|
||||
if user_id:
|
||||
tasks.append(
|
||||
ban_dao.safe_get_or_none(user_id=user_id, group_id__isnull=True)
|
||||
)
|
||||
|
||||
# 等待所有查询完成,添加超时控制
|
||||
if tasks:
|
||||
try:
|
||||
ban_records = await asyncio.wait_for(
|
||||
asyncio.gather(*tasks), timeout=DB_TIMEOUT_SECONDS
|
||||
)
|
||||
if len(tasks) == 2:
|
||||
group_user, user = ban_records
|
||||
elif user_id and group_id:
|
||||
group_user = ban_records[0]
|
||||
else:
|
||||
user = ban_records[0]
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(
|
||||
f"查询ban记录超时: user_id={user_id}, group_id={group_id}",
|
||||
LOGGER_COMMAND,
|
||||
)
|
||||
# 超时时返回0,避免阻塞
|
||||
return 0
|
||||
|
||||
# 检查记录并计算ban时间
|
||||
results = []
|
||||
if group_user:
|
||||
results.append(group_user)
|
||||
if user:
|
||||
results.append(user)
|
||||
|
||||
# 如果没有找到记录,返回0
|
||||
if not results:
|
||||
return 0
|
||||
|
||||
logger.debug(f"查询到的ban记录: {results}", LOGGER_COMMAND)
|
||||
# 检查所有记录,找出最严格的ban(时间最长的)
|
||||
max_ban_time: int = 0
|
||||
for result in results:
|
||||
if result.duration > 0 or result.duration == -1:
|
||||
# 直接计算ban时间,避免再次查询数据库
|
||||
ban_time = calculate_ban_time(result)
|
||||
if ban_time == -1 or ban_time > max_ban_time:
|
||||
max_ban_time = ban_time
|
||||
|
||||
return max_ban_time
|
||||
finally:
|
||||
# 记录执行时间
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed > WARNING_THRESHOLD: # 记录耗时超过500ms的检查
|
||||
logger.warning(
|
||||
f"is_ban 耗时: {elapsed:.3f}s",
|
||||
LOGGER_COMMAND,
|
||||
session=user_id,
|
||||
group_id=group_id,
|
||||
)
|
||||
|
||||
|
||||
def check_plugin_type(matcher: Matcher) -> bool:
|
||||
"""判断插件类型是否是隐藏插件
|
||||
|
||||
参数:
|
||||
matcher: Matcher
|
||||
|
||||
返回:
|
||||
bool: 是否为隐藏插件
|
||||
"""
|
||||
if plugin := matcher.plugin:
|
||||
if metadata := plugin.metadata:
|
||||
extra = metadata.extra
|
||||
if extra.get("plugin_type") in [PluginType.HIDDEN]:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def format_time(time_val: float) -> str:
|
||||
"""格式化时间
|
||||
|
||||
参数:
|
||||
time_val: ban时长
|
||||
|
||||
返回:
|
||||
str: 格式化时间文本
|
||||
"""
|
||||
if time_val == -1:
|
||||
return "∞"
|
||||
time_val = abs(int(time_val))
|
||||
if time_val < 60:
|
||||
time_str = f"{time_val!s} 秒"
|
||||
else:
|
||||
minute = int(time_val / 60)
|
||||
if minute > 60:
|
||||
hours = minute // 60
|
||||
minute %= 60
|
||||
time_str = f"{hours} 小时 {minute}分钟"
|
||||
else:
|
||||
time_str = f"{minute} 分钟"
|
||||
return time_str
|
||||
|
||||
|
||||
async def group_handle(group_id: str) -> None:
|
||||
"""群组ban检查
|
||||
|
||||
参数:
|
||||
group_id: 群组id
|
||||
|
||||
异常:
|
||||
SkipPluginException: 群组处于黑名单
|
||||
"""
|
||||
start_time = time.time()
|
||||
try:
|
||||
if await is_ban(None, group_id):
|
||||
raise SkipPluginException("群组处于黑名单中...")
|
||||
finally:
|
||||
# 记录执行时间
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed > WARNING_THRESHOLD: # 记录耗时超过500ms的检查
|
||||
logger.warning(
|
||||
f"group_handle 耗时: {elapsed:.3f}s",
|
||||
LOGGER_COMMAND,
|
||||
group_id=group_id,
|
||||
)
|
||||
|
||||
|
||||
async def user_handle(module: str, entity: EntityIDs, session: Uninfo) -> None:
|
||||
"""用户ban检查
|
||||
|
||||
参数:
|
||||
module: 插件模块名
|
||||
entity: 实体ID信息
|
||||
session: Uninfo
|
||||
|
||||
异常:
|
||||
SkipPluginException: 用户处于黑名单
|
||||
"""
|
||||
start_time = time.time()
|
||||
try:
|
||||
ban_result = Config.get_config("hook", "BAN_RESULT")
|
||||
time_val = await is_ban(entity.user_id, entity.group_id)
|
||||
if not time_val:
|
||||
return
|
||||
time_str = format_time(time_val)
|
||||
plugin_dao = DataAccess(PluginInfo)
|
||||
try:
|
||||
db_plugin = await asyncio.wait_for(
|
||||
plugin_dao.safe_get_or_none(module=module), timeout=DB_TIMEOUT_SECONDS
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"查询插件信息超时: {module}", LOGGER_COMMAND)
|
||||
# 超时时不阻塞,继续执行
|
||||
raise SkipPluginException("用户处于黑名单中...")
|
||||
|
||||
if (
|
||||
db_plugin
|
||||
and not db_plugin.ignore_prompt
|
||||
and time_val != -1
|
||||
and ban_result
|
||||
and freq.is_send_limit_message(db_plugin, entity.user_id, False)
|
||||
):
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
send_message(
|
||||
session,
|
||||
[
|
||||
At(flag="user", target=entity.user_id),
|
||||
f"{ban_result}\n在..在 {time_str} 后才会理你喔",
|
||||
],
|
||||
entity.user_id,
|
||||
),
|
||||
timeout=DB_TIMEOUT_SECONDS,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"发送消息超时: {entity.user_id}", LOGGER_COMMAND)
|
||||
raise SkipPluginException("用户处于黑名单中...")
|
||||
finally:
|
||||
# 记录执行时间
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed > WARNING_THRESHOLD: # 记录耗时超过500ms的检查
|
||||
logger.warning(
|
||||
f"user_handle 耗时: {elapsed:.3f}s",
|
||||
LOGGER_COMMAND,
|
||||
session=session,
|
||||
)
|
||||
|
||||
|
||||
async def auth_ban(matcher: Matcher, bot: Bot, session: Uninfo) -> None:
|
||||
"""权限检查 - ban 检查
|
||||
|
||||
参数:
|
||||
matcher: Matcher
|
||||
bot: Bot
|
||||
session: Uninfo
|
||||
"""
|
||||
start_time = time.time()
|
||||
try:
|
||||
if not check_plugin_type(matcher):
|
||||
return
|
||||
if not matcher.plugin_name:
|
||||
return
|
||||
entity = get_entity_ids(session)
|
||||
if entity.user_id in bot.config.superusers:
|
||||
return
|
||||
if entity.group_id:
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
group_handle(entity.group_id), timeout=DB_TIMEOUT_SECONDS
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"群组ban检查超时: {entity.group_id}", LOGGER_COMMAND)
|
||||
# 超时时不阻塞,继续执行
|
||||
|
||||
if entity.user_id:
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
user_handle(matcher.plugin_name, entity, session),
|
||||
timeout=DB_TIMEOUT_SECONDS,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"用户ban检查超时: {entity.user_id}", LOGGER_COMMAND)
|
||||
# 超时时不阻塞,继续执行
|
||||
finally:
|
||||
# 记录总执行时间
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed > WARNING_THRESHOLD: # 记录耗时超过500ms的检查
|
||||
logger.warning(
|
||||
f"auth_ban 总耗时: {elapsed:.3f}s, plugin={matcher.plugin_name}",
|
||||
LOGGER_COMMAND,
|
||||
session=session,
|
||||
)
|
||||
@@ -0,0 +1,55 @@
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from zhenxun.models.bot_console import BotConsole
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.services.data_access import DataAccess
|
||||
from zhenxun.services.db_context import DB_TIMEOUT_SECONDS
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.common_utils import CommonUtils
|
||||
|
||||
from .config import LOGGER_COMMAND, WARNING_THRESHOLD
|
||||
from .exception import SkipPluginException
|
||||
|
||||
|
||||
async def auth_bot(plugin: PluginInfo, bot_id: str):
|
||||
"""bot层面的权限检查
|
||||
|
||||
参数:
|
||||
plugin: PluginInfo
|
||||
bot_id: bot id
|
||||
|
||||
异常:
|
||||
SkipPluginException: 忽略插件
|
||||
SkipPluginException: 忽略插件
|
||||
"""
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
# 从数据库或缓存中获取 bot 信息
|
||||
bot_dao = DataAccess(BotConsole)
|
||||
|
||||
try:
|
||||
bot: BotConsole | None = await asyncio.wait_for(
|
||||
bot_dao.safe_get_or_none(bot_id=bot_id), timeout=DB_TIMEOUT_SECONDS
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"查询Bot信息超时: bot_id={bot_id}", LOGGER_COMMAND)
|
||||
# 超时时不阻塞,继续执行
|
||||
return
|
||||
|
||||
if not bot or not bot.status:
|
||||
raise SkipPluginException("Bot不存在或休眠中阻断权限检测...")
|
||||
if CommonUtils.format(plugin.module) in bot.block_plugins:
|
||||
raise SkipPluginException(
|
||||
f"Bot插件 {plugin.name}({plugin.module}) 权限检查结果为关闭..."
|
||||
)
|
||||
finally:
|
||||
# 记录执行时间
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed > WARNING_THRESHOLD: # 记录耗时超过500ms的检查
|
||||
logger.warning(
|
||||
f"auth_bot 耗时: {elapsed:.3f}s, "
|
||||
f"bot_id={bot_id}, plugin={plugin.module}",
|
||||
LOGGER_COMMAND,
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
import time
|
||||
|
||||
from nonebot_plugin_uninfo import Uninfo
|
||||
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.user_console import UserConsole
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from .config import LOGGER_COMMAND, WARNING_THRESHOLD
|
||||
from .exception import SkipPluginException
|
||||
from .utils import send_message
|
||||
|
||||
|
||||
async def auth_cost(user: UserConsole, plugin: PluginInfo, session: Uninfo) -> int:
|
||||
"""检测是否满足金币条件
|
||||
|
||||
参数:
|
||||
user: UserConsole
|
||||
plugin: PluginInfo
|
||||
session: Uninfo
|
||||
|
||||
返回:
|
||||
int: 需要消耗的金币
|
||||
"""
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
if user.gold < plugin.cost_gold:
|
||||
"""插件消耗金币不足"""
|
||||
await send_message(session, f"金币不足..该功能需要{plugin.cost_gold}金币..")
|
||||
raise SkipPluginException(f"{plugin.name}({plugin.module}) 金币限制...")
|
||||
return plugin.cost_gold
|
||||
finally:
|
||||
# 记录执行时间
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed > WARNING_THRESHOLD: # 记录耗时超过500ms的检查
|
||||
logger.warning(
|
||||
f"auth_cost 耗时: {elapsed:.3f}s, plugin={plugin.module}",
|
||||
LOGGER_COMMAND,
|
||||
session=session,
|
||||
)
|
||||
@@ -0,0 +1,68 @@
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from nonebot_plugin_alconna import UniMsg
|
||||
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.services.data_access import DataAccess
|
||||
from zhenxun.services.db_context import DB_TIMEOUT_SECONDS
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.utils import EntityIDs
|
||||
|
||||
from .config import LOGGER_COMMAND, WARNING_THRESHOLD, SwitchEnum
|
||||
from .exception import SkipPluginException
|
||||
|
||||
|
||||
async def auth_group(plugin: PluginInfo, entity: EntityIDs, message: UniMsg):
|
||||
"""群黑名单检测 群总开关检测
|
||||
|
||||
参数:
|
||||
plugin: PluginInfo
|
||||
entity: EntityIDs
|
||||
message: UniMsg
|
||||
"""
|
||||
start_time = time.time()
|
||||
|
||||
if not entity.group_id:
|
||||
return
|
||||
|
||||
try:
|
||||
text = message.extract_plain_text()
|
||||
|
||||
# 从数据库或缓存中获取群组信息
|
||||
group_dao = DataAccess(GroupConsole)
|
||||
|
||||
try:
|
||||
group: GroupConsole | None = await asyncio.wait_for(
|
||||
group_dao.safe_get_or_none(
|
||||
group_id=entity.group_id, channel_id__isnull=True
|
||||
),
|
||||
timeout=DB_TIMEOUT_SECONDS,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error("查询群组信息超时", LOGGER_COMMAND, session=entity.user_id)
|
||||
# 超时时不阻塞,继续执行
|
||||
return
|
||||
|
||||
if not group:
|
||||
raise SkipPluginException("群组信息不存在...")
|
||||
if group.level < 0:
|
||||
raise SkipPluginException("群组黑名单, 目标群组群权限权限-1...")
|
||||
if text.strip() != SwitchEnum.ENABLE and not group.status:
|
||||
raise SkipPluginException("群组休眠状态...")
|
||||
if plugin.level > group.level:
|
||||
raise SkipPluginException(
|
||||
f"{plugin.name}({plugin.module}) 群等级限制,"
|
||||
f"该功能需要的群等级: {plugin.level}..."
|
||||
)
|
||||
finally:
|
||||
# 记录执行时间
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed > WARNING_THRESHOLD: # 记录耗时超过500ms的检查
|
||||
logger.warning(
|
||||
f"auth_group 耗时: {elapsed:.3f}s, plugin={plugin.module}",
|
||||
LOGGER_COMMAND,
|
||||
session=entity.user_id,
|
||||
group_id=entity.group_id,
|
||||
)
|
||||
@@ -0,0 +1,318 @@
|
||||
import asyncio
|
||||
import time
|
||||
from typing import ClassVar
|
||||
|
||||
import nonebot
|
||||
from nonebot_plugin_uninfo import Uninfo
|
||||
from pydantic import BaseModel
|
||||
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.plugin_limit import PluginLimit
|
||||
from zhenxun.services.db_context import DB_TIMEOUT_SECONDS
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import LimitWatchType, PluginLimitType
|
||||
from zhenxun.utils.manager.priority_manager import PriorityLifecycle
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.utils.utils import (
|
||||
CountLimiter,
|
||||
FreqLimiter,
|
||||
UserBlockLimiter,
|
||||
get_entity_ids,
|
||||
)
|
||||
|
||||
from .config import LOGGER_COMMAND, WARNING_THRESHOLD
|
||||
from .exception import SkipPluginException
|
||||
|
||||
driver = nonebot.get_driver()
|
||||
|
||||
|
||||
@PriorityLifecycle.on_startup(priority=5)
|
||||
async def _():
|
||||
"""初始化限制"""
|
||||
await LimitManager.init_limit()
|
||||
|
||||
|
||||
class Limit(BaseModel):
|
||||
limit: PluginLimit
|
||||
limiter: FreqLimiter | UserBlockLimiter | CountLimiter
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
class LimitManager:
|
||||
add_module: ClassVar[list] = []
|
||||
last_update_time: ClassVar[float] = 0
|
||||
update_interval: ClassVar[float] = 6000 # 1小时更新一次
|
||||
is_updating: ClassVar[bool] = False # 防止并发更新
|
||||
|
||||
cd_limit: ClassVar[dict[str, Limit]] = {}
|
||||
block_limit: ClassVar[dict[str, Limit]] = {}
|
||||
count_limit: ClassVar[dict[str, Limit]] = {}
|
||||
|
||||
# 模块限制缓存,避免频繁查询数据库
|
||||
module_limit_cache: ClassVar[dict[str, tuple[float, list[PluginLimit]]]] = {}
|
||||
module_cache_ttl: ClassVar[float] = 60 # 模块缓存有效期(秒)
|
||||
|
||||
@classmethod
|
||||
async def init_limit(cls):
|
||||
"""初始化限制"""
|
||||
cls.last_update_time = time.time()
|
||||
try:
|
||||
await asyncio.wait_for(cls.update_limits(), timeout=DB_TIMEOUT_SECONDS * 2)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error("初始化限制超时", LOGGER_COMMAND)
|
||||
|
||||
@classmethod
|
||||
async def update_limits(cls):
|
||||
"""更新限制信息"""
|
||||
# 防止并发更新
|
||||
if cls.is_updating:
|
||||
return
|
||||
|
||||
cls.is_updating = True
|
||||
try:
|
||||
start_time = time.time()
|
||||
try:
|
||||
limit_list = await asyncio.wait_for(
|
||||
PluginLimit.filter(status=True).all(), timeout=DB_TIMEOUT_SECONDS
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error("查询限制信息超时", LOGGER_COMMAND)
|
||||
cls.is_updating = False
|
||||
return
|
||||
|
||||
# 清空旧数据
|
||||
cls.add_module = []
|
||||
cls.cd_limit = {}
|
||||
cls.block_limit = {}
|
||||
cls.count_limit = {}
|
||||
# 添加新数据
|
||||
for limit in limit_list:
|
||||
cls.add_limit(limit)
|
||||
|
||||
cls.last_update_time = time.time()
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed > WARNING_THRESHOLD: # 记录耗时超过500ms的更新
|
||||
logger.warning(f"更新限制信息耗时: {elapsed:.3f}s", LOGGER_COMMAND)
|
||||
finally:
|
||||
cls.is_updating = False
|
||||
|
||||
@classmethod
|
||||
def add_limit(cls, limit: PluginLimit):
|
||||
"""添加限制
|
||||
|
||||
参数:
|
||||
limit: PluginLimit
|
||||
"""
|
||||
if limit.module not in cls.add_module:
|
||||
cls.add_module.append(limit.module)
|
||||
if limit.limit_type == PluginLimitType.BLOCK:
|
||||
cls.block_limit[limit.module] = Limit(
|
||||
limit=limit, limiter=UserBlockLimiter()
|
||||
)
|
||||
elif limit.limit_type == PluginLimitType.CD:
|
||||
cls.cd_limit[limit.module] = Limit(
|
||||
limit=limit, limiter=FreqLimiter(limit.cd)
|
||||
)
|
||||
elif limit.limit_type == PluginLimitType.COUNT:
|
||||
cls.count_limit[limit.module] = Limit(
|
||||
limit=limit, limiter=CountLimiter(limit.max_count)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def unblock(
|
||||
cls, module: str, user_id: str, group_id: str | None, channel_id: str | None
|
||||
):
|
||||
"""解除插件block
|
||||
|
||||
参数:
|
||||
module: 模块名
|
||||
user_id: 用户id
|
||||
group_id: 群组id
|
||||
channel_id: 频道id
|
||||
"""
|
||||
if limit_model := cls.block_limit.get(module):
|
||||
limit = limit_model.limit
|
||||
limiter: UserBlockLimiter = limit_model.limiter # type: ignore
|
||||
key_type = user_id
|
||||
if group_id and limit.watch_type == LimitWatchType.GROUP:
|
||||
key_type = channel_id or group_id
|
||||
logger.debug(
|
||||
f"解除对象: {key_type} 的block限制",
|
||||
LOGGER_COMMAND,
|
||||
session=user_id,
|
||||
group_id=group_id,
|
||||
)
|
||||
limiter.set_false(key_type)
|
||||
|
||||
@classmethod
|
||||
async def get_module_limits(cls, module: str) -> list[PluginLimit]:
|
||||
"""获取模块的限制信息,使用缓存减少数据库查询
|
||||
|
||||
参数:
|
||||
module: 模块名
|
||||
|
||||
返回:
|
||||
list[PluginLimit]: 限制列表
|
||||
"""
|
||||
current_time = time.time()
|
||||
|
||||
# 检查缓存
|
||||
if module in cls.module_limit_cache:
|
||||
cache_time, limits = cls.module_limit_cache[module]
|
||||
if current_time - cache_time < cls.module_cache_ttl:
|
||||
return limits
|
||||
|
||||
# 缓存不存在或已过期,从数据库查询
|
||||
try:
|
||||
start_time = time.time()
|
||||
limits = await asyncio.wait_for(
|
||||
PluginLimit.filter(module=module, status=True).all(),
|
||||
timeout=DB_TIMEOUT_SECONDS,
|
||||
)
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed > WARNING_THRESHOLD: # 记录耗时超过500ms的查询
|
||||
logger.warning(
|
||||
f"查询模块限制信息耗时: {elapsed:.3f}s, 模块: {module}",
|
||||
LOGGER_COMMAND,
|
||||
)
|
||||
|
||||
# 更新缓存
|
||||
cls.module_limit_cache[module] = (current_time, limits)
|
||||
return limits
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"查询模块限制信息超时: {module}", LOGGER_COMMAND)
|
||||
# 超时时返回空列表,避免阻塞
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
async def check(
|
||||
cls,
|
||||
module: str,
|
||||
user_id: str,
|
||||
group_id: str | None,
|
||||
channel_id: str | None,
|
||||
):
|
||||
"""检测限制
|
||||
|
||||
参数:
|
||||
module: 模块名
|
||||
user_id: 用户id
|
||||
group_id: 群组id
|
||||
channel_id: 频道id
|
||||
|
||||
异常:
|
||||
IgnoredException: IgnoredException
|
||||
"""
|
||||
start_time = time.time()
|
||||
|
||||
# 定期更新全局限制信息
|
||||
if (
|
||||
time.time() - cls.last_update_time > cls.update_interval
|
||||
and not cls.is_updating
|
||||
):
|
||||
# 使用异步任务更新,避免阻塞当前请求
|
||||
asyncio.create_task(cls.update_limits()) # noqa: RUF006
|
||||
|
||||
# 如果模块不在已加载列表中,只加载该模块的限制
|
||||
if module not in cls.add_module:
|
||||
limits = await cls.get_module_limits(module)
|
||||
for limit in limits:
|
||||
cls.add_limit(limit)
|
||||
|
||||
# 检查各种限制
|
||||
try:
|
||||
if limit_model := cls.cd_limit.get(module):
|
||||
await cls.__check(limit_model, user_id, group_id, channel_id)
|
||||
if limit_model := cls.block_limit.get(module):
|
||||
await cls.__check(limit_model, user_id, group_id, channel_id)
|
||||
if limit_model := cls.count_limit.get(module):
|
||||
await cls.__check(limit_model, user_id, group_id, channel_id)
|
||||
finally:
|
||||
# 记录总执行时间
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed > WARNING_THRESHOLD: # 记录耗时超过500ms的检查
|
||||
logger.warning(
|
||||
f"限制检查耗时: {elapsed:.3f}s, 模块: {module}",
|
||||
LOGGER_COMMAND,
|
||||
session=user_id,
|
||||
group_id=group_id,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def __check(
|
||||
cls,
|
||||
limit_model: Limit | None,
|
||||
user_id: str,
|
||||
group_id: str | None,
|
||||
channel_id: str | None,
|
||||
):
|
||||
"""检测限制
|
||||
|
||||
参数:
|
||||
limit_model: Limit
|
||||
user_id: 用户id
|
||||
group_id: 群组id
|
||||
channel_id: 频道id
|
||||
|
||||
异常:
|
||||
IgnoredException: IgnoredException
|
||||
"""
|
||||
if not limit_model:
|
||||
return
|
||||
limit = limit_model.limit
|
||||
limiter = limit_model.limiter
|
||||
is_limit = (
|
||||
LimitWatchType.ALL
|
||||
or (group_id and limit.watch_type == LimitWatchType.GROUP)
|
||||
or (not group_id and limit.watch_type == LimitWatchType.USER)
|
||||
)
|
||||
key_type = user_id
|
||||
if group_id and limit.watch_type == LimitWatchType.GROUP:
|
||||
key_type = channel_id or group_id
|
||||
if is_limit and not limiter.check(key_type):
|
||||
if limit.result:
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
MessageUtils.build_message(limit.result).send(),
|
||||
timeout=DB_TIMEOUT_SECONDS,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"发送限制消息超时: {limit.module}", LOGGER_COMMAND)
|
||||
raise SkipPluginException(
|
||||
f"{limit.module}({limit.limit_type}) 正在限制中..."
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
f"开始进行限制 {limit.module}({limit.limit_type})...",
|
||||
LOGGER_COMMAND,
|
||||
session=user_id,
|
||||
group_id=group_id,
|
||||
)
|
||||
if isinstance(limiter, FreqLimiter):
|
||||
limiter.start_cd(key_type)
|
||||
if isinstance(limiter, UserBlockLimiter):
|
||||
limiter.set_true(key_type)
|
||||
if isinstance(limiter, CountLimiter):
|
||||
limiter.increase(key_type)
|
||||
|
||||
|
||||
async def auth_limit(plugin: PluginInfo, session: Uninfo):
|
||||
"""插件限制
|
||||
|
||||
参数:
|
||||
plugin: PluginInfo
|
||||
session: Uninfo
|
||||
"""
|
||||
entity = get_entity_ids(session)
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
LimitManager.check(
|
||||
plugin.module, entity.user_id, entity.group_id, entity.channel_id
|
||||
),
|
||||
timeout=DB_TIMEOUT_SECONDS * 2, # 给予更长的超时时间
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"检查插件限制超时: {plugin.module}", LOGGER_COMMAND)
|
||||
# 超时时不抛出异常,允许继续执行
|
||||
@@ -0,0 +1,242 @@
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot_plugin_uninfo import Uninfo
|
||||
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.services.data_access import DataAccess
|
||||
from zhenxun.services.db_context import DB_TIMEOUT_SECONDS
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.common_utils import CommonUtils
|
||||
from zhenxun.utils.enum import BlockType
|
||||
from zhenxun.utils.utils import get_entity_ids
|
||||
|
||||
from .config import LOGGER_COMMAND, WARNING_THRESHOLD
|
||||
from .exception import IsSuperuserException, SkipPluginException
|
||||
from .utils import freq, is_poke, send_message
|
||||
|
||||
|
||||
class GroupCheck:
|
||||
def __init__(
|
||||
self, plugin: PluginInfo, group_id: str, session: Uninfo, is_poke: bool
|
||||
) -> None:
|
||||
self.group_id = group_id
|
||||
self.session = session
|
||||
self.is_poke = is_poke
|
||||
self.plugin = plugin
|
||||
self.group_dao = DataAccess(GroupConsole)
|
||||
self.group_data = None
|
||||
|
||||
async def check(self):
|
||||
start_time = time.time()
|
||||
try:
|
||||
# 只查询一次数据库,使用 DataAccess 的缓存机制
|
||||
try:
|
||||
self.group_data = await asyncio.wait_for(
|
||||
self.group_dao.safe_get_or_none(
|
||||
group_id=self.group_id, channel_id__isnull=True
|
||||
),
|
||||
timeout=DB_TIMEOUT_SECONDS,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"查询群组数据超时: {self.group_id}", LOGGER_COMMAND)
|
||||
return # 超时时不阻塞,继续执行
|
||||
|
||||
# 检查超级用户禁用
|
||||
if (
|
||||
self.group_data
|
||||
and CommonUtils.format(self.plugin.module)
|
||||
in self.group_data.superuser_block_plugin
|
||||
):
|
||||
if freq.is_send_limit_message(self.plugin, self.group_id, self.is_poke):
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
send_message(
|
||||
self.session,
|
||||
"超级管理员禁用了该群此功能...",
|
||||
self.group_id,
|
||||
),
|
||||
timeout=DB_TIMEOUT_SECONDS,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"发送消息超时: {self.group_id}", LOGGER_COMMAND)
|
||||
raise SkipPluginException(
|
||||
f"{self.plugin.name}({self.plugin.module})"
|
||||
f" 超级管理员禁用了该群此功能..."
|
||||
)
|
||||
|
||||
# 检查普通禁用
|
||||
if (
|
||||
self.group_data
|
||||
and CommonUtils.format(self.plugin.module)
|
||||
in self.group_data.block_plugin
|
||||
):
|
||||
if freq.is_send_limit_message(self.plugin, self.group_id, self.is_poke):
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
send_message(
|
||||
self.session, "该群未开启此功能...", self.group_id
|
||||
),
|
||||
timeout=DB_TIMEOUT_SECONDS,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"发送消息超时: {self.group_id}", LOGGER_COMMAND)
|
||||
raise SkipPluginException(
|
||||
f"{self.plugin.name}({self.plugin.module}) 未开启此功能..."
|
||||
)
|
||||
|
||||
# 检查全局禁用
|
||||
if self.plugin.block_type == BlockType.GROUP:
|
||||
if freq.is_send_limit_message(self.plugin, self.group_id, self.is_poke):
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
send_message(
|
||||
self.session, "该功能在群组中已被禁用...", self.group_id
|
||||
),
|
||||
timeout=DB_TIMEOUT_SECONDS,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"发送消息超时: {self.group_id}", LOGGER_COMMAND)
|
||||
raise SkipPluginException(
|
||||
f"{self.plugin.name}({self.plugin.module})该插件在群组中已被禁用..."
|
||||
)
|
||||
finally:
|
||||
# 记录执行时间
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed > WARNING_THRESHOLD: # 记录耗时超过500ms的检查
|
||||
logger.warning(
|
||||
f"GroupCheck.check 耗时: {elapsed:.3f}s, 群组: {self.group_id}",
|
||||
LOGGER_COMMAND,
|
||||
)
|
||||
|
||||
|
||||
class PluginCheck:
|
||||
def __init__(self, group_id: str | None, session: Uninfo, is_poke: bool):
|
||||
self.session = session
|
||||
self.is_poke = is_poke
|
||||
self.group_id = group_id
|
||||
self.group_dao = DataAccess(GroupConsole)
|
||||
self.group_data = None
|
||||
|
||||
async def check_user(self, plugin: PluginInfo):
|
||||
"""全局私聊禁用检测
|
||||
|
||||
参数:
|
||||
plugin: PluginInfo
|
||||
|
||||
异常:
|
||||
IgnoredException: 忽略插件
|
||||
"""
|
||||
if plugin.block_type == BlockType.PRIVATE:
|
||||
if freq.is_send_limit_message(plugin, self.session.user.id, self.is_poke):
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
send_message(self.session, "该功能在私聊中已被禁用..."),
|
||||
timeout=DB_TIMEOUT_SECONDS,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error("发送消息超时", LOGGER_COMMAND)
|
||||
raise SkipPluginException(
|
||||
f"{plugin.name}({plugin.module}) 该插件在私聊中已被禁用..."
|
||||
)
|
||||
|
||||
async def check_global(self, plugin: PluginInfo):
|
||||
"""全局状态
|
||||
|
||||
参数:
|
||||
plugin: PluginInfo
|
||||
|
||||
异常:
|
||||
IgnoredException: 忽略插件
|
||||
"""
|
||||
start_time = time.time()
|
||||
try:
|
||||
if plugin.status or plugin.block_type != BlockType.ALL:
|
||||
return
|
||||
"""全局状态"""
|
||||
if self.group_id:
|
||||
# 使用 DataAccess 的缓存机制
|
||||
try:
|
||||
self.group_data = await asyncio.wait_for(
|
||||
self.group_dao.safe_get_or_none(
|
||||
group_id=self.group_id, channel_id__isnull=True
|
||||
),
|
||||
timeout=DB_TIMEOUT_SECONDS,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"查询群组数据超时: {self.group_id}", LOGGER_COMMAND)
|
||||
return # 超时时不阻塞,继续执行
|
||||
|
||||
if self.group_data and self.group_data.is_super:
|
||||
raise IsSuperuserException()
|
||||
|
||||
sid = self.group_id or self.session.user.id
|
||||
if freq.is_send_limit_message(plugin, sid, self.is_poke):
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
send_message(self.session, "全局未开启此功能...", sid),
|
||||
timeout=DB_TIMEOUT_SECONDS,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"发送消息超时: {sid}", LOGGER_COMMAND)
|
||||
raise SkipPluginException(
|
||||
f"{plugin.name}({plugin.module}) 全局未开启此功能..."
|
||||
)
|
||||
finally:
|
||||
# 记录执行时间
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed > WARNING_THRESHOLD: # 记录耗时超过500ms的检查
|
||||
logger.warning(
|
||||
f"PluginCheck.check_global 耗时: {elapsed:.3f}s", LOGGER_COMMAND
|
||||
)
|
||||
|
||||
|
||||
async def auth_plugin(plugin: PluginInfo, session: Uninfo, event: Event):
|
||||
"""插件状态
|
||||
|
||||
参数:
|
||||
plugin: PluginInfo
|
||||
session: Uninfo
|
||||
event: Event
|
||||
"""
|
||||
start_time = time.time()
|
||||
try:
|
||||
entity = get_entity_ids(session)
|
||||
is_poke_event = is_poke(event)
|
||||
user_check = PluginCheck(entity.group_id, session, is_poke_event)
|
||||
|
||||
if entity.group_id:
|
||||
group_check = GroupCheck(plugin, entity.group_id, session, is_poke_event)
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
group_check.check(), timeout=DB_TIMEOUT_SECONDS * 2
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"群组检查超时: {entity.group_id}", LOGGER_COMMAND)
|
||||
# 超时时不阻塞,继续执行
|
||||
else:
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
user_check.check_user(plugin), timeout=DB_TIMEOUT_SECONDS
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error("用户检查超时", LOGGER_COMMAND)
|
||||
# 超时时不阻塞,继续执行
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
user_check.check_global(plugin), timeout=DB_TIMEOUT_SECONDS
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error("全局检查超时", LOGGER_COMMAND)
|
||||
# 超时时不阻塞,继续执行
|
||||
finally:
|
||||
# 记录总执行时间
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed > WARNING_THRESHOLD: # 记录耗时超过500ms的检查
|
||||
logger.warning(
|
||||
f"auth_plugin 总耗时: {elapsed:.3f}s, 模块: {plugin.module}",
|
||||
LOGGER_COMMAND,
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
import nonebot
|
||||
from nonebot_plugin_uninfo import Uninfo
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
|
||||
from .exception import SkipPluginException
|
||||
|
||||
Config.add_plugin_config(
|
||||
"hook",
|
||||
"FILTER_BOT",
|
||||
True,
|
||||
help="过滤当前连接bot(防止bot互相调用)",
|
||||
default_value=True,
|
||||
type=bool,
|
||||
)
|
||||
|
||||
|
||||
def bot_filter(session: Uninfo):
|
||||
"""过滤bot调用bot
|
||||
|
||||
参数:
|
||||
session: Uninfo
|
||||
|
||||
异常:
|
||||
SkipPluginException: bot互相调用
|
||||
"""
|
||||
if not Config.get_config("hook", "FILTER_BOT"):
|
||||
return
|
||||
bot_ids = list(nonebot.get_bots().keys())
|
||||
if session.user.id == session.self_id:
|
||||
return
|
||||
if session.user.id in bot_ids:
|
||||
raise SkipPluginException(
|
||||
f"bot:{session.self_id} 尝试调用 bot:{session.user.id}"
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
from enum import StrEnum
|
||||
else:
|
||||
from strenum import StrEnum
|
||||
|
||||
LOGGER_COMMAND = "AuthChecker"
|
||||
|
||||
|
||||
class SwitchEnum(StrEnum):
|
||||
ENABLE = "醒来"
|
||||
DISABLE = "休息吧"
|
||||
|
||||
|
||||
WARNING_THRESHOLD = 0.5 # 警告阈值(秒)
|
||||
@@ -0,0 +1,26 @@
|
||||
class IsSuperuserException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SkipPluginException(Exception):
|
||||
def __init__(self, info: str, *args: object) -> None:
|
||||
super().__init__(*args)
|
||||
self.info = info
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.info
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.info
|
||||
|
||||
|
||||
class PermissionExemption(Exception):
|
||||
def __init__(self, info: str, *args: object) -> None:
|
||||
super().__init__(*args)
|
||||
self.info = info
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.info
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.info
|
||||
@@ -0,0 +1,91 @@
|
||||
import contextlib
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot_plugin_uninfo import Uninfo
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.utils.utils import FreqLimiter
|
||||
|
||||
from .config import LOGGER_COMMAND
|
||||
|
||||
base_config = Config.get("hook")
|
||||
|
||||
|
||||
def is_poke(event: Event) -> bool:
|
||||
"""判断是否为poke类型
|
||||
|
||||
参数:
|
||||
event: Event
|
||||
|
||||
返回:
|
||||
bool: 是否为poke类型
|
||||
"""
|
||||
with contextlib.suppress(ImportError):
|
||||
from nonebot.adapters.onebot.v11 import PokeNotifyEvent
|
||||
|
||||
return isinstance(event, PokeNotifyEvent)
|
||||
return False
|
||||
|
||||
|
||||
async def send_message(
|
||||
session: Uninfo, message: list | str, check_tag: str | None = None
|
||||
):
|
||||
"""发送消息
|
||||
|
||||
参数:
|
||||
session: Uninfo
|
||||
message: 消息
|
||||
check_tag: cd flag
|
||||
"""
|
||||
try:
|
||||
if not check_tag:
|
||||
await MessageUtils.build_message(message).send(reply_to=True)
|
||||
elif freq._flmt.check(check_tag):
|
||||
freq._flmt.start_cd(check_tag)
|
||||
await MessageUtils.build_message(message).send(reply_to=True)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"发送消息失败",
|
||||
LOGGER_COMMAND,
|
||||
session=session,
|
||||
e=e,
|
||||
)
|
||||
|
||||
|
||||
class FreqUtils:
|
||||
def __init__(self):
|
||||
check_notice_info_cd = Config.get_config("hook", "CHECK_NOTICE_INFO_CD")
|
||||
if check_notice_info_cd is None or check_notice_info_cd < 0:
|
||||
raise ValueError("模块: [hook], 配置项: [CHECK_NOTICE_INFO_CD] 为空或小于0")
|
||||
self._flmt = FreqLimiter(check_notice_info_cd)
|
||||
self._flmt_g = FreqLimiter(check_notice_info_cd)
|
||||
self._flmt_s = FreqLimiter(check_notice_info_cd)
|
||||
self._flmt_c = FreqLimiter(check_notice_info_cd)
|
||||
|
||||
def is_send_limit_message(
|
||||
self, plugin: PluginInfo, sid: str, is_poke: bool
|
||||
) -> bool:
|
||||
"""是否发送提示消息
|
||||
|
||||
参数:
|
||||
plugin: PluginInfo
|
||||
sid: 检测键
|
||||
is_poke: 是否是戳一戳
|
||||
|
||||
返回:
|
||||
bool: 是否发送提示消息
|
||||
"""
|
||||
if is_poke:
|
||||
return False
|
||||
if not base_config.get("IS_SEND_TIP_MESSAGE"):
|
||||
return False
|
||||
if plugin.plugin_type == PluginType.DEPENDANT:
|
||||
return False
|
||||
return plugin.module != "ai" if self._flmt_s.check(sid) else False
|
||||
|
||||
|
||||
freq = FreqUtils()
|
||||
@@ -0,0 +1,375 @@
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.exception import IgnoredException
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot_plugin_alconna import UniMsg
|
||||
from nonebot_plugin_uninfo import Uninfo
|
||||
from tortoise.exceptions import IntegrityError
|
||||
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.user_console import UserConsole
|
||||
from zhenxun.services.data_access import DataAccess
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import GoldHandle, PluginType
|
||||
from zhenxun.utils.exception import InsufficientGold
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
from zhenxun.utils.utils import get_entity_ids
|
||||
|
||||
from .auth.auth_admin import auth_admin
|
||||
from .auth.auth_ban import auth_ban
|
||||
from .auth.auth_bot import auth_bot
|
||||
from .auth.auth_cost import auth_cost
|
||||
from .auth.auth_group import auth_group
|
||||
from .auth.auth_limit import LimitManager, auth_limit
|
||||
from .auth.auth_plugin import auth_plugin
|
||||
from .auth.bot_filter import bot_filter
|
||||
from .auth.config import LOGGER_COMMAND, WARNING_THRESHOLD
|
||||
from .auth.exception import (
|
||||
IsSuperuserException,
|
||||
PermissionExemption,
|
||||
SkipPluginException,
|
||||
)
|
||||
|
||||
# 超时设置(秒)
|
||||
TIMEOUT_SECONDS = 5.0
|
||||
# 熔断计数器
|
||||
CIRCUIT_BREAKERS = {
|
||||
"auth_ban": {"failures": 0, "threshold": 3, "active": False, "reset_time": 0},
|
||||
"auth_bot": {"failures": 0, "threshold": 3, "active": False, "reset_time": 0},
|
||||
"auth_group": {"failures": 0, "threshold": 3, "active": False, "reset_time": 0},
|
||||
"auth_admin": {"failures": 0, "threshold": 3, "active": False, "reset_time": 0},
|
||||
"auth_plugin": {"failures": 0, "threshold": 3, "active": False, "reset_time": 0},
|
||||
"auth_limit": {"failures": 0, "threshold": 3, "active": False, "reset_time": 0},
|
||||
}
|
||||
# 熔断重置时间(秒)
|
||||
CIRCUIT_RESET_TIME = 300 # 5分钟
|
||||
|
||||
|
||||
# 超时装饰器
|
||||
async def with_timeout(coro, timeout=TIMEOUT_SECONDS, name=None):
|
||||
"""带超时控制的协程执行
|
||||
|
||||
参数:
|
||||
coro: 要执行的协程
|
||||
timeout: 超时时间(秒)
|
||||
name: 操作名称,用于日志记录
|
||||
|
||||
返回:
|
||||
协程的返回值,或者在超时时抛出 TimeoutError
|
||||
"""
|
||||
try:
|
||||
return await asyncio.wait_for(coro, timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
if name:
|
||||
logger.error(f"{name} 操作超时 (>{timeout}s)", LOGGER_COMMAND)
|
||||
# 更新熔断计数器
|
||||
if name in CIRCUIT_BREAKERS:
|
||||
CIRCUIT_BREAKERS[name]["failures"] += 1
|
||||
if (
|
||||
CIRCUIT_BREAKERS[name]["failures"]
|
||||
>= CIRCUIT_BREAKERS[name]["threshold"]
|
||||
and not CIRCUIT_BREAKERS[name]["active"]
|
||||
):
|
||||
CIRCUIT_BREAKERS[name]["active"] = True
|
||||
CIRCUIT_BREAKERS[name]["reset_time"] = (
|
||||
time.time() + CIRCUIT_RESET_TIME
|
||||
)
|
||||
logger.warning(
|
||||
f"{name} 熔断器已激活,将在 {CIRCUIT_RESET_TIME} 秒后重置",
|
||||
LOGGER_COMMAND,
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
# 检查熔断状态
|
||||
def check_circuit_breaker(name):
|
||||
"""检查熔断器状态
|
||||
|
||||
参数:
|
||||
name: 操作名称
|
||||
|
||||
返回:
|
||||
bool: 是否已熔断
|
||||
"""
|
||||
if name not in CIRCUIT_BREAKERS:
|
||||
return False
|
||||
|
||||
# 检查是否需要重置熔断器
|
||||
if (
|
||||
CIRCUIT_BREAKERS[name]["active"]
|
||||
and time.time() > CIRCUIT_BREAKERS[name]["reset_time"]
|
||||
):
|
||||
CIRCUIT_BREAKERS[name]["active"] = False
|
||||
CIRCUIT_BREAKERS[name]["failures"] = 0
|
||||
logger.info(f"{name} 熔断器已重置", LOGGER_COMMAND)
|
||||
|
||||
return CIRCUIT_BREAKERS[name]["active"]
|
||||
|
||||
|
||||
async def get_plugin_and_user(
|
||||
module: str, user_id: str
|
||||
) -> tuple[PluginInfo, UserConsole]:
|
||||
"""获取用户数据和插件信息
|
||||
|
||||
参数:
|
||||
module: 模块名
|
||||
user_id: 用户id
|
||||
|
||||
异常:
|
||||
PermissionExemption: 插件数据不存在
|
||||
PermissionExemption: 插件类型为HIDDEN
|
||||
PermissionExemption: 重复创建用户
|
||||
PermissionExemption: 用户数据不存在
|
||||
|
||||
返回:
|
||||
tuple[PluginInfo, UserConsole]: 插件信息,用户信息
|
||||
"""
|
||||
user_dao = DataAccess(UserConsole)
|
||||
plugin_dao = DataAccess(PluginInfo)
|
||||
|
||||
# 并行查询插件和用户数据
|
||||
plugin_task = plugin_dao.safe_get_or_none(module=module)
|
||||
user_task = user_dao.safe_get_or_none(user_id=user_id)
|
||||
|
||||
try:
|
||||
plugin, user = await with_timeout(
|
||||
asyncio.gather(plugin_task, user_task), name="get_plugin_and_user"
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
# 如果并行查询超时,尝试串行查询
|
||||
logger.warning("并行查询超时,尝试串行查询", LOGGER_COMMAND)
|
||||
plugin = await with_timeout(
|
||||
plugin_dao.safe_get_or_none(module=module), name="get_plugin"
|
||||
)
|
||||
user = await with_timeout(
|
||||
user_dao.safe_get_or_none(user_id=user_id), name="get_user"
|
||||
)
|
||||
|
||||
if not plugin:
|
||||
raise PermissionExemption(f"插件:{module} 数据不存在,已跳过权限检查...")
|
||||
if plugin.plugin_type == PluginType.HIDDEN:
|
||||
raise PermissionExemption(
|
||||
f"插件: {plugin.name}:{plugin.module} 为HIDDEN,已跳过权限检查..."
|
||||
)
|
||||
user = None
|
||||
try:
|
||||
user = await user_dao.safe_get_or_none(user_id=user_id)
|
||||
except IntegrityError as e:
|
||||
raise PermissionExemption("重复创建用户,已跳过该次权限检查...") from e
|
||||
if not user:
|
||||
raise PermissionExemption("用户数据不存在,已跳过权限检查...")
|
||||
return plugin, user
|
||||
|
||||
|
||||
async def get_plugin_cost(
|
||||
bot: Bot, user: UserConsole, plugin: PluginInfo, session: Uninfo
|
||||
) -> int:
|
||||
"""获取插件费用
|
||||
|
||||
参数:
|
||||
bot: Bot
|
||||
user: 用户数据
|
||||
plugin: 插件数据
|
||||
session: Uninfo
|
||||
|
||||
异常:
|
||||
IsSuperuserException: 超级用户
|
||||
IsSuperuserException: 超级用户
|
||||
|
||||
返回:
|
||||
int: 调用插件金币费用
|
||||
"""
|
||||
cost_gold = await with_timeout(auth_cost(user, plugin, session), name="auth_cost")
|
||||
if session.user.id in bot.config.superusers:
|
||||
if plugin.plugin_type == PluginType.SUPERUSER:
|
||||
raise IsSuperuserException()
|
||||
if not plugin.limit_superuser:
|
||||
raise IsSuperuserException()
|
||||
return cost_gold
|
||||
|
||||
|
||||
async def reduce_gold(user_id: str, module: str, cost_gold: int, session: Uninfo):
|
||||
"""扣除用户金币
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
module: 插件模块名称
|
||||
cost_gold: 消耗金币
|
||||
session: Uninfo
|
||||
"""
|
||||
user_dao = DataAccess(UserConsole)
|
||||
try:
|
||||
await with_timeout(
|
||||
UserConsole.reduce_gold(
|
||||
user_id,
|
||||
cost_gold,
|
||||
GoldHandle.PLUGIN,
|
||||
module,
|
||||
PlatformUtils.get_platform(session),
|
||||
),
|
||||
name="reduce_gold",
|
||||
)
|
||||
except InsufficientGold:
|
||||
if u := await UserConsole.get_user(user_id):
|
||||
u.gold = 0
|
||||
await u.save(update_fields=["gold"])
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(
|
||||
f"扣除金币超时,用户: {user_id}, 金币: {cost_gold}",
|
||||
LOGGER_COMMAND,
|
||||
session=session,
|
||||
)
|
||||
|
||||
# 清除缓存,使下次查询时从数据库获取最新数据
|
||||
await user_dao.clear_cache(user_id=user_id)
|
||||
logger.debug(f"调用功能花费金币: {cost_gold}", LOGGER_COMMAND, session=session)
|
||||
|
||||
|
||||
# 辅助函数,用于记录每个 hook 的执行时间
|
||||
async def time_hook(coro, name, time_dict):
|
||||
start = time.time()
|
||||
try:
|
||||
# 检查熔断状态
|
||||
if check_circuit_breaker(name):
|
||||
logger.info(f"{name} 熔断器激活中,跳过执行", LOGGER_COMMAND)
|
||||
time_dict[name] = "熔断跳过"
|
||||
return
|
||||
|
||||
# 添加超时控制
|
||||
return await with_timeout(coro, name=name)
|
||||
except asyncio.TimeoutError:
|
||||
time_dict[name] = f"超时 (>{TIMEOUT_SECONDS}s)"
|
||||
finally:
|
||||
if name not in time_dict:
|
||||
time_dict[name] = f"{time.time() - start:.3f}s"
|
||||
|
||||
|
||||
async def auth(
|
||||
matcher: Matcher,
|
||||
event: Event,
|
||||
bot: Bot,
|
||||
session: Uninfo,
|
||||
message: UniMsg,
|
||||
):
|
||||
"""权限检查
|
||||
|
||||
参数:
|
||||
matcher: matcher
|
||||
event: Event
|
||||
bot: bot
|
||||
session: Uninfo
|
||||
message: UniMsg
|
||||
"""
|
||||
start_time = time.time()
|
||||
cost_gold = 0
|
||||
ignore_flag = False
|
||||
entity = get_entity_ids(session)
|
||||
module = matcher.plugin_name or ""
|
||||
|
||||
# 用于记录各个 hook 的执行时间
|
||||
hook_times = {}
|
||||
hooks_time = 0 # 初始化 hooks_time 变量
|
||||
|
||||
try:
|
||||
if not module:
|
||||
raise PermissionExemption("Matcher插件名称不存在...")
|
||||
|
||||
# 获取插件和用户数据
|
||||
plugin_user_start = time.time()
|
||||
try:
|
||||
plugin, user = await with_timeout(
|
||||
get_plugin_and_user(module, entity.user_id), name="get_plugin_and_user"
|
||||
)
|
||||
hook_times["get_plugin_user"] = f"{time.time() - plugin_user_start:.3f}s"
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(
|
||||
f"获取插件和用户数据超时,模块: {module}",
|
||||
LOGGER_COMMAND,
|
||||
session=session,
|
||||
)
|
||||
raise PermissionExemption("获取插件和用户数据超时,请稍后再试...")
|
||||
|
||||
# 获取插件费用
|
||||
cost_start = time.time()
|
||||
try:
|
||||
cost_gold = await with_timeout(
|
||||
get_plugin_cost(bot, user, plugin, session), name="get_plugin_cost"
|
||||
)
|
||||
hook_times["cost_gold"] = f"{time.time() - cost_start:.3f}s"
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(
|
||||
f"获取插件费用超时,模块: {module}", LOGGER_COMMAND, session=session
|
||||
)
|
||||
# 继续执行,不阻止权限检查
|
||||
|
||||
# 执行 bot_filter
|
||||
bot_filter(session)
|
||||
|
||||
# 并行执行所有 hook 检查,并记录执行时间
|
||||
hooks_start = time.time()
|
||||
|
||||
# 创建所有 hook 任务
|
||||
hook_tasks = [
|
||||
time_hook(auth_ban(matcher, bot, session), "auth_ban", hook_times),
|
||||
time_hook(auth_bot(plugin, bot.self_id), "auth_bot", hook_times),
|
||||
time_hook(auth_group(plugin, entity, message), "auth_group", hook_times),
|
||||
time_hook(auth_admin(plugin, session), "auth_admin", hook_times),
|
||||
time_hook(auth_plugin(plugin, session, event), "auth_plugin", hook_times),
|
||||
time_hook(auth_limit(plugin, session), "auth_limit", hook_times),
|
||||
]
|
||||
|
||||
# 使用 gather 并行执行所有 hook,但添加总体超时控制
|
||||
try:
|
||||
await with_timeout(
|
||||
asyncio.gather(*hook_tasks),
|
||||
timeout=TIMEOUT_SECONDS * 2, # 给总体执行更多时间
|
||||
name="auth_hooks_gather",
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(
|
||||
f"权限检查 hooks 总体执行超时,模块: {module}",
|
||||
LOGGER_COMMAND,
|
||||
session=session,
|
||||
)
|
||||
# 不抛出异常,允许继续执行
|
||||
|
||||
hooks_time = time.time() - hooks_start
|
||||
|
||||
except SkipPluginException as e:
|
||||
LimitManager.unblock(module, entity.user_id, entity.group_id, entity.channel_id)
|
||||
logger.info(str(e), LOGGER_COMMAND, session=session)
|
||||
ignore_flag = True
|
||||
except IsSuperuserException:
|
||||
logger.debug("超级用户跳过权限检测...", LOGGER_COMMAND, session=session)
|
||||
except PermissionExemption as e:
|
||||
logger.info(str(e), LOGGER_COMMAND, session=session)
|
||||
|
||||
# 扣除金币
|
||||
if not ignore_flag and cost_gold > 0:
|
||||
gold_start = time.time()
|
||||
try:
|
||||
await with_timeout(
|
||||
reduce_gold(entity.user_id, module, cost_gold, session),
|
||||
name="reduce_gold",
|
||||
)
|
||||
hook_times["reduce_gold"] = f"{time.time() - gold_start:.3f}s"
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(
|
||||
f"扣除金币超时,模块: {module}", LOGGER_COMMAND, session=session
|
||||
)
|
||||
|
||||
# 记录总执行时间
|
||||
total_time = time.time() - start_time
|
||||
if total_time > WARNING_THRESHOLD: # 如果总时间超过500ms,记录详细信息
|
||||
logger.warning(
|
||||
f"权限检查耗时过长: {total_time:.3f}s, 模块: {module}, "
|
||||
f"hooks时间: {hooks_time:.3f}s, "
|
||||
f"详情: {hook_times}",
|
||||
LOGGER_COMMAND,
|
||||
session=session,
|
||||
)
|
||||
|
||||
if ignore_flag:
|
||||
raise IgnoredException("权限检测 ignore")
|
||||
@@ -1,41 +1,43 @@
|
||||
from nonebot.adapters.onebot.v11 import Bot, Event
|
||||
import time
|
||||
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.message import run_postprocessor, run_preprocessor
|
||||
from nonebot_plugin_alconna import UniMsg
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_uninfo import Uninfo
|
||||
|
||||
from ._auth_checker import LimitManage, checker
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from .auth.config import LOGGER_COMMAND
|
||||
from .auth_checker import LimitManager, auth
|
||||
|
||||
|
||||
# # 权限检测
|
||||
@run_preprocessor
|
||||
async def _(
|
||||
matcher: Matcher, event: Event, bot: Bot, session: EventSession, message: UniMsg
|
||||
):
|
||||
await checker.auth(
|
||||
async def _(matcher: Matcher, event: Event, bot: Bot, session: Uninfo, message: UniMsg):
|
||||
start_time = time.time()
|
||||
await auth(
|
||||
matcher,
|
||||
event,
|
||||
bot,
|
||||
session,
|
||||
message,
|
||||
)
|
||||
logger.debug(f"权限检测耗时:{time.time() - start_time}秒", LOGGER_COMMAND)
|
||||
|
||||
|
||||
# 解除命令block阻塞
|
||||
@run_postprocessor
|
||||
async def _(
|
||||
matcher: Matcher,
|
||||
exception: Exception | None,
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
session: EventSession,
|
||||
):
|
||||
user_id = session.id1
|
||||
group_id = session.id3
|
||||
channel_id = session.id2
|
||||
if not group_id:
|
||||
group_id = channel_id
|
||||
channel_id = None
|
||||
async def _(matcher: Matcher, session: Uninfo):
|
||||
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
|
||||
if user_id and matcher.plugin:
|
||||
module = matcher.plugin.name
|
||||
LimitManage.unblock(module, user_id, group_id, channel_id)
|
||||
LimitManager.unblock(module, user_id, group_id, channel_id)
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.exception import IgnoredException
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.message import run_preprocessor
|
||||
from nonebot.typing import T_State
|
||||
from nonebot_plugin_alconna import At
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.models.ban_console import BanConsole
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.utils.utils import FreqLimiter
|
||||
|
||||
Config.add_plugin_config(
|
||||
"hook",
|
||||
"BAN_RESULT",
|
||||
"才不会给你发消息.",
|
||||
help="对被ban用户发送的消息",
|
||||
)
|
||||
|
||||
_flmt = FreqLimiter(300)
|
||||
|
||||
|
||||
# 检查是否被ban
|
||||
@run_preprocessor
|
||||
async def _(
|
||||
matcher: Matcher, bot: Bot, event: Event, state: T_State, session: EventSession
|
||||
):
|
||||
extra = {}
|
||||
if plugin := matcher.plugin:
|
||||
if metadata := plugin.metadata:
|
||||
extra = metadata.extra
|
||||
if extra.get("plugin_type") in [PluginType.HIDDEN]:
|
||||
return
|
||||
user_id = session.id1
|
||||
group_id = session.id3 or session.id2
|
||||
if group_id:
|
||||
if user_id in bot.config.superusers:
|
||||
return
|
||||
if await BanConsole.is_ban(None, group_id):
|
||||
logger.debug("群组处于黑名单中...", "ban_hook")
|
||||
raise IgnoredException("群组处于黑名单中...")
|
||||
if g := await GroupConsole.get_group(group_id):
|
||||
if g.level < 0:
|
||||
logger.debug("群黑名单, 群权限-1...", "ban_hook")
|
||||
raise IgnoredException("群黑名单, 群权限-1..")
|
||||
if user_id:
|
||||
ban_result = Config.get_config("hook", "BAN_RESULT")
|
||||
if user_id in bot.config.superusers:
|
||||
return
|
||||
if await BanConsole.is_ban(user_id, group_id):
|
||||
time = await BanConsole.check_ban_time(user_id, group_id)
|
||||
if time == -1:
|
||||
time_str = "∞"
|
||||
else:
|
||||
time = abs(int(time))
|
||||
if time < 60:
|
||||
time_str = f"{time!s} 秒"
|
||||
else:
|
||||
minute = int(time / 60)
|
||||
if minute > 60:
|
||||
hours = minute // 60
|
||||
minute %= 60
|
||||
time_str = f"{hours} 小时 {minute}分钟"
|
||||
else:
|
||||
time_str = f"{minute} 分钟"
|
||||
if (
|
||||
not extra.get("ignore_prompt")
|
||||
and time != -1
|
||||
and ban_result
|
||||
and _flmt.check(user_id)
|
||||
):
|
||||
_flmt.start_cd(user_id)
|
||||
await MessageUtils.build_message(
|
||||
[
|
||||
At(flag="user", target=user_id),
|
||||
f"{ban_result}\n在..在 {time_str} 后才会理你喔",
|
||||
]
|
||||
).send()
|
||||
logger.debug("用户处于黑名单中...", "ban_hook")
|
||||
raise IgnoredException("用户处于黑名单中...")
|
||||
@@ -9,6 +9,8 @@ from zhenxun.utils.enum import BotSentType
|
||||
from zhenxun.utils.manager.message_manager import MessageManager
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
|
||||
LOG_COMMAND = "MessageHook"
|
||||
|
||||
|
||||
def replace_message(message: Message) -> str:
|
||||
"""将消息中的at、image、record、face替换为字符串
|
||||
@@ -54,11 +56,11 @@ async def handle_api_result(
|
||||
if user_id and message_id:
|
||||
MessageManager.add(str(user_id), str(message_id))
|
||||
logger.debug(
|
||||
f"收集消息id,user_id: {user_id}, msg_id: {message_id}", "msg_hook"
|
||||
f"收集消息id,user_id: {user_id}, msg_id: {message_id}", LOG_COMMAND
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"收集消息id发生错误...data: {data}, result: {result}", "msg_hook", e=e
|
||||
f"收集消息id发生错误...data: {data}, result: {result}", LOG_COMMAND, e=e
|
||||
)
|
||||
if not Config.get_config("hook", "RECORD_BOT_SENT_MESSAGES"):
|
||||
return
|
||||
@@ -80,6 +82,6 @@ async def handle_api_result(
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"消息发送记录发生错误...data: {data}, result: {result}",
|
||||
"msg_hook",
|
||||
LOG_COMMAND,
|
||||
e=e,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user