diff --git a/bot.py b/bot.py index ed451561..e284f260 100644 --- a/bot.py +++ b/bot.py @@ -1,314 +1,25 @@ -from aiogram import Bot, Dispatcher, Router, F, types -from aiogram.filters import Command -from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, Message, ReplyKeyboardMarkup, KeyboardButton +from aiogram import Bot, Dispatcher, Router from aiogram.fsm.storage.memory import MemoryStorage -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 datetime import datetime, timedelta -from config import API_TOKEN, ADMIN_PASSWORD, ADMIN_USERNAME, ADMIN_CHAT_ID, DATABASE_PATH -from database import add_connection, has_active_key, get_active_key_email, get_balance, store_key -import uuid -import aiosqlite -import re - -def escape_markdown(text: str) -> str: - """ - Экранирование специальных символов для Markdown. - """ - escape_chars = r'_*[]()~`>#+-=|{}.!' - return re.sub(r'([%s])' % re.escape(escape_chars), r'\\\1', text) - -class Form(StatesGroup): - waiting_for_key_name = State() - waiting_for_admin_confirmation = State() - waiting_for_statistics = State() - waiting_for_expiry_date = State() - viewing_profile = State() +from config import API_TOKEN +from auth import login_with_credentials bot = Bot(token=API_TOKEN) storage = MemoryStorage() dp = Dispatcher(bot=bot, storage=storage) router = Router() -# Создаем сессию при старте бота -session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) - -def create_main_menu_keyboard(): - button_start = KeyboardButton(text="В начало") - return ReplyKeyboardMarkup(keyboard=[[button_start]], resize_keyboard=True) - -@dp.message(Command("start")) -async def start_command(message: Message): - welcome_text = "Добро пожаловать! Вы можете создать ключ для подключения VPN, просмотреть статистику использования, узнать дату окончания ключа или просмотреть ваш профиль." - - button_create_key = InlineKeyboardButton(text='Создать ключ', callback_data='create_key') - button_view_stats = InlineKeyboardButton(text='Посмотреть статистику', callback_data='view_stats') - button_view_expiry = InlineKeyboardButton(text='Дата окончания ключа', callback_data='view_expiry') - button_view_profile = InlineKeyboardButton(text='Мой профиль', callback_data='view_profile') - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [button_create_key], - [button_view_stats], - [button_view_expiry], - [button_view_profile] - ]) - - main_menu_keyboard = create_main_menu_keyboard() - - await message.reply(welcome_text, reply_markup=keyboard) - await message.answer("Чтобы вернуться в начало, нажмите кнопку ниже:", reply_markup=main_menu_keyboard) +from handlers import start, profile, keys, stats, expiry, balance +from key_management import router as key_management_router -@dp.callback_query(F.data == 'view_profile') -async def process_callback_view_profile(callback_query: types.CallbackQuery, state: FSMContext): - tg_id = callback_query.from_user.id - username = callback_query.from_user.username # Получаем никнейм пользователя - - try: - email = await get_active_key_email(tg_id) - if email: - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute("SELECT expiry_time FROM connections WHERE tg_id = ? AND expiry_time > ?", - (tg_id, int(datetime.utcnow().timestamp() * 1000))) as cursor: - record = await cursor.fetchone() - if record: - expiry_time = record[0] - expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime("%Y-%m-%d %H:%M:%S") - else: - expiry_date = "Неизвестно" - - # Получите баланс клиента (замените пример на вашу реальную логику получения баланса) - balance = await get_balance(tg_id) # Предполагается, что у вас есть функция для получения баланса - - profile_message = ( - f"Профиль клиента:\n" - f"Никнейм: @{username}\n" # Добавляем никнейм - f"Email: {email}\n" - f"Дата окончания ключа: {expiry_date}\n" - f"Баланс: {balance}\n" - ) - - # Кнопка для просмотра ключей - button_view_keys = InlineKeyboardButton(text='Мои ключи', callback_data='view_keys') - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [button_view_keys] - ]) - - profile_message += "Вы можете просмотреть ваши ключи ниже:" - else: - profile_message = "У вас нет активных ключей." - keyboard = None - - except Exception as e: - profile_message = f"Ошибка при получении данных профиля: {e}" - keyboard = None - - await callback_query.message.reply(profile_message, reply_markup=keyboard) - await callback_query.answer() +# Регистрация обработчиков +dp.include_router(start.router) +dp.include_router(profile.router) +dp.include_router(keys.router) +dp.include_router(stats.router) +dp.include_router(expiry.router) +dp.include_router(balance.router) +dp.include_router(key_management_router) -@dp.callback_query(F.data == 'view_keys') -async def process_callback_view_keys(callback_query: types.CallbackQuery): - tg_id = callback_query.from_user.id - - try: - async with aiosqlite.connect(DATABASE_PATH) as db: - # Извлекаем ключ для текущего пользователя по его Telegram ID - async with db.execute(''' - SELECT k.key - FROM keys k - JOIN connections c ON k.client_id = c.client_id - WHERE c.tg_id = ? - ''', (tg_id,)) as cursor: - record = await cursor.fetchone() - - if record: - key = record[0] - # Форматируем текст для цитаты - response_message = f"Ваш ключ:\n
{key}
" - else: - response_message = "У вас нет ключей." - - except Exception as e: - response_message = f"Ошибка при получении ключей: {e}" - - # Отправляем сообщение - await bot.send_message(tg_id, response_message, parse_mode="HTML", reply_to_message_id=callback_query.message.message_id) - await callback_query.answer() - - - - -@dp.callback_query(F.data == 'create_key') -async def process_callback_create_key(callback_query: types.CallbackQuery, state: FSMContext): - await callback_query.message.reply("Введите имя вашего профиля VPN:") - await state.set_state(Form.waiting_for_key_name) - await callback_query.answer() - -@dp.message() -async def handle_text(message: types.Message, state: FSMContext): - current_state = await state.get_state() - - if message.text == "В начало": - await start_command(message) - return - - if current_state == Form.waiting_for_key_name.state: - tg_id = message.from_user.id - key_name = message.text - tg_name = message.from_user.username - - if not key_name: - await message.reply("Имя клиента не указано.") - await state.clear() - return - - if await has_active_key(tg_id): - await message.reply("У вас уже есть активный ключ. Вы не можете создать новый.") - await state.clear() - return - - # Сохраняем информацию о запросе в контексте состояния - await state.update_data(key_name=key_name, tg_id=tg_id) - - # Отправляем запрос на подтверждение админу - admin_message = ( - f"Новый запрос на создание ключа:\n" - f"Телеграм-ID: {tg_id}\n" - f"Имя ключа: {key_name}\n" - f"Имя пользователя Telegram: @{tg_name}\n" - f"Введите 'yes' для подтверждения или 'no' для отклонения." - ) - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='Да', callback_data=f'confirm_key_{tg_id}_{key_name}')], - [InlineKeyboardButton(text='Нет', callback_data=f'reject_key_{tg_id}')] - ]) - await bot.send_message(ADMIN_CHAT_ID, admin_message, reply_markup=keyboard) - - await state.set_state(Form.waiting_for_admin_confirmation) - await message.reply("Ожидайте подтверждения администратора.") - await state.clear() - -@dp.callback_query(F.data.startswith('confirm_key_')) -async def handle_admin_confirmation(callback_query: CallbackQuery, state: FSMContext): - if callback_query.message.chat.id != int(ADMIN_CHAT_ID): - return - - data = callback_query.data.split('_', 3) - tg_id = int(data[2]) - key_name = data[3] - - try: - client_id = str(uuid.uuid4()) - email = key_name - limit_ip = 1 - total_gb = 0 - current_time = datetime.utcnow() - expiry_time = int((current_time + timedelta(days=30)).timestamp() * 1000) - enable = True - flow = "xtls-rprx-vision" - balance = 0 - - add_client(session, client_id, email, tg_id, limit_ip, total_gb, expiry_time, enable, flow) - - connection_link = link(session, client_id, email) - - await add_connection(tg_id, client_id, email, expiry_time, balance) - await store_key(client_id, email, connection_link) - await bot.send_message(tg_id, f"Ключ создан:\n
{connection_link}
", parse_mode="HTML") - await callback_query.message.reply("Ключ создан и отправлен клиенту.") - except Exception as e: - await callback_query.message.reply(f"Ошибка при создании ключа: {e}") - - await callback_query.answer() - -@dp.callback_query(F.data.startswith('reject_key_')) -async def handle_rejection(callback_query: CallbackQuery, state: FSMContext): - if callback_query.message.chat.id != int(ADMIN_CHAT_ID): - return - - tg_id = int(callback_query.data.split('_', 3)[2]) - await bot.send_message(tg_id, "Создание ключа отклонено.") - await callback_query.message.reply("Запрос отклонен.") - await callback_query.answer() - -@dp.callback_query(F.data == 'view_stats') -async def process_callback_view_stats(callback_query: types.CallbackQuery, state: FSMContext): - tg_id = callback_query.from_user.id - - try: - email = await get_active_key_email(tg_id) - if email: - # Получаем client_id из базы данных - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute("SELECT client_id FROM connections WHERE tg_id = ? AND expiry_time > ?", - (tg_id, int(datetime.utcnow().timestamp() * 1000))) as cursor: - record = await cursor.fetchone() - if record: - client_id = record[0] - # Передаем оба параметра в функцию link - connection_link = link(session, client_id, email) - - # Извлечение данных о загрузке и выгрузке из ссылки - up_match = re.search(r'up=(\d+)', connection_link) - down_match = re.search(r'down=(\d+)', connection_link) - - up = up_match.group(1) if up_match else "Неизвестно" - down = down_match.group(1) if down_match else "Неизвестно" - - statistics = f"Статистика вашего ключа:\nЗагрузка: {up} MB\nВыгрузка: {down} MB" - else: - statistics = "У вас нет активных ключей." - else: - statistics = "У вас нет активных ключей." - - except Exception as e: - statistics = f"Ошибка при получении статистики: {e}" - - await callback_query.message.reply(f"Ваша статистика:\n{statistics}") - await callback_query.answer() - - -@dp.callback_query(F.data == 'view_expiry') -async def process_callback_view_expiry(callback_query: types.CallbackQuery): - tg_id = callback_query.from_user.id - - try: - email = await get_active_key_email(tg_id) - if email: - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute("SELECT expiry_time FROM connections WHERE tg_id = ? AND expiry_time > ?", - (tg_id, int(datetime.utcnow().timestamp() * 1000))) as cursor: - record = await cursor.fetchone() - if record: - expiry_time = record[0] - expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime("%Y-%m-%d %H:%M:%S") - message_text = f"Дата окончания вашего ключа: {expiry_date}" - else: - message_text = "У вас нет активных ключей." - else: - message_text = "У вас нет активных ключей." - - except Exception as e: - message_text = f"Ошибка при получении даты окончания ключа: {e}" - - await callback_query.message.reply(message_text) - await callback_query.answer() - -@dp.callback_query(F.data == 'view_balance') -async def process_callback_view_balance(callback_query: types.CallbackQuery): - tg_id = callback_query.from_user.id - - try: - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute("SELECT balance FROM connections WHERE tg_id = ? AND expiry_time > ?", - (tg_id, int(datetime.utcnow().timestamp() * 1000))) as cursor: - record = await cursor.fetchone() - balance = record[0] - message_text = f"Ваш баланс: {balance} RUB" - - except Exception as e: - message_text = f"Ошибка при получении баланса: {e}" - - await callback_query.message.reply(message_text) - await callback_query.answer() +async def on_startup(dispatcher: Dispatcher): + await login_with_credentials() diff --git a/handlers/__pycache__/balance.cpython-310.pyc b/handlers/__pycache__/balance.cpython-310.pyc new file mode 100644 index 00000000..3100575b Binary files /dev/null and b/handlers/__pycache__/balance.cpython-310.pyc differ diff --git a/handlers/__pycache__/expiry.cpython-310.pyc b/handlers/__pycache__/expiry.cpython-310.pyc new file mode 100644 index 00000000..044c85c1 Binary files /dev/null and b/handlers/__pycache__/expiry.cpython-310.pyc differ diff --git a/handlers/__pycache__/keys.cpython-310.pyc b/handlers/__pycache__/keys.cpython-310.pyc new file mode 100644 index 00000000..9876cae9 Binary files /dev/null and b/handlers/__pycache__/keys.cpython-310.pyc differ diff --git a/handlers/__pycache__/profile.cpython-310.pyc b/handlers/__pycache__/profile.cpython-310.pyc new file mode 100644 index 00000000..697ac76f Binary files /dev/null and b/handlers/__pycache__/profile.cpython-310.pyc differ diff --git a/handlers/__pycache__/start.cpython-310.pyc b/handlers/__pycache__/start.cpython-310.pyc new file mode 100644 index 00000000..88c6e1e8 Binary files /dev/null and b/handlers/__pycache__/start.cpython-310.pyc differ diff --git a/handlers/__pycache__/stats.cpython-310.pyc b/handlers/__pycache__/stats.cpython-310.pyc new file mode 100644 index 00000000..04fb78cc Binary files /dev/null and b/handlers/__pycache__/stats.cpython-310.pyc differ diff --git a/handlers/balance.py b/handlers/balance.py new file mode 100644 index 00000000..ccd01086 --- /dev/null +++ b/handlers/balance.py @@ -0,0 +1,10 @@ +from aiogram import types, Router +import aiosqlite + +router = Router() + +@router.callback_query(lambda c: c.data == 'view_balance') +async def process_callback_view_balance(callback_query: types.CallbackQuery): + tg_id = callback_query.from_user.id + + diff --git a/handlers/expiry.py b/handlers/expiry.py new file mode 100644 index 00000000..71295447 --- /dev/null +++ b/handlers/expiry.py @@ -0,0 +1,33 @@ +from aiogram import types, Router +from database import get_active_key_email +from datetime import datetime +import aiosqlite +from config import DATABASE_PATH + +router = Router() + +@router.callback_query(lambda c: c.data == 'view_expiry') +async def process_callback_view_expiry(callback_query: types.CallbackQuery): + tg_id = callback_query.from_user.id + + try: + email = await get_active_key_email(tg_id) + if email: + async with aiosqlite.connect(DATABASE_PATH) as db: + async with db.execute("SELECT expiry_time FROM connections WHERE tg_id = ? AND expiry_time > ?", + (tg_id, int(datetime.utcnow().timestamp() * 1000))) as cursor: + record = await cursor.fetchone() + if record: + expiry_time = record[0] + expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime("%Y-%m-%d %H:%M:%S") + message_text = f"Дата окончания вашего ключа: {expiry_date}" + else: + message_text = "У вас нет активных ключей." + else: + message_text = "У вас нет активных ключей." + + except Exception as e: + message_text = f"Ошибка при получении даты окончания ключа: {e}" + + await callback_query.message.reply(message_text) + await callback_query.answer() diff --git a/handlers/keys.py b/handlers/keys.py new file mode 100644 index 00000000..043b4fc7 --- /dev/null +++ b/handlers/keys.py @@ -0,0 +1,31 @@ +from aiogram import types, Router +import aiosqlite +from config import DATABASE_PATH +from bot import bot +router = Router() + +@router.callback_query(lambda c: c.data == 'view_keys') +async def process_callback_view_keys(callback_query: types.CallbackQuery): + tg_id = callback_query.from_user.id + + try: + async with aiosqlite.connect(DATABASE_PATH) as db: + async with db.execute(''' + SELECT k.key + FROM keys k + JOIN connections c ON k.client_id = c.client_id + WHERE c.tg_id = ? + ''', (tg_id,)) as cursor: + record = await cursor.fetchone() + + if record: + key = record[0] + response_message = f"Ваш ключ:\n
{key}
" + else: + response_message = "У вас нет ключей." + + except Exception as e: + response_message = f"Ошибка при получении ключей: {e}" + + await bot.send_message(tg_id, response_message, parse_mode="HTML", reply_to_message_id=callback_query.message.message_id) + await callback_query.answer() diff --git a/handlers/profile.py b/handlers/profile.py new file mode 100644 index 00000000..ec859c61 --- /dev/null +++ b/handlers/profile.py @@ -0,0 +1,51 @@ +from aiogram import types, Router +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from database import get_active_key_email, get_balance +from datetime import datetime +import aiosqlite +from aiogram.fsm.context import FSMContext +from config import DATABASE_PATH + +router = Router() + +@router.callback_query(lambda c: c.data == 'view_profile') +async def process_callback_view_profile(callback_query: types.CallbackQuery, state: FSMContext): + tg_id = callback_query.from_user.id + username = callback_query.from_user.username + + try: + email = await get_active_key_email(tg_id) + if email: + async with aiosqlite.connect(DATABASE_PATH) as db: + async with db.execute("SELECT expiry_time FROM connections WHERE tg_id = ? AND expiry_time > ?", + (tg_id, int(datetime.utcnow().timestamp() * 1000))) as cursor: + record = await cursor.fetchone() + if record: + expiry_time = record[0] + expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime("%Y-%m-%d %H:%M:%S") + else: + expiry_date = "Неизвестно" + + balance = await get_balance(tg_id) + profile_message = ( + f"Профиль клиента:\n" + f"Никнейм: @{username}\n" + f"Email: {email}\n" + f"Дата окончания ключа: {expiry_date}\n" + f"Баланс: {balance}\n" + ) + + button_view_keys = InlineKeyboardButton(text='Мои ключи', callback_data='view_keys') + keyboard = InlineKeyboardMarkup(inline_keyboard=[[button_view_keys]]) + + profile_message += "Вы можете просмотреть ваши ключи ниже:" + else: + profile_message = "У вас нет активных ключей." + keyboard = None + + except Exception as e: + profile_message = f"Ошибка при получении данных профиля: {e}" + keyboard = None + + await callback_query.message.reply(profile_message, reply_markup=keyboard) + await callback_query.answer() diff --git a/handlers/start.py b/handlers/start.py new file mode 100644 index 00000000..b245e72e --- /dev/null +++ b/handlers/start.py @@ -0,0 +1,26 @@ +from aiogram import types, Router +from aiogram.filters import Command +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMarkup, KeyboardButton +from config import ADMIN_CHAT_ID + +router = Router() + +@router.message(Command("start")) +async def start_command(message: types.Message): + welcome_text = "Добро пожаловать! Вы можете создать ключ для подключения VPN, просмотреть статистику использования, узнать дату окончания ключа или просмотреть ваш профиль." + + button_create_key = InlineKeyboardButton(text='Создать ключ', callback_data='create_key') + button_view_stats = InlineKeyboardButton(text='Посмотреть статистику', callback_data='view_stats') + button_view_expiry = InlineKeyboardButton(text='Дата окончания ключа', callback_data='view_expiry') + button_view_profile = InlineKeyboardButton(text='Мой профиль', callback_data='view_profile') + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [button_create_key], + [button_view_stats], + [button_view_expiry], + [button_view_profile] + ]) + + main_menu_keyboard = ReplyKeyboardMarkup(keyboard=[[KeyboardButton(text="В начало")]], resize_keyboard=True) + + await message.reply(welcome_text, reply_markup=keyboard) + await message.answer("Чтобы вернуться в начало, нажмите кнопку ниже:", reply_markup=main_menu_keyboard) diff --git a/handlers/stats.py b/handlers/stats.py new file mode 100644 index 00000000..c0b1ae12 --- /dev/null +++ b/handlers/stats.py @@ -0,0 +1,45 @@ +from aiogram import types, Router +import re +from database import get_active_key_email +from auth import link, login_with_credentials +from datetime import datetime +import aiosqlite +from config import DATABASE_PATH +from config import ADMIN_USERNAME, ADMIN_PASSWORD + + +router = Router() +session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) + +@router.callback_query(lambda c: c.data == 'view_stats') +async def process_callback_view_stats(callback_query: types.CallbackQuery): + tg_id = callback_query.from_user.id + + try: + email = await get_active_key_email(tg_id) + if email: + async with aiosqlite.connect(DATABASE_PATH) as db: + async with db.execute("SELECT client_id FROM connections WHERE tg_id = ? AND expiry_time > ?", + (tg_id, int(datetime.utcnow().timestamp() * 1000))) as cursor: + record = await cursor.fetchone() + if record: + client_id = record[0] + connection_link = link(session, client_id, email) + + up_match = re.search(r'up=(\d+)', connection_link) + down_match = re.search(r'down=(\d+)', connection_link) + + up = up_match.group(1) if up_match else "Неизвестно" + down = down_match.group(1) if down_match else "Неизвестно" + + statistics = f"Статистика вашего ключа:\nЗагрузка: {up} MB\nВыгрузка: {down} MB" + else: + statistics = "У вас нет активных ключей." + else: + statistics = "У вас нет активных ключей." + + except Exception as e: + statistics = f"Ошибка при получении статистики: {e}" + + await callback_query.message.reply(f"Ваша статистика:\n{statistics}") + await callback_query.answer() diff --git a/key_management.py b/key_management.py new file mode 100644 index 00000000..e99727aa --- /dev/null +++ b/key_management.py @@ -0,0 +1,128 @@ +from aiogram import Bot, Dispatcher, Router, F, types +from aiogram.filters import Command +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, Message, ReplyKeyboardMarkup, KeyboardButton +from aiogram.fsm.storage.memory import MemoryStorage +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 datetime import datetime, timedelta +from config import API_TOKEN, ADMIN_PASSWORD, ADMIN_USERNAME, ADMIN_CHAT_ID, DATABASE_PATH +from database import add_connection, has_active_key, get_active_key_email, get_balance, store_key +import uuid +import aiosqlite +import re +from bot import dp +from handlers.start import start_command +from bot import bot +from handlers.stats import session + +router = Router() + +def escape_markdown(text: str) -> str: + """ + Экранирование специальных символов для Markdown. + """ + escape_chars = r'_*[]()~`>#+-=|{}.!' + return re.sub(r'([%s])' % re.escape(escape_chars), r'\\\1', text) + +class Form(StatesGroup): + waiting_for_key_name = State() + waiting_for_admin_confirmation = State() + waiting_for_statistics = State() + waiting_for_expiry_date = State() + viewing_profile = State() + + +@dp.callback_query(F.data == 'create_key') +async def process_callback_create_key(callback_query: types.CallbackQuery, state: FSMContext): + await callback_query.message.reply("Введите имя вашего профиля VPN:") + await state.set_state(Form.waiting_for_key_name) + await callback_query.answer() + +@dp.message() +async def handle_text(message: types.Message, state: FSMContext): + current_state = await state.get_state() + + if message.text == "В начало": + await start_command(message) + return + + if current_state == Form.waiting_for_key_name.state: + tg_id = message.from_user.id + key_name = message.text + tg_name = message.from_user.username + + if not key_name: + await message.reply("Имя клиента не указано.") + await state.clear() + return + + if await has_active_key(tg_id): + await message.reply("У вас уже есть активный ключ. Вы не можете создать новый.") + await state.clear() + return + + # Сохраняем информацию о запросе в контексте состояния + await state.update_data(key_name=key_name, tg_id=tg_id) + + # Отправляем запрос на подтверждение админу + admin_message = ( + f"Новый запрос на создание ключа:\n" + f"Телеграм-ID: {tg_id}\n" + f"Имя ключа: {key_name}\n" + f"Имя пользователя Telegram: @{tg_name}\n" + f"Введите 'yes' для подтверждения или 'no' для отклонения." + ) + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text='Да', callback_data=f'confirm_key_{tg_id}_{key_name}')], + [InlineKeyboardButton(text='Нет', callback_data=f'reject_key_{tg_id}')] + ]) + await bot.send_message(ADMIN_CHAT_ID, admin_message, reply_markup=keyboard) + + await state.set_state(Form.waiting_for_admin_confirmation) + await message.reply("Ожидайте подтверждения администратора.") + await state.clear() + +@dp.callback_query(F.data.startswith('confirm_key_')) +async def handle_admin_confirmation(callback_query: CallbackQuery, state: FSMContext): + if callback_query.message.chat.id != int(ADMIN_CHAT_ID): + return + + data = callback_query.data.split('_', 3) + tg_id = int(data[2]) + key_name = data[3] + + try: + client_id = str(uuid.uuid4()) + email = key_name + limit_ip = 1 + total_gb = 0 + current_time = datetime.utcnow() + expiry_time = int((current_time + timedelta(days=30)).timestamp() * 1000) + enable = True + flow = "xtls-rprx-vision" + balance = 0 + + add_client(session, client_id, email, tg_id, limit_ip, total_gb, expiry_time, enable, flow) + + connection_link = link(session, client_id, email) + + await add_connection(tg_id, client_id, email, expiry_time, balance) + await store_key(client_id, email, connection_link) + await bot.send_message(tg_id, f"Ключ создан:\n
{connection_link}
", parse_mode="HTML") + await callback_query.message.reply("Ключ создан и отправлен клиенту.") + except Exception as e: + await callback_query.message.reply(f"Ошибка при создании ключа: {e}") + + await callback_query.answer() + +@dp.callback_query(F.data.startswith('reject_key_')) +async def handle_rejection(callback_query: CallbackQuery, state: FSMContext): + if callback_query.message.chat.id != int(ADMIN_CHAT_ID): + return + + tg_id = int(callback_query.data.split('_', 3)[2]) + await bot.send_message(tg_id, "Создание ключа отклонено.") + await callback_query.message.reply("Запрос отклонен.") + await callback_query.answer()