import asyncio import logging import sqlite3 from threading import Thread import telebot from pyzabbix import ZabbixAPI from telebot import types import backend_bot import bot_database from backend_flask import app from backend_locks import bot from backend_locks import db_lock from backend_zabbix import get_triggers_for_group, get_triggers_for_all_groups from config import * from utilities.log_manager import LogManager from utilities.rabbitmq import consume_from_queue from utilities.telegram_utilities import show_main_menu, show_settings_menu from utilities.user_state_manager import UserStateManager # Инициализируем класс UserStateManager state = UserStateManager() # Инициализация LogManager log_manager = LogManager(log_dir='logs', retention_days=30) # Настройка pyTelegramBotAPI logger telebot.logger = logging.getLogger('telebot') # Важно: вызов schedule_log_rotation для планировки ротации и архивации логов log_manager.schedule_log_rotation() # Handle /help command to provide instructions @bot.message_handler(commands=['help']) def handle_help(message): chat_id = message.chat.id if not bot_database.is_whitelisted(chat_id): backend_bot.bot.send_message(chat_id, "Вы неавторизованы для использования этого бота.") return help_text = ( '/start - Показать меню бота\n' 'Настройки - Перейти в режим настроек и управления подписками\n' 'Активные события - Получение всех нерешённых событий мониторинга по выбранным сервисам выбранного региона\n' 'Помощь - Описание всех возможностей бота' ) backend_bot.bot.send_message(message.chat.id, help_text, parse_mode="html") show_main_menu(message.chat.id) # Handle /register command for new user registration def handle_register(message): chat_id = message.chat.id username = message.from_user.username if username: username = f"@{username}" else: username = "N/A" text = ( f'Для продолжения регистрации необходимо отправить с корпоративного почтового адреса "РТ МИС" письмо на адрес {SUPPORT_EMAIL}\n' f'В теме письма указать "Подтверждение регистрации в телеграм-боте TeleZab".\n' f'В теле письма указать:\n' f'1. ФИО\n' f'2. Ваш Chat ID: {chat_id}\n' f'3. Ваше имя пользователя: {username}') backend_bot.bot.send_message(chat_id, text, parse_mode="HTML") bot_database.log_user_event(chat_id, username, "Requested registration") # Handle /start command @bot.message_handler(commands=['start']) def handle_start(message): show_main_menu(message.chat.id) # Settings menu for users # Основной обработчик меню @bot.message_handler(func=lambda message: True) def handle_menu_selection(message): chat_id = message.chat.id text = message.text.strip() username = message.from_user.username # Проверка авторизации if not bot_database.is_whitelisted(chat_id) and text != 'Регистрация': backend_bot.bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.") return # Получаем текущее состояние пользователя current_state = state.get_state(chat_id) # Обработка команд в зависимости от состояния if current_state == "MAIN_MENU": backend_bot.handle_main_menu(message, chat_id, text) elif current_state == "REGISTRATION": handle_register(message) elif current_state == "SETTINGS_MENU": backend_bot.handle_settings_menu(message, chat_id, text) elif current_state == "SUBSCRIBE": backend_bot.process_subscription_button(message, chat_id, username) elif current_state == "UNSUBSCRIBE": backend_bot.process_unsubscription_button(message, chat_id, username) else: backend_bot.bot.send_message(chat_id, "Команда не распознана.") show_main_menu(chat_id) @bot.callback_query_handler(func=lambda call: call.data == "cancel_action") def handle_cancel_action(call): chat_id = call.message.chat.id message_id = call.message.message_id backend_bot.bot.clear_step_handler_by_chat_id(chat_id) backend_bot.bot.send_message(chat_id, f"Действие отменено") backend_bot.bot.edit_message_reply_markup(chat_id, message_id, reply_markup=None) state.set_state(chat_id, "SETTINGS_MENU") show_settings_menu(chat_id) return @bot.callback_query_handler(func=lambda call: call.data == "cancel_active_triggers") def handle_cancel_active_triggers(call): chat_id = call.message.chat.id message_id = call.message.message_id backend_bot.bot.clear_step_handler_by_chat_id(chat_id) backend_bot.bot.send_message(chat_id, f"Действие отменено") backend_bot.bot.edit_message_reply_markup(chat_id, message_id, reply_markup=None) state.set_state(chat_id, "MAIN_MENU") show_main_menu(chat_id) return # Handle displaying active subscriptions for a user def handle_my_subscriptions_button(message): chat_id = message.chat.id username = f"@{message.from_user.username}" if message.from_user.username else "N/A" if not bot_database.is_whitelisted(chat_id): backend_bot.bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.") telebot.logger.info(f"Unauthorized access attempt by {username} {chat_id}") return user_regions = bot_database.get_user_subscribed_regions(chat_id) if not user_regions: backend_bot.bot.send_message(chat_id, "Вы не подписаны ни на один регион.") telebot.logger.debug(f"Запрашиваем {user_regions} for {username} {chat_id}") else: user_regions.sort(key=lambda x: int(x[0])) # Сортировка по числовому значению region_id regions_list = bot_database.format_regions_list(user_regions) backend_bot.bot.send_message(chat_id, f"Ваши активные подписки:\n{regions_list}") telebot.logger.debug(f"Запрашиваем {user_regions} for {username} {chat_id}") show_settings_menu(chat_id) # Handle displaying all active regions def handle_active_regions_button(message): chat_id = message.chat.id username = f"@{message.from_user.username}" if message.from_user.username else "N/A" if not bot_database.is_whitelisted(chat_id): backend_bot.bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.") telebot.logger.info(f"Unauthorized access attempt by {username} {chat_id}") return regions = bot_database.get_sorted_regions() # Используем функцию для получения отсортированных регионов if not regions: backend_bot.bot.send_message(chat_id, "Нет активных регионов.") else: regions_list = bot_database.format_regions_list(regions) backend_bot.bot.send_message(chat_id, f"Активные регионы:\n{regions_list}") show_settings_menu(chat_id) def handle_notification_mode_button(message): chat_id = message.chat.id username = f"@{message.from_user.username}" if message.from_user.username else "N/A" telebot.logger.debug(f"Handling notification mode button for user {username} ({chat_id}).") if not bot_database.is_whitelisted(chat_id): backend_bot.bot.send_message(chat_id, "Вы неавторизованы для использования этого бота") telebot.logger.warning(f"Unauthorized access attempt by {username} ({chat_id})") return # Логируем успешное авторизованное использование бота telebot.logger.info(f"User {username} ({chat_id}) is authorized and is selecting a notification mode.") # Отправляем клавиатуру выбора режима уведомлений markup = types.InlineKeyboardMarkup() markup.add(types.InlineKeyboardButton(text="Критические события", callback_data="notification_mode_disaster")) markup.add(types.InlineKeyboardButton(text="Все события", callback_data="notification_mode_all")) backend_bot.bot.send_message(chat_id, "Выберите уровень событий мониторинга, уведомление о которых хотите получать:\n" '1. Критические события (приоритет "DISASTER") - события, являющиеся потенциальными авариями и требующие оперативного решения.\nВ Zabbix обязательно имеют тег "CALL" для оперативного привлечения инженеров к устранению.\n\n' '2. Все события (По умолчанию) - критические события, а также события Zabbix высокого ("HIGH") приоритета, имеющие потенциально значительное влияние на сервис и требующее устранение в плановом порядке.', reply_markup=markup, parse_mode="HTML") telebot.logger.info(f"Sent notification mode selection message to {username} ({chat_id}).") @bot.callback_query_handler(func=lambda call: call.data.startswith("notification_mode_")) def handle_notification_mode_selection(call): chat_id = call.message.chat.id message_id = call.message.message_id mode = call.data.split("_")[2] telebot.logger.debug(f"User ({chat_id}) selected notification mode: {mode}.") # Убираем клавиатуру backend_bot.bot.edit_message_reply_markup(chat_id=chat_id, message_id=message_id, reply_markup=None) telebot.logger.debug(f"Removed inline keyboard for user ({chat_id}).") # Обновляем режим уведомлений disaster_only = True if mode == "disaster" else False try: telebot.logger.debug(f"Attempting to update notification mode in the database for user {chat_id}.") with db_lock: conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() query = 'UPDATE subscriptions SET disaster_only = ? WHERE chat_id = ?' cursor.execute(query, (disaster_only, chat_id)) conn.commit() mode_text = "Критические события" if disaster_only else "Все события" backend_bot.bot.send_message(chat_id, f"Режим уведомлений успешно изменён на: {mode_text}") telebot.logger.info(f"Notification mode for user ({chat_id}) updated to: {mode_text}") # Логируем изменение состояния пользователя state.set_state(chat_id, "SETTINGS_MENU") telebot.logger.debug(f"User state for {chat_id} set to SETTINGS_MENU.") # Показываем меню настроек show_settings_menu(chat_id) telebot.logger.debug(f"Displayed settings menu to {chat_id}.") except Exception as e: telebot.logger.error(f"Error updating notification mode for {chat_id}: {e}") backend_bot.bot.send_message(chat_id, "Произошла ошибка при изменении режима уведомлений.") finally: conn.close() telebot.logger.debug(f"Database connection closed for user {chat_id}.") # Логируем успешный ответ callback-запроса bot.answer_callback_query(call.id) telebot.logger.debug(f"Callback query for user ({chat_id}) answered.") # Фаза 1: Запрос активных событий и выбор региона с постраничным переключением def handle_active_triggers(message): chat_id = message.chat.id regions = bot_database.get_sorted_regions() # Используем функцию get_regions для получения регионов start_index = 0 markup = create_region_keyboard(regions, start_index) backend_bot.bot.send_message(chat_id, "Выберите регион для получения активных событий:", reply_markup=markup) def create_region_keyboard(regions, start_index, regions_per_page=10): markup = types.InlineKeyboardMarkup() end_index = min(start_index + regions_per_page, len(regions)) # Создаём кнопки для регионов for i in range(start_index, end_index): region_id, region_name = regions[i] button = types.InlineKeyboardButton(text=f"{region_id}: {region_name}", callback_data=f"region_{region_id}") markup.add(button) # Добавляем кнопки для переключения страниц navigation_row = [] if start_index > 0: navigation_row.append(types.InlineKeyboardButton(text="<", callback_data=f"prev_{start_index}")) if end_index < len(regions): navigation_row.append(types.InlineKeyboardButton(text=">", callback_data=f"next_{end_index}")) if navigation_row: markup.row(*navigation_row) markup.row(types.InlineKeyboardButton(text='Отмена', callback_data='cancel_active_triggers')) return markup @bot.callback_query_handler( func=lambda call: call.data.startswith("region_") or call.data.startswith("prev_") or call.data.startswith( "next_")) def handle_region_pagination(call): chat_id = call.message.chat.id message_id = call.message.message_id data = call.data regions = bot_database.get_sorted_regions() # Используем функцию get_regions для получения регионов regions_per_page = 10 # Если был выбран регион, то убираем клавиатуру и продолжаем выполнение функции if data.startswith("region_"): region_id = data.split("_")[1] bot.edit_message_reply_markup(chat_id=chat_id, message_id=message_id, reply_markup=None) handle_region_selection(call, region_id) # Продолжаем выполнение функции после выбора региона # Если была нажата кнопка для переключения страниц elif data.startswith("prev_") or data.startswith("next_"): direction, index = data.split("_") index = int(index) # Рассчитываем новый индекс страницы start_index = max(0, index - regions_per_page) if direction == "prev" else min(len(regions) - regions_per_page, index) # Обновляем клавиатуру для новой страницы markup = create_region_keyboard(regions, start_index, regions_per_page) bot.edit_message_reply_markup(chat_id=chat_id, message_id=message_id, reply_markup=markup) bot.answer_callback_query(call.id) # Фаза 2: Обработка выбора региона и предложить выбор группы def handle_region_selection(call, region_id): chat_id = call.message.chat.id try: # Получаем группы хостов для выбранного региона zapi = ZabbixAPI(ZABBIX_URL) zapi.login(api_token=ZABBIX_API_TOKEN) host_groups = zapi.hostgroup.get(output=["groupid", "name"], search={"name": region_id}) filtered_groups = [group for group in host_groups if 'test' not in group['name'].lower() and f'_{region_id}' in group['name']] # Если нет групп if not filtered_groups: backend_bot.bot.send_message(chat_id, "Нет групп хостов для этого региона.") show_main_menu(chat_id) return # Создаем клавиатуру с выбором группы или всех групп markup = types.InlineKeyboardMarkup() for group in filtered_groups: markup.add(types.InlineKeyboardButton(text=group['name'], callback_data=f"group_{group['groupid']}")) markup.add(types.InlineKeyboardButton(text="Все группы региона\n(Долгое выполнение)", callback_data=f"all_groups_{region_id}")) backend_bot.bot.send_message(chat_id, "Выберите группу хостов или получите события по всем группам региона:", reply_markup=markup) except Exception as e: backend_bot.bot.send_message(chat_id, f"Ошибка при подключении к Zabbix API.\n{str(e)}") show_main_menu(chat_id) # Фаза 3: Обработка выбора группы/всех групп и запрос периода @bot.callback_query_handler(func=lambda call: call.data.startswith("group_") or call.data.startswith("all_groups_")) def handle_group_or_all_groups(call): chat_id = call.message.chat.id message_id = call.message.message_id # Убираем клавиатуру после выбора группы bot.edit_message_reply_markup(chat_id=chat_id, message_id=message_id, reply_markup=None) # Если выбрана конкретная группа if call.data.startswith("group_"): group_id = call.data.split("_")[1] get_triggers_for_group(chat_id, group_id) # Сразу получаем события для группы show_main_menu(chat_id) # Если выбраны все группы региона elif call.data.startswith("all_groups_"): region_id = call.data.split("_")[2] get_triggers_for_all_groups(chat_id, region_id) # Сразу получаем события для всех групп региона show_main_menu(chat_id) def run_polling(): bot.infinity_polling(timeout=10, long_polling_timeout=5) # Запуск Flask-приложения def run_flask(): app.run(port=5000, host='0.0.0.0', debug=True, use_reloader=False) # Основная функция для запуска def main(): # Инициализация базы данных bot_database.init_db() # Запуск Flask и бота в отдельных потоках Thread(target=run_flask, daemon=True).start() Thread(target=run_polling, daemon=True).start() # Запуск асинхронных задач asyncio.run(consume_from_queue()) if __name__ == '__main__': main()