Spaces:
Running
Running
Commit ·
fcb1319
1
Parent(s): 0690679
Route Telegram alerts via Cloudflare Worker relay (HF blocks api.telegram.org)
Browse files- main.py +8 -10
- telegram_alert.py +22 -17
main.py
CHANGED
|
@@ -383,20 +383,18 @@ def catalyst(symbol: str = ""):
|
|
| 383 |
|
| 384 |
@app.get("/api/test-telegram")
|
| 385 |
def test_telegram():
|
| 386 |
-
"""Send a test Telegram message
|
| 387 |
import requests as req
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
text = "✅ Trade Copilot — Test Message\n\nTelegram alerts are working correctly."
|
| 393 |
-
url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
|
| 394 |
try:
|
| 395 |
-
resp = req.post(
|
| 396 |
body = resp.json()
|
| 397 |
if resp.status_code == 200:
|
| 398 |
-
return {"ok": True, "message": "Test message sent successfully"}
|
| 399 |
-
return JSONResponse({"ok": False, "status": resp.status_code, "
|
| 400 |
except Exception as e:
|
| 401 |
return JSONResponse({"ok": False, "error": type(e).__name__ + ": " + str(e)}, status_code=500)
|
| 402 |
|
|
|
|
| 383 |
|
| 384 |
@app.get("/api/test-telegram")
|
| 385 |
def test_telegram():
|
| 386 |
+
"""Send a test Telegram message via Cloudflare Worker relay."""
|
| 387 |
import requests as req
|
| 388 |
+
worker_url = os.environ.get("CLOUDFLARE_WORKER_URL", "")
|
| 389 |
+
if not worker_url:
|
| 390 |
+
return JSONResponse({"ok": False, "error": "CLOUDFLARE_WORKER_URL not set in HF Secrets"}, status_code=500)
|
| 391 |
+
text = "✅ <b>Trade Copilot — Test Message</b>\n\nTelegram alerts are working correctly via Cloudflare Worker."
|
|
|
|
|
|
|
| 392 |
try:
|
| 393 |
+
resp = req.post(worker_url, json={"text": text, "parse_mode": "HTML"}, timeout=10)
|
| 394 |
body = resp.json()
|
| 395 |
if resp.status_code == 200:
|
| 396 |
+
return {"ok": True, "message": "Test message sent successfully via CF Worker"}
|
| 397 |
+
return JSONResponse({"ok": False, "status": resp.status_code, "error": body}, status_code=500)
|
| 398 |
except Exception as e:
|
| 399 |
return JSONResponse({"ok": False, "error": type(e).__name__ + ": " + str(e)}, status_code=500)
|
| 400 |
|
telegram_alert.py
CHANGED
|
@@ -1,8 +1,12 @@
|
|
| 1 |
"""Telegram alert sender for high-confidence trade cards.
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
Dedup design (verified by 7-agent panel):
|
| 8 |
- Background loop uses key prefix "bg:" — 4-hour cooldown
|
|
@@ -19,6 +23,8 @@ from datetime import datetime, timezone
|
|
| 19 |
|
| 20 |
logger = logging.getLogger("telegram_alert")
|
| 21 |
|
|
|
|
|
|
|
| 22 |
TELEGRAM_BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
|
| 23 |
TELEGRAM_CHAT_ID = os.environ.get("TELEGRAM_CHAT_ID", "")
|
| 24 |
|
|
@@ -192,11 +198,10 @@ async def send_alert(card: dict, tv_sym: str, manual: bool = False) -> bool:
|
|
| 192 |
"""
|
| 193 |
global _warned_missing
|
| 194 |
|
| 195 |
-
if not
|
| 196 |
if not _warned_missing:
|
| 197 |
logger.warning(
|
| 198 |
-
"Telegram alerts disabled — set
|
| 199 |
-
"TELEGRAM_CHAT_ID in HF Spaces Secrets."
|
| 200 |
)
|
| 201 |
_warned_missing = True
|
| 202 |
return False
|
|
@@ -210,24 +215,24 @@ async def send_alert(card: dict, tv_sym: str, manual: bool = False) -> bool:
|
|
| 210 |
)
|
| 211 |
return False
|
| 212 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
text = build_message(card, tv_sym)
|
| 214 |
-
|
| 215 |
-
payload = {
|
| 216 |
-
"chat_id": TELEGRAM_CHAT_ID,
|
| 217 |
-
"text": text,
|
| 218 |
-
"parse_mode": "HTML",
|
| 219 |
-
"disable_web_page_preview": True,
|
| 220 |
-
}
|
| 221 |
|
| 222 |
try:
|
| 223 |
async with aiohttp.ClientSession() as session:
|
| 224 |
async with session.post(
|
| 225 |
-
|
|
|
|
|
|
|
| 226 |
) as resp:
|
| 227 |
if resp.status == 200:
|
| 228 |
_mark_sent(card, manual)
|
| 229 |
logger.info(
|
| 230 |
-
"Telegram alert sent [%s]: %s %s conf=%.1f",
|
| 231 |
"manual" if manual else "bg",
|
| 232 |
card.get("symbol"), card.get("direction"),
|
| 233 |
float(card.get("confidence", 0)),
|
|
@@ -235,12 +240,12 @@ async def send_alert(card: dict, tv_sym: str, manual: bool = False) -> bool:
|
|
| 235 |
return True
|
| 236 |
body = await resp.text()
|
| 237 |
logger.error(
|
| 238 |
-
"
|
| 239 |
resp.status, card.get("symbol"), body[:300],
|
| 240 |
)
|
| 241 |
except Exception as exc:
|
| 242 |
logger.error(
|
| 243 |
-
"
|
| 244 |
card.get("symbol"), exc, exc_info=True,
|
| 245 |
)
|
| 246 |
|
|
|
|
| 1 |
"""Telegram alert sender for high-confidence trade cards.
|
| 2 |
|
| 3 |
+
Routing: HF Spaces cannot reach api.telegram.org directly (IP blocked).
|
| 4 |
+
Messages are routed via a Cloudflare Worker relay:
|
| 5 |
+
HF Space → CLOUDFLARE_WORKER_URL → api.telegram.org → Telegram
|
| 6 |
+
|
| 7 |
+
Env vars required (set in HF Spaces Secrets):
|
| 8 |
+
CLOUDFLARE_WORKER_URL — your Worker URL, e.g. https://trade-alert.yourname.workers.dev
|
| 9 |
+
(TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID are set as env vars on the Worker itself, not here)
|
| 10 |
|
| 11 |
Dedup design (verified by 7-agent panel):
|
| 12 |
- Background loop uses key prefix "bg:" — 4-hour cooldown
|
|
|
|
| 23 |
|
| 24 |
logger = logging.getLogger("telegram_alert")
|
| 25 |
|
| 26 |
+
CLOUDFLARE_WORKER_URL = os.environ.get("CLOUDFLARE_WORKER_URL", "")
|
| 27 |
+
# Keep these for backward compat / fallback reference
|
| 28 |
TELEGRAM_BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
|
| 29 |
TELEGRAM_CHAT_ID = os.environ.get("TELEGRAM_CHAT_ID", "")
|
| 30 |
|
|
|
|
| 198 |
"""
|
| 199 |
global _warned_missing
|
| 200 |
|
| 201 |
+
if not CLOUDFLARE_WORKER_URL:
|
| 202 |
if not _warned_missing:
|
| 203 |
logger.warning(
|
| 204 |
+
"Telegram alerts disabled — set CLOUDFLARE_WORKER_URL in HF Spaces Secrets."
|
|
|
|
| 205 |
)
|
| 206 |
_warned_missing = True
|
| 207 |
return False
|
|
|
|
| 215 |
)
|
| 216 |
return False
|
| 217 |
|
| 218 |
+
if not CLOUDFLARE_WORKER_URL:
|
| 219 |
+
logger.warning("CLOUDFLARE_WORKER_URL not set in HF Secrets — alerts disabled.")
|
| 220 |
+
return False
|
| 221 |
+
|
| 222 |
text = build_message(card, tv_sym)
|
| 223 |
+
payload = {"text": text, "parse_mode": "HTML"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
|
| 225 |
try:
|
| 226 |
async with aiohttp.ClientSession() as session:
|
| 227 |
async with session.post(
|
| 228 |
+
CLOUDFLARE_WORKER_URL,
|
| 229 |
+
json=payload,
|
| 230 |
+
timeout=aiohttp.ClientTimeout(total=10)
|
| 231 |
) as resp:
|
| 232 |
if resp.status == 200:
|
| 233 |
_mark_sent(card, manual)
|
| 234 |
logger.info(
|
| 235 |
+
"Telegram alert sent via CF Worker [%s]: %s %s conf=%.1f",
|
| 236 |
"manual" if manual else "bg",
|
| 237 |
card.get("symbol"), card.get("direction"),
|
| 238 |
float(card.get("confidence", 0)),
|
|
|
|
| 240 |
return True
|
| 241 |
body = await resp.text()
|
| 242 |
logger.error(
|
| 243 |
+
"CF Worker error %d for %s: %s",
|
| 244 |
resp.status, card.get("symbol"), body[:300],
|
| 245 |
)
|
| 246 |
except Exception as exc:
|
| 247 |
logger.error(
|
| 248 |
+
"CF Worker request failed for %s: %s",
|
| 249 |
card.get("symbol"), exc, exc_info=True,
|
| 250 |
)
|
| 251 |
|