Telezab/telezab.py
UdoChudo 52e31864b3 feat: Develop web interface
- 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>
2025-06-10 14:39:11 +05:00

311 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()