Files
NeoBot/plugins/broadcast.py
K2cr2O1 56b1014419 refactor(core): 重构核心模块结构并添加开发文档
将核心模块按功能重新组织为更清晰的结构,包括 managers、handlers 和 utils 目录
添加完整的开发文档,涵盖快速开始、项目结构、核心概念和插件开发指南
更新所有相关模块的导入路径以匹配新的结构
将单例模式实现提取到单独的 singleton.py 文件
2026-01-07 22:51:27 +08:00

117 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
管理员专用的广播插件
功能:
- 仅限管理员在私聊中调用。
- 通过回复一条消息并发送指令,将该消息转发给机器人所在的所有群聊。
- 此插件不写入 __plugin_meta__保持隐藏。
"""
import asyncio
from core.managers.command_manager import matcher
from models import MessageEvent, PrivateMessageEvent
from core.managers.permission_manager import ADMIN
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=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,
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 # 消费事件,防止其他处理器响应