feat(web_parser): 新增通用web链接解析插件框架

refactor: 重构B站、抖音、GitHub解析器为模块化结构

fix(executor): 增强docker容器错误处理和回调稳定性

style(templates): 优化帮助页面和代码执行结果的样式

perf(web_parser): 添加API缓存和消息去重机制

docs: 更新插件元信息和注释

chore: 移除旧的独立解析器插件文件
This commit is contained in:
2026-01-22 01:58:13 +08:00
parent 5f943c1792
commit 1420d0f0b2
13 changed files with 1665 additions and 995 deletions

View File

@@ -3,15 +3,19 @@ import html
import textwrap
import asyncio
from typing import Dict
import datetime
import sys
from core.managers.command_manager import matcher
from models.events.message import MessageEvent
from core.permission import Permission
from core.utils.logger import logger
from core.managers.image_manager import image_manager
from models.message import MessageSegment
__plugin_meta__ = {
"name": "Python 代码执行",
"description": "在安全的沙箱环境中执行 Python 代码片段,支持单行、多行和转发回复",
"description": "在安全的沙箱环境中执行 Python 代码片段,支持单行、多行和图片输出",
"usage": "/py <单行代码>\n/code_py <单行代码>\n/py (进入多行输入模式)",
}
@@ -19,48 +23,88 @@ __plugin_meta__ = {
# 结构: {(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 generate_and_send_code_image(event: MessageEvent, input_code: str, output_result: str):
"""
将输入和输出打包成转发消息进行回复
参考 forward_test.py 的实现,兼容私聊和群聊。
生成代码执行结果的图片并发送,如果发送失败则降级为文本消息
Args:
event (MessageEvent): 消息事件对象
input_code (str): 用户输入的代码
output_result (str): 代码执行结果
"""
bot = event.bot
# 1. 构建消息节点列表
nodes = [
bot.build_forward_node(
user_id=event.user_id,
nickname=event.sender.nickname if event.sender else 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)
# 准备模板数据
user_nickname = event.sender.nickname if event.sender else str(event.user_id)
user_id = event.user_id
avatar_initial = user_nickname[0] if user_nickname else "U"
# 构建QQ头像URL
qq_avatar_url = f"https://q1.qlogo.cn/g?b=qq&nk={user_id}&s=640"
template_data = {
"user_nickname": user_nickname,
"user_id": user_id,
"avatar_initial": avatar_initial,
"qq_avatar_url": qq_avatar_url,
"code": input_code,
"result": output_result,
"timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"execution_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"python_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
"result_title": "执行成功" if "Traceback" not in output_result and "Error" not in output_result else "执行出错",
"result_class": "result-success" if "Traceback" not in output_result and "Error" not in output_result else "result-error"
}
# 渲染模板为图片
image_base64 = await image_manager.render_template_to_base64(
template_name="code_execution.html",
data=template_data,
output_name=f"code_execution_{event.user_id}_{int(datetime.datetime.now().timestamp())}.png",
quality=90,
image_type="png"
)
if image_base64:
# 发送图片
await event.reply(MessageSegment.image(image_base64))
else:
# 如果图片生成失败,降级为文本消息
await event.reply(f"--- 你的代码 ---\n{input_code}\n--- 执行结果 ---\n{output_result}")
except Exception as e:
logger.error(f"[code_py] 发送转发消息失败: {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):
"""
核心代码执行逻辑。
Args:
event (MessageEvent): 消息事件对象
code (str): 要执行的Python代码
"""
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让它能直接接收回复函数
# 定义一个包装回调函数,确保正确处理异步操作和异常
async def callback_wrapper(result):
try:
await generate_and_send_code_image(event, code, result)
except Exception as e:
logger.error(f"[code_py] 执行回调时发生错误: {e}")
# 即使回调失败,也要确保任务被标记为完成
# 降级为简单文本回复
try:
await event.reply(f"代码执行结果:\n{result}")
except Exception as reply_error:
logger.error(f"[code_py] 发送降级回复时也失败: {reply_error}")
await code_executor.add_task(
code,
lambda result: reply_as_forward(event, code, result)
callback_wrapper
)
await event.reply("代码已提交至沙箱执行队列,请稍候...")