From 4797759f9b47e729e10116dfe8b7b89e3bd560b0 Mon Sep 17 00:00:00 2001 From: Vlad Date: Mon, 16 Sep 2024 07:03:41 +0300 Subject: [PATCH 1/2] =?UTF-8?q?=D0=BE=D0=BF=D1=80=D0=BE=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 110 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 38 deletions(-) diff --git a/bot.py b/bot.py index 579a3d32..c82c3d6d 100644 --- a/bot.py +++ b/bot.py @@ -15,6 +15,7 @@ import aiosqlite class Form(StatesGroup): waiting_for_key_name = State() + waiting_for_admin_confirmation = State() waiting_for_statistics = State() waiting_for_expiry_date = State() @@ -26,6 +27,9 @@ router = Router() # Создаем сессию при старте бота session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) +# ID администратора +ADMIN_ID = 476217106 # Замените на ваш Telegram ID + @dp.message(Command("start")) async def start_command(message: Message): welcome_text = "Добро пожаловать! Вы можете создать ключ для подключения VPN, просмотреть статистику использования или узнать дату окончания ключа." @@ -43,6 +47,74 @@ async def process_callback_create_key(callback_query: types.CallbackQuery, state 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 current_state == Form.waiting_for_key_name.state: + tg_id = message.from_user.id + + if await has_active_key(tg_id): + await message.reply("У вас уже есть активный ключ. Вы не можете создать новый.") + await state.clear() + return + + # Сохраняем введенное имя профиля + profile_name = message.text + await state.update_data(profile_name=profile_name) + + # Отправляем запрос на подтверждение админу + admin_text = f"Пользователь {tg_id} запросил создание ключа для профиля '{profile_name}'. Подтвердите создание ключа (Да/Нет)." + await bot.send_message(ADMIN_ID, admin_text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Да", callback_data=f'confirm_create_{tg_id}_{profile_name}')], + [InlineKeyboardButton(text="Нет", callback_data=f'reject_create_{tg_id}')] + ])) + + await message.reply("Запрос на создание ключа отправлен администратору для подтверждения.") + await state.set_state(Form.waiting_for_admin_confirmation) + + elif current_state == Form.waiting_for_admin_confirmation.state: + await message.reply("Ожидание подтверждения от администратора.") + +@dp.callback_query(F.data.startswith('confirm_create_')) +async def process_admin_confirmation(callback_query: types.CallbackQuery, state: FSMContext): + _, tg_id, profile_name = callback_query.data.split('_', 2) + tg_id = int(tg_id) + + try: + client_id = str(uuid.uuid4()) + 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" + + add_client(session, client_id, profile_name, tg_id, limit_ip, total_gb, expiry_time, enable, flow) + + connection_link = link(session, profile_name) + + await add_connection(tg_id, client_id, profile_name, expiry_time) + + user_message = f"Ключ создан:\n
{connection_link}
" + await bot.send_message(tg_id, user_message, parse_mode="HTML") + + await callback_query.message.reply("Ключ успешно создан и отправлен пользователю.") + except Exception as e: + await callback_query.message.reply(f"Ошибка при создании ключа: {e}") + + await state.clear() + +@dp.callback_query(F.data.startswith('reject_create_')) +async def process_admin_rejection(callback_query: types.CallbackQuery, state: FSMContext): + _, tg_id = callback_query.data.split('_', 1) + tg_id = int(tg_id) + + await bot.send_message(tg_id, "Создание ключа было отклонено администратором.") + await callback_query.message.reply("Запрос на создание ключа был отклонен.") + + await state.clear() + @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 @@ -95,44 +167,6 @@ async def process_callback_view_expiry(callback_query: types.CallbackQuery): await callback_query.message.reply(message_text) await callback_query.answer() -@dp.message() -async def handle_text(message: types.Message, state: FSMContext): - current_state = await state.get_state() - - if current_state == Form.waiting_for_key_name.state: - tg_id = message.from_user.id - - # Проверка, есть ли уже активный ключ у клиента - if await has_active_key(tg_id): - await message.reply("У вас уже есть активный ключ. Вы не можете создать новый.") - await state.clear() - return - - try: - client_id = str(uuid.uuid4()) - email = message.text - 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" - - # Создание клиента - add_client(session, client_id, email, tg_id, limit_ip, total_gb, expiry_time, enable, flow) - - # Получение ссылки на подключение - connection_link = link(session, email) - - # Сохранение данных о ключе в базе данных - await add_connection(tg_id, client_id, email, expiry_time) - - # Отправка ключа пользователю - await message.reply(f"Ключ создан:\n
{connection_link}
", parse_mode="HTML") - - await state.clear() - except Exception as e: - await message.reply(f"Ошибка: {e}") async def main(): From 412893b689794f66d90f56e6d90493e5933994fa Mon Sep 17 00:00:00 2001 From: Vlad Date: Mon, 16 Sep 2024 23:01:15 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D0=BE=D0=BF=D1=80=D0=BE=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- auth.py | 4 +- bot.py | 196 +++++++++++++++++++++++++++++----------------------- database.py | 9 +++ 3 files changed, 121 insertions(+), 88 deletions(-) diff --git a/auth.py b/auth.py index 6d3cbc00..48ea4624 100644 --- a/auth.py +++ b/auth.py @@ -7,7 +7,7 @@ session = None def login_with_credentials(username, password): global session session = requests.Session() - auth_url = "https://vpn.pocomacho.ru:34268/solonet/login/" + auth_url = "https://solonet.pocomacho.ru:62553/solonet/login/" data = { "username": username, "password": password @@ -48,6 +48,6 @@ def link(session, client_id: str): flow = stream_settings.get('flow', 'xtls-rprx-vision') # Создание ссылки для подключения VLESS - val = f"vless://{client_id}@vpn.pocomacho.ru:443?type={tcp}&security={reality}&fp=chrome&pbk=ZIMoEnEd8-qMJkReVU5JxbiEj8CCSrvpm_ckvJ-46TE&sni=yahoo.com&sid=0fb4b595&spx=%2F&flow={flow}#VPN_F-{client_id}" + val = f"vless://{client_id}@solonet.pocomacho.ru:443?type={tcp}&security={reality}&fp=chrome&pbk=Ik3cfHdM5RXYCuzTxhaFfo58Fzwjvw11AtKsz5-izxA&fp=chrome&sni=discord.com&sid=6fc9&spx=%2F&flow={flow}#VPN_F-{client_id}" return val diff --git a/bot.py b/bot.py index c82c3d6d..e0037853 100644 --- a/bot.py +++ b/bot.py @@ -4,20 +4,18 @@ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message 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 auth import login_with_credentials, link, get_clients from client import add_client from datetime import datetime, timedelta -from config import API_TOKEN, ADMIN_PASSWORD, ADMIN_USERNAME -from database import add_connection, has_active_key, get_active_key_email, DATABASE_PATH +from config import API_TOKEN, ADMIN_PASSWORD, ADMIN_USERNAME, ADMIN_CHAT_ID +from database import add_connection, has_active_key, get_active_key_email, get_key_expiry_time import uuid -import re import aiosqlite class Form(StatesGroup): waiting_for_key_name = State() waiting_for_admin_confirmation = State() - waiting_for_statistics = State() - waiting_for_expiry_date = State() + key_to_create = State() # Состояние для хранения ключа, который будет создан bot = Bot(token=API_TOKEN) storage = MemoryStorage() @@ -27,9 +25,6 @@ router = Router() # Создаем сессию при старте бота session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) -# ID администратора -ADMIN_ID = 476217106 # Замените на ваш Telegram ID - @dp.message(Command("start")) async def start_command(message: Message): welcome_text = "Добро пожаловать! Вы можете создать ключ для подключения VPN, просмотреть статистику использования или узнать дату окончания ключа." @@ -41,10 +36,27 @@ async def start_command(message: Message): await message.reply(welcome_text, reply_markup=keyboard) +@router.callback_query(F.data == "view_expiry") +async def process_callback_view_expiry_date(callback_query: types.CallbackQuery): + tg_id = callback_query.from_user.id + + # Получаем дату окончания ключа из базы данных + expiry_time = await get_key_expiry_time(tg_id) + + if expiry_time: + expiry_date = expiry_time.strftime('%Y-%m-%d %H:%M:%S') + response_message = f"Дата окончания вашего ключа: {expiry_date}" + else: + response_message = "У вас нет активного ключа." + + await callback_query.message.reply(response_message) + 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 state.update_data(tg_id=callback_query.from_user.id) # Сохраняем tg_id для дальнейшего использования await callback_query.answer() @dp.message() @@ -59,30 +71,57 @@ async def handle_text(message: types.Message, state: FSMContext): await state.clear() return - # Сохраняем введенное имя профиля - profile_name = message.text - await state.update_data(profile_name=profile_name) + # Сохраняем имя ключа и tg_id для подтверждения + await state.set_state(Form.waiting_for_admin_confirmation) + await state.update_data(key_name=message.text, tg_id=tg_id) # Отправляем запрос на подтверждение админу - admin_text = f"Пользователь {tg_id} запросил создание ключа для профиля '{profile_name}'. Подтвердите создание ключа (Да/Нет)." - await bot.send_message(ADMIN_ID, admin_text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Да", callback_data=f'confirm_create_{tg_id}_{profile_name}')], - [InlineKeyboardButton(text="Нет", callback_data=f'reject_create_{tg_id}')] - ])) - - await message.reply("Запрос на создание ключа отправлен администратору для подтверждения.") - await state.set_state(Form.waiting_for_admin_confirmation) + admin_message = f"Запрос на создание ключа от пользователя {message.from_user.username}. Имя ключа: {message.text}. Подтвердите создание ключа." + button_yes = InlineKeyboardButton(text='Подтвердить', callback_data='confirm_key') + button_no = InlineKeyboardButton(text='Отклонить', callback_data='reject_key') + keyboard = InlineKeyboardMarkup(inline_keyboard=[[button_yes, button_no]]) + await bot.send_message(ADMIN_CHAT_ID, admin_message, reply_markup=keyboard) + await message.reply("Ваш запрос на создание ключа отправлен администратору. Ожидайте подтверждения.") + await state.clear() - elif current_state == Form.waiting_for_admin_confirmation.state: - await message.reply("Ожидание подтверждения от администратора.") +@dp.message() +async def handle_text(message: types.Message, state: FSMContext): + current_state = await state.get_state() + + if current_state == Form.waiting_for_key_name.state: + tg_id = message.from_user.id -@dp.callback_query(F.data.startswith('confirm_create_')) + if await has_active_key(tg_id): + await message.reply("У вас уже есть активный ключ. Вы не можете создать новый.") + await state.clear() + return + + await state.set_state(Form.waiting_for_admin_confirmation) + await state.update_data(key_name=message.text, tg_id=tg_id) # Сохраняем имя ключа и tg_id для подтверждения + + # Отправляем запрос на подтверждение админу + admin_message = f"Запрос на создание ключа от пользователя {message.from_user.username}. Имя ключа: {message.text}. Подтвердите создание ключа." + button_yes = InlineKeyboardButton(text='Подтвердить', callback_data='confirm_key') + button_no = InlineKeyboardButton(text='Отклонить', callback_data='reject_key') + keyboard = InlineKeyboardMarkup(inline_keyboard=[[button_yes, button_no]]) + await bot.send_message(476217106, admin_message, reply_markup=keyboard) + await message.reply("Ваш запрос на создание ключа отправлен администратору. Ожидайте подтверждения.") + await state.clear() + +@dp.callback_query(F.data == 'confirm_key') async def process_admin_confirmation(callback_query: types.CallbackQuery, state: FSMContext): - _, tg_id, profile_name = callback_query.data.split('_', 2) - tg_id = int(tg_id) + user_data = await state.get_data() + key_name = user_data.get('key_name') + tg_id = user_data.get('tg_id') + + if not key_name or not tg_id: + await callback_query.answer("Ошибка: не удалось получить данные для создания ключа.") + return try: + # Генерация уникального client_id и создание ключа client_id = str(uuid.uuid4()) + email = key_name limit_ip = 1 total_gb = 0 current_time = datetime.utcnow() @@ -90,84 +129,69 @@ async def process_admin_confirmation(callback_query: types.CallbackQuery, state: enable = True flow = "xtls-rprx-vision" - add_client(session, client_id, profile_name, tg_id, limit_ip, total_gb, expiry_time, enable, flow) + add_client(session, client_id, email, tg_id, limit_ip, total_gb, expiry_time, enable, flow) - connection_link = link(session, profile_name) + # Генерация ссылки для подключения + connection_link = link(session, email) - await add_connection(tg_id, client_id, profile_name, expiry_time) + # Сохранение данных о подключении в базе данных + await add_connection(tg_id, client_id, email, expiry_time) - user_message = f"Ключ создан:\n
{connection_link}
" - await bot.send_message(tg_id, user_message, parse_mode="HTML") + # Отправка ключа клиенту + await bot.send_message(tg_id, f"Ключ создан:\n
{connection_link}
", parse_mode="HTML") + await bot.send_message(callback_query.from_user.id, "Ключ успешно создан и отправлен клиенту.") - await callback_query.message.reply("Ключ успешно создан и отправлен пользователю.") + await callback_query.answer("Ключ создан и отправлен клиенту.") except Exception as e: - await callback_query.message.reply(f"Ошибка при создании ключа: {e}") - - await state.clear() - -@dp.callback_query(F.data.startswith('reject_create_')) -async def process_admin_rejection(callback_query: types.CallbackQuery, state: FSMContext): - _, tg_id = callback_query.data.split('_', 1) - tg_id = int(tg_id) - - await bot.send_message(tg_id, "Создание ключа было отклонено администратором.") - await callback_query.message.reply("Запрос на создание ключа был отклонен.") + await bot.send_message(callback_query.from_user.id, f"Ошибка: {e}") await state.clear() @dp.callback_query(F.data == 'view_stats') -async def process_callback_view_stats(callback_query: types.CallbackQuery, state: FSMContext): +async def process_callback_view_stats(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id + # Проверяем, есть ли активный ключ для этого пользователя + email = await get_active_key_email(tg_id) + + if not email: + await callback_query.message.reply("У вас нет активного ключа для просмотра статистики.") + return + + # Получаем статистику через API сервера try: - email = await get_active_key_email(tg_id) - if email: - connection_link = link(session, 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 = "У вас нет активных ключей." - + clients = get_clients(session) # Предположим, что get_clients возвращает список клиентов с их данными + client_data = next((client for client in clients if client['email'] == email), None) + + if not client_data: + await callback_query.message.reply("Не удалось получить статистику для этого пользователя.") + return + + # Подготавливаем статистику для отображения + used_gb = client_data.get('usedGB', 0) # Использованные данные + limit_gb = client_data.get('totalGB', 0) # Лимит данных + expiry_time = client_data.get('expiryTime', 0) # Время окончания подписки + + expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S') + + stats_message = (f"Статистика использования:\n" + f"Использовано: {used_gb} ГБ\n" + f"Лимит: {limit_gb} ГБ\n" + f"Дата окончания ключа: {expiry_date}") + + await callback_query.message.reply(stats_message) + except Exception as e: - statistics = f"Ошибка при получении статистики: {e}" - - await callback_query.message.reply(f"Ваша статистика:\n{statistics}") - await callback_query.answer() + await callback_query.message.reply(f"Ошибка при получении статистики: {e}") -@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 == 'reject_key') +async def process_admin_rejection(callback_query: types.CallbackQuery, state: FSMContext): + await bot.send_message(callback_query.from_user.id, "Создание ключа отклонено.") + await callback_query.answer() + await state.clear() async def main(): dp.include_router(router) # Подключение роутера diff --git a/database.py b/database.py index e2fd22a3..3b4ed63a 100644 --- a/database.py +++ b/database.py @@ -38,3 +38,12 @@ async def get_active_key_email(tg_id: int) -> str: record = await cursor.fetchone() return record[0] if record else None +async def get_key_expiry_time(tg_id: int) -> datetime: + 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] + return datetime.utcfromtimestamp(expiry_time / 1000) + return None \ No newline at end of file