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)