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)