本次提交对核心模块进行了深度重构,引入 Pydantic 增强配置管理的类型安全性,并全面优化了插件管理系统。 主要变更详情: 1. 核心架构与配置 - 重构配置加载模块:引入 Pydantic 模型 (`core/config_models.py`),提供严格的配置项类型检查、验证及默认值管理。 - 统一模块结构:规范化模块导入路径,移除冗余的 `__init__.py` 文件,提升项目结构的清晰度。 - 性能优化:集成 Redis 缓存支持 (`RedisManager`),有效降低高频 API 调用开销,提升响应速度。 2. 插件系统升级 - 实现热重载机制:新增插件文件变更监听功能,支持开发过程中自动重载插件,提升开发效率。 - 优化生命周期管理:改进插件加载与卸载逻辑,支持精确卸载指定插件及其关联的命令、事件处理器和定时任务。 3. 功能特性增强 - 新增媒体 API:引入 `MediaAPI` 模块,封装图片、语音等富媒体资源的获取与处理接口。 - 完善权限体系:重构权限管理系统,实现管理员与操作员的分级控制,支持更细粒度的命令权限校验。 4. 代码质量与稳定性 - 全面类型修复:解决 `mypy` 静态类型检查发现的大量类型错误(包括 `CommandManager`、`EventFactory` 及 `Bot` API 签名不匹配问题)。 - 增强错误处理:优化消息处理管道的异常捕获机制,完善关键路径的日志记录,提升系统运行稳定性。
117 lines
4.0 KiB
Python
117 lines
4.0 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
管理员专用的广播插件
|
||
功能:
|
||
- 仅限管理员在私聊中调用。
|
||
- 通过回复一条消息并发送指令,将该消息转发给机器人所在的所有群聊。
|
||
- 此插件不写入 __plugin_meta__,保持隐藏。
|
||
"""
|
||
import asyncio
|
||
from core.managers.command_manager import matcher
|
||
from models.events.message import MessageEvent, PrivateMessageEvent
|
||
from core.permission import Permission
|
||
from core.utils.logger import logger
|
||
|
||
# --- 会话状态管理 ---
|
||
# 结构: {user_id: asyncio.TimerHandle}
|
||
broadcast_sessions: dict[int, asyncio.TimerHandle] = {}
|
||
|
||
def cleanup_session(user_id: int):
|
||
"""
|
||
清理超时的广播会话。
|
||
"""
|
||
if user_id in broadcast_sessions:
|
||
del broadcast_sessions[user_id]
|
||
logger.info(f"[Broadcast] 会话 {user_id} 已超时,自动取消。")
|
||
|
||
@matcher.command("broadcast", "广播", permission=Permission.ADMIN)
|
||
async def broadcast_start(event: MessageEvent):
|
||
"""
|
||
广播指令的入口,启动一个等待用户消息的会话。
|
||
"""
|
||
# 1. 仅限私聊
|
||
if not isinstance(event, PrivateMessageEvent):
|
||
return
|
||
|
||
user_id = event.user_id
|
||
|
||
# 如果上一个会话的超时任务还在,先取消它
|
||
if user_id in broadcast_sessions:
|
||
broadcast_sessions[user_id].cancel()
|
||
|
||
await event.reply("已进入广播模式,请在 60 秒内发送您想要广播的消息内容。")
|
||
|
||
# 设置 60 秒超时
|
||
loop = asyncio.get_running_loop()
|
||
timeout_handler = loop.call_later(
|
||
60,
|
||
cleanup_session,
|
||
user_id
|
||
)
|
||
broadcast_sessions[user_id] = timeout_handler
|
||
|
||
@matcher.on_message()
|
||
async def handle_broadcast_content(event: MessageEvent):
|
||
"""
|
||
通用消息处理器,用于捕获广播模式下的消息输入。
|
||
将捕获到的消息打包成一个新的合并转发消息并广播。
|
||
"""
|
||
# 仅处理私聊消息,且用户在广播会话中
|
||
if not isinstance(event, PrivateMessageEvent) or event.user_id not in broadcast_sessions:
|
||
return
|
||
|
||
user_id = event.user_id
|
||
|
||
# 成功捕获到消息,取消超时任务并清理会话
|
||
broadcast_sessions[user_id].cancel()
|
||
del broadcast_sessions[user_id]
|
||
|
||
message_to_broadcast = event.message
|
||
if not message_to_broadcast:
|
||
await event.reply("捕获到的消息为空,已取消广播。")
|
||
return True
|
||
|
||
# --- 执行广播逻辑 ---
|
||
bot = event.bot
|
||
try:
|
||
group_list = await bot.get_group_list()
|
||
if not group_list:
|
||
await event.reply("机器人目前没有加入任何群聊。")
|
||
return True
|
||
except Exception as e:
|
||
logger.error(f"[Broadcast] 获取群聊列表失败: {e}")
|
||
await event.reply(f"获取群聊列表时发生错误: {e}")
|
||
return True
|
||
|
||
success_count, failed_count = 0, 0
|
||
total_groups = len(group_list)
|
||
await event.reply(f"已收到广播内容,准备打包并向 {total_groups} 个群聊广播...")
|
||
|
||
# --- 将管理员发送的消息打包成一个单节点的合并转发消息 ---
|
||
try:
|
||
nodes_to_send = [
|
||
bot.build_forward_node(
|
||
user_id=event.user_id,
|
||
nickname=event.sender.nickname if event.sender else "未知用户",
|
||
message=message_to_broadcast
|
||
)
|
||
]
|
||
except Exception as e:
|
||
logger.error(f"[Broadcast] 构建转发节点失败: {e}")
|
||
await event.reply(f"构建转发消息节点时发生错误: {e}")
|
||
return True
|
||
|
||
# --- 向所有群聊发送打包好的合并转发消息 ---
|
||
for group in group_list:
|
||
try:
|
||
await bot.send_group_forward_msg(group.group_id, nodes_to_send)
|
||
success_count += 1
|
||
except Exception as e:
|
||
failed_count += 1
|
||
logger.error(f"[Broadcast] 转发至群聊 {group.group_id} 失败: {e}")
|
||
|
||
report = f"广播完成。\n总群聊: {total_groups}\n成功: {success_count}\n失败: {failed_count}"
|
||
await event.reply(report)
|
||
|
||
return True # 消费事件,防止其他处理器响应
|