import asyncio import logging 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_zabbix import get_triggers_for_group, get_triggers_for_all_groups from config import * from models import Subscriptions from utilities.database import db 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)[0]: 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): chat_id = message.chat.id if bot_database.is_whitelisted(chat_id)[0]: show_main_menu(chat_id) else: # Отображаем только кнопку "Регистрация" markup = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=1) item = types.KeyboardButton("Регистрация") markup.add(item) backend_bot.bot.send_message(chat_id, "Пожалуйста, зарегистрируйтесь для использования бота.", reply_markup=markup) state.set_state(chat_id, "REGISTRATION") # Основной обработчик меню @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)[0] 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 @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] username = f"@{call.from_user.username}" if call.from_user.username else "N/A" # Получаем username 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 {username} {chat_id}.") with app.app_context(): # Создаем контекст приложения subscriptions = db.session.query(Subscriptions).filter_by(chat_id=chat_id).all() for subscription in subscriptions: subscription.disaster_only = disaster_only db.session.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}.") # Логируем событие в базу данных bot_database.log_user_event(chat_id, username, f"Notification mode updated to: {mode_text}") except Exception as e: telebot.logger.error(f"Error updating notification mode for {chat_id}: {e}") backend_bot.bot.send_message(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()