wuhp commited on
Commit
5d33ebb
·
verified ·
1 Parent(s): b53f356

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +142 -189
app.py CHANGED
@@ -1,319 +1,272 @@
1
  import asyncio
2
  import json
3
  import os
4
- import time
5
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect
6
  from fastapi.responses import HTMLResponse, FileResponse
7
  from playwright.async_api import async_playwright
8
 
9
  app = FastAPI()
10
 
11
- # =========================
12
- # PATHS
13
- # =========================
14
- BASE_DIR = os.getcwd()
15
- DOWNLOAD_DIR = os.path.join(BASE_DIR, "downloads")
16
- SCRIPT_DIR = os.path.join(BASE_DIR, "scripts")
17
 
18
- os.makedirs(DOWNLOAD_DIR, exist_ok=True)
19
- os.makedirs(SCRIPT_DIR, exist_ok=True)
 
 
 
 
20
 
21
- # =========================
22
- # GLOBAL BROWSER STATE
23
- # =========================
24
  browser = None
25
  context = None
26
  page = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  # =========================
29
- # HTML UI (INLINE)
30
  # =========================
31
  HTML = """
32
  <!DOCTYPE html>
33
  <html>
34
  <head>
35
  <meta charset="utf-8">
36
- <title>Browser</title>
37
  <style>
38
- body { margin:0; background:#0e0e0e; color:white; font-family:sans-serif; }
39
- #topbar { display:flex; padding:8px; background:#1a1a1a; }
40
- input { flex:1; padding:6px; background:#222; color:white; border:none; }
41
- button { margin-left:5px; background:#333; color:white; border:none; padding:6px; cursor:pointer; }
42
 
43
  #canvas { width:100vw; height:calc(100vh - 40px); display:block; }
44
 
45
- #menu {
46
- position:absolute;
47
- top:40px;
48
- right:10px;
49
- background:#222;
50
- padding:10px;
51
- display:none;
52
- }
53
-
54
- #editor {
55
  position:absolute;
56
- top:50px;
57
- left:50px;
58
- width:600px;
59
- height:400px;
60
  background:#111;
61
  border:1px solid #444;
62
  display:none;
63
  }
64
 
 
 
 
65
  textarea {
66
- width:100%;
67
- height:80%;
68
- background:#000;
69
- color:#0f0;
70
  border:none;
71
  font-family:monospace;
72
  }
73
-
74
- #downloads {
75
- position:absolute;
76
- bottom:10px;
77
- left:10px;
78
- background:#222;
79
- padding:10px;
80
- max-height:200px;
81
- overflow:auto;
82
- }
83
  </style>
84
  </head>
85
  <body>
86
 
87
- <div id="topbar">
88
- <button onclick="nav('back')">←</button>
89
- <button onclick="nav('forward')">→</button>
90
- <button onclick="nav('reload')">⟳</button>
91
- <input id="url" placeholder="Enter URL"/>
92
- <button onclick="go()">Go</button>
93
- <button onclick="toggleMenu()">⋮</button>
94
  </div>
95
 
96
  <canvas id="canvas"></canvas>
97
 
98
  <div id="menu">
99
- <button onclick="openEditor()">Open Editor</button><br>
100
- <button onclick="runScript()">Run Script</button><br>
101
- <button onclick="loadScripts()">Load Scripts</button><br>
102
  </div>
103
 
104
  <div id="editor">
105
- <textarea id="code">// Example:
106
- let titles = await queryAll("h2");
107
- await saveJson("titles.json", titles.map(t=>t.innerText));
108
  </textarea>
109
- <button onclick="saveScript()">Save</button>
110
- <button onclick="closeEditor()">Close</button>
111
  </div>
112
 
113
- <div id="downloads"></div>
114
-
115
  <script>
116
  let ws = new WebSocket(`ws://${location.host}/ws`);
117
- let canvas = document.getElementById("canvas");
118
- let ctx = canvas.getContext("2d");
119
 
120
- function resize() {
121
- canvas.width = window.innerWidth;
122
- canvas.height = window.innerHeight - 40;
123
  }
124
- window.onresize = resize;
125
  resize();
126
 
127
- ws.onmessage = (e) => {
128
- let blob = new Blob([e.data], {type:'image/jpeg'});
129
- let img = new Image();
130
- img.onload = () => ctx.drawImage(img,0,0,canvas.width,canvas.height);
131
- img.src = URL.createObjectURL(blob);
132
  };
133
 
134
- function go(){
135
- ws.send(JSON.stringify({type:"goto", url:document.getElementById("url").value}));
136
- }
137
 
138
- function nav(type){
139
- ws.send(JSON.stringify({type:type}));
140
  }
141
 
142
- function toggleMenu(){
143
- let m = document.getElementById("menu");
144
- m.style.display = m.style.display === "none" ? "block":"none";
145
- }
146
 
147
- function openEditor(){
148
- document.getElementById("editor").style.display="block";
 
149
  }
150
 
151
- function closeEditor(){
152
- document.getElementById("editor").style.display="none";
153
- }
154
 
155
  function runScript(){
156
- let code = document.getElementById("code").value;
157
- ws.send(JSON.stringify({type:"run", code:code}));
158
  }
159
 
160
- function saveScript(){
161
- let name = prompt("Script name?");
162
- let code = document.getElementById("code").value;
163
- ws.send(JSON.stringify({type:"save", name:name, code:code}));
 
 
164
  }
165
 
166
- function loadScripts(){
167
- ws.send(JSON.stringify({type:"list"}));
168
- }
169
 
170
- canvas.addEventListener("click", e=>{
171
- ws.send(JSON.stringify({
172
- type:"click",
173
- x:e.offsetX,
174
- y:e.offsetY
175
- }));
176
- });
177
-
178
- document.addEventListener("keydown", e=>{
179
- ws.send(JSON.stringify({
180
- type:"key",
181
- key:e.key
182
- }));
183
- });
184
  </script>
185
 
186
  </body>
187
  </html>
188
  """
