CORVO-AI commited on
Commit
4b62bbf
·
verified ·
1 Parent(s): d5f5403

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +205 -281
app.py CHANGED
@@ -6,54 +6,42 @@ import time
6
 
7
  app = Flask(__name__)
8
 
9
- # Global variables to store workspace and bot IDs
10
- GLOBAL_WORKSPACE_ID = None
11
- GLOBAL_BOT_ID = None
12
-
13
-
14
- # Authorization value used in requests (should be updated with a valid Authorization),
15
  TOKEN = "Bearer bp_pat_vTuxol25N0ymBpYaWqtWpFfGPKt260IfT784"
 
16
  # -------------------------------------------------------------------
17
- # Helper functions for random bot/workspace names
18
  # -------------------------------------------------------------------
19
  def generate_random_name(length=5):
20
- """Generate a random name for workspace or bot"""
21
  return ''.join(random.choices(string.ascii_letters, k=length))
22
 
23
  # -------------------------------------------------------------------
24
- # Functions to create/delete workspaces and bots
25
  # -------------------------------------------------------------------
26
  def create_workspace():
27
- """Create a new workspace and return its ID"""
28
- ws_url = "https://api.botpress.cloud/v1/admin/workspaces"
29
  headers = {
30
  "User-Agent": "Mozilla/5.0",
31
  "Authorization": TOKEN
32
  }
33
  payload = {"name": generate_random_name()}
34
-
35
  try:
36
- response = requests.post(ws_url, headers=headers, json=payload)
37
- if response.status_code == 200:
38
- response_json = response.json()
39
- workspace_id = response_json.get('id')
40
- print(f"Successfully created workspace: {workspace_id}")
41
- return workspace_id
42
- else:
43
- print(f"Workspace creation failed with: {response.status_code}, {response.text}")
44
- return None
45
  except Exception as e:
46
- print(f"Error creating workspace: {str(e)}")
47
- return None
48
-
49
 
50
  def create_bot(workspace_id):
51
- """Create a new bot in the specified workspace and return its ID"""
52
  if not workspace_id:
53
- print("Cannot create bot: No workspace ID provided")
54
  return None
55
-
56
- bot_url = "https://api.botpress.cloud/v1/admin/bots"
57
  headers = {
58
  "User-Agent": "Mozilla/5.0",
59
  "x-workspace-id": workspace_id,
@@ -61,40 +49,28 @@ def create_bot(workspace_id):
61
  "Content-Type": "application/json"
62
  }
63
  payload = {"name": generate_random_name()}
64
-
65
  try:
66
- response = requests.post(bot_url, headers=headers, json=payload)
67
- if response.status_code == 200:
68
- response_json = response.json()
69
- bot_id = response_json.get("bot", {}).get("id")
70
- if not bot_id:
71
- print("Bot ID not found in the response.")
72
- return None
73
-
74
- print(f"Successfully created bot: {bot_id} in workspace: {workspace_id}")
75
-
76
- # Install integration for the new bot
77
- integration_success = install_bot_integration(bot_id, workspace_id)
78
- if integration_success:
79
- print(f"Successfully installed integration for bot {bot_id}")
80
  return bot_id
81
- else:
82
- print(f"Failed to install integration for bot {bot_id}")
83
- return bot_id # Still return the bot ID even if integration fails
84
- else:
85
- print(f"Bot creation failed with: {response.status_code}, {response.text}")
86
- return None
87
  except Exception as e:
88
- print(f"Error creating bot: {str(e)}")
89
- return None
90
-
91
 
92
  def install_bot_integration(bot_id, workspace_id):
93
- """Install required integration for the bot to function properly"""
94
  if not bot_id or not workspace_id:
95
- print("Cannot install integration: Missing bot ID or workspace ID")
96
  return False
97
-
98
  url = f"https://api.botpress.cloud/v1/admin/bots/{bot_id}"
99
  headers = {
100
  "User-Agent": "Mozilla/5.0",
@@ -103,7 +79,6 @@ def install_bot_integration(bot_id, workspace_id):
103
  "x-bot-id": bot_id,
104
  "x-workspace-id": workspace_id
105
  }
