| | from flask import Flask, request, jsonify |
| | import requests |
| | import random |
| | import string |
| | import time |
| |
|
| | app = Flask(__name__) |
| |
|
| | |
| | TOKEN = "Bearer bp_pat_vTuxol25N0ymBpYaWqtWpFfGPKt260IfT784" |
| |
|
| | |
| | |
| | |
| | def generate_random_name(length=5): |
| | return ''.join(random.choices(string.ascii_letters, k=length)) |
| |
|
| | |
| | |
| | |
| | def create_workspace(): |
| | url = "https://api.botpress.cloud/v1/admin/workspaces" |
| | headers = { |
| | "User-Agent": "Mozilla/5.0", |
| | "Authorization": TOKEN |
| | } |
| | payload = {"name": generate_random_name()} |
| | try: |
| | r = requests.post(url, headers=headers, json=payload, timeout=60) |
| | if r.status_code == 200: |
| | ws_id = r.json().get("id") |
| | if ws_id: |
| | print(f"Workspace created: {ws_id}") |
| | return ws_id |
| | print(f"Workspace creation failed: {r.status_code} {r.text}") |
| | except Exception as e: |
| | print(f"Error creating workspace: {e}") |
| | return None |
| |
|
| | def create_bot(workspace_id): |
| | if not workspace_id: |
| | print("No workspace_id provided for bot creation") |
| | return None |
| | url = "https://api.botpress.cloud/v1/admin/bots" |
| | headers = { |
| | "User-Agent": "Mozilla/5.0", |
| | "x-workspace-id": workspace_id, |
| | "Authorization": TOKEN, |
| | "Content-Type": "application/json" |
| | } |
| | payload = {"name": generate_random_name()} |
| | try: |
| | r = requests.post(url, headers=headers, json=payload, timeout=60) |
| | if r.status_code == 200: |
| | bot_id = r.json().get("bot", {}).get("id") |
| | if bot_id: |
| | print(f"Bot created: {bot_id} in workspace: {workspace_id}") |
| | |
| | installed = install_bot_integration(bot_id, workspace_id) |
| | if installed: |
| | print(f"Integration installed for bot {bot_id}") |
| | else: |
| | print(f"Integration install failed for bot {bot_id} (continuing)") |
| | return bot_id |
| | print(f"Bot creation failed: {r.status_code} {r.text}") |
| | except Exception as e: |
| | print(f"Error creating bot: {e}") |
| | return None |
| |
|
| | def install_bot_integration(bot_id, workspace_id): |
| | if not bot_id or not workspace_id: |
| | print("Missing bot_id or workspace_id for integration") |
| | return False |
| | url = f"https://api.botpress.cloud/v1/admin/bots/{bot_id}" |
| | headers = { |
| | "User-Agent": "Mozilla/5.0", |
| | "Authorization": TOKEN, |
| | "Content-Type": "application/json", |
| | "x-bot-id": bot_id, |
| | "x-workspace-id": workspace_id |
| | } |
| | payload = { |
| | "integrations": { |
| | "intver_01JZ6J0NKYBXC0V6K5DBDZKKDK": { |
| | "enabled": True |
| | } |
| | } |
| | } |
| | try: |
| | r = requests.put(url, headers=headers, json=payload, timeout=60) |
| | if r.status_code == 200: |
| | return True |
| | print(f"Integration install failed: {r.status_code} {r.text}") |
| | except Exception as e: |
| | print(f"Error installing integration: {e}") |
| | return False |
| |
|
| | def delete_bot(bot_id, workspace_id): |
| | if not bot_id or not workspace_id: |
| | print("Missing bot_id or workspace_id for bot deletion") |
| | return False |
| | url = f"https://api.botpress.cloud/v1/admin/bots/{bot_id}" |
| | headers = { |
| | "User-Agent": "Mozilla/5.0", |
| | "x-workspace-id": workspace_id, |
| | "Authorization": TOKEN |
| | } |
| | try: |
| | r = requests.delete(url, headers=headers, timeout=60) |
| | if r.status_code in [200, 204]: |
| | print(f"Bot deleted: {bot_id}") |
| | return True |
| | print(f"Bot deletion failed: {r.status_code} {r.text}") |
| | except Exception as e: |
| | print(f"Error deleting bot: {e}") |
| | return False |
| |
|
| | def delete_workspace(workspace_id): |
| | if not workspace_id: |
| | print("Missing workspace_id for workspace deletion") |
| | return False |
| | url = f"https://api.botpress.cloud/v1/admin/workspaces/{workspace_id}" |
| | headers = { |
| | "User-Agent": "Mozilla/5.0", |
| | "Authorization": TOKEN |
| | } |
| | try: |
| | r = requests.delete(url, headers=headers, timeout=60) |
| | if r.status_code in [200, 204]: |
| | print(f"Workspace deleted: {workspace_id}") |
| | return True |
| | print(f"Workspace deletion failed: {r.status_code} {r.text}") |
| | except Exception as e: |
| | print(f"Error deleting workspace: {e}") |
| | return False |
| |
|
| | |
| | |
| | |
| | def capture_screenshot(url, width, height, full_page, bot_id, workspace_id, timeout=120): |
| | if not bot_id or not workspace_id: |
| | return {"error": "Missing bot_id or workspace_id"} |
| |
|
| | api_url = "https://api.botpress.cloud/v1/chat/actions" |
| | headers = { |
| | "User-Agent": "Mozilla/5.0", |
| | "x-bot-id": bot_id, |
| | "x-workspace-id": workspace_id, |
| | "Content-Type": "application/json", |
| | "Authorization": TOKEN |
| | } |
| | payload = { |
| | "type": "browser:captureScreenshot", |
| | "input": { |
| | "url": url, |
| | "cssToInject": "greregreg", |
| | "javascriptToInject": "gerregr", |
| | "width": width, |
| | "height": height, |
| | "fullPage": bool(full_page), |
| | "waitFor": 10000 |
| | } |
| | } |
| |
|
| | max_retries = 3 |
| | for attempt in range(max_retries): |
| | try: |
| | print(f"Capture attempt {attempt+1} for {url}") |
| | r = requests.post(api_url, headers=headers, json=payload, timeout=timeout) |
| | if r.status_code == 200: |
| | data = r.json() |
| | |
| | |
| | return data |
| | elif r.status_code in [404, 408, 502, 503, 504]: |
| | print(f"Transient error {r.status_code}: retrying...") |
| | time.sleep(2) |
| | continue |
| | elif r.status_code == 403: |
| | print(f"Forbidden (403): {r.text}") |
| | |
| | return {"error": "Forbidden", "status": 403, "details": safe_text(r)} |
| | else: |
| | print(f"Unexpected {r.status_code}: {r.text}") |
| | return {"error": "Unexpected error", "status": r.status_code, "details": safe_text(r)} |
| | except requests.exceptions.Timeout: |
| | print("Request timed out, retrying...") |
| | if attempt < max_retries - 1: |
| | continue |
| | return {"error": "Timeout"} |
| | except Exception as e: |
| | print(f"Exception: {e}") |
| | if attempt < max_retries - 1: |
| | time.sleep(1) |
| | continue |
| | return {"error": str(e)} |
| |
|
| | return {"error": "Failed after retries"} |
| |
|
| | def safe_text(response): |
| | try: |
| | return response.text |
| | except Exception: |
| | return "<unavailable>" |
| |
|
| | |
| | |
| | |
| | @app.route("/capture", methods=["POST"]) |
| | def capture_endpoint(): |
| | """ |
| | JSON body example: |
| | { |
| | "urls": ["https://x.com/USTreasury", "https://example.com"], // required, array of one or more |
| | "width": 10850, // optional, default 1080 |
| | "height": 1920, // optional, default 1920 |
| | "fullPage": true // optional, default true |
| | } |
| | Response: |
| | { |
| | "results": [ |
| | { |
| | "url": "...", |
| | "output": { |
| | "imageUrl": "...png", |
| | "htmlUrl": "...html" |
| | }, |
| | "meta": { "cached": false } |
| | }, |
| | { ... } |
| | ], |
| | "errors": [ |
| | { "url": "...", "error": "...", "status": 403, "details": "..." } |
| | ] |
| | } |
| | Behavior: |
| | - Create workspace, then bot. |
| | - Run capture for all URLs with the same bot/workspace. |
| | - Delete bot first, then workspace, at the end of the request. |
| | """ |
| | |
| | try: |
| | body = request.get_json(force=True) or {} |
| | except Exception: |
| | return jsonify({"error": "Invalid JSON"}), 400 |
| |
|
| | urls = body.get("urls") |
| | if not urls or not isinstance(urls, list): |
| | return jsonify({"error": "urls is required and must be a list"}), 400 |
| |
|
| | |
| | width = body.get("width", 1080) |
| | height = body.get("height", 1920) |
| | full_page = body.get("fullPage", True) |
| |
|
| | |
| | try: |
| | width = int(width) |
| | if width <= 0: |
| | width = 1080 |
| | except Exception: |
| | width = 1080 |
| | try: |
| | height = int(height) |
| | if height <= 0: |
| | height = 1920 |
| | except Exception: |
| | height = 1920 |
| | full_page = bool(full_page) |
| |
|
| | workspace_id = None |
| | bot_id = None |
| |
|
| | |
| | workspace_id = create_workspace() |
| | if not workspace_id: |
| | return jsonify({"error": "Failed to create workspace"}), 500 |
| |
|
| | bot_id = create_bot(workspace_id) |
| | if not bot_id: |
| | |
| | delete_workspace(workspace_id) |
| | return jsonify({"error": "Failed to create bot"}), 500 |
| |
|
| | results = [] |
| | errors = [] |
| |
|
| | |
| | for url in urls: |
| | if not isinstance(url, str) or not url.strip(): |
| | errors.append({"url": url, "error": "Invalid URL"}) |
| | continue |
| |
|
| | capture = capture_screenshot( |
| | url=url.strip(), |
| | width=width, |
| | height=height, |
| | full_page=full_page, |
| | bot_id=bot_id, |
| | workspace_id=workspace_id |
| | ) |
| |
|
| | if isinstance(capture, dict) and capture.get("error"): |
| | errors.append({"url": url, **capture}) |
| | else: |
| | |
| | output = capture.get("output", {}) |
| | meta = capture.get("meta", {}) |
| | results.append({ |
| | "url": url, |
| | "output": { |
| | "imageUrl": output.get("imageUrl"), |
| | "htmlUrl": output.get("htmlUrl") |
| | }, |
| | "meta": meta |
| | }) |
| |
|
| | |
| | try: |
| | if bot_id: |
| | delete_bot(bot_id, workspace_id) |
| | finally: |
| | if workspace_id: |
| | delete_workspace(workspace_id) |
| |
|
| | response_body = {"results": results} |
| | if errors: |
| | response_body["errors"] = errors |
| |
|
| | |
| | if results: |
| | return jsonify(response_body), 200 |
| | else: |
| | return jsonify(response_body), 502 |
| |
|
| | |
| | |
| | |
| | if __name__ == "__main__": |
| | app.run(host="0.0.0.0", port=7860, debug=True) |
| |
|