chart-pro / app.py
CORVO-AI's picture
Update app.py
121e4b0 verified
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)