106
- # Integration payload
107
  payload = {
108
  "integrations": {
109
  "intver_01JSCCBZGK79MXK2FSHKHTRKFD": {
@@ -111,90 +86,62 @@ def install_bot_integration(bot_id, workspace_id):
111
  }
112
  }
113
  }
114
-
115
  try:
116
- response = requests.put(url, headers=headers, json=payload)
117
- if response.status_code == 200:
118
- print(f"Successfully installed integration for bot {bot_id}")
119
  return True
120
- else:
121
- print(f"Failed to install integration: {response.status_code}, {response.text}")
122
- return False
123
  except Exception as e:
124
- print(f"Error installing integration: {str(e)}")
125
- return False
126
-
127
 
128
  def delete_bot(bot_id, workspace_id):
129
- """Delete a bot from the specified workspace"""
130
  if not bot_id or not workspace_id:
131
- print("Cannot delete bot: Missing bot ID or workspace ID")
132
  return False
133
-
134
  url = f"https://api.botpress.cloud/v1/admin/bots/{bot_id}"
135
  headers = {
136
  "User-Agent": "Mozilla/5.0",
137
  "x-workspace-id": workspace_id,
138
  "Authorization": TOKEN
139
  }
140
-
141
  try:
142
- response = requests.delete(url, headers=headers)
143
- if response.status_code in [200, 204]:
144
- print(f"Successfully deleted bot: {bot_id}")
145
  return True
146
- else:
147
- print(f"Failed to delete bot: {response.status_code}, {response.text}")
148
- return False
149
  except Exception as e:
150
- print(f"Error deleting bot: {str(e)}")
151
- return False
152
-
153
 
154
  def delete_workspace(workspace_id):
155
- """Delete a workspace"""
156
  if not workspace_id:
157
- print("Cannot delete workspace: No workspace ID provided")
158
  return False
159
-
160
  url = f"https://api.botpress.cloud/v1/admin/workspaces/{workspace_id}"
161
  headers = {
162
  "User-Agent": "Mozilla/5.0",
163
  "Authorization": TOKEN
164
  }
165
-
166
  try:
167
- response = requests.delete(url, headers=headers)
168
- if response.status_code in [200, 204]:
169
- print(f"Successfully deleted workspace: {workspace_id}")
170
  return True
171
- else:
172
- print(f"Failed to delete workspace: {response.status_code}, {response.text}")
173
- return False
174
  except Exception as e:
175
- print(f"Error deleting workspace: {str(e)}")
176
- return False
177
-
178
 
179
  # -------------------------------------------------------------------
180
- # Browse PAGE Function
181
  # -------------------------------------------------------------------
182
- def browse_pages(urls, wait_for=350, bot_id=None, workspace_id=None):
183
- """
184
- Browses specific web pages using the Botpress API
185
- Args:
186
- urls (list): List of URLs to browse
187
- wait_for (int): Wait time in milliseconds
188
- bot_id (str): The bot ID to use for the request
189
- workspace_id (str): The workspace ID to use for the request
190
- Returns:
191
- tuple: (browse_results, bot_id, workspace_id)
192
- """
193
  if not bot_id or not workspace_id:
194
- print("Cannot perform browse: Missing bot ID or workspace ID")
195
- return {"error": "Missing bot ID or workspace ID"}, bot_id, workspace_id
196
 
197
- # Prepare the headers
198
  headers = {
199
  "User-Agent": "Mozilla/5.0",
200
  "x-bot-id": bot_id,
@@ -202,212 +149,189 @@ def browse_pages(urls, wait_for=350, bot_id=None, workspace_id=None):
202
  "Content-Type": "application/json",
203
  "Authorization": TOKEN
204
  }
205
-
206
- # Prepare the payload for the API
207
  payload = {
208
- "type": "browser:browsePages",
209
  "input": {
210
- "urls": urls,
211
- "waitFor": wait_for
 
 
212
  }
213
  }
214
 
215
- botpress_url = "https://api.botpress.cloud/v1/chat/actions"
216
  max_retries = 3
217
- timeout = 60
218
-
219
- # Flag to track if we need to create new IDs due to quota exceeded
220
- quota_exceeded = False
221
-
222
- # Attempt to send the request
223
  for attempt in range(max_retries):
224
  try:
