Files
grotti/grotti/notify.py
T
Dominic Gundel 4ddffbbeb1 Grotti 3.2.0: Jetzt-Liste, Filament-Pause, Strom-Torte, UI-Polish.
Jetzt/Topbar und Material-Routing, Pause bei Filament-Ende, Druckbericht mit Stromanteilen, Design- und Kamera-Stream-Polish; Bedarf-Alert-Spam und Platte-belegt-Bugs behoben.
2026-10-18 14:58:32 +02:00

101 lines
3.0 KiB
Python

"""Telegram- und ntfy-Benachrichtigungen."""
from __future__ import annotations
import json
import threading
import urllib.error
import urllib.parse
import urllib.request
def telegram_configured(cfg):
tg = (cfg or {}).get("telegram") or {}
return bool(tg.get("enabled") and tg.get("bot_token") and tg.get("chat_id"))
def send_telegram(cfg, text):
"""Sendet Text an Telegram. Fehler werden geschluckt (False)."""
if not telegram_configured(cfg):
return False
tg = cfg["telegram"]
url = "https://api.telegram.org/bot%s/sendMessage" % tg["bot_token"]
payload = urllib.parse.urlencode({
"chat_id": str(tg["chat_id"]),
"text": text[:4000],
"disable_web_page_preview": "1",
}).encode("utf-8")
request = urllib.request.Request(url, data=payload, method="POST")
try:
with urllib.request.urlopen(request, timeout=12) as response:
body = json.loads(response.read().decode("utf-8", "replace"))
return bool(body.get("ok"))
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError, OSError):
return False
ALERT_GROUP = {
"abbruch": "fehler",
"bedarf": "queue",
"wartung": "queue",
"auftrag": "queue",
}
DEFAULT_OFF = {"ablauf", "start"}
def alert_enabled(cfg, kind):
alerts = (cfg or {}).get("alerts") or {}
key = ALERT_GROUP.get(kind, kind)
return bool(alerts.get(key, key not in DEFAULT_OFF))
def ntfy_configured(cfg):
ntfy = (cfg or {}).get("ntfy") or {}
return bool(ntfy.get("enabled") and ntfy.get("topic"))
def send_ntfy(cfg, text):
if not ntfy_configured(cfg):
return False
ntfy = cfg["ntfy"]
server = str(ntfy.get("server") or "https://ntfy.sh").rstrip("/")
topic = str(ntfy.get("topic") or "").strip()
if not topic:
return False
url = "%s/%s" % (server, urllib.parse.quote(topic, safe=""))
headers = {"Title": "Grotti", "Priority": "default"}
token = str(ntfy.get("token") or "").strip()
if token:
headers["Authorization"] = "Bearer %s" % token
request = urllib.request.Request(
url,
data=text[:4000].encode("utf-8"),
method="POST",
headers=headers,
)
try:
with urllib.request.urlopen(request, timeout=12) as response:
return 200 <= response.status < 300
except (urllib.error.URLError, TimeoutError, OSError):
return False
def _push_alerts(cfg, message):
send_telegram(cfg, message)
send_ntfy(cfg, message)
def record(conn, cfg, host, printer, kind, message, filename="", dedupe_s=60, run_id=None):
"""Ereignis immer speichern; Telegram/ntfy nur einmal pro Art und Drucker im Zeitfenster."""
from grotti import store
recent = store.recent_event_kind(conn, host, kind, within_s=dedupe_s)
store.add_event(conn, host, printer, kind, message, filename or "", run_id=run_id)
if not recent and alert_enabled(cfg, kind):
threading.Thread(
target=_push_alerts,
args=(cfg, message),
name="grotti-notify",
daemon=True,
).start()