""" Notification template builders for ntfy push notifications. Each build_* function returns a dict with keys: title, message, tags, priority. These are passed directly to send_ntfy_notification(). """ from datetime import datetime, date from typing import Optional # ── Shared helpers ──────────────────────────────────────────────────────────── def urgency_label(target_date: date, today: date) -> str: """Human-readable urgency string relative to today.""" delta = (target_date - today).days if delta < 0: return f"OVERDUE ({abs(delta)}d ago)" elif delta == 0: return "Today" elif delta == 1: return "Tomorrow" elif delta <= 7: return f"in {delta} days" else: return target_date.strftime("%d %b") def day_str(dt: datetime, today: date) -> str: """Return 'Today', 'Tomorrow', or a short date string.""" d = dt.date() if d == today: return "Today" delta = (d - today).days if delta == 1: return "Tomorrow" return dt.strftime("%a %d %b") def time_str(dt: datetime, all_day: bool = False) -> str: """Return 'All day' or HH:MM.""" if all_day: return "All day" return dt.strftime("%H:%M") def _truncate(text: str, max_len: int) -> str: """Truncate with ellipsis if over limit.""" return (text[:max_len - 3] + "...") if len(text) > max_len else text # ── Template builders ───────────────────────────────────────────────────────── def build_event_notification( title: str, start_datetime: datetime, all_day: bool, today: date, location_name: Optional[str] = None, description: Optional[str] = None, is_starred: bool = False, ) -> dict: """Build notification payload for a calendar event reminder.""" day = day_str(start_datetime, today) time = time_str(start_datetime, all_day) loc = f" @ {location_name}" if location_name else "" desc = f" — {description[:80]}" if description else "" return { "title": _truncate(f"Calendar: {title}", 80), "message": _truncate(f"{day} at {time}{loc}{desc}", 200), "tags": ["calendar"], "priority": 4 if is_starred else 3, } def build_reminder_notification( title: str, remind_at: datetime, today: date, description: Optional[str] = None, ) -> dict: """Build notification payload for a general reminder.""" day = day_str(remind_at, today) time = time_str(remind_at) desc = f" — {description[:80]}" if description else "" return { "title": _truncate(f"Reminder: {title}", 80), "message": _truncate(f"{day} at {time}{desc}", 200), "tags": ["bell"], "priority": 3, } def build_todo_notification( title: str, due_date: date, today: date, priority: str = "medium", category: Optional[str] = None, ) -> dict: """Build notification payload for a todo due date alert.""" urgency = urgency_label(due_date, today) priority_label = priority.capitalize() cat = f" [{category}]" if category else "" # High priority for today/overdue, default otherwise ntfy_priority = 4 if (due_date - today).days <= 0 else 3 return { "title": _truncate(f"Due {urgency}: {title}", 80), "message": _truncate(f"Priority: {priority_label}{cat}", 200), "tags": ["white_check_mark"], "priority": ntfy_priority, } def build_project_notification( name: str, due_date: date, today: date, status: str = "in_progress", ) -> dict: """Build notification payload for a project deadline alert.""" urgency = urgency_label(due_date, today) # Format status label status_label = status.replace("_", " ").title() ntfy_priority = 4 if due_date <= today else 3 return { "title": _truncate(f"Project Deadline: {name}", 80), "message": _truncate(f"Due {urgency} — Status: {status_label}", 200), "tags": ["briefcase"], "priority": ntfy_priority, }