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()