"""Telegram-уведомления с антиспамом.""" from __future__ import annotations import time from typing import Optional import httpx from app.db.database import get_session from app.db.models import Setting _antispam: dict[str, float] = {} THROTTLE_S = 600 # не чаще 1 раза в 10 мин на ключ def _get(key: str) -> str: with get_session() as s: row = s.get(Setting, key) return row.value if row else "" def _set(key: str, value: str) -> None: with get_session() as s: row = s.get(Setting, key) or Setting(key=key) row.value = value s.add(row) s.commit() def get_config() -> dict: return { "token": _get("tg_token"), "chat_id": _get("tg_chat_id"), "enabled": _get("tg_enabled") == "1", "on_alert": _get("tg_on_alert") == "1", "on_task_done": _get("tg_on_task_done") == "1", } def save_config(token: str, chat_id: str, enabled: bool, on_alert: bool, on_task_done: bool) -> None: _set("tg_token", token) _set("tg_chat_id", chat_id) _set("tg_enabled", "1" if enabled else "0") _set("tg_on_alert", "1" if on_alert else "0") _set("tg_on_task_done", "1" if on_task_done else "0") async def send(text: str, key: Optional[str] = None, force: bool = False) -> bool: """Отправить сообщение. key — для антиспама (например 'disk:server1').""" cfg = get_config() if not cfg["enabled"] or not cfg["token"] or not cfg["chat_id"]: return False if key and not force: last = _antispam.get(key, 0) if time.time() - last < THROTTLE_S: return False _antispam[key] = time.time() url = f"https://api.telegram.org/bot{cfg['token']}/sendMessage" try: async with httpx.AsyncClient(timeout=15) as cl: r = await cl.post(url, json={ "chat_id": cfg["chat_id"], "text": text, "parse_mode": "HTML", "disable_web_page_preview": True, }) return r.status_code == 200 except Exception: return False async def test() -> tuple[bool, str]: cfg = get_config() if not cfg["token"] or not cfg["chat_id"]: return False, "заполните token и chat_id" ok = await send("✅ Debian Manager: тестовое уведомление", force=True) return ok, "отправлено" if ok else "ошибка отправки (проверьте token/chat_id)" async def alert(server_name: str, text: str) -> None: cfg = get_config() if cfg["enabled"] and cfg["on_alert"]: await send(f"⚠️ {server_name}: {text}", key=f"alert:{server_name}:{text[:30]}") async def task_done(server_name: str, title: str, status: str) -> None: cfg = get_config() if cfg["enabled"] and cfg["on_task_done"]: icon = "✅" if status == "success" else "❌" await send(f"{icon} {server_name}: {title} — {status}", force=True)