Initial commit: Debian Manager — управление Debian-серверами
- SSH/SFTP управление без агентов - Пакеты (apt), Docker, файлы, systemd, ufw, cron - Плейбуки, групповые операции, планировщик - Бэкапы, отчёты, Portainer, Proxmox VE, Gitea Deploy - Терминал, Telegram-уведомления, тёмная/светлая тема
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
"""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"⚠️ <b>{server_name}</b>: {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} <b>{server_name}</b>: {title} — {status}", force=True)
|
||||
Reference in New Issue
Block a user