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.
101 lines
3.0 KiB
Python
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()
|