Small cleanup code

This commit is contained in:
Влад Зверев 2024-09-19 15:21:52 +05:00
parent 4ef050e3c8
commit a1b961ae20
2 changed files with 125 additions and 134 deletions

View File

@ -29,6 +29,7 @@ class LogManager:
'flask_error': os.path.join(self.log_dir, 'flask_error.log'), 'flask_error': os.path.join(self.log_dir, 'flask_error.log'),
'app': os.path.join(self.log_dir, 'app.log'), 'app': os.path.join(self.log_dir, 'app.log'),
'app_error': os.path.join(self.log_dir, 'app_error.log'), 'app_error': os.path.join(self.log_dir, 'app_error.log'),
'debug': os.path.join(self.log_dir, 'debug.log'),
} }
# Ensure the log directory exists # Ensure the log directory exists
@ -49,6 +50,12 @@ class LogManager:
'error': { 'error': {
'format': '[%(asctime)s] %(levelname)s %(module)s: %(message)s', 'format': '[%(asctime)s] %(levelname)s %(module)s: %(message)s',
}, },
'werkzeug': {
'format': '[%(asctime)s] %(levelname)s %(message)s'
},
'debug': {
'format': '[%(asctime)s] %(levelname)s %(module)s [%(funcName)s:%(lineno)d]: %(message)s'
}
}, },
'handlers': { 'handlers': {
'telebot_console': { 'telebot_console': {
@ -59,14 +66,14 @@ class LogManager:
'flask_console': { 'flask_console': {
'class': 'log_manager.UTF8StreamHandler', 'class': 'log_manager.UTF8StreamHandler',
'stream': 'ext://sys.stdout', 'stream': 'ext://sys.stdout',
'formatter': 'default', 'formatter': 'werkzeug',
}, },
'flask_file': { 'flask_file': {
'class': 'logging.handlers.TimedRotatingFileHandler', 'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': self.log_files['flask'], 'filename': self.log_files['flask'],
'when': 'midnight', 'when': 'midnight',
'backupCount': self.retention_days, 'backupCount': self.retention_days,
'formatter': 'default', 'formatter': 'werkzeug',
'encoding': 'utf-8', 'encoding': 'utf-8',
}, },
'flask_error_file': { 'flask_error_file': {
@ -74,7 +81,7 @@ class LogManager:
'filename': self.log_files['flask_error'], 'filename': self.log_files['flask_error'],
'when': 'midnight', 'when': 'midnight',
'backupCount': self.retention_days, 'backupCount': self.retention_days,
'formatter': 'error', 'formatter': 'werkzeug',
'encoding': 'utf-8', 'encoding': 'utf-8',
'level': 'ERROR', 'level': 'ERROR',
}, },
@ -95,6 +102,15 @@ class LogManager:
'encoding': 'utf-8', 'encoding': 'utf-8',
'level': 'ERROR', 'level': 'ERROR',
}, },
'debug_file': {
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': self.log_files['debug'],
'when': 'midnight',
'backupCount': self.retention_days,
'formatter': 'debug',
'encoding': 'utf-8',
'level': 'DEBUG',
},
}, },
'loggers': { 'loggers': {
'flask': { 'flask': {
@ -107,6 +123,16 @@ class LogManager:
'handlers': ['app_file', 'app_error_file', 'telebot_console'], 'handlers': ['app_file', 'app_error_file', 'telebot_console'],
'propagate': False, 'propagate': False,
}, },
'werkzeug': {
'level': 'INFO',
'handlers': ['flask_file', 'flask_error_file', 'flask_console'],
'propagate': False,
},
'debug': {
'level': 'DEBUG',
'handlers': ['debug_file'],
'propagate': False,
},
} }
# 'root': { # 'root': {
# 'level': 'DEBUG', # 'level': 'DEBUG',
@ -134,6 +160,19 @@ class LogManager:
# Clean up old archives # Clean up old archives
self.cleanup_old_archives() self.cleanup_old_archives()
def configure_werkzeug_logging(self):
"""Отключаем встроенный логгер Werkzeug и задаём собственные настройки логирования."""
werkzeug_logger = logging.getLogger('werkzeug')
werkzeug_logger.handlers = [] # Удаляем существующие обработчики
# Добавляем кастомный обработчик для форматирования логов
handler = TimedRotatingFileHandler(self.log_files['flask'], when='midnight', backupCount=self.retention_days, encoding='utf-8')
handler.setFormatter(logging.Formatter('[%(asctime)s] %(levelname)s %(message)s'))
werkzeug_logger.addHandler(handler)
# Отключаем дублирование логов
werkzeug_logger.propagate = False
def cleanup_old_archives(self): def cleanup_old_archives(self):
"""Deletes archived logs older than retention_days.""" """Deletes archived logs older than retention_days."""
now = datetime.now() now = datetime.now()

View File

