Update admin_handlers.py

This commit is contained in:
Egor
2025-08-17 05:32:30 +03:00
committed by GitHub
parent 572170b1cc
commit b317368df2

View File

@@ -811,7 +811,37 @@ async def list_users_callback(callback: CallbackQuery, user: User, db: Database,
if not await check_admin_access(callback, user):
return
await show_users_page(callback, user, db, page=0)
try:
users = await db.get_all_users()
if not users:
await callback.message.edit_text(
"❌ Пользователи не найдены",
reply_markup=back_keyboard("admin_users", user.language)
)
return
text = t('user_list', user.language) + "\n\n"
for u in users[:20]:
username = u.username or "N/A"
text += t('user_item', user.language,
id=u.telegram_id,
username=username,
balance=u.balance
) + "\n"
if len(users) > 20:
text += f"\n... и еще {len(users) - 20} пользователей"
await callback.message.edit_text(
text,
reply_markup=back_keyboard("admin_users", user.language)
)
except Exception as e:
logger.error(f"Error listing users: {e}")
await callback.answer(t('error_occurred', user.language))
@admin_router.callback_query(F.data == "admin_balance")
async def admin_balance_callback(callback: CallbackQuery, user: User, **kwargs):
@@ -3822,162 +3852,16 @@ def create_users_pagination_keyboard(current_page: int, total_pages: int, langua
return InlineKeyboardMarkup(inline_keyboard=buttons)
@admin_router.callback_query(F.data.startswith("users_page_"))
async def users_page_callback(callback: CallbackQuery, user: User, db: Database, **kwargs):
async def users_page_callback(callback: CallbackQuery, user: User, api: RemnaWaveAPI = None, state: FSMContext = None, **kwargs):
if not await check_admin_access(callback, user):
return
try:
page = int(callback.data.split("_")[-1])
await show_users_page(callback, user, db, page=page)
except (ValueError, IndexError) as e:
logger.error(f"Error parsing page number: {e}")
await callback.answer("❌ Ошибка навигации")
async def show_users_page(callback: CallbackQuery, user: User, db: Database, page: int = 0):
try:
page_size = 8
offset = page * page_size
users, total_count = await db.get_users_paginated(offset=offset, limit=page_size)
if not users and page == 0:
await callback.message.edit_text(
"❌ **Пользователи не найдены**",
reply_markup=back_keyboard("admin_users", user.language),
parse_mode='Markdown'
)
return
if not users and page > 0:
# Если страница пуста, переходим на предыдущую
await show_users_page(callback, user, db, page - 1)
return
total_pages = (total_count + page_size - 1) // page_size
current_time = datetime.now().strftime("%H:%M:%S")
text = f"👥 **Управление пользователями**\n"
text += f"📊 Всего: **{total_count}** | Страница: **{page + 1}/{total_pages}**\n\n"
for i, u in enumerate(users, 1):
status_emoji, status_text = format_user_status(u)
display_name = truncate_text(u.first_name, 15)
username_display = f"@{u.username}" if u.username else "без @"
username_display = truncate_text(username_display, 18)
text += f"**{offset + i}.** {status_emoji} **{display_name}**\n"
text += f"{username_display} • ID: `{u.telegram_id}`\n"
if u.is_admin:
text += f" └ Статус: **{status_text}**\n\n"
else:
text += f" └ Баланс: **{u.balance:.0f}₽**\n\n"
text += f"🕐 _Обновлено: {current_time}_"
keyboard = create_users_management_keyboard(
users, page, total_pages, offset, user.language
)
try:
await callback.message.edit_text(
text,
reply_markup=keyboard,
parse_mode='Markdown'
)
except Exception as edit_error:
if "message is not modified" in str(edit_error).lower():
await callback.answer("✅ Список актуален", show_alert=False)
else:
logger.error(f"Error editing users message: {edit_error}")
await callback.answer("❌ Ошибка обновления", show_alert=True)
await show_system_users_list_paginated(callback, user, api, state, page)
except Exception as e:
logger.error(f"Error showing users page: {e}")
await callback.message.edit_text(
"❌ Ошибка загрузки списка пользователей",
reply_markup=back_keyboard("admin_users", user.language)
)
def create_users_management_keyboard(users: List[User], page: int, total_pages: int,
offset: int, language: str = 'ru') -> InlineKeyboardMarkup:
buttons = []
for i, u in enumerate(users):
user_index = offset + i + 1
display_name = u.first_name[:8] if u.first_name else f"ID{u.telegram_id}"
if len(display_name) > 8:
display_name = display_name[:8]
buttons.append([
InlineKeyboardButton(
text=f"👤 {user_index}. {display_name}",
callback_data=f"user_detail_{u.telegram_id}"
)
])
action_buttons = [
InlineKeyboardButton(text="💰", callback_data=f"user_balance_{u.telegram_id}"),
InlineKeyboardButton(text="📋", callback_data=f"user_subs_{u.telegram_id}"),
InlineKeyboardButton(text="✉️", callback_data=f"user_message_{u.telegram_id}"),
]
if not u.is_admin:
action_buttons.append(
InlineKeyboardButton(text="🔧", callback_data=f"user_manage_{u.telegram_id}")
)
buttons.append(action_buttons)
if users:
buttons.append([
InlineKeyboardButton(text="━━━━━━━━━━━━━━━━━━━━", callback_data="noop")
])
if total_pages > 1:
nav_buttons = []
if page > 0:
nav_buttons.append(
InlineKeyboardButton(text="", callback_data="users_page_0")
)
if page > 0:
nav_buttons.append(
InlineKeyboardButton(text="◀️", callback_data=f"users_page_{page - 1}")
)
nav_buttons.append(
InlineKeyboardButton(text=f"📄 {page + 1}/{total_pages}", callback_data="noop")
)
if page < total_pages - 1:
nav_buttons.append(
InlineKeyboardButton(text="▶️", callback_data=f"users_page_{page + 1}")
)
if page < total_pages - 1:
nav_buttons.append(
InlineKeyboardButton(text="", callback_data=f"users_page_{total_pages - 1}")
)
buttons.append(nav_buttons)
buttons.extend([
[
InlineKeyboardButton(text="🔍 Поиск", callback_data="search_user"),
InlineKeyboardButton(text="📊 Статистика", callback_data="users_stats"),
InlineKeyboardButton(text="🔄 Обновить", callback_data=f"users_page_{page}")
],
[
InlineKeyboardButton(text="📋 Все подписки", callback_data="admin_user_subscriptions_all"),
InlineKeyboardButton(text="💰 Управление балансом", callback_data="admin_balance")
],
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_users")]
])
return InlineKeyboardMarkup(inline_keyboard=buttons)
logger.error(f"Error in pagination: {e}")
await callback.answer("❌ Ошибка навигации", show_alert=True)
@admin_router.callback_query(F.data.startswith("refresh_system_users_"))
async def refresh_system_users_callback(callback: CallbackQuery, user: User, api: RemnaWaveAPI = None, **kwargs):
@@ -4096,390 +3980,169 @@ async def users_statistics_callback(callback: CallbackQuery, user: User, api: Re
reply_markup=system_users_keyboard(user.language)
)
@admin_router.callback_query(F.data == "search_user")
@admin_router.callback_query(F.data == "search_user_uuid")
async def search_user_callback(callback: CallbackQuery, user: User, state: FSMContext, **kwargs):
if not await check_admin_access(callback, user):
return
await callback.message.edit_text(
"🔍 **Поиск пользователей**\n\n"
"Введите для поиска:\n"
"Имя пользователя\n"
"Username (без @)\n"
"• Telegram ID\n\n"
"📝 Напишите запрос:",
reply_markup=cancel_keyboard(user.language),
parse_mode='Markdown'
"🔍 Поиск пользователя\n\n"
"Вы можете искать по:\n"
"UUID (полный)\n"
"Short UUID\n"
"• Telegram ID\n"
"• Username\n"
"• Email\n\n"
"📝 Введите любой идентификатор:",
reply_markup=cancel_keyboard(user.language)
)
await state.set_state(BotStates.admin_search_user)
await state.set_state(BotStates.admin_search_user_any)
@admin_router.message(StateFilter(BotStates.admin_search_user))
async def handle_user_search(message: Message, state: FSMContext, user: User, db: Database, **kwargs):
search_query = message.text.strip()
@admin_router.message(StateFilter(BotStates.admin_search_user_any))
async def handle_search_user_any(message: Message, state: FSMContext, user: User, api: RemnaWaveAPI = None, db: Database = None, **kwargs):
search_input = message.text.strip()
if len(search_query) < 2:
await message.answer("❌ Запрос должен содержать минимум 2 символа")
if not api:
await message.answer(
"❌ API недоступен",
reply_markup=system_users_keyboard(user.language)
)
await state.clear()
return
try:
users, total_count = await db.get_users_paginated(
offset=0, limit=10, search_query=search_query
)
search_msg = await message.answer("🔍 Поиск пользователя...")
user_data = None
search_method = None
if not users:
await message.answer(
f"❌ По запросу **'{search_query}'** пользователи не найдены",
reply_markup=admin_menu_keyboard(user.language),
if validate_squad_uuid(search_input):
user_data = await api.get_user_by_uuid(search_input)
search_method = "UUID"
if not user_data:
try:
telegram_id = int(search_input)
user_data = await api.get_user_by_telegram_id(telegram_id)
search_method = "Telegram ID"
except ValueError:
pass
if not user_data:
user_data = await api.get_user_by_short_uuid(search_input)
search_method = "Short UUID"
if not user_data:
user_data = await api.get_user_by_username(search_input)
search_method = "Username"
if not user_data and '@' in search_input:
user_data = await api.get_user_by_email(search_input)
search_method = "Email"
if not user_data:
await search_msg.edit_text(
f"❌ Пользователь не найден\n\n"
f"Искомое значение: `{search_input}`\n\n"
f"Проверены методы поиска:\n"
f"• UUID\n"
f"• Short UUID\n"
f"• Telegram ID\n"
f"• Username\n"
f"• Email\n\n"
f"Проверьте правильность ввода и попробуйте снова",
reply_markup=system_users_keyboard(user.language),
parse_mode='Markdown'
)
await state.clear()
return
text = f"🔍 **Результаты поиска: '{search_query}'**\n"
text += f"📊 Найдено: **{total_count}** пользователей\n\n"
local_user = None
if user_data.get('telegramId') and db:
local_user = await db.get_user_by_telegram_id(user_data['telegramId'])
for i, u in enumerate(users, 1):
status_emoji, status_text = format_user_status(u)
display_name = truncate_text(u.first_name, 20)
username_display = f"@{u.username}" if u.username else "без @"
text = f"👤 Информация о пользователе\n"
text += f"🔍 Найден по: {search_method}\n\n"
text += f"📛 Username: `{user_data.get('username', 'N/A')}`\n"
text += f"🆔 UUID: `{user_data.get('uuid', 'N/A')}`\n"
text += f"🔗 Short UUID: `{user_data.get('shortUuid', 'N/A')}`\n"
if user_data.get('telegramId'):
text += f"📱 Telegram ID: `{user_data.get('telegramId')}`\n"
if local_user:
text += f"💰 Баланс в боте: {local_user.balance} руб.\n"
if user_data.get('email'):
text += f"📧 Email: {user_data.get('email')}\n"
status = user_data.get('status', 'UNKNOWN')
status_emoji = "" if status == 'ACTIVE' else ""
text += f"\n🔘 Статус: {status_emoji} {status}\n"
if user_data.get('expireAt'):
expire_date = user_data['expireAt']
text += f"⏰ Истекает: {expire_date[:10]}\n"
text += f"**{i}.** {status_emoji} **{display_name}**\n"
text += f"{username_display} • ID: `{u.telegram_id}`\n"
if u.is_admin:
text += f" └ Статус: **{status_text}**\n\n"
else:
text += f" └ Баланс: **{u.balance:.0f}₽**\n\n"
try:
expire_dt = datetime.fromisoformat(expire_date.replace('Z', '+00:00'))
days_left = (expire_dt - datetime.now()).days
if days_left > 0:
text += f"📅 Осталось дней: {days_left}\n"
else:
text += f"❌ Подписка истекла\n"
except:
pass
if total_count > 10:
text += f"_... и еще {total_count - 10} пользователей_\n\n"
traffic_limit = user_data.get('trafficLimitBytes', 0)
used_traffic = user_data.get('usedTrafficBytes', 0)
text += "💡 Нажмите на номер пользователя для подробной информации"
if traffic_limit > 0:
text += f"\n📊 Лимит трафика: {format_bytes(traffic_limit)}\n"
text += f"📈 Использовано: {format_bytes(used_traffic)}\n"
usage_percent = (used_traffic / traffic_limit) * 100
text += f"📉 Использовано: {usage_percent:.1f}%\n"
else:
text += f"\n📊 Лимит трафика: Безлимитный\n"
text += f"📈 Использовано: {format_bytes(used_traffic)}\n"
keyboard = create_search_results_keyboard(users, user.language)
keyboard = create_user_management_keyboard(user_data.get('uuid'), user_data.get('status'), user.language)
await message.answer(
text,
reply_markup=keyboard,
parse_mode='Markdown'
)
await search_msg.edit_text(text, reply_markup=keyboard)
except Exception as e:
logger.error(f"Error searching users: {e}")
await message.answer(
"❌ Ошибка при поиске пользователей",
reply_markup=admin_menu_keyboard(user.language)
)
await state.clear()
logger.error(f"Error searching user: {e}")
def create_search_results_keyboard(users: List[User], language: str = 'ru') -> InlineKeyboardMarkup:
def create_user_management_keyboard(user_uuid: str, status: str, language: str = 'ru') -> InlineKeyboardMarkup:
buttons = []
for i, u in enumerate(users, 1):
display_name = truncate_text(u.first_name, 15)
status_emoji, _ = format_user_status(u)
if status == 'ACTIVE':
buttons.append([
InlineKeyboardButton(
text=f"{status_emoji} {i}. {display_name}",
callback_data=f"user_detail_{u.telegram_id}"
)
InlineKeyboardButton(text="❌ Отключить", callback_data=f"disable_user_{user_uuid}"),
InlineKeyboardButton(text="🔄 Сбросить трафик", callback_data=f"reset_user_traffic_{user_uuid}")
])
else:
buttons.append([
InlineKeyboardButton(text="✅ Включить", callback_data=f"enable_user_{user_uuid}"),
InlineKeyboardButton(text="🔄 Сбросить трафик", callback_data=f"reset_user_traffic_{user_uuid}")
])
buttons.extend([
[InlineKeyboardButton(text="🔍 Новый поиск", callback_data="search_user")],
[InlineKeyboardButton(text="📋 Все пользователи", callback_data="list_users")],
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_users")]
buttons.append([
InlineKeyboardButton(text="📅 Изменить срок", callback_data=f"edit_user_expiry_{user_uuid}"),
InlineKeyboardButton(text="📊 Изменить трафик", callback_data=f"edit_user_traffic_{user_uuid}")
])
buttons.append([
InlineKeyboardButton(text="📈 Статистика", callback_data=f"user_usage_stats_{user_uuid}"),
InlineKeyboardButton(text="🔄 Обновить", callback_data=f"refresh_user_{user_uuid}")
])
buttons.append([
InlineKeyboardButton(text="🔍 Новый поиск", callback_data="search_user_uuid"),
InlineKeyboardButton(text="🔙 Назад", callback_data="system_users")
])
return InlineKeyboardMarkup(inline_keyboard=buttons)
@admin_router.callback_query(F.data == "users_stats")
async def users_stats_callback(callback: CallbackQuery, user: User, db: Database, **kwargs):
if not await check_admin_access(callback, user):
return
try:
# Получаем расширенную статистику
stats = await get_extended_user_stats(db)
text = f"📊 **Статистика пользователей**\n\n"
text += f"**Общие показатели:**\n"
text += f"Всего пользователей: **{stats['total_users']}**\n"
text += f"• Администраторов: **{stats['admin_count']}**\n"
text += f"• Обычных пользователей: **{stats['regular_count']}**\n\n"
text += f"**По балансу:**\n"
text += f"С нулевым балансом: **{stats['zero_balance']}**\n"
text += f"С балансом 1-100₽: **{stats['low_balance']}**\n"
text += f"С балансом 101-1000₽: **{stats['medium_balance']}**\n"
text += f"С балансом >1000₽: **{stats['high_balance']}**\n\n"
text += f"**Активность:**\n"
text += f"• Использовали триал: **{stats['trial_used']}**\n"
text += f"С активными подписками: **{stats['active_subscriptions']}**\n"
text += f"• Общий баланс всех: **{stats['total_balance']:.2f}₽**\n\n"
text += f"**Регистрации:**\n"
text += f"За сегодня: **{stats['registered_today']}**\n"
text += f"За неделю: **{stats['registered_week']}**\n"
text += f"За месяц: **{stats['registered_month']}**\n\n"
text += f"🕐 _Обновлено: {datetime.now().strftime('%H:%M:%S')}_"
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔄 Обновить", callback_data="users_stats")],
[InlineKeyboardButton(text="📋 Список пользователей", callback_data="list_users")],
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_users")]
])
await callback.message.edit_text(
text,
reply_markup=keyboard,
parse_mode='Markdown'
)
except Exception as e:
logger.error(f"Error getting user stats: {e}")
await callback.answer("❌ Ошибка получения статистики", show_alert=True)
async def get_extended_user_stats(db: Database) -> dict:
async with db.session_factory() as session:
try:
from sqlalchemy import select, func, and_
from datetime import datetime, timedelta
total_users = await session.execute(select(func.count(User.id)))
total_users = total_users.scalar() or 0
admin_count = await session.execute(
select(func.count(User.id)).where(User.is_admin == True)
)
admin_count = admin_count.scalar() or 0
zero_balance = await session.execute(
select(func.count(User.id)).where(User.balance == 0)
)
zero_balance = zero_balance.scalar() or 0
low_balance = await session.execute(
select(func.count(User.id)).where(
and_(User.balance > 0, User.balance <= 100)
)
)
low_balance = low_balance.scalar() or 0
medium_balance = await session.execute(
select(func.count(User.id)).where(
and_(User.balance > 100, User.balance <= 1000)
)
)
medium_balance = medium_balance.scalar() or 0
high_balance = await session.execute(
select(func.count(User.id)).where(User.balance > 1000)
)
high_balance = high_balance.scalar() or 0
total_balance = await session.execute(select(func.sum(User.balance)))
total_balance = total_balance.scalar() or 0.0
trial_used = await session.execute(
select(func.count(User.id)).where(User.is_trial_used == True)
)
trial_used = trial_used.scalar() or 0
active_subs = await session.execute(
select(func.count(func.distinct(UserSubscription.user_id)))
.where(
and_(
UserSubscription.is_active == True,
UserSubscription.expires_at > datetime.utcnow()
)
)
)
active_subs = active_subs.scalar() or 0
today = datetime.now().date()
week_ago = today - timedelta(days=7)
month_ago = today - timedelta(days=30)
registered_today = await session.execute(
select(func.count(User.id)).where(
func.date(User.created_at) == today
)
)
registered_today = registered_today.scalar() or 0
registered_week = await session.execute(
select(func.count(User.id)).where(
User.created_at >= week_ago
)
)
registered_week = registered_week.scalar() or 0
registered_month = await session.execute(
select(func.count(User.id)).where(
User.created_at >= month_ago
)
)
registered_month = registered_month.scalar() or 0
return {
'total_users': total_users,
'admin_count': admin_count,
'regular_count': total_users - admin_count,
'zero_balance': zero_balance,
'low_balance': low_balance,
'medium_balance': medium_balance,
'high_balance': high_balance,
'total_balance': total_balance,
'trial_used': trial_used,
'active_subscriptions': active_subs,
'registered_today': registered_today,
'registered_week': registered_week,
'registered_month': registered_month
}
except Exception as e:
logger.error(f"Error getting extended user stats: {e}")
return {
'total_users': 0, 'admin_count': 0, 'regular_count': 0,
'zero_balance': 0, 'low_balance': 0, 'medium_balance': 0, 'high_balance': 0,
'total_balance': 0.0, 'trial_used': 0, 'active_subscriptions': 0,
'registered_today': 0, 'registered_week': 0, 'registered_month': 0
}
@admin_router.callback_query(F.data.startswith("user_balance_"))
async def user_balance_callback(callback: CallbackQuery, user: User, db: Database, **kwargs):
if not await check_admin_access(callback, user):
return
try:
user_id = int(callback.data.split("_")[2])
target_user = await db.get_user_by_telegram_id(user_id)
if not target_user:
await callback.answer("❌ Пользователь не найден", show_alert=True)
return
payments = await db.get_user_payments(user_id)
recent_payments = payments[:5] if payments else []
text = f"💰 **Управление балансом пользователя**\n\n"
text += f"👤 **{target_user.first_name or 'Без имени'}**\n"
text += f"@{target_user.username or 'без username'} • ID: `{target_user.telegram_id}`\n\n"
text += f"💳 **Текущий баланс: {target_user.balance:.2f}₽**\n\n"
if recent_payments:
text += f"📊 **Последние операции:**\n"
for payment in recent_payments:
status_emoji = "" if payment.status == 'completed' else "" if payment.status == 'pending' else ""
date_str = format_datetime(payment.created_at, user.language)
amount_str = f"+{payment.amount}" if payment.amount > 0 else str(payment.amount)
text += f"{status_emoji} {amount_str}₽ • {date_str}\n"
text += "\n"
else:
text += f"📊 История платежей пуста\n\n"
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[
InlineKeyboardButton(text="💸 Добавить баланс", callback_data=f"admin_add_balance_to_{user_id}"),
InlineKeyboardButton(text="💳 Списать баланс", callback_data=f"admin_subtract_balance_{user_id}")
],
[
InlineKeyboardButton(text="📊 Вся история", callback_data=f"user_payments_{user_id}"),
InlineKeyboardButton(text="🔄 Обновить", callback_data=f"user_balance_{user_id}")
],
[InlineKeyboardButton(text="🔙 К пользователю", callback_data=f"user_detail_{user_id}")]
])
await callback.message.edit_text(
text,
reply_markup=keyboard,
parse_mode='Markdown'
)
except Exception as e:
logger.error(f"Error showing user balance: {e}")
await callback.answer("❌ Ошибка загрузки баланса", show_alert=True)
@admin_router.callback_query(F.data.startswith("user_subs_"))
async def user_subs_callback(callback: CallbackQuery, user: User, db: Database, **kwargs):
if not await check_admin_access(callback, user):
return
try:
user_id = int(callback.data.split("_")[2])
target_user = await db.get_user_by_telegram_id(user_id)
if not target_user:
await callback.answer("❌ Пользователь не найден", show_alert=True)
return
# Получаем подписки пользователя
user_subs = await db.get_user_subscriptions(user_id)
text = f"📋 **Подписки пользователя**\n\n"
text += f"👤 **{target_user.first_name or 'Без имени'}**\n"
text += f"@{target_user.username or 'без username'} • ID: `{target_user.telegram_id}`\n\n"
if user_subs:
active_count = 0
expired_count = 0
current_time = datetime.utcnow()
text += f"📊 **Всего подписок: {len(user_subs)}**\n\n"
for i, sub in enumerate(user_subs, 1):
# Получаем информацию о тарифе
subscription = await db.get_subscription_by_id(sub.subscription_id)
sub_name = subscription.name if subscription else "Неизвестный тариф"
# Определяем статус
if sub.is_active and sub.expires_at > current_time:
status_emoji = "🟢"
status_text = "Активна"
active_count += 1
days_left = (sub.expires_at - current_time).days
status_text += f" ({days_left}д)"
else:
status_emoji = "🔴"
status_text = "Истекла"
expired_count += 1
text += f"**{i}.** {status_emoji} **{sub_name}**\n"
text += f"{status_text}\n"
text += f" └ До: {format_datetime(sub.expires_at, user.language)}\n\n"
text += f"📈 Активных: **{active_count}** • Истекших: **{expired_count}**\n"
else:
text += f"У пользователя нет подписок\n"
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[
InlineKeyboardButton(text="🛒 Создать подписку", callback_data=f"admin_create_user_sub_{user_id}"),
InlineKeyboardButton(text="📋 Все подписки", callback_data="admin_user_subscriptions_all")
],
[
InlineKeyboardButton(text="🔄 Обновить", callback_data=f"user_subs_{user_id}"),
InlineKeyboardButton(text="🔙 К пользователю", callback_data=f"user_detail_{user_id}")
]
])
await callback.message.edit_text(
text,
reply_markup=keyboard,
parse_mode='Markdown'
)
except Exception as e:
logger.error(f"Error showing user subscriptions: {e}")
await callback.answer("❌ Ошибка загрузки подписок", show_alert=True)
@admin_router.callback_query(F.data.startswith("edit_user_expiry_"))
async def edit_user_expiry_callback(callback: CallbackQuery, user: User, state: FSMContext, **kwargs):
if not await check_admin_access(callback, user):
@@ -8198,77 +7861,6 @@ async def autopay_subscriptions_list_callback(callback: CallbackQuery, user: Use
reply_markup=back_keyboard("admin_autopay", user.language)
)
@admin_router.callback_query(F.data.startswith("user_detail_"))
async def user_detail_callback(callback: CallbackQuery, user: User, db: Database, **kwargs):
if not await check_admin_access(callback, user):
return
try:
user_id = int(callback.data.split("_")[2])
target_user = await db.get_user_by_telegram_id(user_id)
if not target_user:
await callback.answer("❌ Пользователь не найден", show_alert=True)
return
user_subs = await db.get_user_subscriptions(user_id)
user_payments = await db.get_user_payments(user_id)
active_subs = [s for s in user_subs if s.is_active and s.expires_at > datetime.utcnow()]
total_spent = sum(p.amount for p in user_payments if p.status == 'completed')
text = f"👤 **Детальная информация о пользователе**\n\n"
text += f"**Основная информация:**\n"
text += f"• Имя: {target_user.first_name or 'Не указано'}\n"
text += f"• Username: @{target_user.username or 'отсутствует'}\n"
text += f"• ID: `{target_user.telegram_id}`\n"
text += f"• Статус: {'👑 Администратор' if target_user.is_admin else '👤 Пользователь'}\n"
text += f"• Язык: {target_user.language.upper() if target_user.language else 'RU'}\n\n"
text += f"**Финансы:**\n"
text += f"• Текущий баланс: **{target_user.balance:.2f}₽**\n"
text += f"Всего потрачено: **{total_spent:.2f}₽**\n"
text += f"• Количество платежей: {len(user_payments)}\n\n"
text += f"**Подписки:**\n"
text += f"Всего подписок: {len(user_subs)}\n"
text += f"• Активных: {len(active_subs)}\n"
text += f"• Использовал триал: {'' if target_user.is_trial_used else ''}\n\n"
text += f"**Даты:**\n"
text += f"• Регистрация: {format_datetime(target_user.created_at, user.language)}\n"
keyboard = create_user_detail_keyboard(user_id, user.language)
await callback.message.edit_text(
text,
reply_markup=keyboard,
parse_mode='Markdown'
)
except Exception as e:
logger.error(f"Error showing user detail: {e}")
await callback.answer("❌ Ошибка загрузки данных пользователя", show_alert=True)
def create_user_detail_keyboard(user_id: int, language: str = 'ru') -> InlineKeyboardMarkup:
buttons = [
[
InlineKeyboardButton(text="💰 Управление балансом", callback_data=f"user_balance_{user_id}"),
InlineKeyboardButton(text="📋 Подписки", callback_data=f"user_subs_{user_id}")
],
[
InlineKeyboardButton(text="💳 История платежей", callback_data=f"user_payments_{user_id}"),
InlineKeyboardButton(text="✉️ Отправить сообщение", callback_data=f"user_message_{user_id}")
],
[
InlineKeyboardButton(text="🔧 Управление", callback_data=f"user_manage_{user_id}"),
InlineKeyboardButton(text="🔄 Обновить", callback_data=f"user_detail_{user_id}")
],
[InlineKeyboardButton(text="🔙 К списку пользователей", callback_data="list_users")]
]
return InlineKeyboardMarkup(inline_keyboard=buttons)
@admin_router.callback_query(F.data.startswith("autopay_user_detail_"))
async def autopay_user_detail_callback(callback: CallbackQuery, user: User, **kwargs):
if not await check_admin_access(callback, user):
@@ -8388,174 +7980,6 @@ async def admin_user_subscriptions_filters_callback(callback: CallbackQuery, use
logger.error(f"Error showing subscriptions filters: {e}")
await callback.answer("❌ Ошибка загрузки фильтров", show_alert=True)
@admin_router.callback_query(F.data.startswith("quick_add_balance_"))
async def quick_add_balance_callback(callback: CallbackQuery, user: User, db: Database, **kwargs):
"""Быстрое добавление баланса пользователю"""
if not await check_admin_access(callback, user):
return
try:
parts = callback.data.split("_")
user_id = int(parts[3])
amount = float(parts[4])
target_user = await db.get_user_by_telegram_id(user_id)
if not target_user:
await callback.answer("❌ Пользователь не найден", show_alert=True)
return
# Добавляем баланс
success = await db.add_balance(user_id, amount)
if success:
# Создаем запись о платеже
await db.create_payment(
user_id=user_id,
amount=amount,
payment_type='admin_topup',
description=f'Быстрое пополнение администратором (ID: {user.telegram_id})',
status='completed'
)
# Обрабатываем реферальные вознаграждения
bot = kwargs.get('bot')
if bot:
try:
from referral_system import process_referral_rewards
await process_referral_rewards(
user_id, amount, None, db, bot, payment_type='admin_topup'
)
except ImportError:
pass
await callback.answer(f"✅ Добавлено {amount}₽ пользователю", show_alert=True)
# Уведомляем пользователя
if bot:
try:
await bot.send_message(
user_id,
f"💰 Ваш баланс пополнен на {amount}₽ администратором"
)
except Exception as e:
logger.warning(f"Failed to notify user {user_id}: {e}")
log_user_action(user.telegram_id, "quick_balance_add", f"User: {user_id}, Amount: {amount}")
# Обновляем отображение баланса
await user_balance_callback(callback, user, db, **kwargs)
else:
await callback.answer("❌ Ошибка добавления баланса", show_alert=True)
except Exception as e:
logger.error(f"Error in quick add balance: {e}")
await callback.answer("❌ Ошибка операции", show_alert=True)
@admin_router.callback_query(F.data.startswith("user_detailed_stats_"))
async def user_detailed_stats_callback(callback: CallbackQuery, user: User, db: Database, **kwargs):
"""Детальная статистика пользователя"""
if not await check_admin_access(callback, user):
return
try:
user_id = int(callback.data.split("_")[3])
target_user = await db.get_user_by_telegram_id(user_id)
if not target_user:
await callback.answer("❌ Пользователь не найден", show_alert=True)
return
# Собираем подробную статистику
user_subs = await db.get_user_subscriptions(user_id)
user_payments = await db.get_user_payments(user_id)
# Анализируем подписки
current_time = datetime.utcnow()
active_subs = [s for s in user_subs if s.is_active and s.expires_at > current_time]
expired_subs = [s for s in user_subs if not s.is_active or s.expires_at <= current_time]
# Анализируем платежи
completed_payments = [p for p in user_payments if p.status == 'completed']
pending_payments = [p for p in user_payments if p.status == 'pending']
total_spent = sum(p.amount for p in completed_payments)
avg_payment = total_spent / len(completed_payments) if completed_payments else 0
# Анализируем активность по месяцам
from collections import defaultdict
monthly_spending = defaultdict(float)
for payment in completed_payments:
month_key = payment.created_at.strftime("%Y-%m")
monthly_spending[month_key] += payment.amount
text = f"📊 **Детальная статистика пользователя**\n\n"
text += f"👤 **{target_user.first_name or 'Без имени'}**\n"
text += f"@{target_user.username or 'без username'} • ID: `{target_user.telegram_id}`\n\n"
text += f"💰 **Финансовая активность:**\n"
text += f"• Текущий баланс: **{target_user.balance:.2f}₽**\n"
text += f"Всего потрачено: **{total_spent:.2f}₽**\n"
text += f"• Средний платеж: **{avg_payment:.2f}₽**\n"
text += f"• Завершенных платежей: **{len(completed_payments)}**\n"
text += f"• Ожидающих платежей: **{len(pending_payments)}**\n\n"
text += f"📋 **Статистика подписок:**\n"
text += f"Всего подписок: **{len(user_subs)}**\n"
text += f"• Активных: **{len(active_subs)}**\n"
text += f"• Истекших: **{len(expired_subs)}**\n"
text += f"• Использовал триал: **{'' if target_user.is_trial_used else ''}**\n\n"
if monthly_spending:
text += f"📈 **Активность по месяцам (последние 3):**\n"
sorted_months = sorted(monthly_spending.items(), reverse=True)[:3]
for month, amount in sorted_months:
text += f"{month}: **{amount:.2f}₽**\n"
text += "\n"
text += f"📅 **Временные метки:**\n"
text += f"• Регистрация: {format_datetime(target_user.created_at, user.language)}\n"
if user_payments:
last_payment = max(user_payments, key=lambda p: p.created_at)
text += f"• Последний платеж: {format_datetime(last_payment.created_at, user.language)}\n"
if active_subs:
nearest_expiry = min(active_subs, key=lambda s: s.expires_at)
text += f"• Ближайшее истечение: {format_datetime(nearest_expiry.expires_at, user.language)}\n"
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[
InlineKeyboardButton(text="💳 История платежей", callback_data=f"user_payments_{user_id}"),
InlineKeyboardButton(text="📋 Все подписки", callback_data=f"user_subs_{user_id}")
],
[
InlineKeyboardButton(text="📊 Экспорт данных", callback_data=f"export_user_data_{user_id}"),
InlineKeyboardButton(text="🔄 Обновить", callback_data=f"user_detailed_stats_{user_id}")
],
[InlineKeyboardButton(text="🔙 К управлению", callback_data=f"user_manage_{user_id}")]
])
await callback.message.edit_text(
text,
reply_markup=keyboard,
parse_mode='Markdown'
)
except Exception as e:
logger.error(f"Error getting detailed user stats: {e}")
await callback.answer("❌ Ошибка загрузки статистики", show_alert=True)
# Вспомогательная функция для логирования действий администратора
def log_user_action(admin_id: int, action: str, details: str = ""):
"""Логирует действия администратора для аудита"""
try:
import logging
audit_logger = logging.getLogger('admin_audit')
audit_logger.info(f"Admin {admin_id} performed {action}: {details}")
except Exception as e:
logger.warning(f"Failed to log admin action: {e}")
@admin_router.callback_query(F.data.startswith("filter_subs_"))
async def filter_subscriptions_callback(callback: CallbackQuery, user: User, **kwargs):
if not await check_admin_access(callback, user):