diff --git a/handlers/start.py b/handlers/start.py index f1c9ce94..549822bf 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -1,22 +1,22 @@ import os - from aiogram import Router from aiogram.filters import Command from aiogram.fsm.state import State, StatesGroup from aiogram.types import (BufferedInputFile, CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message) from handlers.texts import ABOUT_VPN, WELCOME_TEXT - from bot import bot from config import CHANNEL_URL, SUPPORT_CHAT_URL -from database import add_connection, add_referral, check_connection_exists +from database import add_connection, add_referral, check_connection_exists, get_trial +from handlers.trial_key import create_trial_key +from handlers.texts import INSTRUCTIONS_TRIAL router = Router() class FeedbackState(StatesGroup): waiting_for_feedback = State() -async def send_welcome_message(chat_id: int): +async def send_welcome_message(chat_id: int, trial_status: int): welcome_text = WELCOME_TEXT image_path = os.path.join(os.path.dirname(__file__), 'pic.jpg') @@ -26,12 +26,15 @@ async def send_welcome_message(chat_id: int): return inline_keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text='🔗 Подключить VPN', callback_data='connect_vpn')] if trial_status == 0 else [], [InlineKeyboardButton(text='👤 Мой профиль', callback_data='view_profile')], [InlineKeyboardButton(text='🔒 О VPN', callback_data='about_vpn')], - [InlineKeyboardButton(text='📞 Поддержка', url=SUPPORT_CHAT_URL)], - [InlineKeyboardButton(text='📢 Наш канал', url=CHANNEL_URL)] + [InlineKeyboardButton(text='📞 Поддержка', url=SUPPORT_CHAT_URL)], + [InlineKeyboardButton(text='📢 Наш канал', url=CHANNEL_URL)], ]) + inline_keyboard.inline_keyboard = [row for row in inline_keyboard.inline_keyboard if row] + with open(image_path, 'rb') as image_from_buffer: await bot.send_photo( chat_id, @@ -49,13 +52,43 @@ async def start_command(message: Message): print(f"Referral ID: {referrer_tg_id}") if not await check_connection_exists(message.from_user.id): await add_connection(message.from_user.id) - await add_referral(message.from_user.id, referrer_tg_id) - await message.answer("Вас пригласил друг, добро пожаловать!") else: await message.answer("Вы уже зарегистрированы в системе!") - await send_welcome_message(message.chat.id) + + trial_status = await get_trial(message.from_user.id) + await send_welcome_message(message.chat.id, trial_status) + +@router.callback_query(lambda c: c.data == 'connect_vpn') +async def handle_connect_vpn(callback_query: CallbackQuery): + await callback_query.message.delete() + user_id = callback_query.from_user.id + + # Создаём триальный ключ + trial_key_info = await create_trial_key(user_id) + + if 'error' in trial_key_info: + await callback_query.message.answer(trial_key_info['error']) + else: + # Формируем сообщение с ключом и инструкциями + key_message = ( + f"Ваш ключ доступа:\n
{trial_key_info['key']}
\n\n" + f"Инструкции:\n{INSTRUCTIONS_TRIAL}" + ) + + # Кнопка "В профиль" + button_profile = InlineKeyboardButton(text='👤 Мой профиль', callback_data='view_profile') + inline_keyboard_profile = InlineKeyboardMarkup(inline_keyboard=[[button_profile]]) + + # Отправляем текст с ключом и инструкциями в виде цитаты + await callback_query.message.answer( + key_message, + parse_mode='HTML', + reply_markup=inline_keyboard_profile + ) + + await callback_query.answer() @router.callback_query(lambda c: c.data == 'about_vpn') async def handle_about_vpn(callback_query: CallbackQuery): @@ -75,5 +108,6 @@ async def handle_about_vpn(callback_query: CallbackQuery): @router.callback_query(lambda c: c.data == 'back_to_menu') async def handle_back_to_menu(callback_query: CallbackQuery): await callback_query.message.delete() - await send_welcome_message(callback_query.from_user.id) + trial_status = await get_trial(callback_query.from_user.id) + await send_welcome_message(callback_query.from_user.id, trial_status) await callback_query.answer() diff --git a/handlers/texts.py b/handlers/texts.py index 11d04a3e..173bda4f 100644 --- a/handlers/texts.py +++ b/handlers/texts.py @@ -68,15 +68,28 @@ INSTRUCTIONS = ( "💬 Если у вас возникнут вопросы, не стесняйтесь обращаться в [поддержку](https://t.me/solonet_sup)." ) -KEY_EXPIRY_10H = "🔔 Уведомление: Ваш ключ для сервера {server_id} истекает через 10 часов.\n" \ - "Email: {email}\n" \ - "Дата истечения: {expiry_date}" +INSTRUCTIONS_TRIAL = ( + "📋 Инструкции по использованию вашего ключа:\n\n" + "1. Скачайте приложение для вашего устройства:\n" + " - Для Android: V2Ray\n" + " - Для iPhone: Streisand\n" + " - Для Windows: Hiddify Next\n\n" + "2. Скопируйте предоставленный ключ, который вы получили ранее.\n" + "3. Откройте приложение и нажмите на плюсик сверху справа.\n" + "4. Выберите 'Вставить из буфера обмена' для добавления ключа.\n\n" + "💬 Если у вас возникнут вопросы, не стесняйтесь обращаться в поддержку." +) -KEY_EXPIRY_24H = "⏳ Уведомление: Ваш ключ для сервера {server_id} истекает через 24 часа.\n" \ - "Email: {email}\n" \ + +KEY_EXPIRY_10H = "🔔 Уведомление: Ваш ключ {email} для сервера {server_id} истекает через 10 часов.\n" \ + "Дата истечения: {expiry_date}"\ + "Перейдите в профиль и пополните баланс, всего 100 рублей на целый месяц"\ + "ключ продлится автоматически" + +KEY_EXPIRY_24H = "🔔 Уведомление: Ваш ключ {email} для сервера {server_id} истекает через 24 часа.\n" \ "Осталось времени: {hours_left} часов\n" \ "Дата истечения: {expiry_date}\n" \ - "Баланс: {balance:.2f} руб." + "Баланс: {balance:.2f} руб."\ KEY_RENEWED = "Ваш ключ был продлен на месяц." KEY_RENEWAL_FAILED = "Не удалось продлить ключ на панели, обратитесь в поддержку." diff --git a/handlers/trial_key.py b/handlers/trial_key.py new file mode 100644 index 00000000..db3388d2 --- /dev/null +++ b/handlers/trial_key.py @@ -0,0 +1,67 @@ +import random +import asyncpg +import time +import uuid # Импортируем модуль для генерации UUID +from config import DATABASE_URL, SERVERS, ADMIN_USERNAME, ADMIN_PASSWORD +from auth import login_with_credentials, link # Импортируйте необходимые функции +from client import add_client +from database import store_key, add_connection +from handlers.texts import INSTRUCTIONS +from datetime import datetime, timedelta + +def generate_random_email(): + """Генерирует случайный набор символов.""" + random_string = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789', k=6)) + return random_string + +async def create_trial_key(tg_id: int): + conn = await asyncpg.connect(DATABASE_URL) + try: + server_id = await get_least_loaded_server(conn) + session = await login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD) + current_time = datetime.utcnow() + + expiry_time = current_time + timedelta(days=30, hours=3) + expiry_timestamp = int(expiry_time.timestamp() * 1000) + + client_id = str(uuid.uuid4()) + email = generate_random_email() + response = await add_client( + session, server_id, client_id, email, tg_id, + limit_ip=1, total_gb=0, expiry_time=expiry_timestamp, + enable=True, flow="xtls-rprx-vision" + ) + if response.get("success"): + connection_link = await link(session, server_id, client_id, email) + + 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) + + await store_key(tg_id, client_id, email, expiry_timestamp, connection_link, server_id) + + instructions = INSTRUCTIONS + return { + 'key': connection_link, + 'instructions': instructions + } + else: + return {'error': 'Не удалось добавить клиента на панель'} + finally: + await conn.close() + +async def get_least_loaded_server(conn): + least_loaded_server_id = None + min_load_percentage = float('inf') + + for server_id, server in SERVERS.items(): + count = await conn.fetchval('SELECT COUNT(*) FROM keys WHERE server_id = $1', server_id) + percent_full = (count / 60) * 100 if count <= 60 else 100 + if percent_full < min_load_percentage: + min_load_percentage = percent_full + least_loaded_server_id = server_id + + return least_loaded_server_id