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, SHOW_START_MENU_ONCE, SUPPORT_CHAT_URL, ) from database import ( add_referral, get_referral_by_referred_id, get_trial, add_user, check_user_exists ) from handlers.buttons import ABOUT_VPN, BACK, CHANNEL, MAIN_MENU, SUPPORT, TRIAL_SUB from handlers.captcha import generate_captcha from handlers.coupons import activate_coupon from handlers.keys.key_mode.key_create import create_key from handlers.profile import process_callback_view_profile from handlers.texts import ( GIFT_ALREADY_USED_OR_NOT_EXISTS_MSG, NEW_REFERRAL_NOTIFICATION, NOT_SUBSCRIBED_YET_MSG, REFERRAL_SUCCESS_MSG, SUBSCRIPTION_CHECK_ERROR_MSG, SUBSCRIPTION_CONFIRMED_MSG, SUBSCRIPTION_REQUIRED_MSG, WELCOME_TEXT, get_about_vpn, ) from logger import logger from .admin.panel.keyboard import AdminPanelCallback 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): logger.info(f"Вызвана функция start_command для пользователя {message.chat.id}") if CAPTCHA_ENABLE and captcha: user_exists = await check_user_exists(message.chat.id) if not user_exists: 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=SUBSCRIPTION_REQUIRED_MSG, 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=SUBSCRIPTION_REQUIRED_MSG, 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: if "coupons_" in text: logger.info(f"Обнаружена ссылка на купон: {text}") coupon_code = text.split("coupons_")[1] await activate_coupon(message, state, session, coupon_code=coupon_code, admin=admin) return if "gift_" in text: parts = text.split("gift_")[1].split("_") if len(parts) < 2: await message.answer("❌ Неверный формат ссылки на подарок.") return await process_callback_view_profile(message, state, admin) gift_id = parts[0] async with session.transaction(): gift_info = await session.fetchrow( """ SELECT sender_tg_id, selected_months, expiry_time, is_used, recipient_tg_id FROM gifts WHERE gift_id = $1 FOR UPDATE """, gift_id, ) if not gift_info: await message.answer(GIFT_ALREADY_USED_OR_NOT_EXISTS_MSG) return await process_callback_view_profile(message, state, admin) if gift_info["is_used"]: await message.answer("Этот подарок уже был использован.") return await process_callback_view_profile(message, state, admin) if gift_info["sender_tg_id"] == message.chat.id: await message.answer("❌ Вы не можете получить подарок от самого себя.") return await process_callback_view_profile(message, state, admin) if gift_info["recipient_tg_id"]: await message.answer("❌ Этот подарок уже был активирован другим пользователем.") return await process_callback_view_profile(message, state, admin) existing_referral = await get_referral_by_referred_id(message.chat.id, session) if not existing_referral: await add_referral(message.chat.id, gift_info["sender_tg_id"], session) user_exists = await check_user_exists(message.chat.id) if not user_exists: from_user = message.from_user await add_user( tg_id=from_user.id, username=from_user.username, first_name=from_user.first_name, last_name=from_user.last_name, language_code=from_user.language_code, is_bot=from_user.is_bot, session=session, ) await session.execute("UPDATE users SET trial = 1 WHERE tg_id = $1", message.chat.id) await create_key( message.chat.id, gift_info["expiry_time"].replace(tzinfo=None), state, session, message, ) await session.execute( "UPDATE gifts SET is_used = TRUE, recipient_tg_id = $1 WHERE gift_id = $2", message.chat.id, gift_id, ) await message.answer( f"🎉 Ваш подарок на {gift_info['selected_months']} " f"{'месяц' if gift_info['selected_months'] == 1 else 'месяца' if gift_info['selected_months'] in [2, 3, 4] else 'месяцев'} активирован!" ) return if "referral_" in text: try: referrer_tg_id = int(text.split("referral_")[1]) user_exists_now = await check_user_exists(message.chat.id) if user_exists_now: await message.answer("❌ Вы уже зарегистрированы и не можете использовать реферальную ссылку.") return await process_callback_view_profile(message, state, admin) if referrer_tg_id == message.chat.id: await message.answer("❌ Вы не можете быть рефералом самого себя.") return await process_callback_view_profile(message, state, admin) existing_referral = await get_referral_by_referred_id(message.chat.id, session) if existing_referral: return await process_callback_view_profile(message, state, admin) await add_referral(message.chat.id, referrer_tg_id, session) from_user = message.from_user await add_user( tg_id=from_user.id, username=from_user.username, first_name=from_user.first_name, last_name=from_user.last_name, language_code=from_user.language_code, is_bot=from_user.is_bot, session=session, ) await message.answer(REFERRAL_SUCCESS_MSG.format(referrer_tg_id=referrer_tg_id)) try: await bot.send_message( referrer_tg_id, NEW_REFERRAL_NOTIFICATION.format(referred_id=message.chat.id), ) except Exception as e: logger.error(f"Не удалось отправить уведомление пригласившему ({referrer_tg_id}): {e}") return await process_callback_view_profile(message, state, admin) except (ValueError, IndexError): pass logger.info("Пользователь зашел без реферальной ссылки, подарка или купона.") except Exception as e: logger.error(f"Ошибка при обработке текста {message.text} — {e}", exc_info=True) await message.answer("❌ Произошла ошибка. Попробуйте позже.") return final_exists = await check_user_exists(message.chat.id) if final_exists: if SHOW_START_MENU_ONCE: return await process_callback_view_profile(message, state, admin) else: return await show_start_menu(message, admin, session) else: from_user = message.from_user await add_user( tg_id=from_user.id, username=from_user.username, first_name=from_user.first_name, last_name=from_user.last_name, language_code=from_user.language_code, is_bot=from_user.is_bot, session=session, ) return 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(NOT_SUBSCRIBED_YET_MSG, show_alert=True) builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="✅ Я подписался", callback_data="check_subscription")) await callback_query.message.edit_text( SUBSCRIPTION_REQUIRED_MSG, reply_markup=builder.as_markup(), ) else: await callback_query.answer(SUBSCRIPTION_CONFIRMED_MSG) 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(SUBSCRIPTION_CHECK_ERROR_MSG, 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=TRIAL_SUB, callback_data="create_key")) else: logger.warning(f"Сессия базы данных отсутствует, пропускаем проверку триала для {message.chat.id}") if not SHOW_START_MENU_ONCE: builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) if CHANNEL_EXISTS: builder.row( InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL), InlineKeyboardButton(text=CHANNEL, url=CHANNEL_URL), ) else: builder.row(InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL)) if admin: builder.row( InlineKeyboardButton(text="📊 Администратор", callback_data=AdminPanelCallback(action="admin").pack()) ) builder.row(InlineKeyboardButton(text=ABOUT_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")) support_btn = InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL) if CHANNEL_EXISTS: channel_btn = InlineKeyboardButton(text=CHANNEL, url=CHANNEL_URL) builder.row(support_btn, channel_btn) else: builder.row(support_btn) builder.row(InlineKeyboardButton(text=BACK, callback_data="start")) text = get_about_vpn("3.2.3-minor") image_path = os.path.join("img", "pic.jpg") await edit_or_send_message( target_message=callback_query.message, text=text, reply_markup=builder.as_markup(), media_path=image_path, force_text=False, )