滚木
This commit is contained in:
@@ -82,7 +82,6 @@ class MessageHandler(BaseHandler):
|
|||||||
def command(
|
def command(
|
||||||
self,
|
self,
|
||||||
*names: str,
|
*names: str,
|
||||||
*names: str,
|
|
||||||
permission: Optional[Permission] = None,
|
permission: Optional[Permission] = None,
|
||||||
override_permission_check: bool = False
|
override_permission_check: bool = False
|
||||||
) -> Callable:
|
) -> Callable:
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ def load_all_plugins():
|
|||||||
加载过程中会提取插件元数据 `__plugin_meta__` 并注册到 CommandManager。
|
加载过程中会提取插件元数据 `__plugin_meta__` 并注册到 CommandManager。
|
||||||
"""
|
"""
|
||||||
plugin_dir = os.path.join(
|
plugin_dir = os.path.join(
|
||||||
os.path.dirname(os.path.abspath(__file__)), "..", "plugins"
|
os.path.dirname(os.path.abspath(__file__)), "..", "..", "plugins"
|
||||||
)
|
)
|
||||||
package_name = "plugins"
|
package_name = "plugins"
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import html
|
import html
|
||||||
import textwrap
|
import textwrap
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import html
|
|
||||||
import textwrap
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
@@ -16,20 +13,12 @@ __plugin_meta__ = {
|
|||||||
"name": "Python 代码执行",
|
"name": "Python 代码执行",
|
||||||
"description": "在安全的沙箱环境中执行 Python 代码片段,支持单行、多行和转发回复。",
|
"description": "在安全的沙箱环境中执行 Python 代码片段,支持单行、多行和转发回复。",
|
||||||
"usage": "/py <单行代码>\n/code_py <单行代码>\n/py (进入多行输入模式)",
|
"usage": "/py <单行代码>\n/code_py <单行代码>\n/py (进入多行输入模式)",
|
||||||
"name": "Python 代码执行",
|
|
||||||
"description": "在安全的沙箱环境中执行 Python 代码片段,支持单行、多行和转发回复。",
|
|
||||||
"usage": "/py <单行代码>\n/code_py <单行代码>\n/py (进入多行输入模式)",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- 会话状态管理 ---
|
# --- 会话状态管理 ---
|
||||||
# 结构: {(user_id, group_id): asyncio.TimerHandle}
|
# 结构: {(user_id, group_id): asyncio.TimerHandle}
|
||||||
multi_line_sessions: Dict[tuple, asyncio.TimerHandle] = {}
|
multi_line_sessions: Dict[tuple, asyncio.TimerHandle] = {}
|
||||||
|
|
||||||
async def reply_as_forward(event: MessageEvent, input_code: str, output_result: str):
|
|
||||||
# --- 会话状态管理 ---
|
|
||||||
# 结构: {(user_id, group_id): asyncio.TimerHandle}
|
|
||||||
multi_line_sessions: Dict[tuple, asyncio.TimerHandle] = {}
|
|
||||||
|
|
||||||
async def reply_as_forward(event: MessageEvent, input_code: str, output_result: str):
|
async def reply_as_forward(event: MessageEvent, input_code: str, output_result: str):
|
||||||
"""
|
"""
|
||||||
将输入和输出打包成转发消息进行回复。
|
将输入和输出打包成转发消息进行回复。
|
||||||
@@ -59,38 +48,9 @@ async def reply_as_forward(event: MessageEvent, input_code: str, output_result:
|
|||||||
# 降级为普通消息回复
|
# 降级为普通消息回复
|
||||||
await event.reply(f"--- 你的代码 ---\n{input_code}\n--- 执行结果 ---\n{output_result}")
|
await event.reply(f"--- 你的代码 ---\n{input_code}\n--- 执行结果 ---\n{output_result}")
|
||||||
|
|
||||||
async def execute_code(event: MessageEvent, code: str):
|
|
||||||
将输入和输出打包成转发消息进行回复。
|
|
||||||
参考 forward_test.py 的实现,兼容私聊和群聊。
|
|
||||||
"""
|
|
||||||
bot = event.bot
|
|
||||||
|
|
||||||
# 1. 构建消息节点列表
|
|
||||||
nodes = [
|
|
||||||
bot.build_forward_node(
|
|
||||||
user_id=event.user_id,
|
|
||||||
nickname=event.sender.nickname or str(event.user_id),
|
|
||||||
message=f"--- Your Code ---\n{input_code}"
|
|
||||||
),
|
|
||||||
bot.build_forward_node(
|
|
||||||
user_id=event.self_id,
|
|
||||||
nickname="Code Executor",
|
|
||||||
message=f"--- Execution Result ---\n{output_result}"
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 2. 发送合并转发消息
|
|
||||||
await bot.send_forwarded_messages(event, nodes)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[code_py] 发送转发消息失败: {e}")
|
|
||||||
# 降级为普通消息回复
|
|
||||||
await event.reply(f"--- 你的代码 ---\n{input_code}\n--- 执行结果 ---\n{output_result}")
|
|
||||||
|
|
||||||
async def execute_code(event: MessageEvent, code: str):
|
async def execute_code(event: MessageEvent, code: str):
|
||||||
"""
|
"""
|
||||||
核心代码执行逻辑。
|
核心代码执行逻辑。
|
||||||
核心代码执行逻辑。
|
|
||||||
"""
|
"""
|
||||||
code_executor = getattr(event.bot, 'code_executor', None)
|
code_executor = getattr(event.bot, 'code_executor', None)
|
||||||
if not code_executor or not code_executor.docker_client:
|
if not code_executor or not code_executor.docker_client:
|
||||||
@@ -137,74 +97,15 @@ def normalize_code(code: str) -> str:
|
|||||||
return code.strip()
|
return code.strip()
|
||||||
|
|
||||||
|
|
||||||
@matcher.command("py", "python", "code_py", permission=ADMIN)
|
|
||||||
async def code_py_main(event: MessageEvent, args: list[str]):
|
|
||||||
code_executor = getattr(event.bot, 'code_executor', None)
|
|
||||||
if not code_executor or not code_executor.docker_client:
|
|
||||||
await event.reply("代码执行服务当前不可用,请检查 Docker 连接配置。")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 修改 add_task,让它能直接接收回复函数
|
|
||||||
await code_executor.add_task(
|
|
||||||
code,
|
|
||||||
lambda result: reply_as_forward(event, code, result)
|
|
||||||
)
|
|
||||||
await event.reply("代码已提交至沙箱执行队列,请稍候...")
|
|
||||||
|
|
||||||
def cleanup_session(session_key: tuple):
|
|
||||||
"""
|
|
||||||
清理超时的会话。
|
|
||||||
"""
|
|
||||||
if session_key in multi_line_sessions:
|
|
||||||
del multi_line_sessions[session_key]
|
|
||||||
logger.info(f"[code_py] 会话 {session_key} 已超时,自动取消。")
|
|
||||||
|
|
||||||
def normalize_code(code: str) -> str:
|
|
||||||
"""
|
|
||||||
规范化用户输入的 Python 代码字符串。
|
|
||||||
|
|
||||||
主要处理两个问题:
|
|
||||||
1. 对消息中可能存在的 HTML 实体进行解码 (e.g., [ -> [)。
|
|
||||||
2. 移除整个代码块的公共前导缩进,以修复因复制粘贴导致的多余缩进。
|
|
||||||
|
|
||||||
:param code: 原始代码字符串。
|
|
||||||
:return: 规范化后的代码字符串。
|
|
||||||
"""
|
|
||||||
# 1. 解码 HTML 实体
|
|
||||||
code = html.unescape(code)
|
|
||||||
|
|
||||||
# 2. 移除公共前导缩进
|
|
||||||
try:
|
|
||||||
code = textwrap.dedent(code)
|
|
||||||
except Exception:
|
|
||||||
# 在某些情况下(例如,不一致的缩进),dedent 可能会失败,
|
|
||||||
# 但我们不希望因此中断流程,所以捕获异常并继续。
|
|
||||||
pass
|
|
||||||
|
|
||||||
return code.strip()
|
|
||||||
|
|
||||||
|
|
||||||
@matcher.command("py", "python", "code_py", permission=ADMIN)
|
@matcher.command("py", "python", "code_py", permission=ADMIN)
|
||||||
async def code_py_main(event: MessageEvent, args: list[str]):
|
async def code_py_main(event: MessageEvent, args: list[str]):
|
||||||
"""
|
"""
|
||||||
/py 命令的主入口。
|
/py 命令的主入口。
|
||||||
- 如果有参数,直接执行。
|
- 如果有参数,直接执行。
|
||||||
- 如果没有参数,开启多行输入模式。
|
- 如果没有参数,开启多行输入模式。
|
||||||
/py 命令的主入口。
|
|
||||||
- 如果有参数,直接执行。
|
|
||||||
- 如果没有参数,开启多行输入模式。
|
|
||||||
"""
|
"""
|
||||||
code_to_run = " ".join(args)
|
code_to_run = " ".join(args)
|
||||||
|
|
||||||
if code_to_run:
|
|
||||||
# 单行模式,对代码进行规范化处理
|
|
||||||
normalized_code = normalize_code(code_to_run)
|
|
||||||
if not normalized_code:
|
|
||||||
await event.reply("代码为空或格式错误,请输入有效的代码。")
|
|
||||||
return
|
|
||||||
await execute_code(event, normalized_code)
|
|
||||||
code_to_run = " ".join(args)
|
|
||||||
|
|
||||||
if code_to_run:
|
if code_to_run:
|
||||||
# 单行模式,对代码进行规范化处理
|
# 单行模式,对代码进行规范化处理
|
||||||
normalized_code = normalize_code(code_to_run)
|
normalized_code = normalize_code(code_to_run)
|
||||||
@@ -231,24 +132,6 @@ async def code_py_main(event: MessageEvent, args: list[str]):
|
|||||||
session_key
|
session_key
|
||||||
)
|
)
|
||||||
multi_line_sessions[session_key] = timeout_handler
|
multi_line_sessions[session_key] = timeout_handler
|
||||||
# 多行模式
|
|
||||||
# 使用 getattr 兼容私聊和群聊
|
|
||||||
session_key = (event.user_id, getattr(event, 'group_id', 'private'))
|
|
||||||
|
|
||||||
# 如果上一个会话的超时任务还在,先取消它
|
|
||||||
if session_key in multi_line_sessions:
|
|
||||||
multi_line_sessions[session_key].cancel()
|
|
||||||
|
|
||||||
await event.reply("已进入多行代码输入模式,请直接发送你的代码。\n(60秒内无操作将自动取消)")
|
|
||||||
|
|
||||||
# 设置 60 秒超时
|
|
||||||
loop = asyncio.get_running_loop()
|
|
||||||
timeout_handler = loop.call_later(
|
|
||||||
60,
|
|
||||||
cleanup_session,
|
|
||||||
session_key
|
|
||||||
)
|
|
||||||
multi_line_sessions[session_key] = timeout_handler
|
|
||||||
|
|
||||||
@matcher.on_message()
|
@matcher.on_message()
|
||||||
async def handle_multi_line_code(event: MessageEvent):
|
async def handle_multi_line_code(event: MessageEvent):
|
||||||
@@ -265,26 +148,6 @@ async def handle_multi_line_code(event: MessageEvent):
|
|||||||
# 对多行代码进行规范化处理
|
# 对多行代码进行规范化处理
|
||||||
normalized_code = normalize_code(event.raw_message)
|
normalized_code = normalize_code(event.raw_message)
|
||||||
|
|
||||||
if not normalized_code:
|
|
||||||
await event.reply("捕获到的代码为空或格式错误,已取消输入。")
|
|
||||||
return
|
|
||||||
|
|
||||||
await execute_code(event, normalized_code)
|
|
||||||
return True # 消费事件,防止其他处理器响应
|
|
||||||
async def handle_multi_line_code(event: MessageEvent):
|
|
||||||
"""
|
|
||||||
通用消息处理器,用于捕获多行模式下的代码输入。
|
|
||||||
"""
|
|
||||||
# 使用 getattr 兼容私聊和群聊
|
|
||||||
session_key = (event.user_id, getattr(event, 'group_id', 'private'))
|
|
||||||
if session_key in multi_line_sessions:
|
|
||||||
# 取消超时任务
|
|
||||||
multi_line_sessions[session_key].cancel()
|
|
||||||
del multi_line_sessions[session_key]
|
|
||||||
|
|
||||||
# 对多行代码进行规范化处理
|
|
||||||
normalized_code = normalize_code(event.raw_message)
|
|
||||||
|
|
||||||
if not normalized_code:
|
if not normalized_code:
|
||||||
await event.reply("捕获到的代码为空或格式错误,已取消输入。")
|
await event.reply("捕获到的代码为空或格式错误,已取消输入。")
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user