import html import traceback import time from datetime import datetime from aiogram import Router, F from aiogram.types import CallbackQuery from aiogram.fsm.context import FSMContext from aiogram.filters import MagicData from helper_bot.handlers.voice.constants import CALLBACK_SAVE, CALLBACK_DELETE from helper_bot.handlers.voice.services import AudioFileService from helper_bot.keyboards.keyboards import create_keyboard_with_pagination, get_reply_keyboard_admin, \ create_keyboard_for_ban_reason from helper_bot.utils.helper_func import get_banned_users_list, get_banned_users_buttons from helper_bot.utils.base_dependency_factory import get_global_instance from .dependency_factory import get_post_publish_service, get_ban_service from .exceptions import UserBlockedBotError, PostNotFoundError, UserNotFoundError, PublishError, BanError from .constants import ( CALLBACK_PUBLISH, CALLBACK_DECLINE, CALLBACK_BAN, CALLBACK_UNLOCK, CALLBACK_RETURN, CALLBACK_PAGE, MESSAGE_PUBLISHED, MESSAGE_DECLINED, MESSAGE_USER_BANNED, MESSAGE_USER_UNLOCKED, MESSAGE_ERROR, ERROR_BOT_BLOCKED ) from logs.custom_logger import logger # Local imports - metrics from helper_bot.utils.metrics import ( track_time, track_errors, db_query_time, track_file_operations ) callback_router = Router() @callback_router.callback_query(F.data == CALLBACK_PUBLISH) @track_time("post_for_group", "callback_handlers") @track_errors("callback_handlers", "post_for_group") async def post_for_group( call: CallbackQuery, settings: MagicData("settings") ): publish_service = get_post_publish_service() # TODO: переделать на MagicData logger.info( f'Получен callback-запрос с действием: {call.data} от пользователя {call.from_user.full_name} (ID сообщения: {call.message.message_id})') try: await publish_service.publish_post(call) await call.answer(text=MESSAGE_PUBLISHED, cache_time=3) except UserBlockedBotError: await call.answer(text=MESSAGE_ERROR, show_alert=True, cache_time=3) except (PostNotFoundError, PublishError) as e: logger.error(f'Ошибка при публикации поста: {str(e)}') await call.answer(text=MESSAGE_ERROR, show_alert=True, cache_time=3) except Exception as e: if str(e) == ERROR_BOT_BLOCKED: await call.answer(text=MESSAGE_ERROR, show_alert=True, cache_time=3) else: important_logs = settings['Telegram']['important_logs'] await call.bot.send_message( chat_id=important_logs, text=f"Произошла ошибка: {str(e)}\n\nTraceback:\n{traceback.format_exc()}" ) logger.error(f'Неожиданная ошибка при публикации поста: {str(e)}') await call.answer(text=MESSAGE_ERROR, show_alert=True, cache_time=3) @callback_router.callback_query(F.data == CALLBACK_DECLINE) @track_time("decline_post_for_group", "callback_handlers") @track_errors("callback_handlers", "decline_post_for_group") async def decline_post_for_group( call: CallbackQuery, settings: MagicData("settings") ): publish_service = get_post_publish_service() # TODO: переделать на MagicData logger.info( f'Получен callback-запрос с данными: {call.data} от пользователя {call.from_user.full_name} (ID: {call.from_user.id})') try: await publish_service.decline_post(call) await call.answer(text=MESSAGE_DECLINED, cache_time=3) except UserBlockedBotError: await call.answer(text=MESSAGE_ERROR, show_alert=True, cache_time=3) except (PostNotFoundError, PublishError) as e: logger.error(f'Ошибка при отклонении поста: {str(e)}') await call.answer(text=MESSAGE_ERROR, show_alert=True, cache_time=3) except Exception as e: if str(e) == ERROR_BOT_BLOCKED: await call.answer(text=MESSAGE_ERROR, show_alert=True, cache_time=3) else: important_logs = settings['Telegram']['important_logs'] await call.bot.send_message( chat_id=important_logs, text=f"Произошла ошибка: {str(e)}\n\nTraceback:\n{traceback.format_exc()}" ) logger.error(f'Неожиданная ошибка при отклонении поста: {str(e)}') await call.answer(text=MESSAGE_ERROR, show_alert=True, cache_time=3) @callback_router.callback_query(F.data == CALLBACK_BAN) @track_time("ban_user_from_post", "callback_handlers") @track_errors("callback_handlers", "ban_user_from_post") async def ban_user_from_post(call: CallbackQuery, **kwargs): ban_service = get_ban_service() # TODO: переделать на MagicData try: await ban_service.ban_user_from_post(call) await call.answer(text=MESSAGE_USER_BANNED, cache_time=3) except UserBlockedBotError: await call.answer(text=MESSAGE_ERROR, show_alert=True, cache_time=3) except (UserNotFoundError, BanError) as e: logger.error(f'Ошибка при блокировке пользователя: {str(e)}') await call.answer(text=MESSAGE_ERROR, show_alert=True, cache_time=3) except Exception as e: if str(e) == ERROR_BOT_BLOCKED: await call.answer(text=MESSAGE_ERROR, show_alert=True, cache_time=3) else: logger.error(f'Неожиданная ошибка при блокировке пользователя: {str(e)}') await call.answer(text=MESSAGE_ERROR, show_alert=True, cache_time=3) @callback_router.callback_query(F.data.contains(CALLBACK_BAN)) @track_time("process_ban_user", "callback_handlers") @track_errors("callback_handlers", "process_ban_user") async def process_ban_user(call: CallbackQuery, state: FSMContext, **kwargs): ban_service = get_ban_service() # TODO: переделать на MagicData user_id = call.data[4:] logger.info(f"Вызов функции process_ban_user. Данные callback: {call.data} пользователь: {user_id}") # Проверяем, что user_id является валидным числом try: user_id_int = int(user_id) except ValueError: logger.error(f"Некорректный user_id в callback: {user_id}") await call.answer(text="Ошибка: некорректный ID пользователя", show_alert=True, cache_time=3) return try: user_name = await ban_service.ban_user(str(user_id_int), "") await state.update_data(user_id=user_id_int, user_name=user_name, message_for_user=None, date_to_unban=None) markup = create_keyboard_for_ban_reason() user_name_escaped = html.escape(str(user_name)) full_name_escaped = html.escape(str(call.message.from_user.full_name)) await call.message.answer( text=f"Выбран пользователь:\nid: {user_id_int}\nusername: {user_name_escaped}\nИмя:{full_name_escaped}\nВыбери причину бана из списка или напиши ее в чат", reply_markup=markup ) await state.set_state('BAN_2') except UserNotFoundError: markup = get_reply_keyboard_admin() await call.message.answer(text='Пользователь с таким ID не найден в базе', reply_markup=markup) await state.set_state('ADMIN') @callback_router.callback_query(F.data.contains(CALLBACK_UNLOCK)) @track_time("process_unlock_user", "callback_handlers") @track_errors("callback_handlers", "process_unlock_user") async def process_unlock_user(call: CallbackQuery, **kwargs): ban_service = get_ban_service() # TODO: переделать на MagicData user_id = call.data[7:] # Проверяем, что user_id является валидным числом try: user_id_int = int(user_id) except ValueError: logger.error(f"Некорректный user_id в callback: {user_id}") await call.answer(text="Ошибка: некорректный ID пользователя", show_alert=True, cache_time=3) return try: username = await ban_service.unlock_user(str(user_id_int)) await call.answer(f'{MESSAGE_USER_UNLOCKED} {username}', show_alert=True) except UserNotFoundError: await call.answer(text='Пользователь не найден в базе', show_alert=True, cache_time=3) except Exception as e: logger.error(f'Ошибка при разблокировке пользователя: {str(e)}') await call.answer(text=MESSAGE_ERROR, show_alert=True, cache_time=3) @callback_router.callback_query(F.data == CALLBACK_RETURN) @track_time("return_to_main_menu", "callback_handlers") @track_errors("callback_handlers", "return_to_main_menu") async def return_to_main_menu(call: CallbackQuery, **kwargs): await call.message.delete() logger.info(f"Запуск админ панели для пользователя: {call.message.from_user.id}") markup = get_reply_keyboard_admin() await call.message.answer("Добро пожаловать в админку. Выбери что хочешь:", reply_markup=markup) @callback_router.callback_query(F.data.contains(CALLBACK_PAGE)) @track_time("change_page", "callback_handlers") @track_errors("callback_handlers", "change_page") async def change_page( call: CallbackQuery, bot_db: MagicData("bot_db"), **kwargs ): try: page_number = int(call.data[5:]) except ValueError: logger.error(f"Некорректный номер страницы в callback: {call.data}") await call.answer(text="Ошибка: некорректный номер страницы", show_alert=True, cache_time=3) return logger.info(f"Переход на страницу {page_number}") if call.message.text == 'Список пользователей которые последними обращались к боту': list_users = await bot_db.get_last_users(30) keyboard = create_keyboard_with_pagination(page_number, len(list_users), list_users, 'ban') await call.bot.edit_message_reply_markup( chat_id=call.message.chat.id, message_id=call.message.message_id, reply_markup=keyboard ) else: message_user = get_banned_users_list(int(page_number) * 7 - 7, bot_db) await call.bot.edit_message_text( chat_id=call.message.chat.id, message_id=call.message.message_id, text=message_user ) buttons = get_banned_users_buttons(bot_db) keyboard = create_keyboard_with_pagination(page_number, len(buttons), buttons, 'unlock') await call.bot.edit_message_reply_markup( chat_id=call.message.chat.id, message_id=call.message.message_id, reply_markup=keyboard ) @callback_router.callback_query(F.data == CALLBACK_SAVE) @track_time("save_voice_message", "callback_handlers") @track_errors("callback_handlers", "save_voice_message") @track_file_operations("voice") @db_query_time("save_voice_message", "audio_moderate", "mixed") async def save_voice_message( call: CallbackQuery, bot_db: MagicData("bot_db"), settings: MagicData("settings"), **kwargs ): try: logger.info(f"Начинаем сохранение голосового сообщения. Message ID: {call.message.message_id}") # Создаем сервис для работы с аудио файлами audio_service = AudioFileService(bot_db) # Получаем ID пользователя из базы user_id = await bot_db.get_user_id_by_message_id_for_voice_bot(call.message.message_id) logger.info(f"Получен user_id: {user_id}") # Генерируем имя файла file_name = await audio_service.generate_file_name(user_id) logger.info(f"Сгенерировано имя файла: {file_name}") # Собираем инфо о сообщении time_UTC = int(time.time()) date_added = datetime.fromtimestamp(time_UTC) # Получаем file_id из voice сообщения file_id = call.message.voice.file_id if call.message.voice else "" logger.info(f"Получен file_id: {file_id}") # ВАЖНО: Сначала скачиваем и сохраняем файл на диск logger.info("Начинаем скачивание и сохранение файла на диск...") await audio_service.download_and_save_audio(call.bot, call.message, file_name) logger.info("Файл успешно скачан и сохранен на диск") # Только после успешного сохранения файла - сохраняем в базу данных logger.info("Начинаем сохранение информации в базу данных...") await audio_service.save_audio_file(file_name, user_id, date_added, file_id) logger.info("Информация успешно сохранена в базу данных") # Удаляем сообщение из предложки logger.info("Удаляем сообщение из предложки...") await call.bot.delete_message( chat_id=settings['Telegram']['group_for_posts'], message_id=call.message.message_id ) logger.info("Сообщение удалено из предложки") # Удаляем запись из таблицы audio_moderate logger.info("Удаляем запись из таблицы audio_moderate...") await bot_db.delete_audio_moderate_record(call.message.message_id) logger.info("Запись удалена из таблицы audio_moderate") await call.answer(text='Сохранено!', cache_time=3) logger.info(f"Голосовое сообщение успешно сохранено: {file_name}") except Exception as e: logger.error(f"Ошибка при сохранении голосового сообщения: {e}") logger.error(f"Traceback: {traceback.format_exc()}") # Дополнительная информация для диагностики try: if 'call' in locals() and call.message: logger.error(f"Message ID: {call.message.message_id}") logger.error(f"User ID: {user_id if 'user_id' in locals() else 'не определен'}") logger.error(f"File name: {file_name if 'file_name' in locals() else 'не определен'}") except: pass await call.answer(text='Ошибка при сохранении!', cache_time=3) @callback_router.callback_query(F.data == CALLBACK_DELETE) @track_time("delete_voice_message", "callback_handlers") @track_errors("callback_handlers", "delete_voice_message") @db_query_time("delete_voice_message", "audio_moderate", "delete") async def delete_voice_message( call: CallbackQuery, bot_db: MagicData("bot_db"), settings: MagicData("settings"), **kwargs ): try: # Удаляем сообщение из предложки await call.bot.delete_message( chat_id=settings['Telegram']['group_for_posts'], message_id=call.message.message_id ) # Удаляем запись из таблицы audio_moderate await bot_db.delete_audio_moderate_record(call.message.message_id) await call.answer(text='Удалено!', cache_time=3) except Exception as e: logger.error(f"Ошибка при удалении голосового сообщения: {e}") await call.answer(text='Ошибка при удалении!', cache_time=3)