Forge / app.py
Sebebeb's picture
Update app.py
e6c829c verified
Raw History Blame Contribute Delete
6.09 kB
import os
import threading
from flask import Flask, request, Response, jsonify
from playwright.sync_api import sync_playwright
app = Flask(__name__)
VIEWPORT = {"width": 1280, "height": 800}
_lock = threading.Lock()
_state = {"playwright": None, "browser": None, "page": None}
USER_AGENT = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
)
STEALTH_JS = """
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});
window.chrome = { runtime: {} };
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
parameters.name === 'notifications'
? Promise.resolve({ state: Notification.permission })
: originalQuery(parameters)
);
"""
def get_page():
if _state["page"] is None:
_state["playwright"] = sync_playwright().start()
_state["browser"] = _state["playwright"].chromium.launch(
headless=True,
args=[
"--no-sandbox",
"--disable-dev-shm-usage",
"--disable-blink-features=AutomationControlled",
],
ignore_default_args=["--enable-automation"],
)
context = _state["browser"].new_context(
viewport=VIEWPORT,
user_agent=USER_AGENT,
locale="en-US",
timezone_id="America/New_York",
)
context.add_init_script(STEALTH_JS)
_state["page"] = context.new_page()
_state["page"].goto("https://example.com", wait_until="domcontentloaded")
return _state["page"]
HOME = """<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Remote Browser</title>
<style>
html,body{margin:0;height:100%;background:#111;
font-family:-apple-system,Helvetica,Arial,sans-serif;}
#bar{display:flex;gap:8px;padding:10px 14px;background:#1e1e1e;}
#bar input{flex:1;padding:8px 10px;border-radius:6px;border:none;font-size:14px;}
#bar button{padding:8px 16px;border-radius:6px;border:none;
background:#4f8cff;color:#fff;font-size:14px;cursor:pointer;}
#stage{width:100%;height:calc(100vh - 52px);overflow:auto;background:#000;
display:flex;justify-content:center;}
#view{cursor:pointer;max-width:100%;display:block;}
</style></head>
<body>
<div id="bar">
<input id="url" value="https://example.com" />
<button onclick="go()">Go</button>
<button onclick="back()">&#8592;</button>
</div>
<div id="stage"><img id="view" src="/screenshot?t=0" /></div>
<script>
const img = document.getElementById('view');
const urlBox = document.getElementById('url');
const vw = __VW__, vh = __VH__;
function refresh() { img.src = '/screenshot?t=' + Date.now(); }
async function go() {
await fetch('/go', {method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({url: urlBox.value})});
setTimeout(refresh, 400);
}
async function back() {
await fetch('/back', {method:'POST'});
setTimeout(refresh, 400);
}
img.addEventListener('click', async (e) => {
const rect = img.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width * vw;
const y = (e.clientY - rect.top) / rect.height * vh;
await fetch('/click', {method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({x, y})});
setTimeout(refresh, 150);
});
document.addEventListener('keydown', async (e) => {
if (document.activeElement === urlBox) return;
e.preventDefault();
await fetch('/key', {method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({key: e.key})});
refresh();
});
document.getElementById('stage').addEventListener('wheel', async (e) => {
await fetch('/scroll', {method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({dy: e.deltaY})});
refresh();
});
setInterval(refresh, 500);
</script>
</body></html>"""
@app.route("/")
def home():
html = HOME.replace("__VW__", str(VIEWPORT["width"])).replace(
"__VH__", str(VIEWPORT["height"])
)
return html
@app.route("/screenshot")
def screenshot():
with _lock:
page = get_page()
try:
data = page.screenshot(type="jpeg", quality=100)
except Exception:
data = b""
return Response(data, content_type="image/jpeg")
@app.route("/go", methods=["POST"])
def go():
url = (request.json or {}).get("url", "").strip()
if not url:
return jsonify(ok=False)
if not url.startswith(("http://", "https://")):
url = "https://" + url
with _lock:
page = get_page()
try:
page.goto(url, wait_until="domcontentloaded", timeout=20000)
except Exception as e:
return jsonify(ok=False, error=str(e))
return jsonify(ok=True)
@app.route("/back", methods=["POST"])
def back():
with _lock:
page = get_page()
try:
page.go_back(timeout=10000)
except Exception:
pass
return jsonify(ok=True)
@app.route("/click", methods=["POST"])
def click():
data = request.json or {}
with _lock:
page = get_page()
try:
page.mouse.click(data.get("x", 0), data.get("y", 0))
except Exception:
pass
return jsonify(ok=True)
@app.route("/key", methods=["POST"])
def key():
data = request.json or {}
with _lock:
page = get_page()
try:
page.keyboard.press(data.get("key", ""))
except Exception:
pass
return jsonify(ok=True)
@app.route("/scroll", methods=["POST"])
def scroll():
data = request.json or {}
with _lock:
page = get_page()
try:
page.mouse.wheel(0, data.get("dy", 0))
except Exception:
pass
return jsonify(ok=True)
if __name__ == "__main__":
port = int(os.environ.get("PORT", 7860))
app.run(host="0.0.0.0", port=port, threaded=False)