1cada42370
Single notification path via ntfy HTTP publish for reliable Android delivery; remove VAPID, push subscriptions, and SW push handlers. PWA settings show topic subscribe link; humor texts and deep-link actions unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
79 lines
2.1 KiB
Python
79 lines
2.1 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
|
|
import httpx
|
|
|
|
from server.settings import settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _publish_url() -> str | None:
|
|
if not settings.ntfy_url or not settings.ntfy_topic:
|
|
return None
|
|
return f"{settings.ntfy_url.rstrip('/')}/{settings.ntfy_topic}"
|
|
|
|
|
|
def _click_base() -> str:
|
|
return settings.ntfy_click_url.rstrip("/")
|
|
|
|
|
|
async def send_slot_reminder(
|
|
slot_id: str,
|
|
title: str,
|
|
body: str,
|
|
*,
|
|
silent: bool = True,
|
|
) -> int:
|
|
url = _publish_url()
|
|
if not url:
|
|
logger.warning(
|
|
"Notify skipped slot=%s reason=ntfy_not_configured (NTFY_URL + NTFY_TOPIC)",
|
|
slot_id,
|
|
)
|
|
return 0
|
|
|
|
click_base = _click_base()
|
|
headers: dict[str, str] = {
|
|
"Title": title,
|
|
"Click": f"{click_base}/?slot={slot_id}",
|
|
"Priority": "2" if silent else "3",
|
|
"Tags": "pill",
|
|
"Actions": json.dumps(
|
|
[
|
|
{
|
|
"action": "view",
|
|
"label": "Genommen ✓",
|
|
"url": f"{click_base}/?action=take&slot={slot_id}",
|
|
},
|
|
{
|
|
"action": "view",
|
|
"label": "+15 Min",
|
|
"url": f"{click_base}/?action=snooze&slot={slot_id}&minutes=15",
|
|
},
|
|
{
|
|
"action": "view",
|
|
"label": "+30 Min",
|
|
"url": f"{click_base}/?action=snooze&slot={slot_id}&minutes=30",
|
|
},
|
|
]
|
|
),
|
|
}
|
|
if settings.ntfy_token:
|
|
headers["Authorization"] = f"Bearer {settings.ntfy_token}"
|
|
|
|
logger.info("Notify start slot=%s title=%r topic=%s", slot_id, title, settings.ntfy_topic)
|
|
|
|
try:
|
|
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
resp = await client.post(url, content=body.encode("utf-8"), headers=headers)
|
|
resp.raise_for_status()
|
|
except httpx.HTTPError as exc:
|
|
logger.warning("Notify failed slot=%s error=%s", slot_id, exc)
|
|
return 0
|
|
|
|
logger.info("Notify ok slot=%s", slot_id)
|
|
return 1
|