""" Обработчики для работы со счетами в розыгрышах """ 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()