feat: 添加多线程架构支持并优化性能
实现线程管理器以支持高并发场景,添加GIL-free模式提升Python 3.14下的多线程性能 新增B站API集成和本地文件服务器功能,改进镜像插件支持GIF处理 更新文档说明多线程架构和GIL-free模式的使用方法
This commit is contained in:
@@ -4,18 +4,25 @@
|
||||
功能:
|
||||
- 仅限管理员在私聊中调用。
|
||||
- 通过回复一条消息并发送指令,将该消息转发给机器人所在的所有群聊。
|
||||
- 此插件不写入 __plugin_meta__,保持隐藏。
|
||||
- 支持跨机器人广播:当任意机器人接收到广播消息时,会通过 Redis 发布消息,
|
||||
所有其他机器人订阅后也会转发给它们各自的群聊。
|
||||
- 使用通用消息格式,不使用合并转发(聊天记录)格式。
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
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
|
||||
from core.managers.redis_manager import redis_manager
|
||||
|
||||
# --- 会话状态管理 ---
|
||||
# 结构: {user_id: asyncio.TimerHandle}
|
||||
broadcast_sessions: dict[int, asyncio.TimerHandle] = {}
|
||||
|
||||
# 广播消息订阅任务
|
||||
_broadcast_subscription_task = None
|
||||
|
||||
def cleanup_session(user_id: int):
|
||||
"""
|
||||
清理超时的广播会话。
|
||||
@@ -24,6 +31,103 @@ def cleanup_session(user_id: int):
|
||||
del broadcast_sessions[user_id]
|
||||
logger.info(f"[Broadcast] 会话 {user_id} 已超时,自动取消。")
|
||||
|
||||
|
||||
async def broadcast_message_to_groups(bot, message, source_robot_id: str = "unknown"):
|
||||
"""
|
||||
将消息广播到所有群聊
|
||||
|
||||
Args:
|
||||
bot: 机器人实例
|
||||
message: 要发送的消息
|
||||
source_robot_id: 消息来源机器人ID(用于日志)
|
||||
"""
|
||||
try:
|
||||
group_list = await bot.get_group_list()
|
||||
if not group_list:
|
||||
logger.warning(f"[Broadcast] 机器人 {source_robot_id} 目前没有加入任何群聊")
|
||||
return
|
||||
|
||||
success_count, failed_count = 0, 0
|
||||
total_groups = len(group_list)
|
||||
|
||||
for group in group_list:
|
||||
try:
|
||||
await bot.send_group_msg(group.group_id, message)
|
||||
success_count += 1
|
||||
except Exception as e:
|
||||
failed_count += 1
|
||||
logger.error(f"[Broadcast] 机器人 {source_robot_id} 发送至群聊 {group.group_id} 失败: {e}")
|
||||
|
||||
logger.success(f"[Broadcast] 机器人 {source_robot_id} 广播完成: {total_groups} 个群聊, 成功 {success_count}, 失败 {failed_count}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[Broadcast] 机器人 {source_robot_id} 获取群聊列表失败: {e}")
|
||||
|
||||
|
||||
async def start_broadcast_subscription():
|
||||
"""
|
||||
启动 Redis 广播消息订阅
|
||||
"""
|
||||
global _broadcast_subscription_task
|
||||
|
||||
if _broadcast_subscription_task is None:
|
||||
_broadcast_subscription_task = asyncio.create_task(broadcast_subscription_loop())
|
||||
logger.success("[Broadcast] Redis 广播订阅已启动")
|
||||
|
||||
|
||||
async def stop_broadcast_subscription():
|
||||
"""
|
||||
停止 Redis 广播消息订阅
|
||||
"""
|
||||
global _broadcast_subscription_task
|
||||
|
||||
if _broadcast_subscription_task:
|
||||
_broadcast_subscription_task.cancel()
|
||||
try:
|
||||
await _broadcast_subscription_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
_broadcast_subscription_task = None
|
||||
logger.info("[Broadcast] Redis 广播订阅已停止")
|
||||
|
||||
|
||||
async def broadcast_subscription_loop():
|
||||
"""
|
||||
Redis 广播消息订阅循环
|
||||
"""
|
||||
if redis_manager.redis is None:
|
||||
logger.warning("[Broadcast] Redis 未初始化,无法启动广播订阅")
|
||||
return
|
||||
|
||||
try:
|
||||
pubsub = redis_manager.redis.pubsub()
|
||||
await pubsub.subscribe("neobot_broadcast")
|
||||
|
||||
logger.success("[Broadcast] 已订阅 Redis 广播频道")
|
||||
|
||||
async for message in pubsub.listen():
|
||||
if message["type"] == "message":
|
||||
try:
|
||||
data = json.loads(message["data"])
|
||||
robot_id = data.get("robot_id", "unknown")
|
||||
message_data = data.get("message")
|
||||
|
||||
logger.info(f"[Broadcast] 收到跨机器人广播消息: 来源 {robot_id}")
|
||||
|
||||
# 获取当前机器人的实例
|
||||
from core.ws import WS
|
||||
if WS.instance:
|
||||
await broadcast_message_to_groups(WS.instance, message_data, robot_id)
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"[Broadcast] 解析广播消息失败: {e}")
|
||||
except Exception as e:
|
||||
logger.error(f"[Broadcast] 处理广播消息失败: {e}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[Broadcast] 广播订阅循环异常: {e}")
|
||||
|
||||
|
||||
@matcher.command("broadcast", "广播", permission=Permission.ADMIN)
|
||||
async def broadcast_start(event: MessageEvent):
|
||||
"""
|
||||
@@ -49,12 +153,15 @@ async def broadcast_start(event: MessageEvent):
|
||||
user_id
|
||||
)
|
||||
broadcast_sessions[user_id] = timeout_handler
|
||||
|
||||
# 确保广播订阅已启动
|
||||
await start_broadcast_subscription()
|
||||
|
||||
@matcher.on_message()
|
||||
async def handle_broadcast_content(event: MessageEvent):
|
||||
"""
|
||||
通用消息处理器,用于捕获广播模式下的消息输入。
|
||||
将捕获到的消息打包成一个新的合并转发消息并广播。
|
||||
将捕获到的消息直接发送给机器人所在的所有群聊,并通过 Redis 发布给其他机器人。
|
||||
"""
|
||||
# 仅处理私聊消息,且用户在广播会话中
|
||||
if not isinstance(event, PrivateMessageEvent) or event.user_id not in broadcast_sessions:
|
||||
@@ -71,46 +178,27 @@ async def handle_broadcast_content(event: MessageEvent):
|
||||
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}")
|
||||
# 获取当前机器人ID(使用反向WS的机器人ID)
|
||||
from core.ws import WS
|
||||
robot_id = "unknown"
|
||||
if WS.instance and hasattr(WS.instance, 'self_id'):
|
||||
robot_id = str(WS.instance.self_id)
|
||||
|
||||
report = f"广播完成。\n总群聊: {total_groups}\n成功: {success_count}\n失败: {failed_count}"
|
||||
await event.reply(report)
|
||||
# --- 执行本地广播 ---
|
||||
await broadcast_message_to_groups(event.bot, message_to_broadcast, robot_id)
|
||||
|
||||
# --- 通过 Redis 发布消息给其他机器人 ---
|
||||
try:
|
||||
if redis_manager.redis:
|
||||
broadcast_data = {
|
||||
"robot_id": robot_id,
|
||||
"message": message_to_broadcast
|
||||
}
|
||||
await redis_manager.redis.publish("neobot_broadcast", json.dumps(broadcast_data))
|
||||
logger.success(f"[Broadcast] 已通过 Redis 发布广播消息: 来源 {robot_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"[Broadcast] 发布 Redis 消息失败: {e}")
|
||||
|
||||
await event.reply("广播已完成!")
|
||||
|
||||
return True # 消费事件,防止其他处理器响应
|
||||
|
||||
Reference in New Issue
Block a user