"""
Обработчики для работы с анонимными вопросами
"""
from datetime import datetime
from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from aiogram.filters import StateFilter
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from config import config
from models.user import User
from models.question import Question, QuestionStatus
from services.infrastructure.database import DatabaseService
from services.business.question_service import QuestionService
from services.business.user_service import UserService
from services.business.message_service import MessageService
from services.business.pagination_service import PaginationService
from services.utils import is_valid_answer_text, send_answer_to_author
from services.utils import is_valid_question_text, format_question_info, escape_html
from services.infrastructure.logger import get_logger
from services.infrastructure.metrics import get_metrics_service, track_question_processing, track_answer_processing
from services.infrastructure.logging_decorators import log_function_call, log_business_event, log_fsm_transition, log_utility
from services.infrastructure.logging_utils import log_user_action, log_business_operation, log_fsm_event
from dependencies import inject_question_services, inject_answer_services
from keyboards.inline import get_answer_keyboard, get_question_view_keyboard, get_user_questions_keyboard
from keyboards.reply import get_main_keyboard_for_user, get_cancel_keyboard
from keyboards.inline import get_admin_keyboard
logger = get_logger(__name__)
router = Router()
@log_function_call(log_params=True, log_result=False)
async def _format_questions_list(
questions_with_authors: list,
page: int,
per_page: int,
user_id: int,
question_service: QuestionService
) -> str:
"""Форматирование списка вопросов для отображения (оптимизированная версия)"""
try:
# Проверяем, является ли пользователь суперпользователем
is_superuser = False
if user_id:
try:
from dependencies import get_auth
auth_service = get_auth()
if auth_service:
is_superuser = await auth_service.is_superuser(user_id)
except Exception as e:
logger.warning(f"Ошибка при проверке суперпользователя: {e}")
is_superuser = False
# Формируем текст сообщения
questions_text = ""
for i, (question, author_user) in enumerate(questions_with_authors, page * per_page + 1):
# Проверяем, что question не None
if not question:
logger.warning(f"Найден None question в списке на позиции {i}")
continue
# Дополнительная проверка на наличие атрибута status
if not hasattr(question, 'status') or question.status is None:
logger.warning(f"Вопрос {question.id if hasattr(question, 'id') else 'unknown'} не имеет статуса")
continue
status_emoji = {
'pending': '⏳',
'answered': '✅',
'rejected': '❌',
'deleted': '🗑️'
}
emoji = status_emoji.get(question.status.value, '❓')
preview = question_service.get_question_preview(question, 40)
# Используем user_question_number для отображения, если он есть
display_number = question.user_question_number if question.user_question_number is not None else i
questions_text += f"{i}. {emoji} #{display_number}"
# Для суперпользователей добавляем информацию об авторе вопроса
if is_superuser and author_user:
try:
from services.utils import format_user_display_name
author_info = format_user_display_name(author_user)
questions_text += f" Вопрос от {author_info}"
except Exception as e:
logger.error(f"Ошибка при форматировании информации об авторе вопроса: {e}")
elif is_superuser and not author_user:
questions_text += " (автор неизвестен)"
questions_text += "\n"
questions_text += f" {preview}\n"
questions_text += f" 📅 {question.created_at.strftime('%d.%m.%Y %H:%M')}\n\n"
questions_text += "💡 Нажмите на номер вопроса для просмотра деталей."
return questions_text
except Exception as e:
logger.error(f"Ошибка при форматировании списка вопросов: {e}")
return "❌ Ошибка при форматировании списка вопросов."
@router.message(F.text == "❌ Отмена")
@log_fsm_transition(to_state="cancelled")
@log_function_call(log_params=True)
async def cancel_action(message: Message, state: FSMContext):
"""Обработчик кнопки 'Отмена'"""
await state.clear()
# Используем новую систему авторизации
is_admin = False
try:
from dependencies import get_auth
auth_service = get_auth()
if auth_service:
is_admin = auth_service.is_admin(message.from_user.id)
except Exception as e:
logger.warning(f"Ошибка при проверке админа: {e}")
is_admin = False
keyboard = get_admin_keyboard() if is_admin else get_main_keyboard_for_user(message.from_user.id)
await message.answer(
"❌ Действие отменено.",
reply_markup=keyboard
)
@log_function_call(log_params=True, log_result=True)
async def _send_questions_page(
target,
questions_text: str,
keyboard,
message_service: MessageService
) -> bool:
"""Отправка страницы с вопросами"""
try:
if isinstance(target, CallbackQuery):
# Это CallbackQuery
logger.info("Отправляем CallbackQuery через edit_text")
try:
await message_service.edit_message(target, questions_text, keyboard)
except Exception as edit_error:
# Если не удалось отредактировать, отправляем новое сообщение
logger.warning(f"Не удалось отредактировать сообщение: {edit_error}")
await message_service.send_message(target, questions_text, keyboard)
else:
# Это Message - используем answer() напрямую
logger.info("Отправляем Message через answer()")
await message_service.send_message(target, questions_text, keyboard)
return True
except Exception as e:
logger.error(f"Ошибка при отправке страницы вопросов: {e}")
return False
@log_business_event("show_questions_page", log_params=True, log_result=False)
async def show_questions_page(
message_or_callback,
questions: list,
page: int = 0,
per_page: int = 9,
user_id: int = None,
question_service: QuestionService = None,
user_service: UserService = None,
message_service: MessageService = None,
pagination_service: PaginationService = None
):
"""
Показать страницу с вопросами
Args:
message_or_callback: Message или CallbackQuery объект
questions: Список всех вопросов
page: Номер страницы (начиная с 0)
per_page: Количество вопросов на странице
user_id: ID пользователя для проверки прав суперпользователя
question_service: Сервис для работы с вопросами
user_service: Сервис для работы с пользователями
message_service: Сервис для отправки сообщений
pagination_service: Сервис для пагинации
"""
try:
# Логируем тип объекта для отладки
object_type = type(message_or_callback).__name__
questions_count = len(questions) if questions is not None else 0
logger.info(f"show_questions_page вызвана с объектом типа: {object_type}, page: {page}, questions: {questions_count}")
# Используем сервисы если они переданы, иначе создаем временные
if not question_service:
from dependencies import get_question_service
question_service = get_question_service()
if not user_service:
from dependencies import get_user_service
user_service = get_user_service()
if not message_service:
from dependencies import get_message_service
message_service = get_message_service()
if not pagination_service:
from dependencies import get_pagination_service
pagination_service = get_pagination_service()
# Получаем общее количество вопросов для пагинации
total_questions_count = await user_service.database.get_user_questions_count(user_id)
if total_questions_count == 0:
empty_text = (
"📭 У вас пока нет вопросов.\n\n"
"🔗 Поделитесь своей ссылкой, чтобы получать анонимные вопросы!"
)
if message_service:
await message_service.send_message(message_or_callback, empty_text)
else:
if isinstance(message_or_callback, CallbackQuery):
await message_or_callback.message.edit_text(empty_text, reply_markup=None)
else:
await message_or_callback.answer(empty_text)
return
# Рассчитываем пагинацию на основе БД
total_questions, current_page, total_pages, offset = await pagination_service.calculate_pagination_from_db(
total_questions_count, page, per_page
)
# Получаем вопросы с авторами для текущей страницы (оптимизированный запрос)
questions_with_authors = await user_service.database.get_user_questions_with_authors(
user_id, limit=per_page, offset=offset
)
# Формируем информацию о пагинации
start_idx = current_page * per_page
end_idx = min(start_idx + per_page, total_questions)
pagination_info = pagination_service.format_pagination_info(
current_page, total_pages, start_idx, end_idx, total_questions
)
# Формируем текст сообщения
questions_text = f"📋 Ваши вопросы\n\n"
questions_text += pagination_info
# Добавляем список вопросов
questions_list_text = await _format_questions_list(
questions_with_authors, current_page, per_page, user_id, question_service
)
questions_text += questions_list_text
# Создаем клавиатуру с пагинацией, используя реальные вопросы из базы данных
keyboard = get_user_questions_keyboard(questions_with_authors, current_page, per_page, total_questions)
# Отправляем страницу
await _send_questions_page(message_or_callback, questions_text, keyboard, message_service)
except Exception as e:
logger.error(f"Ошибка при отображении страницы вопросов: {e},")
try:
if message_service:
await message_service.send_error_message(
message_or_callback,
"❌ Ошибка при загрузке вопросов"
)
else:
if isinstance(message_or_callback, CallbackQuery):
await message_or_callback.answer("❌ Ошибка при загрузке вопросов", show_alert=True)
else:
await message_or_callback.answer("❌ Ошибка при загрузке вопросов")
except Exception as answer_error:
logger.error(f"Не удалось отправить сообщение об ошибке: {answer_error}")
class QuestionStates(StatesGroup):
"""Состояния для работы с вопросами"""
waiting_for_question = State()
waiting_for_answer = State()
editing_answer = State()
@router.message(F.text == "📋 Мои вопросы")
@log_business_event("my_questions_button", log_params=True)
async def my_questions_button(
message: Message,
state: FSMContext,
question_service: QuestionService,
message_service: MessageService
):
"""Обработчик кнопки 'Мои вопросы'"""
try:
await state.clear()
# Получаем все вопросы пользователя
all_questions = await question_service.get_user_questions(message.from_user.id, limit=1000)
if not all_questions:
await message_service.send_message(
message,
"📭 У вас пока нет вопросов.\n\n"
"🔗 Поделитесь своей ссылкой, чтобы получать анонимные вопросы!",
get_main_keyboard_for_user(message.from_user.id)
)
else:
# Показываем первую страницу
await show_questions_page(
message,
all_questions,
page=0,
user_id=message.from_user.id,
question_service=question_service,
message_service=message_service
)
except Exception as e:
logger.error(f"Ошибка при получении вопросов: {e}")
await message_service.send_error_message(
message,
"❌ Произошла ошибка при получении вопросов. Попробуйте позже."
)
@log_utility
def _has_attachments(message: Message) -> bool:
"""Проверяет, содержит ли сообщение вложения"""
return (
message.photo is not None or
message.video is not None or
message.audio is not None or
message.document is not None or
message.sticker is not None or
message.animation is not None or
message.voice is not None or
message.video_note is not None or
message.contact is not None or
message.location is not None or
message.media_group_id is not None or
message.caption is not None
)
@router.message(StateFilter(QuestionStates.waiting_for_question))
@inject_question_services
@log_fsm_transition(to_state="processing_question")
@log_business_event("process_anonymous_question", log_params=True)
async def process_anonymous_question(
message: Message,
state: FSMContext,
question_service: QuestionService,
user_service: UserService,
message_service: MessageService,
validator,
**kwargs
):
"""Обработка анонимного вопроса"""
# Убираем dispatcher из kwargs, если он есть
kwargs.pop('dispatcher', None)
try:
# Получаем данные из состояния
data = await state.get_data()
target_user_id = data.get('target_user_id')
if not target_user_id:
await message_service.send_message(
message,
"❌ Ошибка: не найден получатель вопроса.",
get_main_keyboard_for_user(message.from_user.id)
)
await state.clear()
return
# Проверяем, содержит ли сообщение вложения
if _has_attachments(message):
logger.warning(f"⚠️ Пользователь {message.from_user.id} отправил сообщение с вложениями")
await message_service.send_message(
message,
"❌ Вложения не допускаются\n\n"
"📝 Вопрос должен состоять только из текста.\n"
"🚫 Фотографии, видео, документы, аудио и другие вложения не принимаются.\n\n"
"Попробуйте отправить вопрос еще раз:",
get_cancel_keyboard()
)
return
# Проверяем, что сообщение содержит текст
if not message.text:
logger.warning(f"⚠️ Пользователь {message.from_user.id} отправил пустое сообщение")
await message_service.send_message(
message,
"❌ Вопрос не может быть пустым\n\n"
"Попробуйте отправить вопрос еще раз:",
get_cancel_keyboard()
)
return
# Валидируем текст вопроса
validation_result = validator.validate_question_text(
message.text,
config.MAX_QUESTION_LENGTH
)
if not validation_result:
logger.warning(f"⚠️ Невалидный вопрос от пользователя {message.from_user.id}: {validation_result.error_message}")
await message_service.send_message(
message,
f"❌ {validation_result.error_message}\n\n"
"Попробуйте отправить вопрос еще раз:",
get_cancel_keyboard()
)
return
# Используем санитизированный текст
sanitized_question_text = validation_result.sanitized_value
# Проверяем, не заблокирован ли отправитель получателем
if await user_service.is_user_blocked(target_user_id, message.from_user.id):
await message_service.send_message(
message,
"🚫 Вы заблокированы этим пользователем\n\n"
"К сожалению, вы не можете отправлять вопросы этому пользователю.",
get_main_keyboard_for_user(message.from_user.id)
)
await state.clear()
return
# Создаем вопрос с санитизированным текстом
question = await question_service.create_question(
message.from_user.id,
target_user_id,
sanitized_question_text
)
# Отправляем уведомление получателю
await _send_question_notification(
message.bot,
question,
target_user_id,
user_service,
message_service
)
# Отправляем подтверждение отправителю
confirmation_text = (
"✅ Вопрос отправлен!\n\n"
"📝 Ваш вопрос был передан получателю анонимно.\n"
"🔔 Он получит уведомление и сможет ответить на него.\n\n"
"💡 Спасибо за использование нашего бота!"
)
await message_service.send_message(
message,
confirmation_text,
get_main_keyboard_for_user(message.from_user.id)
)
await state.clear()
except Exception as e:
logger.error(f"Ошибка при обработке вопроса: {e}")
await message_service.send_error_message(
message,
"❌ Произошла ошибка при отправке вопроса. Попробуйте позже."
)
await state.clear()
@log_function_call(log_params=True, log_result=True)
async def _send_question_notification(
bot,
question: Question,
target_user_id: int,
user_service: UserService,
message_service: MessageService
) -> bool:
"""Отправка уведомления о новом вопросе"""
try:
# Получаем информацию о получателе
target_user = await user_service.get_user_by_telegram_id(target_user_id)
if not target_user:
return False
# Проверяем, является ли получатель суперпользователем
is_target_superuser = False
try:
from dependencies import get_auth
auth_service = get_auth()
if auth_service:
is_target_superuser = await auth_service.is_superuser(target_user_id)
except Exception as e:
logger.warning(f"Ошибка при проверке суперпользователя для уведомления: {e}")
is_target_superuser = False
notification_text = "❓ Новый анонимный вопрос!\n\n"
# Для суперпользователей добавляем информацию об авторе
if is_target_superuser and question.from_user_id:
try:
from services.utils import format_user_display_name
author_user = await user_service.get_user_by_telegram_id(question.from_user_id)
if author_user:
author_info = format_user_display_name(author_user)
notification_text = f"❓ Новый вопрос от {author_info}!\n\n"
except Exception as e:
logger.error(f"Ошибка при получении информации об авторе для уведомления: {e}")
notification_text += f"📝 Вопрос:\n{escape_html(question.message_text)}\n\n"
notification_text += f"📅 {question.created_at.strftime('%d.%m.%Y %H:%M')}"
# Отправляем уведомление
await message_service.send_bot_message(
bot,
target_user_id,
notification_text,
get_answer_keyboard(question.id, question.from_user_id)
)
return True
except Exception as e:
logger.error(f"Не удалось отправить уведомление пользователю {target_user_id}: {e}")
return False
@router.callback_query(F.data.startswith("answer_"))
@log_fsm_transition(to_state="waiting_for_answer")
@log_business_event("answer_question_callback", log_params=True)
async def answer_question_callback(callback: CallbackQuery, state: FSMContext, validator = None):
"""Обработчик кнопки 'Ответить' на вопрос"""
try:
# Валидируем callback data
if validator:
validation_result = validator.validate_callback_data(callback.data)
if not validation_result:
logger.warning(f"⚠️ Невалидный callback data от пользователя {callback.from_user.id}: {callback.data}")
await callback.answer("❌ Неверные данные", show_alert=True)
return
# Извлекаем и валидируем question_id
question_id_str = callback.data.split("_")[1]
try:
question_id = int(question_id_str)
if question_id <= 0:
logger.warning(f"⚠️ Невалидный question_id в callback: {question_id}")
await callback.answer("❌ Неверный ID вопроса", show_alert=True)
return
except ValueError:
logger.warning(f"⚠️ Неверный формат question_id в callback: {question_id_str}")
await callback.answer("❌ Неверный формат ID", show_alert=True)
return
from dependencies import get_database
db = get_database()
# Получаем вопрос
question = await db.get_question(question_id)
if not question:
await callback.answer("❌ Вопрос не найден", show_alert=True)
return
if question.to_user_id != callback.from_user.id:
await callback.answer("❌ У вас нет прав на этот вопрос", show_alert=True)
return
if question.status != QuestionStatus.PENDING:
await callback.answer("❌ На этот вопрос уже отвечено", show_alert=True)
return
# Устанавливаем состояние ожидания ответа
await state.set_state(QuestionStates.waiting_for_answer)
await state.update_data(question_id=question_id)
# Отправляем сообщение с просьбой ввести ответ
await callback.message.edit_text(
f"💬 Ответ на вопрос #{question_id}\n\n"
f"📝 Вопрос:\n{escape_html(question.message_text)}\n\n"
f"✍️ Введите ваш ответ:",
reply_markup=None,
parse_mode="HTML"
)
await callback.answer()
except Exception as e:
logger.error(f"Ошибка при обработке ответа на вопрос: {e}")
await callback.answer("❌ Произошла ошибка", show_alert=True)
@router.message(StateFilter(QuestionStates.waiting_for_answer))
@inject_answer_services
@log_fsm_transition(to_state="processing_answer")
@log_business_event("process_answer", log_params=True)
async def process_answer(
message: Message,
state: FSMContext,
validator,
message_service: MessageService,
**kwargs
):
"""Обработка ответа на вопрос"""
try:
# Получаем данные из состояния
data = await state.get_data()
question_id = data.get('question_id')
if not question_id:
await message.answer(
"❌ Ошибка: не найден вопрос для ответа.",
reply_markup=get_main_keyboard_for_user(message.from_user.id)
)
await state.clear()
return
# Валидируем текст ответа
validation_result = validator.validate_answer_text(
message.text,
config.MAX_ANSWER_LENGTH
)
if not validation_result:
logger.warning(f"⚠️ Невалидный ответ от пользователя {message.from_user.id}: {validation_result.error_message}")
await message.answer(
f"❌ {validation_result.error_message}\n\n"
"Попробуйте отправить ответ еще раз:",
reply_markup=get_cancel_keyboard()
)
return
# Используем санитизированный текст
sanitized_answer_text = validation_result.sanitized_value
# Сохраняем ответ
from dependencies import get_database
db = get_database()
question = await db.get_question(question_id)
if question:
question.mark_as_answered(sanitized_answer_text)
await db.update_question(question)
# Обновляем статистику пользователя (если нужно)
# user = await db.get_user(message.from_user.id)
# if user:
# user.increment_questions_answered()
# await db.update_user(user)
# Отправляем ответ автору вопроса
logger.info(f"Попытка отправить ответ автору вопроса {question.id}, from_user_id: {question.from_user_id}")
await send_answer_to_author(message.bot, question, question.answer_text)
# Отправляем подтверждение
await message_service.send_message(
message,
"✅ Ответ сохранен!\n\n"
"💬 Ваш ответ был сохранен и будет показан при просмотре вопроса.\n\n"
"📋 Используйте 'Мои вопросы' для просмотра всех вопросов и ответов.",
get_main_keyboard_for_user(message.from_user.id)
)
await state.clear()
except Exception as e:
logger.error(f"Ошибка при обработке ответа: {e}")
await message_service.send_message(
message,
"❌ Произошла ошибка при сохранении ответа. Попробуйте позже.",
get_main_keyboard_for_user(message.from_user.id)
)
await state.clear()
@router.callback_query(F.data.startswith("reject_"))
@log_business_event("reject_question_callback", log_params=True)
async def reject_question_callback(callback: CallbackQuery, validator = None):
"""Обработчик кнопки 'Отклонить' вопрос"""
try:
# Валидируем callback data
if validator:
validation_result = validator.validate_callback_data(callback.data)
if not validation_result:
logger.warning(f"⚠️ Невалидный callback data от пользователя {callback.from_user.id}: {callback.data}")
await callback.answer("❌ Неверные данные", show_alert=True)
return
# Извлекаем и валидируем question_id
question_id_str = callback.data.split("_")[1]
try:
question_id = int(question_id_str)
if question_id <= 0:
logger.warning(f"⚠️ Невалидный question_id в callback: {question_id}")
await callback.answer("❌ Неверный ID вопроса", show_alert=True)
return
except ValueError:
logger.warning(f"⚠️ Неверный формат question_id в callback: {question_id_str}")
await callback.answer("❌ Неверный формат ID", show_alert=True)
return
from dependencies import get_database
db = get_database()
# Получаем вопрос
question = await db.get_question(question_id)
if not question:
await callback.answer("❌ Вопрос не найден", show_alert=True)
return
if question.to_user_id != callback.from_user.id:
await callback.answer("❌ У вас нет прав на этот вопрос", show_alert=True)
return
if question.status != QuestionStatus.PENDING:
await callback.answer("❌ На этот вопрос уже отвечено", show_alert=True)
return
# Отклоняем вопрос
question.mark_as_rejected()
await db.update_question(question)
# Обновляем сообщение
await callback.message.edit_text(
f"❌ Вопрос #{question.user_question_number if question.user_question_number is not None else question_id} отклонен\n\n"
f"📝 Вопрос:\n{escape_html(question.message_text)}\n\n"
f"📅 {question.created_at.strftime('%d.%m.%Y %H:%M')}\n"
f"❌ Отклонен: {question.answered_at.strftime('%d.%m.%Y %H:%M')}",
reply_markup=None,
parse_mode="HTML"
)
await callback.answer("❌ Вопрос отклонен")
except Exception as e:
logger.error(f"Ошибка при отклонении вопроса: {e}")
await callback.answer("❌ Произошла ошибка", show_alert=True)
@router.callback_query(F.data.startswith("delete_"))
@log_business_event("delete_question_callback", log_params=True)
async def delete_question_callback(callback: CallbackQuery, validator = None):
"""Обработчик кнопки 'Удалить' вопрос"""
try:
# Валидируем callback data
if validator:
validation_result = validator.validate_callback_data(callback.data)
if not validation_result:
logger.warning(f"⚠️ Невалидный callback data от пользователя {callback.from_user.id}: {callback.data}")
await callback.answer("❌ Неверные данные", show_alert=True)
return
# Извлекаем и валидируем question_id
question_id_str = callback.data.split("_")[1]
try:
question_id = int(question_id_str)
if question_id <= 0:
logger.warning(f"⚠️ Невалидный question_id в callback: {question_id}")
await callback.answer("❌ Неверный ID вопроса", show_alert=True)
return
except ValueError:
logger.warning(f"⚠️ Неверный формат question_id в callback: {question_id_str}")
await callback.answer("❌ Неверный формат ID", show_alert=True)
return
from dependencies import get_database
db = get_database()
# Получаем вопрос
question = await db.get_question(question_id)
if not question:
await callback.answer("❌ Вопрос не найден", show_alert=True)
return
if question.to_user_id != callback.from_user.id:
await callback.answer("❌ У вас нет прав на этот вопрос", show_alert=True)
return
# Удаляем вопрос
question.mark_as_deleted()
await db.update_question(question)
# Обновляем сообщение
await callback.message.edit_text(
f"🗑️ Вопрос #{question.user_question_number if question.user_question_number is not None else question_id} удален\n\n"
f"📅 Удален: {question.answered_at.strftime('%d.%m.%Y %H:%M')}",
reply_markup=None,
parse_mode="HTML"
)
await callback.answer("🗑️ Вопрос удален")
except Exception as e:
logger.error(f"Ошибка при удалении вопроса: {e}")
await callback.answer("❌ Произошла ошибка", show_alert=True)
@router.callback_query(F.data.startswith("questions_page_"))
@log_business_event("questions_pagination_handler", log_params=True)
async def questions_pagination_handler(callback: CallbackQuery):
"""Обработчик пагинации списка вопросов (оптимизированная версия)"""
try:
# Извлекаем номер страницы из callback_data
page = int(callback.data.split("_")[-1])
# Получаем сервисы
from dependencies import get_user_service
user_service = get_user_service()
# Получаем общее количество вопросов для пагинации (оптимизированный запрос)
total_questions_count = await user_service.database.get_user_questions_count(callback.from_user.id)
if total_questions_count == 0:
await callback.answer("❌ Вопросы не найдены", show_alert=True)
return
# Показываем нужную страницу (show_questions_page сам загрузит данные из БД)
logger.info(f"Показываем страницу {page} для пользователя {callback.from_user.id}, всего вопросов: {total_questions_count}")
await show_questions_page(callback, None, page, user_id=callback.from_user.id)
await callback.answer()
except ValueError as e:
logger.error(f"Ошибка парсинга номера страницы: {e}")
await callback.answer("❌ Неверный номер страницы", show_alert=True)
except Exception as e:
logger.error(f"Ошибка при пагинации вопросов: {e}")
await callback.answer("❌ Ошибка при загрузке страницы", show_alert=True)
@router.callback_query(F.data == "back_to_questions")
@log_business_event("back_to_questions_handler", log_params=True)
async def back_to_questions_handler(callback: CallbackQuery):
"""Обработчик кнопки 'Назад к списку вопросов'"""
try:
# Получаем базу данных
from dependencies import get_database
db = get_database()
# Получаем все вопросы пользователя
all_questions = await db.get_user_questions(callback.from_user.id, limit=1000)
if not all_questions:
await callback.answer("❌ Вопросы не найдены", show_alert=True)
return
# Показываем первую страницу списка
await show_questions_page(callback, all_questions, page=0, user_id=callback.from_user.id)
await callback.answer()
except Exception as e:
logger.error(f"Ошибка при возврате к списку вопросов: {e}")
await callback.answer("❌ Ошибка при загрузке списка", show_alert=True)
@router.callback_query(F.data.startswith("view_question_"))
@log_business_event("view_question_handler", log_params=True)
async def view_question_handler(callback: CallbackQuery, validator = None):
"""Обработчик просмотра конкретного вопроса"""
try:
# Валидируем callback data
if validator:
validation_result = validator.validate_callback_data(callback.data)
if not validation_result:
logger.warning(f"⚠️ Невалидный callback data от пользователя {callback.from_user.id}: {callback.data}")
await callback.answer("❌ Неверные данные", show_alert=True)
return
# Извлекаем и валидируем question_id
question_id_str = callback.data.split("_")[-1]
try:
question_id = int(question_id_str)
if question_id <= 0:
logger.warning(f"⚠️ Невалидный question_id в callback: {question_id}")
await callback.answer("❌ Неверный ID вопроса", show_alert=True)
return
except ValueError:
logger.warning(f"⚠️ Неверный формат question_id в callback: {question_id_str}")
await callback.answer("❌ Неверный формат ID", show_alert=True)
return
# Получаем базу данных
from dependencies import get_database
db = get_database()
# Получаем вопрос
question = await db.get_question(question_id)
if not question:
await callback.answer("❌ Вопрос не найден", show_alert=True)
return
# Форматируем информацию о вопросе
question_text = format_question_info(question, show_answer=True)
# Создаем клавиатуру для просмотра вопроса
keyboard = get_question_view_keyboard(question)
await callback.message.edit_text(
question_text,
reply_markup=keyboard,
parse_mode="HTML"
)
await callback.answer()
except Exception as e:
logger.error(f"Ошибка при просмотре вопроса: {e}")
await callback.answer("❌ Ошибка при загрузке вопроса", show_alert=True)
@router.callback_query(F.data.startswith("block_"))
@log_business_event("block_user_callback", log_params=True)
async def block_user_callback(callback: CallbackQuery, validator = None):
"""Обработчик блокировки пользователя"""
try:
# Валидируем callback data
if validator:
validation_result = validator.validate_callback_data(callback.data)
if not validation_result:
logger.warning(f"⚠️ Невалидный callback data от пользователя {callback.from_user.id}: {callback.data}")
await callback.answer("❌ Неверные данные", show_alert=True)
return
# Парсим данные: block_{user_id}_{question_id}
data_parts = callback.data.split("_")
if len(data_parts) != 3:
await callback.answer("❌ Ошибка в данных", show_alert=True)
return
# Валидируем user_id и question_id
try:
blocked_user_id = int(data_parts[1])
question_id = int(data_parts[2])
if validator:
# Валидируем Telegram ID
user_id_validation = validator.validate_telegram_id(blocked_user_id)
if not user_id_validation:
logger.warning(f"⚠️ Невалидный blocked_user_id в callback: {blocked_user_id}")
await callback.answer("❌ Неверный ID пользователя", show_alert=True)
return
# Валидируем question_id
if question_id <= 0:
logger.warning(f"⚠️ Невалидный question_id в callback: {question_id}")
await callback.answer("❌ Неверный ID вопроса", show_alert=True)
return
except ValueError:
logger.warning(f"⚠️ Неверный формат ID в callback: {callback.data}")
await callback.answer("❌ Неверный формат ID", show_alert=True)
return
blocker_id = callback.from_user.id
# Проверяем, не блокирует ли пользователь сам себя
if blocked_user_id == blocker_id:
await callback.answer("❌ Нельзя заблокировать самого себя", show_alert=True)
return
# Получаем базу данных
from dependencies import get_database
db = get_database()
# Проверяем, не заблокирован ли уже пользователь
if await db.is_user_blocked(blocker_id, blocked_user_id):
await callback.answer("❌ Пользователь уже заблокирован", show_alert=True)
return
# Блокируем пользователя
await db.block_user(blocker_id, blocked_user_id)
# Получаем информацию о заблокированном пользователе
blocked_user = await db.get_user(blocked_user_id)
blocked_name = blocked_user.display_name if blocked_user else f"ID: {blocked_user_id}"
# Обновляем сообщение с вопросом
question_text = f"🚫 Пользователь заблокирован\n\n"
question_text += f"👤 Заблокирован: {blocked_name}\n"
question_text += f"📅 Дата блокировки: {datetime.now().strftime('%d.%m.%Y %H:%M')}\n\n"
question_text += "✅ Пользователь больше не сможет отправлять вам вопросы."
# Создаем клавиатуру с кнопкой разблокировки
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.types import InlineKeyboardButton
builder = InlineKeyboardBuilder()
builder.add(
InlineKeyboardButton(
text="🔓 Разблокировать",
callback_data=f"unblock_{blocked_user_id}_{question_id}"
)
)
builder.add(
InlineKeyboardButton(
text="⬅️ К списку вопросов",
callback_data="back_to_questions"
)
)
builder.adjust(1)
await callback.message.edit_text(
question_text,
reply_markup=builder.as_markup(),
parse_mode="HTML"
)
await callback.answer("✅ Пользователь заблокирован", show_alert=True)
except ValueError:
await callback.answer("❌ Неверный формат данных", show_alert=True)
except Exception as e:
logger.error(f"Ошибка при блокировке пользователя: {e}")
await callback.answer("❌ Произошла ошибка при блокировке", show_alert=True)
@router.callback_query(F.data.startswith("unblock_") & ~F.data.startswith("unblock_from_list_"))
@log_business_event("unblock_user_callback", log_params=True)
async def unblock_user_callback(callback: CallbackQuery):
"""Обработчик разблокировки пользователя"""
try:
# Логируем входящие данные для отладки
logger.info(f"🔓 Попытка разблокировки пользователя. Callback data: '{callback.data}'")
# Парсим данные: unblock_{user_id}_{question_id}
data_parts = callback.data.split("_")
logger.info(f"🔍 Разобранные части данных: {data_parts}, количество частей: {len(data_parts)}")
if len(data_parts) != 3:
logger.error(f"❌ Неверное количество частей в данных: {len(data_parts)}, ожидалось 3. Данные: {callback.data}")
await callback.answer("❌ Ошибка в данных", show_alert=True)
return
try:
unblocked_user_id = int(data_parts[1])
question_id = int(data_parts[2])
unblocker_id = callback.from_user.id
logger.info(f"🔍 Парсинг успешен: unblocked_user_id={unblocked_user_id}, question_id={question_id}, unblocker_id={unblocker_id}")
except ValueError as e:
logger.error(f"❌ Ошибка парсинга ID: {e}. Данные: {data_parts}")
await callback.answer("❌ Ошибка в данных пользователя", show_alert=True)
return
# Получаем базу данных
from dependencies import get_database
db = get_database()
# Проверяем, заблокирован ли пользователь
is_blocked = await db.is_user_blocked(unblocker_id, unblocked_user_id)
logger.info(f"🔍 Проверка блокировки: is_blocked={is_blocked} для unblocker_id={unblocker_id}, unblocked_user_id={unblocked_user_id}")
if not is_blocked:
logger.warning(f"⚠️ Попытка разблокировать незаблокированного пользователя: unblocker_id={unblocker_id}, unblocked_user_id={unblocked_user_id}")
await callback.answer("❌ Пользователь не заблокирован", show_alert=True)
return
# Разблокируем пользователя
logger.info(f"🔓 Начинаем разблокировку: unblocker_id={unblocker_id}, unblocked_user_id={unblocked_user_id}")
success = await db.unblock_user(unblocker_id, unblocked_user_id)
logger.info(f"🔓 Результат разблокировки: success={success}")
if not success:
logger.error(f"❌ Не удалось разблокировать пользователя: unblocker_id={unblocker_id}, unblocked_user_id={unblocked_user_id}")
await callback.answer("❌ Не удалось разблокировать пользователя", show_alert=True)
return
# Получаем информацию о разблокированном пользователе
unblocked_user = await db.get_user(unblocked_user_id)
unblocked_name = unblocked_user.display_name if unblocked_user else f"ID: {unblocked_user_id}"
# Обновляем сообщение
question_text = f"🔓 Пользователь разблокирован\n\n"
question_text += f"👤 Разблокирован: {unblocked_name}\n"
question_text += f"📅 Дата разблокировки: {datetime.now().strftime('%d.%m.%Y %H:%M')}\n\n"
question_text += "✅ Пользователь снова может отправлять вам вопросы."
# Создаем клавиатуру с кнопкой блокировки
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.types import InlineKeyboardButton
builder = InlineKeyboardBuilder()
builder.add(
InlineKeyboardButton(
text="🚫 Заблокировать",
callback_data=f"block_{unblocked_user_id}_{question_id}"
)
)
builder.add(
InlineKeyboardButton(
text="⬅️ К списку вопросов",
callback_data="back_to_questions"
)
)
builder.adjust(1)
await callback.message.edit_text(
question_text,
reply_markup=builder.as_markup(),
parse_mode="HTML"
)
logger.info(f"✅ Пользователь успешно разблокирован: unblocked_user_id={unblocked_user_id}")
await callback.answer("✅ Пользователь разблокирован", show_alert=True)
except ValueError as e:
logger.error(f"❌ Ошибка ValueError при разблокировке: {e}")
await callback.answer("❌ Неверный формат данных", show_alert=True)
except Exception as e:
logger.error(f"❌ Неожиданная ошибка при разблокировке пользователя: {e}")
await callback.answer("❌ Произошла ошибка при разблокировке", show_alert=True)
@router.message(F.text == "🚫 Заблокированные")
@log_business_event("blocked_users_button", log_params=True)
async def blocked_users_button(message: Message):
"""Обработчик кнопки 'Заблокированные'"""
try:
from dependencies import get_database
db = get_database()
# Получаем список заблокированных пользователей
blocked_user_ids = await db.user_blocks.get_blocked_users(message.from_user.id)
if not blocked_user_ids:
await message.answer(
"📝 Заблокированные пользователи\n\n"
"У вас нет заблокированных пользователей.",
parse_mode="HTML"
)
return
# Получаем информацию о заблокированных пользователях
blocked_users = []
for user_id in blocked_user_ids:
user = await db.get_user(user_id)
if user:
blocked_users.append(user)
if not blocked_users:
await message.answer(
"📝 Заблокированные пользователи\n\n"
"Заблокированные пользователи не найдены в системе.",
parse_mode="HTML"
)
return
# Формируем сообщение со списком заблокированных
text = "🚫 Заблокированные пользователи\n\n"
for i, user in enumerate(blocked_users, 1):
text += f"{i}. {user.display_name}\n"
if user.username:
text += f" @{user.username}\n"
text += f" ID: {user.telegram_id}\n\n"
text += f"📊 Всего заблокировано: {len(blocked_users)}"
# Создаем клавиатуру с кнопками разблокировки
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.types import InlineKeyboardButton
builder = InlineKeyboardBuilder()
# Добавляем кнопки для каждого заблокированного пользователя
for user in blocked_users[:10]: # Ограничиваем до 10 кнопок
builder.add(
InlineKeyboardButton(
text=f"🔓 {user.display_name}",
callback_data=f"unblock_from_list_{user.telegram_id}"
)
)
# Добавляем кнопку "Разблокировать всех"
if len(blocked_users) > 1:
builder.add(
InlineKeyboardButton(
text="🔓 Разблокировать всех",
callback_data="unblock_all"
)
)
builder.adjust(1) # По одной кнопке в ряд
await message.answer(
text,
reply_markup=builder.as_markup(),
parse_mode="HTML"
)
except Exception as e:
logger.error(f"Ошибка при получении списка заблокированных: {e}")
await message.answer(
"❌ Произошла ошибка при получении списка заблокированных пользователей.",
reply_markup=get_main_keyboard_for_user(message.from_user.id)
)
@router.callback_query(F.data.startswith("unblock_from_list_"))
@log_business_event("unblock_from_list_callback", log_params=True)
async def unblock_from_list_callback(callback: CallbackQuery):
"""Обработчик разблокировки пользователя из списка"""
try:
# Парсим данные: unblock_from_list_{user_id}
user_id = int(callback.data.split("_")[-1])
unblocker_id = callback.from_user.id
# Получаем базу данных
from dependencies import get_database
db = get_database()
# Проверяем, заблокирован ли пользователь
if not await db.is_user_blocked(unblocker_id, user_id):
await callback.answer("❌ Пользователь не заблокирован", show_alert=True)
return
# Разблокируем пользователя
success = await db.unblock_user(unblocker_id, user_id)
if not success:
await callback.answer("❌ Не удалось разблокировать пользователя", show_alert=True)
return
# Получаем информацию о разблокированном пользователе
unblocked_user = await db.get_user(user_id)
unblocked_name = unblocked_user.display_name if unblocked_user else f"ID: {user_id}"
await callback.answer(f"✅ {unblocked_name} разблокирован", show_alert=True)
# Обновляем сообщение со списком
await blocked_users_button(callback.message)
except ValueError:
await callback.answer("❌ Неверный формат данных", show_alert=True)
except Exception as e:
logger.error(f"Ошибка при разблокировке из списка: {e}")
await callback.answer("❌ Произошла ошибка при разблокировке", show_alert=True)
@router.callback_query(F.data == "unblock_all")
@log_business_event("unblock_all_callback", log_params=True)
async def unblock_all_callback(callback: CallbackQuery):
"""Обработчик разблокировки всех пользователей"""
try:
unblocker_id = callback.from_user.id
# Получаем базу данных
from dependencies import get_database
db = get_database()
# Получаем список заблокированных пользователей
blocked_user_ids = await db.user_blocks.get_blocked_users(unblocker_id)
if not blocked_user_ids:
await callback.answer("❌ Нет заблокированных пользователей", show_alert=True)
return
# Разблокируем всех пользователей
unblocked_count = 0
for user_id in blocked_user_ids:
success = await db.unblock_user(unblocker_id, user_id)
if success:
unblocked_count += 1
await callback.answer(f"✅ Разблокировано {unblocked_count} пользователей", show_alert=True)
# Обновляем сообщение со списком
await blocked_users_button(callback.message)
except Exception as e:
logger.error(f"Ошибка при разблокировке всех: {e}")
await callback.answer("❌ Произошла ошибка при разблокировке", show_alert=True)