alert_relay.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. #!/usr/bin/env python3
  2. """HyperDX alert-webhook → Telegram relay (ohmu).
  3. Võtab vastu HyperDX "generic" webhook POST-i ja saadab sisu Telegrami.
  4. Kasutab ainult standardteeki (ei vaja pip-pakette).
  5. HyperDX generic-webhook saadab tavaliselt: {"text": "..."} (Slack-laadne).
  6. Varasem versioon ootas välju name/state/message, mida generic payload ei sisalda
  7. -> sõnumis oli "Seis: ?" ja toor-JSON. Nüüd saadetakse `text` otse.
  8. Env: TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID (vaikimisi 5036685258)
  9. Kuulab: 0.0.0.0:9099
  10. """
  11. from __future__ import annotations
  12. import json
  13. import os
  14. import re
  15. from datetime import datetime, timezone
  16. import urllib.parse
  17. import urllib.request
  18. from http.server import BaseHTTPRequestHandler, HTTPServer
  19. TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "").strip()
  20. CHAT = os.environ.get("TELEGRAM_CHAT_ID", "5036685258")
  21. PORT = int(os.environ.get("PORT", "9099"))
  22. # HyperDX generic payload võib olla pikk; Telegram lubab 4096 tähemärki.
  23. MAX_LEN = 4000
  24. try:
  25. from zoneinfo import ZoneInfo
  26. TZ = ZoneInfo("Europe/Tallinn")
  27. except Exception: # noqa: BLE001
  28. TZ = None
  29. _TR_UTC = re.compile(r"Time Range \(UTC\): \[(.+?) - (.+?)\)")
  30. def _parse_utc(s):
  31. s = s.strip()
  32. for fmt in ("%b %d %I:%M:%S %p", "%b %d %H:%M:%S", "%Y-%m-%d %H:%M:%S"):
  33. try:
  34. dt = datetime.strptime(s, fmt)
  35. except ValueError:
  36. continue
  37. # HyperDX jätab aasta ära -> strptime annab 1900; kasuta jooksvat aastat,
  38. # muidu tuleb Tallinna ajalooline LMT-siire (+1:39) ja kellaaeg on vale.
  39. if dt.year == 1900:
  40. dt = dt.replace(year=datetime.now(timezone.utc).year)
  41. return dt.replace(tzinfo=timezone.utc)
  42. return None
  43. def _fmt_local(s):
  44. dt = _parse_utc(s)
  45. if dt is None:
  46. return s
  47. loc = dt.astimezone(TZ) if TZ else dt
  48. return loc.strftime("%b %d %H:%M:%S")
  49. def localize_times(text: str) -> str:
  50. """HyperDX esitab aja UTC-na -> näita Tallinna ajas (nagu teised teenused)."""
  51. def repl(m):
  52. return "Aeg (Tallinn): [%s - %s)" % (_fmt_local(m.group(1)), _fmt_local(m.group(2)))
  53. return _TR_UTC.sub(repl, text)
  54. def send(text: str) -> None:
  55. if not TOKEN:
  56. print("[relay] TELEGRAM_BOT_TOKEN puudub", flush=True)
  57. return
  58. # Ilma parse_mode'ita (plain text) — HyperDX tekst sisaldab Markdown-erimärke
  59. # (nt [ ] * _), mis muidu põhjustaksid Telegrami "can't parse entities" vea.
  60. data = urllib.parse.urlencode({"chat_id": CHAT, "text": text[:MAX_LEN]}).encode()
  61. req = urllib.request.Request(f"https://api.telegram.org/bot{TOKEN}/sendMessage", data=data)
  62. try:
  63. with urllib.request.urlopen(req, timeout=20) as r:
  64. r.read()
  65. except Exception as e: # noqa: BLE001
  66. print("[relay] Telegram viga:", str(e)[:200], flush=True)
  67. def format_msg(data: dict) -> str:
  68. # 1) HyperDX generic webhook -> {"text": "..."} — saada otse.
  69. text = data.get("text")
  70. if isinstance(text, str) and text.strip():
  71. return localize_times(text.strip())
  72. # 2) Muud payload'id: proovi tuntud välju.
  73. def pick(*keys):
  74. for k in keys:
  75. v = data.get(k) or (data.get("alert") or {}).get(k)
  76. if v not in (None, ""):
  77. return v
  78. return None
  79. name = pick("name", "alertName", "title")
  80. state = pick("state", "status", "event")
  81. msg = pick("message", "note", "description")
  82. lines: list[str] = []
  83. if name:
  84. lines.append(str(name))
  85. if state:
  86. icon = {"ALERT": "🔴", "alert": "🔴", "RESOLVED": "✅", "resolved": "✅", "OK": "✅"}.get(str(state), "⚠️")
  87. lines.append(f"{icon} Seis: {state}")
  88. if msg:
  89. lines.append(str(msg)[:1500])
  90. if not lines:
  91. lines.append(json.dumps(data, ensure_ascii=False)[:1200])
  92. return localize_times("\n".join(lines))
  93. class H(BaseHTTPRequestHandler):
  94. def do_POST(self): # noqa: N802
  95. n = int(self.headers.get("Content-Length", "0") or 0)
  96. raw = self.rfile.read(n) if n else b"{}"
  97. try:
  98. data = json.loads(raw or b"{}")
  99. except Exception: # noqa: BLE001
  100. data = {"text": raw.decode("utf-8", "replace")[:1000]}
  101. send(format_msg(data if isinstance(data, dict) else {"text": str(data)}))
  102. self.send_response(200)
  103. self.send_header("Content-Type", "application/json")
  104. self.end_headers()
  105. self.wfile.write(b'{"ok":true}')
  106. def do_GET(self): # noqa: N802
  107. self.send_response(200)
  108. self.end_headers()
  109. self.wfile.write(b"alert-relay ok")
  110. def log_message(self, *args): # vaikimisi logi maha
  111. return
  112. if __name__ == "__main__":
  113. print(f"[relay] kuulan :{PORT} -> Telegram {CHAT}", flush=True)
  114. HTTPServer(("0.0.0.0", PORT), H).serve_forever()