225
- print(f"Attempt {attempt+1}: Sending browse request to Botpress API with bot_id={bot_id}, workspace_id={workspace_id}")
226
- response = requests.post(botpress_url, json=payload, headers=headers, timeout=timeout)
227
-
228
- # If successful (200)
229
- if response.status_code == 200:
230
- data = response.json()
231
- print(f"Successfully received browse results from Botpress API")
232
- return data, bot_id, workspace_id
233
-
234
- # Check for quota exceeded error specifically
235
- elif response.status_code == 403:
236
- error_data = response.json()
237
- error_message = error_data.get('message', '')
238
-
239
- # Check if this is the specific quota exceeded error
240
- if "has reached its usage limit for ai spend" in error_message:
241
- print(f"Quota exceeded error detected: {error_message}")
242
- quota_exceeded = True
243
- break
244
- else:
245
- print(f"Received 403 error but not quota exceeded: {error_message}")
246
- if attempt < max_retries - 1:
247
- time.sleep(2)
248
- continue
249
- else:
250
- return {"error": f"Unable to get browse results (Error 403)."}, bot_id, workspace_id
251
-
252
- # Handle network errors or timeouts (just retry)
253
- elif response.status_code in [404, 408, 502, 503, 504]:
254
- print(f"Received error {response.status_code}. Retrying...")
255
- time.sleep(3) # Wait before retrying
256
  continue
257
-
258
- # Any other error status code
 
 
259
  else:
260
- print(f"Received unexpected error: {response.status_code}, {response.text}")
261
- if attempt < max_retries - 1:
262
- time.sleep(2)
263
- continue
264
- else:
265
- return {"error": f"Unable to get browse results (Error {response.status_code})."}, bot_id, workspace_id
266
-
267
  except requests.exceptions.Timeout:
268
- print(f"Request timed out. Retrying...")
269
  if attempt < max_retries - 1:
270
- time.sleep(2)
271
  continue
272
- else:
273
- return {"error": "The browse request timed out. Please try again."}, bot_id, workspace_id
274
-
275
  except Exception as e:
276
- print(f"Error during request: {str(e)}")
277
  if attempt < max_retries - 1:
278
- time.sleep(2)
279
  continue
280
- else:
281
- return {"error": f"Unable to get browse results: {str(e)}"}, bot_id, workspace_id
282
-
283
- # If quota exceeded, we need to create new resources
284
- if quota_exceeded:
285
- print("Quota exceeded. Creating new workspace and bot...")
286
-
287
- # First delete the bot, then the workspace (in that order)
288
- if bot_id and workspace_id:
289
- print(f"Deleting bot {bot_id} first...")
290
- delete_success = delete_bot(bot_id, workspace_id)
291
- if delete_success:
292
- print(f"Successfully deleted bot {bot_id}")
293
- else:
294
- print(f"Failed to delete bot {bot_id}")
295
 
296
- # Wait 3 seconds before deleting the workspace
297
- print("Waiting 3 seconds before deleting workspace...")
298
- time.sleep(3)
299
-
300
- print(f"Now deleting workspace {workspace_id}...")
301
- ws_delete_success = delete_workspace(workspace_id)
302
- if ws_delete_success:
303
- print(f"Successfully deleted workspace {workspace_id}")
304
- else:
305
- print(f"Failed to delete workspace {workspace_id}")
306
-
307
- # Create new workspace
308
- new_workspace_id = create_workspace()
309
- if not new_workspace_id:
310
- return {"error": "Failed to create a new workspace after quota exceeded. Please try again later."}, bot_id, workspace_id
311
-
312
- # Create new bot in the new workspace
313
- new_bot_id = create_bot(new_workspace_id)
314
- if not new_bot_id:
315
- return {"error": "Failed to create a new bot after quota exceeded. Please try again later."}, new_workspace_id, workspace_id
316
-
317
- # Update headers with new bot ID
318
- headers["x-bot-id"] = new_bot_id
319
-
320
- # Try one more time with the new IDs
321
- try:
322
- print(f"Retrying with new bot_id={new_bot_id}, workspace_id={new_workspace_id}")
323
- retry_response = requests.post(botpress_url, json=payload, headers=headers, timeout=timeout)
324
-
325
- if retry_response.status_code == 200:
326
- data = retry_response.json()
327
- print(f"Successfully received browse results with new IDs")
328
- return data, new_bot_id, new_workspace_id
329
- else:
330
- print(f"Failed with new IDs: {retry_response.status_code}, {retry_response.text}")
331
- return {"error": f"Unable to get browse results with new credentials."}, new_bot_id, new_workspace_id
332
-
333
- except Exception as e:
334
- print(f"Error with new IDs: {str(e)}")
335
- return {"error": f"Unable to get browse results with new credentials: {str(e)}"}, new_bot_id, new_workspace_id
336
-
337
- # Should not reach here due to the handling in the loop
338
- return {"error": "Unable to get browse results."}, bot_id, workspace_id
339
 
 
 
 
 
 
340
 
