第五轮审计(J1~J16)全量修复 + 子代理 max_turns 钳制: - 子代理 max_turns 钳制(0/None 回退默认 10,负数钳制 1)+ 14 项回归测试 - J1 任务系统投毒:add_memo 入库校验(时间/间隔/类型)、调度器预检与触发事务 防御、展示路径兜底、register 移入 try、add_user_task 补 repeat_end_time 透传 - J2/J13 前端 XSS:三页面 marked 输出经 DOMPurify 净化、entry1 系统消息/任务 分隔条改 textContent、CDN 脚本补 SRI - J3 FTS 删除换序(先 fts 后内容表,索引不再残留) - J5 原子写推广:新增 common/atomic.py(mkstemp+fsync+os.replace),config/prompt/ history 全部接入;损坏配置备份后回退 - J6 单例初始化加锁(PromptManager/ConfigManager 并发首例不双重加载) - J7 broadcast 并发发送 + wait_for 超时回收僵死连接 - J4/J8/J9~J12/J14~J16:验证异常脱敏、交互输入容错、MemoDatabase WAL+busy_timeout、 memo 批量删除、/new 竞态、skills 引号转义、voice_client 清理、死代码移除、 skill_manager 同名键/help 截断
301 lines
9.6 KiB
Python
Executable File
301 lines
9.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""截图工具 - 截取计算机桌面并保存。"""
|
||
|
||
import argparse
|
||
import os
|
||
import sys
|
||
import time
|
||
import platform
|
||
import subprocess
|
||
from datetime import datetime
|
||
|
||
# ==================== 配置常量 ====================
|
||
|
||
SCREENSHOT_DIR = os.path.expanduser("~/.aibox/screenshot")
|
||
SUPPORTED_SYSTEMS = ['Windows', 'Linux', 'Darwin']
|
||
|
||
|
||
# ==================== 辅助函数 ====================
|
||
|
||
def ensure_screenshot_dir():
|
||
"""确保截图目录存在"""
|
||
os.makedirs(SCREENSHOT_DIR, exist_ok=True)
|
||
return SCREENSHOT_DIR
|
||
|
||
|
||
def get_timestamp_filename(prefix="screenshot", extension="png"):
|
||
"""生成基于时间戳的文件名"""
|
||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||
return f"{prefix}_{timestamp}.{extension}"
|
||
|
||
|
||
def get_system():
|
||
"""获取当前操作系统"""
|
||
return platform.system()
|
||
|
||
|
||
# ==================== 各平台截图实现 ====================
|
||
|
||
def take_screenshot_windows(output_path, delay=0):
|
||
"""Windows平台截图"""
|
||
if delay > 0:
|
||
time.sleep(delay)
|
||
|
||
# J12(审计 2026-08-19):output_path 经 PowerShell 单引号转义——
|
||
# 路径中的单引号翻倍(PS 字符串转义),AI/用户可控文件名无法闭合
|
||
# 字符串注入脚本(此前 `${output_path}` 直拼,`");calc;#` 即注入)
|
||
ps_path = str(output_path).replace("'", "''")
|
||
try:
|
||
ps_script = f'''
|
||
Add-Type -AssemblyName System.Windows.Forms
|
||
Add-Type -AssemblyName System.Drawing
|
||
|
||
$screen = [System.Windows.Forms.Screen]::PrimaryScreen
|
||
$bounds = $screen.Bounds
|
||
|
||
$bitmap = New-Object System.Drawing.Bitmap $bounds.Width, $bounds.Height
|
||
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
||
$graphics.CopyFromScreen($bounds.X, $bounds.Y, 0, 0, $bounds.Size)
|
||
|
||
$bitmap.Save('{ps_path}', [System.Drawing.Imaging.ImageFormat]::Png)
|
||
$graphics.Dispose()
|
||
$bitmap.Dispose()
|
||
|
||
Write-Output "截图已保存: {ps_path}"
|
||
'''
|
||
|
||
result = subprocess.run(
|
||
["powershell", "-Command", ps_script],
|
||
capture_output=True,
|
||
text=True,
|
||
timeout=30
|
||
)
|
||
|
||
if result.returncode == 0 and os.path.exists(output_path):
|
||
return True, f"截图成功: {output_path}"
|
||
else:
|
||
error_msg = result.stderr if result.stderr else "未知错误"
|
||
return False, f"截图失败: {error_msg}"
|
||
|
||
except subprocess.TimeoutExpired:
|
||
return False, "截图超时"
|
||
except Exception as e:
|
||
return False, f"截图错误: {str(e)}"
|
||
|
||
|
||
def take_screenshot_linux(output_path, delay=0):
|
||
"""Linux平台截图"""
|
||
if delay > 0:
|
||
time.sleep(delay)
|
||
|
||
tools = []
|
||
|
||
if subprocess.run(["which", "scrot"], capture_output=True).returncode == 0:
|
||
tools.append(("scrot", ["scrot", "-z", output_path]))
|
||
|
||
if subprocess.run(["which", "gnome-screenshot"], capture_output=True).returncode == 0:
|
||
tools.append(("gnome-screenshot", ["gnome-screenshot", "-f", output_path]))
|
||
|
||
if subprocess.run(["which", "import"], capture_output=True).returncode == 0:
|
||
tools.append(("import", ["import", "-window", "root", output_path]))
|
||
|
||
if not tools:
|
||
return False, "未找到截图工具,请安装 scrot, gnome-screenshot 或 ImageMagick"
|
||
|
||
for tool_name, cmd in tools:
|
||
try:
|
||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||
if result.returncode == 0 and os.path.exists(output_path):
|
||
return True, f"截图成功 (使用 {tool_name}): {output_path}"
|
||
except:
|
||
continue
|
||
|
||
return False, "所有截图工具都失败了"
|
||
|
||
|
||
def take_screenshot_macos(output_path, delay=0):
|
||
"""macOS平台截图"""
|
||
if delay > 0:
|
||
time.sleep(delay)
|
||
|
||
try:
|
||
if subprocess.run(["which", "screencapture"], capture_output=True).returncode != 0:
|
||
return False, "未找到 screencapture 命令"
|
||
|
||
cmd = ["screencapture", "-x", output_path]
|
||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||
|
||
if result.returncode == 0 and os.path.exists(output_path):
|
||
return True, f"截图成功: {output_path}"
|
||
else:
|
||
error_msg = result.stderr if result.stderr else "未知错误"
|
||
return False, f"截图失败: {error_msg}"
|
||
|
||
except subprocess.TimeoutExpired:
|
||
return False, "截图超时"
|
||
except Exception as e:
|
||
return False, f"截图错误: {str(e)}"
|
||
|
||
|
||
# ==================== 截图函数 ====================
|
||
|
||
def take_screenshot(output_dir=None, filename=None, delay=0):
|
||
"""截取屏幕截图"""
|
||
system = get_system()
|
||
|
||
if system not in SUPPORTED_SYSTEMS:
|
||
return False, f"不支持的操作系统: {system},支持的系统: {', '.join(SUPPORTED_SYSTEMS)}", None
|
||
|
||
if output_dir:
|
||
save_dir = output_dir
|
||
else:
|
||
save_dir = ensure_screenshot_dir()
|
||
|
||
os.makedirs(save_dir, exist_ok=True)
|
||
|
||
if filename:
|
||
filepath = os.path.join(save_dir, filename)
|
||
else:
|
||
filepath = os.path.join(save_dir, get_timestamp_filename())
|
||
|
||
if system == 'Windows':
|
||
success, message = take_screenshot_windows(filepath, delay)
|
||
elif system == 'Linux':
|
||
success, message = take_screenshot_linux(filepath, delay)
|
||
elif system == 'Darwin':
|
||
success, message = take_screenshot_macos(filepath, delay)
|
||
else:
|
||
return False, f"不支持的操作系统: {system}", None
|
||
|
||
if success:
|
||
return True, message, filepath
|
||
else:
|
||
return False, message, None
|
||
|
||
|
||
def list_recent_screenshots(limit=10):
|
||
"""列出最近的截图文件"""
|
||
save_dir = ensure_screenshot_dir()
|
||
|
||
if not os.path.exists(save_dir):
|
||
return f"截图目录不存在: {save_dir}"
|
||
|
||
screenshots = []
|
||
for f in os.listdir(save_dir):
|
||
if f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')):
|
||
filepath = os.path.join(save_dir, f)
|
||
mtime = os.path.getmtime(filepath)
|
||
size = os.path.getsize(filepath)
|
||
screenshots.append((mtime, f, size))
|
||
|
||
screenshots.sort(reverse=True)
|
||
|
||
if not screenshots:
|
||
return f"截图目录中没有截图文件: {save_dir}"
|
||
|
||
result = [f"📸 最近的截图 (共 {len(screenshots)} 个):"]
|
||
for i, (mtime, fname, size) in enumerate(screenshots[:limit], 1):
|
||
dt = datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M:%S")
|
||
|
||
if size < 1024:
|
||
size_str = f"{size}B"
|
||
elif size < 1024 * 1024:
|
||
size_str = f"{size/1024:.1f}KB"
|
||
else:
|
||
size_str = f"{size/1024/1024:.1f}MB"
|
||
|
||
result.append(f" {i}. {fname} - {dt} ({size_str})")
|
||
|
||
if len(screenshots) > limit:
|
||
result.append(f" ... 还有 {len(screenshots) - limit} 个文件")
|
||
|
||
return "\n".join(result)
|
||
|
||
|
||
# ==================== 命令行接口 ====================
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(
|
||
description="截图工具 - 截取计算机桌面并保存。",
|
||
epilog="""
|
||
使用示例:
|
||
# 立即截图(自动生成文件名)
|
||
screenshot
|
||
|
||
# 3秒后截图
|
||
screenshot --delay 3
|
||
|
||
# 截图并指定文件名
|
||
screenshot --filename "my_screenshot.png"
|
||
|
||
# 截图并保存到指定目录
|
||
screenshot --output-dir "~/Desktop"
|
||
|
||
# 列出最近的截图
|
||
screenshot --list --limit 5
|
||
|
||
# 5秒后截图并保存到桌面
|
||
screenshot --delay 5 --output-dir "~/Desktop"
|
||
|
||
截图保存位置:
|
||
~/.aibox/screenshot/
|
||
自动命名格式: screenshot_YYYYMMDD_HHMMSS.png
|
||
|
||
支持的截图工具:
|
||
Linux: scrot, gnome-screenshot, ImageMagick
|
||
macOS: screencapture (系统自带)
|
||
Windows: PowerShell .NET (系统自带)
|
||
"""
|
||
)
|
||
|
||
parser.add_argument("--output-dir", "-o", help="截图保存目录(默认: ~/.aibox/screenshot/)")
|
||
parser.add_argument("--filename", "-f", help="文件名(默认: 自动生成时间戳文件名)")
|
||
parser.add_argument("--delay", "-d", type=int, default=0, help="延迟秒数(默认: 0)")
|
||
parser.add_argument("--list", "-l", action="store_true", help="列出最近的截图文件")
|
||
parser.add_argument("--limit", "-n", type=int, default=10, help="列出截图时的数量限制(默认: 10)")
|
||
|
||
args = parser.parse_args()
|
||
|
||
# 列出截图
|
||
if args.list:
|
||
print(list_recent_screenshots(args.limit))
|
||
return
|
||
|
||
# 执行截图
|
||
try:
|
||
success, message, filepath = take_screenshot(
|
||
output_dir=args.output_dir,
|
||
filename=args.filename,
|
||
delay=args.delay
|
||
)
|
||
|
||
if success:
|
||
print(f"✅ {message}")
|
||
|
||
if filepath and os.path.exists(filepath):
|
||
file_size = os.path.getsize(filepath)
|
||
if file_size < 1024:
|
||
size_str = f"{file_size}B"
|
||
elif file_size < 1024 * 1024:
|
||
size_str = f"{file_size/1024:.1f}KB"
|
||
else:
|
||
size_str = f"{file_size/1024/1024:.1f}MB"
|
||
print(f"📦 文件大小: {size_str}")
|
||
|
||
# 显示目录中的截图总数
|
||
save_dir = args.output_dir if args.output_dir else SCREENSHOT_DIR
|
||
save_dir = os.path.expanduser(save_dir)
|
||
if os.path.exists(save_dir):
|
||
screenshots = [f for f in os.listdir(save_dir) if f.lower().endswith(('.png', '.jpg', '.jpeg'))]
|
||
print(f"📁 该目录共有 {len(screenshots)} 张截图")
|
||
else:
|
||
print(f"❌ {message}")
|
||
sys.exit(1)
|
||
|
||
except Exception as e:
|
||
print(f"❌ 执行错误: {str(e)}")
|
||
sys.exit(1)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main() |