from aiogram import Bot, Dispatcher, Router, F from aiogram.types import ( Message, CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, BotCommand ) from aiogram.filters import Command, StateFilter from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from aiogram.fsm.storage.memory import MemoryStorage from sqlalchemy.ext.asyncio import AsyncSession import asyncio import logging import signal import sys from src.core.config import BOT_TOKEN, ADMIN_IDS from src.core.database import async_session_maker, init_db from src.core.services import UserService, LotteryService, ParticipationService from src.core.models import User from src.handlers.admin_panel import admin_router from src.handlers.account_handlers import account_router from src.handlers.registration_handlers import router as registration_router from src.handlers.admin_account_handlers import router as admin_account_router from src.handlers.redraw_handlers import router as redraw_router from src.handlers.chat_handlers import router as chat_router from src.handlers.admin_chat_handlers import router as admin_chat_router from src.utils.async_decorators import ( async_user_action, admin_async_action, db_operation, TaskManagerMiddleware, shutdown_task_manager, format_task_stats, TaskPriority ) from src.utils.account_utils import validate_account_number, format_account_number from src.display.winner_display import format_winner_display # Настройка логирования logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Состояния для FSM class CreateLotteryStates(StatesGroup): waiting_for_title = State() waiting_for_description = State() waiting_for_prizes = State() class SetWinnerStates(StatesGroup): waiting_for_lottery_id = State() waiting_for_place = State() waiting_for_user_id = State() class AccountStates(StatesGroup): waiting_for_account_number = State() # Инициализация бота bot = Bot(token=BOT_TOKEN) storage = MemoryStorage() dp = Dispatcher(storage=storage) router = Router() # Подключаем middleware для управления задачами dp.message.middleware(TaskManagerMiddleware()) dp.callback_query.middleware(TaskManagerMiddleware()) def is_admin(user_id: int) -> bool: """Проверка, является ли пользователь администратором""" return user_id in ADMIN_IDS def get_main_keyboard(is_admin_user: bool = False) -> InlineKeyboardMarkup: """Главная клавиатура""" buttons = [ [InlineKeyboardButton(text="🎲 Активные розыгрыши", callback_data="list_lotteries")] ] if not is_admin_user: buttons.extend([ [InlineKeyboardButton(text="📝 Мои участия", callback_data="my_participations")], [InlineKeyboardButton(text="💳 Мой счёт", callback_data="my_account")] ]) if is_admin_user: buttons.extend([ [InlineKeyboardButton(text="🔧 Админ-панель", callback_data="admin_panel")], [InlineKeyboardButton(text="➕ Создать розыгрыш", callback_data="create_lottery")], [InlineKeyboardButton(text="📊 Статистика задач", callback_data="task_stats")] ]) return InlineKeyboardMarkup(inline_keyboard=buttons) @router.message(Command("start")) async def cmd_start(message: Message): """Обработчик команды /start""" async with async_session_maker() as session: user = await UserService.get_or_create_user( session, telegram_id=message.from_user.id, username=message.from_user.username, first_name=message.from_user.first_name, last_name=message.from_user.last_name ) # Устанавливаем права администратора, если пользователь в списке if message.from_user.id in ADMIN_IDS: await UserService.set_admin(session, message.from_user.id, True) is_registered = user.is_registered is_admin_user = is_admin(message.from_user.id) welcome_text = f"Добро пожаловать, {message.from_user.first_name}! 🎉\n\n" welcome_text += "Это бот для проведения розыгрышей.\n\n" # Для обычных пользователей - проверяем регистрацию if not is_admin_user and not is_registered: welcome_text += "⚠️ Для участия в розыгрышах необходимо пройти регистрацию.\n\n" buttons = [ [InlineKeyboardButton(text="📝 Зарегистрироваться", callback_data="start_registration")], [InlineKeyboardButton(text="🎲 Активные розыгрыши", callback_data="list_lotteries")] ] await message.answer( welcome_text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons) ) return welcome_text += "Выберите действие из меню ниже:" if is_admin_user: welcome_text += "\n\n👑 У вас есть права администратора!" await message.answer( welcome_text, reply_markup=get_main_keyboard(is_admin_user) ) @router.callback_query(F.data == "list_lotteries") async def show_active_lotteries(callback: CallbackQuery): """Показать активные розыгрыши""" 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="back_to_main")] ]) ) return text = "🎲 Активные розыгрыши:\n\n" buttons = [] for lottery in lotteries: async with async_session_maker() as session: participants_count = await ParticipationService.get_participants_count( session, lottery.id ) text += f"🎯 {lottery.title}\n" text += f"👥 Участников: {participants_count}\n" text += f"📅 Создан: {lottery.created_at.strftime('%d.%m.%Y %H:%M')}\n\n" buttons.append([ InlineKeyboardButton( text=f"🎲 {lottery.title}", callback_data=f"lottery_{lottery.id}" ) ]) buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_main")]) await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons) ) @router.callback_query(F.data.startswith("lottery_")) async def show_lottery_details(callback: CallbackQuery): """Показать детали розыгрыша""" lottery_id = int(callback.data.split("_")[1]) async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) user = await UserService.get_user_by_telegram_id(session, callback.from_user.id) if not lottery: await callback.answer("Розыгрыш не найден", show_alert=True) return participants_count = await ParticipationService.get_participants_count(session, lottery_id) # Проверяем, участвует ли пользователь is_participating = any( p.user_id == user.id for p in lottery.participations ) if user else False text = f"🎯 {lottery.title}\n\n" text += f"📋 Описание: {lottery.description or 'Не указано'}\n\n" if lottery.prizes: text += "🏆 Призы:\n" for i, prize in enumerate(lottery.prizes, 1): text += f"{i}. {prize}\n" text += "\n" text += f"👥 Участников: {participants_count}\n" text += f"📅 Создан: {lottery.created_at.strftime('%d.%m.%Y %H:%M')}\n" if lottery.is_completed: text += "\n✅ Розыгрыш завершен" # Показываем победителей async with async_session_maker() as session: winners = await LotteryService.get_winners(session, lottery_id) if winners: text += "\n\n🏆 Победители:\n" for winner in winners: # Безопасное отображение победителя if winner.user: if winner.user.username: winner_display = f"@{winner.user.username}" else: winner_display = f"{winner.user.first_name}" elif winner.account_number: winner_display = f"Счет: {winner.account_number}" else: winner_display = "Участник" text += f"{winner.place}. {winner_display} - {winner.prize}\n" else: text += f"\n🟢 Статус: {'Активен' if lottery.is_active else 'Неактивен'}" if is_participating: text += "\n✅ Вы участвуете в розыгрыше" buttons = [] if not lottery.is_completed and lottery.is_active and not is_participating: buttons.append([ InlineKeyboardButton( text="🎫 Участвовать", callback_data=f"join_{lottery_id}" ) ]) if is_admin(callback.from_user.id) and not lottery.is_completed: buttons.append([ InlineKeyboardButton( text="🎲 Провести розыгрыш", callback_data=f"conduct_{lottery_id}" ) ]) buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="list_lotteries")]) await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons) ) @router.callback_query(F.data.startswith("join_")) async def join_lottery(callback: CallbackQuery): """Присоединиться к розыгрышу""" lottery_id = int(callback.data.split("_")[1]) async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, callback.from_user.id) if not user: await callback.answer("Ошибка получения данных пользователя", show_alert=True) return # Используем правильный метод ParticipationService success = await ParticipationService.add_participant(session, lottery_id, user.id) if success: await callback.answer("✅ Вы успешно присоединились к розыгрышу!", show_alert=True) else: await callback.answer("❌ Вы уже участвуете в этом розыгрыше", show_alert=True) # Обновляем информацию о розыгрыше await show_lottery_details(callback) async def notify_winners_async(bot: Bot, lottery_id: int, results: dict): """ Асинхронно отправить уведомления победителям с кнопкой подтверждения Вызывается после проведения розыгрыша """ async with async_session_maker() as session: from src.core.registration_services import AccountService, WinnerNotificationService from src.core.models import Winner from sqlalchemy import select # Получаем информацию о розыгрыше lottery = await LotteryService.get_lottery(session, lottery_id) if not lottery: return # Получаем всех победителей из БД winners_result = await session.execute( select(Winner).where(Winner.lottery_id == lottery_id) ) winners = winners_result.scalars().all() for winner in winners: try: # Если у победителя есть account_number, ищем владельца if winner.account_number: owner = await AccountService.get_account_owner(session, winner.account_number) if owner and owner.telegram_id: # Создаем токен верификации verification = await WinnerNotificationService.create_verification_token( session, winner.id ) # Формируем сообщение с кнопкой подтверждения message = ( f"🎉 **Поздравляем! Ваш счет выиграл!**\n\n" f"🎯 Розыгрыш: {lottery.title}\n" f"🏆 Место: {winner.place}\n" f"🎁 Приз: {winner.prize}\n" f"💳 **Выигрышный счет: {winner.account_number}**\n\n" f"⏰ **У вас есть 24 часа для подтверждения!**\n\n" f"Нажмите кнопку ниже, чтобы подтвердить получение приза по этому счету.\n" f"Если вы не подтвердите в течение 24 часов, " f"приз будет разыгран заново.\n\n" f"ℹ️ Если у вас несколько выигрышных счетов, " f"подтвердите каждый из них отдельно." ) # Создаем кнопку подтверждения с указанием счета keyboard = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton( text=f"✅ Подтвердить счет {winner.account_number}", callback_data=f"confirm_win_{winner.id}" )], [InlineKeyboardButton( text="📞 Связаться с администратором", url=f"tg://user?id={ADMIN_IDS[0]}" )] ]) # Отправляем уведомление с кнопкой await bot.send_message( owner.telegram_id, message, reply_markup=keyboard, parse_mode="Markdown" ) # Отмечаем, что уведомление отправлено winner.is_notified = True await session.commit() logger.info(f"Отправлено уведомление победителю {owner.telegram_id} за счет {winner.account_number}") # Если победитель - обычный пользователь (старая система) elif winner.user_id: user_result = await session.execute( select(User).where(User.id == winner.user_id) ) user = user_result.scalar_one_or_none() if user and user.telegram_id: message = ( f"🎉 Поздравляем! Вы выиграли!\n\n" f"🎯 Розыгрыш: {lottery.title}\n" f"🏆 Место: {winner.place}\n" f"🎁 Приз: {winner.prize}\n\n" f"⏰ **У вас есть 24 часа для подтверждения!**\n\n" f"Нажмите кнопку ниже, чтобы подтвердить получение приза." ) keyboard = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton( text="✅ Подтвердить получение приза", callback_data=f"confirm_win_{winner.id}" )] ]) await bot.send_message( user.telegram_id, message, reply_markup=keyboard, parse_mode="Markdown" ) winner.is_notified = True await session.commit() logger.info(f"Отправлено уведомление победителю {user.telegram_id}") except Exception as e: logger.error(f"Ошибка при отправке уведомления победителю: {e}") @router.callback_query(F.data.startswith("confirm_win_")) async def confirm_winner_response(callback: CallbackQuery): """Обработка подтверждения выигрыша победителем""" winner_id = int(callback.data.split("_")[2]) async with async_session_maker() as session: from src.core.models import Winner from sqlalchemy import select from sqlalchemy.orm import joinedload # Получаем выигрыш с загрузкой связанного розыгрыша winner_result = await session.execute( select(Winner) .options(joinedload(Winner.lottery)) .where(Winner.id == winner_id) ) winner = winner_result.scalar_one_or_none() if not winner: await callback.answer("❌ Выигрыш не найден", show_alert=True) return # Проверяем, не подтвержден ли уже этот конкретный счет if winner.is_claimed: await callback.message.edit_text( "✅ **Выигрыш этого счета уже подтвержден!**\n\n" f"🎯 Розыгрыш: {winner.lottery.title}\n" f"🏆 Место: {winner.place}\n" f"🎁 Приз: {winner.prize}\n" f"💳 Счет: {winner.account_number}\n\n" "Администратор свяжется с вами для передачи приза.", parse_mode="Markdown" ) return # Проверяем, что подтверждает владелец именно ЭТОГО счета user = await UserService.get_user_by_telegram_id(session, callback.from_user.id) if winner.account_number: # Проверяем что счет принадлежит текущему пользователю from src.core.registration_services import AccountService owner = await AccountService.get_account_owner(session, winner.account_number) if not owner or owner.telegram_id != callback.from_user.id: await callback.answer( f"❌ Счет {winner.account_number} вам не принадлежит", show_alert=True ) return elif winner.user_id: # Старая логика для выигрышей без счета if not user or user.id != winner.user_id: await callback.answer("❌ Это не ваш выигрыш", show_alert=True) return # Подтверждаем выигрыш ЭТОГО конкретного счета from datetime import datetime, timezone winner.is_claimed = True winner.claimed_at = datetime.now(timezone.utc) await session.commit() # Обновляем сообщение с указанием счета confirmation_text = ( "✅ **Выигрыш успешно подтвержден!**\n\n" f"🎯 Розыгрыш: {winner.lottery.title}\n" f"🏆 Место: {winner.place}\n" f"🎁 Приз: {winner.prize}\n" ) if winner.account_number: confirmation_text += f"💳 Счет: {winner.account_number}\n" confirmation_text += ( "\n🎊 Поздравляем! Администратор свяжется с вами " "для передачи приза в ближайшее время.\n\n" "Спасибо за участие!" ) await callback.message.edit_text( confirmation_text, parse_mode="Markdown" ) # Уведомляем администраторов о подтверждении конкретного счета for admin_id in ADMIN_IDS: try: admin_msg = ( f"✅ **Победитель подтвердил получение приза!**\n\n" f"🎯 Розыгрыш: {winner.lottery.title}\n" f"🏆 Место: {winner.place}\n" f"🎁 Приз: {winner.prize}\n" ) # Обязательно показываем счет if winner.account_number: admin_msg += f"� **Подтвержденный счет: {winner.account_number}**\n\n" if user: admin_msg += f"👤 Владелец: {user.first_name}" if user.username: admin_msg += f" (@{user.username})" admin_msg += f"\n🎫 Клубная карта: {user.club_card_number}\n" if user.phone: admin_msg += f"📱 Телефон: {user.phone}\n" await callback.bot.send_message(admin_id, admin_msg, parse_mode="Markdown") except: pass logger.info( f"Победитель {callback.from_user.id} подтвердил выигрыш {winner_id} " f"(счет: {winner.account_number})" ) await callback.answer("✅ Выигрыш подтвержден!", show_alert=True) @router.callback_query(F.data.startswith("conduct_")) async def conduct_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) if not lottery: await callback.answer("❌ Розыгрыш не найден", show_alert=True) return results = await LotteryService.conduct_draw(session, lottery_id) if not results: await callback.answer("❌ Не удалось провести розыгрыш", show_alert=True) return text = "🎉 Розыгрыш завершен!\n\n🏆 Победители:\n\n" for place, winner_info in results.items(): user_obj = winner_info['user'] prize = winner_info['prize'] # Безопасное отображение победителя if hasattr(user_obj, 'username') and user_obj.username: winner_display = f"@{user_obj.username}" elif hasattr(user_obj, 'first_name'): winner_display = f"{user_obj.first_name}" elif hasattr(user_obj, 'account_number'): winner_display = f"Счет: {user_obj.account_number}" else: winner_display = "Участник" text += f"{place}. {winner_display}\n" text += f" 🎁 {prize}\n\n" # Отправляем уведомления победителям асинхронно asyncio.create_task(notify_winners_async(callback.bot, lottery_id, results)) text += "📨 Уведомления отправляются победителям...\n" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 К розыгрышам", callback_data="list_lotteries")] ]) ) # Создание розыгрыша @router.callback_query(F.data == "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 await callback.message.edit_text( "📝 Создание нового розыгрыша\n\n" "Введите название розыгрыша:", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="❌ Отмена", callback_data="back_to_main")] ]) ) await state.set_state(CreateLotteryStates.waiting_for_title) @router.message(StateFilter(CreateLotteryStates.waiting_for_title)) async def process_lottery_title(message: Message, state: FSMContext): """Обработка названия розыгрыша""" await state.update_data(title=message.text) await message.answer( "📋 Введите описание розыгрыша (или отправьте '-' для пропуска):" ) await state.set_state(CreateLotteryStates.waiting_for_description) @router.message(StateFilter(CreateLotteryStates.waiting_for_description)) async def process_lottery_description(message: Message, state: FSMContext): """Обработка описания розыгрыша""" description = None if message.text == "-" else message.text await state.update_data(description=description) await message.answer( "🏆 Введите призы через новую строку:\n\n" "Пример:\n" "1000 рублей\n" "iPhone 15\n" "Подарочный сертификат" ) await state.set_state(CreateLotteryStates.waiting_for_prizes) @router.message(StateFilter(CreateLotteryStates.waiting_for_prizes)) async def process_lottery_prizes(message: Message, state: FSMContext): """Обработка призов розыгрыша""" prizes = [prize.strip() for prize in message.text.split('\n') if prize.strip()] async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, message.from_user.id) if not user: await message.answer("❌ Ошибка получения данных пользователя") await state.clear() return data = await state.get_data() lottery = await LotteryService.create_lottery( session, title=data['title'], description=data['description'], prizes=prizes, creator_id=user.id ) await state.clear() text = f"✅ Розыгрыш успешно создан!\n\n" text += f"🎯 Название: {lottery.title}\n" text += f"📋 Описание: {lottery.description or 'Не указано'}\n\n" text += f"🏆 Призы:\n" for i, prize in enumerate(prizes, 1): text += f"{i}. {prize}\n" await message.answer( text, reply_markup=get_main_keyboard(is_admin(message.from_user.id)) ) # Установка ручного победителя @router.callback_query(F.data == "set_winner") async def start_set_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="back_to_main")] ]) ) return text = "👑 Установка ручного победителя\n\n" text += "Выберите розыгрыш:\n\n" buttons = [] for lottery in lotteries: text += f"🎯 {lottery.title} (ID: {lottery.id})\n" buttons.append([ InlineKeyboardButton( text=f"{lottery.title}", callback_data=f"setwinner_{lottery.id}" ) ]) buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_main")]) await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons) ) @router.callback_query(F.data.startswith("setwinner_")) async def select_winner_place(callback: CallbackQuery, state: FSMContext): """Выбор места для ручного победителя""" 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 3 text = f"👑 Установка ручного победителя для розыгрыша:\n" text += f"🎯 {lottery.title}\n\n" text += f"Введите номер места (1-{num_prizes}):" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="❌ Отмена", callback_data="set_winner")] ]) ) await state.set_state(SetWinnerStates.waiting_for_place) @router.message(StateFilter(SetWinnerStates.waiting_for_place)) async def process_winner_place(message: Message, state: FSMContext): """Обработка места победителя""" try: place = int(message.text) if place < 1: raise ValueError except ValueError: await message.answer("❌ Введите корректный номер места (положительное число)") return await state.update_data(place=place) await message.answer( f"👑 Установка ручного победителя на {place} место\n\n" "Введите Telegram ID пользователя:" ) await state.set_state(SetWinnerStates.waiting_for_user_id) @router.message(StateFilter(SetWinnerStates.waiting_for_user_id)) async def process_winner_user_id(message: Message, state: FSMContext): """Обработка ID пользователя-победителя""" try: telegram_id = int(message.text) except ValueError: await message.answer("❌ Введите корректный Telegram 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: await message.answer( f"✅ Ручной победитель установлен!\n\n" f"🏆 Место: {data['place']}\n" f"👤 Telegram ID: {telegram_id}", reply_markup=get_main_keyboard(is_admin(message.from_user.id)) ) else: await message.answer( "❌ Не удалось установить ручного победителя.\n" "Проверьте, что пользователь существует в системе.", reply_markup=get_main_keyboard(is_admin(message.from_user.id)) ) @router.callback_query(F.data == "my_participations") async def show_my_participations(callback: CallbackQuery): """Показать участие пользователя в розыгрышах""" async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, callback.from_user.id) if not user: await callback.answer("Ошибка получения данных пользователя", show_alert=True) return participations = await ParticipationService.get_user_participations(session, user.id) if not participations: await callback.message.edit_text( "📝 Вы пока не участвуете в розыгрышах", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_main")] ]) ) return text = "📝 Ваши участия в розыгрышах:\n\n" for participation in participations: lottery = participation.lottery status = "✅ Завершен" if lottery.is_completed else "🟢 Активен" text += f"🎯 {lottery.title}\n" text += f"📊 Статус: {status}\n" text += f"📅 Участие с: {participation.created_at.strftime('%d.%m.%Y %H:%M')}\n\n" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_main")] ]) ) # Хэндлеры для работы с номерами счетов @router.callback_query(F.data == "my_account") @db_operation() async def show_my_account(callback: CallbackQuery): """Показать информацию о счетах пользователя""" async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, callback.from_user.id) if not user: await callback.answer("Пользователь не найден", show_alert=True) return # Проверяем регистрацию if not user.is_registered: text = "❌ **Вы не зарегистрированы**\n\n" text += "Пройдите регистрацию для доступа к счетам" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="📝 Зарегистрироваться", callback_data="start_registration")], [InlineKeyboardButton(text="🔙 Главное меню", callback_data="back_to_main")] ]), parse_mode="Markdown" ) return # Получаем счета пользователя from src.core.registration_services import AccountService accounts = await AccountService.get_user_accounts(session, user.id) text = "💳 **Ваши счета**\n\n" if accounts: text += f"🎫 Клубная карта: `{user.club_card_number}`\n" text += f"� Код верификации: `{user.verification_code}`\n\n" text += f"**Счета ({len(accounts)}):**\n\n" for i, acc in enumerate(accounts, 1): status = "✅ Активен" if acc.is_active else "❌ Неактивен" text += f"{i}. `{acc.account_number}`\n" text += f" {status}\n\n" text += "ℹ️ Счета используются для участия в розыгрышах" else: text += f"🎫 Клубная карта: `{user.club_card_number}`\n\n" text += "❌ У вас нет счетов\n\n" text += "Обратитесь к администратору для добавления счетов" buttons = [[InlineKeyboardButton(text="🔙 Главное меню", callback_data="back_to_main")]] await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons), parse_mode="Markdown" ) @router.callback_query(F.data.in_(["add_account", "change_account"])) @db_operation() async def start_account_setup(callback: CallbackQuery, state: FSMContext): """Начало процесса привязки/изменения счёта""" await state.set_state(AccountStates.waiting_for_account_number) action = "привязки" if callback.data == "add_account" else "изменения" text = f"💳 **Процедура {action} счёта**\n\n" text += "Введите номер вашего клиентского счёта в формате:\n" text += "`12-34-56-78-90-12-34`\n\n" text += "📝 **Требования:**\n" text += "• Ровно 14 цифр\n" text += "• Разделены дефисами через каждые 2 цифры\n" text += "• Номер должен быть уникальным\n\n" text += "✉️ Отправьте номер счёта в ответном сообщении" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="❌ Отмена", callback_data="my_account")] ]), parse_mode="Markdown" ) @router.message(StateFilter(AccountStates.waiting_for_account_number)) @db_operation() async def process_account_number(message: Message, state: FSMContext): """Обработка введённого номера счёта""" account_input = message.text.strip() # Форматируем и валидируем номер formatted_number = format_account_number(account_input) if not formatted_number: await message.answer( "❌ **Некорректный формат номера счёта**\n\n" "Номер должен содержать ровно 14 цифр.\n" "Пример правильного формата: `12-34-56-78-90-12-34`\n\n" "Попробуйте ещё раз:", parse_mode="Markdown" ) return async with async_session_maker() as session: # Проверяем уникальность existing_user = await UserService.get_user_by_account(session, formatted_number) if existing_user and existing_user.telegram_id != message.from_user.id: await message.answer( "❌ **Номер счёта уже используется**\n\n" "Данный номер счёта уже привязан к другому пользователю.\n" "Убедитесь, что вы вводите правильный номер.\n\n" "Попробуйте ещё раз:" ) return # Обновляем номер счёта success = await UserService.set_account_number( session, message.from_user.id, formatted_number ) if success: await state.clear() await message.answer( f"✅ **Счёт успешно привязан!**\n\n" f"💳 Номер счёта: `{formatted_number}`\n\n" f"Теперь вы можете участвовать в розыгрышах.\n" f"Ваш номер счёта будет использоваться для идентификации.", parse_mode="Markdown", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🏠 Главное меню", callback_data="back_to_main")] ]) ) else: await message.answer( "❌ **Ошибка привязки счёта**\n\n" "Произошла ошибка при сохранении номера счёта.\n" "Попробуйте ещё раз или обратитесь к администратору.", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="my_account")] ]) ) @router.callback_query(F.data == "task_stats") @admin_async_action() async def show_task_stats(callback: CallbackQuery): """Показать статистику задач (только для админов)""" if not is_admin(callback.from_user.id): await callback.answer("Доступ запрещён", show_alert=True) return stats_text = await format_task_stats() await callback.message.edit_text( stats_text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔄 Обновить", callback_data="task_stats")], [InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_main")] ]), parse_mode="Markdown" ) @router.callback_query(F.data == "back_to_main") async def back_to_main(callback: CallbackQuery, state: FSMContext): """Вернуться в главное меню""" await state.clear() is_admin_user = is_admin(callback.from_user.id) await callback.message.edit_text( "🏠 Главное меню\n\nВыберите действие:", reply_markup=get_main_keyboard(is_admin_user) ) async def set_commands(): """Установка команд бота""" commands = [ BotCommand(command="start", description="🚀 Запустить бота"), ] await bot.set_my_commands(commands) async def main(): """Главная функция""" # Инициализация базы данных await init_db() # Установка команд await set_commands() # Подключение роутеров dp.include_router(registration_router) # Роутер регистрации (первый) dp.include_router(admin_account_router) # Роутер админских команд для счетов dp.include_router(admin_chat_router) # Роутер админских команд чата (до обычных обработчиков) dp.include_router(redraw_router) # Роутер повторного розыгрыша dp.include_router(account_router) # Роутер для работы со счетами dp.include_router(chat_router) # Роутер чата пользователей (ПОСЛЕДНИМ!) dp.include_router(router) dp.include_router(admin_router) # Обработка сигналов для graceful shutdown def signal_handler(): logger.info("Получен сигнал завершения, остановка бота...") asyncio.create_task(shutdown_task_manager()) # Настройка обработчиков сигналов if sys.platform != "win32": for sig in (signal.SIGTERM, signal.SIGINT): asyncio.get_event_loop().add_signal_handler(sig, signal_handler) # Запуск бота logger.info("Бот запущен") try: await dp.start_polling(bot) finally: # Остановка менеджера задач при завершении await shutdown_task_manager() if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: logger.info("Бот остановлен пользователем") except Exception as e: logger.error(f"Критическая ошибка: {e}") finally: logger.info("Завершение работы")