Files
blackbird/aibox/skills/screenshot.py
T
qing 457bb900fb fix(aibox): round-5 audit batch K - max_turns clamp, task input validation, XSS hardening, atomic writes, broadcast timeout
第五轮审计(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 截断
2026-08-19 07:01:13 +08:00

301 lines
9.6 KiB
Python
Executable File
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.
#!/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()