feat: добавлено управление сообщениями пользователей в админ-панель
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
- Добавлена кнопка 'Сообщения пользователей' в админ меню - Реализован просмотр последних сообщений с фильтрацией - Возможность просмотра медиа (фото, видео) прямо в боте - Функция удаления сообщений администратором - Удаление происходит как в БД, так и у пользователей в Telegram - Просмотр всех сообщений конкретного пользователя - Добавлены методы в ChatMessageService и UserService - Метод get_user_messages_all для получения всех сообщений - Метод mark_as_deleted для пометки сообщений как удаленных - Метод count_messages для подсчета количества сообщений - Метод get_user_by_id в UserService
This commit is contained in:
@@ -16,8 +16,9 @@ import json
|
||||
|
||||
from ..core.database import async_session_maker
|
||||
from ..core.services import UserService, LotteryService, ParticipationService
|
||||
from ..core.chat_services import ChatMessageService
|
||||
from ..core.config import ADMIN_IDS
|
||||
from ..core.models import User, Lottery, Participation, Account
|
||||
from ..core.models import User, Lottery, Participation, Account, ChatMessage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -3412,5 +3413,286 @@ async def apply_display_type(callback: CallbackQuery, state: FSMContext):
|
||||
await state.clear()
|
||||
|
||||
|
||||
# ============= УПРАВЛЕНИЕ СООБЩЕНИЯМИ ПОЛЬЗОВАТЕЛЕЙ =============
|
||||
|
||||
@admin_router.callback_query(F.data == "admin_messages")
|
||||
async def show_messages_menu(callback: CallbackQuery):
|
||||
"""Показать меню управления сообщениями"""
|
||||
if not is_admin(callback.from_user.id):
|
||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||
return
|
||||
|
||||
text = "💬 *Управление сообщениями пользователей*\n\n"
|
||||
text += "Здесь вы можете просматривать и удалять сообщения пользователей.\n\n"
|
||||
text += "Выберите действие:"
|
||||
|
||||
buttons = [
|
||||
[InlineKeyboardButton(text="📋 Последние сообщения", callback_data="admin_messages_recent")],
|
||||
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_panel")]
|
||||
]
|
||||
|
||||
await callback.message.edit_text(
|
||||
text,
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons),
|
||||
parse_mode="Markdown"
|
||||
)
|
||||
|
||||
|
||||
@admin_router.callback_query(F.data == "admin_messages_recent")
|
||||
async def show_recent_messages(callback: CallbackQuery, page: int = 0):
|
||||
"""Показать последние сообщения"""
|
||||
if not is_admin(callback.from_user.id):
|
||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||
return
|
||||
|
||||
limit = 10
|
||||
offset = page * limit
|
||||
|
||||
async with async_session_maker() as session:
|
||||
messages = await ChatMessageService.get_user_messages_all(
|
||||
session,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
include_deleted=False
|
||||
)
|
||||
|
||||
if not messages:
|
||||
text = "💬 Нет сообщений для отображения"
|
||||
buttons = [[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_messages")]]
|
||||
else:
|
||||
text = f"💬 *Последние сообщения*\n\n"
|
||||
|
||||
# Добавляем кнопки для просмотра сообщений
|
||||
buttons = []
|
||||
for msg in messages:
|
||||
sender = msg.sender
|
||||
username = f"@{sender.username}" if sender.username else f"ID{sender.telegram_id}"
|
||||
msg_preview = ""
|
||||
if msg.text:
|
||||
msg_preview = msg.text[:20] + "..." if len(msg.text) > 20 else msg.text
|
||||
else:
|
||||
msg_preview = msg.message_type
|
||||
|
||||
buttons.append([InlineKeyboardButton(
|
||||
text=f"👁 {username}: {msg_preview}",
|
||||
callback_data=f"admin_message_view_{msg.id}"
|
||||
)])
|
||||
|
||||
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_messages")])
|
||||
|
||||
await callback.message.edit_text(
|
||||
text,
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons),
|
||||
parse_mode="Markdown"
|
||||
)
|
||||
|
||||
|
||||
@admin_router.callback_query(F.data.startswith("admin_message_view_"))
|
||||
async def view_message(callback: CallbackQuery):
|
||||
"""Просмотр конкретного сообщения"""
|
||||
if not is_admin(callback.from_user.id):
|
||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||
return
|
||||
|
||||
message_id = int(callback.data.split("_")[-1])
|
||||
|
||||
async with async_session_maker() as session:
|
||||
msg = await ChatMessageService.get_message(session, message_id)
|
||||
|
||||
if not msg:
|
||||
await callback.answer("❌ Сообщение не найдено", show_alert=True)
|
||||
return
|
||||
|
||||
sender = msg.sender
|
||||
username = f"@{sender.username}" if sender.username else f"ID: {sender.telegram_id}"
|
||||
|
||||
text = f"💬 *Просмотр сообщения*\n\n"
|
||||
text += f"👤 Отправитель: {username}\n"
|
||||
text += f"🆔 Telegram ID: `{sender.telegram_id}`\n"
|
||||
text += f"📝 Тип: {msg.message_type}\n"
|
||||
text += f"📅 Дата: {msg.created_at.strftime('%d.%m.%Y %H:%M:%S')}\n\n"
|
||||
|
||||
if msg.text:
|
||||
text += f"📄 *Текст:*\n{msg.text}\n\n"
|
||||
|
||||
if msg.file_id:
|
||||
text += f"📎 File ID: `{msg.file_id}`\n\n"
|
||||
|
||||
if msg.is_deleted:
|
||||
text += f"🗑 *Удалено:* Да\n"
|
||||
if msg.deleted_at:
|
||||
text += f" Дата: {msg.deleted_at.strftime('%d.%m.%Y %H:%M')}\n"
|
||||
|
||||
buttons = []
|
||||
|
||||
# Кнопка удаления (если еще не удалено)
|
||||
if not msg.is_deleted:
|
||||
buttons.append([InlineKeyboardButton(
|
||||
text="🗑 Удалить сообщение",
|
||||
callback_data=f"admin_message_delete_{message_id}"
|
||||
)])
|
||||
|
||||
# Кнопка для просмотра всех сообщений пользователя
|
||||
buttons.append([InlineKeyboardButton(
|
||||
text="📋 Все сообщения пользователя",
|
||||
callback_data=f"admin_messages_user_{sender.id}"
|
||||
)])
|
||||
|
||||
buttons.append([InlineKeyboardButton(text="🔙 К списку", callback_data="admin_messages_recent")])
|
||||
|
||||
# Если сообщение содержит медиа, попробуем его показать
|
||||
if msg.file_id and msg.message_type in ['photo', 'video', 'document', 'animation']:
|
||||
try:
|
||||
if msg.message_type == 'photo':
|
||||
await callback.message.answer_photo(
|
||||
photo=msg.file_id,
|
||||
caption=text,
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons),
|
||||
parse_mode="Markdown"
|
||||
)
|
||||
await callback.message.delete()
|
||||
await callback.answer()
|
||||
return
|
||||
elif msg.message_type == 'video':
|
||||
await callback.message.answer_video(
|
||||
video=msg.file_id,
|
||||
caption=text,
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons),
|
||||
parse_mode="Markdown"
|
||||
)
|
||||
await callback.message.delete()
|
||||
await callback.answer()
|
||||
return
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отправке медиа: {e}")
|
||||
|
||||
await callback.message.edit_text(
|
||||
text,
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons),
|
||||
parse_mode="Markdown"
|
||||
)
|
||||
|
||||
|
||||
@admin_router.callback_query(F.data.startswith("admin_message_delete_"))
|
||||
async def delete_message(callback: CallbackQuery):
|
||||
"""Удалить сообщение пользователя"""
|
||||
if not is_admin(callback.from_user.id):
|
||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||
return
|
||||
|
||||
message_id = int(callback.data.split("_")[-1])
|
||||
|
||||
async with async_session_maker() as session:
|
||||
msg = await ChatMessageService.get_message(session, message_id)
|
||||
|
||||
if not msg:
|
||||
await callback.answer("❌ Сообщение не найдено", show_alert=True)
|
||||
return
|
||||
|
||||
# Получаем админа
|
||||
admin = await UserService.get_or_create_user(
|
||||
session,
|
||||
callback.from_user.id,
|
||||
callback.from_user.username,
|
||||
callback.from_user.first_name,
|
||||
callback.from_user.last_name
|
||||
)
|
||||
|
||||
# Помечаем сообщение как удаленное
|
||||
success = await ChatMessageService.mark_as_deleted(
|
||||
session,
|
||||
message_id,
|
||||
admin.id
|
||||
)
|
||||
|
||||
if success:
|
||||
# Пытаемся удалить сообщение из чата пользователя
|
||||
try:
|
||||
if msg.forwarded_message_ids:
|
||||
# Удаляем пересланные копии у всех пользователей
|
||||
for user_tg_id, tg_msg_id in msg.forwarded_message_ids.items():
|
||||
try:
|
||||
await callback.bot.delete_message(
|
||||
chat_id=int(user_tg_id),
|
||||
message_id=tg_msg_id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Не удалось удалить сообщение {tg_msg_id} у пользователя {user_tg_id}: {e}")
|
||||
|
||||
# Удаляем оригинальное сообщение у отправителя
|
||||
try:
|
||||
await callback.bot.delete_message(
|
||||
chat_id=msg.sender.telegram_id,
|
||||
message_id=msg.telegram_message_id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Не удалось удалить оригинальное сообщение: {e}")
|
||||
|
||||
await callback.answer("✅ Сообщение удалено!", show_alert=True)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении сообщений: {e}")
|
||||
await callback.answer("⚠️ Помечено как удаленное", show_alert=True)
|
||||
else:
|
||||
await callback.answer("❌ Ошибка при удалении", show_alert=True)
|
||||
|
||||
# Возвращаемся к списку
|
||||
await show_recent_messages(callback, 0)
|
||||
|
||||
|
||||
@admin_router.callback_query(F.data.startswith("admin_messages_user_"))
|
||||
async def show_user_messages(callback: CallbackQuery):
|
||||
"""Показать все сообщения конкретного пользователя"""
|
||||
if not is_admin(callback.from_user.id):
|
||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||
return
|
||||
|
||||
user_id = int(callback.data.split("_")[-1])
|
||||
|
||||
async with async_session_maker() as session:
|
||||
user = await UserService.get_user_by_id(session, user_id)
|
||||
|
||||
if not user:
|
||||
await callback.answer("❌ Пользователь не найден", show_alert=True)
|
||||
return
|
||||
|
||||
messages = await ChatMessageService.get_user_messages(
|
||||
session,
|
||||
user_id,
|
||||
limit=20,
|
||||
include_deleted=True
|
||||
)
|
||||
|
||||
username = f"@{user.username}" if user.username else f"ID: {user.telegram_id}"
|
||||
|
||||
text = f"💬 *Сообщения {username}*\n\n"
|
||||
|
||||
if not messages:
|
||||
text += "Нет сообщений"
|
||||
buttons = [[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_messages_recent")]]
|
||||
else:
|
||||
# Кнопки для просмотра отдельных сообщений
|
||||
buttons = []
|
||||
for msg in messages[:15]:
|
||||
status = "🗑" if msg.is_deleted else "✅"
|
||||
msg_preview = ""
|
||||
if msg.text:
|
||||
msg_preview = msg.text[:25] + "..." if len(msg.text) > 25 else msg.text
|
||||
else:
|
||||
msg_preview = msg.message_type
|
||||
|
||||
buttons.append([InlineKeyboardButton(
|
||||
text=f"{status} {msg_preview} ({msg.created_at.strftime('%d.%m %H:%M')})",
|
||||
callback_data=f"admin_message_view_{msg.id}"
|
||||
)])
|
||||
|
||||
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_messages_recent")])
|
||||
|
||||
await callback.message.edit_text(
|
||||
text,
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons),
|
||||
parse_mode="Markdown"
|
||||
)
|
||||
|
||||
|
||||
# Экспорт роутера
|
||||
__all__ = ['admin_router']
|
||||
Reference in New Issue
Block a user