Files
new_lottery_bot/admin_panel.py
2025-11-12 20:57:36 +09:00

2304 lines
102 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Расширенная админ-панель для управления розыгрышами
"""
from aiogram import Router, F
from aiogram.types import (
CallbackQuery, Message, InlineKeyboardButton, InlineKeyboardMarkup
)
from aiogram.filters import StateFilter
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from sqlalchemy.ext.asyncio import AsyncSession
from datetime import datetime, timedelta
import json
from database import async_session_maker
from services import UserService, LotteryService, ParticipationService
from config import ADMIN_IDS
from models import User
# Состояния для админки
class AdminStates(StatesGroup):
# Создание розыгрыша
lottery_title = State()
lottery_description = State()
lottery_prizes = State()
lottery_confirm = State()
# Управление участниками
add_participant_lottery = State()
add_participant_user = State()
add_participant_bulk = State()
add_participant_bulk_accounts = State()
remove_participant_lottery = State()
remove_participant_user = State()
remove_participant_bulk = State()
remove_participant_bulk_accounts = State()
participant_search = State()
# Установка победителей
set_winner_lottery = State()
set_winner_place = State()
set_winner_user = State()
# Редактирование розыгрыша
edit_lottery_select = State()
edit_lottery_field = State()
edit_lottery_value = State()
# Настройки отображения победителей
lottery_display_type_select = State()
lottery_display_type_set = State()
admin_router = Router()
def is_admin(user_id: int) -> bool:
"""Проверка прав администратора"""
return user_id in ADMIN_IDS
def get_admin_main_keyboard() -> InlineKeyboardMarkup:
"""Главная админ-панель"""
buttons = [
[InlineKeyboardButton(text="🎲 Управление розыгрышами", callback_data="admin_lotteries")],
[InlineKeyboardButton(text="👥 Управление участниками", callback_data="admin_participants")],
[InlineKeyboardButton(text="👑 Управление победителями", callback_data="admin_winners")],
[InlineKeyboardButton(text="📊 Статистика", callback_data="admin_stats")],
[InlineKeyboardButton(text="⚙️ Настройки", callback_data="admin_settings")],
[InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_main")]
]
return InlineKeyboardMarkup(inline_keyboard=buttons)
def get_lottery_management_keyboard() -> InlineKeyboardMarkup:
"""Клавиатура управления розыгрышами"""
buttons = [
[InlineKeyboardButton(text=" Создать розыгрыш", callback_data="admin_create_lottery")],
[InlineKeyboardButton(text="📝 Редактировать розыгрыш", callback_data="admin_edit_lottery")],
[InlineKeyboardButton(text="🎭 Настройка отображения победителей", callback_data="admin_winner_display_settings")],
[InlineKeyboardButton(text="📋 Список всех розыгрышей", callback_data="admin_list_all_lotteries")],
[InlineKeyboardButton(text="🏁 Завершить розыгрыш", callback_data="admin_finish_lottery")],
[InlineKeyboardButton(text="🗑️ Удалить розыгрыш", callback_data="admin_delete_lottery")],
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_panel")]
]
return InlineKeyboardMarkup(inline_keyboard=buttons)
def get_participant_management_keyboard() -> InlineKeyboardMarkup:
"""Клавиатура управления участниками"""
buttons = [
[InlineKeyboardButton(text=" Добавить участника", callback_data="admin_add_participant")],
[
InlineKeyboardButton(text="📥 Массовое добавление (ID)", callback_data="admin_bulk_add_participant"),
InlineKeyboardButton(text="🏦 Массовое добавление (счета)", callback_data="admin_bulk_add_accounts")
],
[InlineKeyboardButton(text=" Удалить участника", callback_data="admin_remove_participant")],
[
InlineKeyboardButton(text="📤 Массовое удаление (ID)", callback_data="admin_bulk_remove_participant"),
InlineKeyboardButton(text="🏦 Массовое удаление (счета)", callback_data="admin_bulk_remove_accounts")
],
[InlineKeyboardButton(text="👥 Все участники", callback_data="admin_list_all_participants")],
[InlineKeyboardButton(text="🔍 Поиск участников", callback_data="admin_search_participants")],
[InlineKeyboardButton(text="📊 Участники по розыгрышам", callback_data="admin_participants_by_lottery")],
[InlineKeyboardButton(text="📈 Отчет по участникам", callback_data="admin_participants_report")],
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_panel")]
]
return InlineKeyboardMarkup(inline_keyboard=buttons)
def get_winner_management_keyboard() -> InlineKeyboardMarkup:
"""Клавиатура управления победителями"""
buttons = [
[InlineKeyboardButton(text="👑 Установить победителя", callback_data="admin_set_manual_winner")],
[InlineKeyboardButton(text="📝 Изменить победителя", callback_data="admin_edit_winner")],
[InlineKeyboardButton(text="❌ Удалить победителя", callback_data="admin_remove_winner")],
[InlineKeyboardButton(text="📋 Список победителей", callback_data="admin_list_winners")],
[InlineKeyboardButton(text="🎲 Провести розыгрыш", callback_data="admin_conduct_draw")],
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_panel")]
]
return InlineKeyboardMarkup(inline_keyboard=buttons)
@admin_router.callback_query(F.data == "admin_panel")
async def show_admin_panel(callback: CallbackQuery):
"""Показать админ-панель"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
async with async_session_maker() as session:
# Быстрая статистика
from sqlalchemy import select, func
from models import User, Lottery, Participation
users_count = await session.scalar(select(func.count(User.id)))
lotteries_count = await session.scalar(select(func.count(Lottery.id)))
active_lotteries = await session.scalar(
select(func.count(Lottery.id))
.where(Lottery.is_active == True, Lottery.is_completed == False)
)
total_participations = await session.scalar(select(func.count(Participation.id)))
text = f"🔧 Админ-панель\n\n"
text += f"📊 Быстрая статистика:\n"
text += f"👥 Пользователей: {users_count}\n"
text += f"🎲 Всего розыгрышей: {lotteries_count}\n"
text += f"🟢 Активных: {active_lotteries}\n"
text += f"🎫 Участий: {total_participations}\n\n"
text += "Выберите раздел для управления:"
await callback.message.edit_text(text, reply_markup=get_admin_main_keyboard())
# ======================
# УПРАВЛЕНИЕ РОЗЫГРЫШАМИ
# ======================
@admin_router.callback_query(F.data == "admin_lotteries")
async def show_lottery_management(callback: CallbackQuery):
"""Управление розыгрышами"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
text = "🎲 Управление розыгрышами\n\n"
text += "Здесь вы можете создавать, редактировать и управлять розыгрышами.\n\n"
text += "Выберите действие:"
await callback.message.edit_text(text, reply_markup=get_lottery_management_keyboard())
@admin_router.callback_query(F.data == "admin_create_lottery")
async def start_create_lottery(callback: CallbackQuery, state: FSMContext):
"""Начать создание розыгрыша"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
text = "📝 Создание нового розыгрыша\n\n"
text += "Шаг 1 из 4\n\n"
text += "Введите название розыгрыша:"
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="❌ Отмена", callback_data="admin_lotteries")]
])
)
await state.set_state(AdminStates.lottery_title)
@admin_router.message(StateFilter(AdminStates.lottery_title))
async def process_lottery_title(message: Message, state: FSMContext):
"""Обработка названия розыгрыша"""
if not is_admin(message.from_user.id):
await message.answer("❌ Недостаточно прав")
return
await state.update_data(title=message.text)
text = f"📝 Создание нового розыгрыша\n\n"
text += f"Шаг 2 из 4\n\n"
text += f"✅ Название: {message.text}\n\n"
text += f"Введите описание розыгрыша (или '-' для пропуска):"
await message.answer(text)
await state.set_state(AdminStates.lottery_description)
@admin_router.message(StateFilter(AdminStates.lottery_description))
async def process_lottery_description(message: Message, state: FSMContext):
"""Обработка описания розыгрыша"""
if not is_admin(message.from_user.id):
await message.answer("❌ Недостаточно прав")
return
description = None if message.text == "-" else message.text
await state.update_data(description=description)
data = await state.get_data()
text = f"📝 Создание нового розыгрыша\n\n"
text += f"Шаг 3 из 4\n\n"
text += f"✅ Название: {data['title']}\n"
text += f"✅ Описание: {description or 'Не указано'}\n\n"
text += f"Введите призы (каждый с новой строки):\n\n"
text += f"Пример:\n"
text += f"🥇 iPhone 15 Pro\n"
text += f"🥈 MacBook Air\n"
text += f"🥉 AirPods Pro\n"
text += f"🏆 10,000 рублей"
await message.answer(text)
await state.set_state(AdminStates.lottery_prizes)
@admin_router.message(StateFilter(AdminStates.lottery_prizes))
async def process_lottery_prizes(message: Message, state: FSMContext):
"""Обработка призов розыгрыша"""
if not is_admin(message.from_user.id):
await message.answer("❌ Недостаточно прав")
return
prizes = [prize.strip() for prize in message.text.split('\n') if prize.strip()]
await state.update_data(prizes=prizes)
data = await state.get_data()
text = f"📝 Создание нового розыгрыша\n\n"
text += f"Шаг 4 из 4 - Подтверждение\n\n"
text += f"🎯 Название: {data['title']}\n"
text += f"📋 Описание: {data['description'] or 'Не указано'}\n\n"
text += f"🏆 Призы:\n"
for i, prize in enumerate(prizes, 1):
text += f"{i}. {prize}\n"
text += f"\n✅ Подтвердите создание розыгрыша:"
await message.answer(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="✅ Создать", callback_data="confirm_create_lottery")],
[InlineKeyboardButton(text="❌ Отмена", callback_data="admin_lotteries")]
])
)
await state.set_state(AdminStates.lottery_confirm)
@admin_router.callback_query(F.data == "confirm_create_lottery", StateFilter(AdminStates.lottery_confirm))
async def confirm_create_lottery(callback: CallbackQuery, state: FSMContext):
"""Подтверждение создания розыгрыша"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
data = await state.get_data()
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
lottery = await LotteryService.create_lottery(
session,
title=data['title'],
description=data['description'],
prizes=data['prizes'],
creator_id=user.id
)
await state.clear()
text = f"✅ Розыгрыш успешно создан!\n\n"
text += f"🆔 ID: {lottery.id}\n"
text += f"🎯 Название: {lottery.title}\n"
text += f"📅 Создан: {lottery.created_at.strftime('%d.%m.%Y %H:%M')}\n\n"
text += f"Розыгрыш доступен для участников."
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🎲 К управлению розыгрышами", callback_data="admin_lotteries")],
[InlineKeyboardButton(text="🏠 В главное меню", callback_data="back_to_main")]
])
)
@admin_router.callback_query(F.data == "admin_list_all_lotteries")
async def list_all_lotteries(callback: CallbackQuery):
"""Список всех розыгрышей"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
async with async_session_maker() as session:
from sqlalchemy import select
from models import Lottery
result = await session.execute(
select(Lottery).order_by(Lottery.created_at.desc())
)
lotteries = result.scalars().all()
if not lotteries:
text = "📋 Розыгрышей пока нет"
buttons = [[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")]]
else:
text = f"📋 Все розыгрыши ({len(lotteries)}):\n\n"
buttons = []
for lottery in lotteries[:10]: # Показываем первые 10
status = "🟢" if lottery.is_active and not lottery.is_completed else "" if lottery.is_completed else "🔴"
async with async_session_maker() as session:
participants_count = await ParticipationService.get_participants_count(
session, lottery.id
)
text += f"{status} {lottery.title}\n"
text += f" ID: {lottery.id} | Участников: {participants_count}\n"
text += f" Создан: {lottery.created_at.strftime('%d.%m %H:%M')}\n\n"
buttons.append([
InlineKeyboardButton(
text=f"📝 {lottery.title[:25]}..." if len(lottery.title) > 25 else lottery.title,
callback_data=f"admin_lottery_detail_{lottery.id}"
)
])
if len(lotteries) > 10:
text += f"... и еще {len(lotteries) - 10} розыгрышей"
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")])
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data.startswith("admin_lottery_detail_"))
async def show_lottery_detail(callback: CallbackQuery):
"""Детальная информация о розыгрыше"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
lottery_id = int(callback.data.split("_")[-1])
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
if not lottery:
await callback.answer("Розыгрыш не найден", show_alert=True)
return
participants_count = await ParticipationService.get_participants_count(session, lottery_id)
winners = await LotteryService.get_winners(session, lottery_id) if lottery.is_completed else []
status_emoji = "🟢" if lottery.is_active and not lottery.is_completed else "" if lottery.is_completed else "🔴"
status_text = "Активен" if lottery.is_active and not lottery.is_completed else "Завершен" if lottery.is_completed else "Неактивен"
text = f"🎲 Детали розыгрыша\n\n"
text += f"🆔 ID: {lottery.id}\n"
text += f"🎯 Название: {lottery.title}\n"
text += f"📋 Описание: {lottery.description or 'Не указано'}\n"
text += f"{status_emoji} Статус: {status_text}\n"
text += f"👥 Участников: {participants_count}\n"
text += f"📅 Создан: {lottery.created_at.strftime('%d.%m.%Y %H:%M')}\n\n"
if lottery.prizes:
text += f"🏆 Призы:\n"
for i, prize in enumerate(lottery.prizes, 1):
text += f"{i}. {prize}\n"
text += "\n"
# Ручные победители
if lottery.manual_winners:
text += f"👑 Предустановленные победители:\n"
for place, telegram_id in lottery.manual_winners.items():
async with async_session_maker() as session:
winner_user = await UserService.get_user_by_telegram_id(session, telegram_id)
name = winner_user.username if winner_user and winner_user.username else str(telegram_id)
text += f"{place} место: @{name}\n"
text += "\n"
# Результаты розыгрыша
if lottery.is_completed and winners:
text += f"🏆 Результаты:\n"
for winner in winners:
manual_mark = " 👑" if winner.is_manual else ""
username = f"@{winner.user.username}" if winner.user.username else winner.user.first_name
text += f"{winner.place}. {username}{manual_mark}\n"
buttons = []
if not lottery.is_completed:
buttons.extend([
[InlineKeyboardButton(text="👑 Установить победителя", callback_data=f"admin_set_winner_{lottery_id}")],
[InlineKeyboardButton(text="🎲 Провести розыгрыш", callback_data=f"admin_conduct_{lottery_id}")],
])
buttons.extend([
[InlineKeyboardButton(text="📝 Редактировать", callback_data=f"admin_edit_{lottery_id}")],
[InlineKeyboardButton(text="👥 Участники", callback_data=f"admin_participants_{lottery_id}")],
[InlineKeyboardButton(text="🔙 К списку", callback_data="admin_list_all_lotteries")]
])
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
# ======================
# УПРАВЛЕНИЕ УЧАСТНИКАМИ
# ======================
@admin_router.callback_query(F.data == "admin_participants")
async def show_participant_management(callback: CallbackQuery):
"""Управление участниками"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
text = "👥 Управление участниками\n\n"
text += "Здесь вы можете добавлять и удалять участников розыгрышей.\n\n"
text += "Выберите действие:"
await callback.message.edit_text(text, reply_markup=get_participant_management_keyboard())
@admin_router.callback_query(F.data.startswith("admin_participants_"))
async def show_lottery_participants(callback: CallbackQuery):
"""Показать участников конкретного розыгрыша"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
lottery_id = int(callback.data.split("_")[-1])
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
if not lottery:
await callback.answer("Розыгрыш не найден", show_alert=True)
return
text = f"👥 Участники розыгрыша\n"
text += f"🎯 {lottery.title}\n\n"
if not lottery.participations:
text += "Участников пока нет"
buttons = [[InlineKeyboardButton(text="🔙 Назад", callback_data=f"admin_lottery_detail_{lottery_id}")]]
else:
text += f"Всего участников: {len(lottery.participations)}\n\n"
for i, participation in enumerate(lottery.participations[:20], 1): # Показываем первых 20
user = participation.user
username = f"@{user.username}" if user.username else "Нет username"
text += f"{i}. {user.first_name} {user.last_name or ''}\n"
text += f" {username} | ID: {user.telegram_id}\n"
text += f" Участвует с: {participation.created_at.strftime('%d.%m %H:%M')}\n\n"
if len(lottery.participations) > 20:
text += f"... и еще {len(lottery.participations) - 20} участников"
buttons = [
[InlineKeyboardButton(text=" Добавить участника", callback_data=f"admin_add_to_{lottery_id}")],
[InlineKeyboardButton(text=" Удалить участника", callback_data=f"admin_remove_from_{lottery_id}")],
[InlineKeyboardButton(text="🔙 Назад", callback_data=f"admin_lottery_detail_{lottery_id}")]
]
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
# ======================
# НОВЫЕ ХЭНДЛЕРЫ ДЛЯ УПРАВЛЕНИЯ УЧАСТНИКАМИ
# ======================
@admin_router.callback_query(F.data == "admin_add_participant")
async def start_add_participant(callback: CallbackQuery, state: FSMContext):
"""Начать добавление участника"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
async with async_session_maker() as session:
lotteries = await LotteryService.get_active_lotteries(session)
if not lotteries:
await callback.message.edit_text(
"❌ Нет активных розыгрышей",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")]
])
)
return
text = " Добавление участника\n\n"
text += "Выберите розыгрыш:\n\n"
buttons = []
for lottery in lotteries:
async with async_session_maker() as session:
count = await ParticipationService.get_participants_count(session, lottery.id)
text += f"🎯 {lottery.title} (участников: {count})\n"
buttons.append([
InlineKeyboardButton(
text=f"🎯 {lottery.title[:35]}...",
callback_data=f"admin_add_part_to_{lottery.id}"
)
])
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")])
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data.startswith("admin_add_part_to_"))
async def choose_user_to_add(callback: CallbackQuery, state: FSMContext):
"""Выбор пользователя для добавления"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
lottery_id = int(callback.data.split("_")[-1])
await state.update_data(add_participant_lottery_id=lottery_id)
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
text = f" Добавление в: {lottery.title}\n\n"
text += "Введите Telegram ID или username пользователя:\n\n"
text += "Примеры:\n"
text += "• @username\n"
text += "• 123456789"
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="❌ Отмена", callback_data="admin_add_participant")]
])
)
await state.set_state(AdminStates.add_participant_user)
@admin_router.message(StateFilter(AdminStates.add_participant_user))
async def process_add_participant(message: Message, state: FSMContext):
"""Обработка добавления участника"""
if not is_admin(message.from_user.id):
await message.answer("❌ Недостаточно прав")
return
data = await state.get_data()
lottery_id = data['add_participant_lottery_id']
user_input = message.text.strip()
async with async_session_maker() as session:
# Ищем пользователя
user = None
if user_input.startswith('@'):
username = user_input[1:]
user = await UserService.get_user_by_username(session, username)
elif user_input.isdigit():
telegram_id = int(user_input)
user = await UserService.get_user_by_telegram_id(session, telegram_id)
if not user:
await message.answer(
"❌ Пользователь не найден в системе.\n"
"Пользователь должен сначала запустить бота командой /start"
)
return
# Добавляем участника
success = await ParticipationService.add_participant(session, lottery_id, user.id)
lottery = await LotteryService.get_lottery(session, lottery_id)
await state.clear()
if success:
username = f"@{user.username}" if user.username else "Нет username"
await message.answer(
f"✅ Участник добавлен!\n\n"
f"👤 Пользователь: {user.first_name} {user.last_name or ''}\n"
f"📱 Username: {username}\n"
f"🆔 ID: {user.telegram_id}\n"
f"🎯 Розыгрыш: {lottery.title}",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")]
])
)
else:
await message.answer(
f"⚠️ Пользователь {user.first_name} уже участвует в этом розыгрыше",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")]
])
)
@admin_router.callback_query(F.data == "admin_list_all_participants")
async def list_all_participants(callback: CallbackQuery):
"""Список всех участников"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
async with async_session_maker() as session:
users = await UserService.get_all_users(session, limit=50)
# Получаем статистику для каждого пользователя
user_stats = []
for user in users:
stats = await ParticipationService.get_participant_stats(session, user.id)
user_stats.append((user, stats))
if not user_stats:
await callback.message.edit_text(
"В системе нет пользователей",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")]
])
)
return
text = "👥 Все участники системы\n\n"
text += f"Всего пользователей: {len(users)}\n\n"
for i, (user, stats) in enumerate(user_stats[:20], 1):
username = f"@{user.username}" if user.username else "Нет username"
text += f"{i}. {user.first_name} {user.last_name or ''}\n"
text += f" {username} | ID: {user.telegram_id}\n"
text += f" 🎫 Участий: {stats['participations_count']} | 🏆 Побед: {stats['wins_count']}\n"
if stats['last_participation']:
text += f" 📅 Последнее участие: {stats['last_participation'].strftime('%d.%m.%Y')}\n"
text += "\n"
if len(users) > 20:
text += f"... и еще {len(users) - 20} пользователей"
buttons = [
[InlineKeyboardButton(text="📊 Подробный отчет", callback_data="admin_participants_report")],
[InlineKeyboardButton(text="🔄 Обновить", callback_data="admin_list_all_participants")],
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")]
]
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data == "admin_participants_report")
async def generate_participants_report(callback: CallbackQuery):
"""Генерация отчета по участникам"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
async with async_session_maker() as session:
from sqlalchemy import func, select
from models import User, Participation, Winner
# Общие статистики
total_users = await session.scalar(select(func.count(User.id)))
total_participations = await session.scalar(select(func.count(Participation.id)))
total_winners = await session.scalar(select(func.count(Winner.id)))
# Топ участников по количеству участий
top_participants = await session.execute(
select(User.first_name, User.username, func.count(Participation.id).label('count'))
.join(Participation)
.group_by(User.id)
.order_by(func.count(Participation.id).desc())
.limit(10)
)
top_participants = top_participants.fetchall()
# Топ победителей
top_winners = await session.execute(
select(User.first_name, User.username, func.count(Winner.id).label('wins'))
.join(Winner)
.group_by(User.id)
.order_by(func.count(Winner.id).desc())
.limit(5)
)
top_winners = top_winners.fetchall()
# Недавняя активность
recent_users = await session.execute(
select(User.first_name, User.username, User.created_at)
.order_by(User.created_at.desc())
.limit(5)
)
recent_users = recent_users.fetchall()
text = "📈 Подробный отчет по участникам\n\n"
text += "📊 ОБЩАЯ СТАТИСТИКА\n"
text += f"👥 Всего пользователей: {total_users}\n"
text += f"🎫 Всего участий: {total_participations}\n"
text += f"🏆 Всего побед: {total_winners}\n"
if total_users > 0:
avg_participations = total_participations / total_users
text += f"📈 Среднее участий на пользователя: {avg_participations:.1f}\n"
text += "\n"
if top_participants:
text += "🔥 ТОП УЧАСТНИКИ (по количеству участий)\n"
for i, (first_name, username, count) in enumerate(top_participants, 1):
name = f"@{username}" if username else first_name
text += f"{i}. {name} - {count} участий\n"
text += "\n"
if top_winners:
text += "👑 ТОП ПОБЕДИТЕЛИ\n"
for i, (first_name, username, wins) in enumerate(top_winners, 1):
name = f"@{username}" if username else first_name
text += f"{i}. {name} - {wins} побед\n"
text += "\n"
if recent_users:
text += "🆕 НЕДАВНИЕ РЕГИСТРАЦИИ\n"
for first_name, username, created_at in recent_users:
name = f"@{username}" if username else first_name
text += f"{name} - {created_at.strftime('%d.%m.%Y %H:%M')}\n"
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="💾 Экспорт данных", callback_data="admin_export_participants")],
[InlineKeyboardButton(text="🔄 Обновить отчет", callback_data="admin_participants_report")],
[InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")]
])
)
@admin_router.callback_query(F.data == "admin_export_participants")
async def export_participants_data(callback: CallbackQuery):
"""Экспорт данных участников"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
await callback.answer("📊 Генерируем отчет...", show_alert=False)
async with async_session_maker() as session:
users = await UserService.get_all_users(session)
export_data = {
"timestamp": datetime.now().isoformat(),
"total_users": len(users),
"users": []
}
for user in users:
stats = await ParticipationService.get_participant_stats(session, user.id)
user_data = {
"id": user.id,
"telegram_id": user.telegram_id,
"first_name": user.first_name,
"last_name": user.last_name,
"username": user.username,
"created_at": user.created_at.isoformat() if user.created_at else None,
"participations_count": stats["participations_count"],
"wins_count": stats["wins_count"],
"last_participation": stats["last_participation"].isoformat() if stats["last_participation"] else None
}
export_data["users"].append(user_data)
# Формируем JSON для вывода
import json
json_data = json.dumps(export_data, ensure_ascii=False, indent=2)
# Отправляем JSON как текст (в реальном боте можно отправить как файл)
text = f"📊 Экспорт данных участников\n\n"
text += f"Дата: {datetime.now().strftime('%d.%m.%Y %H:%M')}\n"
text += f"Всего пользователей: {len(users)}\n\n"
text += "Данные готовы к экспорту (JSON формат)\n"
text += f"Размер: {len(json_data)} символов"
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="📈 К отчету", callback_data="admin_participants_report")],
[InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")]
])
)
@admin_router.callback_query(F.data == "admin_search_participants")
async def start_search_participants(callback: CallbackQuery, state: FSMContext):
"""Начать поиск участников"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
text = "🔍 Поиск участников\n\n"
text += "Введите имя, фамилию или username для поиска:\n\n"
text += "Примеры:\n"
text += "• Иван\n"
text += "• username\n"
text += "• Петров"
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="❌ Отмена", callback_data="admin_participants")]
])
)
await state.set_state(AdminStates.participant_search)
@admin_router.message(StateFilter(AdminStates.participant_search))
async def process_search_participants(message: Message, state: FSMContext):
"""Обработка поиска участников"""
if not is_admin(message.from_user.id):
await message.answer("❌ Недостаточно прав")
return
search_term = message.text.strip()
async with async_session_maker() as session:
users = await UserService.search_users(session, search_term)
# Получаем статистику для найденных пользователей
user_stats = []
for user in users:
stats = await ParticipationService.get_participant_stats(session, user.id)
user_stats.append((user, stats))
await state.clear()
if not user_stats:
await message.answer(
f"❌ Пользователи с поисковым запросом '{search_term}' не найдены",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")]
])
)
return
text = f"🔍 Результаты поиска: '{search_term}'\n\n"
text += f"Найдено: {len(users)} пользователей\n\n"
for i, (user, stats) in enumerate(user_stats[:15], 1):
username = f"@{user.username}" if user.username else "Нет username"
text += f"{i}. {user.first_name} {user.last_name or ''}\n"
text += f" {username} | ID: {user.telegram_id}\n"
text += f" 🎫 Участий: {stats['participations_count']} | 🏆 Побед: {stats['wins_count']}\n"
text += "\n"
if len(users) > 15:
text += f"... и еще {len(users) - 15} найденных пользователей"
await message.answer(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔍 Новый поиск", callback_data="admin_search_participants")],
[InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")]
])
)
@admin_router.callback_query(F.data == "admin_bulk_add_participant")
async def start_bulk_add_participant(callback: CallbackQuery, state: FSMContext):
"""Начать массовое добавление участников"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
async with async_session_maker() as session:
lotteries = await LotteryService.get_active_lotteries(session)
if not lotteries:
await callback.message.edit_text(
"❌ Нет активных розыгрышей",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")]
])
)
return
text = "📥 Массовое добавление участников\n\n"
text += "Выберите розыгрыш:\n\n"
buttons = []
for lottery in lotteries:
async with async_session_maker() as session:
count = await ParticipationService.get_participants_count(session, lottery.id)
text += f"🎯 {lottery.title} (участников: {count})\n"
buttons.append([
InlineKeyboardButton(
text=f"🎯 {lottery.title[:35]}...",
callback_data=f"admin_bulk_add_to_{lottery.id}"
)
])
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")])
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data.startswith("admin_bulk_add_to_"))
async def choose_users_bulk_add(callback: CallbackQuery, state: FSMContext):
"""Выбор пользователей для массового добавления"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
lottery_id = int(callback.data.split("_")[-1])
await state.update_data(bulk_add_lottery_id=lottery_id)
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
text = f"📥 Массовое добавление в: {lottery.title}\n\n"
text += "Введите список Telegram ID или username через запятую:\n\n"
text += "Примеры:\n"
text += "• @user1, @user2, @user3\n"
text += "• 123456789, 987654321, 555444333\n"
text += "• @user1, 123456789, @user3"
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="❌ Отмена", callback_data="admin_bulk_add_participant")]
])
)
await state.set_state(AdminStates.add_participant_bulk)
@admin_router.message(StateFilter(AdminStates.add_participant_bulk))
async def process_bulk_add_participant(message: Message, state: FSMContext):
"""Обработка массового добавления участников"""
if not is_admin(message.from_user.id):
await message.answer("❌ Недостаточно прав")
return
data = await state.get_data()
lottery_id = data['bulk_add_lottery_id']
# Парсим входные данные
user_inputs = [x.strip() for x in message.text.split(',') if x.strip()]
telegram_ids = []
async with async_session_maker() as session:
for user_input in user_inputs:
try:
if user_input.startswith('@'):
username = user_input[1:]
user = await UserService.get_user_by_username(session, username)
if user:
telegram_ids.append(user.telegram_id)
elif user_input.isdigit():
telegram_ids.append(int(user_input))
except:
continue
# Массовое добавление
results = await ParticipationService.add_participants_bulk(session, lottery_id, telegram_ids)
lottery = await LotteryService.get_lottery(session, lottery_id)
await state.clear()
text = f"📥 Результат массового добавления\n\n"
text += f"🎯 Розыгрыш: {lottery.title}\n\n"
text += f"✅ Добавлено: {results['added']}\n"
text += f"⚠️ Уже участвуют: {results['skipped']}\n"
text += f"❌ Ошибок: {len(results['errors'])}\n\n"
if results['details']:
text += "Детали:\n"
for detail in results['details'][:10]: # Первые 10
text += f"{detail}\n"
if len(results['details']) > 10:
text += f"... и еще {len(results['details']) - 10} записей"
await message.answer(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")]
])
)
@admin_router.callback_query(F.data == "admin_bulk_remove_participant")
async def start_bulk_remove_participant(callback: CallbackQuery, state: FSMContext):
"""Начать массовое удаление участников"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
async with async_session_maker() as session:
lotteries = await LotteryService.get_all_lotteries(session)
if not lotteries:
await callback.message.edit_text(
"❌ Нет розыгрышей",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")]
])
)
return
text = "📤 Массовое удаление участников\n\n"
text += "Выберите розыгрыш:\n\n"
buttons = []
for lottery in lotteries:
async with async_session_maker() as session:
count = await ParticipationService.get_participants_count(session, lottery.id)
if count > 0:
text += f"🎯 {lottery.title} (участников: {count})\n"
buttons.append([
InlineKeyboardButton(
text=f"🎯 {lottery.title[:35]}...",
callback_data=f"admin_bulk_remove_from_{lottery.id}"
)
])
if not buttons:
await callback.message.edit_text(
"❌ Нет розыгрышей с участниками",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")]
])
)
return
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")])
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data.startswith("admin_bulk_remove_from_"))
async def choose_users_bulk_remove(callback: CallbackQuery, state: FSMContext):
"""Выбор пользователей для массового удаления"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
lottery_id = int(callback.data.split("_")[-1])
await state.update_data(bulk_remove_lottery_id=lottery_id)
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
text = f"📤 Массовое удаление из: {lottery.title}\n\n"
text += "Введите список Telegram ID или username через запятую:\n\n"
text += "Примеры:\n"
text += "• @user1, @user2, @user3\n"
text += "• 123456789, 987654321, 555444333\n"
text += "• @user1, 123456789, @user3"
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="❌ Отмена", callback_data="admin_bulk_remove_participant")]
])
)
await state.set_state(AdminStates.remove_participant_bulk)
@admin_router.message(StateFilter(AdminStates.remove_participant_bulk))
async def process_bulk_remove_participant(message: Message, state: FSMContext):
"""Обработка массового удаления участников"""
if not is_admin(message.from_user.id):
await message.answer("❌ Недостаточно прав")
return
data = await state.get_data()
lottery_id = data['bulk_remove_lottery_id']
# Парсим входные данные
user_inputs = [x.strip() for x in message.text.split(',') if x.strip()]
telegram_ids = []
async with async_session_maker() as session:
for user_input in user_inputs:
try:
if user_input.startswith('@'):
username = user_input[1:]
user = await UserService.get_user_by_username(session, username)
if user:
telegram_ids.append(user.telegram_id)
elif user_input.isdigit():
telegram_ids.append(int(user_input))
except:
continue
# Массовое удаление
results = await ParticipationService.remove_participants_bulk(session, lottery_id, telegram_ids)
lottery = await LotteryService.get_lottery(session, lottery_id)
await state.clear()
text = f"📤 Результат массового удаления\n\n"
text += f"🎯 Розыгрыш: {lottery.title}\n\n"
text += f"✅ Удалено: {results['removed']}\n"
text += f"⚠️ Не найдено: {results['not_found']}\n"
text += f"❌ Ошибок: {len(results['errors'])}\n\n"
if results['details']:
text += "Детали:\n"
for detail in results['details'][:10]: # Первые 10
text += f"{detail}\n"
if len(results['details']) > 10:
text += f"... и еще {len(results['details']) - 10} записей"
await message.answer(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")]
])
)
# ======================
# МАССОВОЕ УПРАВЛЕНИЕ УЧАСТНИКАМИ ПО СЧЕТАМ
# ======================
@admin_router.callback_query(F.data == "admin_bulk_add_accounts")
async def start_bulk_add_accounts(callback: CallbackQuery, state: FSMContext):
"""Начать массовое добавление участников по номерам счетов"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
async with async_session_maker() as session:
lotteries = await LotteryService.get_active_lotteries(session)
if not lotteries:
await callback.message.edit_text(
"❌ Нет активных розыгрышей",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")]
])
)
return
text = "🏦 Массовое добавление по номерам счетов\n\n"
text += "Выберите розыгрыш:\n\n"
buttons = []
for lottery in lotteries:
async with async_session_maker() as session:
count = await ParticipationService.get_participants_count(session, lottery.id)
text += f"🎯 {lottery.title} (участников: {count})\n"
buttons.append([
InlineKeyboardButton(
text=f"🎯 {lottery.title[:35]}...",
callback_data=f"admin_bulk_add_accounts_to_{lottery.id}"
)
])
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")])
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data.startswith("admin_bulk_add_accounts_to_"))
async def choose_accounts_bulk_add(callback: CallbackQuery, state: FSMContext):
"""Выбор номеров счетов для массового добавления"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
lottery_id = int(callback.data.split("_")[-1])
await state.update_data(bulk_add_accounts_lottery_id=lottery_id)
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
text = f"🏦 Массовое добавление в: {lottery.title}\n\n"
text += "Введите список номеров счетов через запятую или новую строку:\n\n"
text += "Примеры:\n"
text += "• 12-34-56-78-90-12-34-56\n"
text += "• 98-76-54-32-10-98-76-54, 11-22-33-44-55-66-77-88\n"
text += "• 12345678901234567890 (будет отформатирован)\n\n"
text += "Формат: XX-XX-XX-XX-XX-XX-XX-XX\n"
text += "Всего 8 пар цифр разделенных дефисами"
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="❌ Отмена", callback_data="admin_bulk_add_accounts")]
])
)
await state.set_state(AdminStates.add_participant_bulk_accounts)
@admin_router.message(StateFilter(AdminStates.add_participant_bulk_accounts))
async def process_bulk_add_accounts(message: Message, state: FSMContext):
"""Обработка массового добавления участников по номерам счетов"""
if not is_admin(message.from_user.id):
await message.answer("❌ Недостаточно прав")
return
data = await state.get_data()
lottery_id = data['bulk_add_accounts_lottery_id']
# Парсим входные данные - поддерживаем и запятые, и переносы строк
account_inputs = []
for line in message.text.split('\n'):
for account in line.split(','):
account = account.strip()
if account:
account_inputs.append(account)
async with async_session_maker() as session:
# Массовое добавление по номерам счетов
results = await ParticipationService.add_participants_by_accounts_bulk(session, lottery_id, account_inputs)
lottery = await LotteryService.get_lottery(session, lottery_id)
await state.clear()
text = f"🏦 Результат массового добавления по счетам\n\n"
text += f"🎯 Розыгрыш: {lottery.title}\n\n"
text += f"✅ Добавлено: {results['added']}\n"
text += f"⚠️ Уже участвуют: {results['skipped']}\n"
text += f"🚫 Неверных форматов: {len(results['invalid_accounts'])}\n"
text += f"❌ Ошибок: {len(results['errors'])}\n\n"
if results['details']:
text += "✅ Успешно добавлены:\n"
for detail in results['details'][:7]: # Первые 7
text += f"{detail}\n"
if len(results['details']) > 7:
text += f"... и еще {len(results['details']) - 7} записей\n\n"
if results['invalid_accounts']:
text += "\n🚫 Неверные форматы:\n"
for invalid in results['invalid_accounts'][:5]:
text += f"{invalid}\n"
if len(results['invalid_accounts']) > 5:
text += f"... и еще {len(results['invalid_accounts']) - 5} номеров\n"
if results['errors']:
text += "\n❌ Ошибки:\n"
for error in results['errors'][:3]:
text += f"{error}\n"
if len(results['errors']) > 3:
text += f"... и еще {len(results['errors']) - 3} ошибок\n"
await message.answer(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")]
])
)
@admin_router.callback_query(F.data == "admin_bulk_remove_accounts")
async def start_bulk_remove_accounts(callback: CallbackQuery, state: FSMContext):
"""Начать массовое удаление участников по номерам счетов"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
async with async_session_maker() as session:
lotteries = await LotteryService.get_all_lotteries(session)
if not lotteries:
await callback.message.edit_text(
"❌ Нет розыгрышей",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")]
])
)
return
text = "🏦 Массовое удаление по номерам счетов\n\n"
text += "Выберите розыгрыш:\n\n"
buttons = []
for lottery in lotteries:
async with async_session_maker() as session:
count = await ParticipationService.get_participants_count(session, lottery.id)
text += f"🎯 {lottery.title} (участников: {count})\n"
buttons.append([
InlineKeyboardButton(
text=f"🎯 {lottery.title[:35]}...",
callback_data=f"admin_bulk_remove_accounts_from_{lottery.id}"
)
])
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")])
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data.startswith("admin_bulk_remove_accounts_from_"))
async def choose_accounts_bulk_remove(callback: CallbackQuery, state: FSMContext):
"""Выбор номеров счетов для массового удаления"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
lottery_id = int(callback.data.split("_")[-1])
await state.update_data(bulk_remove_accounts_lottery_id=lottery_id)
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
text = f"🏦 Массовое удаление из: {lottery.title}\n\n"
text += "Введите список номеров счетов через запятую или новую строку:\n\n"
text += "Примеры:\n"
text += "• 12-34-56-78-90-12-34-56\n"
text += "• 98-76-54-32-10-98-76-54, 11-22-33-44-55-66-77-88\n"
text += "• 12345678901234567890 (будет отформатирован)\n\n"
text += "Формат: XX-XX-XX-XX-XX-XX-XX-XX"
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="❌ Отмена", callback_data="admin_bulk_remove_accounts")]
])
)
await state.set_state(AdminStates.remove_participant_bulk_accounts)
@admin_router.message(StateFilter(AdminStates.remove_participant_bulk_accounts))
async def process_bulk_remove_accounts(message: Message, state: FSMContext):
"""Обработка массового удаления участников по номерам счетов"""
if not is_admin(message.from_user.id):
await message.answer("❌ Недостаточно прав")
return
data = await state.get_data()
lottery_id = data['bulk_remove_accounts_lottery_id']
# Парсим входные данные - поддерживаем и запятые, и переносы строк
account_inputs = []
for line in message.text.split('\n'):
for account in line.split(','):
account = account.strip()
if account:
account_inputs.append(account)
async with async_session_maker() as session:
# Массовое удаление по номерам счетов
results = await ParticipationService.remove_participants_by_accounts_bulk(session, lottery_id, account_inputs)
lottery = await LotteryService.get_lottery(session, lottery_id)
await state.clear()
text = f"🏦 Результат массового удаления по счетам\n\n"
text += f"🎯 Розыгрыш: {lottery.title}\n\n"
text += f"✅ Удалено: {results['removed']}\n"
text += f"⚠️ Не найдено: {results['not_found']}\n"
text += f"🚫 Неверных форматов: {len(results['invalid_accounts'])}\n"
text += f"❌ Ошибок: {len(results['errors'])}\n\n"
if results['details']:
text += "✅ Успешно удалены:\n"
for detail in results['details'][:7]: # Первые 7
text += f"{detail}\n"
if len(results['details']) > 7:
text += f"... и еще {len(results['details']) - 7} записей\n\n"
if results['invalid_accounts']:
text += "\n🚫 Неверные форматы:\n"
for invalid in results['invalid_accounts'][:5]:
text += f"{invalid}\n"
if len(results['invalid_accounts']) > 5:
text += f"... и еще {len(results['invalid_accounts']) - 5} номеров\n"
if results['errors']:
text += "\n❌ Ошибки:\n"
for error in results['errors'][:3]:
text += f"{error}\n"
if len(results['errors']) > 3:
text += f"... и еще {len(results['errors']) - 3} ошибок\n"
await message.answer(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")]
])
)
# ======================
# ДОПОЛНИТЕЛЬНЫЕ ХЭНДЛЕРЫ УЧАСТНИКОВ
# ======================
@admin_router.callback_query(F.data == "admin_participants_by_lottery")
async def show_participants_by_lottery(callback: CallbackQuery):
"""Показать участников по розыгрышам"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
async with async_session_maker() as session:
lotteries = await LotteryService.get_all_lotteries(session)
if not lotteries:
await callback.message.edit_text(
"❌ Нет розыгрышей",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")]
])
)
return
text = "📊 Участники по розыгрышам\n\n"
for lottery in lotteries[:15]: # Показываем первые 15
async with async_session_maker() as session:
count = await ParticipationService.get_participants_count(session, lottery.id)
status = "🟢" if getattr(lottery, 'is_active', True) else "🔴"
text += f"{status} {lottery.title}: {count} участников\n"
if len(lotteries) > 15:
text += f"\n... и еще {len(lotteries) - 15} розыгрышей"
buttons = []
for lottery in lotteries[:10]: # Кнопки для первых 10
buttons.append([
InlineKeyboardButton(
text=f"👥 {lottery.title[:30]}...",
callback_data=f"admin_participants_{lottery.id}"
)
])
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")])
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data == "admin_participants_report")
async def show_participants_report(callback: CallbackQuery):
"""Отчет по участникам"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
async with async_session_maker() as session:
from sqlalchemy import func, select
from models import User, Participation, Lottery
# Общая статистика по участникам
total_participants = await session.scalar(
select(func.count(func.distinct(User.id)))
.select_from(User)
.join(Participation)
)
total_participations = await session.scalar(select(func.count(Participation.id)))
# Топ активных участников
top_participants = await session.execute(
select(
User.first_name,
User.username,
User.account_number,
func.count(Participation.id).label('participations')
)
.join(Participation)
.group_by(User.id)
.order_by(func.count(Participation.id).desc())
.limit(10)
)
top_participants = top_participants.fetchall()
# Участники с аккаунтами vs без
users_with_accounts = await session.scalar(
select(func.count(User.id)).where(User.account_number.isnot(None))
)
users_without_accounts = await session.scalar(
select(func.count(User.id)).where(User.account_number.is_(None))
)
text = "📈 Отчет по участникам\n\n"
text += f"👥 Всего уникальных участников: {total_participants}\n"
text += f"📊 Всего участий: {total_participations}\n"
text += f"🏦 С номерами счетов: {users_with_accounts}\n"
text += f"🆔 Только Telegram ID: {users_without_accounts}\n\n"
if top_participants:
text += "🏆 Топ-10 активных участников:\n"
for i, (name, username, account, count) in enumerate(top_participants, 1):
display_name = f"@{username}" if username else name
if account:
display_name += f" ({account[-7:]})" # Последние 7 символов счёта
text += f"{i}. {display_name} - {count} участий\n"
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔄 Обновить", callback_data="admin_participants_report")],
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")]
])
)
@admin_router.callback_query(F.data == "admin_edit_lottery")
async def start_edit_lottery(callback: CallbackQuery, state: FSMContext):
"""Начать редактирование розыгрыша"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
async with async_session_maker() as session:
lotteries = await LotteryService.get_all_lotteries(session)
if not lotteries:
await callback.message.edit_text(
"❌ Нет розыгрышей для редактирования",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")]
])
)
return
text = "📝 Редактирование розыгрыша\n\n"
text += "Выберите розыгрыш для редактирования:\n\n"
buttons = []
for lottery in lotteries[:10]: # Первые 10 розыгрышей
status = "🟢" if getattr(lottery, 'is_active', True) else "🔴"
text += f"{status} {lottery.title}\n"
buttons.append([
InlineKeyboardButton(
text=f"📝 {lottery.title[:30]}...",
callback_data=f"admin_edit_lottery_select_{lottery.id}"
)
])
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")])
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data.startswith("admin_edit_lottery_select_"))
async def choose_edit_field(callback: CallbackQuery, state: FSMContext):
"""Выбор поля для редактирования"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
lottery_id = int(callback.data.split("_")[-1])
await state.update_data(edit_lottery_id=lottery_id)
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
text = f"📝 Редактирование: {lottery.title}\n\n"
text += "Выберите, что хотите изменить:\n\n"
text += f"📝 Название: {lottery.title}\n"
text += f"📄 Описание: {lottery.description[:50]}{'...' if len(lottery.description) > 50 else ''}\n"
text += f"🎁 Призы: {len(getattr(lottery, 'prizes', []))} шт.\n"
text += f"🎭 Отображение: {getattr(lottery, 'winner_display_type', 'username')}\n"
text += f"🟢 Активен: {'Да' if getattr(lottery, 'is_active', True) else 'Нет'}"
buttons = [
[InlineKeyboardButton(text="📝 Изменить название", callback_data=f"admin_edit_field_{lottery_id}_title")],
[InlineKeyboardButton(text="📄 Изменить описание", callback_data=f"admin_edit_field_{lottery_id}_description")],
[InlineKeyboardButton(text="🎁 Изменить призы", callback_data=f"admin_edit_field_{lottery_id}_prizes")],
[
InlineKeyboardButton(text="⏸️ Деактивировать" if getattr(lottery, 'is_active', True) else "▶️ Активировать",
callback_data=f"admin_toggle_active_{lottery_id}"),
InlineKeyboardButton(text="🎭 Тип отображения", callback_data=f"admin_set_display_{lottery_id}")
],
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_edit_lottery")]
]
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data.startswith("admin_toggle_active_"))
async def toggle_lottery_active(callback: CallbackQuery):
"""Переключить активность розыгрыша"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
lottery_id = int(callback.data.split("_")[-1])
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
current_active = getattr(lottery, 'is_active', True)
# Переключаем статус
success = await LotteryService.set_lottery_active(session, lottery_id, not current_active)
if success:
new_status = "активирован" if not current_active else "деактивирован"
await callback.answer(f"✅ Розыгрыш {new_status}!", show_alert=True)
else:
await callback.answer("❌ Ошибка изменения статуса", show_alert=True)
# Обновляем отображение
await choose_edit_field(callback, None)
@admin_router.callback_query(F.data == "admin_finish_lottery")
async def start_finish_lottery(callback: CallbackQuery):
"""Завершить розыгрыш"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
async with async_session_maker() as session:
lotteries = await LotteryService.get_active_lotteries(session)
if not lotteries:
await callback.message.edit_text(
"❌ Нет активных розыгрышей для завершения",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")]
])
)
return
text = "🏁 Завершение розыгрыша\n\n"
text += "Выберите розыгрыш для завершения:\n\n"
buttons = []
for lottery in lotteries:
async with async_session_maker() as session:
count = await ParticipationService.get_participants_count(session, lottery.id)
text += f"🎯 {lottery.title} ({count} участников)\n"
buttons.append([
InlineKeyboardButton(
text=f"🏁 {lottery.title[:30]}...",
callback_data=f"admin_confirm_finish_{lottery.id}"
)
])
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")])
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data.startswith("admin_confirm_finish_"))
async def confirm_finish_lottery(callback: CallbackQuery):
"""Подтвердить завершение розыгрыша"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
lottery_id = int(callback.data.split("_")[-1])
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
count = await ParticipationService.get_participants_count(session, lottery_id)
text = f"🏁 Завершение розыгрыша\n\n"
text += f"🎯 {lottery.title}\n"
text += f"👥 Участников: {count}\n\n"
text += "⚠️ После завершения розыгрыш станет неактивным и новые участники не смогут присоединиться.\n\n"
text += "Вы уверены?"
buttons = [
[
InlineKeyboardButton(text="✅ Да, завершить", callback_data=f"admin_do_finish_{lottery_id}"),
InlineKeyboardButton(text="❌ Отмена", callback_data="admin_finish_lottery")
]
]
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data.startswith("admin_do_finish_"))
async def do_finish_lottery(callback: CallbackQuery):
"""Выполнить завершение розыгрыша"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
lottery_id = int(callback.data.split("_")[-1])
async with async_session_maker() as session:
success = await LotteryService.complete_lottery(session, lottery_id)
lottery = await LotteryService.get_lottery(session, lottery_id)
if success:
text = f"✅ Розыгрыш завершён!\n\n"
text += f"🎯 {lottery.title}\n"
text += f"📅 Завершён: {datetime.now().strftime('%d.%m.%Y %H:%M')}"
await callback.answer("✅ Розыгрыш завершён!", show_alert=True)
else:
text = "❌ Ошибка завершения розыгрыша"
await callback.answer("❌ Ошибка завершения", show_alert=True)
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🎲 К управлению розыгрышами", callback_data="admin_lotteries")]
])
)
@admin_router.callback_query(F.data == "admin_delete_lottery")
async def start_delete_lottery(callback: CallbackQuery):
"""Удаление розыгрыша"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
async with async_session_maker() as session:
lotteries = await LotteryService.get_all_lotteries(session)
if not lotteries:
await callback.message.edit_text(
"❌ Нет розыгрышей для удаления",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")]
])
)
return
text = "🗑️ Удаление розыгрыша\n\n"
text += "⚠️ ВНИМАНИЕ! Это действие нельзя отменить!\n\n"
text += "Выберите розыгрыш для удаления:\n\n"
buttons = []
for lottery in lotteries[:10]:
status = "🟢" if getattr(lottery, 'is_active', True) else "🔴"
async with async_session_maker() as session:
count = await ParticipationService.get_participants_count(session, lottery.id)
text += f"{status} {lottery.title} ({count} участников)\n"
buttons.append([
InlineKeyboardButton(
text=f"🗑️ {lottery.title[:25]}...",
callback_data=f"admin_confirm_delete_{lottery.id}"
)
])
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")])
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data.startswith("admin_confirm_delete_"))
async def confirm_delete_lottery(callback: CallbackQuery):
"""Подтвердить удаление розыгрыша"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
lottery_id = int(callback.data.split("_")[-1])
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
count = await ParticipationService.get_participants_count(session, lottery_id)
text = f"🗑️ Удаление розыгрыша\n\n"
text += f"🎯 {lottery.title}\n"
text += f"👥 Участников: {count}\n\n"
text += "⚠️ ВНИМАНИЕ!\n"
text += "Все данные о розыгрыше будут удалены навсегда\n"
text += "Все участия в розыгрыше будут удалены\n"
text += "• Это действие НЕЛЬЗЯ отменить!\n\n"
text += "Вы ТОЧНО уверены?"
buttons = [
[
InlineKeyboardButton(text="🗑️ ДА, УДАЛИТЬ", callback_data=f"admin_do_delete_{lottery_id}"),
InlineKeyboardButton(text="ОТМЕНА", callback_data="admin_delete_lottery")
]
]
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data.startswith("admin_do_delete_"))
async def do_delete_lottery(callback: CallbackQuery):
"""Выполнить удаление розыгрыша"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
lottery_id = int(callback.data.split("_")[-1])
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
lottery_title = lottery.title
success = await LotteryService.delete_lottery(session, lottery_id)
if success:
text = f"✅ Розыгрыш удалён!\n\n"
text += f"🎯 {lottery_title}\n"
text += f"📅 Удалён: {datetime.now().strftime('%d.%m.%Y %H:%M')}"
await callback.answer("✅ Розыгрыш удалён!", show_alert=True)
else:
text = "❌ Ошибка удаления розыгрыша"
await callback.answer("❌ Ошибка удаления", show_alert=True)
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🎲 К управлению розыгрышами", callback_data="admin_lotteries")]
])
)
# ======================
# УПРАВЛЕНИЕ ПОБЕДИТЕЛЯМИ
# ======================
@admin_router.callback_query(F.data == "admin_winners")
async def show_winner_management(callback: CallbackQuery):
"""Управление победителями"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
text = "👑 Управление победителями\n\n"
text += "Здесь вы можете устанавливать предопределенных победителей и проводить розыгрыши.\n\n"
text += "Выберите действие:"
await callback.message.edit_text(text, reply_markup=get_winner_management_keyboard())
@admin_router.callback_query(F.data == "admin_set_manual_winner")
async def start_set_manual_winner(callback: CallbackQuery, state: FSMContext):
"""Начать установку ручного победителя"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
async with async_session_maker() as session:
lotteries = await LotteryService.get_active_lotteries(session)
if not lotteries:
await callback.message.edit_text(
"❌ Нет активных розыгрышей для установки победителей",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_winners")]
])
)
return
text = "👑 Установка предопределенного победителя\n\n"
text += "Выберите розыгрыш:\n\n"
buttons = []
for lottery in lotteries:
text += f"🎯 {lottery.title} (ID: {lottery.id})\n"
# Показываем уже установленных ручных победителей
if lottery.manual_winners:
text += f" 👑 Установлены места: {', '.join(lottery.manual_winners.keys())}\n"
text += "\n"
buttons.append([
InlineKeyboardButton(
text=f"🎯 {lottery.title[:30]}...",
callback_data=f"admin_choose_winner_lottery_{lottery.id}"
)
])
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_winners")])
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data.startswith("admin_choose_winner_lottery_"))
async def choose_winner_place(callback: CallbackQuery, state: FSMContext):
"""Выбор места для победителя"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
lottery_id = int(callback.data.split("_")[-1])
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
if not lottery:
await callback.answer("Розыгрыш не найден", show_alert=True)
return
await state.update_data(lottery_id=lottery_id)
num_prizes = len(lottery.prizes) if lottery.prizes else 5
text = f"👑 Установка победителя\n"
text += f"🎯 Розыгрыш: {lottery.title}\n\n"
if lottery.manual_winners:
text += f"Уже установлены места: {', '.join(lottery.manual_winners.keys())}\n\n"
text += f"Введите номер места (1-{num_prizes}):"
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="❌ Отмена", callback_data="admin_set_manual_winner")]
])
)
await state.set_state(AdminStates.set_winner_place)
@admin_router.message(StateFilter(AdminStates.set_winner_place))
async def process_winner_place(message: Message, state: FSMContext):
"""Обработка места победителя"""
if not is_admin(message.from_user.id):
await message.answer("❌ Недостаточно прав")
return
try:
place = int(message.text)
if place < 1:
raise ValueError
except ValueError:
await message.answer("❌ Введите корректный номер места (положительное число)")
return
data = await state.get_data()
lottery_id = data['lottery_id']
# Проверяем, не занято ли место
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
if lottery.manual_winners and str(place) in lottery.manual_winners:
existing_id = lottery.manual_winners[str(place)]
existing_user = await UserService.get_user_by_telegram_id(session, existing_id)
name = existing_user.username if existing_user and existing_user.username else str(existing_id)
await message.answer(
f"⚠️ Место {place} уже занято пользователем @{name}\n"
f"Введите другой номер места:"
)
return
await state.update_data(place=place)
text = f"👑 Установка победителя на {place} место\n"
text += f"🎯 Розыгрыш: {lottery.title}\n\n"
text += f"Введите Telegram ID или username пользователя:"
await message.answer(text)
await state.set_state(AdminStates.set_winner_user)
@admin_router.message(StateFilter(AdminStates.set_winner_user))
async def process_winner_user(message: Message, state: FSMContext):
"""Обработка пользователя-победителя"""
if not is_admin(message.from_user.id):
await message.answer("❌ Недостаточно прав")
return
user_input = message.text.strip()
# Пробуем определить, это ID или username
if user_input.startswith('@'):
user_input = user_input[1:] # Убираем @
is_username = True
elif user_input.isdigit():
is_username = False
telegram_id = int(user_input)
else:
is_username = True
async with async_session_maker() as session:
if is_username:
# Поиск по username
from sqlalchemy import select
from models import User
result = await session.execute(
select(User).where(User.username == user_input)
)
user = result.scalar_one_or_none()
if not user:
await message.answer("❌ Пользователь с таким username не найден")
return
telegram_id = user.telegram_id
else:
user = await UserService.get_user_by_telegram_id(session, telegram_id)
if not user:
await message.answer("❌ Пользователь с таким ID не найден")
return
data = await state.get_data()
async with async_session_maker() as session:
success = await LotteryService.set_manual_winner(
session,
data['lottery_id'],
data['place'],
telegram_id
)
await state.clear()
if success:
username = f"@{user.username}" if user.username else user.first_name
await message.answer(
f"✅ Предопределенный победитель установлен!\n\n"
f"🏆 Место: {data['place']}\n"
f"👤 Пользователь: {username}\n"
f"🆔 ID: {telegram_id}\n\n"
f"При проведении розыгрыша этот пользователь автоматически займет {data['place']} место.",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="👑 К управлению победителями", callback_data="admin_winners")]
])
)
else:
await message.answer(
"Не удалось установить победителя. Проверьте данные.",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="👑 К управлению победителями", callback_data="admin_winners")]
])
)
# ======================
# СТАТИСТИКА
# ======================
@admin_router.callback_query(F.data == "admin_stats")
async def show_detailed_stats(callback: CallbackQuery):
"""Подробная статистика"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
async with async_session_maker() as session:
from sqlalchemy import select, func
from models import User, Lottery, Participation, Winner
# Общая статистика
total_users = await session.scalar(select(func.count(User.id)))
total_lotteries = await session.scalar(select(func.count(Lottery.id)))
active_lotteries = await session.scalar(
select(func.count(Lottery.id))
.where(Lottery.is_active == True, Lottery.is_completed == False)
)
completed_lotteries = await session.scalar(
select(func.count(Lottery.id)).where(Lottery.is_completed == True)
)
total_participations = await session.scalar(select(func.count(Participation.id)))
total_winners = await session.scalar(select(func.count(Winner.id)))
manual_winners = await session.scalar(
select(func.count(Winner.id)).where(Winner.is_manual == True)
)
# Топ активных пользователей
top_users = await session.execute(
select(User.first_name, User.username, func.count(Participation.id).label('count'))
.join(Participation)
.group_by(User.id)
.order_by(func.count(Participation.id).desc())
.limit(5)
)
top_users = top_users.fetchall()
text = "📊 Детальная статистика\n\n"
text += "👥 ПОЛЬЗОВАТЕЛИ\n"
text += f"Всего зарегистрировано: {total_users}\n\n"
text += "🎲 РОЗЫГРЫШИ\n"
text += f"Всего создано: {total_lotteries}\n"
text += f"🟢 Активных: {active_lotteries}\n"
text += f"✅ Завершенных: {completed_lotteries}\n\n"
text += "🎫 УЧАСТИЕ\n"
text += f"Всего участий: {total_participations}\n"
if total_lotteries > 0:
avg_participation = total_participations / total_lotteries
text += f"Среднее участие на розыгрыш: {avg_participation:.1f}\n\n"
text += "🏆 ПОБЕДИТЕЛИ\n"
text += f"Всего победителей: {total_winners}\n"
text += f"👑 Предустановленных: {manual_winners}\n"
text += f"🎲 Случайных: {total_winners - manual_winners}\n\n"
if top_users:
text += "🔥 САМЫЕ АКТИВНЫЕ УЧАСТНИКИ\n"
for i, (first_name, username, count) in enumerate(top_users, 1):
name = f"@{username}" if username else first_name
text += f"{i}. {name} - {count} участий\n"
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔄 Обновить", callback_data="admin_stats")],
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_panel")]
])
)
# ======================
# НАСТРОЙКИ
# ======================
@admin_router.callback_query(F.data == "admin_settings")
async def show_admin_settings(callback: CallbackQuery):
"""Настройки админ-панели"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
text = "⚙️ Настройки системы\n\n"
text += f"👑 Администраторы: {len(ADMIN_IDS)}\n"
text += f"🗄️ База данных: SQLAlchemy ORM\n"
text += f"📅 Сегодня: {datetime.now().strftime('%d.%m.%Y %H:%M')}\n\n"
text += "Доступные действия:"
buttons = [
[InlineKeyboardButton(text="💾 Экспорт данных", callback_data="admin_export_data")],
[InlineKeyboardButton(text="🧹 Очистка старых данных", callback_data="admin_cleanup")],
[InlineKeyboardButton(text="📋 Системная информация", callback_data="admin_system_info")],
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_panel")]
]
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data == "admin_system_info")
async def show_system_info(callback: CallbackQuery):
"""Системная информация"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
import sys
import platform
from config import DATABASE_URL
text = "💻 Системная информация\n\n"
text += f"🐍 Python: {sys.version.split()[0]}\n"
text += f"💾 Платформа: {platform.system()} {platform.release()}\n"
text += f"🗄️ База данных: {DATABASE_URL.split('://')[0]}\n"
text += f"👑 Админов: {len(ADMIN_IDS)}\n"
text += f"🕐 Время работы: {datetime.now().strftime('%d.%m.%Y %H:%M')}\n"
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_settings")]
])
)
# ======================
# НАСТРОЙКА ОТОБРАЖЕНИЯ ПОБЕДИТЕЛЕЙ
# ======================
@admin_router.callback_query(F.data == "admin_winner_display_settings")
async def show_winner_display_settings(callback: CallbackQuery, state: FSMContext):
"""Настройка отображения победителей для розыгрышей"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
async with async_session_maker() as session:
lotteries = await LotteryService.get_all_lotteries(session)
if not lotteries:
await callback.message.edit_text(
"❌ Нет розыгрышей",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")]
])
)
return
text = "🎭 Настройка отображения победителей\n\n"
text += "Выберите розыгрыш для настройки:\n\n"
buttons = []
for lottery in lotteries[:10]: # Первые 10 розыгрышей
display_type_emoji = {
'username': '👤',
'chat_id': '🆔',
'account_number': '🏦'
}.get(getattr(lottery, 'winner_display_type', 'username'), '👤')
text += f"{display_type_emoji} {lottery.title} - {getattr(lottery, 'winner_display_type', 'username')}\n"
buttons.append([
InlineKeyboardButton(
text=f"{display_type_emoji} {lottery.title[:30]}...",
callback_data=f"admin_set_display_{lottery.id}"
)
])
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")])
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data.startswith("admin_set_display_"))
async def choose_display_type(callback: CallbackQuery, state: FSMContext):
"""Выбор типа отображения для конкретного розыгрыша"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
lottery_id = int(callback.data.split("_")[-1])
await state.update_data(display_lottery_id=lottery_id)
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
current_type = getattr(lottery, 'winner_display_type', 'username')
text = f"🎭 Настройка отображения для:\n{lottery.title}\n\n"
text += f"Текущий тип: {current_type}\n\n"
text += "Выберите новый тип отображения:\n\n"
text += "👤 Username - показывает @username или имя\n"
text += "🆔 Chat ID - показывает Telegram ID пользователя\n"
text += "🏦 Account Number - показывает номер клиентского счета"
buttons = [
[
InlineKeyboardButton(text="👤 Username", callback_data=f"admin_apply_display_{lottery_id}_username"),
InlineKeyboardButton(text="🆔 Chat ID", callback_data=f"admin_apply_display_{lottery_id}_chat_id")
],
[InlineKeyboardButton(text="🏦 Account Number", callback_data=f"admin_apply_display_{lottery_id}_account_number")],
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_winner_display_settings")]
]
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data.startswith("admin_apply_display_"))
async def apply_display_type(callback: CallbackQuery, state: FSMContext):
"""Применить выбранный тип отображения"""
import logging
logger = logging.getLogger(__name__)
logger.info(f"🎭 Попытка смены типа отображения. Callback data: {callback.data}")
if not is_admin(callback.from_user.id):
logger.warning(f"🚫 Отказ в доступе пользователю {callback.from_user.id}")
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
try:
parts = callback.data.split("_")
logger.info(f"🔍 Разбор callback data: {parts}")
# Format: admin_apply_display_{lottery_id}_{display_type}
# Для account_number нужно склеить последние части
lottery_id = int(parts[3])
display_type = "_".join(parts[4:]) # Склеиваем все остальные части
logger.info(f"🎯 Розыгрыш ID: {lottery_id}, Новый тип: {display_type}")
async with async_session_maker() as session:
logger.info(f"📝 Вызов set_winner_display_type({lottery_id}, {display_type})")
success = await LotteryService.set_winner_display_type(session, lottery_id, display_type)
logger.info(f"💾 Результат сохранения: {success}")
lottery = await LotteryService.get_lottery(session, lottery_id)
logger.info(f"📋 Получен розыгрыш: {lottery.title if lottery else 'None'}")
if success:
display_type_name = {
'username': 'Username (@username или имя)',
'chat_id': 'Chat ID (Telegram ID)',
'account_number': 'Account Number (номер счета)'
}.get(display_type, display_type)
text = f"✅ Тип отображения изменен!\n\n"
text += f"🎯 Розыгрыш: {lottery.title}\n"
text += f"🎭 Новый тип: {display_type_name}\n\n"
text += "Теперь победители этого розыгрыша будут отображаться в выбранном формате."
logger.info(f"✅ Успех! Тип изменен на {display_type}")
await callback.answer("✅ Настройка сохранена!", show_alert=True)
else:
text = "❌ Ошибка при сохранении настройки"
logger.error(f"❌ Ошибка сохранения для розыгрыша {lottery_id}, тип {display_type}")
except Exception as e:
logger.error(f"💥 Исключение при смене типа отображения: {e}")
text = f"❌ Ошибка: {str(e)}"
await callback.answer("❌ Ошибка при сохранении!", show_alert=True)
return
await callback.answer("❌ Ошибка сохранения", show_alert=True)
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🎭 К настройке отображения", callback_data="admin_winner_display_settings")],
[InlineKeyboardButton(text="🎲 К управлению розыгрышами", callback_data="admin_lotteries")]
])
)
await state.clear()
# Экспорт роутера
__all__ = ['admin_router']