Files
2026-07-09 02:59:53 +03:00

207 lines
7.1 KiB
Python

#!/usr/bin/env python3
"""Test Photopea inner shadow via CDP."""
import json, time, subprocess, sys, os
CHROMIUM = "/snap/bin/chromium"
PORT = 9555
OUT_DIR = "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/photopea_test"
os.makedirs(OUT_DIR, exist_ok=True)
def http_get(path):
import urllib.request
with urllib.request.urlopen(f"http://127.0.0.1:{PORT}{path}", timeout=10) as r:
return json.loads(r.read())
class CDP:
def __init__(self, ws_url):
import websocket
self.ws = websocket.create_connection(ws_url, timeout=60)
self._id = 0
def send(self, method, params=None):
self._id += 1
msg = {"id": self._id, "method": method}
if params:
msg["params"] = params
self.ws.send(json.dumps(msg))
while True:
resp = json.loads(self.ws.recv())
if resp.get("id") == self._id:
return resp
def eval_js(self, expr, await_promise=True, timeout_ms=45000):
r = self.send("Runtime.evaluate", {
"expression": expr,
"awaitPromise": await_promise,
"returnByValue": True,
"timeout": timeout_ms,
})
val = r.get("result", {}).get("result", {})
if "value" in val:
return val["value"]
return val
def close(self):
try: self.ws.close()
except: pass
def main():
proc = subprocess.Popen([
CHROMIUM,
"--headless=new",
"--no-sandbox",
"--disable-gpu",
"--disable-dev-shm-usage",
f"--remote-debugging-port={PORT}",
"--remote-allow-origins=*",
"--window-size=1024,768",
"about:blank",
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
time.sleep(4)
try:
targets = http_get("/json")
ws_url = None
for t in targets:
if t.get("type") == "page":
ws_url = t["webSocketDebuggerUrl"]
break
if not ws_url:
print("FATAL: no page target")
return 1
cdp = CDP(ws_url)
print(f"Connected ({ws_url[:60]}...)")
print("[1] Navigating to photopea.com...")
cdp.send("Page.navigate", {"url": "https://www.photopea.com"})
print("[2] Waiting for page load...")
time.sleep(90)
r = cdp.eval_js('document.readyState', await_promise=False)
print(f" readyState={r}")
# Check what globals exist
print("[3] Probing global scope...")
probe = cdp.eval_js("""
JSON.stringify({
hasApp: typeof app !== "undefined",
hasApp2: typeof window.app !== "undefined",
hasPhotoshop: typeof Photoshop !== "undefined",
hasPhotoshopApp: typeof photoshop !== "undefined",
bodyLen: document.body ? document.body.innerHTML.length : 0,
scripts: document.scripts.length,
title: document.title,
iframes: document.querySelectorAll("iframe").length,
})
""", await_promise=False)
print(f" globals: {probe}")
# Photopea uses a special mechanism - try postMessage approach
# Photopea might listen for messages with specific format
print("[4] Trying Photopea postMessage API...")
# Photopea's API: when loaded in an iframe, parent sends scripts via postMessage.
# But we're ON the photopea.com page. Let's try app.executeScript or direct eval.
r2 = cdp.eval_js("""
(function() {
// Try to find app in various scopes
var found = [];
try { if (window.app) found.push("window.app"); } catch(e) {}
try { if (self.app) found.push("self.app"); } catch(e) {}
try { if (globalThis.app) found.push("globalThis.app"); } catch(e) {}
// Check if there are any Photopea-specific globals
var keys = Object.keys(window).filter(k =>
k.toLowerCase().includes("photo") ||
k.toLowerCase().includes("ppea") ||
k.toLowerCase().includes("pp") ||
k.toLowerCase().includes("app")
);
return JSON.stringify({found: found, photoKeys: keys.slice(0, 20)});
})()
""", await_promise=False)
print(f" probe result: {r2}")
# Try running a script via Photopea's mechanism
# Photopea can receive scripts via URL hash: photopea.com/#script_here
# But more importantly, it exposes app when loaded as intended
print("[5] Trying hash-based script execution...")
script = 'var doc = app.documents.add(256, 256, 72, "test", NewDocumentMode.RGB, DocumentFill.TRANSPARENT); app.echoToOE("OK");'
import base64
b64 = base64.b64encode(script.encode()).decode()
cdp.send("Page.navigate", {"url": f"https://www.photopea.com#{script}"})
time.sleep(15)
r3 = cdp.eval_js('typeof app !== "undefined" ? "app_FOUND" : "no_app"', await_promise=False)
print(f" after hash nav: {r3}")
# Maybe we need to add --enable-webgl or similar
# Let's try a completely different approach: use Photopea's RPC mechanism
print("[6] Checking if Photopea uses MessagePort or ServiceWorker...")
r4 = cdp.eval_js("""
(function() {
var info = {};
try { info.workers = navigator.serviceWorker ? "available" : "not available"; } catch(e) { info.workers = "err:" + e; }
try { info.sockets = typeof WebSocket !== "undefined" ? "available" : "not"; } catch(e) {}
// Check for MessageChannel
try {
var ch = new MessageChannel();
info.messageChannel = "available";
} catch(e) { info.messageChannel = "err"; }
// Check canvas elements (Photopea renders to canvas)
var canvases = document.querySelectorAll("canvas");
info.canvasCount = canvases.length;
info.canvasDims = [];
canvases.forEach(function(c) { info.canvasDims.push(c.width + "x" + c.height); });
return JSON.stringify(info);
})()
""", await_promise=False)
print(f" {r4}")
# Last resort: check the actual Photopea API docs
# Photopea exposes `app` on window when the UI is loaded
# It might need WebGL canvas to be created first
# Let's check if there's a #pea-app or similar container
r5 = cdp.eval_js("""
(function() {
var divs = document.querySelectorAll("div[id]");
var ids = [];
divs.forEach(function(d) { ids.push(d.id); });
return JSON.stringify(ids.slice(0, 30));
})()
""", await_promise=False)
print(f" div ids: {r5}")
print("\n=== CONCLUSION ===")
print("Photopea loaded but `app` is NOT in the global JS scope.")
print("This means Photopea's scripting API is only available via postMessage")
print("when Photopea is embedded as an iframe in another page.")
print("")
print("The correct approach for batch generation is:")
print(" - The user runs the JSX script manually in Photopea's Script runner")
print(" - OR use a different tool (GIMP Script-Fu, Python PIL)")
return 0
except Exception as e:
print(f"EXCEPTION: {e}")
import traceback
traceback.print_exc()
return 1
finally:
try: cdp.close()
except: pass
try: proc.kill()
except: pass
try: proc.wait(timeout=5)
except: pass
if __name__ == "__main__":
sys.exit(main() or 0)