341
  # -------------------------------------------------------------------
342
- # Flask Endpoints
343
  # -------------------------------------------------------------------
344
- @app.route("/browse", methods=["POST"])
345
- def browse_endpoint():
346
  """
347
- Endpoint for browsing specific web pages.
348
- Expects JSON with:
 
 
 
 
 
 
349
  {
350
- "urls": ["https://example.com", "https://example.org"],
351
- "wait_for": 350 // Optional, defaults to 350ms
 
 
 
 
 
 
 
 
 
 
 
 
352
  }
353
- Returns the raw browse results JSON from the Botpress API
 
 
 
354
  """
355
- global GLOBAL_WORKSPACE_ID, GLOBAL_BOT_ID
356
-
357
- # Parse JSON from request
358
- data = request.get_json(force=True)
359
- urls = data.get("urls", [])
360
- wait_for = data.get("wait_for", 350)
361
-
362
- # Validate URLs
363
- if not urls or not isinstance(urls, list):
364
- return jsonify({"error": "URLs parameter is required and must be a list"}), 400
365
-
366
- # Validate wait_for
367
  try:
368
- wait_for = int(wait_for)
369
- if wait_for <= 0:
370
- wait_for = 350
371
- print(f"Invalid wait_for value. Using default: {wait_for}")
372
- except (ValueError, TypeError):
373
- wait_for = 350
374
- print(f"Invalid wait_for format. Using default: {wait_for}")
375
-
376
- # If we don't yet have a workspace or bot, create them
377
- if not GLOBAL_WORKSPACE_ID or not GLOBAL_BOT_ID:
378
- print("No existing IDs found. Creating new workspace and bot...")
379
- GLOBAL_WORKSPACE_ID = create_workspace()
380
- if GLOBAL_WORKSPACE_ID:
381
- GLOBAL_BOT_ID = create_bot(GLOBAL_WORKSPACE_ID)
382
 
383
- # If creation failed
384
- if not GLOBAL_WORKSPACE_ID or not GLOBAL_BOT_ID:
385
- return jsonify({"error": "Failed to initialize browse capability. Please try again later."}), 500
386
-
387
- # Call our browse function
388
- print(f"Sending browse request with urls='{urls}', wait_for={wait_for}")
389
- print(f"Using existing bot_id={GLOBAL_BOT_ID}, workspace_id={GLOBAL_WORKSPACE_ID}")
390
-
391
- browse_results, updated_bot_id, updated_workspace_id = browse_pages(
392
- urls,
393
- wait_for,
394
- GLOBAL_BOT_ID,
395
- GLOBAL_WORKSPACE_ID
396
- )
397
-
398
- # Update global IDs if they changed
399
- if updated_bot_id != GLOBAL_BOT_ID or updated_workspace_id != GLOBAL_WORKSPACE_ID:
400
- print(f"Updating global IDs: bot_id={updated_bot_id}, workspace_id={updated_workspace_id}")
401
- GLOBAL_BOT_ID = updated_bot_id
402
- GLOBAL_WORKSPACE_ID = updated_workspace_id
403
 
404
- # Return the raw API response
405
- return jsonify(browse_results)
 
 
406
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
 
408
  # -------------------------------------------------------------------
409
  # Run the Flask app
410
  # -------------------------------------------------------------------
411
-
412
  if __name__ == "__main__":
413
  app.run(host="0.0.0.0", port=7860, debug=True)
 
6
 
7
  app = Flask(__name__)
8
 
