207 lines
7.1 KiB
Python
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)
|