import asyncio import uuid from datetime import datetime, timedelta import asyncpg from aiogram import F, Router from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message from loguru import logger from bot import bot, dp from config import CONNECT_ANDROID, CONNECT_IOS, DATABASE_URL, DOWNLOAD_ANDROID, DOWNLOAD_IOS, PUBLIC_LINK, SERVERS from database import add_connection, get_balance, store_key, update_balance from handlers.instructions.instructions import send_instructions from handlers.keys.key_utils import create_key_on_server from handlers.profile import process_callback_view_profile from handlers.texts import KEY, KEY_TRIAL, NULL_BALANCE, RENEWAL_PLANS, key_message_success from handlers.utils import sanitize_key_name router = Router() class Form(StatesGroup): waiting_for_server_selection = State() waiting_for_key_name = State() viewing_profile = State() waiting_for_message = State() @dp.callback_query(F.data == "create_key") async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext): tg_id = callback_query.from_user.id try: await bot.delete_message( chat_id=tg_id, message_id=callback_query.message.message_id ) except Exception: pass server_id = "все сервера" await state.update_data(selected_server_id=server_id) await select_server(callback_query, state) await callback_query.answer() async def select_server(callback_query: CallbackQuery, state: FSMContext): conn = await asyncpg.connect(DATABASE_URL) try: existing_connection = await conn.fetchrow( "SELECT trial FROM connections WHERE tg_id = $1", callback_query.from_user.id, ) finally: await conn.close() trial_status = existing_connection["trial"] if existing_connection else 0 if trial_status == 1: await bot.send_message( chat_id=callback_query.from_user.id, text=KEY, parse_mode="HTML", reply_markup=InlineKeyboardMarkup( inline_keyboard=[ [ InlineKeyboardButton( text="✅ Да, подключить новое устройство", callback_data="confirm_create_new_key", ) ], [ InlineKeyboardButton( text="↩️ Назад", callback_data="cancel_create_key" ) ], ] ), ) await state.update_data(creating_new_key=True) else: await bot.send_message( chat_id=callback_query.from_user.id, text=KEY_TRIAL, parse_mode="HTML" ) await state.set_state(Form.waiting_for_key_name) await callback_query.answer() @dp.callback_query(F.data == "confirm_create_new_key") async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext): tg_id = callback_query.from_user.id logger.info(f"User {tg_id} confirmed creation of a new key.") balance = await get_balance(tg_id) if balance < RENEWAL_PLANS["1"]["price"]: replenish_button = InlineKeyboardButton( text="Перейти в профиль", callback_data="view_profile" ) keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]]) await callback_query.message.edit_text(NULL_BALANCE, reply_markup=keyboard) await state.clear() return logger.info(f"Balance for user {tg_id} is sufficient. Asking for device name.") await callback_query.message.edit_text( "🔑 Пожалуйста, введите имя подключаемого устройства:" ) await state.set_state(Form.waiting_for_key_name) logger.info(f"State set to waiting_for_key_name for user {tg_id}") await state.update_data(creating_new_key=True) await callback_query.answer() @dp.callback_query(F.data == "cancel_create_key") async def cancel_create_key(callback_query: CallbackQuery, state: FSMContext): await process_callback_view_profile(callback_query, state) await callback_query.answer() @router.message(Form.waiting_for_key_name) async def handle_key_name_input(message: Message, state: FSMContext): tg_id = message.from_user.id key_name = sanitize_key_name(message.text) logger.info(f"User {tg_id} is attempting to create a key with the name: {key_name}") if not key_name: await message.bot.send_message( tg_id, "📝 Пожалуйста, назовите устройство на английском языке." ) logger.warning(f"User {tg_id} entered an invalid key name: {key_name}") return conn = await asyncpg.connect(DATABASE_URL) try: logger.info( f"Checking if key name '{key_name}' already exists in the database." ) existing_key = await conn.fetchrow( "SELECT * FROM keys WHERE email = $1", key_name.lower() ) if existing_key: await message.bot.send_message( tg_id, "❌ Упс! Это имя уже используется. Выберите другое уникальное название для ключа.", ) logger.warning( f"Key name '{key_name}' already exists in the database for user {tg_id}." ) await state.set_state(Form.waiting_for_key_name) return finally: await conn.close() client_id = str(uuid.uuid4()) email = key_name.lower() current_time = datetime.utcnow() expiry_time = None conn = await asyncpg.connect(DATABASE_URL) try: logger.info(f"Checking trial status for user {tg_id}.") existing_connection = await conn.fetchrow( "SELECT trial FROM connections WHERE tg_id = $1", tg_id ) finally: await conn.close() trial_status = existing_connection["trial"] if existing_connection else 0 if trial_status == 0: expiry_time = current_time + timedelta(days=1, hours=3) logger.info(f"Assigned 1-day trial to user {tg_id}.") else: balance = await get_balance(tg_id) if balance < RENEWAL_PLANS["1"]["price"]: replenish_button = InlineKeyboardButton( text="Перейти в профиль", callback_data="view_profile" ) keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]]) await message.bot.send_message( tg_id, "💳 Недостаточно средств для создания подписки на новое устройство. Пополните баланс в личном кабинете.", reply_markup=keyboard, ) 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, hours=3) logger.info(f"User {tg_id} balance deducted for key creation.") expiry_timestamp = int(expiry_time.timestamp() * 1000) public_link = f"{PUBLIC_LINK}{email}" logger.info(f"Generated public link for the key: {public_link}") button_profile = InlineKeyboardButton( text="👤 Личный кабинет", callback_data="view_profile" ) button_iphone = InlineKeyboardButton( text="🍏 Подключить", url=f"{CONNECT_IOS}{public_link}" ) button_android = InlineKeyboardButton( text="🤖 Подключить", url=f"{CONNECT_ANDROID}{public_link}", ) button_download_ios = InlineKeyboardButton(text="🍏 Скачать", url=DOWNLOAD_IOS) button_download_android = InlineKeyboardButton( text="🤖 Скачать", url=DOWNLOAD_ANDROID, ) keyboard = InlineKeyboardMarkup( inline_keyboard=[ [button_download_ios, button_download_android], [button_iphone, button_android], [button_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.bot.send_message( tg_id, key_message, parse_mode="HTML", reply_markup=keyboard ) try: tasks = [] for server_id in SERVERS: tasks.append( asyncio.create_task( create_key_on_server( server_id, tg_id, client_id, email, expiry_timestamp ) ) ) await asyncio.gather(*tasks) conn = await asyncpg.connect(DATABASE_URL) try: logger.info(f"Updating trial status for user {tg_id} in the database.") existing_connection = await conn.fetchrow( "SELECT * FROM connections WHERE tg_id = $1", tg_id ) if existing_connection: await conn.execute( "UPDATE connections SET trial = 1 WHERE tg_id = $1", tg_id ) else: await add_connection(tg_id, 0, 1) finally: await conn.close() logger.info(f"Storing key for user {tg_id} in the database.") await store_key( tg_id, client_id, email, expiry_timestamp, public_link, "all_servers" ) except Exception as e: logger.error(f"Error while creating the key for user {tg_id}: {e}") await message.bot.send_message(tg_id, f"❌ Ошибка при создании ключа: {e}") await state.clear() @dp.callback_query(F.data == "instructions") async def handle_instructions(callback_query: CallbackQuery): await send_instructions(callback_query) @dp.callback_query(F.data == "back_to_main") async def handle_back_to_main(callback_query: CallbackQuery, state: FSMContext): await process_callback_view_profile(callback_query, state) await callback_query.answer()