#!/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)