189
 
 
190
  # =========================
191
- # START PLAYWRIGHT
192
  # =========================
193
- async def start_browser():
194
- global browser, context, page
 
195
  pw = await async_playwright().start()
196
- browser = await pw.chromium.launch(headless=True)
 
 
 
 
 
197
  context = await browser.new_context(viewport={"width":1280,"height":720})
198
  page = await context.new_page()
199
  await page.goto("https://example.com")
200
 
 
201
  @app.on_event("startup")
202
  async def startup():
203
- await start_browser()
 
204
 
205
  # =========================
206
  # ROUTES
207
  # =========================
208
  @app.get("/")
209
- async def index():
210
  return HTMLResponse(HTML)
211
 
 
212
  @app.get("/download/{file}")
213
- async def download(file: str):
214
- return FileResponse(os.path.join(DOWNLOAD_DIR, file))
 
215
 
216
  # =========================
217
  # WEBSOCKET
218
  # =========================
219
  @app.websocket("/ws")
220
- async def websocket(ws: WebSocket):
221
  await ws.accept()
222
 
223
  async def stream():
224
  while True:
225
- img = await page.screenshot(type="jpeg", quality=70)
226
- await ws.send_bytes(img)
227
- await asyncio.sleep(0.03) # ~30 FPS
 
 
 
228
 
229
  asyncio.create_task(stream())
230
 
231
  try:
232
  while True:
233
- data = await ws.receive_text()
234
- msg = json.loads(data)
235
 
236
- if msg["type"] == "goto":
237
- await page.goto(msg["url"])
 
238
 
239
- elif msg["type"] == "back":
240
  await page.go_back()
241
 
242
- elif msg["type"] == "forward":
243
  await page.go_forward()
244
 
245
- elif msg["type"] == "reload":
246
  await page.reload()
247
 
248
- elif msg["type"] == "click":
249
  await page.mouse.click(msg["x"], msg["y"])
250
 
251
- elif msg["type"] == "key":
252
  await page.keyboard.press(msg["key"])
253
 
254
- elif msg["type"] == "save":
255
- path = os.path.join(SCRIPT_DIR, msg["name"] + ".js")
256
- with open(path, "w") as f:
257
- f.write(msg["code"])
258
 
259
- elif msg["type"] == "run":
260
- script = msg["code"]
261
 
262
- wrapped = f"""
263
  async () => {{
264
- const saveTxt = async (name, text) => {{
265
- await fetch('/save_txt', {{
266
- method:'POST',
267
- body:JSON.stringify({{name,text}})
268
- }});
269
- }};
270
- const saveJson = async (name, obj) => {{
271
- await fetch('/save_json', {{
272
- method:'POST',
273
- body:JSON.stringify({{name,obj}})
274
- }});
275
- }};
276
- const saveCsv = async (name, rows) => {{
277
- await fetch('/save_csv', {{
278
- method:'POST',
279
- body:JSON.stringify({{name,rows}})
280
- }});
281
- }};
282
- const queryAll = (sel) => [...document.querySelectorAll(sel)];
283
- {script}
284
  }}
285
- """
286
- await page.evaluate(wrapped)
287
 
288
  except WebSocketDisconnect:
289
- print("Disconnected")
 
290
 
291
  # =========================
292
- # SAVE ROUTES
293
  # =========================
