Compare commits
1 Commits
step35/595
...
step35/541
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0763a20d74 |
@@ -37,6 +37,12 @@ from glitch_patterns import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from playwright.sync_api import sync_playwright
|
||||||
|
_PLAYWRIGHT_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
_PLAYWRIGHT_AVAILABLE = False
|
||||||
@dataclass
|
@dataclass
|
||||||
class DetectedGlitch:
|
class DetectedGlitch:
|
||||||
"""A single detected glitch with metadata."""
|
"""A single detected glitch with metadata."""
|
||||||
@@ -97,7 +103,7 @@ def generate_scan_angles(num_angles: int) -> list[dict]:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def capture_screenshots(url: str, angles: list[dict], output_dir: Path) -> list[Path]:
|
def capture_screenshots(url: str, angles: list[dict], output_dir: Path, live: bool = False) -> list[Path]:
|
||||||
"""Capture screenshots of a 3D web world from multiple angles.
|
"""Capture screenshots of a 3D web world from multiple angles.
|
||||||
|
|
||||||
Uses browser_vision tool when available; falls back to placeholder generation
|
Uses browser_vision tool when available; falls back to placeholder generation
|
||||||
@@ -111,7 +117,7 @@ def capture_screenshots(url: str, angles: list[dict], output_dir: Path) -> list[
|
|||||||
|
|
||||||
# Attempt browser-based capture via browser_vision
|
# Attempt browser-based capture via browser_vision
|
||||||
try:
|
try:
|
||||||
result = _browser_capture(url, angle, filename)
|
result = _browser_capture(url, angle, filename, live=live)
|
||||||
if result:
|
if result:
|
||||||
screenshots.append(filename)
|
screenshots.append(filename)
|
||||||
continue
|
continue
|
||||||
@@ -125,27 +131,84 @@ def capture_screenshots(url: str, angles: list[dict], output_dir: Path) -> list[
|
|||||||
return screenshots
|
return screenshots
|
||||||
|
|
||||||
|
|
||||||
def _browser_capture(url: str, angle: dict, output_path: Path) -> bool:
|
def _browser_capture(url: str, angle: dict, output_path: Path, live: bool = False) -> bool:
|
||||||
"""Capture a screenshot via browser automation.
|
"""Capture a screenshot via browser automation.
|
||||||
|
|
||||||
This is a stub that delegates to the browser_vision tool when run
|
Uses Playwright to navigate to the URL and capture a screenshot.
|
||||||
in an environment that provides it. In CI or offline mode, returns False.
|
In live mode, this is required; otherwise, falls back to placeholder.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url: Target URL to scan
|
||||||
|
angle: Camera angle dict with yaw/pitch/label
|
||||||
|
output_path: Where to save the screenshot
|
||||||
|
live: If True, hard-fail when browser unavailable
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True on success, False otherwise
|
||||||
"""
|
"""
|
||||||
# Check if browser_vision is available via environment
|
# Import check
|
||||||
bv_script = os.environ.get("BROWSER_VISION_SCRIPT")
|
if not _PLAYWRIGHT_AVAILABLE:
|
||||||
if bv_script and Path(bv_script).exists():
|
if live:
|
||||||
import subprocess
|
raise ImportError(
|
||||||
cmd = [
|
"browser automation requires playwright. Install with: pip install playwright && playwright install chromium"
|
||||||
sys.executable, bv_script,
|
)
|
||||||
"--url", url,
|
|
||||||
"--screenshot", str(output_path),
|
|
||||||
"--rotate-yaw", str(angle["yaw"]),
|
|
||||||
"--rotate-pitch", str(angle["pitch"]),
|
|
||||||
]
|
|
||||||
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
||||||
return proc.returncode == 0 and output_path.exists()
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
with sync_playwright() as p:
|
||||||
|
browser = p.chromium.launch(headless=True)
|
||||||
|
page = browser.new_page(viewport={"width": 1280, "height": 720})
|
||||||
|
page.goto(url, wait_until="networkidle", timeout=30000)
|
||||||
|
page.wait_for_timeout(2000)
|
||||||
|
|
||||||
|
# Rotate camera if using custom angles (yaw/pitch in degrees)
|
||||||
|
yaw = angle.get("yaw", 0)
|
||||||
|
pitch = angle.get("pitch", 0)
|
||||||
|
if yaw != 0 or pitch != 0:
|
||||||
|
yaw_rad = yaw * (3.14159 / 180)
|
||||||
|
pitch_rad = pitch * (3.14159 / 180)
|
||||||
|
js = f"""
|
||||||
|
(function() {{
|
||||||
|
// Try common Three.js camera locations
|
||||||
|
let cam = null;
|
||||||
|
if (window.renderer && window.renderer.camera) cam = window.renderer.camera;
|
||||||
|
else if (window.scene && window.scene.camera) cam = window.scene.camera;
|
||||||
|
else if (window.camera) cam = window.camera;
|
||||||
|
// Check for Three.js r125+ camera in renderer.info
|
||||||
|
const canvases = document.querySelectorAll('canvas');
|
||||||
|
for (const c of canvases) {{
|
||||||
|
if (c.__three__ && c.__three__.camera) {{ cam = c.__three__.camera; break; }}
|
||||||
|
}}
|
||||||
|
if (cam) {{
|
||||||
|
cam.rotation.set({pitch_rad}, {yaw_rad}, 0, 'YXZ');
|
||||||
|
if (cam.updateMatrixWorld) cam.updateMatrixWorld();
|
||||||
|
}}
|
||||||
|
}})();
|
||||||
|
"""
|
||||||
|
page.evaluate(js)
|
||||||
|
page.wait_for_timeout(500)
|
||||||
|
|
||||||
|
page.screenshot(path=str(output_path))
|
||||||
|
browser.close()
|
||||||
|
return output_path.exists()
|
||||||
|
except Exception as e:
|
||||||
|
if live:
|
||||||
|
raise
|
||||||
|
# Silent fail for automatic fallback
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_placeholder_screenshot(path: Path, angle: dict):
|
||||||
|
"""Generate a minimal 1x1 PNG as a placeholder for testing."""
|
||||||
|
png_data = (
|
||||||
|
b"\x89PNG\r\n\x1a\n"
|
||||||
|
b"\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01"
|
||||||
|
b"\x08\x06\x00\x00\x00\x1f\x15\xc4\x89"
|
||||||
|
b"\x00\x00\x00\nIDATx\x9cc\x00\x01\x00\x00\x05\x00\x01"
|
||||||
|
b"\r\n\xb4\x00\x00\x00\x00IEND\xaeB`\x82"
|
||||||
|
)
|
||||||
|
path.write_bytes(png_data)
|
||||||
|
|
||||||
|
|
||||||
def _generate_placeholder_screenshot(path: Path, angle: dict):
|
def _generate_placeholder_screenshot(path: Path, angle: dict):
|
||||||
"""Generate a minimal 1x1 PNG as a placeholder for testing."""
|
"""Generate a minimal 1x1 PNG as a placeholder for testing."""
|
||||||
@@ -524,6 +587,11 @@ Examples:
|
|||||||
help="Minimum severity to include in report",
|
help="Minimum severity to include in report",
|
||||||
)
|
)
|
||||||
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
|
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
|
||||||
|
parser.add_argument(
|
||||||
|
"--live",
|
||||||
|
action="store_true",
|
||||||
|
help="Use live browser automation (requires playwright); falls back to placeholders if unavailable",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--threejs",
|
"--threejs",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -555,7 +623,7 @@ Examples:
|
|||||||
|
|
||||||
# Capture screenshots
|
# Capture screenshots
|
||||||
screenshots_dir = Path(f"/tmp/matrix_glitch_{scan_id}")
|
screenshots_dir = Path(f"/tmp/matrix_glitch_{scan_id}")
|
||||||
screenshots = capture_screenshots(args.url, angles, screenshots_dir)
|
screenshots = capture_screenshots(args.url, angles, screenshots_dir, live=args.live)
|
||||||
print(f"[*] Captured {len(screenshots)} screenshots")
|
print(f"[*] Captured {len(screenshots)} screenshots")
|
||||||
|
|
||||||
# Filter patterns by severity and type
|
# Filter patterns by severity and type
|
||||||
|
|||||||
Reference in New Issue
Block a user