diff --git a/app/main.py b/app/main.py index ff5eb72..8bc386d 100644 --- a/app/main.py +++ b/app/main.py @@ -563,6 +563,7 @@ def settings_page(request: Request): "current_user": user_id, "ntfy_topic": notif.get("ntfy_topic") or "", "ntfy_server": notif.get("ntfy_server") or "https://ntfy.sh", + "ntfy_token": notif.get("ntfy_token") or "", "rappel_debut_h": notif.get("rappel_debut_h", 7), "rappel_fin_h": notif.get("rappel_fin_h", 20), "arrivee_url": f"{base_url}/presence/{notif['token']}/arrivee", @@ -578,6 +579,7 @@ def settings_page(request: Request): def settings_save( request: Request, ntfy_topic: str = Form(""), ntfy_server: str = Form(""), rappel_debut_h: int = Form(7), rappel_fin_h: int = Form(20), + ntfy_token: str = Form(""), ): user_id = _get_user(request) if not user_id: @@ -587,10 +589,11 @@ def settings_save( rappel_fin_h = max(rappel_debut_h + 1, min(24, rappel_fin_h)) logger.info( - "réglages notif mis à jour: user_id=%r ntfy_topic=%r ntfy_server=%r plage=%sh-%sh", + "réglages notif mis à jour: user_id=%r ntfy_topic=%r ntfy_server=%r plage=%sh-%sh token=%s", user_id, ntfy_topic, ntfy_server, rappel_debut_h, rappel_fin_h, + "(défini)" if ntfy_token.strip() else "(vide)", ) - save_notif_config(user_id, ntfy_topic, ntfy_server, rappel_debut_h, rappel_fin_h) + save_notif_config(user_id, ntfy_topic, ntfy_server, rappel_debut_h, rappel_fin_h, ntfy_token) return RedirectResponse("/settings?saved=1", status_code=303) @@ -616,7 +619,7 @@ def settings_test_ntfy(request: Request): ok = send_ntfy( server, topic, "Ceci est une notification de test — ta configuration ntfy fonctionne.", - "Test pointeuse", user_id=user_id, + "Test pointeuse", user_id=user_id, token=notif.get("ntfy_token", ""), ) cls = "settings-saved" if ok else "settings-error" msg = ("Notification envoyée. Vérifie ton téléphone, elle doit arriver en quelques secondes." diff --git a/app/models.py b/app/models.py index 3e024db..a6d6012 100644 --- a/app/models.py +++ b/app/models.py @@ -156,11 +156,12 @@ DEFAULT_RAPPEL_FIN_H = 20 def load_notif_config(user_id: str) -> dict: - """Return {"ntfy_topic", "ntfy_server", "rappel_debut_h", "rappel_fin_h", "token"}.""" + """Return {ntfy_topic, ntfy_server, ntfy_token, rappel_debut_h, rappel_fin_h, token}.""" f = _notif_file(user_id) if f.exists(): cfg = json.loads(f.read_text()) cfg.setdefault("ntfy_server", DEFAULT_NTFY_SERVER) + cfg.setdefault("ntfy_token", "") cfg.setdefault("rappel_debut_h", DEFAULT_RAPPEL_DEBUT_H) cfg.setdefault("rappel_fin_h", DEFAULT_RAPPEL_FIN_H) if cfg.get("token"): @@ -168,6 +169,7 @@ def load_notif_config(user_id: str) -> dict: else: cfg = { "ntfy_topic": None, "ntfy_server": DEFAULT_NTFY_SERVER, + "ntfy_token": "", "rappel_debut_h": DEFAULT_RAPPEL_DEBUT_H, "rappel_fin_h": DEFAULT_RAPPEL_FIN_H, } cfg["token"] = secrets.token_hex(16) @@ -179,10 +181,12 @@ def load_notif_config(user_id: str) -> dict: def save_notif_config( user_id: str, ntfy_topic: str, ntfy_server: str = "", rappel_debut_h: int = DEFAULT_RAPPEL_DEBUT_H, rappel_fin_h: int = DEFAULT_RAPPEL_FIN_H, + ntfy_token: str = "", ): cfg = load_notif_config(user_id) cfg["ntfy_topic"] = ntfy_topic.strip() or None cfg["ntfy_server"] = ntfy_server.strip().rstrip("/") or DEFAULT_NTFY_SERVER + cfg["ntfy_token"] = ntfy_token.strip() cfg["rappel_debut_h"] = rappel_debut_h cfg["rappel_fin_h"] = rappel_fin_h _ensure_user_dirs(user_id) diff --git a/app/notifications.py b/app/notifications.py index 4957081..7e1d0ad 100644 --- a/app/notifications.py +++ b/app/notifications.py @@ -11,11 +11,14 @@ logger = logging.getLogger("pointeuse.notifications") CHECK_INTERVAL_SECONDS = 300 -def send_ntfy(server: str, topic: str, message: str, title: str, user_id: str | None = None) -> bool: +def send_ntfy(server: str, topic: str, message: str, title: str, user_id: str | None = None, token: str = "") -> bool: + headers = {"Title": title.encode("utf-8"), "Priority": "high"} + if token: + headers["Authorization"] = f"Bearer {token}" req = urllib.request.Request( f"{server}/{topic}", data=message.encode("utf-8"), - headers={"Title": title.encode("utf-8"), "Priority": "high"}, + headers=headers, method="POST", ) try: @@ -43,6 +46,7 @@ async def check_user(user_id: str, now: datetime): notif = load_notif_config(user_id) topic = notif.get("ntfy_topic") server = notif.get("ntfy_server", "https://ntfy.sh") + token = notif.get("ntfy_token", "") debut_h = notif.get("rappel_debut_h", 7) fin_h = notif.get("rappel_fin_h", 20) if not topic: @@ -64,14 +68,14 @@ async def check_user(user_id: str, now: datetime): await asyncio.to_thread( send_ntfy, server, topic, "Tu es dans les locaux mais pas encore pointé ce matin.", - "⏰ Pense à pointer", user_id, + "⏰ Pense à pointer", user_id, token, ) elif not presence.get("present") and jour.get("matin_entree") and not jour.get("aprem_sortie"): logger.info("rappel 'pense à dépointer' pour %r", user_id) await asyncio.to_thread( send_ntfy, server, topic, "Tu as quitté les locaux sans dépointer la sortie.", - "⏰ Pense à dépointer", user_id, + "⏰ Pense à dépointer", user_id, token, ) diff --git a/app/templates/aide.html b/app/templates/aide.html index cbe55e8..ad5860f 100644 --- a/app/templates/aide.html +++ b/app/templates/aide.html @@ -57,6 +57,13 @@ (Réglages → Applications → ntfy → Batterie → "Aucune restriction"). Sinon Android peut couper les notifications en arrière-plan. +
403 Forbidden,
+ elle exige un jeton d'accès. Crée-le sur le serveur (panneau d'admin ntfy, onglet
+ "Access tokens"), puis colle-le dans le champ Jeton d'accès sur
+ /settings. Il est envoyé comme
+ Authorization: Bearer ... à chaque publication.
+ Astuce : si tu veux ton propre serveur ntfy (auto-hébergé, pour éviter le topic public), diff --git a/app/templates/settings.html b/app/templates/settings.html index 6dd1e47..6ed47e0 100644 --- a/app/templates/settings.html +++ b/app/templates/settings.html @@ -84,6 +84,16 @@ placeholder="ex: antoine-pointeuse-x7f2" autocomplete="off" > + +