9
+ # Authorization value used in requests (update with a valid Authorization)
 
 
 
 
 
10
  TOKEN = "Bearer bp_pat_vTuxol25N0ymBpYaWqtWpFfGPKt260IfT784"
11
+
12
  # -------------------------------------------------------------------
13
+ # Helper functions
14
  # -------------------------------------------------------------------
15
  def generate_random_name(length=5):
 
16
  return ''.join(random.choices(string.ascii_letters, k=length))
17
 
18
  # -------------------------------------------------------------------
19
+ # Workspace and Bot management
20
  # -------------------------------------------------------------------
21
  def create_workspace():
22
+ url = "https://api.botpress.cloud/v1/admin/workspaces"
 
23
  headers = {
24
  "User-Agent": "Mozilla/5.0",
25
  "Authorization": TOKEN
26
  }
27
  payload = {"name": generate_random_name()}
 
28
  try:
29
+ r = requests.post(url, headers=headers, json=payload, timeout=60)
30
+ if r.status_code == 200:
31
+ ws_id = r.json().get("id")
32
+ if ws_id:
33
+ print(f"Workspace created: {ws_id}")
34
+ return ws_id
35
+ print(f"Workspace creation failed: {r.status_code} {r.text}")
 
 
36
  except Exception as e:
37
+ print(f"Error creating workspace: {e}")
38
+ return None
 
39
 
40
  def create_bot(workspace_id):
 
41
  if not workspace_id:
42
+ print("No workspace_id provided for bot creation")
43
  return None
44
+ url = "https://api.botpress.cloud/v1/admin/bots"
 
45
  headers = {
46
  "User-Agent": "Mozilla/5.0",
47
  "x-workspace-id": workspace_id,
 
49
  "Content-Type": "application/json"
50
  }
51
  payload = {"name": generate_random_name()}
 
52
  try:
53
+ r = requests.post(url, headers=headers, json=payload, timeout=60)
54
+ if r.status_code == 200:
55
+ bot_id = r.json().get("bot", {}).get("id")
56
+ if bot_id:
57
+ print(f"Bot created: {bot_id} in workspace: {workspace_id}")
58
+ # Optionally install integration (same as original)
59
+ installed = install_bot_integration(bot_id, workspace_id)
60
+ if installed:
61
+ print(f"Integration installed for bot {bot_id}")
62
+ else:
63
+ print(f"Integration install failed for bot {bot_id} (continuing)")
 
 
 
64
  return bot_id
65
+ print(f"Bot creation failed: {r.status_code} {r.text}")
 
 
 
 
 
66
  except Exception as e:
67
+ print(f"Error creating bot: {e}")
68
+ return None
 
69
 
70
  def install_bot_integration(bot_id, workspace_id):
 
71
  if not bot_id or not workspace_id:
72
+ print("Missing bot_id or workspace_id for integration")
73
  return False
 
74
  url = f"https://api.botpress.cloud/v1/admin/bots/{bot_id}"
75
  headers = {
76
  "User-Agent": "Mozilla/5.0",
 
79
  "x-bot-id": bot_id,
80
  "x-workspace-id": workspace_id
81
  }
 
82
  payload = {
83
  "integrations": {
84
  "intver_01JSCCBZGK79MXK2FSHKHTRKFD": {
 
86
  }
87
  }
88
  }
 
89
  try:
90
+ r = requests.put(url, headers=headers, json=payload, timeout=60)
91
+ if r.status_code == 200:
 
92
  return True
93
+ print(f"Integration install failed: {r.status_code} {r.text}")
 
 
94
  except Exception as e:
95
+ print(f"Error installing integration: {e}")
96
+ return False
 
97
 
98
  def delete_bot(bot_id, workspace_id):
 
99
  if not bot_id or not workspace_id:
100
+ print("Missing bot_id or workspace_id for bot deletion")
101
  return False
 
102
  url = f"https://api.botpress.cloud/v1/admin/bots/{bot_id}"
103
  headers = {
104
  "User-Agent": "Mozilla/5.0",
105
  "x-workspace-id": workspace_id,
106
  "Authorization": TOKEN
107
  }
 
108
  try:
