from __future__ import annotations
import json
import logging
import time
import urllib.parse
import urllib.request
from datetime import datetime
from config import Config
from state import State
log = logging.getLogger(__name__)
ICONS = {"CRITICAL": "π΄", "WARNING": "π‘", "INFO": "π’"}
class Alerter:
def __init__(self, config: Config, state: State) -> None:
self.config = config
self.state = state
# ββ Persistent Alerting βββββββββββββββββββββββββββββββββββ
# Gleiche Logik wie im Bash-Script:
# - Neuer Fehler β sofort melden, runs = 0
# - Fehler bleibt β runs++, melden wenn runs >= repeat_runs
# - Fehler weg β Entwarnung, State lΓΆschen
def should_alert(self, key: str, level: str) -> bool:
repeat = (
self.config.alert.critical_repeat_runs
if level == "CRITICAL"
else self.config.alert.warning_repeat_runs
)
active = self.state.get(f"{key}_active")
runs: int = self.state.get(f"{key}_runs", 0)
if active != "1":
self.state.set(f"{key}_active", "1")
self.state.set(f"{key}_runs", 0)
return True
runs += 1
self.state.set(f"{key}_runs", runs)
if repeat == 0:
return False
if runs >= repeat:
self.state.set(f"{key}_runs", 0)
return True
return False
def clear_alert(self, key: str) -> bool:
"""Clear state and return True if the alert was previously active."""
was_active = self.state.get(f"{key}_active") == "1"
self.state.delete(f"{key}_active", f"{key}_runs")
return was_active
# ββ Sending βββββββββββββββββββββββββββββββββββββββββββββββ
def send_alert(self, level: str, title: str, body: str, key: str = "") -> None:
icon = ICONS.get(level, "βͺ")
suffix = ""
if key:
runs: int = self.state.get(f"{key}_runs", 0)
if runs > 0:
suffix = f" (Erinnerung #{runs})"
ts = datetime.now().strftime("%d.%m.%Y %H:%M:%S")
msg = (
f"{icon} [{level}] {self.config.hostname}{suffix}\n"
f"{title}\n\n"
f"{body}\n\n"
f"π {ts}"
)
log.info("ALERT %s: %s", level, title)
self._send_telegram(msg)
def _send_telegram(self, text: str) -> bool:
url = f"https://api.telegram.org/bot{self.config.telegram.token}/sendMessage"
data = urllib.parse.urlencode({
"chat_id": self.config.telegram.chat_id,
"text": text,
"parse_mode": "HTML",
}).encode()
for attempt in range(1, 4):
try:
with urllib.request.urlopen(url, data=data, timeout=10) as resp:
result = json.loads(resp.read())
if result.get("ok"):
return True
except Exception as e:
log.warning("Telegram Fehler (Versuch %d/3): %s", attempt, e)
if attempt < 3:
time.sleep(attempt * 2)
log.error("Telegram: Alert konnte nicht gesendet werden!")
return False