294
- @app.post("/save_txt")
295
- async def save_txt(data: dict):
296
- with open(os.path.join(DOWNLOAD_DIR, data["name"]), "w") as f:
297
- f.write(data["text"])
298
- return {"ok":True}
299
-
300
- @app.post("/save_json")
301
- async def save_json(data: dict):
302
- with open(os.path.join(DOWNLOAD_DIR, data["name"]), "w") as f:
303
- json.dump(data["obj"], f, indent=2)
304
- return {"ok":True}
305
-
306
- @app.post("/save_csv")
307
- async def save_csv(data: dict):
308
- import csv
309
- rows = data["rows"]
310
- if not rows:
311
- return {"ok":False}
312
-
313
- keys = rows[0].keys()
314
- with open(os.path.join(DOWNLOAD_DIR, data["name"]), "w", newline="") as f:
315
- writer = csv.DictWriter(f, fieldnames=keys)
316
- writer.writeheader()
317
- writer.writerows(rows)
318
-
319
- return {"ok":True}
 
1
  import asyncio
2
  import json
3
  import os
4
+ import re
5
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect
6
  from fastapi.responses import HTMLResponse, FileResponse
7
  from playwright.async_api import async_playwright
8
 
9
  app = FastAPI()
10
 
11
+ PORT = int(os.environ.get("PORT", 7860))
 
 
 
 
 
12
 
13
+ BASE = os.getcwd()
14
+ DOWNLOADS = os.path.join(BASE, "downloads")
15
+ SCRIPTS = os.path.join(BASE, "scripts")
16
+
17
+ os.makedirs(DOWNLOADS, exist_ok=True)
18
+ os.makedirs(SCRIPTS, exist_ok=True)
19
 
 
 
 
20
  browser = None
21
  context = None
22
  page = None
23
+ pw = None
24
+
25
+ # =========================
26
+ # SMART NAVIGATION FIX
27
+ # =========================
28
+ def normalize_input(text: str) -> str:
29
+ text = text.strip()
30
+
31
+ if not text:
32
+ return "https://example.com"
33
+
34
+ # already URL
35
+ if text.startswith("http://") or text.startswith("https://"):
36
+ return text
37
+
38
+ # looks like domain
39
+ if "." in text and " " not in text:
40
+ return "https://" + text
41
+
42
+ # fallback search
43
+ q = text.replace(" ", "+")
44
+ return f"https://www.google.com/search?q={q}"
45
+
46
 
47
  # =========================
48
+ # FRONTEND
49
  # =========================
50
  HTML = """
51
  <!DOCTYPE html>
52
  <html>
53
  <head>
54
  <meta charset="utf-8">
55
+ <title>HF Browser</title>
56
  <style>
57
+ body { margin:0; background:#0e0e0e; color:white; font-family:sans-serif; overflow:hidden; }
58
+ #bar { display:flex; padding:8px; background:#1a1a1a; }
59
+ input { flex:1; background:#222; color:white; border:none; padding:6px; }
60
+ button { margin-left:5px; background:#333; color:white; border:none; padding:6px; }
61
 
62
  #canvas { width:100vw; height:calc(100vh - 40px); display:block; }
63
 
64
+ #menu, #editor {
 
 
 
 
 
 
 
 
 
65
  position:absolute;
 
 
 
 
66
  background:#111;
67
  border:1px solid #444;
68
  display:none;
69
  }
70
 
71
+ #menu { top:45px; right:10px; padding:10px; }
72
+ #editor { top:60px; left:60px; width:600px; height:400px; }
73
+
74
  textarea {
75
+ width:100%; height:80%;
76
+ background:black; color:#0f0;
 
 
77
  border:none;
78
  font-family:monospace;
79
  }
 
 
 
 
 
 
 
 
 
 
80
  </style>
81
  </head>
82
  <body>
83
 
84
+ <div id="bar">
85
+ <button onclick="nav('back')">←</button>
86
+ <button onclick="nav('forward')">→</button>
87
+ <button onclick="nav('reload')">⟳</button>
88
+ <input id="url" placeholder="Search or URL"/>
89
+ <button onclick="go()">Go</button>
90
+ <button onclick="menu()">⋮</button>
91
  </div>
92
 
93
  <canvas id="canvas"></canvas>
94
 
95
  <div id="menu">
96
+ <button onclick="openEditor()">Editor</button><br>
97
+ <button onclick="runScript()">Run</button><br>
 
98
  </div>
99
 
100
  <div id="editor">
101
+ <textarea id="code">// JS scraping script
102
+ let links = await queryAll("a");
103
+ await saveJson("links.json", links.map(l=>l.href));
104
  </textarea>
105
+ <button onclick="save()">Save</button>
106
+ <button onclick="closeEditor()">Close</button>
107
  </div>
108
 
 
 
109
  <script>
110
  let ws = new WebSocket(`ws://${location.host}/ws`);
111
+ let c = document.getElementById("canvas");
112
+ let x = c.getContext("2d");
113
 
114
+ function resize(){
115
+ c.width = innerWidth;
116
+ c.height = innerHeight - 40;
117
  }
118
+ onresize = resize;
119
  resize();
120
 
121
+ ws.onmessage = (e)=>{
122
+ let b = new Blob([e.data], {type:'image/jpeg'});
123
+ let i = new Image();
124
+ i.onload = ()=>x.drawImage(i,0,0,c.width,c.height);
125
+ i.src = URL.createObjectURL(b);
126
  };
127
 
128
+ function send(d){ ws.send(JSON.stringify(d)); }
 
 
129
 
130
+ function go(){
131
+ send({type:"goto", url:document.getElementById("url").value});
132
  }
133
 
134
+ function nav(t){ send({type:t}); }
 
 
 
135
 
136
+ function menu(){
137
+ let m=document.getElementById("menu");
138
+ m.style.display = m.style.display==="block"?"none":"block";
139
  }
140
 
141
+ function openEditor(){ document.getElementById("editor").style.display="block"; }
142
+ function closeEditor(){ document.getElementById("editor").style.display="none"; }
 
143
 
144
  function runScript(){
145
+ send({type:"run", code:document.getElementById("code").value});
 
146
  }
147
 
148
+ function save(){
149
+ send({
150
+ type:"save",
151
+ name:prompt("name"),
152
+ code:document.getElementById("code").value
153
+ });
154
  }
155
 
156
+ canvas.onclick = e=>{
157
+ send({type:"click", x:e.offsetX, y:e.offsetY});
158
+ };
159
 
160
+ document.onkeydown = e=>{
161
+ send({type:"key", key:e.key});
162
+ };
 
 
 
 
 
 
 
 
 
 
 
163
  </script>
164
 
165
  </body>
166
  </html>
167
  """