109
+ r = requests.delete(url, headers=headers, timeout=60)
110
+ if r.status_code in [200, 204]:
111
+ print(f"Bot deleted: {bot_id}")
112
  return True
113
+ print(f"Bot deletion failed: {r.status_code} {r.text}")
 
 
114
  except Exception as e:
115
+ print(f"Error deleting bot: {e}")
116
+ return False
 
117
 
118
  def delete_workspace(workspace_id):
 
119
  if not workspace_id:
120
+ print("Missing workspace_id for workspace deletion")
121
  return False
 
122
  url = f"https://api.botpress.cloud/v1/admin/workspaces/{workspace_id}"
123
  headers = {
124
  "User-Agent": "Mozilla/5.0",
125
  "Authorization": TOKEN
126
  }
 
127
  try:
128
+ r = requests.delete(url, headers=headers, timeout=60)
129
+ if r.status_code in [200, 204]:
130
+ print(f"Workspace deleted: {workspace_id}")
131
  return True
132
+ print(f"Workspace deletion failed: {r.status_code} {r.text}")
 
 
133
  except Exception as e:
134
+ print(f"Error deleting workspace: {e}")
135
+ return False
 
136
 
137
  # -------------------------------------------------------------------
138
+ # Capture Screenshot (Botpress chat action)
139
  # -------------------------------------------------------------------
140
+ def capture_screenshot(url, width, height, full_page, bot_id, workspace_id, timeout=120):
 
 
 
 
 
 
 
 
 
 
141
  if not bot_id or not workspace_id:
142
+ return {"error": "Missing bot_id or workspace_id"}
 
143
 
144
+ api_url = "https://api.botpress.cloud/v1/chat/actions"
145
  headers = {
146
  "User-Agent": "Mozilla/5.0",
147
  "x-bot-id": bot_id,
 
149
  "Content-Type": "application/json",
150
  "Authorization": TOKEN
151
  }
 
 
152
  payload = {
153
+ "type": "browser:captureScreenshot",
154
  "input": {
155
+ "url": url,
156
+ "width": width,
157
+ "height": height,
158
+ "fullPage": bool(full_page)
159
  }
160
  }
161
 
 
162
  max_retries = 3
 
 
 
 
 
 
163
  for attempt in range(max_retries):
164
  try:
165
+ print(f"Capture attempt {attempt+1} for {url}")
166
+ r = requests.post(api_url, headers=headers, json=payload, timeout=timeout)
167
+ if r.status_code == 200:
168
+ data = r.json()
169
+ # Expecting:
170
+ # { "output": {"imageUrl": "...", "htmlUrl": "..."}, "meta": {"cached": false}}
171
+ return data
172
+ elif r.status_code in [404, 408, 502, 503, 504]:
173
+ print(f"Transient error {r.status_code}: retrying...")
174
+ time.sleep(2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  continue
176
+ elif r.status_code == 403:
177
+ print(f"Forbidden (403): {r.text}")
178
+ # Not rotating bot/workspace here; per-request lifecycle handles cleanup
179
+ return {"error": "Forbidden", "status": 403, "details": safe_text(r)}
180
  else:
181
+ print(f"Unexpected {r.status_code}: {r.text}")
182
+ return {"error": "Unexpected error", "status": r.status_code, "details": safe_text(r)}
 
 
 
 
 
183
  except requests.exceptions.Timeout:
184
+ print("Request timed out, retrying...")
185
  if attempt < max_retries - 1:
 
186
  continue
187
+ return {"error": "Timeout"}
 
 
188
  except Exception as e:
189
+ print(f"Exception: {e}")
190
  if attempt < max_retries - 1:
191
+ time.sleep(1)
192
  continue
193
+ return {"error": str(e)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
 
195
+ return {"error": "Failed after retries"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
+ def safe_text(response):
198
+ try:
199
+ return response.text
200
+ except Exception:
201
+ return "<unavailable>"
202
 
203
  # -------------------------------------------------------------------
204
+ # Flask Endpoint
205
  # -------------------------------------------------------------------
206
+ @app.route("/capture", methods=["POST"])
207
+ def capture_endpoint():
208
  """
209
+ JSON body example:
210
+ {
211
+ "urls": ["https://x.com/USTreasury", "https://example.com"], // required, array of one or more
212
+ "width": 10850, // optional, default 1080
213
+ "height": 1920, // optional, default 1920
214
+ "fullPage": true // optional, default true
215
+ }
216
+ Response:
217
  {
218
+ "results": [
219
+ {
220
+ "url": "...",
221
+ "output": {
222
+ "imageUrl": "...png",
223
+ "htmlUrl": "...html"
224
+ },
225
+ "meta": { "cached": false }
226
+ },
227
+ { ... }
228
+ ],
229
+ "errors": [
230
+ { "url": "...", "error": "...", "status": 403, "details": "..." }
231
+ ]
232
  }
233
+ Behavior:
234
+ - Create workspace, then bot.
235
+ - Run capture for all URLs with the same bot/workspace.
236
+ - Delete bot first, then workspace, at the end of the request.
237
  """
238
+ # Parse and validate body
 
 
 
 
 
 
 
 
 
 
 
239
  try:
240
+ body = request.get_json(force=True) or {}
241
+ except Exception:
242
+ return jsonify({"error": "Invalid JSON"}), 400
 
 
 
 
 
 
 
 
 
 
 
243
 
244
+ urls = body.get("urls")
245
+ if not urls or not isinstance(urls, list):
246
+ return jsonify({"error": "urls is required and must be a list"}), 400
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
 
248
+ # Optional params
249
+ width = body.get("width", 1080)
250
+ height = body.get("height", 1920)
251
+ full_page = body.get("fullPage", True)
252
 
253
+ # Normalize numeric params
254
+ try:
255
+ width = int(width)
256
+ if width <= 0:
257
+ width = 1080
258
+ except Exception:
259
+ width = 1080
260
+ try:
261
+ height = int(height)
262
+ if height <= 0:
263
+ height = 1920
264
+ except Exception:
265
+ height = 1920
266
+ full_page = bool(full_page)
267
+
268
+ workspace_id = None
269
+ bot_id = None
270
+
271
+ # Create (Workspace then Bot)
272
+ workspace_id = create_workspace()
273
+ if not workspace_id:
274
+ return jsonify({"error": "Failed to create workspace"}), 500
275
+
276
+ bot_id = create_bot(workspace_id)
277
+ if not bot_id:
278
+ # Cleanup: if bot creation fails, delete workspace
279
+ delete_workspace(workspace_id)
280
+ return jsonify({"error": "Failed to create bot"}), 500
281
+
282
+ results = []
283
+ errors = []
284
+
285
+ # Process multiple URLs using the same bot/workspace
286
+ for url in urls:
287
+ if not isinstance(url, str) or not url.strip():
288
+ errors.append({"url": url, "error": "Invalid URL"})
289
+ continue
290
+
291
+ capture = capture_screenshot(
292
+ url=url.strip(),
293
+ width=width,
294
+ height=height,
295
+ full_page=full_page,
296
+ bot_id=bot_id,
297
+ workspace_id=workspace_id
298
+ )
299
+
300
+ if isinstance(capture, dict) and capture.get("error"):
301
+ errors.append({"url": url, **capture})
302
+ else:
303
+ # Ensure consistent output structure
304
+ output = capture.get("output", {})
305
+ meta = capture.get("meta", {})
306
+ results.append({
307
+ "url": url,
308
+ "output": {
309
+ "imageUrl": output.get("imageUrl"),
310
+ "htmlUrl": output.get("htmlUrl")
311
+ },
312
+ "meta": meta
313
+ })
314
+
315
+ # Delete (Bot first, then Workspace) regardless of success
316
+ try:
317
+ if bot_id:
318
+ delete_bot(bot_id, workspace_id)
319
+ finally:
320
+ if workspace_id:
321
+ delete_workspace(workspace_id)
322
+
323
+ response_body = {"results": results}
324
+ if errors:
325
+ response_body["errors"] = errors
326
+
327
+ # If all failed, return 502; else 200
328
+ if results:
329
+ return jsonify(response_body), 200
330
+ else:
331
+ return jsonify(response_body), 502
332
 
333
  # -------------------------------------------------------------------
334
  # Run the Flask app
335
  # -------------------------------------------------------------------
 
336
  if __name__ == "__main__":
337
  app.run(host="0.0.0.0", port=7860, debug=True)