File size: 5,611 Bytes
121e4b0 20a3b47 121e4b0 305d870 5492f08 ed64977 121e4b0 e1d36ec 121e4b0 1f75665 121e4b0 909eff0 1f75665 121e4b0 909eff0 121e4b0 909eff0 121e4b0 909eff0 121e4b0 e1d36ec 121e4b0 e1d36ec 909eff0 121e4b0 909eff0 fec291d 121e4b0 fec291d 121e4b0 cc82d6a 121e4b0 909eff0 121e4b0 909eff0 121e4b0 909eff0 121e4b0 5492f08 121e4b0 1c5cb1e 5492f08 121e4b0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | from flask import Flask, request, jsonify, render_template, url_for
from urllib.parse import urlencode
import requests
import os
app = Flask(__name__)
# External screenshot service config
SCREENSHOT_API = "https://corvo-ai-xx-sc.hf.space/capture"
# Allowed intervals and mapping to TradingView "interval" code
INTERVAL_MAP = {
# minutes
"1m": "1",
"3m": "3",
"5m": "5",
"5min": "5",
"15m": "15",
"15min": "15",
"30m": "30",
"30min": "30",
# hours
"1h": "60",
"2h": "120",
"4h": "240",
# days/weeks/months
"1d": "D",
"1D": "D",
"d": "D",
"D": "D",
"1w": "W",
"1W": "W",
"w": "W",
"W": "W",
"1mth": "M",
"1M": "M",
"m": "M",
"M": "M"
}
def normalize_interval(raw_interval: str) -> str:
if not raw_interval:
return "D"
key = raw_interval.strip()
return INTERVAL_MAP.get(key, key)
def parse_indicators_param(indicators_raw):
# Accept indicators as:
# - JSON array in body or query (list)
# - Comma-separated string "PUB;id1,PUB;id2"
# Returns a list of indicator IDs
if indicators_raw is None:
return []
if isinstance(indicators_raw, list):
return [str(x).strip() for x in indicators_raw if str(x).strip()]
if isinstance(indicators_raw, str):
parts = [p.strip() for p in indicators_raw.split(",") if p.strip()]
return parts
return []
@app.route("/")
def index():
return jsonify({
"message": "Chart Screenshot API",
"endpoints": {
"chart_html": "/chart?symbol=BTCUSDT&exchange=BINANCE&interval=1h&indicators=PUB;abc,PUB;def",
"screenshot_api": "/api/screenshot"
}
})
@app.route("/chart")
def chart():
# This endpoint renders the TradingView chart using query params:
# symbol, exchange, interval, indicators (comma separated), theme (optional)
symbol = request.args.get("symbol", "BTCUSDT")
exchange = request.args.get("exchange", "BINANCE")
interval = normalize_interval(request.args.get("interval", "1D"))
indicators = parse_indicators_param(request.args.get("indicators"))
theme = request.args.get("theme", "dark")
return render_template(
"chart.html",
symbol=symbol,
exchange=exchange,
interval=interval,
indicators=indicators,
theme=theme
)
@app.route("/api/screenshot", methods=["POST"])
def api_screenshot():
"""
Request JSON:
{
"symbol": "BTCUSDT", // required
"exchange": "BINANCE", // optional, default BINANCE
"interval": "1h", // optional, default 1D
"indicators": ["PUB;id1","PUB;id2"], // optional, or comma-separated string
"theme": "dark", // optional, default "dark"
"width": 1080, // optional
"height": 1920, // optional
"fullPage": false // optional
}
Response JSON:
{
"imageUrl": "https://...png",
"htmlUrl": "https://...html",
"screenshotMeta": {...}
}
"""
data = request.get_json(silent=True) or {}
symbol = data.get("symbol", None)
if not symbol:
return jsonify({"error": "symbol is required"}), 400
exchange = data.get("exchange", "BINANCE")
interval = normalize_interval(data.get("interval", "1D"))
indicators = parse_indicators_param(data.get("indicators"))
theme = data.get("theme", "dark")
# Build the chart URL hosted by this same app
# indicators are passed as comma-separated values
query = {
"symbol": symbol,
"exchange": exchange,
"interval": interval,
"theme": theme
}
if indicators:
query["indicators"] = ",".join(indicators)
# Absolute URL to /chart so the screenshot service can access it
chart_url = request.url_root.rstrip("/") + url_for("chart") + "?" + urlencode(query)
# Build screenshot payload
width = data.get("width", 1080)
height = data.get("height", 1920)
full_page = data.get("fullPage", False)
payload = {
"urls": [chart_url],
"width": width,
"height": height,
"fullPage": bool(full_page)
}
try:
resp = requests.post(SCREENSHOT_API, json=payload, timeout=60)
except requests.RequestException as e:
return jsonify({"error": "Failed to reach screenshot service", "details": str(e)}), 502
if resp.status_code != 200:
return jsonify({"error": "Screenshot service error", "status": resp.status_code, "body": resp.text}), 502
body = resp.json()
errors = body.get("errors") or []
if errors:
return jsonify({"error": "Screenshot service reported errors", "details": errors}), 502
results = body.get("results") or []
if not results:
return jsonify({"error": "No results from screenshot service"}), 502
first = results[0]
output = first.get("output") or {}
image_url = output.get("imageUrl")
html_url = output.get("htmlUrl")
if not image_url:
return jsonify({"error": "Screenshot result missing imageUrl", "raw": first}), 502
# Return only the URLs (what you asked for), plus meta for debugging
return jsonify({
"imageUrl": image_url,
"htmlUrl": html_url,
"screenshotMeta": first.get("meta", {})
})
if __name__ == "__main__":
# Host and port can be configured via env
host = os.getenv("HOST", "0.0.0.0")
port = int(os.getenv("PORT", "7860"))
app.run(host=host, port=port, debug=True) |