395 lines
19 KiB
Python
395 lines
19 KiB
Python
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 log_manager import LogManager
|
||
from rabbitmq import consume_from_queue
|
||
from user_state_manager import UserStateManager
|
||
from utils import show_main_menu, show_settings_menu
|
||
|
||
# Инициализируем класс UserStateManager
|
||
user_state_manager = 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()
|
||
|
||
|
||
# # Lock for database operations
|
||
# db_lock = Lock()
|
||
|
||
# 25 messages per second
|
||
|
||
# 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 = (
|
||
'<b>/start</b> - Показать меню бота\n'
|
||
'<b>Настройки</b> - Перейти в режим настроек и управления подписками\n'
|
||
'<b>Активные события</b> - Получение всех нерешённых событий мониторинга по выбранным сервисам выбранного региона\n'
|
||
'<b>Помощь</b> - <a href="https://confluence.is-mis.ru/pages/viewpage.action?pageId=416785183">Описание всех возможностей бота</a>'
|
||
)
|
||
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'В теме письма указать "<b>Подтверждение регистрации в телеграм-боте TeleZab</b>".\n'
|
||
f'В теле письма указать:\n'
|
||
f'1. <b>ФИО</b>\n'
|
||
f'2. <b>Ваш Chat ID</b>: {chat_id}\n'
|
||
f'3. <b>Ваше имя пользователя</b>: {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 = user_state_manager.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)
|
||
user_state_manager.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)
|
||
user_state_manager.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. <b>Критические события</b> (приоритет "DISASTER") - события, являющиеся потенциальными авариями и требующие оперативного решения.\nВ Zabbix обязательно имеют тег "CALL" для оперативного привлечения инженеров к устранению.\n\n'
|
||
'2. <b>Все события (По умолчанию)</b> - критические события, а также события 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}")
|
||
|
||
# Логируем изменение состояния пользователя
|
||
user_state_manager.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()
|