mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-04-28 16:50:08 +00:00
@@ -79,12 +79,16 @@ async def show_admin_tickets(
|
||||
ticket_data = []
|
||||
for ticket in tickets:
|
||||
user_name = ticket.user.full_name if ticket.user else "Unknown"
|
||||
username = ticket.user.username if ticket.user else None
|
||||
telegram_id = ticket.user.telegram_id if ticket.user else None
|
||||
ticket_data.append({
|
||||
'id': ticket.id,
|
||||
'title': ticket.title,
|
||||
'status_emoji': ticket.status_emoji,
|
||||
'priority_emoji': ticket.priority_emoji,
|
||||
'user_name': user_name,
|
||||
'username': username,
|
||||
'telegram_id': telegram_id,
|
||||
'is_closed': ticket.is_closed,
|
||||
'locked_emoji': ("🔒" if ticket.is_user_reply_blocked else "")
|
||||
})
|
||||
@@ -169,9 +173,13 @@ async def view_admin_ticket(
|
||||
}.get(ticket.status, ticket.status)
|
||||
|
||||
user_name = ticket.user.full_name if ticket.user else "Unknown"
|
||||
|
||||
telegram_id_display = ticket.user.telegram_id if ticket.user else "—"
|
||||
username_display = (ticket.user.username or "отсутствует") if ticket.user else "отсутствует"
|
||||
|
||||
ticket_text = f"🎫 Тикет #{ticket.id}\n\n"
|
||||
ticket_text += f"👤 Пользователь: {user_name}\n"
|
||||
ticket_text += f"🆔 Telegram ID: <code>{telegram_id_display}</code>\n"
|
||||
ticket_text += f"📱 Username: @{username_display}\n"
|
||||
ticket_text += f"📝 Заголовок: {ticket.title}\n"
|
||||
ticket_text += f"📊 Статус: {ticket.status_emoji} {status_text}\n"
|
||||
ticket_text += f"📅 Создан: {ticket.created_at.strftime('%d.%m.%Y %H:%M')}\n"
|
||||
@@ -201,6 +209,29 @@ async def view_admin_ticket(
|
||||
db_user.language,
|
||||
is_user_blocked=ticket.is_user_reply_blocked
|
||||
)
|
||||
# Кнопка открытия профиля пользователя в админке
|
||||
try:
|
||||
if ticket.user:
|
||||
admin_profile_btn = types.InlineKeyboardButton(
|
||||
text="👤 К пользователю",
|
||||
callback_data=f"admin_user_manage_{ticket.user.id}_from_ticket_{ticket.id}"
|
||||
)
|
||||
keyboard.inline_keyboard.insert(0, [admin_profile_btn])
|
||||
except Exception:
|
||||
pass
|
||||
# Кнопки профиля и быстрого ЛС
|
||||
try:
|
||||
if ticket.user and ticket.user.telegram_id:
|
||||
buttons_row = []
|
||||
if ticket.user.username:
|
||||
profile_url = f"https://t.me/{ticket.user.username}"
|
||||
buttons_row.append(types.InlineKeyboardButton(text="👤 Профиль", url=profile_url))
|
||||
pm_url = f"tg://user?id={ticket.user.telegram_id}"
|
||||
buttons_row.append(types.InlineKeyboardButton(text="✉ Написать в ЛС", url=pm_url))
|
||||
if buttons_row:
|
||||
keyboard.inline_keyboard.insert(0, buttons_row)
|
||||
except Exception:
|
||||
pass
|
||||
if has_photos:
|
||||
try:
|
||||
keyboard.inline_keyboard.insert(0, [types.InlineKeyboardButton(text=texts.t("TICKET_ATTACHMENTS", "📎 Вложения"), callback_data=f"admin_ticket_attachments_{ticket_id}")])
|
||||
@@ -445,6 +476,17 @@ async def close_admin_ticket(
|
||||
# audit
|
||||
try:
|
||||
is_mod = (not settings.is_admin(callback.from_user.id) and SupportSettingsService.is_moderator(callback.from_user.id))
|
||||
# обогатим details контактами пользователя тикета
|
||||
details = {}
|
||||
try:
|
||||
t = await TicketCRUD.get_ticket_by_id(db, ticket_id, load_user=True)
|
||||
if t and t.user:
|
||||
details.update({
|
||||
"target_telegram_id": t.user.telegram_id,
|
||||
"target_username": t.user.username,
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
await TicketCRUD.add_support_audit(
|
||||
db,
|
||||
actor_user_id=db_user.id if db_user else None,
|
||||
@@ -453,7 +495,7 @@ async def close_admin_ticket(
|
||||
action="close_ticket",
|
||||
ticket_id=ticket_id,
|
||||
target_user_id=None,
|
||||
details={}
|
||||
details=details
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -645,6 +687,29 @@ async def handle_admin_block_duration_input(
|
||||
ticket_text += "📎 Вложение: фото\n\n"
|
||||
|
||||
kb = get_admin_ticket_view_keyboard(updated.id, updated.is_closed, db_user.language, is_user_blocked=updated.is_user_reply_blocked)
|
||||
# Кнопка открытия профиля пользователя в админке
|
||||
try:
|
||||
if updated.user:
|
||||
admin_profile_btn = types.InlineKeyboardButton(
|
||||
text="👤 К пользователю",
|
||||
callback_data=f"admin_user_manage_{updated.user.id}_from_ticket_{updated.id}"
|
||||
)
|
||||
kb.inline_keyboard.insert(0, [admin_profile_btn])
|
||||
except Exception:
|
||||
pass
|
||||
# Кнопки профиля и ЛС при обновлении карточки
|
||||
try:
|
||||
if updated.user and updated.user.telegram_id:
|
||||
buttons_row = []
|
||||
if updated.user.username:
|
||||
profile_url = f"https://t.me/{updated.user.username}"
|
||||
buttons_row.append(types.InlineKeyboardButton(text="👤 Профиль", url=profile_url))
|
||||
pm_url = f"tg://user?id={updated.user.telegram_id}"
|
||||
buttons_row.append(types.InlineKeyboardButton(text="✉ Написать в ЛС", url=pm_url))
|
||||
if buttons_row:
|
||||
kb.inline_keyboard.insert(0, buttons_row)
|
||||
except Exception:
|
||||
pass
|
||||
has_photos = any(getattr(m, "has_media", False) and getattr(m, "media_type", None) == "photo" for m in updated.messages or [])
|
||||
if has_photos:
|
||||
try:
|
||||
@@ -699,6 +764,16 @@ async def unblock_user_in_ticket(
|
||||
try:
|
||||
is_mod = (not settings.is_admin(callback.from_user.id) and SupportSettingsService.is_moderator(callback.from_user.id))
|
||||
ticket_id = int(callback.data.replace("admin_unblock_user_ticket_", ""))
|
||||
details = {}
|
||||
try:
|
||||
t = await TicketCRUD.get_ticket_by_id(db, ticket_id, load_user=True)
|
||||
if t and t.user:
|
||||
details.update({
|
||||
"target_telegram_id": t.user.telegram_id,
|
||||
"target_username": t.user.username,
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
await TicketCRUD.add_support_audit(
|
||||
db,
|
||||
actor_user_id=db_user.id if db_user else None,
|
||||
@@ -707,7 +782,7 @@ async def unblock_user_in_ticket(
|
||||
action="unblock_user",
|
||||
ticket_id=ticket_id,
|
||||
target_user_id=None,
|
||||
details={}
|
||||
details=details
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -741,6 +816,16 @@ async def block_user_permanently(
|
||||
# audit
|
||||
try:
|
||||
is_mod = (not settings.is_admin(callback.from_user.id) and SupportSettingsService.is_moderator(callback.from_user.id))
|
||||
details = {}
|
||||
try:
|
||||
t = await TicketCRUD.get_ticket_by_id(db, ticket_id, load_user=True)
|
||||
if t and t.user:
|
||||
details.update({
|
||||
"target_telegram_id": t.user.telegram_id,
|
||||
"target_username": t.user.username,
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
await TicketCRUD.add_support_audit(
|
||||
db,
|
||||
actor_user_id=db_user.id if db_user else None,
|
||||
@@ -749,7 +834,7 @@ async def block_user_permanently(
|
||||
action="block_user_perm",
|
||||
ticket_id=ticket_id,
|
||||
target_user_id=None,
|
||||
details={}
|
||||
details=details
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -766,7 +766,32 @@ async def show_user_management(
|
||||
state: FSMContext
|
||||
):
|
||||
|
||||
user_id = int(callback.data.split('_')[-1])
|
||||
# Поддерживаем переход "из тикета": admin_user_manage_{userId}_from_ticket_{ticketId}
|
||||
parts = callback.data.split('_')
|
||||
try:
|
||||
user_id = int(parts[3]) # admin_user_manage_{userId}
|
||||
except Exception:
|
||||
user_id = int(callback.data.split('_')[-1])
|
||||
origin_ticket_id = None
|
||||
if "from" in parts and "ticket" in parts:
|
||||
try:
|
||||
origin_ticket_id = int(parts[-1])
|
||||
except Exception:
|
||||
origin_ticket_id = None
|
||||
# Если пришли из тикета — запомним в состоянии, чтобы сохранять кнопку возврата
|
||||
try:
|
||||
if origin_ticket_id:
|
||||
await state.update_data(origin_ticket_id=origin_ticket_id, origin_ticket_user_id=user_id)
|
||||
except Exception:
|
||||
pass
|
||||
# Если не пришло в колбэке — попробуем достать из состояния
|
||||
if origin_ticket_id is None:
|
||||
try:
|
||||
data_state = await state.get_data()
|
||||
if data_state.get("origin_ticket_user_id") == user_id:
|
||||
origin_ticket_id = data_state.get("origin_ticket_id")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Проверяем, откуда пришел пользователь
|
||||
back_callback = "admin_users_list"
|
||||
@@ -844,9 +869,22 @@ async def show_user_management(
|
||||
if current_state == AdminStates.viewing_user_from_balance_list:
|
||||
back_callback = "admin_users_balance_filter"
|
||||
|
||||
# Базовая клавиатура профиля
|
||||
kb = get_user_management_keyboard(user.id, user.status, db_user.language, back_callback)
|
||||
# Если пришли из тикета — добавим в начало кнопку возврата к тикету
|
||||
try:
|
||||
if origin_ticket_id:
|
||||
back_to_ticket_btn = types.InlineKeyboardButton(
|
||||
text="🎫 Вернуться к тикету",
|
||||
callback_data=f"admin_view_ticket_{origin_ticket_id}"
|
||||
)
|
||||
kb.inline_keyboard.insert(0, [back_to_ticket_btn])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
await callback.message.edit_text(
|
||||
text,
|
||||
reply_markup=get_user_management_keyboard(user.id, user.status, db_user.language, back_callback)
|
||||
reply_markup=kb
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database.models import User, Ticket, TicketStatus
|
||||
from app.database.crud.ticket import TicketCRUD, TicketMessageCRUD
|
||||
from app.database.crud.user import get_user_by_id
|
||||
from app.keyboards.inline import (
|
||||
get_ticket_cancel_keyboard,
|
||||
get_my_tickets_keyboard,
|
||||
@@ -937,10 +938,21 @@ async def notify_admins_about_new_ticket(ticket: Ticket, db: AsyncSession):
|
||||
if len(title) > 60:
|
||||
title = title[:57] + "..."
|
||||
|
||||
# Загрузим пользователя, чтобы отобразить реальный Telegram ID и username
|
||||
try:
|
||||
user = await get_user_by_id(db, ticket.user_id)
|
||||
except Exception:
|
||||
user = None
|
||||
full_name = user.full_name if user else "Unknown"
|
||||
telegram_id_display = user.telegram_id if user else "—"
|
||||
username_display = (user.username or "отсутствует") if user else "отсутствует"
|
||||
|
||||
notification_text = (
|
||||
f"🎫 <b>НОВЫЙ ТИКЕТ</b>\n\n"
|
||||
f"🆔 <b>ID:</b> <code>{ticket.id}</code>\n"
|
||||
f"👤 <b>User ID:</b> <code>{ticket.user_id}</code>\n"
|
||||
f"👤 <b>Пользователь:</b> {full_name}\n"
|
||||
f"🆔 <b>Telegram ID:</b> <code>{telegram_id_display}</code>\n"
|
||||
f"📱 <b>Username:</b> @{username_display}\n"
|
||||
f"📝 <b>Заголовок:</b> {title or '—'}\n"
|
||||
f"📅 <b>Создан:</b> {ticket.created_at.strftime('%d.%m.%Y %H:%M')}\n"
|
||||
)
|
||||
|
||||
@@ -1925,10 +1925,22 @@ def get_admin_tickets_keyboard(
|
||||
status_emoji = ticket.get('status_emoji', '❓')
|
||||
if ticket.get('is_closed', False):
|
||||
status_emoji = '✅'
|
||||
user_name = ticket.get('user_name', 'Unknown')[:15]
|
||||
user_name = ticket.get('user_name', 'Unknown')
|
||||
username = ticket.get('username')
|
||||
telegram_id = ticket.get('telegram_id')
|
||||
# Сформируем компактное отображение: Имя (@username | ID)
|
||||
name_parts = [user_name[:15]]
|
||||
contact_parts = []
|
||||
if username:
|
||||
contact_parts.append(f"@{username}")
|
||||
if telegram_id:
|
||||
contact_parts.append(str(telegram_id))
|
||||
if contact_parts:
|
||||
name_parts.append(f"({' | '.join(contact_parts)})")
|
||||
name_display = ' '.join(name_parts)
|
||||
title = ticket.get('title', 'Без названия')[:20]
|
||||
locked_emoji = ticket.get('locked_emoji', '')
|
||||
button_text = f"{status_emoji} #{ticket['id']} {locked_emoji} {user_name}: {title}".replace(" ", " ")
|
||||
button_text = f"{status_emoji} #{ticket['id']} {locked_emoji} {name_display}: {title}".replace(" ", " ")
|
||||
row = [InlineKeyboardButton(text=button_text, callback_data=f"admin_view_ticket_{ticket['id']}")]
|
||||
if ticket.get('is_closed', False):
|
||||
closed_rows.append(row)
|
||||
|
||||
@@ -1325,10 +1325,17 @@ class MonitoringService:
|
||||
if len(title) > 60:
|
||||
title = title[:57] + '...'
|
||||
|
||||
# Детали пользователя: имя, Telegram ID и username
|
||||
full_name = ticket.user.full_name if ticket.user else "Unknown"
|
||||
telegram_id_display = ticket.user.telegram_id if ticket.user else "—"
|
||||
username_display = (ticket.user.username or "отсутствует") if ticket.user else "отсутствует"
|
||||
|
||||
text = (
|
||||
f"⏰ <b>Ожидание ответа на тикет превышено</b>\n\n"
|
||||
f"🆔 <b>ID:</b> <code>{ticket.id}</code>\n"
|
||||
f"👤 <b>User ID:</b> <code>{ticket.user_id}</code>\n"
|
||||
f"👤 <b>Пользователь:</b> {full_name}\n"
|
||||
f"🆔 <b>Telegram ID:</b> <code>{telegram_id_display}</code>\n"
|
||||
f"📱 <b>Username:</b> @{username_display}\n"
|
||||
f"📝 <b>Заголовок:</b> {title or '—'}\n"
|
||||
f"⏱️ <b>Ожидает ответа:</b> {waited_minutes} мин\n"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user