import os from typing import Any from aiogram import F, Router from aiogram.filters import Command from aiogram.fsm.context import FSMContext from aiogram.types import ( CallbackQuery, InlineKeyboardButton, Message, ) from aiogram.utils.keyboard import InlineKeyboardBuilder from bot import bot from config import ( CAPTCHA_ENABLE, CHANNEL_EXISTS, CHANNEL_ID, CHANNEL_REQUIRED, CHANNEL_URL, DONATIONS_ENABLE, SUPPORT_CHAT_URL, ) from database import ( add_connection, add_referral, check_connection_exists, get_referral_by_referred_id, get_trial, update_balance, ) from handlers.captcha import generate_captcha from handlers.keys.key_management import create_key from handlers.texts import WELCOME_TEXT, get_about_vpn from keyboards.admin.panel_kb import AdminPanelCallback from logger import logger from .utils import edit_or_send_message router = Router() @router.callback_query(F.data == "start") async def handle_start_callback_query( callback_query: CallbackQuery, state: FSMContext, session: Any, admin: bool, captcha: bool = False ): await start_command(callback_query.message, state, session, admin, captcha) @router.message(Command("start")) async def start_command(message: Message, state: FSMContext, session: Any, admin: bool, captcha: bool = True): """Обрабатывает команду /start, включая логику проверки подписки, рефералов и подарков.""" logger.info(f"Вызвана функция start_command для пользователя {message.chat.id}") if CAPTCHA_ENABLE and captcha: captcha_data = await generate_captcha(message, state) await edit_or_send_message( target_message=message, text=captcha_data["text"], reply_markup=captcha_data["markup"], ) return state_data = await state.get_data() text_to_process = state_data.get("original_text", message.text) if CHANNEL_EXISTS and CHANNEL_REQUIRED: try: member = await bot.get_chat_member(CHANNEL_ID, message.chat.id) if member.status not in ["member", "administrator", "creator"]: await state.update_data(original_text=text_to_process) builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="✅ Я подписался", callback_data="check_subscription")) await edit_or_send_message( target_message=message, text=f"Для использования бота, пожалуйста, подпишитесь на наш канал: {CHANNEL_URL}", reply_markup=builder.as_markup(), ) return else: logger.info(f"Пользователь {message.chat.id} подписан на канал (статус: {member.status}). Продолжаем работу.") except Exception as e: logger.error(f"Ошибка проверки подписки пользователя {message.chat.id}: {e}") await state.update_data(start_text=text_to_process) builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="✅ Я подписался", callback_data="check_subscription")) await edit_or_send_message( target_message=message, text=f"Пожалуйста, подпишитесь на наш канал: {CHANNEL_URL}", reply_markup=builder.as_markup(), ) return await process_start_logic(message, state, session, admin, text_to_process) async def process_start_logic( message: Message, state: FSMContext, session: Any, admin: bool, text_to_process: str = None ): text = text_to_process if text_to_process is not None else message.text if text: try: connection_exists = await check_connection_exists(message.chat.id) logger.info(f"Проверка существования подключения: {connection_exists}") if not connection_exists: await add_connection(tg_id=message.chat.id, session=session) logger.info(f"Пользователь {message.chat.id} успешно добавлен в базу данных.") if "coupons_" in text: logger.info(f"Обнаружена ссылка на купон: {text}") coupon_code = text.split("coupons_")[1].strip() logger.info(f"Пользователь {message.chat.id} ввёл купон: {coupon_code}") coupon = await session.fetchrow( "SELECT id, code, amount, usage_limit, usage_count, is_used FROM coupons WHERE code = $1", coupon_code, ) if coupon is None: logger.warning(f"Купон {coupon_code} не найден.") await message.answer("❌ Купон не найден!") return await show_start_menu(message, admin, session) usage_exists = await session.fetchval( "SELECT 1 FROM coupon_usages WHERE coupon_id = $1 AND user_id = $2", coupon["id"], message.chat.id, ) if usage_exists: logger.info(f"Пользователь {message.chat.id} уже активировал купон {coupon_code}.") await message.answer("❌ Вы уже использовали этот купон!") return await show_start_menu(message, admin, session) if coupon["is_used"] or coupon["usage_count"] >= coupon["usage_limit"]: logger.info(f"Купон {coupon_code} уже использован или исчерпан.") await message.answer("❌ Этот купон уже использован!") return await show_start_menu(message, admin, session) await update_balance(message.chat.id, coupon["amount"]) logger.info(f"Начислено {coupon['amount']} единиц для пользователя {message.chat.id}") new_usage_count = coupon["usage_count"] + 1 is_used = new_usage_count >= coupon["usage_limit"] await session.execute( "UPDATE coupons SET usage_count = $1, is_used = $2 WHERE code = $3", new_usage_count, is_used, coupon_code, ) await session.execute( "INSERT INTO coupon_usages (coupon_id, user_id, used_at) VALUES ($1, $2, NOW())", coupon["id"], message.chat.id, ) logger.info( f"Купон {coupon_code} успешно использован пользователем {message.chat.id}, начислено {coupon['amount']} RUB." ) await message.answer(f"🎉 Ваш баланс пополнен на {coupon['amount']} RUB по купону!") return await show_start_menu(message, admin, session) if "gift_" in text: logger.info(f"Обнаружена ссылка на подарок: {text}") parts = text.split("gift_")[1].split("_") if len(parts) < 2: logger.error("Неверный формат ссылки на подарок: недостаточно частей после 'gift_'") await message.answer("❌ Неверный формат ссылки на подарок.") return await show_start_menu(message, admin, session) gift_id = parts[0] recipient_tg_id = message.chat.id gift_info = await session.fetchrow( """ SELECT sender_tg_id, selected_months, expiry_time, is_used, recipient_tg_id FROM gifts WHERE gift_id = $1 """, gift_id, ) if gift_info is None: logger.warning(f"Подарок с ID {gift_id} уже был использован или не существует.") await message.answer("Этот подарок уже был использован или не существует.") return await show_start_menu(message, admin, session) if gift_info["is_used"]: logger.warning(f"Подарок с ID {gift_id} уже был активирован ранее.") await message.answer("Этот подарок уже был использован.") return await show_start_menu(message, admin, session) if gift_info["sender_tg_id"] == recipient_tg_id: logger.warning(f"Пользователь {recipient_tg_id} попытался активировать свой же подарок.") await message.answer("❌ Вы не можете получить подарок от самого себя.") return await show_start_menu(message, admin, session) if gift_info["recipient_tg_id"] is not None: logger.warning( f"Подарок {gift_id} уже привязан к другому пользователю ({gift_info['recipient_tg_id']})." ) await message.answer("❌ Этот подарок уже был активирован другим пользователем.") return await show_start_menu(message, admin, session) if not connection_exists: await add_referral(recipient_tg_id, gift_info["sender_tg_id"], session) logger.info( f"Пользователь {recipient_tg_id} теперь является рефералом отправителя {gift_info['sender_tg_id']}." ) await session.execute( """ UPDATE connections SET trial = 1 WHERE tg_id = $1 """, recipient_tg_id, ) selected_months = gift_info["selected_months"] expiry_time = gift_info["expiry_time"].replace(tzinfo=None) logger.info(f"Подарок с ID {gift_id} успешно найден для пользователя {recipient_tg_id}.") await create_key(recipient_tg_id, expiry_time, state, session, message) logger.info(f"Ключ создан для пользователя {recipient_tg_id} на срок {selected_months} месяцев.") await session.execute( """ UPDATE gifts SET is_used = TRUE, recipient_tg_id = $1 WHERE gift_id = $2 """, recipient_tg_id, gift_id, ) await message.answer( f"🎉 Ваш подарок на {selected_months} " f"{'месяц' if selected_months == 1 else 'месяца' if selected_months in [2, 3, 4] else 'месяцев'} активирован!" ) logger.info(f"Подарок на {selected_months} месяцев активирован для пользователя {recipient_tg_id}.") return elif "referral_" in text: try: referrer_tg_id = int(text.split("referral_")[1]) if connection_exists: logger.info(f"Пользователь {message.chat.id} уже зарегистрирован и не может стать рефералом.") await message.answer("❌ Вы уже зарегистрированы и не можете использовать реферальную ссылку.") return await show_start_menu(message, admin, session) if referrer_tg_id == message.chat.id: logger.warning(f"Пользователь {message.chat.id} попытался стать рефералом самого себя.") await message.answer("❌ Вы не можете быть рефералом самого себя.") return await show_start_menu(message, admin, session) existing_referral = await get_referral_by_referred_id(message.chat.id, session) if existing_referral: logger.info(f"Реферал с ID {message.chat.id} уже существует.") return await show_start_menu(message, admin, session) await add_referral(message.chat.id, referrer_tg_id, session) logger.info(f"Реферал {message.chat.id} использовал ссылку от пользователя {referrer_tg_id}") await message.answer(f"Вы стали рефералом пользователя с ID {referrer_tg_id}") try: await bot.send_message(referrer_tg_id, f"🎉 Ваш реферал {message.chat.id} успешно зарегистрировался!") logger.info(f"Уведомление отправлено пользователю {referrer_tg_id} о новом реферале {message.chat.id}") except Exception as e: logger.error(f"Не удалось отправить уведомление пригласившему ({referrer_tg_id}): {e}") return await show_start_menu(message, admin, session) except (ValueError, IndexError) as e: logger.error(f"Ошибка при обработке реферальной ссылки: {e}") return else: logger.info(f"Пользователь {message.chat.id} зашел без реферальной ссылки, подарка или купона.") await show_start_menu(message, admin, session) except (ValueError, IndexError) as e: logger.error(f"Ошибка при обработке сообщения пользователя {message.chat.id}: {e}") await message.answer("❌ Произошла ошибка. Пожалуйста, попробуйте снова.") else: await show_start_menu(message, admin, session) @router.callback_query(F.data == "check_subscription") async def check_subscription_callback(callback_query: CallbackQuery, state: FSMContext, session: Any, admin: bool): user_id = callback_query.from_user.id logger.info(f"[CALLBACK] Получен callback 'check_subscription' от пользователя {user_id}") try: member = await bot.get_chat_member(CHANNEL_ID, user_id) logger.info(f"[CALLBACK] Статус подписки пользователя {user_id}: {member.status}") if member.status not in ["member", "administrator", "creator"]: await callback_query.answer("Вы еще не подписаны на канал!", show_alert=True) builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="✅ Я подписался", callback_data="check_subscription")) await callback_query.message.edit_text( f"Для использования бота, пожалуйста, подпишитесь на наш канал: {CHANNEL_URL}", reply_markup=builder.as_markup(), ) else: await callback_query.answer("Подписка подтверждена!") data = await state.get_data() original_text = data.get("original_text") if not original_text: original_text = callback_query.message.text await process_start_logic(callback_query.message, state, session, admin, text_to_process=original_text) logger.info(f"[CALLBACK] Завершен вызов process_start_logic для пользователя {user_id}") except Exception as e: logger.error(f"[CALLBACK] Ошибка проверки подписки для пользователя {user_id}: {e}", exc_info=True) await callback_query.answer("Ошибка проверки подписки, повторите попытку", show_alert=True) async def show_start_menu(message: Message, admin: bool, session: Any): """Функция для отображения стандартного меню через редактирование сообщения. Если редактирование не удалось, отправляем новое сообщение.""" logger.info(f"Показываю главное меню для пользователя {message.chat.id}") image_path = os.path.join("img", "pic.jpg") builder = InlineKeyboardBuilder() if session is not None: trial_status = await get_trial(message.chat.id, session) logger.info(f"Trial status для {message.chat.id}: {trial_status}") if trial_status == 0: builder.row(InlineKeyboardButton(text="🔗 Подключить VPN", callback_data="create_key")) else: logger.warning(f"Сессия базы данных отсутствует, пропускаем проверку триала для {message.chat.id}") builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) if CHANNEL_EXISTS: builder.row( InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL), InlineKeyboardButton(text="📢 Канал", url=CHANNEL_URL), ) else: builder.row(InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL)) if admin: builder.row( InlineKeyboardButton(text="🔧 Администратор", callback_data=AdminPanelCallback(action="admin").pack()) ) builder.row(InlineKeyboardButton(text="🌐 О VPN", callback_data="about_vpn")) await edit_or_send_message( target_message=message, text=WELCOME_TEXT, reply_markup=builder.as_markup(), media_path=image_path, ) @router.callback_query(F.data == "about_vpn") async def handle_about_vpn(callback_query: CallbackQuery): builder = InlineKeyboardBuilder() if DONATIONS_ENABLE: builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate")) builder.row(InlineKeyboardButton(text="📞 Техническая поддержка", url=SUPPORT_CHAT_URL)) if CHANNEL_EXISTS: builder.row(InlineKeyboardButton(text="📢 Канал", url=CHANNEL_URL)) builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="start")) text = get_about_vpn("3.2.3-minor") await edit_or_send_message( target_message=callback_query.message, text=text, reply_markup=builder.as_markup(), media_path=None, force_text=False, )