@ -42,25 +42,25 @@ def load_value_from_file(file_name):
# Функция для получения переменной из окружения или файла # Функция для получения переменной из окружения или файла
def get_variable_value(variable_name): # def get_variable_value(variable_name):
# Попытка получить значение из окружения # # Попытка получить значение из окружения
value = os.getenv(variable_name) # value = os.getenv(variable_name)
#
# Если переменная окружения не установлена, попробуем загрузить из файла # # Если переменная окружения не установлена, попробуем загрузить из файла
if not value: # if not value:
file_value = "file_" + variable_name # file_value = "file_" + variable_name
value = os.getenv(file_value) # value = os.getenv(file_value)
with open(value, 'r') as file: # with open(value, 'r') as file:
value = file.read() # value = file.read()
return value # return value
return value # return value
# #
DEV = get_variable_value('DEV') DEV = os.getenv('DEV')
# Загрузка переменных окружения или значений из файлов # Загрузка переменных окружения или значений из файлов
TOKEN = get_variable_value('TELEGRAM_TOKEN') TOKEN = os.getenv('TELEGRAM_TOKEN')
ZABBIX_API_TOKEN = get_variable_value('ZABBIX_API_TOKEN') ZABBIX_API_TOKEN = os.getenv('ZABBIX_API_TOKEN')
ZABBIX_URL = get_variable_value('ZABBIX_URL') ZABBIX_URL = os.getenv('ZABBIX_URL')
DB_PATH = 'db/telezab.db' DB_PATH = 'db/telezab.db'
SUPPORT_EMAIL = "shiftsupport-rtmis@rtmis.ru" SUPPORT_EMAIL = "shiftsupport-rtmis@rtmis.ru"
BASE_URL = '/telezab' BASE_URL = '/telezab'
@ -70,7 +70,7 @@ region_api = RegionAPI(DB_PATH)
user_state_manager = UserStateManager() user_state_manager = UserStateManager()
# Initialize Flask application # Initialize Flask application
app = Flask(__name__, template_folder='templates') app = Flask(__name__,static_url_path='/static', template_folder='templates')
# Инициализация LogManager # Инициализация LogManager
log_manager = LogManager(log_dir='logs', retention_days=30) log_manager = LogManager(log_dir='logs', retention_days=30)
@ -233,16 +233,6 @@ def get_admins():
conn.close() conn.close()
return admins return admins
# # Get list of regions
# def get_regions():
# with db_lock:
# conn = sqlite3.connect(DB_PATH)
# cursor = conn.cursor()
# cursor.execute('SELECT region_id, region_name FROM regions WHERE active = TRUE ORDER BY region_id')
# regions = cursor.fetchall()
# conn.close()
# return regions
def get_sorted_regions(): def get_sorted_regions():
with db_lock: with db_lock:
@ -251,12 +241,10 @@ def get_sorted_regions():
cursor.execute('SELECT region_id, region_name FROM regions WHERE active = TRUE') cursor.execute('SELECT region_id, region_name FROM regions WHERE active = TRUE')
regions = cursor.fetchall() regions = cursor.fetchall()
conn.close() conn.close()
# Сортируем регионы по числовому значению region_id # Сортируем регионы по числовому значению region_id
regions.sort(key=lambda x: int(x[0])) regions.sort(key=lambda x: int(x[0]))
return regions return regions
# Check if region exists # Check if region exists
def region_exists(region_id): def region_exists(region_id):
with db_lock: with db_lock:
@ -267,7 +255,6 @@ def region_exists(region_id):
conn.close() conn.close()
return count > 0 return count > 0
# Get list of regions a user is subscribed to # Get list of regions a user is subscribed to
def get_user_subscribed_regions(chat_id): def get_user_subscribed_regions(chat_id):
with db_lock: with db_lock:
@ -282,11 +269,10 @@ def get_user_subscribed_regions(chat_id):
''', (chat_id,)) ''', (chat_id,))
regions = cursor.fetchall() regions = cursor.fetchall()
conn.close() conn.close()
# Сортируем регионы по числовому значению region_id
regions.sort(key=lambda x: int(x[0]))
return regions return regions
# Check if user is subscribed to a region # Check if user is subscribed to a region
def is_subscribed(chat_id, region_id): def is_subscribed(chat_id, region_id):
with db_lock: with db_lock:
@ -301,7 +287,6 @@ def is_subscribed(chat_id, region_id):
conn.close() conn.close()
return count > 0 return count > 0
# Format regions list # Format regions list
def format_regions_list(regions): def format_regions_list(regions):
return '\n'.join([f"{region_id} - {region_name}" for region_id, region_name in regions]) return '\n'.join([f"{region_id} - {region_name}" for region_id, region_name in regions])
@ -327,8 +312,6 @@ def log_user_event(chat_id, username, action):
NOTIFICATION_MODE = 1 NOTIFICATION_MODE = 1
SETTINGS_MODE = 2 SETTINGS_MODE = 2
# Handle /help command to provide instructions # Handle /help command to provide instructions
@bot.message_handler(commands=['help']) @bot.message_handler(commands=['help'])
def handle_help(message): def handle_help(message):
@ -339,13 +322,12 @@ def handle_help(message):
help_text = ( help_text = (
'<b>/start</b> - Показать меню бота\n' '<b>/start</b> - Показать меню бота\n'
'<b>Настройки</b> - Перейти в режим настроек и управления подписками\n' '<b>Настройки</b> - Перейти в режим настроек и управления подписками\n'
'<b>Активные события</b> - Получение всех нерешённых событий мониторинга по выбранным сервисам в выбранного региона\n' '<b>Активные события</b> - Получение всех нерешённых событий мониторинга по выбранным сервисам выбранного региона\n'
'<b>Помощь</b> - <a href="https://confluence.is-mis.ru/pages/viewpage.action?pageId=416785183">Описание всех возможностей бота</a>' '<b>Помощь</b> - <a href="https://confluence.is-mis.ru/pages/viewpage.action?pageId=416785183">Описание всех возможностей бота</a>'
) )
bot.send_message(message.chat.id, help_text, parse_mode="html") bot.send_message(message.chat.id, help_text, parse_mode="html")
show_main_menu(message.chat.id) show_main_menu(message.chat.id)
# Handle /register command for new user registration # Handle /register command for new user registration
def handle_register(message): def handle_register(message):
chat_id = message.chat.id chat_id = message.chat.id
@ -363,12 +345,12 @@ def handle_register(message):
bot.send_message(chat_id,text,parse_mode="HTML") bot.send_message(chat_id,text,parse_mode="HTML")
log_user_event(chat_id, username, "Requested registration") log_user_event(chat_id, username, "Requested registration")
# Handle /start command # Handle /start command
@bot.message_handler(commands=['start']) @bot.message_handler(commands=['start'])
def handle_start(message): def handle_start(message):
show_main_menu(message.chat.id) show_main_menu(message.chat.id)
def show_main_menu(chat_id): def show_main_menu(chat_id):
markup = telebot.types.ReplyKeyboardMarkup(one_time_keyboard=True, resize_keyboard=True) markup = telebot.types.ReplyKeyboardMarkup(one_time_keyboard=True, resize_keyboard=True)
if is_whitelisted(chat_id): if is_whitelisted(chat_id):
@ -377,7 +359,6 @@ def show_main_menu(chat_id):
else: else:
user_state_manager.set_state(chat_id, "REGISTRATION") user_state_manager.set_state(chat_id, "REGISTRATION")
markup.add('Регистрация') markup.add('Регистрация')
bot.send_message(chat_id, "Выберите действие:", reply_markup=markup) bot.send_message(chat_id, "Выберите действие:", reply_markup=markup)
@ -385,8 +366,7 @@ def create_settings_keyboard(chat_id, admins_list):
markup = telebot.types.ReplyKeyboardMarkup(one_time_keyboard=True, resize_keyboard=True) markup = telebot.types.ReplyKeyboardMarkup(one_time_keyboard=True, resize_keyboard=True)
# Линия 1: "Подписаться", "Отписаться" # Линия 1: "Подписаться", "Отписаться"
markup.row('Подписаться','Отписаться') markup.row('Подписаться','Отписаться')
markup.row('Мои подписки') markup.row('Мои подписки','Режим уведомлений')
markup.row('Режим уведомлений')
if DEV == '1': if DEV == '1':
if chat_id in admins_list: if chat_id in admins_list:
markup.row('Активные регионы') markup.row('Активные регионы')
@ -394,17 +374,14 @@ def create_settings_keyboard(chat_id, admins_list):
markup.row('Назад') markup.row('Назад')
return markup return markup
# Settings menu for users # Settings menu for users
def show_settings_menu(chat_id): def show_settings_menu(chat_id):
if not is_whitelisted(chat_id): if not is_whitelisted(chat_id):
user_state_manager.set_state(chat_id, "REGISTRATION") user_state_manager.set_state(chat_id, "REGISTRATION")
bot.send_message(chat_id, "Вы неавторизованы для использования этого бота") bot.send_message(chat_id, "Вы неавторизованы для использования этого бота")
return return
admins_list = get_admins() admins_list = get_admins()
markup = create_settings_keyboard(chat_id, admins_list) markup = create_settings_keyboard(chat_id, admins_list)
bot.send_message(chat_id, "Вы находитесь в режиме настроек. Выберите действие:", reply_markup=markup) bot.send_message(chat_id, "Вы находитесь в режиме настроек. Выберите действие:", reply_markup=markup)
# Основной обработчик меню # Основной обработчик меню
@ -413,15 +390,12 @@ def handle_menu_selection(message):
chat_id = message.chat.id chat_id = message.chat.id
text = message.text.strip() text = message.text.strip()
username = message.from_user.username username = message.from_user.username
# Проверка авторизации # Проверка авторизации
if not is_whitelisted(chat_id) and text != 'Регистрация': if not is_whitelisted(chat_id) and text != 'Регистрация':
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.") bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
return return
# Получаем текущее состояние пользователя # Получаем текущее состояние пользователя
current_state = user_state_manager.get_state(chat_id) current_state = user_state_manager.get_state(chat_id)
# Обработка команд в зависимости от состояния # Обработка команд в зависимости от состояния
if current_state == "MAIN_MENU": if current_state == "MAIN_MENU":
handle_main_menu(message, chat_id, text) handle_main_menu(message, chat_id, text)
@ -462,7 +436,6 @@ def handle_main_menu(message, chat_id, text):
def handle_settings_menu(message, chat_id, text): def handle_settings_menu(message, chat_id, text):
"""Обработка команд в меню настроек.""" """Обработка команд в меню настроек."""
admins_list = get_admins() admins_list = get_admins()
if text.lower() == 'подписаться': if text.lower() == 'подписаться':
user_state_manager.set_state(chat_id, "SUBSCRIBE") user_state_manager.set_state(chat_id, "SUBSCRIBE")
handle_subscribe_button(message) handle_subscribe_button(message)
@ -494,83 +467,58 @@ def handle_subscribe_button(message):
if not is_whitelisted(chat_id): if not is_whitelisted(chat_id):
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.") bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
return return
username = message.from_user.username username = message.from_user.username
if username: if username:
username = f"@{username}" username = f"@{username}"
else: else:
username = "N/A" username = "N/A"
regions_list = format_regions_list(get_sorted_regions()) regions_list = format_regions_list(get_sorted_regions())
markup = telebot.types.InlineKeyboardMarkup() markup = telebot.types.InlineKeyboardMarkup()
markup.add(telebot.types.InlineKeyboardButton(text="Отмена", markup.add(telebot.types.InlineKeyboardButton(text="Отмена",
callback_data=f"cancel_action")) callback_data=f"cancel_action"))
bot.send_message(chat_id, f"Отправьте номера регионов через запятую:\n{regions_list}\n",reply_markup=markup) bot.send_message(chat_id, f"Отправьте номера регионов через запятую:\n{regions_list}\n",reply_markup=markup)
# Сохраняем ID сообщения с клавиатурой для последующего редактирования
# user_state_manager.set_state(chat_id, "WAITING_FOR_INPUT",
# extra_data={"cancel_message_id": sent_message.message_id})
bot.register_next_step_handler_by_chat_id(chat_id, process_subscription_button, chat_id, username) bot.register_next_step_handler_by_chat_id(chat_id, process_subscription_button, chat_id, username)
def process_subscription_button(message, chat_id, username): def process_subscription_button(message, chat_id, username):
subbed_regions = [] subbed_regions = []
invalid_regions = [] invalid_regions = []
if message.text.lower() == 'отмена': if message.text.lower() == 'отмена':
bot.send_message(chat_id, "Действие отменено.") bot.send_message(chat_id, "Действие отменено.")
user_state_manager.set_state(chat_id, "SETTINGS_MENU") user_state_manager.set_state(chat_id, "SETTINGS_MENU")
return show_settings_menu(chat_id) return show_settings_menu(chat_id)
if not all(part.strip().isdigit() for part in message.text.split(',')): if not all(part.strip().isdigit() for part in message.text.split(',')):
markup = telebot.types.InlineKeyboardMarkup() markup = telebot.types.InlineKeyboardMarkup()
markup.add(telebot.types.InlineKeyboardButton(text="Отмена", markup.add(telebot.types.InlineKeyboardButton(text="Отмена",
callback_data=f"cancel_action")) callback_data=f"cancel_action"))
bot.send_message(chat_id, "Неверный формат данных. Введите номер или номера регионов через запятую.", reply_markup=markup) bot.send_message(chat_id, "Неверный формат данных. Введите номер или номера регионов через запятую.", reply_markup=markup)
# # Сохраняем ID сообщения с клавиатурой для последующего редактирования
# user_state_manager.set_state(chat_id, "WAITING_FOR_INPUT", extra_data={"cancel_message_id": sent_message.message_id})
bot.register_next_step_handler_by_chat_id(chat_id, process_subscription_button, chat_id, username) bot.register_next_step_handler_by_chat_id(chat_id, process_subscription_button, chat_id, username)
return return
region_ids = message.text.split(',') region_ids = message.text.split(',')
valid_region_ids = [region[0] for region in get_sorted_regions()] valid_region_ids = [region[0] for region in get_sorted_regions()]
with db_lock: with db_lock:
conn = sqlite3.connect(DB_PATH) conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor() cursor = conn.cursor()
for region_id in region_ids: for region_id in region_ids:
region_id = region_id.strip() region_id = region_id.strip()
if region_id not in valid_region_ids: if region_id not in valid_region_ids:
invalid_regions.append(region_id) invalid_regions.append(region_id)
continue continue
cursor.execute('INSERT OR IGNORE INTO subscriptions (chat_id, region_id, username, active) VALUES (?, ?, ?, TRUE)', cursor.execute('INSERT OR IGNORE INTO subscriptions (chat_id, region_id, username, active) VALUES (?, ?, ?, TRUE)',
(chat_id, region_id, username)) (chat_id, region_id, username))
if cursor.rowcount == 0: if cursor.rowcount == 0:
cursor.execute('UPDATE subscriptions SET active = TRUE WHERE chat_id = ? AND region_id = ?', (chat_id, region_id)) cursor.execute('UPDATE subscriptions SET active = TRUE WHERE chat_id = ? AND region_id = ?', (chat_id, region_id))
subbed_regions.append(region_id) subbed_regions.append(region_id)
conn.commit() conn.commit()
# # Получаем ID сообщения с клавиатурой "Отмена" и скрываем её
# state_data = user_state_manager.get_state(chat_id)
# cancel_message_id = state_data.get("cancel_message_id")
#
# if cancel_message_id:
# try:
# bot.edit_message_reply_markup(chat_id, cancel_message_id, reply_markup=None)
# except telebot.apihelper.ApiTelegramException as e:
# telebot.logger.error(f"Failed to edit message: {e}")
if len(invalid_regions) > 0: if len(invalid_regions) > 0:
bot.send_message(chat_id, f"Регион с ID {', '.join(invalid_regions)} не существует. Введите корректные номера или 'отмена'.") bot.send_message(chat_id, f"Регион с ID {', '.join(invalid_regions)} не существует. Введите корректные номера или 'отмена'.")
bot.send_message(chat_id, f"Подписка на регионы: {', '.join(subbed_regions)} оформлена.") bot.send_message(chat_id, f"Подписка на регионы: {', '.join(subbed_regions)} оформлена.")
log_user_event(chat_id, username, f"Subscribed to regions: {', '.join(subbed_regions)}") log_user_event(chat_id, username, f"Subscribed to regions: {', '.join(subbed_regions)}")
user_state_manager.set_state(chat_id, "SETTINGS_MENU") user_state_manager.set_state(chat_id, "SETTINGS_MENU")
show_settings_menu(chat_id) show_settings_menu(chat_id)
def handle_unsubscribe_button(message): def handle_unsubscribe_button(message):
chat_id = message.chat.id chat_id = message.chat.id
if not is_whitelisted(chat_id): if not is_whitelisted(chat_id):
@ -578,27 +526,22 @@ def handle_unsubscribe_button(message):
telebot.logger.info(f"Unauthorized access attempt by {chat_id}") telebot.logger.info(f"Unauthorized access attempt by {chat_id}")
user_state_manager.set_state(chat_id, "REGISTRATION") user_state_manager.set_state(chat_id, "REGISTRATION")
return show_main_menu(chat_id) return show_main_menu(chat_id)
username = message.from_user.username username = message.from_user.username
if username: if username:
username = f"@{username}" username = f"@{username}"
else: else:
username = "N/A" username = "N/A"
# Получаем список подписок пользователя # Получаем список подписок пользователя
user_regions = get_user_subscribed_regions(chat_id) user_regions = get_user_subscribed_regions(chat_id)
if not user_regions: if not user_regions:
bot.send_message(chat_id, "Вы не подписаны ни на один регион.") bot.send_message(chat_id, "Вы не подписаны ни на один регион.")
user_state_manager.set_state(chat_id, "SETTINGS_MENU") user_state_manager.set_state(chat_id, "SETTINGS_MENU")
return show_settings_menu(chat_id) return show_settings_menu(chat_id)
regions_list = format_regions_list(user_regions) regions_list = format_regions_list(user_regions)
markup = telebot.types.InlineKeyboardMarkup() markup = telebot.types.InlineKeyboardMarkup()
markup.add(telebot.types.InlineKeyboardButton(text="Отмена", markup.add(telebot.types.InlineKeyboardButton(text="Отмена", callback_data=f"cancel_action"))
callback_data=f"cancel_action"))
bot.send_message(chat_id, f"Отправьте номер или номера регионов, от которых хотите отписаться (через запятую):\n{regions_list}\n",reply_markup=markup) bot.send_message(chat_id, f"Отправьте номер или номера регионов, от которых хотите отписаться (через запятую):\n{regions_list}\n",reply_markup=markup)
# user_state_manager.set_state(chat_id, "WAITING_FOR_INPUT",
# extra_data={"cancel_message_id": sent_message.message_id})
bot.register_next_step_handler_by_chat_id(chat_id, process_unsubscription_button, chat_id, username) bot.register_next_step_handler_by_chat_id(chat_id, process_unsubscription_button, chat_id, username)
@ -606,51 +549,31 @@ def process_unsubscription_button(message, chat_id, username):
unsubbed_regions = [] unsubbed_regions = []
invalid_regions = [] invalid_regions = []
markup = telebot.types.InlineKeyboardMarkup() markup = telebot.types.InlineKeyboardMarkup()
markup.add(telebot.types.InlineKeyboardButton(text="Отмена", markup.add(telebot.types.InlineKeyboardButton(text="Отмена", callback_data=f"cancel_action"))
callback_data=f"cancel_action"))
if message.text.lower() == 'отмена': if message.text.lower() == 'отмена':
bot.send_message(chat_id, "Действие отменено.") bot.send_message(chat_id, "Действие отменено.")
user_state_manager.set_state(chat_id, "SETTINGS_MENU") user_state_manager.set_state(chat_id, "SETTINGS_MENU")
return show_settings_menu(chat_id) return show_settings_menu(chat_id)
# Проверка, что введённая строка содержит только цифры и запятые # Проверка, что введённая строка содержит только цифры и запятые
if not all(part.strip().isdigit() for part in message.text.split(',')): if not all(part.strip().isdigit() for part in message.text.split(',')):
bot.send_message(chat_id, "Некорректный формат. Введите номера регионов через запятую.", reply_markup=markup) bot.send_message(chat_id, "Некорректный формат. Введите номера регионов через запятую.", reply_markup=markup)
# user_state_manager.set_state(chat_id, "WAITING_FOR_INPUT",
# extra_data={"cancel_message_id": sent_message.message_id})
bot.register_next_step_handler_by_chat_id(chat_id, process_unsubscription_button, chat_id, username) bot.register_next_step_handler_by_chat_id(chat_id, process_unsubscription_button, chat_id, username)
return return
region_ids = message.text.split(',') region_ids = message.text.split(',')
valid_region_ids = [region[0] for region in get_user_subscribed_regions(chat_id)] valid_region_ids = [region[0] for region in get_user_subscribed_regions(chat_id)]
with db_lock: with db_lock:
conn = sqlite3.connect(DB_PATH) conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor() cursor = conn.cursor()
for region_id in region_ids: for region_id in region_ids:
region_id = region_id.strip() region_id = region_id.strip()
if region_id not in valid_region_ids: if region_id not in valid_region_ids:
invalid_regions.append(region_id) invalid_regions.append(region_id)
continue continue
# Удаление подписки # Удаление подписки
query = 'UPDATE subscriptions SET active = FALSE WHERE chat_id = ? AND region_id = ?' query = 'UPDATE subscriptions SET active = FALSE WHERE chat_id = ? AND region_id = ?'
cursor.execute(query, (chat_id, region_id)) cursor.execute(query, (chat_id, region_id))
unsubbed_regions.append(region_id) unsubbed_regions.append(region_id)
conn.commit() conn.commit()
# # Получаем ID сообщения с клавиатурой "Отмена" и скрываем её
# state_data = user_state_manager.get_state(chat_id)
# cancel_message_id = state_data.get_extra_data(chat_id)
# if cancel_message_id:
# try:
# bot.edit_message_reply_markup(chat_id, cancel_message_id, reply_markup=None)
# except telebot.apihelper.ApiTelegramException as e:
# telebot.logger.error(f"Failed to edit message: {e}")
if len(invalid_regions) > 0: if len(invalid_regions) > 0:
bot.send_message(chat_id, f"Регион с ID {', '.join(invalid_regions)} не найден в ваших подписках.") bot.send_message(chat_id, f"Регион с ID {', '.join(invalid_regions)} не найден в ваших подписках.")
bot.send_message(chat_id, f"Отписка от регионов: {', '.join(unsubbed_regions)} выполнена.") bot.send_message(chat_id, f"Отписка от регионов: {', '.join(unsubbed_regions)} выполнена.")
@ -670,6 +593,7 @@ def handle_cancel_action(call):
show_settings_menu(chat_id) show_settings_menu(chat_id)
return return
@bot.callback_query_handler(func=lambda call: call.data == "cancel_active_triggers") @bot.callback_query_handler(func=lambda call: call.data == "cancel_active_triggers")
def handle_cancel_active_triggers(call): def handle_cancel_active_triggers(call):
chat_id = call.message.chat.id chat_id = call.message.chat.id
@ -680,13 +604,9 @@ def handle_cancel_active_triggers(call):
user_state_manager.set_state(chat_id, "MAIN_MENU") user_state_manager.set_state(chat_id, "MAIN_MENU")
show_main_menu(chat_id) show_main_menu(chat_id)
return return
###################################################################################################################### ######################################################################################################################
## help_Region_Manager
##
######################################################################################################################
# Handle admin region management commands # Handle admin region management commands
######################################################################################################################
def handle_region_manager(chat_id: int, action: str): def handle_region_manager(chat_id: int, action: str):
if action == 'add': if action == 'add':
bot.send_message(chat_id, "Введите ID и название региона в формате:\n<region_id> <region_name>") bot.send_message(chat_id, "Введите ID и название региона в формате:\n<region_id> <region_name>")
@ -694,10 +614,8 @@ def handle_region_manager(chat_id: int, action: str):
elif action == 'remove': elif action == 'remove':
bot.send_message(chat_id, "Введите ID региона, который хотите сделать неактивным") bot.send_message(chat_id, "Введите ID региона, который хотите сделать неактивным")
bot.register_next_step_handler_by_chat_id(chat_id, process_remove_region) bot.register_next_step_handler_by_chat_id(chat_id, process_remove_region)
###################################################################################################################### ######################################################################################################################
## help_Region_Manager # Handle admin region management commands
##
###################################################################################################################### ######################################################################################################################
class RegionManager: class RegionManager:
def __init__(self, db_path): def __init__(self, db_path):
@ -812,6 +730,7 @@ def handle_region_action(call):
bot.send_message(chat_id, f"Регион {region_id} обновлен до {region_name} и активирован.") bot.send_message(chat_id, f"Регион {region_id} обновлен до {region_name} и активирован.")
region_manager.log_event(chat_id, username, region_manager.log_event(chat_id, username,
f"Admin replaced and reactivated region {region_id} - {region_name}") f"Admin replaced and reactivated region {region_id} - {region_name}")
telebot.logger.info(f"Admin {username} replaced and reactivated region {region_id} - {region_name}")
user_state_manager.set_state(chat_id, "SETTINGS_MENU") user_state_manager.set_state(chat_id, "SETTINGS_MENU")
@ -819,11 +738,12 @@ def handle_region_action(call):
region_manager.add_region(region_id, region_name) region_manager.add_region(region_id, region_name)
bot.send_message(chat_id, f"Регион {region_id} активирован.") bot.send_message(chat_id, f"Регион {region_id} активирован.")
region_manager.log_event(chat_id, username, f"Admin reactivated region {region_id} - {region_name}") region_manager.log_event(chat_id, username, f"Admin reactivated region {region_id} - {region_name}")
telebot.logger.info(f"Admin {username} activate {region_id} - {region_name}")
user_state_manager.set_state(chat_id, "SETTINGS_MENU") user_state_manager.set_state(chat_id, "SETTINGS_MENU")
elif action == "cancel_region": elif action == "cancel_region":
bot.send_message(chat_id, "Действие отменено.") bot.send_message(chat_id, "Действие отменено.")
telebot.logger.info(f"Admin {username} canceled region action.") telebot.logger.info(f"Admin {username} canceled region actions.")
user_state_manager.set_state(chat_id, "SETTINGS_MENU") user_state_manager.set_state(chat_id, "SETTINGS_MENU")
bot.edit_message_reply_markup(chat_id=chat_id, message_id=call.message.message_id, reply_markup=None) bot.edit_message_reply_markup(chat_id=chat_id, message_id=call.message.message_id, reply_markup=None)
bot.answer_callback_query(call.id) bot.answer_callback_query(call.id)
@ -840,6 +760,7 @@ def process_remove_region(message):
if success: if success:
bot.send_message(chat_id, f"Регион {region_id} теперь неактивен, и все активные подписки обновлены.") bot.send_message(chat_id, f"Регион {region_id} теперь неактивен, и все активные подписки обновлены.")
region_manager.log_event(chat_id, username, f"Admin {username} deactivated region {region_id}") region_manager.log_event(chat_id, username, f"Admin {username} deactivated region {region_id}")
telebot.logger.info(f"Admin {username} deactivated region {region_id}")
user_state_manager.set_state(chat_id, "SETTINGS_MENU") user_state_manager.set_state(chat_id, "SETTINGS_MENU")
show_settings_menu(chat_id) show_settings_menu(chat_id)
@ -860,27 +781,31 @@ def process_remove_region(message):
# Handle displaying active subscriptions for a user # Handle displaying active subscriptions for a user
def handle_my_subscriptions_button(message): def handle_my_subscriptions_button(message):
chat_id = message.chat.id chat_id = message.chat.id
username = f"@{message.from_user.username}" if message.from_user.username else "N/A"
if not is_whitelisted(chat_id): if not is_whitelisted(chat_id):
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.") bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
telebot.logger.info(f"Unauthorized access attempt by {chat_id}") telebot.logger.info(f"Unauthorized access attempt by {username} {chat_id}")
return return
user_regions = get_user_subscribed_regions(chat_id) user_regions = get_user_subscribed_regions(chat_id)
if not user_regions: if not user_regions:
bot.send_message(chat_id, "Вы не подписаны ни на один регион.") bot.send_message(chat_id, "Вы не подписаны ни на один регион.")
telebot.logger.debug(f"Запрашиваем {user_regions} for {username} {chat_id}")
else: else:
user_regions.sort(key=lambda x: int(x[0])) # Сортировка по числовому значению region_id user_regions.sort(key=lambda x: int(x[0])) # Сортировка по числовому значению region_id
regions_list = format_regions_list(user_regions) regions_list = format_regions_list(user_regions)
bot.send_message(chat_id, f"Ваши активные подписки:\n{regions_list}") bot.send_message(chat_id, f"Ваши активные подписки:\n{regions_list}")
telebot.logger.debug(f"Запрашиваем {user_regions} for {username} {chat_id}")
show_settings_menu(chat_id) show_settings_menu(chat_id)
# Handle displaying all active regions # Handle displaying all active regions
def handle_active_regions_button(message): def handle_active_regions_button(message):
chat_id = message.chat.id chat_id = message.chat.id
username = f"@{message.from_user.username}" if message.from_user.username else "N/A"
if not is_whitelisted(chat_id): if not is_whitelisted(chat_id):
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.") bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
telebot.logger.info(f"Unauthorized access attempt by {chat_id}") telebot.logger.info(f"Unauthorized access attempt by {username} {chat_id}")
return return
regions = get_sorted_regions() # Используем функцию для получения отсортированных регионов regions = get_sorted_regions() # Используем функцию для получения отсортированных регионов
@ -1010,18 +935,30 @@ def extract_region_number(host):
def handle_notification_mode_button(message): def handle_notification_mode_button(message):
chat_id = message.chat.id 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 is_whitelisted(chat_id): if not is_whitelisted(chat_id):
bot.send_message(chat_id, "Вы неавторизованы для использования этого бота") bot.send_message(chat_id, "Вы неавторизованы для использования этого бота")
telebot.logger.warning(f"Unauthorized access attempt by {username} ({chat_id})")
return return
# Логируем успешное авторизованное использование бота
telebot.logger.info(f"User {username} ({chat_id}) is authorized and is selecting a notification mode.")
# Отправляем клавиатуру выбора режима уведомлений
markup = types.InlineKeyboardMarkup() markup = types.InlineKeyboardMarkup()
markup.add(types.InlineKeyboardButton(text="Критические события", callback_data="notification_mode_disaster")) markup.add(types.InlineKeyboardButton(text="Критические события", callback_data="notification_mode_disaster"))
markup.add(types.InlineKeyboardButton(text="Все события", callback_data="notification_mode_all")) markup.add(types.InlineKeyboardButton(text="Все события", callback_data="notification_mode_all"))
bot.send_message(chat_id, "Выберите уровень событий мониторинга, уведомление о которых хотите получать:\n" bot.send_message(chat_id,
"Выберите уровень событий мониторинга, уведомление о которых хотите получать:\n"
'1. <b>Критические события</b> (приоритет "DISASTER") - события, являющиеся потенциальными авариями и требующие оперативного решения.\nВ Zabbix обязательно имеют тег "CALL" для оперативного привлечения инженеров к устранению.\n\n' '1. <b>Критические события</b> (приоритет "DISASTER") - события, являющиеся потенциальными авариями и требующие оперативного решения.\nВ Zabbix обязательно имеют тег "CALL" для оперативного привлечения инженеров к устранению.\n\n'
'2. <b>Все события (По умолчанию)</b> - критические события, а также события Zabbix высокого ("HIGH") приоритета, имеющие потенциально значительное влияние на сервис и требующее устранение в плановом порядке.', '2. <b>Все события (По умолчанию)</b> - критические события, а также события Zabbix высокого ("HIGH") приоритета, имеющие потенциально значительное влияние на сервис и требующее устранение в плановом порядке.',
reply_markup=markup,parse_mode="HTML") 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_")) @bot.callback_query_handler(func=lambda call: call.data.startswith("notification_mode_"))
@ -1030,13 +967,17 @@ def handle_notification_mode_selection(call):
message_id = call.message.message_id message_id = call.message.message_id
mode = call.data.split("_")[2] mode = call.data.split("_")[2]
telebot.logger.debug(f"User ({chat_id}) selected notification mode: {mode}.")
# Убираем клавиатуру # Убираем клавиатуру
bot.edit_message_reply_markup(chat_id=chat_id, message_id=message_id, reply_markup=None) 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 disaster_only = True if mode == "disaster" else False
try: try:
telebot.logger.debug(f"Attempting to update notification mode in the database for user {chat_id}.")
with db_lock: with db_lock:
conn = sqlite3.connect(DB_PATH) conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor() cursor = conn.cursor()
@ -1046,15 +987,25 @@ def handle_notification_mode_selection(call):
mode_text = "Критические события" if disaster_only else "Все события" mode_text = "Критические события" if disaster_only else "Все события"
bot.send_message(chat_id, f"Режим уведомлений успешно изменён на: {mode_text}") 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") 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) show_settings_menu(chat_id)
telebot.logger.debug(f"Displayed settings menu to {chat_id}.")
except Exception as e: except Exception as e:
telebot.logger.error(f"Error updating notification mode for {chat_id}: {e}") telebot.logger.error(f"Error updating notification mode for {chat_id}: {e}")
bot.send_message(chat_id, "Произошла ошибка при изменении режима уведомлений.") bot.send_message(chat_id, "Произошла ошибка при изменении режима уведомлений.")
finally: finally:
conn.close() conn.close()
telebot.logger.debug(f"Database connection closed for user {chat_id}.")
# Логируем успешный ответ callback-запроса
bot.answer_callback_query(call.id) bot.answer_callback_query(call.id)
telebot.logger.debug(f"Callback query for user ({chat_id}) answered.")
@ -1349,7 +1300,7 @@ def webhook():
if region_id is None: if region_id is None:
app.logger.error(f"Не удалось извлечь номер региона из host: {data.get('host')}") app.logger.error(f"Не удалось извлечь номер региона из host: {data.get('host')}")
return jsonify({"status": "error", "message": "Invalid host format"}), 400 return jsonify({"status": "error", "message": "Invalid host format"}), 400
app.logger.info(f"Извлечён номер региона: {region_id}") app.logger.debug(f"Извлечён номер региона: {region_id}")
# Запрос подписчиков для отправки уведомления в зависимости от уровня опасности # Запрос подписчиков для отправки уведомления в зависимости от уровня опасности
if data['severity'] == '5': # Авария if data['severity'] == '5': # Авария
@ -1361,7 +1312,7 @@ def webhook():
cursor.execute(query, (region_id,)) cursor.execute(query, (region_id,))
results = cursor.fetchall() results = cursor.fetchall()
app.logger.info(f"Найдено подписчиков: {len(results)} для региона {region_id}") app.logger.debug(f"Найдено подписчиков: {len(results)} для региона {region_id}")
# Проверка статуса региона (активен или нет) # Проверка статуса региона (активен или нет)
query = 'SELECT active FROM regions WHERE region_id = ?' query = 'SELECT active FROM regions WHERE region_id = ?'
@ -1369,7 +1320,7 @@ def webhook():
region_row = cursor.fetchone() region_row = cursor.fetchone()
if region_row and region_row[0]: # Если регион активен if region_row and region_row[0]: # Если регион активен
app.logger.info(f"Регион {region_id} активен. Начинаем рассылку сообщений.") app.logger.debug(f"Регион {region_id} активен. Начинаем рассылку сообщений.")
message = format_message(data) message = format_message(data)
undelivered = False undelivered = False
@ -1379,7 +1330,7 @@ def webhook():
app.logger.info(f"Отправка сообщения пользователю @{username} (chat_id={chat_id}) [{formatted_message}]") app.logger.info(f"Отправка сообщения пользователю @{username} (chat_id={chat_id}) [{formatted_message}]")
try: try:
send_to_queue({'chat_id': chat_id, 'username': username, 'message': message}) send_to_queue({'chat_id': chat_id, 'username': username, 'message': message})
app.logger.info(f"Сообщение поставлено в очередь для {chat_id} (@{username})") app.logger.debug(f"Сообщение поставлено в очередь для {chat_id} (@{username})")
except Exception as e: except Exception as e:
app.logger.error(f"Ошибка при отправке сообщения для {chat_id} (@{username}): {e}") app.logger.error(f"Ошибка при отправке сообщения для {chat_id} (@{username}): {e}")
undelivered = True undelivered = True
@ -1392,7 +1343,7 @@ def webhook():
# Коммитим изменения в базе данных # Коммитим изменения в базе данных
conn.commit() conn.commit()
app.logger.info("Изменения в базе данных успешно сохранены.") app.logger.debug("Изменения в базе данных успешно сохранены.")
conn.close() conn.close()
# Возвращаем успешный ответ # Возвращаем успешный ответ
@ -1415,19 +1366,20 @@ def webhook():
def format_message(data): def format_message(data):
try: try:
priority_map = { priority_map = {
'4': 'Высокая', 'High': '⚠️',
'5': 'Авария' 'Disaster': '⛔️'
} }
priority = priority_map.get(data['severity'], 'Неизвестно') priority = priority_map.get(data['severity'])
if data['status'].upper() == "PROBLEM": if data['status'].upper() == "PROBLEM":
message = ( message = (
f"⚠️ {data['host']} ({data['ip']})\n" f"{priority} {data['host']} ({data['ip']})\n"
f"<b>Описание</b>: {data['msg']}\n" f"<b>Описание</b>: {data['msg']}\n"
f"Критичность: {data['severity']}" f"<b>Критичность</b>: {data['severity']}\n"
f"<b>Время возникновения проблемы</b>: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(data['date_reception'])))} Мск\n"
) )
if 'link' in data: if 'link' in data:
message += f'\nURL: <a href="{data['link']}">Ссылка на график</a>' message += f'<b>URL</b>: <a href="{data['link']}">Ссылка на график</a>'
return message return message
else: else:
message = ( message = (
@ -1435,7 +1387,7 @@ def format_message(data):
f"<b>Описание</b>: {data['msg']}\n" f"<b>Описание</b>: {data['msg']}\n"
f"<b>Критичность</b>: {data['severity']}\n" f"<b>Критичность</b>: {data['severity']}\n"
f"<b>Проблема устранена!</b>\n" f"<b>Проблема устранена!</b>\n"
f"<b>Время устранения проблемы</b>: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(data['date_reception'])))}\n" f"<b>Время устранения проблемы</b>: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(data['date_reception'])))} Мск\n"
) )
if 'link' in data: if 'link' in data:
message += f'<b>URL</b>: <a href="{data['link']}">Ссылка на график</a>' message += f'<b>URL</b>: <a href="{data['link']}">Ссылка на график</a>'