124 lines
6.0 KiB
PHP
124 lines
6.0 KiB
PHP
<?php
|
|
/* LIVE SCREENSHOT — captures any page on demand via Playwright + chromium headless
|
|
Cache: 60s. Falls back to cached image if exists.
|
|
Usage: /api/live-screenshot.php?url=https://weval-consulting.com/ethica-hub.html
|
|
Or: /api/live-screenshot.php?path=/ethica-hub.html (auto-prepends domain)
|
|
*/
|
|
header("Cache-Control: max-age=600, public");
|
|
|
|
$cache_dir = "/dev/shm/live-screenshots";
|
|
@mkdir($cache_dir, 0755, true);
|
|
|
|
$url = $_GET["url"] ?? "";
|
|
$path = $_GET["path"] ?? "";
|
|
if ($path && !$url) {
|
|
$url = "https://weval-consulting.com" . (strpos($path, "/") === 0 ? $path : "/$path");
|
|
}
|
|
if (!$url) {
|
|
http_response_code(400);
|
|
echo "Need ?url= or ?path=";
|
|
exit;
|
|
}
|
|
|
|
// For API/PHP endpoints — render JSON pretty
|
|
$is_api = preg_match("/\/api\/.*\.php$/", $url);
|
|
|
|
// Cache key
|
|
$key = md5($url);
|
|
$cache = "$cache_dir/$key.png";
|
|
|
|
// Use cached if < 5min old
|
|
if (file_exists($cache) && (time() - filemtime($cache)) < 600) {
|
|
header("Content-Type: image/png");
|
|
header("X-Cache: HIT");
|
|
readfile($cache);
|
|
exit;
|
|
}
|
|
|
|
// OPUS FALLBACK — for S95 Arsenal/API URLs that systematically 500, generate status card instead of launching Playwright
|
|
$is_s95 = (preg_match('#/(w|wv)/(api|commonia|includes|production|widgets|artifacts|statistics|cli|docs)#', $url) || preg_match('#95\.216|:5890|:8443|s95#i', $url));
|
|
if ($is_s95 || $is_api) {
|
|
// Quick HTTP check first
|
|
$ch_test = curl_init($url);
|
|
curl_setopt_array($ch_test, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>5, CURLOPT_NOBODY=>true, CURLOPT_FOLLOWLOCATION=>true, CURLOPT_SSL_VERIFYPEER=>false]);
|
|
curl_exec($ch_test);
|
|
$http_code = curl_getinfo($ch_test, CURLINFO_HTTP_CODE);
|
|
$total_time = round(curl_getinfo($ch_test, CURLINFO_TOTAL_TIME) * 1000);
|
|
curl_close($ch_test);
|
|
|
|
// If 200 OK and it's an API endpoint: render the JSON response as code
|
|
if ($http_code >= 200 && $http_code < 400 && $is_api) {
|
|
// Let Playwright handle it (will render the page)
|
|
} else if ($http_code >= 400 || $http_code === 0) {
|
|
// Generate SVG status card → PNG via Playwright minimal render
|
|
$short_url = preg_replace('#^https?://[^/]+#', '', $url);
|
|
$status_color = ($http_code >= 500) ? '#ef4444' : (($http_code >= 400) ? '#f59e0b' : '#6b7280');
|
|
$server_label = $is_s95 ? 'S95 Arsenal' : 'S204';
|
|
$svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 640"><rect fill="#0f172a" width="1024" height="640" rx="12"/>'
|
|
. '<rect fill="#1e293b" x="40" y="40" width="944" height="560" rx="8"/>'
|
|
. '<circle cx="512" cy="200" r="50" fill="' . $status_color . '" opacity="0.15"/>'
|
|
. '<text x="512" y="215" text-anchor="middle" fill="' . $status_color . '" font-family="monospace" font-size="42" font-weight="bold">' . $http_code . '</text>'
|
|
. '<text x="512" y="300" text-anchor="middle" fill="#94a3b8" font-family="sans-serif" font-size="16">' . htmlspecialchars($short_url) . '</text>'
|
|
. '<text x="512" y="340" text-anchor="middle" fill="#475569" font-family="monospace" font-size="13">' . $server_label . ' | ' . $total_time . 'ms</text>'
|
|
. '<text x="512" y="430" text-anchor="middle" fill="#334155" font-family="sans-serif" font-size="11">Screenshot unavailable — endpoint returned ' . $http_code . '</text>'
|
|
. '</svg>';
|
|
|
|
// OPUS Phase5 — Serve SVG DIRECTEMENT (zéro Playwright, zéro latence)
|
|
// Cache le SVG aussi pour éviter de régénérer
|
|
@file_put_contents("$cache_dir/$key.svg", $svg);
|
|
header("Content-Type: image/svg+xml");
|
|
header("X-Cache: SVG-DIRECT");
|
|
header("X-Status: " . $http_code);
|
|
echo $svg;
|
|
exit;
|
|
}
|
|
}
|
|
|
|
// Generate via Playwright Python
|
|
$py = "
|
|
import sys, asyncio
|
|
from playwright.async_api import async_playwright
|
|
|
|
async def cap():
|
|
async with async_playwright() as p:
|
|
browser = await p.chromium.launch(headless=True, args=[\"--no-sandbox\",\"--disable-dev-shm-usage\"])
|
|
ctx = await browser.new_context(viewport={\"width\":1024,\"height\":640}, ignore_https_errors=True)
|
|
page = await ctx.new_page()
|
|
try:
|
|
await page.goto(\"$url\", timeout=8000, wait_until=\"domcontentloaded\")
|
|
await page.wait_for_timeout(800)
|
|
await page.screenshot(path=\"$cache\", clip={\"x\":0,\"y\":0,\"width\":1024,\"height\":640})
|
|
except Exception as e:
|
|
# Fallback: render error message as image
|
|
await page.set_content(f\"<html><body style=font:18px monospace;background:#1a0a0a;color:#ff6b6b;padding:40px>SCREENSHOT FAIL<br><br>{type(e).__name__}<br>{str(e)[:200]}<br><br>URL: $url</body></html>\")
|
|
await page.screenshot(path=\"$cache\")
|
|
await browser.close()
|
|
|
|
asyncio.run(cap())
|
|
";
|
|
|
|
$tmp = tempnam("/tmp", "ss_");
|
|
file_put_contents($tmp, $py);
|
|
$out = shell_exec("timeout 12 python3 $tmp 2>&1");
|
|
@unlink($tmp);
|
|
|
|
if (file_exists($cache) && filesize($cache) > 100) {
|
|
header("Content-Type: image/png");
|
|
header("X-Cache: MISS");
|
|
header("X-Generator: playwright");
|
|
readfile($cache);
|
|
} else {
|
|
// OPUS Phase5 — Fallback SVG générique au lieu de 503 brut
|
|
$short = preg_replace("#^https?://[^/]+#", "", $url);
|
|
$fallback_svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 640"><rect fill="#0f172a" width="1024" height="640" rx="12"/>'
|
|
. '<rect fill="#1e293b" x="40" y="40" width="944" height="560" rx="8"/>'
|
|
. '<circle cx="512" cy="200" r="50" fill="#475569" opacity="0.2"/>'
|
|
. '<text x="512" y="210" text-anchor="middle" fill="#64748b" font-family="monospace" font-size="30">⏳</text>'
|
|
. '<text x="512" y="300" text-anchor="middle" fill="#94a3b8" font-family="sans-serif" font-size="14">' . htmlspecialchars($short) . '</text>'
|
|
. '<text x="512" y="340" text-anchor="middle" fill="#334155" font-family="monospace" font-size="11">Screenshot en cours de génération</text>'
|
|
. '</svg>';
|
|
header("Content-Type: image/svg+xml");
|
|
header("X-Cache: PLACEHOLDER");
|
|
echo $fallback_svg;
|
|
}
|