import html import time import traceback from datetime import datetime from aiogram import F, Router from aiogram.filters import MagicData from aiogram.fsm.context import FSMContext from aiogram.types import CallbackQuery from helper_bot.handlers.admin.utils import format_user_info from helper_bot.handlers.voice.constants import CALLBACK_DELETE, CALLBACK_SAVE from helper_bot.handlers.voice.services import AudioFileService from helper_bot.keyboards.keyboards import ( create_keyboard_for_ban_reason, create_keyboard_with_pagination, get_reply_keyboard_admin, ) from helper_bot.utils.base_dependency_factory import get_global_instance from helper_bot.utils.helper_func import get_banned_users_buttons, get_banned_users_list # Local imports - metrics from helper_bot.utils.metrics import ( db_query_time, track_errors, track_file_operations, track_time, ) from logs.custom_logger import logger from .constants import ( CALLBACK_BAN, CALLBACK_DECLINE, CALLBACK_PAGE, CALLBACK_PUBLISH, CALLBACK_RETURN, CALLBACK_UNLOCK, ERROR_BOT_BLOCKED, MESSAGE_DECLINED, MESSAGE_ERROR, MESSAGE_PUBLISHED, MESSAGE_USER_BANNED, MESSAGE_USER_UNLOCKED, ) from .dependency_factory import get_ban_service, get_post_publish_service from .exceptions import ( BanError, PostNotFoundError, PublishError, UserBlockedBotError, UserNotFoundError, ) 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, bot_db: MagicData("bot_db"), **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: # Получаем username пользователя username = await ban_service.ban_user(str(user_id_int), "") if not username: raise UserNotFoundError(f"Пользователь с ID {user_id_int} не найден в базе") # Получаем full_name пользователя из базы данных full_name = await bot_db.get_full_name_by_id(user_id_int) if not full_name: full_name = "Неизвестно" # Сохраняем данные в формате, совместимом с admin_handlers await state.update_data( target_user_id=user_id_int, target_username=username, target_full_name=full_name, ) # Используем единый формат отображения информации о пользователе user_info = format_user_info(user_id_int, username, full_name) markup = create_keyboard_for_ban_reason() await call.message.answer( text=f"{user_info}\n\nВыбери причину бана из списка или напиши ее в чат", reply_markup=markup, ) await state.set_state("AWAIT_BAN_DETAILS") logger.info( f"process_ban_user: Состояние изменено на AWAIT_BAN_DETAILS для пользователя {user_id_int}" ) 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 = await 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 = await 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)