diff --git a/handlers/admin/admin_user_editor.py b/handlers/admin/admin_user_editor.py index bb5c956c..d579cb5f 100644 --- a/handlers/admin/admin_user_editor.py +++ b/handlers/admin/admin_user_editor.py @@ -223,7 +223,7 @@ async def get_key_details(email, session): cluster_name = "Неизвестный кластер" for cluster_name, cluster_servers in servers.items(): if any(server['inbound_id'] == record['server_id'] for server in cluster_servers): - cluster_name = cluster_name + cluster_name = cluster_name break expiry_date = datetime.utcfromtimestamp(record['expiry_time'] / 1000) @@ -242,7 +242,7 @@ async def get_key_details(email, session): 'key': record['key'], 'expiry_date': expiry_date.strftime("%d %B %Y года"), 'days_left_message': days_left_message, - 'server_name': cluster_name, + 'server_name': cluster_name, 'balance': record['balance'], 'tg_id': record['tg_id'], } diff --git a/handlers/instructions/instructions.py b/handlers/instructions/instructions.py index 5109c6df..efa48223 100644 --- a/handlers/instructions/instructions.py +++ b/handlers/instructions/instructions.py @@ -5,7 +5,7 @@ from aiogram import F, Router, types from aiogram.types import BufferedInputFile, InlineKeyboardButton from aiogram.utils.keyboard import InlineKeyboardBuilder -from config import CONNECT_WINDOWS, SUPPORT_CHAT_URL, CONNECT_IOS +from config import CONNECT_IOS, CONNECT_WINDOWS, SUPPORT_CHAT_URL from handlers.texts import INSTRUCTION_PC, INSTRUCTIONS, KEY_MESSAGE router = Router() diff --git a/handlers/keys/key_management.py b/handlers/keys/key_management.py index f281b80d..70701242 100644 --- a/handlers/keys/key_management.py +++ b/handlers/keys/key_management.py @@ -16,18 +16,11 @@ from config import ( DOWNLOAD_IOS, PUBLIC_LINK, RENEWAL_PLANS, + RENEWAL_PRICES, SUPPORT_CHAT_URL, TRIAL_TIME, ) -from database import ( - add_connection, - check_connection_exists, - get_balance, - get_trial, - store_key, - update_balance, - use_trial, -) +from database import get_balance, get_trial, store_key, update_balance from handlers.keys.key_utils import create_key_on_cluster from handlers.texts import KEY, NULL_BALANCE, key_message_success from handlers.utils import generate_random_email, get_least_loaded_cluster @@ -90,10 +83,77 @@ async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContex async def handle_key_creation( tg_id: int, state: FSMContext, session: Any, message_or_query: Union[Message, CallbackQuery] ): - """Создание ключа с рандомным именем.""" + """Создание ключа с учётом выбора тарифного плана.""" + current_time = datetime.utcnow() + trial_status = await get_trial(tg_id, session) + + if trial_status == 0: + expiry_time = current_time + timedelta(days=TRIAL_TIME) + logger.info(f"Assigned 1-day trial to user {tg_id}.") + await create_key(tg_id, expiry_time, state, session, message_or_query) + else: + builder = InlineKeyboardBuilder() + + for plan_id, price in RENEWAL_PRICES.items(): + discount_text = "" + if plan_id == "3": + discount_text = " (5% скидка)" + elif plan_id == "6": + discount_text = " (10% скидка)" + elif plan_id == "12": + discount_text = " (20% 🔥)" + + builder.row( + InlineKeyboardButton( + text=f"📅 {plan_id} мес. - {price}₽{discount_text}", callback_data=f"select_plan_{plan_id}" + ) + ) + + builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) + + await message_or_query.message.answer( + "💳 Выберите тарифный план для создания нового ключа:", reply_markup=builder.as_markup() + ) + await state.update_data(tg_id=tg_id) + await state.set_state(Form.waiting_for_server_selection) + + +@router.callback_query(F.data.startswith("select_plan_")) +async def select_tariff_plan(callback_query: CallbackQuery, state: FSMContext, session: Any): + tg_id = callback_query.message.chat.id + plan_id = callback_query.data.split("_")[-1] + plan_price = RENEWAL_PRICES.get(plan_id) + + if plan_price is None: + await callback_query.message.answer("🚫 Неверный тарифный план.") + return + + duration_days = int(plan_id) * 30 + + balance = await get_balance(tg_id) + if balance < plan_price: + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) + await callback_query.message.answer( + "💳 Недостаточно средств для создания подписки. Пополните баланс в личном кабинете.", + reply_markup=builder.as_markup(), + ) + await state.clear() + return + + await update_balance(tg_id, -plan_price) + + expiry_time = datetime.utcnow() + timedelta(days=duration_days) + + await create_key(tg_id, expiry_time, state, session, callback_query) + + +async def create_key( + tg_id: int, expiry_time: datetime, state: FSMContext, session: Any, message_or_query: Union[Message, CallbackQuery] +): + """Создаёт ключ с заданным сроком действия.""" while True: key_name = generate_random_email() - logger.info(f"Generated random key name for user {tg_id}: {key_name}") existing_key = await session.fetchrow( @@ -101,68 +161,15 @@ async def handle_key_creation( key_name, tg_id, ) - if not existing_key: break - - logger.warning( - f"Randomly generated key name '{key_name}' already exists for user {tg_id}. Generating a new one." - ) + logger.warning(f"Key name '{key_name}' already exists for user {tg_id}. Generating a new one.") client_id = str(uuid.uuid4()) email = key_name.lower() - current_time = datetime.utcnow() - expiry_time = None - - logger.info(f"Checking trial status for user {tg_id}.") - trial_status = await get_trial(tg_id, session) - - if trial_status == 0: - expiry_time = current_time + timedelta(days=TRIAL_TIME) - logger.info(f"Assigned 1-day trial to user {tg_id}.") - else: - balance = await get_balance(tg_id) - if balance < RENEWAL_PLANS["1"]["price"]: - builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) - await message_or_query.message.answer( - "💳 Недостаточно средств для создания подписки на новое устройство. Пополните баланс в личном кабинете.", - reply_markup=builder.as_markup(), - ) - logger.warning(f"User {tg_id} has insufficient funds for key creation.") - await state.clear() - return - - await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"]) - expiry_time = current_time + timedelta(days=30) - logger.info(f"User {tg_id} balance deducted for key creation.") - expiry_timestamp = int(expiry_time.timestamp() * 1000) public_link = f"{PUBLIC_LINK}{email}/{tg_id}" - logger.info(f"Generated public link for the key: {public_link}") - - builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL)) - builder.row( - InlineKeyboardButton(text="🍏 Скачать для iOS", url=DOWNLOAD_IOS), - InlineKeyboardButton(text="🤖 Скачать для Android", url=DOWNLOAD_ANDROID), - ) - builder.row( - InlineKeyboardButton(text="🍏 Подключить на iOS", url=f"{CONNECT_IOS}{public_link}"), - InlineKeyboardButton(text="🤖 Подключить на Android", url=f"{CONNECT_ANDROID}{public_link}"), - ) - builder.row(InlineKeyboardButton(text="💻 Windows/Linux", callback_data=f"connect_pc|{email}")) - builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) - - remaining_time = expiry_time - current_time - days = remaining_time.days - key_message = key_message_success(public_link, f"⏳ Осталось дней: {days} 📅") - - logger.info(f"Sending key message to user {tg_id} with the public link.") - - await message_or_query.message.answer(key_message, reply_markup=builder.as_markup()) - try: least_loaded_cluster = await get_least_loaded_cluster() @@ -179,18 +186,31 @@ async def handle_key_creation( ] await asyncio.gather(*tasks) + logger.info(f"Key created on cluster {least_loaded_cluster} for user {tg_id}.") - logger.info(f"Updating trial status for user {tg_id} in the database.") - connection_exists = await check_connection_exists(tg_id) - if connection_exists: - await use_trial(tg_id, session) - else: - await add_connection(tg_id=tg_id, balance=0, trial=1, session=session) - - logger.info(f"Storing key for user {tg_id} in the database.") await store_key(tg_id, client_id, email, expiry_timestamp, public_link, least_loaded_cluster, session) except Exception as e: - logger.error(f"Error while creating the key for user {tg_id}: {e}") - finally: - await state.clear() + logger.error(f"Error while creating the key for user {tg_id} on cluster: {e}") + await message_or_query.message.answer("❌ Произошла ошибка при создании ключа. Пожалуйста, попробуйте снова.") + return + + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL)) + builder.row( + InlineKeyboardButton(text="🍏 Скачать для iOS", url=DOWNLOAD_IOS), + InlineKeyboardButton(text="🤖 Скачать для Android", url=DOWNLOAD_ANDROID), + ) + builder.row( + InlineKeyboardButton(text="🍏 Подключить на iOS", url=f"{CONNECT_IOS}{public_link}"), + InlineKeyboardButton(text="🤖 Подключить на Android", url=f"{CONNECT_ANDROID}{public_link}"), + ) + builder.row(InlineKeyboardButton(text="💻 Windows/Linux", callback_data=f"connect_pc|{email}")) + builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) + + remaining_time = expiry_time - datetime.utcnow() + days = remaining_time.days + key_message = key_message_success(public_link, f"⏳ Осталось дней: {days} 📅") + + await message_or_query.message.answer(key_message, reply_markup=builder.as_markup()) + await state.clear()