diff --git a/app/services/maintenance_service.py b/app/services/maintenance_service.py
index d6615409..81ae1efc 100644
--- a/app/services/maintenance_service.py
+++ b/app/services/maintenance_service.py
@@ -28,8 +28,15 @@ class MaintenanceService:
self._status = MaintenanceStatus(is_active=False)
self._check_task: Optional[asyncio.Task] = None
self._is_checking = False
- self._max_consecutive_failures = 3
+ self._max_consecutive_failures = 3
+ self._bot = None
+ self._last_notification_sent = None
+ def set_bot(self, bot):
+ """Устанавливает ссылку на бота для отправки уведомлений"""
+ self._bot = bot
+ logger.info("Бот установлен для maintenance_service")
+
@property
def status(self) -> MaintenanceStatus:
return self._status
@@ -51,6 +58,51 @@ class MaintenanceService:
else:
return settings.get_maintenance_message()
+ async def _notify_admins(self, message: str, alert_type: str = "info"):
+ """Отправка уведомлений администраторам"""
+ if not self._bot:
+ logger.warning("Бот не установлен, уведомления не могут быть отправлены")
+ return
+
+ cache_key = f"maintenance_notification_{alert_type}"
+ if await cache.get(cache_key):
+ return
+
+ admin_ids = settings.get_admin_ids()
+ if not admin_ids:
+ logger.warning("Список администраторов пуст")
+ return
+
+ emoji_map = {
+ "error": "🚨",
+ "warning": "⚠️",
+ "success": "✅",
+ "info": "ℹ️"
+ }
+ emoji = emoji_map.get(alert_type, "ℹ️")
+
+ formatted_message = f"{emoji} Maintenance Service\n\n{message}"
+
+ success_count = 0
+ for admin_id in admin_ids:
+ try:
+ await self._bot.send_message(
+ chat_id=admin_id,
+ text=formatted_message,
+ parse_mode="HTML"
+ )
+ success_count += 1
+ await asyncio.sleep(0.1)
+
+ except Exception as e:
+ logger.error(f"Ошибка отправки уведомления админу {admin_id}: {e}")
+
+ if success_count > 0:
+ logger.info(f"Уведомление отправлено {success_count} администраторам")
+ await cache.set(cache_key, True, expire=300)
+ else:
+ logger.error("Не удалось отправить уведомления ни одному администратору")
+
async def enable_maintenance(self, reason: Optional[str] = None, auto: bool = False) -> bool:
try:
if self._status.is_active:
@@ -64,6 +116,18 @@ class MaintenanceService:
await self._save_status_to_cache()
+ notification_msg = f"""
+Режим технических работ ВКЛЮЧЕН
+
+📋 Причина: {self._status.reason}
+🤖 Автоматически: {'Да' if auto else 'Нет'}
+🕐 Время: {self._status.enabled_at.strftime('%d.%m.%Y %H:%M:%S')}
+
+Обычные пользователи временно не смогут использовать бота.
+"""
+
+ await self._notify_admins(notification_msg, "warning" if auto else "info")
+
logger.warning(f"🔧 Режим техработ ВКЛЮЧЕН. Причина: {self._status.reason}")
return True
@@ -77,6 +141,11 @@ class MaintenanceService:
logger.info("Режим техработ уже выключен")
return True
+ was_auto = self._status.auto_enabled
+ duration = None
+ if self._status.enabled_at:
+ duration = datetime.utcnow() - self._status.enabled_at
+
self._status.is_active = False
self._status.enabled_at = None
self._status.reason = None
@@ -85,6 +154,27 @@ class MaintenanceService:
await self._save_status_to_cache()
+ duration_str = ""
+ if duration:
+ hours = int(duration.total_seconds() // 3600)
+ minutes = int((duration.total_seconds() % 3600) // 60)
+ if hours > 0:
+ duration_str = f"\n⏱️ Длительность: {hours}ч {minutes}мин"
+ else:
+ duration_str = f"\n⏱️ Длительность: {minutes}мин"
+
+ notification_msg = f"""
+Режим технических работ ВЫКЛЮЧЕН
+
+🤖 Автоматически: {'Да' if was_auto else 'Нет'}
+🕐 Время: {datetime.utcnow().strftime('%d.%m.%Y %H:%M:%S')}
+{duration_str}
+
+Сервис снова доступен для пользователей.
+"""
+
+ await self._notify_admins(notification_msg, "success")
+
logger.info("✅ Режим техработ ВЫКЛЮЧЕН")
return True
@@ -102,6 +192,17 @@ class MaintenanceService:
self._check_task = asyncio.create_task(self._monitoring_loop())
logger.info(f"🔄 Запущен мониторинг API RemnaWave (интервал: {settings.get_maintenance_check_interval()}с)")
+
+ await self._notify_admins(f"""
+Мониторинг технических работ запущен
+
+🔄 Интервал проверки: {settings.get_maintenance_check_interval()} секунд
+🤖 Автовключение: {'Включено' if settings.is_maintenance_auto_enable() else 'Отключено'}
+🎯 Порог ошибок: {self._max_consecutive_failures}
+
+Система будет следить за доступностью API.
+""", "info")
+
return True
except Exception as e:
@@ -117,7 +218,8 @@ class MaintenanceService:
except asyncio.CancelledError:
pass
- logger.info("⏹️ Мониторинг API остановлен")
+ await self._notify_admins("Мониторинг технических работ остановлен", "info")
+ logger.info("ℹ️ Мониторинг API остановлен")
return True
except Exception as e:
@@ -138,6 +240,18 @@ class MaintenanceService:
is_connected = await test_api_connection(api)
if is_connected:
+ # API восстановилось
+ if not self._status.api_status:
+ await self._notify_admins(f"""
+API RemnaWave восстановлено!
+
+✅ Статус: Доступно
+🕐 Время восстановления: {self._status.last_check.strftime('%H:%M:%S')}
+🔄 Неудачных попыток было: {self._status.consecutive_failures}
+
+API снова отвечает на запросы.
+""", "success")
+
self._status.api_status = True
self._status.consecutive_failures = 0
@@ -147,9 +261,21 @@ class MaintenanceService:
return True
else:
+ was_available = self._status.api_status
self._status.api_status = False
self._status.consecutive_failures += 1
+ if was_available:
+ await self._notify_admins(f"""
+API RemnaWave недоступно!
+
+❌ Статус: Недоступно
+🕐 Время обнаружения: {self._status.last_check.strftime('%H:%M:%S')}
+🔄 Попытка: {self._status.consecutive_failures}
+
+Началась серия неудачных проверок API.
+""", "error")
+
if (self._status.consecutive_failures >= self._max_consecutive_failures and
not self._status.is_active and
settings.is_maintenance_auto_enable()):
@@ -163,6 +289,17 @@ class MaintenanceService:
except Exception as e:
logger.error(f"Ошибка проверки API: {e}")
+
+ if self._status.api_status:
+ await self._notify_admins(f"""
+Ошибка при проверке API RemnaWave
+
+❌ Ошибка: {str(e)}
+🕐 Время: {datetime.utcnow().strftime('%H:%M:%S')}
+
+Не удалось выполнить проверку доступности API.
+""", "error")
+
self._status.api_status = False
self._status.consecutive_failures += 1
return False
@@ -216,7 +353,7 @@ class MaintenanceService:
if status_data.get("last_check"):
self._status.last_check = datetime.fromisoformat(status_data["last_check"])
- logger.info(f"📥 Состояние техработ загружено из кеша: активен={self._status.is_active}")
+ logger.info(f"🔥 Состояние техработ загружено из кеша: активен={self._status.is_active}")
except Exception as e:
logger.error(f"Ошибка загрузки состояния из кеша: {e}")
@@ -232,7 +369,8 @@ class MaintenanceService:
"consecutive_failures": self._status.consecutive_failures,
"monitoring_active": self._check_task is not None and not self._check_task.done(),
"auto_enable_configured": settings.is_maintenance_auto_enable(),
- "check_interval": settings.get_maintenance_check_interval()
+ "check_interval": settings.get_maintenance_check_interval(),
+ "bot_connected": self._bot is not None
}
async def force_api_check(self) -> Dict[str, Any]: