mirror of
https://github.com/EDeev/dorm_alarm.git
synced 2026-06-15 19:11:08 +03:00
256 lines
No EOL
12 KiB
Python
256 lines
No EOL
12 KiB
Python
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"<b>🔔 Система мониторинга активирована!</b>\n\n"
|
||
f"👤 Пользователь: <code>{username}</code>\n"
|
||
f"🆔 ID: <code>{user_id}</code>\n\n"
|
||
f"⏰ Интервал по умолчанию: <b>60 минут</b>\n"
|
||
f"📊 Статус уведомлений: <b>Включены</b>\n\n"
|
||
f"<i>Используйте кнопки ниже для настройки параметров</i>",
|
||
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"<b>📊 Статус системы мониторинга</b>\n\n"
|
||
f"Пользователь: <code>{username}</code>\n"
|
||
f"ID: <code>{user_id}</code>\n\n"
|
||
f"⏰ Интервал: <b>{interval} мин</b>\n"
|
||
f"🔄 Статус уведомлений: {status_emoji} <b>{task_status}</b>\n\n"
|
||
f"👥 Всего пользователей: <b>{db.get_user_count()}</b>",
|
||
reply_markup=keyboard
|
||
)
|
||
|
||
@router.message(Command("help"))
|
||
async def help_handler(msg: Message) -> None:
|
||
"""Справочная информация по командам"""
|
||
help_text = (
|
||
"<b>Бот мониторинга работоспособности</b>\n\n"
|
||
"<b>Доступные команды:</b>\n"
|
||
"/start - Регистрация и активация\n"
|
||
"/status - Текущий статус сервера\n"
|
||
"/help - Справочная информация\n\n"
|
||
"<b>Функциональность:</b>\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"<b>🟢 Сервер работает</b>\n\n"
|
||
f"Время проверки: <code>{current_time}</code>\n"
|
||
f"Статус уведомлений: <b>Включены</b>"
|
||
)
|
||
|
||
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"<b>📊 Статус системы мониторинга</b>\n\n"
|
||
f"Пользователь: <code>{username}</code>\n"
|
||
f"ID: <code>{user_id}</code>\n\n"
|
||
f"⏰ Интервал: <b>{interval} мин</b>\n"
|
||
f"🔄 Статус уведомлений: {status_emoji} <b>{task_status}</b>\n\n"
|
||
f"👥 Всего пользователей: <b>{db.get_user_count()}</b>",
|
||
)
|
||
|
||
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") |