From 4958061a1965afb27d9cf1017edc686631cff601 Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 3 Oct 2024 04:43:55 +0300 Subject: [PATCH 1/5] =?UTF-8?q?=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=20=D1=81?= =?UTF-8?q?=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D0=B0,=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B0=20=D1=81=D1=81?= =?UTF-8?q?=D1=8B=D0=BB=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- auth.py | 24 +++++----- client.py | 23 ++++----- database.py | 32 +++++++++++-- .../key_management.cpython-310.pyc | Bin 0 -> 7509 bytes handlers/__pycache__/keys.cpython-310.pyc | Bin 9405 -> 9230 bytes .../__pycache__/notifications.cpython-310.pyc | Bin 2889 -> 4445 bytes handlers/__pycache__/pay.cpython-310.pyc | Bin 7819 -> 6123 bytes handlers/__pycache__/profile.cpython-310.pyc | Bin 2347 -> 2315 bytes handlers/__pycache__/start.cpython-310.pyc | Bin 4870 -> 3448 bytes handlers/key_management.py | 45 ++++++++++++++---- handlers/keys.py | 21 ++++---- handlers/notifications.py | 11 +++-- handlers/pay.py | 1 - handlers/start.py | 2 +- 14 files changed, 106 insertions(+), 53 deletions(-) create mode 100644 handlers/__pycache__/key_management.cpython-310.pyc diff --git a/auth.py b/auth.py index 20960890..21e193f2 100644 --- a/auth.py +++ b/auth.py @@ -1,15 +1,15 @@ import json - import requests -from config import API_URL, GET_INBOUNDS_URL +from config import SERVERS # Импортируем SERVERS из config.py session = None -def login_with_credentials(username, password): +def login_with_credentials(server_id: str, username: str, password: str): global session session = requests.Session() - auth_url = f"{API_URL}/login/" + api_url = SERVERS[server_id]['API_URL'] # Получаем API_URL для выбранного сервера + auth_url = f"{api_url}/login/" data = { "username": username, "password": password @@ -21,22 +21,23 @@ def login_with_credentials(username, password): else: raise Exception(f"Ошибка авторизации: {response.status_code}, {response.text}") - -def get_clients(session): - response = session.get(GET_INBOUNDS_URL) +def get_clients(session, server_id): + api_url = SERVERS[server_id]['API_URL'] # Получаем GET_INBOUNDS_URL для выбранного сервера + response = session.get(f'{api_url}/panel/api/inbounds/list/') if response.status_code == 200: return response.json() # Возвращает данные по инбаундам и клиентам else: raise Exception(f"Ошибка при получении клиентов: {response.status_code}, {response.text}") -def link(session, client_id: str, email: str): +def link(session, server_id: str, client_id: str, email: str): """ Получение ссылки для подключения по ID клиента. - :param session: requests.Session - авторизованная сессия + :param server_id: str - идентификатор сервера :param client_id: str - идентификатор клиента + :param email: str - электронная почта клиента :return: str - ссылка для подключения """ - response = get_clients(session) + response = get_clients(session, server_id) if 'obj' not in response or len(response['obj']) == 0: raise Exception("Не удалось получить данные клиентов.") @@ -51,6 +52,5 @@ def link(session, client_id: str, email: str): flow = stream_settings.get('flow', 'xtls-rprx-vision') # Создание ссылки для подключения VLESS - val = f"vless://{client_id}@solonet.pocomacho.ru:443?type={tcp}&security={reality}&pbk=Ik3cfHdM5RXYCuzTxhaFfo58Fzwjvw11AtKsz5-izxA&fp=chrome&sni=discord.com&sid=6fc9&spx=%2F&flow={flow}#SoloNet_DE-{email}" + val = f"vless://{client_id}@{SERVERS[server_id]['DOMEN']}?type={tcp}&security={reality}&pbk={SERVERS[server_id]['PBK']}&fp=chrome&sni={SERVERS[server_id]['SNI']}&sid={SERVERS[server_id]['SID']}=%2F&flow={flow}#{SERVERS[server_id]['PREFIX']}-{email}" return val - diff --git a/client.py b/client.py index 19fb12b3..d847e74c 100644 --- a/client.py +++ b/client.py @@ -1,10 +1,9 @@ import json +from config import SERVERS # Импортируем SERVERS из config.py -from config import API_URL - - -def add_client(session, client_id: str, email: str, tg_id: str, limit_ip: int, total_gb: int, expiry_time: int, enable: bool, flow: str): - url = f'{API_URL}/panel/api/inbounds/addClient' +def add_client(session, server_id: str, client_id: str, email: str, tg_id: str, limit_ip: int, total_gb: int, expiry_time: int, enable: bool, flow: str): + api_url = SERVERS[server_id]['API_URL'] # Получаем API_URL для выбранного сервера + url = f'{api_url}/panel/api/inbounds/addClient' email = email.lower() @@ -44,8 +43,9 @@ def add_client(session, client_id: str, email: str, tg_id: str, limit_ip: int, t else: print(f"Ошибка при добавлении клиента: {response.status_code}, {response.text}") -def extend_client_key(session, tg_id, client_id, email: str, new_expiry_time: int) -> bool: - response = session.get(f"{API_URL}/panel/api/inbounds/getClientTraffics/{email}") +def extend_client_key(session, server_id: str, tg_id, client_id, email: str, new_expiry_time: int) -> bool: + api_url = SERVERS[server_id]['API_URL'] # Получаем API_URL для выбранного сервера + response = session.get(f"{api_url}/panel/api/inbounds/getClientTraffics/{email}") print(f"GET {response.url} Status: {response.status_code}") print(f"GET Response: {response.text}") @@ -93,7 +93,7 @@ def extend_client_key(session, tg_id, client_id, email: str, new_expiry_time: in } try: - response = session.post(f"{API_URL}/panel/api/inbounds/updateClient/{client_id}", json=payload, headers=headers) + response = session.post(f"{api_url}/panel/api/inbounds/updateClient/{client_id}", json=payload, headers=headers) print(f"POST {response.url} Status: {response.status_code}") print(f"POST Request Data: {json.dumps(payload, indent=2)}") print(f"POST Response: {response.text}") @@ -107,8 +107,9 @@ def extend_client_key(session, tg_id, client_id, email: str, new_expiry_time: in print(f"Ошибка запроса: {e}") return False -def delete_client(session, client_id: str) -> bool: - url = f"{API_URL}/panel/api/inbounds/1/delClient/{client_id}" +def delete_client(session, server_id: str, client_id: str) -> bool: + api_url = SERVERS[server_id]['API_URL'] # Получаем API_URL для выбранного сервера + url = f"{api_url}/panel/api/inbounds/1/delClient/{client_id}" headers = { 'Accept': 'application/json' } @@ -121,4 +122,4 @@ def delete_client(session, client_id: str) -> bool: return False except Exception as e: print(f"Ошибка запроса: {e}") - return False \ No newline at end of file + return False diff --git a/database.py b/database.py index 565a3157..dca8bd56 100644 --- a/database.py +++ b/database.py @@ -4,9 +4,9 @@ import asyncpg from config import DATABASE_URL - async def init_db(): conn = await asyncpg.connect(DATABASE_URL) + # Создаем таблицу connections, если она не существует await conn.execute(''' CREATE TABLE IF NOT EXISTS connections ( tg_id BIGINT PRIMARY KEY NOT NULL, @@ -14,6 +14,7 @@ async def init_db(): trial INTEGER NOT NULL DEFAULT 0 ) ''') + # Создаем таблицу keys, если она не существует await conn.execute(''' CREATE TABLE IF NOT EXISTS keys ( tg_id BIGINT NOT NULL, @@ -22,11 +23,22 @@ async def init_db(): created_at BIGINT NOT NULL, expiry_time BIGINT NOT NULL, key TEXT NOT NULL, + server_id TEXT NOT NULL DEFAULT 'server1', -- новое поле для идентификатора сервера PRIMARY KEY (tg_id, client_id) ) ''') + # Добавляем поле server_id в таблицу keys, если его нет + try: + await conn.execute(''' + ALTER TABLE keys + ADD COLUMN server_id TEXT NOT NULL DEFAULT 'server1' + ''') + except asyncpg.exceptions.DuplicateColumnError: + # Если поле уже существует, ничего не делаем + pass await conn.close() + async def add_connection(tg_id: int, balance: float = 0.0, trial: int = 0): conn = await asyncpg.connect(DATABASE_URL) await conn.execute(''' @@ -43,12 +55,12 @@ async def check_connection_exists(tg_id: int): await conn.close() return exists -async def store_key(tg_id: int, client_id: str, email: str, expiry_time: int, key: str): +async def store_key(tg_id: int, client_id: str, email: str, expiry_time: int, key: str, server_id: str): conn = await asyncpg.connect(DATABASE_URL) await conn.execute(''' - INSERT INTO keys (tg_id, client_id, email, created_at, expiry_time, key) - VALUES ($1, $2, $3, $4, $5, $6) - ''', tg_id, client_id, email, int(datetime.utcnow().timestamp() * 1000), expiry_time, key) + INSERT INTO keys (tg_id, client_id, email, created_at, expiry_time, key, server_id) + VALUES ($1, $2, $3, $4, $5, $6, $7) + ''', tg_id, client_id, email, int(datetime.utcnow().timestamp() * 1000), expiry_time, key, server_id) await conn.close() async def get_keys(tg_id: int): @@ -61,6 +73,16 @@ async def get_keys(tg_id: int): await conn.close() return records +async def get_keys_by_server(tg_id: int, server_id: str): + conn = await asyncpg.connect(DATABASE_URL) + records = await conn.fetch(''' + SELECT client_id, email, created_at, key + FROM keys + WHERE tg_id = $1 AND server_id = $2 + ''', tg_id, server_id) + await conn.close() + return records + async def has_active_key(tg_id: int) -> bool: conn = await asyncpg.connect(DATABASE_URL) count = await conn.fetchval("SELECT COUNT(*) FROM keys WHERE tg_id = $1", tg_id) diff --git a/handlers/__pycache__/key_management.cpython-310.pyc b/handlers/__pycache__/key_management.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8d7f04073fabf4ec1d3388be48c83f1ea601af0b GIT binary patch literal 7509 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 AQvd(} literal 0 HcmV?d00001 diff --git a/handlers/__pycache__/keys.cpython-310.pyc b/handlers/__pycache__/keys.cpython-310.pyc index 38afa96151e6384814a24becbfb7bbad81b87857..ff42bbce817b7bf0db51bfbbfa3c5beeff40cecc 100644 GIT binary patch delta 163 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 F{|7Se(9Zw> delta 1163 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 literal 7819 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 diff --git a/handlers/key_management.py b/handlers/key_management.py index 82d3e530..762820f8 100644 --- a/handlers/key_management.py +++ b/handlers/key_management.py @@ -10,11 +10,11 @@ from aiogram.types import (CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message) from auth import link, login_with_credentials -from bot import bot, dp +from bot import dp from client import add_client -from config import (ADMIN_ID, ADMIN_PASSWORD, ADMIN_USERNAME, API_TOKEN, - DATABASE_URL) -from database import (add_connection, get_balance, has_active_key, store_key, +from config import (ADMIN_PASSWORD, ADMIN_USERNAME, + DATABASE_URL, SERVERS) +from database import (add_connection, get_balance, store_key, update_balance) from handlers.instructions import send_instructions from handlers.profile import process_callback_view_profile @@ -28,6 +28,7 @@ def sanitize_key_name(key_name: str) -> str: return re.sub(r'[^a-z0-9@._-]', '', key_name.lower()) class Form(StatesGroup): + waiting_for_server_selection = State() waiting_for_key_name = State() viewing_profile = State() @@ -35,10 +36,31 @@ class Form(StatesGroup): async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext): tg_id = callback_query.from_user.id + # Запрос выбора сервера + server_buttons = [ + [InlineKeyboardButton(text=server['name'], callback_data=f'select_server|{server_id}')] + for server_id, server in SERVERS.items() + ] + + await callback_query.message.edit_text( + "⚙️ Выберите сервер для создания ключа:", + parse_mode="HTML", + reply_markup=InlineKeyboardMarkup(inline_keyboard=server_buttons) + ) + + await state.set_state(Form.waiting_for_server_selection) + + await callback_query.answer() + +@dp.callback_query(F.data.startswith('select_server|')) +async def select_server(callback_query: CallbackQuery, state: FSMContext): + server_id = callback_query.data.split('|')[1] + await state.update_data(selected_server_id=server_id) + # Получаем данные о trial из базы данных conn = await asyncpg.connect(DATABASE_URL) try: - existing_connection = await conn.fetchrow('SELECT trial FROM connections WHERE tg_id = $1', tg_id) + existing_connection = await conn.fetchrow('SELECT trial FROM connections WHERE tg_id = $1', callback_query.from_user.id) finally: await conn.close() @@ -69,6 +91,8 @@ async def process_callback_create_key(callback_query: CallbackQuery, state: FSMC @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 + data = await state.get_data() + server_id = data.get('selected_server_id') # Проверяем баланс перед созданием нового ключа balance = await get_balance(tg_id) @@ -131,8 +155,9 @@ async def handle_key_name_input(message: Message, state: FSMContext): data = await state.get_data() creating_new_key = data.get('creating_new_key', False) + server_id = data.get('selected_server_id') - session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) + session = login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD) client_id = str(uuid.uuid4()) email = key_name.lower() current_time = datetime.utcnow() @@ -167,7 +192,7 @@ async def handle_key_name_input(message: Message, state: FSMContext): try: # Попробуем добавить клиента - response = add_client(session, client_id, email, tg_id, limit_ip=1, total_gb=0, expiry_time=expiry_timestamp, enable=True, flow="xtls-rprx-vision") + response = 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 not response.get("success", True): error_msg = response.get("msg", "Неизвестная ошибка.") @@ -178,7 +203,7 @@ async def handle_key_name_input(message: Message, state: FSMContext): else: raise Exception(error_msg) - connection_link = link(session, client_id, email) + connection_link = link(session, server_id, client_id, email) conn = await asyncpg.connect(DATABASE_URL) try: @@ -191,7 +216,7 @@ async def handle_key_name_input(message: Message, state: FSMContext): finally: await conn.close() - await store_key(tg_id, client_id, email, expiry_timestamp, connection_link) + await store_key(tg_id, client_id, email, expiry_timestamp, connection_link, server_id) # Рассчитываем оставшееся время до окончания действия ключа remaining_time = expiry_time - current_time @@ -230,4 +255,4 @@ async def handle_instructions(callback_query: CallbackQuery): @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() \ No newline at end of file + await callback_query.answer() diff --git a/handlers/keys.py b/handlers/keys.py index 10a12eee..0d399640 100644 --- a/handlers/keys.py +++ b/handlers/keys.py @@ -167,7 +167,6 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery): await callback_query.answer() - @router.callback_query(lambda c: c.data.startswith('confirm_delete|')) async def process_callback_confirm_delete(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id @@ -176,13 +175,16 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery): try: conn = await asyncpg.connect(DATABASE_URL) try: - record = await conn.fetchrow('SELECT email FROM keys WHERE client_id = $1', client_id) + # Извлекаем server_id и email из базы данных + record = await conn.fetchrow('SELECT email, server_id FROM keys WHERE client_id = $1', client_id) if record: email = record['email'] + server_id = record['server_id'] # Извлекаем server_id из записи - session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) - success = delete_client(session, client_id) + # Используем server_id для авторизации + session = login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD) + success = delete_client(session, server_id, client_id) if success: await conn.execute('DELETE FROM keys WHERE client_id = $1', client_id) @@ -215,11 +217,13 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery): try: conn = await asyncpg.connect(DATABASE_URL) try: - record = await conn.fetchrow('SELECT email, expiry_time FROM keys WHERE client_id = $1', client_id) + # Извлекаем email, expiry_time и server_id из базы данных + record = await conn.fetchrow('SELECT email, expiry_time, server_id FROM keys WHERE client_id = $1', client_id) if record: email = record['email'] expiry_time = record['expiry_time'] + server_id = record['server_id'] # Извлекаем server_id из записи current_time = datetime.utcnow().timestamp() * 1000 # Текущее время в миллисекундах # Проверяем, если ключ истек, то продлеваем от текущей даты, иначе продлеваем от текущей даты истечения @@ -247,9 +251,9 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery): await bot.edit_message_text("Недостаточно средств для продления ключа.", chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard) return - # Продлеваем ключ через API - session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) - success = extend_client_key(session, tg_id, client_id, email, new_expiry_time) + # Продлеваем ключ через API, используя server_id + session = login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD) + success = extend_client_key(session, server_id, tg_id, client_id, email, new_expiry_time) if success: # Обновляем баланс и ключ в базе данных @@ -272,6 +276,5 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery): await callback_query.answer() - async def handle_error(tg_id, callback_query, message): await bot.edit_message_text(message, chat_id=tg_id, message_id=callback_query.message.message_id) \ No newline at end of file diff --git a/handlers/notifications.py b/handlers/notifications.py index 55b4210b..4a7d408c 100644 --- a/handlers/notifications.py +++ b/handlers/notifications.py @@ -24,7 +24,7 @@ async def notify_expiring_keys(bot: Bot): # Получаем все ключи, которые истекают в течение следующих 10 часов threshold_time = (datetime.utcnow() + timedelta(hours=10)).timestamp() * 1000 # В миллисекундах records = await conn.fetch(''' - SELECT tg_id, email, expiry_time, client_id FROM keys + SELECT tg_id, email, expiry_time, client_id, server_id FROM keys WHERE expiry_time <= $1 AND expiry_time > $2 ''', threshold_time, datetime.utcnow().timestamp() * 1000) @@ -32,6 +32,7 @@ async def notify_expiring_keys(bot: Bot): tg_id = record['tg_id'] email = record['email'] expiry_time = record['expiry_time'] + server_id = record['server_id'] # Получаем server_id из записи # Рассчитываем оставшееся время и уменьшаем его на 3 часа time_left = (expiry_time / 1000) - datetime.utcnow().timestamp() @@ -55,7 +56,7 @@ async def notify_expiring_keys(bot: Bot): # Обрабатываем истекшие ключи expired_records = await conn.fetch(''' - SELECT tg_id, email, client_id FROM keys + SELECT tg_id, email, client_id, server_id FROM keys WHERE expiry_time <= $1 ''', datetime.utcnow().timestamp() * 1000) @@ -63,15 +64,16 @@ async def notify_expiring_keys(bot: Bot): tg_id = record['tg_id'] email = record['email'] client_id = record['client_id'] + server_id = record['server_id'] # Получаем server_id из записи # Удаляем ключ из базы данных await conn.execute('DELETE FROM keys WHERE client_id = $1', client_id) # Создаем сессию с использованием учетных данных - session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) + session = login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD) # Удаляем клиента из панели - delete_client(session, client_id) + delete_client(session, server_id, client_id) # Создаем клавиатуру с кнопкой "В профиль" keyboard = InlineKeyboardMarkup(inline_keyboard=[ @@ -90,6 +92,7 @@ async def notify_expiring_keys(bot: Bot): except Exception as e: print(f"Ошибка при отправке уведомлений: {e}") + @router.message(Command('send_to_all')) async def send_message_to_all_clients(message: types.Message): # Проверяем, является ли отправитель администратором diff --git a/handlers/pay.py b/handlers/pay.py index 901cf5b7..4fa376ea 100644 --- a/handlers/pay.py +++ b/handlers/pay.py @@ -1,7 +1,6 @@ import logging import uuid -import asyncpg from aiogram import Router, types from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup diff --git a/handlers/start.py b/handlers/start.py index 0b75fe2b..c1a1dad5 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -7,7 +7,7 @@ from aiogram.types import (BufferedInputFile, CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message) from bot import bot -from config import (ADMIN_ID, CHANNEL_URL, +from config import (CHANNEL_URL, SUPPORT_CHAT_URL) router = Router() From bba0e3ebdbd4fea60365626a389f4bc92feb3254 Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 3 Oct 2024 06:20:38 +0300 Subject: [PATCH 2/5] =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D1=8E?= =?UTF-8?q?=D1=89=D0=B8=D0=B9=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=20=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handlers/keys.py | 87 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 5 deletions(-) diff --git a/handlers/keys.py b/handlers/keys.py index 0d399640..727bde1c 100644 --- a/handlers/keys.py +++ b/handlers/keys.py @@ -3,10 +3,10 @@ from datetime import datetime, timedelta import asyncpg from aiogram import Router, types -from auth import login_with_credentials +from auth import login_with_credentials, link from bot import bot -from client import delete_client, extend_client_key -from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL +from client import delete_client, extend_client_key, add_client +from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, SERVERS from database import get_balance, update_balance router = Router() @@ -97,8 +97,9 @@ async def process_callback_view_key(callback_query: types.CallbackQuery): renew_button = types.InlineKeyboardButton(text='⏳ Продлить ключ', callback_data=f'renew_key|{client_id}') instructions_button = types.InlineKeyboardButton(text='📘 Инструкции', callback_data='instructions') delete_button = types.InlineKeyboardButton(text='❌ Удалить ключ', callback_data=f'delete_key|{client_id}') + change_location_button = types.InlineKeyboardButton(text='🌍 Сменить локацию', callback_data=f'change_location|{client_id}') back_button = types.InlineKeyboardButton(text='🔙 Назад в профиль', callback_data='view_profile') - keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[renew_button], [instructions_button], [delete_button], [back_button]]) + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[renew_button], [instructions_button], [delete_button], [change_location_button], [back_button]]) await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard, parse_mode="HTML") else: @@ -277,4 +278,80 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery): await callback_query.answer() async def handle_error(tg_id, callback_query, message): - await bot.edit_message_text(message, chat_id=tg_id, message_id=callback_query.message.message_id) \ No newline at end of file + await bot.edit_message_text(message, chat_id=tg_id, message_id=callback_query.message.message_id) + +@router.callback_query(lambda c: c.data.startswith('change_location|')) +async def process_callback_change_location(callback_query: types.CallbackQuery): + tg_id = callback_query.from_user.id + client_id = callback_query.data.split('|')[1] # Используем разделитель вертикальная черта + + # Запрос выбора сервера + server_buttons = [ + [types.InlineKeyboardButton(text=server['name'], callback_data=f'select_server&{server_id}&{client_id}')] + for server_id, server in SERVERS.items() + ] + keyboard = types.InlineKeyboardMarkup(inline_keyboard=server_buttons) + + response_message = "Выберите новый сервер для вашего ключа:" + await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard, parse_mode="HTML") + await callback_query.answer() + +@router.callback_query(lambda c: c.data.startswith('select_server&')) +async def process_callback_select_server(callback_query: types.CallbackQuery): + tg_id = callback_query.from_user.id + server_id, client_id = callback_query.data.split('&')[1], callback_query.data.split('&')[2] + + try: + conn = await asyncpg.connect(DATABASE_URL) + try: + # Извлекаем email и expiry_time из базы данных + record = await conn.fetchrow('SELECT email, expiry_time, server_id FROM keys WHERE client_id = $1', client_id) + + if record: + email = record['email'] + expiry_time = record['expiry_time'] + current_server_id = record['server_id'] + + # Создаем сессию для нового сервера + session = login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD) + + # Рассчитываем новое время окончания ключа + new_expiry_time = int(datetime.utcnow().timestamp() * 1000) + (expiry_time - datetime.utcnow().timestamp() * 1000) + + # Создаем нового клиента на новом сервере + new_client_data = add_client(session, server_id, client_id, email, tg_id, limit_ip=1, total_gb=0, expiry_time=new_expiry_time, enable=True, flow="xtls-rprx-vision") + + if new_client_data: + # Генерируем новый ключ + new_key = link(session, server_id, client_id, email) + + # Обновляем запись в базе данных, только ключ и сервер + await conn.execute('UPDATE keys SET server_id = $1, key = $2 WHERE client_id = $3', + server_id, new_key, client_id) + + # Удаляем клиента с текущего сервера + session = login_with_credentials(current_server_id, ADMIN_USERNAME, ADMIN_PASSWORD) + success_delete = delete_client(session, current_server_id, client_id) + if success_delete: + response_message = ("Ключ успешно перемещен на новый сервер.\n\n" + "**Не забудьте удалить старый ключ из вашего приложения и установить новый.**") + else: + response_message = "Ошибка при удалении ключа с текущего сервера." + else: + response_message = "Ошибка при создании клиента на новом сервере." + + else: + response_message = "Ключ не найден или уже удален." + + back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys') + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]]) + + await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard) + + finally: + await conn.close() + + except Exception as e: + await bot.edit_message_text(f"Ошибка при смене локации: {e}", chat_id=tg_id, message_id=callback_query.message.message_id) + + await callback_query.answer() From bfca706b315f95922ffcb8b9c385cebde8e2e557 Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 3 Oct 2024 06:31:44 +0300 Subject: [PATCH 3/5] =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D1=8E?= =?UTF-8?q?=D1=89=D0=B8=D0=B9=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=20=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handlers/keys.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/handlers/keys.py b/handlers/keys.py index 727bde1c..ffda9c80 100644 --- a/handlers/keys.py +++ b/handlers/keys.py @@ -69,7 +69,7 @@ async def process_callback_view_key(callback_query: types.CallbackQuery): conn = await asyncpg.connect(DATABASE_URL) try: record = await conn.fetchrow(''' - SELECT k.key, k.expiry_time + SELECT k.key, k.expiry_time, k.server_id FROM keys k WHERE k.tg_id = $1 AND k.email = $2 ''', tg_id, key_name) @@ -77,6 +77,11 @@ async def process_callback_view_key(callback_query: types.CallbackQuery): if record: key = record['key'] expiry_time = record['expiry_time'] + server_id = record['server_id'] + + # Получаем название сервера по server_id + server_name = SERVERS.get(server_id, {}).get('name', 'Неизвестный сервер') + expiry_date = datetime.utcfromtimestamp(expiry_time / 1000) current_date = datetime.utcnow() time_left = expiry_date - current_date @@ -91,7 +96,8 @@ async def process_callback_view_key(callback_query: types.CallbackQuery): response_message = (f"🔑 Ваш ключ:\n
{key}
\n" f"📅 Дата окончания: {expiry_date.strftime('%Y-%m-%d %H:%M:%S')}\n" - f"{days_left_message}") + f"{days_left_message}\n" + f"🌍 Сервер: {server_name}") # Кнопки для продления, инструкций и удаления renew_button = types.InlineKeyboardButton(text='⏳ Продлить ключ', callback_data=f'renew_key|{client_id}') @@ -99,7 +105,15 @@ async def process_callback_view_key(callback_query: types.CallbackQuery): delete_button = types.InlineKeyboardButton(text='❌ Удалить ключ', callback_data=f'delete_key|{client_id}') change_location_button = types.InlineKeyboardButton(text='🌍 Сменить локацию', callback_data=f'change_location|{client_id}') back_button = types.InlineKeyboardButton(text='🔙 Назад в профиль', callback_data='view_profile') - keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[renew_button], [instructions_button], [delete_button], [change_location_button], [back_button]]) + + keyboard = types.InlineKeyboardMarkup( + inline_keyboard=[ + [instructions_button], # Инструкции отдельной строкой + [renew_button, delete_button], # Продлить и Удалить в одном ряду + [change_location_button], # Сменить локацию отдельной строкой + [back_button] # Назад отдельной строкой + ] + ) await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard, parse_mode="HTML") else: @@ -113,6 +127,7 @@ async def process_callback_view_key(callback_query: types.CallbackQuery): await callback_query.answer() + # Обработка запроса на удаление ключа @router.callback_query(lambda c: c.data.startswith('delete_key|')) async def process_callback_delete_key(callback_query: types.CallbackQuery): From 87095772febb4584bec443d05c294331e2ab3166 Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 3 Oct 2024 06:42:40 +0300 Subject: [PATCH 4/5] =?UTF-8?q?=D0=B7=D0=B0=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D1=81=D0=B5=D1=80=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D0=BE=D0=B2=20=D0=B2=20=D0=BF=D1=80=D0=BE=D1=86?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handlers/key_management.py | 19 +++++++++++++------ handlers/keys.py | 18 +++++++++++++----- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/handlers/key_management.py b/handlers/key_management.py index 762820f8..3ce76967 100644 --- a/handlers/key_management.py +++ b/handlers/key_management.py @@ -36,12 +36,19 @@ class Form(StatesGroup): async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext): tg_id = callback_query.from_user.id - # Запрос выбора сервера - server_buttons = [ - [InlineKeyboardButton(text=server['name'], callback_data=f'select_server|{server_id}')] - for server_id, server in SERVERS.items() - ] - + # Получаем количество подключений для каждого сервера + server_buttons = [] + conn = await asyncpg.connect(DATABASE_URL) + try: + for server_id, server in SERVERS.items(): + # Получаем количество ключей на сервере + count = await conn.fetchval('SELECT COUNT(*) FROM keys WHERE server_id = $1', server_id) + percent_full = (count / 100) * 100 # Заполнение в процентах + server_name = f"{server['name']} ({percent_full:.1f}%)" + server_buttons.append([InlineKeyboardButton(text=server_name, callback_data=f'select_server|{server_id}')]) + finally: + await conn.close() + await callback_query.message.edit_text( "⚙️ Выберите сервер для создания ключа:", parse_mode="HTML", diff --git a/handlers/keys.py b/handlers/keys.py index ffda9c80..1b884998 100644 --- a/handlers/keys.py +++ b/handlers/keys.py @@ -300,11 +300,19 @@ async def process_callback_change_location(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id client_id = callback_query.data.split('|')[1] # Используем разделитель вертикальная черта - # Запрос выбора сервера - server_buttons = [ - [types.InlineKeyboardButton(text=server['name'], callback_data=f'select_server&{server_id}&{client_id}')] - for server_id, server in SERVERS.items() - ] + # Получаем количество подключений для каждого сервера + server_buttons = [] + conn = await asyncpg.connect(DATABASE_URL) + try: + for server_id, server in SERVERS.items(): + # Получаем количество ключей на сервере + count = await conn.fetchval('SELECT COUNT(*) FROM keys WHERE server_id = $1', server_id) + percent_full = (count / 100) * 100 # Заполнение в процентах + server_name = f"{server['name']} ({percent_full:.1f}%)" + server_buttons.append([types.InlineKeyboardButton(text=server_name, callback_data=f'select_server&{server_id}&{client_id}')]) + finally: + await conn.close() + keyboard = types.InlineKeyboardMarkup(inline_keyboard=server_buttons) response_message = "Выберите новый сервер для вашего ключа:" From f42ca88e0187963f4f79f085dab80119feccf108 Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 3 Oct 2024 06:45:07 +0300 Subject: [PATCH 5/5] =?UTF-8?q?=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D1=82=D0=B5=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handlers/keys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/keys.py b/handlers/keys.py index 1b884998..f88191d8 100644 --- a/handlers/keys.py +++ b/handlers/keys.py @@ -357,7 +357,7 @@ async def process_callback_select_server(callback_query: types.CallbackQuery): success_delete = delete_client(session, current_server_id, client_id) if success_delete: response_message = ("Ключ успешно перемещен на новый сервер.\n\n" - "**Не забудьте удалить старый ключ из вашего приложения и установить новый.**") + "Не забудьте удалить старый ключ из вашего приложения и установить новый.") else: response_message = "Ошибка при удалении ключа с текущего сервера." else: