import asyncio import logging from datetime import datetime from typing import Dict from aiogram import F, Router from aiogram.types import Message, InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery from aiogram.filters import Command from sql import DatabaseManager from init import bot router = Router() db = DatabaseManager() # Глобальный реестр активных задач уведомлений для управления жизненным циклом notification_tasks: Dict[int, asyncio.Task] = {} @router.message(Command("start")) async def start_handler(msg: Message) -> None: """Регистрация пользователя и инициализация уведомлений""" user_id = msg.from_user.id username = msg.from_user.username or msg.from_user.first_name if db.add_user(user_id, username): keyboard = create_main_keyboard() await msg.answer( f"🔔 Система мониторинга активирована!\n\n" f"👤 Пользователь: {username}\n" f"🆔 ID: {user_id}\n\n" f"⏰ Интервал по умолчанию: 60 минут\n" f"📊 Статус уведомлений: Включены\n\n" f"Используйте кнопки ниже для настройки параметров", reply_markup=keyboard ) # Запуск персональной задачи уведомлений start_user_notifications(user_id) else: await msg.answer("❌ Ошибка инициализации системы") @router.message(Command("status")) async def status_handler(msg: Message) -> None: """Отображение текущего статуса пользователя""" user_info = db.get_user_info(msg.from_user.id) if not user_info: await msg.answer("❌ Пользователь не найден. Используйте /start для регистрации") return user_id, username, interval, is_active = user_info status_emoji = "🟢" if is_active else "🔴" task_status = "Включены" if user_id in notification_tasks else "Остановлены" keyboard = create_main_keyboard() await msg.answer( f"📊 Статус системы мониторинга\n\n" f"Пользователь: {username}\n" f"ID: {user_id}\n\n" f"⏰ Интервал: {interval} мин\n" f"🔄 Статус уведомлений: {status_emoji} {task_status}\n\n" f"👥 Всего пользователей: {db.get_user_count()}", reply_markup=keyboard ) @router.message(Command("help")) async def help_handler(msg: Message) -> None: """Справочная информация по командам""" help_text = ( "Бот мониторинга работоспособности\n\n" "Доступные команды:\n" "/start - Регистрация и активация\n" "/status - Текущий статус сервера\n" "/help - Справочная информация\n\n" "Функциональность:\n" "• Периодические уведомления о работе\n" "• Настройка интервала (5-1440 мин)\n" "• Включение/отключение уведомлений" ) await msg.answer(help_text) # === CALLBACK HANDLERS === @router.callback_query(F.data == "toggle_notifications") async def toggle_notifications_handler(call: CallbackQuery) -> None: """Переключение активности уведомлений""" user_id = call.from_user.id new_status = db.toggle_notifications(user_id) if new_status is None: await call.answer("❌ Ошибка обновления настроек") return if new_status: start_user_notifications(user_id) await call.answer("✅ Уведомления включены") else: stop_user_notifications(user_id) await call.answer("⏸️ Уведомления отключены") # Обновление интерфейса с новыми данными await update_status_message(call) @router.callback_query(F.data.startswith("set_interval_")) async def set_interval_handler(call: CallbackQuery) -> None: """Установка нового интервала уведомлений""" interval = int(call.data.split("_")[2]) user_id = call.from_user.id if db.update_interval(user_id, interval): # Перезапуск задачи с новым интервалом restart_user_notifications(user_id) await call.answer(f"⏰ Интервал установлен: {interval} мин") await update_status_message(call) else: await call.answer("❌ Ошибка обновления интервала") @router.callback_query(F.data == "interval_menu") async def interval_menu_handler(call: CallbackQuery) -> None: """Отображение меню выбора интервала""" keyboard = create_interval_keyboard() await call.message.edit_reply_markup(reply_markup=keyboard) @router.callback_query(F.data == "main_menu") async def main_menu_handler(call: CallbackQuery) -> None: """Возврат к главному меню""" keyboard = create_main_keyboard() await call.message.edit_reply_markup(reply_markup=keyboard) # === NOTIFICATION SYSTEM === async def send_notification(user_id: int) -> bool: """Отправка уведомления конкретному пользователю""" try: current_time = datetime.now().strftime("%H:%M:%S") message = ( f"🟢 Сервер работает\n\n" f"Время проверки: {current_time}\n" f"Статус уведомлений: Включены" ) await bot.send_message(user_id, message) db.log_notification(user_id, "sent") logging.info(f"Notification sent to user {user_id}") return True except Exception as e: logging.error(f"Failed to send notification to {user_id}: {e}") db.log_notification(user_id, "failed") return False async def notification_task(user_id: int, interval_minutes: int): """Асинхронная задача периодических уведомлений""" while True: try: await asyncio.sleep(interval_minutes * 60) # Проверка активности пользователя перед отправкой user_info = db.get_user_info(user_id) if user_info and user_info[3]: # is_active await send_notification(user_id) else: logging.info(f"User {user_id} notifications disabled, stopping task") break except asyncio.CancelledError: logging.info(f"Notification task for user {user_id} cancelled") break except Exception as e: logging.error(f"Error in notification task for user {user_id}: {e}") await asyncio.sleep(60) # Retry delay при ошибках def start_user_notifications(user_id: int): """Запуск задачи уведомлений для пользователя""" stop_user_notifications(user_id) # Остановка существующей задачи user_info = db.get_user_info(user_id) if user_info and user_info[3]: # is_active interval = user_info[2] # interval_minutes task = asyncio.create_task(notification_task(user_id, interval)) notification_tasks[user_id] = task logging.info(f"Started notifications for user {user_id} with {interval}min interval") def stop_user_notifications(user_id: int): """Остановка задачи уведомлений для пользователя""" if user_id in notification_tasks: notification_tasks[user_id].cancel() del notification_tasks[user_id] logging.info(f"Stopped notifications for user {user_id}") def restart_user_notifications(user_id: int): """Перезапуск задачи уведомлений с обновленными параметрами""" stop_user_notifications(user_id) start_user_notifications(user_id) # === UTILITY FUNCTIONS === def create_main_keyboard() -> InlineKeyboardMarkup: """Создание основной клавиатуры управления""" buttons = [ [InlineKeyboardButton(text="Изменить интервал", callback_data="interval_menu")], [InlineKeyboardButton(text="Вкл/выкл уведомления", callback_data="toggle_notifications")] ] return InlineKeyboardMarkup(inline_keyboard=buttons) def create_interval_keyboard() -> InlineKeyboardMarkup: """Создание клавиатуры выбора интервала""" intervals = [5, 15, 30, 60, 120, 360, 720, 1440] # От 5 минут до суток buttons = [] # Группировка кнопок по 2 в ряду для компактности for i in range(0, len(intervals), 2): row = [] for j in range(i, min(i + 2, len(intervals))): interval = intervals[j] text = f"{interval}м" if interval < 60 else f"{interval//60}ч" row.append(InlineKeyboardButton( text=text, callback_data=f"set_interval_{interval}" )) buttons.append(row) buttons.append([InlineKeyboardButton(text="↩️ Назад", callback_data="main_menu")]) return InlineKeyboardMarkup(inline_keyboard=buttons) async def update_status_message(call: CallbackQuery): """Обновление сообщения со статусом""" user_info = db.get_user_info(call.from_user.id) if not user_info: return user_id, username, interval, is_active = user_info status_emoji = "🟢" if is_active else "🔴" task_status = "Включены" if user_id in notification_tasks else "Остановлены" text = ( f"📊 Статус системы мониторинга\n\n" f"Пользователь: {username}\n" f"ID: {user_id}\n\n" f"⏰ Интервал: {interval} мин\n" f"🔄 Статус уведомлений: {status_emoji} {task_status}\n\n" f"👥 Всего пользователей: {db.get_user_count()}", ) try: await call.message.edit_text(text, reply_markup=keyboard) except: pass # Игнорирование ошибок при неизменном контенте # Старт функций async def initialize_existing_users(): """Инициализация уведомлений для существующих пользователей при запуске""" active_users = db.get_active_users() for user_id, interval in active_users: start_user_notifications(user_id) logging.info(f"Initialized notifications for {len(active_users)} active users")