- Implemented the initial version of the web interface. refactor: Begin Telegram bot refactoring - Started restructuring the bot’s code for better maintainability. chore: Migrate to Flask project structure - Reorganized the application to follow Flask's project structure. cleanup: Extensive code cleanup - Removed redundant code and improved readability. Signed-off-by: UdoChudo <stream@udochudo.ru>
311 lines
14 KiB
Python
311 lines
14 KiB
Python
import logging
|
||
from multiprocessing import Process
|
||
import telebot
|
||
from pyzabbix import ZabbixAPI
|
||
from telebot import types
|
||
import backend_bot
|
||
import bot_database
|
||
from app import app, create_app
|
||
from app.bot.telezab_bot import run_bot
|
||
from backend_locks import bot
|
||
from backend_zabbix import get_triggers_for_group, get_triggers_for_all_groups
|
||
from config import *
|
||
from app.models import Subscriptions
|
||
from app.extensions.db 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()
|
||
|
||
# Настройка 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 = (
|
||
'<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")
|
||
|
||
|
||
|
||
@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]
|
||
|
||
# Форматируем region_id: добавляем ведущий 0 только если < 10
|
||
if 0 <= int(region_id) < 10:
|
||
region_id_str = f"0{region_id}"
|
||
else:
|
||
region_id_str = str(region_id)
|
||
|
||
button = types.InlineKeyboardButton(
|
||
text=f"{region_id_str}: {region_name}",
|
||
callback_data=f"region_{region_id_str}"
|
||
)
|
||
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]
|
||
telebot.logger.debug(region_id)
|
||
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
|
||
telebot.logger.debug(f"{type(region_id)}, {region_id}, {call.data}")
|
||
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())
|
||
|
||
def start_flask():
|
||
app = create_app()
|
||
app.run(host="0.0.0.0", port=5000)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
flask_process = Process(target=start_flask)
|
||
bot_process = Process(target=run_bot)
|
||
|
||
flask_process.start()
|
||
bot_process.start()
|
||
|
||
flask_process.join()
|
||
bot_process.join()
|