import re import uuid import asyncio from datetime import datetime, timedelta from aiogram import Router, types, F from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, Message from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from auth import login_with_credentials, link from client import add_client from config import API_TOKEN, ADMIN_PASSWORD, ADMIN_USERNAME, ADMIN_CHAT_ID, DATABASE_URL from database import add_connection, has_active_key, get_balance, store_key, update_balance from bot import dp, bot from handlers.profile import process_callback_view_profile from handlers.start import start_command import asyncpg router = Router() # Удаляем специальные символы из имени ключа def sanitize_key_name(key_name: str) -> str: return re.sub(r'[^a-z0-9@._-]', '', key_name.lower()) class Form(StatesGroup): waiting_for_key_name = State() viewing_profile = 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 # Получаем данные о trial из базы данных conn = await asyncpg.connect(DATABASE_URL) try: 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 == 1: await callback_query.message.edit_text( "У вас уже есть активный ключ. Вы можете создать новый ключ за дополнительную плату в размере 100 рублей. " "Хотите продолжить?", 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 callback_query.message.edit_text("Вам будет выдан пробный ключ. Пожалуйста, выберите имя для вашего ключа:") 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 # Проверяем баланс перед созданием нового ключа balance = await get_balance(tg_id) if balance < 100: replenish_button = InlineKeyboardButton(text='Перейти в профиль', callback_data='view_profile') keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]]) await callback_query.message.edit_text("Недостаточно средств на балансе для создания нового ключа.", reply_markup=keyboard) await state.clear() return await callback_query.message.edit_text("Пожалуйста, выберите имя для вашего нового ключа:") await state.set_state(Form.waiting_for_key_name) 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() # Обработка текстовых сообщений @dp.message() async def handle_text(message: Message, state: FSMContext): current_state = await state.get_state() if message.text == "Мой профиль": callback_query = types.CallbackQuery( id="1", from_user=message.from_user, chat_instance='', data='view_profile', message=message ) await process_callback_view_profile(callback_query, state) return if message.text in ["/start", "Меню"]: await start_command(message) return if current_state == Form.waiting_for_key_name.state: await handle_key_name_input(message, state) async def handle_key_name_input(message: Message, state: FSMContext): tg_id = message.from_user.id key_name = sanitize_key_name(message.text) if not key_name: await message.reply("Имя ключа не указано. Введите имя на английском языке.") await state.clear() return data = await state.get_data() creating_new_key = data.get('creating_new_key', False) session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) client_id = str(uuid.uuid4()) email = key_name.lower() current_time = datetime.utcnow() expiry_time = int((current_time + timedelta(days=1)).timestamp() * 1000) if creating_new_key: balance = await get_balance(tg_id) if balance < 100: replenish_button = InlineKeyboardButton(text='Перейти в профиль', callback_data='view_profile') keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]]) await message.reply("Недостаточно средств на балансе для создания нового ключа.", reply_markup=keyboard) await state.clear() return await update_balance(tg_id, -100) try: add_client(session, client_id, email, tg_id, limit_ip=1, total_gb=0, expiry_time=expiry_time, enable=True, flow="xtls-rprx-vision") connection_link = link(session, client_id, email) # Проверка существующей записи conn = await asyncpg.connect(DATABASE_URL) try: 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() await store_key(tg_id, client_id, email, expiry_time, connection_link) keyboard = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions')], [InlineKeyboardButton(text='Перейти в профиль', callback_data='view_profile')] ]) key_message = f"Ключ создан:\n
{connection_link}
" await message.reply(key_message, parse_mode="HTML", reply_markup=keyboard) except Exception as e: await message.reply(f"Ошибка при создании ключа: {e}") await state.clear() @dp.callback_query(F.data == 'instructions') async def handle_instructions(callback_query: CallbackQuery): instructions_message = ( "*Инструкции по использованию вашего ключа:*\n\n" "1. Скачайте приложение для вашего устройства:\n" " - Для Android: [V2Ray](https://play.google.com/store/apps/details?id=com.v2ray.ang&hl=ru&pli=1)\n" " - Для iPhone: [Streisand](https://apps.apple.com/ru/app/streisand/id6450534064)\n\n" "2. Скопируйте предоставленный ключ, который вы получили ранее.\n" "3. Откройте приложение и нажмите на плюсик сверху справа.\n" "4. Выберите 'Вставить из буфера обмена' для добавления ключа.\n\n" "Если у вас возникнут вопросы, не стесняйтесь обращаться в поддержку." ) back_button = InlineKeyboardButton(text='Назад', callback_data='back_to_main') keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]]) await callback_query.message.edit_text(instructions_message, parse_mode='Markdown', reply_markup=keyboard) await callback_query.answer() @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() async def renew_expired_keys(): while True: current_time = datetime.utcnow() conn = await asyncpg.connect(DATABASE_URL) try: active_keys = await conn.fetch('SELECT tg_id FROM connections WHERE trial > 0') finally: await conn.close() for record in active_keys: tg_id = record['tg_id'] balance = await get_balance(tg_id) if balance >= 100: new_expiry_time = int((current_time + timedelta(days=30)).timestamp() * 1000) conn = await asyncpg.connect(DATABASE_URL) try: await conn.execute('UPDATE keys SET expiry_time = $1 WHERE tg_id = $2', new_expiry_time, tg_id) await update_balance(tg_id, -100) finally: await conn.close() print(f"Ключ для пользователя {tg_id} продлен на месяц и списано 100 рублей.") else: print(f"Недостаточно средств на балансе для пользователя {tg_id}. Предложение пополнить баланс.") replenish_keyboard = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance')] ]) await bot.send_message(tg_id, "Ваш баланс недостаточен для продления ключа. Пожалуйста, пополните баланс.", reply_markup=replenish_keyboard) await asyncio.sleep(3600)