"""
Обработчики для работы со счетами в розыгрышах
"""
from aiogram import Router, F
from aiogram.types import Message, CallbackQuery, 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 config import ADMIN_IDS
from database import async_session_maker
from services import LotteryService
from account_services import AccountParticipationService
from account_utils import parse_accounts_from_message, validate_account_number
from typing import List
# Состояния FSM для работы со счетами
class AccountStates(StatesGroup):
waiting_for_lottery_choice = State() # Выбор розыгрыша для добавления счетов
waiting_for_winner_lottery = State() # Выбор розыгрыша для установки победителя
waiting_for_winner_place = State() # Выбор места победителя
searching_accounts = State() # Поиск счетов
# Создаем роутер
account_router = Router()
def is_admin(user_id: int) -> bool:
"""Проверка прав администратора"""
return user_id in ADMIN_IDS
@account_router.message(
F.text,
StateFilter(None),
~F.text.startswith('/') # Исключаем команды
)
async def detect_account_input(message: Message, state: FSMContext):
"""
Обнаружение ввода счетов в сообщении
Активируется только для администраторов
"""
if not is_admin(message.from_user.id):
return
# Парсим счета из сообщения
accounts = parse_accounts_from_message(message.text)
if not accounts:
return # Счета не обнаружены, пропускаем
# Сохраняем счета в состоянии
await state.update_data(detected_accounts=accounts)
# Формируем сообщение
accounts_text = "\n".join([f"• {acc}" for acc in accounts])
count = len(accounts)
text = (
f"🔍 Обнаружен ввод счет{'а' if count == 1 else 'ов'}\n\n"
f"Найдено: {count}\n\n"
f"{accounts_text}\n\n"
f"Выберите действие:"
)
# Кнопки выбора действия
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(
text="➕ Добавить в розыгрыш",
callback_data="account_action:add_to_lottery"
)],
[InlineKeyboardButton(
text="👑 Сделать победителем",
callback_data="account_action:set_as_winner"
)],
[InlineKeyboardButton(
text="❌ Отмена",
callback_data="account_action:cancel"
)]
])
await message.answer(text, reply_markup=keyboard, parse_mode="HTML")
@account_router.callback_query(F.data == "account_action:cancel")
async def cancel_account_action(callback: CallbackQuery, state: FSMContext):
"""Отмена действия со счетами"""
await state.clear()
await callback.message.edit_text("❌ Действие отменено")
await callback.answer()
@account_router.callback_query(F.data == "account_action:add_to_lottery")
async def choose_lottery_for_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, limit=20)
if not lotteries:
await callback.message.edit_text(
"❌ Нет активных розыгрышей.\n\n"
"Сначала создайте розыгрыш через /admin"
)
await state.clear()
await callback.answer()
return
# Формируем кнопки с розыгрышами
buttons = []
for lottery in lotteries:
buttons.append([InlineKeyboardButton(
text=f"🎲 {lottery.title[:40]}",
callback_data=f"add_accounts_to:{lottery.id}"
)])
buttons.append([InlineKeyboardButton(
text="❌ Отмена",
callback_data="account_action:cancel"
)])
keyboard = InlineKeyboardMarkup(inline_keyboard=buttons)
await callback.message.edit_text(
"📋 Выберите розыгрыш:",
reply_markup=keyboard,
parse_mode="HTML"
)
await state.set_state(AccountStates.waiting_for_lottery_choice)
await callback.answer()
@account_router.callback_query(F.data.startswith("add_accounts_to:"))
async def add_accounts_to_lottery(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])
# Получаем сохраненные счета
data = await state.get_data()
accounts = data.get("detected_accounts", [])
if not accounts:
await callback.message.edit_text("❌ Счета не найдены")
await state.clear()
await callback.answer()
return
# Показываем процесс
await callback.message.edit_text("⏳ Добавляем счета...")
async with async_session_maker() as session:
# Получаем информацию о розыгрыше
lottery = await LotteryService.get_lottery(session, lottery_id)
if not lottery:
await callback.message.edit_text("❌ Розыгрыш не найден")
await state.clear()
await callback.answer()
return
# Добавляем счета
results = await AccountParticipationService.add_accounts_bulk(
session, lottery_id, accounts
)
# Формируем результат
text = f"Результаты добавления в розыгрыш:\n{lottery.title}\n\n"
text += f"✅ Добавлено: {results['added']}\n"
text += f"⚠️ Пропущено: {results['skipped']}\n\n"
if results['details']:
text += "Детали:\n"
text += "\n".join(results['details'][:20]) # Показываем первые 20
if len(results['details']) > 20:
text += f"\n... и ещё {len(results['details']) - 20}"
if results['errors']:
text += f"\n\nОшибки:\n"
text += "\n".join(results['errors'][:10])
await callback.message.edit_text(text, parse_mode="HTML")
await state.clear()
await callback.answer("✅ Готово!")
@account_router.callback_query(F.data == "account_action:set_as_winner")
async def choose_lottery_for_winner(callback: CallbackQuery, state: FSMContext):
"""Выбор розыгрыша для установки победителя"""
if not is_admin(callback.from_user.id):
await callback.answer("⛔ Доступно только администраторам", show_alert=True)
return
# Проверяем, что у нас только один счет
data = await state.get_data()
accounts = data.get("detected_accounts", [])
if len(accounts) != 1:
await callback.message.edit_text(
"❌ Для установки победителя введите один счет",
parse_mode="HTML"
)
await state.clear()
await callback.answer()
return
async with async_session_maker() as session:
# Получаем все розыгрыши (активные и завершенные)
lotteries = await LotteryService.get_all_lotteries(session, limit=30)
if not lotteries:
await callback.message.edit_text(
"❌ Нет розыгрышей.\n\n"
"Сначала создайте розыгрыш через /admin"
)
await state.clear()
await callback.answer()
return
# Формируем кнопки
buttons = []
for lottery in lotteries:
status = "✅" if lottery.is_completed else "🎲"
buttons.append([InlineKeyboardButton(
text=f"{status} {lottery.title[:35]}",
callback_data=f"winner_lottery:{lottery.id}"
)])
buttons.append([InlineKeyboardButton(
text="❌ Отмена",
callback_data="account_action:cancel"
)])
keyboard = InlineKeyboardMarkup(inline_keyboard=buttons)
account = accounts[0]
await callback.message.edit_text(
f"👑 Установка победителя\n\n"
f"Счет: {account}\n\n"
f"Выберите розыгрыш:",
reply_markup=keyboard,
parse_mode="HTML"
)
await state.set_state(AccountStates.waiting_for_winner_lottery)
await callback.answer()
@account_router.callback_query(F.data.startswith("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])
# Сохраняем ID розыгрыша
await state.update_data(winner_lottery_id=lottery_id)
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
if not lottery:
await callback.message.edit_text("❌ Розыгрыш не найден")
await state.clear()
await callback.answer()
return
# Получаем призы
prizes = lottery.prizes or []
# Формируем кнопки с местами
buttons = []
for i, prize in enumerate(prizes[:10], 1): # Максимум 10 мест
prize_text = prize if isinstance(prize, str) else prize.get('description', f'Приз {i}')
buttons.append([InlineKeyboardButton(
text=f"🏆 Место {i}: {prize_text[:30]}",
callback_data=f"winner_place:{i}"
)])
# Если призов нет, предлагаем места 1-5
if not buttons:
for i in range(1, 6):
buttons.append([InlineKeyboardButton(
text=f"🏆 Место {i}",
callback_data=f"winner_place:{i}"
)])
buttons.append([InlineKeyboardButton(
text="❌ Отмена",
callback_data="account_action:cancel"
)])
keyboard = InlineKeyboardMarkup(inline_keyboard=buttons)
data = await state.get_data()
account = data.get("detected_accounts", [])[0]
await callback.message.edit_text(
f"👑 Установка победителя\n\n"
f"Розыгрыш: {lottery.title}\n"
f"Счет: {account}\n\n"
f"Выберите место:",
reply_markup=keyboard,
parse_mode="HTML"
)
await state.set_state(AccountStates.waiting_for_winner_place)
await callback.answer()
@account_router.callback_query(F.data.startswith("winner_place:"))
async def set_account_winner(callback: CallbackQuery, state: FSMContext):
"""Установка счета как победителя"""
if not is_admin(callback.from_user.id):
await callback.answer("⛔ Доступно только администраторам", show_alert=True)
return
place = int(callback.data.split(":")[1])
# Получаем данные
data = await state.get_data()
account = data.get("detected_accounts", [])[0]
lottery_id = data.get("winner_lottery_id")
if not account or not lottery_id:
await callback.message.edit_text("❌ Ошибка: данные не найдены")
await state.clear()
await callback.answer()
return
# Показываем процесс
await callback.message.edit_text("⏳ Устанавливаем победителя...")
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
# Получаем приз для этого места
prize = None
if lottery.prizes and len(lottery.prizes) >= place:
prize_info = lottery.prizes[place - 1]
prize = prize_info if isinstance(prize_info, str) else prize_info.get('description')
# Устанавливаем победителя
result = await AccountParticipationService.set_account_as_winner(
session, lottery_id, account, place, prize
)
if result["success"]:
text = (
f"✅ Победитель установлен!\n\n"
f"Розыгрыш: {lottery.title}\n"
f"Счет: {account}\n"
f"Место: {place}\n"
)
if prize:
text += f"Приз: {prize}"
await callback.answer("✅ Победитель установлен!", show_alert=True)
else:
text = f"❌ {result['message']}"
await callback.answer("❌ Ошибка", show_alert=True)
await callback.message.edit_text(text, parse_mode="HTML")
await state.clear()