feat: 整合开发历史 (#20),大更新。。。
This commit is contained in:
166
core/admin_manager.py
Normal file
166
core/admin_manager.py
Normal file
@@ -0,0 +1,166 @@
|
||||
"""
|
||||
管理员管理器模块
|
||||
|
||||
该模块负责管理机器人的管理员列表。
|
||||
它实现了文件和 Redis 缓存之间的数据同步,并提供了一套清晰的 API
|
||||
供其他模块调用。
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
from typing import Set
|
||||
|
||||
from .logger import logger
|
||||
|
||||
|
||||
class AdminManager:
|
||||
"""
|
||||
管理员管理器类
|
||||
|
||||
负责加载、缓存和管理管理员列表。
|
||||
使用单例模式,确保全局只有一个实例。
|
||||
"""
|
||||
_instance = None
|
||||
_REDIS_KEY = "neobot:admins" # 用于存储管理员集合的 Redis 键
|
||||
|
||||
def __new__(cls):
|
||||
"""
|
||||
单例模式实现
|
||||
"""
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance._initialized = False
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
初始化 AdminManager
|
||||
"""
|
||||
if getattr(self, "_initialized", False):
|
||||
return
|
||||
|
||||
# 管理员数据文件路径
|
||||
self.data_file = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"..",
|
||||
"data",
|
||||
"admin.json"
|
||||
)
|
||||
|
||||
self._admins: Set[int] = set()
|
||||
self._initialized = True
|
||||
logger.info("管理员管理器初始化完成")
|
||||
|
||||
async def initialize(self):
|
||||
"""
|
||||
异步初始化,加载数据并同步到 Redis
|
||||
"""
|
||||
await self._load_from_file()
|
||||
await self._sync_to_redis()
|
||||
logger.info("管理员数据加载并同步到 Redis 完成")
|
||||
|
||||
async def _load_from_file(self):
|
||||
"""
|
||||
从 admin.json 加载管理员列表
|
||||
"""
|
||||
try:
|
||||
if os.path.exists(self.data_file):
|
||||
with open(self.data_file, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
admins = data.get("admins", [])
|
||||
self._admins = set(int(admin_id) for admin_id in admins)
|
||||
logger.debug(f"从 {self.data_file} 加载了 {len(self._admins)} 位管理员")
|
||||
else:
|
||||
# 如果文件不存在,创建一个空的
|
||||
self._admins = set()
|
||||
await self._save_to_file()
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
logger.error(f"加载或解析 admin.json 失败: {e}")
|
||||
self._admins = set()
|
||||
|
||||
async def _save_to_file(self):
|
||||
"""
|
||||
将当前管理员列表保存回 admin.json
|
||||
"""
|
||||
try:
|
||||
# 确保目录存在
|
||||
os.makedirs(os.path.dirname(self.data_file), exist_ok=True)
|
||||
# 将 set 转换为 list 以便 JSON 序列化
|
||||
admin_list = [str(admin_id) for admin_id in self._admins]
|
||||
with open(self.data_file, "w", encoding="utf-8") as f:
|
||||
json.dump({"admins": admin_list}, f, indent=2, ensure_ascii=False)
|
||||
logger.debug(f"管理员列表已保存到 {self.data_file}")
|
||||
except Exception as e:
|
||||
logger.error(f"保存 admin.json 失败: {e}")
|
||||
|
||||
async def _sync_to_redis(self):
|
||||
"""
|
||||
将内存中的管理员集合同步到 Redis
|
||||
"""
|
||||
from .redis_manager import redis_manager
|
||||
try:
|
||||
# 首先清空旧的集合
|
||||
await redis_manager.redis.delete(self._REDIS_KEY)
|
||||
if self._admins:
|
||||
# 将所有管理员ID添加到集合中
|
||||
await redis_manager.redis.sadd(self._REDIS_KEY, *self._admins)
|
||||
logger.debug(f"已将 {len(self._admins)} 位管理员同步到 Redis")
|
||||
except Exception as e:
|
||||
logger.error(f"同步管理员到 Redis 失败: {e}")
|
||||
|
||||
async def is_admin(self, user_id: int) -> bool:
|
||||
"""
|
||||
检查用户是否为管理员(从 Redis 缓存读取)
|
||||
"""
|
||||
from .redis_manager import redis_manager
|
||||
try:
|
||||
return await redis_manager.redis.sismember(self._REDIS_KEY, user_id)
|
||||
except Exception as e:
|
||||
logger.error(f"从 Redis 检查管理员权限失败: {e}")
|
||||
# Redis 失败时,回退到内存检查
|
||||
return user_id in self._admins
|
||||
|
||||
async def add_admin(self, user_id: int) -> bool:
|
||||
"""
|
||||
添加管理员,并同步到文件和 Redis
|
||||
"""
|
||||
from .redis_manager import redis_manager
|
||||
if user_id in self._admins:
|
||||
return False # 用户已经是管理员
|
||||
|
||||
self._admins.add(user_id)
|
||||
await self._save_to_file()
|
||||
try:
|
||||
await redis_manager.redis.sadd(self._REDIS_KEY, user_id)
|
||||
logger.info(f"已添加新管理员 {user_id} 并更新缓存")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"添加管理员 {user_id} 到 Redis 失败: {e}")
|
||||
return False
|
||||
|
||||
async def remove_admin(self, user_id: int) -> bool:
|
||||
"""
|
||||
移除管理员,并同步到文件和 Redis
|
||||
"""
|
||||
from .redis_manager import redis_manager
|
||||
if user_id not in self._admins:
|
||||
return False # 用户不是管理员
|
||||
|
||||
self._admins.remove(user_id)
|
||||
await self._save_to_file()
|
||||
try:
|
||||
await redis_manager.redis.srem(self._REDIS_KEY, user_id)
|
||||
logger.info(f"已移除管理员 {user_id} 并更新缓存")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"从 Redis 移除管理员 {user_id} 失败: {e}")
|
||||
return False
|
||||
|
||||
async def get_all_admins(self) -> Set[int]:
|
||||
"""
|
||||
获取所有管理员的集合
|
||||
"""
|
||||
return self._admins.copy()
|
||||
|
||||
|
||||
# 全局 AdminManager 实例
|
||||
admin_manager = AdminManager()
|
||||
@@ -8,7 +8,7 @@ import json
|
||||
from typing import Dict, Any
|
||||
from .base import BaseAPI
|
||||
from models.objects import LoginInfo, VersionInfo, Status
|
||||
from core.redis_manager import redis_client as redis_manager
|
||||
from core.redis_manager import redis_manager
|
||||
|
||||
|
||||
class AccountAPI(BaseAPI):
|
||||
|
||||
@@ -8,7 +8,7 @@ import json
|
||||
from typing import List, Dict, Any
|
||||
from .base import BaseAPI
|
||||
from models.objects import FriendInfo, StrangerInfo
|
||||
from core.redis_manager import redis_client as redis_manager
|
||||
from core.redis_manager import redis_manager
|
||||
|
||||
|
||||
class FriendAPI(BaseAPI):
|
||||
@@ -42,12 +42,12 @@ class FriendAPI(BaseAPI):
|
||||
"""
|
||||
cache_key = f"neobot:cache:get_stranger_info:{user_id}"
|
||||
if not no_cache:
|
||||
cached_data = await redis_manager.get(cache_key)
|
||||
cached_data = await redis_manager.redis.get(cache_key)
|
||||
if cached_data:
|
||||
return StrangerInfo(**json.loads(cached_data))
|
||||
|
||||
res = await self.call_api("get_stranger_info", {"user_id": user_id, "no_cache": no_cache})
|
||||
await redis_manager.set(cache_key, json.dumps(res), ex=3600) # 缓存 1 小时
|
||||
await redis_manager.redis.set(cache_key, json.dumps(res), ex=3600) # 缓存 1 小时
|
||||
return StrangerInfo(**res)
|
||||
|
||||
async def get_friend_list(self, no_cache: bool = False) -> List[FriendInfo]:
|
||||
@@ -62,12 +62,12 @@ class FriendAPI(BaseAPI):
|
||||
"""
|
||||
cache_key = f"neobot:cache:get_friend_list:{self.self_id}"
|
||||
if not no_cache:
|
||||
cached_data = await redis_manager.get(cache_key)
|
||||
cached_data = await redis_manager.redis.get(cache_key)
|
||||
if cached_data:
|
||||
return [FriendInfo(**item) for item in json.loads(cached_data)]
|
||||
|
||||
res = await self.call_api("get_friend_list")
|
||||
await redis_manager.set(cache_key, json.dumps(res), ex=3600) # 缓存 1 小时
|
||||
await redis_manager.redis.set(cache_key, json.dumps(res), ex=3600) # 缓存 1 小时
|
||||
return [FriendInfo(**item) for item in res]
|
||||
|
||||
async def set_friend_add_request(self, flag: str, approve: bool = True, remark: str = "") -> Dict[str, Any]:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"""
|
||||
from typing import List, Dict, Any, Optional
|
||||
import json
|
||||
from core.redis_manager import redis_client as redis_manager
|
||||
from core.redis_manager import redis_manager
|
||||
from .base import BaseAPI
|
||||
from models.objects import GroupInfo, GroupMemberInfo, GroupHonorInfo
|
||||
|
||||
@@ -178,12 +178,12 @@ class GroupAPI(BaseAPI):
|
||||
"""
|
||||
cache_key = f"neobot:cache:get_group_info:{group_id}"
|
||||
if not no_cache:
|
||||
cached_data = await redis_manager.get(cache_key)
|
||||
cached_data = await redis_manager.redis.get(cache_key)
|
||||
if cached_data:
|
||||
return GroupInfo(**json.loads(cached_data))
|
||||
|
||||
res = await self.call_api("get_group_info", {"group_id": group_id})
|
||||
await redis_manager.set(cache_key, json.dumps(res), ex=3600) # 缓存 1 小时
|
||||
await redis_manager.redis.set(cache_key, json.dumps(res), ex=3600) # 缓存 1 小时
|
||||
return GroupInfo(**res)
|
||||
|
||||
async def get_group_list(self) -> List[GroupInfo]:
|
||||
@@ -210,12 +210,12 @@ class GroupAPI(BaseAPI):
|
||||
"""
|
||||
cache_key = f"neobot:cache:get_group_member_info:{group_id}:{user_id}"
|
||||
if not no_cache:
|
||||
cached_data = await redis_manager.get(cache_key)
|
||||
cached_data = await redis_manager.redis.get(cache_key)
|
||||
if cached_data:
|
||||
return GroupMemberInfo(**json.loads(cached_data))
|
||||
|
||||
res = await self.call_api("get_group_member_info", {"group_id": group_id, "user_id": user_id})
|
||||
await redis_manager.set(cache_key, json.dumps(res), ex=3600) # 缓存 1 小时
|
||||
await redis_manager.redis.set(cache_key, json.dumps(res), ex=3600) # 缓存 1 小时
|
||||
return GroupMemberInfo(**res)
|
||||
|
||||
async def get_group_member_list(self, group_id: int) -> List[GroupMemberInfo]:
|
||||
|
||||
@@ -4,19 +4,12 @@
|
||||
该模块定义了 `CommandManager` 类,它是整个机器人框架事件处理的核心。
|
||||
它通过装饰器模式,为插件提供了注册消息指令、通知事件处理器和
|
||||
请求事件处理器的能力。
|
||||
|
||||
主要职责:
|
||||
- 提供 `@matcher.command()` 装饰器来注册命令。
|
||||
- 提供 `@matcher.on_notice()` 装饰器来注册通知处理器。
|
||||
- 提供 `@matcher.on_request()` 装饰器来注册请求处理器。
|
||||
- 负责解析收到的消息,匹配命令前缀并分发给对应的处理器。
|
||||
- 统一处理所有类型的事件,并将其分发给所有已注册的处理器。
|
||||
- 内置一个 `/help` 命令,用于展示所有已加载插件的帮助信息。
|
||||
"""
|
||||
import inspect
|
||||
from typing import Any, Callable, Dict, List, Tuple
|
||||
from typing import Any, Callable, Dict, Optional, Tuple
|
||||
|
||||
from .config_loader import global_config
|
||||
from .event_handler import MessageHandler, NoticeHandler, RequestHandler
|
||||
|
||||
|
||||
# 从配置中获取命令前缀
|
||||
comm_prefixes = global_config.bot.get("command", ("/",))
|
||||
@@ -27,6 +20,7 @@ class CommandManager:
|
||||
命令管理器,负责注册和分发所有类型的事件。
|
||||
|
||||
这是一个单例对象(`matcher`),在整个应用中共享。
|
||||
它将不同类型的事件处理委托给专门的处理器类。
|
||||
"""
|
||||
|
||||
def __init__(self, prefixes: Tuple[str, ...]):
|
||||
@@ -36,51 +30,91 @@ class CommandManager:
|
||||
Args:
|
||||
prefixes (Tuple[str, ...]): 一个包含所有合法命令前缀的元组。
|
||||
"""
|
||||
# --- 初始化所有处理器列表 ---
|
||||
self.prefixes = prefixes
|
||||
self.commands: Dict[str, Callable] = {}
|
||||
self.message_handlers: List[Callable] = []
|
||||
self.notice_handlers: List[Dict] = []
|
||||
self.request_handlers: List[Dict] = []
|
||||
self.plugins: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
# 初始化专门的事件处理器
|
||||
self.message_handler = MessageHandler(prefixes)
|
||||
self.notice_handler = NoticeHandler()
|
||||
self.request_handler = RequestHandler()
|
||||
|
||||
# --- 注册内置指令 ---
|
||||
self.commands["help"] = self._help_command
|
||||
# 将处理器映射到事件类型
|
||||
self.handler_map = {
|
||||
"message": self.message_handler,
|
||||
"notice": self.notice_handler,
|
||||
"request": self.request_handler,
|
||||
}
|
||||
|
||||
# 注册内置的 /help 命令
|
||||
self._register_internal_commands()
|
||||
|
||||
def _register_internal_commands(self):
|
||||
"""
|
||||
注册框架内置的命令
|
||||
"""
|
||||
# Help 命令
|
||||
self.message_handler.command("help")(self._help_command)
|
||||
self.plugins["core.help"] = {
|
||||
"name": "帮助",
|
||||
"description": "显示所有可用指令的帮助信息",
|
||||
"usage": "/help",
|
||||
}
|
||||
|
||||
# --- 装饰器代理 ---
|
||||
|
||||
def on_message(self) -> Callable:
|
||||
"""
|
||||
装饰器:用于注册一个通用的消息处理器。
|
||||
|
||||
被此装饰器注册的函数,会在每次收到消息时(在指令匹配前)被调用。
|
||||
如果函数返回 True,则表示该消息已被“消费”,后续的指令匹配将不会进行。
|
||||
|
||||
Example:
|
||||
@matcher.on_message()
|
||||
async def code_input_handler(bot, event):
|
||||
if is_waiting_for_code(event.user_id):
|
||||
await process_code(event.raw_message)
|
||||
return True # 消费事件
|
||||
装饰器:注册一个通用的消息处理器。
|
||||
"""
|
||||
def decorator(func: Callable) -> Callable:
|
||||
self.message_handlers.append(func)
|
||||
return func
|
||||
return decorator
|
||||
return self.message_handler.on_message()
|
||||
|
||||
def command(
|
||||
self,
|
||||
name: str,
|
||||
permission: Optional[Any] = None,
|
||||
override_permission_check: bool = False
|
||||
) -> Callable:
|
||||
"""
|
||||
装饰器:注册一个消息指令处理器。
|
||||
"""
|
||||
return self.message_handler.command(
|
||||
name,
|
||||
permission=permission,
|
||||
override_permission_check=override_permission_check
|
||||
)
|
||||
|
||||
def on_notice(self, notice_type: Optional[str] = None) -> Callable:
|
||||
"""
|
||||
装饰器:注册一个通知事件处理器。
|
||||
"""
|
||||
return self.notice_handler.register(notice_type=notice_type)
|
||||
|
||||
def on_request(self, request_type: Optional[str] = None) -> Callable:
|
||||
"""
|
||||
装饰器:注册一个请求事件处理器。
|
||||
"""
|
||||
return self.request_handler.register(request_type=request_type)
|
||||
|
||||
# --- 事件处理 ---
|
||||
|
||||
async def handle_event(self, bot, event):
|
||||
"""
|
||||
统一的事件分发入口。
|
||||
|
||||
根据事件的 `post_type` 将其分发给对应的处理器。
|
||||
"""
|
||||
if event.post_type == 'message' and global_config.bot.get('ignore_self_message', False):
|
||||
if hasattr(event, 'user_id') and hasattr(event, 'self_id') and event.user_id == event.self_id:
|
||||
return
|
||||
|
||||
handler = self.handler_map.get(event.post_type)
|
||||
if handler:
|
||||
await handler.handle(bot, event)
|
||||
|
||||
# --- 内置命令实现 ---
|
||||
|
||||
async def _help_command(self, bot, event):
|
||||
"""
|
||||
内置的 `/help` 命令的实现。
|
||||
|
||||
该命令会遍历所有已加载插件的元数据,并生成一段格式化的帮助文本。
|
||||
|
||||
Args:
|
||||
bot: Bot 实例。
|
||||
event: 消息事件对象。
|
||||
"""
|
||||
help_text = "--- 可用指令列表 ---\n"
|
||||
|
||||
@@ -95,187 +129,6 @@ class CommandManager:
|
||||
|
||||
await bot.send(event, help_text.strip())
|
||||
|
||||
def command(self, name: str) -> Callable:
|
||||
"""
|
||||
装饰器:用于注册一个消息指令处理器。
|
||||
|
||||
Example:
|
||||
@matcher.command("echo")
|
||||
async def handle_echo(bot, event, args):
|
||||
await bot.send(event, " ".join(args))
|
||||
|
||||
Args:
|
||||
name (str): 指令的名称(不包含命令前缀)。
|
||||
|
||||
Returns:
|
||||
Callable: 原函数,使其可以继续被调用。
|
||||
"""
|
||||
|
||||
def decorator(func: Callable) -> Callable:
|
||||
self.commands[name] = func
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
def on_notice(self, notice_type: str = None) -> Callable:
|
||||
"""
|
||||
装饰器:用于注册一个通知事件处理器。
|
||||
|
||||
如果 `notice_type` 未指定,则该处理器会接收所有类型的通知事件。
|
||||
|
||||
Args:
|
||||
notice_type (str, optional): 要处理的通知类型 (e.g., "group_increase")。
|
||||
Defaults to None.
|
||||
|
||||
Returns:
|
||||
Callable: 原函数。
|
||||
"""
|
||||
|
||||
def decorator(func: Callable) -> Callable:
|
||||
self.notice_handlers.append({"type": notice_type, "func": func})
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
def on_request(self, request_type: str = None) -> Callable:
|
||||
"""
|
||||
装饰器:用于注册一个请求事件处理器。
|
||||
|
||||
如果 `request_type` 未指定,则该处理器会接收所有类型的请求事件。
|
||||
|
||||
Args:
|
||||
request_type (str, optional): 要处理的请求类型 (e.g., "friend", "group")。
|
||||
Defaults to None.
|
||||
|
||||
Returns:
|
||||
Callable: 原函数。
|
||||
"""
|
||||
|
||||
def decorator(func: Callable) -> Callable:
|
||||
self.request_handlers.append({"type": request_type, "func": func})
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
async def handle_event(self, bot, event):
|
||||
"""
|
||||
统一的事件分发入口。
|
||||
|
||||
由 `WS` 客户端在接收到事件后调用。该方法会根据事件的 `post_type`
|
||||
将其分发给对应的具体处理方法。
|
||||
|
||||
Args:
|
||||
bot: Bot 实例。
|
||||
event: 已解析的事件对象。
|
||||
"""
|
||||
# --- 全局过滤机器人自身消息 ---
|
||||
# 仅对消息事件生效
|
||||
if event.post_type == 'message' and global_config.bot.get('ignore_self_message', False):
|
||||
|
||||
if hasattr(event, 'user_id') and hasattr(event, 'self_id') and event.user_id == event.self_id:
|
||||
return
|
||||
|
||||
post_type = event.post_type
|
||||
|
||||
if post_type == 'message':
|
||||
await self.handle_message(bot, event)
|
||||
elif post_type == 'notice':
|
||||
await self.handle_notice(bot, event)
|
||||
elif post_type == 'request':
|
||||
await self.handle_request(bot, event)
|
||||
|
||||
async def handle_message(self, bot, event):
|
||||
"""
|
||||
处理消息事件,优先执行通用处理器,然后解析并分发指令。
|
||||
"""
|
||||
# --- 1. 执行通用消息处理器 ---
|
||||
for handler in self.message_handlers:
|
||||
# 如果任何一个处理器返回 True,则中断后续处理
|
||||
consumed = await self._run_handler(handler, bot, event)
|
||||
if consumed:
|
||||
return
|
||||
|
||||
# --- 2. 检查并执行指令 ---
|
||||
if not event.raw_message:
|
||||
return
|
||||
|
||||
raw_text = event.raw_message.strip()
|
||||
|
||||
prefix_found = None
|
||||
for p in self.prefixes:
|
||||
if raw_text.startswith(p):
|
||||
prefix_found = p
|
||||
break
|
||||
|
||||
if not prefix_found:
|
||||
return
|
||||
|
||||
full_cmd = raw_text[len(prefix_found) :].split()
|
||||
if not full_cmd:
|
||||
return
|
||||
|
||||
cmd_name = full_cmd[0]
|
||||
args = full_cmd[1:]
|
||||
|
||||
if cmd_name in self.commands:
|
||||
func = self.commands[cmd_name]
|
||||
await self._run_handler(func, bot, event, args)
|
||||
|
||||
async def handle_notice(self, bot, event):
|
||||
"""
|
||||
分发通知事件给所有匹配的处理器。
|
||||
|
||||
Args:
|
||||
bot: Bot 实例。
|
||||
event: 通知事件对象。
|
||||
"""
|
||||
for handler in self.notice_handlers:
|
||||
if handler["type"] is None or handler["type"] == event.notice_type:
|
||||
await self._run_handler(handler["func"], bot, event)
|
||||
|
||||
async def handle_request(self, bot, event):
|
||||
"""
|
||||
分发请求事件给所有匹配的处理器。
|
||||
|
||||
Args:
|
||||
bot: Bot 实例。
|
||||
event: 请求事件对象。
|
||||
"""
|
||||
for handler in self.request_handlers:
|
||||
if handler["type"] is None or handler["type"] == event.request_type:
|
||||
await self._run_handler(handler["func"], bot, event)
|
||||
|
||||
async def _run_handler(self, func: Callable, bot, event, args: List[str] = None):
|
||||
"""
|
||||
智能执行事件处理器,并返回事件是否被消费。
|
||||
|
||||
该方法会检查目标处理器的函数签名,并根据签名动态地传入所需的参数
|
||||
(如 `bot`, `event`, `args`),实现了依赖注入。
|
||||
|
||||
Args:
|
||||
func (Callable): 目标处理器函数。
|
||||
bot: Bot 实例。
|
||||
event: 事件对象。
|
||||
args (List[str], optional): 指令参数列表(仅对消息事件有效)。
|
||||
|
||||
Returns:
|
||||
bool: 如果处理器函数返回 True,则返回 True,否则返回 False。
|
||||
"""
|
||||
sig = inspect.signature(func)
|
||||
params = sig.parameters
|
||||
kwargs = {}
|
||||
|
||||
if "bot" in params:
|
||||
kwargs["bot"] = bot
|
||||
if "event" in params:
|
||||
kwargs["event"] = event
|
||||
if "args" in params and args is not None:
|
||||
kwargs["args"] = args
|
||||
|
||||
# 执行函数并获取返回值
|
||||
result = await func(**kwargs)
|
||||
return result is True
|
||||
|
||||
|
||||
# --- 全局单例 ---
|
||||
|
||||
|
||||
197
core/event_handler.py
Normal file
197
core/event_handler.py
Normal file
@@ -0,0 +1,197 @@
|
||||
"""
|
||||
事件处理器模块
|
||||
|
||||
该模块定义了用于处理不同类型事件的处理器类。
|
||||
每个处理器都负责注册和分发特定类型的事件。
|
||||
"""
|
||||
import inspect
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
from .bot import Bot
|
||||
from .permission_manager import Permission, permission_manager
|
||||
from .exceptions import SyncHandlerError
|
||||
from .executor import run_in_thread_pool
|
||||
|
||||
|
||||
class BaseHandler(ABC):
|
||||
"""
|
||||
事件处理器抽象基类
|
||||
"""
|
||||
def __init__(self):
|
||||
self.handlers: List[Dict[str, Any]] = []
|
||||
|
||||
@abstractmethod
|
||||
async def handle(self, bot: Bot, event: Any):
|
||||
"""
|
||||
处理事件
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def _run_handler(
|
||||
self,
|
||||
func: Callable,
|
||||
bot: Bot,
|
||||
event: Any,
|
||||
args: Optional[List[str]] = None,
|
||||
permission_granted: Optional[bool] = None
|
||||
):
|
||||
"""
|
||||
智能执行事件处理器,并注入所需参数
|
||||
"""
|
||||
sig = inspect.signature(func)
|
||||
params = sig.parameters
|
||||
kwargs = {}
|
||||
|
||||
if "bot" in params:
|
||||
kwargs["bot"] = bot
|
||||
if "event" in params:
|
||||
kwargs["event"] = event
|
||||
if "args" in params and args is not None:
|
||||
kwargs["args"] = args
|
||||
if "permission_granted" in params and permission_granted is not None:
|
||||
kwargs["permission_granted"] = permission_granted
|
||||
|
||||
if inspect.iscoroutinefunction(func):
|
||||
result = await func(**kwargs)
|
||||
else:
|
||||
# 如果是同步函数,则放入线程池执行
|
||||
result = await run_in_thread_pool(func, **kwargs)
|
||||
return result is True
|
||||
|
||||
|
||||
class MessageHandler(BaseHandler):
|
||||
"""
|
||||
消息事件处理器
|
||||
"""
|
||||
def __init__(self, prefixes: Tuple[str, ...]):
|
||||
super().__init__()
|
||||
self.prefixes = prefixes
|
||||
self.commands: Dict[str, Dict] = {}
|
||||
self.message_handlers: List[Callable] = []
|
||||
|
||||
def on_message(self) -> Callable:
|
||||
"""
|
||||
注册通用消息处理器
|
||||
"""
|
||||
def decorator(func: Callable) -> Callable:
|
||||
if not inspect.iscoroutinefunction(func):
|
||||
raise SyncHandlerError(f"消息处理器 {func.__name__} 必须是异步函数 (async def).")
|
||||
self.message_handlers.append(func)
|
||||
return func
|
||||
return decorator
|
||||
|
||||
def command(
|
||||
self,
|
||||
name: str,
|
||||
permission: Optional[Permission] = None,
|
||||
override_permission_check: bool = False
|
||||
) -> Callable:
|
||||
"""
|
||||
注册命令处理器
|
||||
"""
|
||||
def decorator(func: Callable) -> Callable:
|
||||
if not inspect.iscoroutinefunction(func):
|
||||
raise SyncHandlerError(f"命令处理器 {func.__name__} 必须是异步函数 (async def).")
|
||||
self.commands[name] = {
|
||||
"func": func,
|
||||
"permission": permission,
|
||||
"override_permission_check": override_permission_check,
|
||||
}
|
||||
return func
|
||||
return decorator
|
||||
|
||||
async def handle(self, bot: Bot, event: Any):
|
||||
"""
|
||||
处理消息事件,包括通用消息和命令
|
||||
"""
|
||||
for handler in self.message_handlers:
|
||||
consumed = await self._run_handler(handler, bot, event)
|
||||
if consumed:
|
||||
return
|
||||
|
||||
if not event.raw_message:
|
||||
return
|
||||
|
||||
raw_text = event.raw_message.strip()
|
||||
prefix_found = next((p for p in self.prefixes if raw_text.startswith(p)), None)
|
||||
|
||||
if not prefix_found:
|
||||
return
|
||||
|
||||
full_cmd = raw_text[len(prefix_found):].split()
|
||||
if not full_cmd:
|
||||
return
|
||||
|
||||
cmd_name = full_cmd[0]
|
||||
args = full_cmd[1:]
|
||||
|
||||
if cmd_name in self.commands:
|
||||
command_info = self.commands[cmd_name]
|
||||
func = command_info["func"]
|
||||
permission = command_info.get("permission")
|
||||
override_check = command_info.get("override_permission_check", False)
|
||||
|
||||
permission_granted = True
|
||||
if permission:
|
||||
permission_granted = await permission_manager.check_permission(event.user_id, permission)
|
||||
|
||||
if not permission_granted and not override_check:
|
||||
await bot.send(event, f"权限不足,需要 {permission.name} 权限")
|
||||
return
|
||||
|
||||
await self._run_handler(
|
||||
func,
|
||||
bot,
|
||||
event,
|
||||
args=args,
|
||||
permission_granted=permission_granted
|
||||
)
|
||||
|
||||
|
||||
class NoticeHandler(BaseHandler):
|
||||
"""
|
||||
通知事件处理器
|
||||
"""
|
||||
def register(self, notice_type: Optional[str] = None) -> Callable:
|
||||
"""
|
||||
注册通知处理器
|
||||
"""
|
||||
def decorator(func: Callable) -> Callable:
|
||||
if not inspect.iscoroutinefunction(func):
|
||||
raise SyncHandlerError(f"通知处理器 {func.__name__} 必须是异步函数 (async def).")
|
||||
self.handlers.append({"type": notice_type, "func": func})
|
||||
return func
|
||||
return decorator
|
||||
|
||||
async def handle(self, bot: Bot, event: Any):
|
||||
"""
|
||||
处理通知事件
|
||||
"""
|
||||
for handler in self.handlers:
|
||||
if handler["type"] is None or handler["type"] == event.notice_type:
|
||||
await self._run_handler(handler["func"], bot, event)
|
||||
|
||||
|
||||
class RequestHandler(BaseHandler):
|
||||
"""
|
||||
请求事件处理器
|
||||
"""
|
||||
def register(self, request_type: Optional[str] = None) -> Callable:
|
||||
"""
|
||||
注册请求处理器
|
||||
"""
|
||||
def decorator(func: Callable) -> Callable:
|
||||
if not inspect.iscoroutinefunction(func):
|
||||
raise SyncHandlerError(f"请求处理器 {func.__name__} 必须是异步函数 (async def).")
|
||||
self.handlers.append({"type": request_type, "func": func})
|
||||
return func
|
||||
return decorator
|
||||
|
||||
async def handle(self, bot: Bot, event: Any):
|
||||
"""
|
||||
处理请求事件
|
||||
"""
|
||||
for handler in self.handlers:
|
||||
if handler["type"] is None or handler["type"] == event.request_type:
|
||||
await self._run_handler(handler["func"], bot, event)
|
||||
9
core/exceptions.py
Normal file
9
core/exceptions.py
Normal file
@@ -0,0 +1,9 @@
|
||||
"""
|
||||
自定义异常模块
|
||||
"""
|
||||
|
||||
class SyncHandlerError(Exception):
|
||||
"""
|
||||
当尝试注册同步函数作为异步事件处理器时抛出此异常。
|
||||
"""
|
||||
pass
|
||||
27
core/executor.py
Normal file
27
core/executor.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""
|
||||
线程池执行器
|
||||
|
||||
提供一个全局的线程池和异步接口,用于在事件循环中安全地运行同步函数。
|
||||
"""
|
||||
import asyncio
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from functools import partial
|
||||
from typing import Any, Callable
|
||||
|
||||
# 创建一个全局的线程池,可以根据需要调整 max_workers
|
||||
executor = ThreadPoolExecutor(max_workers=10)
|
||||
|
||||
async def run_in_thread_pool(func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
|
||||
"""
|
||||
在线程池中异步运行同步函数
|
||||
|
||||
:param func: 要运行的同步函数
|
||||
:param args: 函数的位置参数
|
||||
:param kwargs: 函数的关键字参数
|
||||
:return: 函数的返回值
|
||||
"""
|
||||
loop = asyncio.get_running_loop()
|
||||
# 使用 functools.partial 绑定函数和参数,以便传递给 run_in_executor
|
||||
func_to_run = partial(func, *args, **kwargs)
|
||||
# loop.run_in_executor 会返回一个 awaitable 对象
|
||||
return await loop.run_in_executor(executor, func_to_run)
|
||||
252
core/permission_manager.py
Normal file
252
core/permission_manager.py
Normal file
@@ -0,0 +1,252 @@
|
||||
"""
|
||||
权限管理器模块
|
||||
|
||||
该模块负责管理用户权限,支持 admin、op、user 三个权限级别。
|
||||
权限数据存储在 `permissions.json` 文件中,格式为:
|
||||
{
|
||||
"users": {
|
||||
"123456": "admin",
|
||||
"789012": "op",
|
||||
"345678": "user"
|
||||
}
|
||||
}
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
from functools import total_ordering
|
||||
from typing import Dict
|
||||
|
||||
from .logger import logger
|
||||
from .admin_manager import admin_manager # 导入 AdminManager
|
||||
|
||||
|
||||
@total_ordering
|
||||
class Permission:
|
||||
"""
|
||||
权限封装类
|
||||
|
||||
封装了权限的名称和等级,并提供了比较方法。
|
||||
使用 @total_ordering 装饰器可以自动生成所有的比较运算符。
|
||||
"""
|
||||
def __init__(self, name: str, level: int):
|
||||
"""
|
||||
初始化权限对象
|
||||
|
||||
Args:
|
||||
name (str): 权限名称 (e.g., "admin", "op")
|
||||
level (int): 权限等级,数字越大权限越高
|
||||
"""
|
||||
self.name = name
|
||||
self.level = level
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
判断权限是否相等
|
||||
"""
|
||||
if not isinstance(other, Permission):
|
||||
return NotImplemented
|
||||
return self.level == other.level
|
||||
|
||||
def __lt__(self, other):
|
||||
"""
|
||||
判断权限是否小于另一个权限
|
||||
"""
|
||||
if not isinstance(other, Permission):
|
||||
return NotImplemented
|
||||
return self.level < other.level
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
返回权限的字符串表示(即权限名称)
|
||||
"""
|
||||
return self.name
|
||||
|
||||
|
||||
# 定义全局权限常量
|
||||
ADMIN = Permission("admin", 3)
|
||||
OP = Permission("op", 2)
|
||||
USER = Permission("user", 1)
|
||||
|
||||
# 用于从字符串名称查找权限对象的字典
|
||||
_PERMISSIONS: Dict[str, Permission] = {
|
||||
p.name: p for p in [ADMIN, OP, USER]
|
||||
}
|
||||
|
||||
|
||||
class PermissionManager:
|
||||
"""
|
||||
权限管理器类
|
||||
|
||||
负责加载、保存和查询用户权限数据。
|
||||
使用单例模式,确保全局只有一个权限管理器实例。
|
||||
"""
|
||||
|
||||
_instance = None
|
||||
|
||||
def __new__(cls):
|
||||
"""
|
||||
单例模式实现
|
||||
|
||||
Returns:
|
||||
PermissionManager: 全局唯一的权限管理器实例
|
||||
"""
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance._initialized = False
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
初始化权限管理器
|
||||
|
||||
如果已经初始化过,则直接返回。
|
||||
"""
|
||||
if getattr(self, "_initialized", False):
|
||||
return
|
||||
|
||||
# 权限数据文件路径
|
||||
self.data_file = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"..",
|
||||
"data",
|
||||
"permissions.json"
|
||||
)
|
||||
|
||||
# 确保数据目录存在
|
||||
data_dir = os.path.dirname(self.data_file)
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
|
||||
# 权限数据存储结构:{"users": {"user_id": "level_name"}}
|
||||
self._data: Dict[str, Dict[str, str]] = {"users": {}}
|
||||
|
||||
# 加载现有数据
|
||||
self.load()
|
||||
|
||||
self._initialized = True
|
||||
logger.info("权限管理器初始化完成")
|
||||
|
||||
def load(self) -> None:
|
||||
"""
|
||||
从文件加载权限数据
|
||||
|
||||
如果文件不存在,则创建空文件并初始化默认数据结构。
|
||||
"""
|
||||
try:
|
||||
if os.path.exists(self.data_file):
|
||||
with open(self.data_file, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
# 兼容旧格式
|
||||
if "users" in data:
|
||||
self._data["users"] = data["users"]
|
||||
else:
|
||||
self._data["users"] = {}
|
||||
logger.debug(f"权限数据已从 {self.data_file} 加载")
|
||||
else:
|
||||
# 文件不存在,创建空文件
|
||||
self.save()
|
||||
logger.debug(f"创建空的权限数据文件: {self.data_file}")
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"权限数据文件格式错误: {e}")
|
||||
# 文件损坏,重置为空数据
|
||||
self._data["users"] = {}
|
||||
self.save()
|
||||
except Exception as e:
|
||||
logger.error(f"加载权限数据失败: {e}")
|
||||
self._data["users"] = {}
|
||||
|
||||
def save(self) -> None:
|
||||
"""
|
||||
将权限数据保存到文件
|
||||
"""
|
||||
try:
|
||||
with open(self.data_file, "w", encoding="utf-8") as f:
|
||||
json.dump(self._data, f, indent=2, ensure_ascii=False)
|
||||
logger.debug(f"权限数据已保存到 {self.data_file}")
|
||||
except Exception as e:
|
||||
logger.error(f"保存权限数据失败: {e}")
|
||||
|
||||
async def get_user_permission(self, user_id: int) -> Permission:
|
||||
"""
|
||||
获取指定用户的权限对象
|
||||
|
||||
Args:
|
||||
user_id (int): 用户 QQ 号
|
||||
|
||||
Returns:
|
||||
Permission: 用户的权限对象,如果用户不存在则返回默认级别 USER
|
||||
"""
|
||||
# 首先,通过 AdminManager 检查是否为管理员
|
||||
if await admin_manager.is_admin(user_id):
|
||||
return ADMIN
|
||||
|
||||
# 如果不是管理员,则从 permissions.json 中查找
|
||||
user_id_str = str(user_id)
|
||||
level_name = self._data["users"].get(user_id_str, USER.name)
|
||||
return _PERMISSIONS.get(level_name, USER)
|
||||
|
||||
def set_user_permission(self, user_id: int, permission: Permission) -> None:
|
||||
"""
|
||||
设置指定用户的权限级别
|
||||
|
||||
Args:
|
||||
user_id (int): 用户 QQ 号
|
||||
permission (Permission): 权限对象
|
||||
|
||||
Raises:
|
||||
ValueError: 如果权限对象无效
|
||||
"""
|
||||
if not isinstance(permission, Permission) or permission.name not in _PERMISSIONS:
|
||||
raise ValueError(f"无效的权限对象: {permission}")
|
||||
|
||||
user_id_str = str(user_id)
|
||||
self._data["users"][user_id_str] = permission.name
|
||||
self.save()
|
||||
logger.info(f"设置用户 {user_id} 的权限级别为 {permission.name}")
|
||||
|
||||
def remove_user(self, user_id: int) -> None:
|
||||
"""
|
||||
移除指定用户的权限设置,恢复为默认级别
|
||||
|
||||
Args:
|
||||
user_id (int): 用户 QQ 号
|
||||
"""
|
||||
user_id_str = str(user_id)
|
||||
if user_id_str in self._data["users"]:
|
||||
del self._data["users"][user_id_str]
|
||||
self.save()
|
||||
logger.info(f"移除用户 {user_id} 的权限设置")
|
||||
|
||||
async def check_permission(self, user_id: int, required_permission: Permission) -> bool:
|
||||
"""
|
||||
检查用户是否具有指定权限级别
|
||||
|
||||
Args:
|
||||
user_id (int): 用户 QQ 号
|
||||
required_permission (Permission): 所需的权限对象
|
||||
|
||||
Returns:
|
||||
bool: 如果用户权限 >= 所需权限,返回 True,否则返回 False
|
||||
"""
|
||||
user_permission = await self.get_user_permission(user_id)
|
||||
return user_permission >= required_permission
|
||||
|
||||
def get_all_users(self) -> Dict[str, str]:
|
||||
"""
|
||||
获取所有设置了权限的用户及其级别名称
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: 用户ID到权限级别名称的映射
|
||||
"""
|
||||
return self._data["users"].copy()
|
||||
|
||||
def clear_all(self) -> None:
|
||||
"""
|
||||
清空所有权限设置
|
||||
"""
|
||||
self._data["users"].clear()
|
||||
self.save()
|
||||
logger.info("已清空所有权限设置")
|
||||
|
||||
|
||||
# 全局权限管理器实例
|
||||
permission_manager = PermissionManager()
|
||||
@@ -11,7 +11,9 @@ import pkgutil
|
||||
import sys
|
||||
|
||||
from core.command_manager import matcher
|
||||
from core.exceptions import SyncHandlerError
|
||||
from .logger import logger
|
||||
from .executor import run_in_thread_pool
|
||||
|
||||
|
||||
def load_all_plugins():
|
||||
@@ -49,6 +51,8 @@ def load_all_plugins():
|
||||
|
||||
type_str = "包" if is_pkg else "文件"
|
||||
logger.success(f" [{type_str}] 成功{action}: {module_name}")
|
||||
except SyncHandlerError as e:
|
||||
logger.error(f" 插件 {module_name} 加载失败: {e} (跳过此插件)")
|
||||
except Exception as e:
|
||||
print(
|
||||
f" {action if 'action' in locals() else '加载'}插件 {module_name} 失败: {e}"
|
||||
@@ -75,50 +79,48 @@ class PluginDataManager:
|
||||
self.plugin_name + ".json",
|
||||
)
|
||||
self.data = {}
|
||||
self.load()
|
||||
|
||||
def load(self):
|
||||
async def load(self):
|
||||
"""读取配置文件"""
|
||||
if not os.path.exists(self.data_file):
|
||||
with open(self.data_file, "w", encoding="utf-8") as f:
|
||||
self.set(self.plugin_name, [])
|
||||
await self.set(self.plugin_name, [])
|
||||
try:
|
||||
with open(self.data_file, "r", encoding="utf-8") as f:
|
||||
self.data = json.load(f)
|
||||
self.data = await run_in_thread_pool(json.load, f)
|
||||
except json.JSONDecodeError:
|
||||
self.data = {}
|
||||
|
||||
def save(self):
|
||||
async def save(self):
|
||||
"""保存配置到文件"""
|
||||
with open(self.data_file, "w", encoding="utf-8") as f:
|
||||
json.dump(self.data, f, indent=2, ensure_ascii=False)
|
||||
await run_in_thread_pool(json.dump, self.data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
def get(self, key, default=None):
|
||||
"""获取配置项"""
|
||||
return self.data.get(key, default)
|
||||
|
||||
def set(self, key, value):
|
||||
async def set(self, key, value):
|
||||
"""设置配置项"""
|
||||
self.data[key] = value
|
||||
self.save()
|
||||
await self.save()
|
||||
|
||||
def add(self, key, value):
|
||||
async def add(self, key, value):
|
||||
"""添加配置项"""
|
||||
if key not in self.data:
|
||||
self.data[key] = []
|
||||
self.data[key].append(value)
|
||||
self.save()
|
||||
await self.save()
|
||||
|
||||
def remove(self, key):
|
||||
async def remove(self, key):
|
||||
"""删除配置项"""
|
||||
if key in self.data:
|
||||
del self.data[key]
|
||||
self.save()
|
||||
await self.save()
|
||||
|
||||
def clear(self):
|
||||
async def clear(self):
|
||||
"""清空所有配置"""
|
||||
self.data.clear()
|
||||
self.save()
|
||||
await self.save()
|
||||
|
||||
def get_all(self):
|
||||
return self.data.copy()
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
import redis
|
||||
import redis.asyncio as redis
|
||||
from .config_loader import global_config as config
|
||||
from .logger import logger
|
||||
|
||||
class RedisManager:
|
||||
"""
|
||||
Redis 连接管理器
|
||||
Redis 连接管理器(异步单例)
|
||||
"""
|
||||
_pool = None
|
||||
_client = None
|
||||
_instance = None
|
||||
_redis = None
|
||||
|
||||
@classmethod
|
||||
def initialize(cls):
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
async def initialize(self):
|
||||
"""
|
||||
初始化 Redis 连接并进行健康检查
|
||||
异步初始化 Redis 连接并进行健康检查
|
||||
"""
|
||||
if cls._pool is None:
|
||||
if self._redis is None:
|
||||
try:
|
||||
host = config.redis['host']
|
||||
port = config.redis['port']
|
||||
@@ -23,39 +27,32 @@ class RedisManager:
|
||||
|
||||
logger.info(f"正在尝试连接 Redis: {host}:{port}, DB: {db}")
|
||||
|
||||
cls._pool = redis.ConnectionPool(
|
||||
self._redis = redis.Redis(
|
||||
host=host,
|
||||
port=port,
|
||||
db=db,
|
||||
password=password,
|
||||
decode_responses=True
|
||||
)
|
||||
cls._client = redis.Redis(connection_pool=cls._pool)
|
||||
if cls._client.ping():
|
||||
if await self._redis.ping():
|
||||
logger.success("Redis 连接成功!")
|
||||
else:
|
||||
logger.error("Redis 连接失败: PING 命令无响应")
|
||||
except redis.exceptions.ConnectionError as e:
|
||||
logger.error(f"Redis 连接失败: {e}")
|
||||
cls._pool = None
|
||||
cls._client = None
|
||||
self._redis = None
|
||||
except Exception as e:
|
||||
logger.exception(f"Redis 初始化时发生未知错误: {e}")
|
||||
cls._pool = None
|
||||
cls._client = None
|
||||
self._redis = None
|
||||
|
||||
@classmethod
|
||||
def get_redis(cls):
|
||||
@property
|
||||
def redis(self):
|
||||
"""
|
||||
获取 Redis 连接
|
||||
|
||||
:return: Redis 连接实例
|
||||
获取 Redis 连接实例
|
||||
"""
|
||||
if cls._client is None:
|
||||
# 理论上 initialize 应该在程序启动时被调用,这里作为备用
|
||||
cls.initialize()
|
||||
return cls._client
|
||||
if self._redis is None:
|
||||
raise ConnectionError("Redis 未初始化或连接失败,请先调用 initialize()")
|
||||
return self._redis
|
||||
|
||||
# 在模块加载时直接初始化
|
||||
RedisManager.initialize()
|
||||
redis_client = RedisManager.get_redis()
|
||||
# 全局 Redis 管理器实例
|
||||
redis_manager = RedisManager()
|
||||
|
||||
Reference in New Issue
Block a user