from __future__ import annotations import json from datetime import datetime from typing import Any import httpx from .db import db, utc_now from .settings_store import get_setting COLORS = { "backup_started": 0x3498DB, "pve_completed": 0x2ECC71, "upload_started": 0x9B59B6, "backup_completed": 0x2ECC71, "backup_failed": 0xE74C3C, "remote_deleted": 0xF39C12, "recovery_action": 0x95A5A6, "local_downloaded": 0x1ABC9C, "local_deleted": 0x95A5A6, } def _field(name: str, value: Any) -> dict: return {"name": name, "value": str(value if value is not None else "-"), "inline": True} def _duration_seconds(backup: dict) -> int | None: started = backup.get("started_at") ended = backup.get("completed_at") or backup.get("deleted_at") or backup.get("pve_completed_at") if not started or not ended: return None try: return int((datetime.fromisoformat(ended) - datetime.fromisoformat(started)).total_seconds()) except Exception: return None async def notify(event_type: str, *, backup: dict | None = None, message: str = "") -> None: webhook_url = get_setting("discord_webhook_url", "") fields = [] backup_id = None if backup: backup_id = backup.get("id") duration = _duration_seconds(backup) enriched = {**backup, "duration_seconds": duration} for key in ("guest_name", "guest_vmid", "id", "state", "size_bytes", "duration_seconds", "error_message"): if enriched.get(key) not in (None, ""): fields.append(_field(key, enriched.get(key))) if message: fields.append(_field("message", message)) payload = { "embeds": [ { "title": event_type.replace("_", " ").title(), "color": COLORS.get(event_type, 0x3498DB), "timestamp": utc_now(), "fields": fields, } ] } success = False error_message = None if webhook_url: try: async with httpx.AsyncClient(timeout=15) as client: response = await client.post(webhook_url, json=payload) response.raise_for_status() success = True except Exception as exc: # Notifications must never fail backups. error_message = str(exc) with db() as conn: conn.execute( """ INSERT INTO notification_logs(event_type, backup_id, payload, success, error_message, created_at) VALUES (?, ?, ?, ?, ?, ?) """, (event_type, backup_id, json.dumps(payload), int(success), error_message, utc_now()), )