"""Liest zurueck, was bei ntfy unter dem Thema der Maschine ankam, mit maskierten Ziffern. Koordinator-Skript, nicht Teil des Repos. Schickt nichts. Das Thema wird nie ausgegeben -- weder hier noch in einer Fehlermeldung. Aufruf aus dem Projektordner (WSL): python -B ntfy_lesen.py [--seit 12h | --seit 2026-10-07T20:00] Exit 0 gelesen, 1 Netzfehler, 3 kein Thema, 4 Weiterleitung oder Fehlerstatus. """ import argparse import json import os import re import sys from datetime import datetime from pathlib import Path from zoneinfo import ZoneInfo sys.path.insert(0, str(Path.cwd())) # Ohne Login-Shell fehlt die Variable (LIESMICH, Abschnitt zu den Geheimnissen); der Pfad selbst # ist kein Geheimnis, sein Inhalt schon. os.environ.setdefault("TRADING_GEHEIMNISSE", "/home/claude/.config/trading-plattform/secrets.toml") import requests from trading import geheimnisse from trading.paper.melder import lade_melder_config from trading.paper.melder import lies_ntfy_geheimnisse BERLIN = ZoneInfo("Europe/Berlin") # ntfy nimmt fuer since eine Dauer (12h, 30m) oder einen Unix-Zeitstempel (docs.ntfy.sh/subscribe/api). DAUER = re.compile(r"^\d+[smhd]$") def seit_parameter(wert: str) -> str: """Eine Dauer bleibt, wie sie ist; eine Ortszeit ohne Zone gilt als Berliner Zeit.""" if DAUER.match(wert): return wert zeit = datetime.fromisoformat(wert) if zeit.tzinfo is None: zeit = zeit.replace(tzinfo=BERLIN) return str(int(zeit.timestamp())) def maskiert(text: object) -> str: """Jede Ziffernfolge als #, weil eine Meldung einen Betrag tragen kann.""" return re.sub(r"\d+", "#", str(text)) def main(argv: list[str] | None = None) -> int: teiler = argparse.ArgumentParser(description="ntfy zuruecklesen, ohne das Thema zu zeigen.") teiler.add_argument( "--seit", default="12h", help="Dauer wie 12h oder Berliner Ortszeit JJJJ-MM-TTTHH:MM; ntfy.sh haelt Meldungen nur Stunden.", ) args = teiler.parse_args(argv) try: seit = seit_parameter(args.seit) except ValueError: print(f"--seit nicht lesbar: {args.seit}") return 2 try: # Beide lesen die Geheimnisdatei; fehlt sie, wirft schon die Konfiguration. config = lade_melder_config("config/paper.toml", secrets_pfad=None) thema, token = lies_ntfy_geheimnisse() except OSError as ausnahme: print(f"Geheimnisdatei nicht lesbar ({type(ausnahme).__name__}): {geheimnisse.beschreibung()}") return 3 if not thema: # Mit leerem Thema hiesse die Adresse .../json; genau das endete am 2026-10-07 mit HTTP 307. print(f"Kein Thema in der Geheimnisdatei: {geheimnisse.beschreibung()}") return 3 kopf = {"Authorization": f"Bearer {token}"} if token else {} try: antwort = requests.get( f"{config.ntfy_url}/{thema}/json", params={"poll": "1", "since": seit}, headers=kopf, timeout=10, allow_redirects=False, ) except Exception as ausnahme: # Nur der Typ: Die Ausnahme von requests traegt die Adresse und damit das Thema. print(f"Ruecklesen gescheitert: {type(ausnahme).__name__}") return 1 print(f"Ruecklesen seit {args.seit}: HTTP {antwort.status_code}") if antwort.status_code >= 300: # Einer Weiterleitung folgen hiesse, das Token an ein anderes Ziel zu schicken. print("Kein Ergebnis: Weiterleitung oder Fehlerstatus, nicht gefolgt.") return 4 anzahl = 0 for zeile in antwort.text.splitlines(): try: eintrag = json.loads(zeile) except ValueError: continue if eintrag.get("event") != "message": continue anzahl += 1 zeit = datetime.fromtimestamp(eintrag.get("time", 0), BERLIN).strftime("%Y-%m-%d %H:%M:%S") erste = (str(eintrag.get("message", "")).splitlines() or [""])[0] # Ohne Angabe fuehrt ntfy die Prioritaet 3 (default) und laesst das Feld weg. print(f" {zeit} Prioritaet {eintrag.get('priority', 3)} Titel '{maskiert(eintrag.get('title'))}' " f"erste Zeile '{maskiert(erste)[:70]}'") print(f"{anzahl} Meldung(en).") return 0 if __name__ == "__main__": sys.exit(main())