168
 
169
+
170
  # =========================
171
+ # PLAYWRIGHT START
172
  # =========================
173
+ async def start():
174
+ global browser, context, page, pw
175
+
176
  pw = await async_playwright().start()
177
+
178
+ browser = await pw.chromium.launch(
179
+ headless=True,
180
+ args=["--no-sandbox", "--disable-dev-shm-usage"]
181
+ )
182
+
183
  context = await browser.new_context(viewport={"width":1280,"height":720})
184
  page = await context.new_page()
185
  await page.goto("https://example.com")
186
 
187
+
188
  @app.on_event("startup")
189
  async def startup():
190
+ await start()
191
+
192
 
193
  # =========================
194
  # ROUTES
195
  # =========================
196
  @app.get("/")
197
+ async def home():
198
  return HTMLResponse(HTML)
199
 
200
+
201
  @app.get("/download/{file}")
202
+ async def dl(file: str):
203
+ return FileResponse(os.path.join(DOWNLOADS, file))
204
+
205
 
206
  # =========================
207
  # WEBSOCKET
208
  # =========================
209
  @app.websocket("/ws")
210
+ async def ws(ws: WebSocket):
211
  await ws.accept()
212
 
213
  async def stream():
214
  while True:
215
+ try:
216
+ img = await page.screenshot(type="jpeg", quality=60)
217
+ await ws.send_bytes(img)
218
+ await asyncio.sleep(0.04)
219
+ except:
220
+ break
221
 
222
  asyncio.create_task(stream())
223
 
224
  try:
225
  while True:
226
+ msg = json.loads(await ws.receive_text())
227
+ t = msg["type"]
228
 
229
+ if t == "goto":
230
+ url = normalize_input(msg["url"])
231
+ await page.goto(url)
232
 
233
+ elif t == "back":
234
  await page.go_back()
235
 
236
+ elif t == "forward":
237
  await page.go_forward()
238
 
239
+ elif t == "reload":
240
  await page.reload()
241
 
242
+ elif t == "click":
243
  await page.mouse.click(msg["x"], msg["y"])
244
 
245
+ elif t == "key":
246
  await page.keyboard.press(msg["key"])
247
 
248
+ elif t == "save":
249
+ path = os.path.join(SCRIPTS, msg["name"] + ".js")
250
+ open(path, "w").write(msg["code"])
 
251
 
252
+ elif t == "run":
253
+ code = msg["code"]
254
 
255
+ await page.evaluate(f"""
256
  async () => {{
257
+ const queryAll = (s) => [...document.querySelectorAll(s)];
258
+ const saveJson = async (n,o)=>fetch('/save',{{method:'POST'}});
259
+ {code}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  }}
261
+ """)
 
262
 
263
  except WebSocketDisconnect:
264
+ pass
265
+
266
 
267
  # =========================
268
+ # ENTRY (HF SAFE)
269
  # =========================
270
+ if __name__ == "__main__":
271
+ import uvicorn
272
+ uvicorn.run(app, host="0.0.0.0", port=PORT)