""" Обработчики команды /report для пользователей """ from datetime import datetime from aiogram import Router, F from aiogram.filters import Command from aiogram.types import Message, CallbackQuery, User from aiogram.utils.keyboard import InlineKeyboardBuilder from aiogram.exceptions import TelegramBadRequest from bot.filters.admin import IsAdmin from configs import settings, COMMANDS from database import get_manager from middleware.loggers import logger __all__ = ("router",) router: Router = Router(name="report_router") # ================= НАСТРОЙКИ ================= # ID чата для отправки репортов (можно вынести в configs) # Если None, репорты отправляются всем владельцам в ЛС REPORT_CHAT_ID = getattr(settings, 'REPORT_CHAT_ID', None) # ================= ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ================= def format_user(user: User) -> str: """ Форматирует информацию о пользователе. Args: user: Объект User Returns: Отформатированная строка с именем и username """ if not user: return "Unknown User" # Формируем имя name_parts = [] if user.first_name: name_parts.append(user.first_name) if user.last_name: name_parts.append(user.last_name) full_name = " ".join(name_parts) if name_parts else "No Name" # Добавляем username если есть if user.username: return f"{full_name} (@{user.username})" else: return full_name def format_datetime(dt: datetime) -> str: """Форматирует datetime""" return dt.strftime("%d.%m.%Y %H:%M:%S") def truncate_text(text: str, max_length: int = 200) -> str: """Обрезает текст до указанной длины""" if len(text) <= max_length: return text return text[:max_length] + "..." def get_report_keyboard( chat_id: int, message_id: int, reported_user_id: int, report_id: str ) -> InlineKeyboardBuilder: """ Создает клавиатуру для репорта. Args: chat_id: ID чата, где было сообщение message_id: ID сообщения reported_user_id: ID пользователя, на которого пожаловались report_id: Уникальный ID репорта """ ikb = InlineKeyboardBuilder() # Кнопки действий ikb.button( text="🚫 Забанить", callback_data=f"report:ban:{chat_id}:{reported_user_id}:{report_id}" ) ikb.button( text="🗑 Удалить", callback_data=f"report:delete:{chat_id}:{message_id}:{report_id}" ) ikb.button( text="✅ Закрыть", callback_data=f"report:close:{report_id}" ) ikb.adjust(2, 1) return ikb def generate_report_id() -> str: """Генерирует уникальный ID репорта""" return f"{int(datetime.now().timestamp() * 1000)}" # ================= КОМАНДА РЕПОРТА ================= @router.message(Command(*COMMANDS.get("report", ["report"]), prefix=settings.PREFIX, ignore_case=True)) async def report_cmd(message: Message) -> None: """ Отправляет жалобу на сообщение администраторам. Доступно всем пользователям. Использование: /report — в ответ на сообщение /report <причина> — в ответ на сообщение с указанием причины Пример: /report спам /report оскорбления """ # Проверяем, что команда в ответ на сообщение if not message.reply_to_message: await message.answer( "❌ Используйте команду в ответ на сообщение\n\n" "Как использовать:\n" "1. Ответьте на сообщение нарушителя\n" "2. Напишите /report или /report причина\n\n" "Пример: /report спам", parse_mode="HTML" ) return reported_message = message.reply_to_message reported_user = reported_message.from_user reporter = message.from_user # Проверка на None if not reported_user or not reporter: await message.answer("❌ Ошибка получения данных пользователя", parse_mode="HTML") return # Нельзя пожаловаться на самого себя if reported_user.id == reporter.id: await message.answer( "⚠️ Нельзя пожаловаться на самого себя", parse_mode="HTML" ) return # Нельзя пожаловаться на бота if reported_user.is_bot: await message.answer( "⚠️ Нельзя пожаловаться на бота", parse_mode="HTML" ) return # Нельзя пожаловаться на администратора manager = get_manager() is_admin = await manager.is_admin(reported_user.id) or reported_user.id in settings.OWNER_ID if is_admin: await message.answer( "⚠️ Нельзя пожаловаться на администратора", parse_mode="HTML" ) return # Извлекаем причину (опционально) parts = message.text.split(maxsplit=1) reason = parts[1] if len(parts) > 1 else "Не указана" # Генерируем ID репорта report_id = generate_report_id() # === ФОРМИРУЕМ СООБЩЕНИЕ РЕПОРТА === report_text = "🚨 НОВЫЙ РЕПОРТ\n\n" # Информация о жалобщике report_text += f"👤 От: {format_user(reporter)} ({reporter.id})\n" # Информация о нарушителе report_text += f"⚠️ На: {format_user(reported_user)} ({reported_user.id})\n\n" # Информация о чате chat_title = message.chat.title if message.chat.title else "Личные сообщения" report_text += f"💬 Чат: {chat_title}\n" report_text += f"🆔 Chat ID: {message.chat.id}\n\n" # Причина report_text += f"📝 Причина: {reason}\n\n" # Текст сообщения report_text += f"📄 Текст сообщения:\n" if reported_message.text: truncated_text = truncate_text(reported_message.text, max_length=300) report_text += f"{truncated_text}\n\n" elif reported_message.caption: truncated_caption = truncate_text(reported_message.caption, max_length=300) report_text += f"{truncated_caption}\n\n" else: content_type = reported_message.content_type report_text += f"[{content_type}]\n\n" # Время report_text += f"🕐 Время: {format_datetime(datetime.now())}\n" report_text += f"🔗 Message ID: {reported_message.message_id}\n\n" report_text += f"💡 ID репорта: {report_id}" # Клавиатура keyboard = get_report_keyboard( chat_id=message.chat.id, message_id=reported_message.message_id, reported_user_id=reported_user.id, report_id=report_id ) # === ОТПРАВКА РЕПОРТА === try: # Если указан админ-чат, отправляем туда if REPORT_CHAT_ID: await message.bot.send_message( chat_id=REPORT_CHAT_ID, text=report_text, parse_mode="HTML", reply_markup=keyboard.as_markup() ) else: # Отправляем всем владельцам sent_count = 0 for owner_id in settings.OWNER_ID: try: await message.bot.send_message( chat_id=owner_id, text=report_text, parse_mode="HTML", reply_markup=keyboard.as_markup() ) sent_count += 1 except Exception as e: logger.error(f"Ошибка отправки репорта владельцу {owner_id}: {e}", log_type="REPORT") if sent_count == 0: raise Exception("Не удалось отправить репорт ни одному владельцу") # Подтверждение пользователю await message.answer( "✅ Жалоба отправлена администраторам\n\n" "Спасибо за бдительность! Администраторы рассмотрят вашу жалобу.", parse_mode="HTML" ) # Логирование logger.info( f"Репорт #{report_id}: {reporter.id} → {reported_user.id} в чате {message.chat.id}", log_type="REPORT" ) except Exception as e: logger.error(f"Ошибка отправки репорта: {e}", log_type="REPORT") await message.answer( "❌ Ошибка отправки жалобы\n\nПопробуйте позже или обратитесь к администратору напрямую.", parse_mode="HTML" ) # ================= ОБРАБОТЧИКИ КНОПОК ================= @router.callback_query(F.data.startswith("report:ban:"), IsAdmin()) async def report_ban_callback(callback: CallbackQuery) -> None: """Обрабатывает нажатие кнопки 'Забанить'""" try: # Парсим данные: report:ban:chat_id:user_id:report_id parts = callback.data.split(":") chat_id = int(parts[2]) user_id = int(parts[3]) report_id = parts[4] # Баним пользователя try: await callback.bot.ban_chat_member( chat_id=chat_id, user_id=user_id ) admin_name = format_user(callback.from_user) # Обновляем сообщение updated_text = callback.message.text + f"\n\n✅ Пользователь забанен ({admin_name})" # Убираем кнопки await callback.message.edit_text( text=updated_text, parse_mode="HTML" ) await callback.answer("✅ Пользователь забанен", show_alert=True) logger.info( f"Репорт #{report_id}: пользователь {user_id} забанен админом {callback.from_user.id}", log_type="REPORT" ) except TelegramBadRequest as e: await callback.answer(f"❌ Ошибка бана: {str(e)}", show_alert=True) except Exception as e: logger.error(f"Ошибка обработки бана из репорта: {e}", log_type="REPORT") await callback.answer("❌ Ошибка выполнения", show_alert=True) @router.callback_query(F.data.startswith("report:delete:"), IsAdmin()) async def report_delete_callback(callback: CallbackQuery) -> None: """Обрабатывает нажатие кнопки 'Удалить'""" try: # Парсим данные: report:delete:chat_id:message_id:report_id parts = callback.data.split(":") chat_id = int(parts[2]) message_id = int(parts[3]) report_id = parts[4] # Удаляем сообщение try: await callback.bot.delete_message( chat_id=chat_id, message_id=message_id ) admin_name = format_user(callback.from_user) # Обновляем сообщение updated_text = callback.message.text + f"\n\n🗑 Сообщение удалено ({admin_name})" # Убираем кнопки await callback.message.edit_text( text=updated_text, parse_mode="HTML" ) await callback.answer("✅ Сообщение удалено", show_alert=True) logger.info( f"Репорт #{report_id}: сообщение {message_id} удалено админом {callback.from_user.id}", log_type="REPORT" ) except TelegramBadRequest as e: await callback.answer(f"❌ Ошибка удаления: {str(e)}", show_alert=True) except Exception as e: logger.error(f"Ошибка удаления из репорта: {e}", log_type="REPORT") await callback.answer("❌ Ошибка выполнения", show_alert=True) @router.callback_query(F.data.startswith("report:close:"), IsAdmin()) async def report_close_callback(callback: CallbackQuery) -> None: """Обрабатывает нажатие кнопки 'Закрыть'""" try: # Парсим данные: report:close:report_id parts = callback.data.split(":") report_id = parts[2] admin_name = format_user(callback.from_user) # Обновляем сообщение updated_text = callback.message.text + f"\n\n✅ Репорт закрыт ({admin_name})" # Убираем кнопки await callback.message.edit_text( text=updated_text, parse_mode="HTML" ) await callback.answer("✅ Репорт закрыт") logger.info( f"Репорт #{report_id} закрыт админом {callback.from_user.id}", log_type="REPORT" ) except Exception as e: logger.error(f"Ошибка закрытия репорта: {e}", log_type="REPORT") await callback.answer("❌ Ошибка выполнения", show_alert=True) # ================= ДОПОЛНИТЕЛЬНЫЕ ФУНКЦИИ ================= @router.message(Command(*COMMANDS.get("reporthelp", ["reporthelp"]), prefix=settings.PREFIX, ignore_case=True)) async def report_help_cmd(message: Message) -> None: """ Показывает справку по системе репортов. Доступно всем пользователям. """ text = ( "🚨 СИСТЕМА РЕПОРТОВ\n\n" "Используйте команду /report, чтобы пожаловаться на сообщение администраторам.\n\n" "📝 Как пожаловаться:\n" "1. Ответьте на сообщение нарушителя\n" "2. Напишите /report\n" "3. Можно указать причину: /report спам\n\n" "✅ Примеры:\n" "• /report — жалоба без причины\n" "• /report спам — жалоба на спам\n" "• /report оскорбления — жалоба на оскорбления\n\n" "⚠️ Важно:\n" "├─ Нельзя пожаловаться на себя\n" "├─ Нельзя пожаловаться на ботов\n" "├─ Нельзя пожаловаться на администраторов\n" "└─ Ложные жалобы могут привести к бану\n\n" "💡 Администраторы получат уведомление и примут меры" ) await message.answer(text, parse_mode="HTML") @router.message(Command(*COMMANDS.get("reportstats", ["reportstats"]), prefix=settings.PREFIX, ignore_case=True), IsAdmin()) async def report_stats_cmd(message: Message) -> None: """ Показывает статистику по репортам (для админов). TODO: Реализовать сохранение статистики в БД """ text = ( "📊 СТАТИСТИКА РЕПОРТОВ\n\n" "⚠️ Функция в разработке\n\n" "Планируется:\n" "• Всего репортов за всё время\n" "• Топ жалобщиков\n" "• Топ нарушителей\n" "• Распределение по причинам\n" "• Статистика обработки\n\n" "💡 Для реализации нужно добавить таблицу reports в БД" ) await message.answer(text, parse_mode="HTML")