From 00efd1ce16d3d8896b2a2bbad234ccae71161e19 Mon Sep 17 00:00:00 2001 From: Vladless Date: Mon, 11 Nov 2024 17:47:26 +0300 Subject: [PATCH] minPython 3.10/py3xui/subs/rm_artefacts --- auth.py | 72 --------- client.py | 288 +++++++++------------------------- handlers/admin/admin.py | 79 +--------- handlers/admin/user_editor.py | 50 ++---- handlers/keys/key_utils.py | 95 ++++++----- handlers/keys/keys.py | 7 +- handlers/keys/trial_key.py | 68 +++++--- handlers/notifications.py | 130 ++++++--------- handlers/start.py | 6 +- middlewares/logging.py | 2 - 10 files changed, 233 insertions(+), 564 deletions(-) delete mode 100644 auth.py diff --git a/auth.py b/auth.py deleted file mode 100644 index 8e6684a0..00000000 --- a/auth.py +++ /dev/null @@ -1,72 +0,0 @@ -import json - -import aiohttp - -from config import SERVERS - -session = None - - -async def login_with_credentials(server_id: str, username: str, password: str): - global session - session = aiohttp.ClientSession() - api_url = SERVERS[server_id]["API_URL"] - auth_url = f"{api_url}/login/" - - data = {"username": username, "password": password} - - async with session.post(auth_url, json=data) as response: - if response.status == 200: - session.cookie_jar.update_cookies(response.cookies) - return session - else: - raise Exception( - f"Ошибка авторизации: {response.status}, {await response.text()}" - ) - - -async def get_clients(session, server_id): - api_url = SERVERS[server_id]["API_URL"] - async with session.get(f"{api_url}/panel/api/inbounds/list/") as response: - if response.status == 200: - return await response.json() - else: - raise Exception( - f"Ошибка при получении клиентов: {response.status}, {await response.text()}" - ) - - -async def link(session, server_id: str, client_id: str, email: str): - """ - Получение ссылки для подключения по ID клиента. - :param server_id: str - идентификатор сервера - :param client_id: str - идентификатор клиента - :param email: str - электронная почта клиента - :return: str - ссылка для подключения - """ - response = await get_clients(session, server_id) - - if "obj" not in response or len(response["obj"]) == 0: - raise Exception("Не удалось получить данные клиентов.") - - inbounds = response["obj"][0] - - stream_settings = json.loads(inbounds["streamSettings"]) - tcp = stream_settings.get("network", "tcp") - reality = stream_settings.get("security", "reality") - flow = stream_settings.get("flow", "xtls-rprx-vision") - - val = ( - f"vless://{client_id}@{SERVERS[server_id]['DOMEN']}?type={tcp}&security={reality}&pbk={SERVERS[server_id]['PBK']}" - f"&fp=chrome&sni={SERVERS[server_id]['SNI']}&sid={SERVERS[server_id]['SID']}=%2F&flow={flow}#{SERVERS[server_id]['PREFIX']}-{email}" - ) - return val - - -async def link_subscription(email, server_id): - server = SERVERS.get(server_id) - if server: - subscription_url = f"{server['SUBSCRIPTION']}/{email}" - return subscription_url - else: - raise ValueError(f"Server '{server_id}' not found in configuration.") diff --git a/client.py b/client.py index ac4fb3ca..fc865000 100644 --- a/client.py +++ b/client.py @@ -1,13 +1,9 @@ -import json - +import py3xui from loguru import logger -from config import SERVERS - async def add_client( - session, - server_id: str, + xui, client_id: str, email: str, tg_id: str, @@ -17,218 +13,90 @@ async def add_client( enable: bool, flow: str, ): - api_url = SERVERS[server_id]["API_URL"] - url = f"{api_url}/panel/api/inbounds/addClient" - - email = email.lower() - - client_data = { - "id": client_id, - "alterId": 0, - "email": email, - "limitIp": limit_ip, - "totalGB": total_gb, - "expiryTime": expiry_time, - "enable": enable, - "tgId": tg_id, - "subId": email, - "flow": flow, - } - - settings = json.dumps({"clients": [client_data]}) - - data = {"id": 1, "settings": settings} - - headers = { - "Content-Type": "application/json", - } - - async with session.post(url, json=data, headers=headers) as response: - logger.info(f"Запрос на добавление клиента: {data}") - logger.info(f"Статус ответа: {response.status}") - response_text = await response.text() - logger.info(f"Ответ от сервера: {response_text}") - - if response.status == 200: - logger.info(f"Клиент добавлен: email={email}") - return await response.json() - else: - logger.error( - f"Ошибка при добавлении клиента: {response.status}, {response_text}" - ) - return None - - -async def reset_client_traffic(session, server_id: str, email: str) -> bool: - """Сбрасывает трафик клиента на сервере""" - api_url = SERVERS[server_id]["API_URL"] - url = f"{api_url}/panel/api/inbounds/1/resetClientTraffic/{email}" - headers = {"Accept": "application/json"} - + """ + Adds a client to the server via 3x-ui. + """ try: - async with session.post(url, headers=headers) as response: - if response.status == 200: - logger.info( - f"Трафик клиента {email} успешно сброшен на сервере {server_id}" - ) - return True - else: - logger.error( - f"Ошибка при сбросе трафика клиента {email} на сервере {server_id}: {response.status} - {await response.text()}" - ) - return False - except Exception as e: - logger.error( - f"Ошибка при попытке сброса трафика клиента {email} на сервере {server_id}: {e}" + await xui.login() + + client = py3xui.Client( + id=client_id, + email=email.lower(), + limit_ip=limit_ip, + total_gb=total_gb, + expiry_time=expiry_time, + enable=enable, + tg_id=tg_id, + sub_id=email, + flow=flow, ) - return False + response = await xui.client.add(1, [client]) -async 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"] + if response: + logger.info(f"Клиент {email} успешно добавлен с ID {client_id}.") + else: + logger.error(f"Не удалось добавить клиента {email} с ID {client_id}.") - async with session.get( - f"{api_url}/panel/api/inbounds/getClientTraffics/{email}" - ) as response: - logger.info(f"GET {response.url} Status: {response.status}") - response_text = await response.text() - logger.info(f"GET Response: {response_text}") + return response if response else {"status": "failed"} - if response.status != 200: - logger.error( - f"Ошибка при получении данных клиента: {response.status} - {response_text}" - ) - return False - - client_data = (await response.json()).get("obj", {}) - logger.info(client_data) - - if not client_data: - logger.error("Не удалось получить данные клиента.") - return False - - current_expiry_time = client_data.get("expiryTime", 0) - - if current_expiry_time == 0: - current_expiry_time = new_expiry_time - - updated_expiry_time = max(current_expiry_time, new_expiry_time) - - payload = { - "id": 1, - "settings": json.dumps( - { - "clients": [ - { - "id": client_id, - "alterId": 0, - "email": email.lower(), - "limitIp": 2, - "totalGB": 0, - "expiryTime": updated_expiry_time, - "enable": True, - "tgId": tg_id, - "subId": email, - "flow": "xtls-rprx-vision", - } - ] - } - ), - } - - headers = {"Content-Type": "application/json", "Accept": "application/json"} - - try: - async with session.post( - f"{api_url}/panel/api/inbounds/updateClient/{client_id}", - json=payload, - headers=headers, - ) as response: - logger.info(f"POST {response.url} Status: {response.status}") - logger.info(f"POST Request Data: {json.dumps(payload, indent=2)}") - response_text = await response.text() - logger.info(f"POST Response: {response_text}") - - if response.status == 200: - return True - else: - logger.error( - f"Ошибка при продлении ключа: {response.status} - {response_text}" - ) - return False - except Exception as e: - logger.error(f"Ошибка запроса: {e}") - return False - - -async def extend_client_key_admin( - session, server_id: str, tg_id, client_id: str, email: str, new_expiry_time: int -) -> bool: - api_url = SERVERS[server_id]["API_URL"] - - payload = { - "id": 1, - "settings": json.dumps( - { - "clients": [ - { - "id": client_id, - "alterId": 0, - "email": email.lower(), - "limitIp": 2, - "totalGB": 0, - "expiryTime": new_expiry_time, - "enable": True, - "tgId": tg_id, - "subId": email, - "flow": "xtls-rprx-vision", - } - ] - } - ), - } - - headers = {"Content-Type": "application/json", "Accept": "application/json"} - - try: - async with session.post( - f"{api_url}/panel/api/inbounds/updateClient/{client_id}", - json=payload, - headers=headers, - ) as response: - logger.info(f"POST {response.url} Status: {response.status}") - logger.info(f"POST Request Data: {json.dumps(payload, indent=2)}") - response_text = await response.text() - logger.info(f"POST Response: {response_text}") - - if response.status == 200: - return True - else: - logger.error( - f"Ошибка при продлении ключа: {response.status} - {response_text}" - ) - return False except Exception as e: - logger.error(f"Ошибка запроса: {e}") - return False + logger.error(f"Ошибка при добавлении клиента {email}: {e}") + return {"status": "failed", "error": str(e)} -async def delete_client(session, server_id: str, client_id: str) -> bool: - api_url = SERVERS[server_id]["API_URL"] - url = f"{api_url}/panel/api/inbounds/1/delClient/{client_id}" - headers = {"Accept": "application/json"} - +async def extend_client_key(xui, email: str, new_expiry_time: int, client_id: str): + """ + Функция для обновления срока действия ключа клиента по email. + """ + await xui.login() try: - async with session.post(url, headers=headers) as response: - if response.status == 200: - return True - else: - logger.error( - f"Ошибка при удалении клиента: {response.status} - {await response.text()}" - ) - return False + client = await xui.client.get_by_email(email) + + if not client: + logger.warning(f"Клиент с email {email} не найден.") + return + + if not client.id: + logger.warning(f"Ошибка: клиент {email} не имеет действительного ID.") + return + + logger.info( + f"Обновление ключа клиента {client.email} с ID {client.id} до нового времени: {new_expiry_time}" + ) + client.id = client_id + client.expiry_time = new_expiry_time + + await xui.client.update(client.id, client) + logger.info( + f"Ключ клиента {client.email} успешно продлён до {new_expiry_time}." + ) + except Exception as e: - logger.error(f"Ошибка запроса: {e}") - return False + logger.error(f"Ошибка при обновлении клиента с email {email}: {e}") + + +async def delete_client( + xui, + email: str, + client_id: str, +): + """ + Функция для удаления клиента с сервера 3x-ui + """ + await xui.login() + try: + client = await xui.client.get_by_email(email) + client.id = client_id + + if not client: + logger.warning(f"Клиент с email {client_id} не найден.") + return + + inbound_id = 1 + await xui.client.delete(inbound_id, client.id) + + logger.info(f"Клиент с ID {client_id} был удален успешно.") + + except Exception as e: + logger.error(f"Ошибка при удалении клиента с ID {client_id}: {e}") diff --git a/handlers/admin/admin.py b/handlers/admin/admin.py index 6106ce79..b17f5286 100644 --- a/handlers/admin/admin.py +++ b/handlers/admin/admin.py @@ -1,14 +1,7 @@ -from datetime import datetime - -import asyncpg from aiogram import Router, types from aiogram.filters import Command -from loguru import logger -from auth import login_with_credentials -from client import extend_client_key_admin -from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL -from database import add_balance_to_client, check_connection_exists, get_client_id_by_email, get_tg_id_by_client_id, update_key_expiry +from database import add_balance_to_client, check_connection_exists router = Router() @@ -32,73 +25,3 @@ async def cmd_add_balance(message: types.Message, is_admin: bool): ) except Exception as e: await message.reply(f"Произошла ошибка: {e}") - - -@router.message(Command("update_key_expiry")) -async def cmd_update_key_expiry(message: types.Message, is_admin: bool): - if is_admin: - try: - parts = message.text.split(maxsplit=2) - if len(parts) != 3: - await message.reply( - "Пожалуйста, используйте формат: /update_key_expiry " - ) - return - - _, email, expiry_time_str = parts - expiry_time = int( - datetime.strptime(expiry_time_str, "%Y-%m-%d %H:%M:%S").timestamp() - * 1000 - ) - - client_id = await get_client_id_by_email(email) - if client_id is None: - await message.reply(f"Клиент с email {email} не найден.") - return - - await update_key_expiry(client_id, expiry_time) - - conn = await asyncpg.connect(DATABASE_URL) - try: - record = await conn.fetchrow( - "SELECT server_id FROM keys WHERE client_id = $1", client_id - ) - if not record: - await message.reply("Клиент не найден в базе данных.") - return - - server_id = record["server_id"] - tg_id = await get_tg_id_by_client_id(client_id) - - session = await login_with_credentials( - server_id, ADMIN_USERNAME, ADMIN_PASSWORD - ) - - logger.info( - f"Попытка обновить панель для server_id: {server_id}, tg_id: {tg_id}, client_id: {client_id}, email: {email}, expiryTime: {expiry_time}" - ) - - success = await extend_client_key_admin( - session, server_id, tg_id, client_id, email, expiry_time - ) - - logger.info( - f"Статус обновления панели: {'Успешно' if success else 'Не удалось'}" - ) - if success: - await message.reply( - f"Время истечения ключа для клиента {client_id} ({email}) обновлено и синхронизировано с панелью." - ) - else: - await message.reply( - f"Время истечения ключа для клиента {client_id} ({email}) обновлено, но не удалось синхронизировать с панелью." - ) - - finally: - await conn.close() - except ValueError: - await message.reply( - "Пожалуйста, используйте формат: /update_key_expiry " - ) - except Exception as e: - await message.reply(f"Произошла ошибка: {e}") diff --git a/handlers/admin/user_editor.py b/handlers/admin/user_editor.py index 1cff36c7..79bc160b 100644 --- a/handlers/admin/user_editor.py +++ b/handlers/admin/user_editor.py @@ -8,12 +8,11 @@ from aiogram.fsm.state import State, StatesGroup from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup from loguru import logger -from auth import login_with_credentials from bot import bot -from client import delete_client, extend_client_key_admin -from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, SERVERS -from database import get_client_id_by_email, get_tg_id_by_client_id, update_key_expiry +from config import DATABASE_URL, SERVERS +from database import get_client_id_by_email, update_key_expiry from handlers.admin.admin_panel import back_to_admin_menu +from handlers.keys.key_utils import delete_key_from_server, renew_server_key from handlers.utils import sanitize_key_name router = Router() @@ -189,7 +188,7 @@ async def process_key_edit(callback_query: CallbackQuery): InlineKeyboardButton( text="Назад", callback_data="back_to_user_editor" ) - ], # Кнопка "Назад" + ], ] ) @@ -332,16 +331,12 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext): await state.clear() return - tg_id = await get_tg_id_by_client_id(client_id) - async def update_key_on_all_servers(): tasks = [] for server_id in SERVERS: tasks.append( asyncio.create_task( - renew_server_key( - server_id, tg_id, client_id, email, expiry_time - ) + renew_server_key(server_id, email, client_id, expiry_time) ) ) await asyncio.gather(*tasks) @@ -372,20 +367,6 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext): await state.clear() -async def renew_server_key(server_id, tg_id, client_id, email, new_expiry_time): - try: - session = await login_with_credentials( - server_id, ADMIN_USERNAME, ADMIN_PASSWORD - ) - await extend_client_key_admin( - session, server_id, tg_id, client_id, email, new_expiry_time - ) - except Exception as e: - logger.error( - f"Не удалось обновить ключ {client_id} на сервере {server_id}: {e}" - ) - - @router.callback_query(F.data.startswith("delete_key_admin|")) async def process_callback_delete_key(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id @@ -447,6 +428,7 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery): ) if record: + email = record["email"] response_message = "Ключ успешно удален." back_button = types.InlineKeyboardButton( text="Назад", callback_data="view_keys" @@ -456,7 +438,9 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery): async def delete_key_from_servers(): tasks = [] for server_id in SERVERS: - tasks.append(delete_key_from_server(server_id, client_id)) + tasks.append( + delete_key_from_server(server_id, email, client_id) + ) await asyncio.gather(*tasks) await delete_key_from_servers() @@ -495,22 +479,6 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery): await callback_query.answer() -async def delete_key_from_server(server_id, client_id): - """Удаление ключа с сервера""" - try: - session = await login_with_credentials( - server_id, ADMIN_USERNAME, ADMIN_PASSWORD - ) - success = await delete_client(session, server_id, client_id) - - if not success: - logger.error(f"Ошибка удаления ключа {client_id} на сервере {server_id}") - except Exception as e: - logger.error( - f"Ошибка при удалении ключа {client_id} с сервера {server_id}: {e}" - ) - - async def delete_key_from_db(client_id): """Удаление ключа из базы данных""" try: diff --git a/handlers/keys/key_utils.py b/handlers/keys/key_utils.py index 4872dd42..23abfd35 100644 --- a/handlers/keys/key_utils.py +++ b/handlers/keys/key_utils.py @@ -1,19 +1,27 @@ import asyncpg from loguru import logger +from py3xui import AsyncApi -from auth import login_with_credentials -from client import add_client, delete_client, extend_client_key, reset_client_traffic -from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, RESET_TRAFFIC +from client import add_client, delete_client, extend_client_key +from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, SERVERS async def create_key_on_server(server_id, tg_id, client_id, email, expiry_timestamp): try: - session = await login_with_credentials( - server_id, ADMIN_USERNAME, ADMIN_PASSWORD + xui = AsyncApi( + SERVERS[server_id]["API_URL"], + username=ADMIN_USERNAME, + password=ADMIN_PASSWORD, ) - response = await add_client( - session, - server_id, + + conn = await asyncpg.connect(DATABASE_URL) + existing_key = await conn.fetchrow("SELECT 1 FROM keys WHERE email = $1", email) + + if existing_key: + raise ValueError(f"Email {email} уже существует в базе данных.") + + await add_client( + xui, client_id, email, tg_id, @@ -23,30 +31,26 @@ async def create_key_on_server(server_id, tg_id, client_id, email, expiry_timest enable=True, flow="xtls-rprx-vision", ) - if not response.get("success", True): - error_msg = response.get("msg", "Неизвестная ошибка.") - if "Duplicate email" in error_msg: - raise ValueError(f"Имя {email} уже занято на сервере {server_id}") - else: - raise Exception(error_msg) + + await conn.close() + except Exception as e: logger.error(f"Ошибка на сервере {server_id}: {e}") + raise e -async def renew_server_key( - server_id, tg_id, client_id, email, new_expiry_time, reset_traffic=RESET_TRAFFIC -): +async def renew_server_key(server_id, email, client_id, new_expiry_time): + """ + Функция для продления срока действия ключа на сервере и сброса трафика, если необходимо. + """ try: - session = await login_with_credentials( - server_id, ADMIN_USERNAME, ADMIN_PASSWORD + xui = AsyncApi( + SERVERS[server_id]["API_URL"], + username=ADMIN_USERNAME, + password=ADMIN_PASSWORD, ) - await extend_client_key( - session, server_id, tg_id, client_id, email, new_expiry_time - ) - - if reset_traffic: - await reset_client_traffic(session, server_id, email) + await extend_client_key(xui, email, new_expiry_time, client_id) except Exception as e: logger.error( @@ -65,32 +69,30 @@ async def delete_key_from_db(client_id): await conn.close() -async def delete_key_from_server(server_id, client_id): +async def delete_key_from_server(server_id, email, client_id): """Удаление ключа с сервера""" try: - async with await login_with_credentials( - server_id, ADMIN_USERNAME, ADMIN_PASSWORD - ) as session: - success = await delete_client(session, server_id, client_id) - - if not success: - logger.error( - f"Ошибка удаления ключа {client_id} на сервере {server_id}" - ) - except Exception as e: - logger.error( - f"Ошибка при удалении ключа {client_id} с сервера {server_id}: {e}" + xui = AsyncApi( + SERVERS[server_id]["API_URL"], + username=ADMIN_USERNAME, + password=ADMIN_PASSWORD, ) + await delete_client(xui, email, client_id) + + except Exception as e: + logger.error(f"Не удалось удалить ключ {client_id} на сервере {server_id}: {e}") + async def update_key_on_server(tg_id, client_id, email, expiry_time, server_id): try: - session = await login_with_credentials( - server_id, ADMIN_USERNAME, ADMIN_PASSWORD + xui = AsyncApi( + SERVERS[server_id]["API_URL"], + username=ADMIN_USERNAME, + password=ADMIN_PASSWORD, ) - response = await add_client( - session, - server_id, + await add_client( + xui, client_id, email, tg_id, @@ -101,12 +103,7 @@ async def update_key_on_server(tg_id, client_id, email, expiry_time, server_id): flow="xtls-rprx-vision", ) - if not response.get("success"): - logger.error( - f"Ошибка при обновлении ключа на сервере {server_id} для {client_id}" - ) - else: - logger.info(f"Ключ успешно обновлен на сервере {server_id} для {client_id}") + logger.info(f"Ключ успешно обновлен на сервере {server_id} для {client_id}") except Exception as e: logger.error( diff --git a/handlers/keys/keys.py b/handlers/keys/keys.py index 5d1be053..5ecb0ea0 100644 --- a/handlers/keys/keys.py +++ b/handlers/keys/keys.py @@ -472,6 +472,7 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery): ) if record: + email = record["email"] response_message = "Ключ успешно удален." back_button = types.InlineKeyboardButton( text="Назад", callback_data="view_keys" @@ -490,7 +491,9 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery): try: tasks = [] for server_id in SERVERS: - tasks.append(delete_key_from_server(server_id, client_id)) + tasks.append( + delete_key_from_server(server_id, email, client_id) + ) await asyncio.gather(*tasks) @@ -606,7 +609,7 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery): for server_id in SERVERS: task = asyncio.create_task( renew_server_key( - server_id, tg_id, client_id, email, new_expiry_time + server_id, email, client_id, new_expiry_time ) ) tasks.append(task) diff --git a/handlers/keys/trial_key.py b/handlers/keys/trial_key.py index 117868d8..048d2946 100644 --- a/handlers/keys/trial_key.py +++ b/handlers/keys/trial_key.py @@ -3,11 +3,11 @@ import uuid from datetime import datetime, timedelta import asyncpg -from loguru import logger +from py3xui import AsyncApi -from config import DATABASE_URL, PUBLIC_LINK, SERVERS +from client import add_client +from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, PUBLIC_LINK, SERVERS from database import store_key -from handlers.keys.key_utils import create_key_on_server from handlers.texts import INSTRUCTIONS from handlers.utils import generate_random_email @@ -45,33 +45,53 @@ async def generate_and_store_keys( tasks = [] for server_id in SERVERS: task = create_key_on_server( - server_id, tg_id, client_id, email, expiry_timestamp + server_id, client_id, email, tg_id, expiry_timestamp ) tasks.append(task) - results = await asyncio.gather(*tasks) + await asyncio.gather(*tasks) - if all(result.get("success") for result in results): - await store_key( - tg_id, - client_id, - email, - expiry_timestamp, - public_link, - server_id="all_servers", - ) + await store_key( + tg_id, + client_id, + email, + expiry_timestamp, + public_link, + server_id="all_servers", + ) - await conn.execute( - """ - INSERT INTO connections (tg_id, trial) - VALUES ($1, 1) - ON CONFLICT (tg_id) + await conn.execute( + """ + INSERT INTO connections (tg_id, trial) + VALUES ($1, 1) + ON CONFLICT (tg_id) DO UPDATE SET trial = 1 """, - tg_id, - ) - else: - logger.error("Не удалось создать ключ на одном или нескольких серверах.") - + tg_id, + ) finally: await conn.close() + + +async def create_key_on_server( + server_id: str, client_id: str, email: str, tg_id: int, expiry_timestamp: int +): + """Создает ключ на сервере и возвращает результат.""" + + xui = AsyncApi( + SERVERS[server_id]["API_URL"], username=ADMIN_USERNAME, password=ADMIN_PASSWORD + ) + + response = await add_client( + xui, + client_id, + email, + tg_id, + limit_ip=1, + total_gb=0, + expiry_time=expiry_timestamp, + enable=True, + flow="xtls-rprx-vision", + ) + + return response diff --git a/handlers/notifications.py b/handlers/notifications.py index f87a7dec..1603c0e2 100644 --- a/handlers/notifications.py +++ b/handlers/notifications.py @@ -4,8 +4,8 @@ from datetime import datetime, timedelta import asyncpg from aiogram import Bot, Router, types from loguru import logger +from py3xui import AsyncApi -from auth import login_with_credentials from client import delete_client, extend_client_key from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, SERVERS from database import delete_key, get_balance, update_balance, update_key_expiry @@ -228,7 +228,7 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time: """ SELECT tg_id, client_id, expiry_time, email FROM keys WHERE expiry_time <= $1 - """, + """, adjusted_current_time, ) @@ -245,109 +245,68 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time: current_date = datetime.utcnow() time_left = expiry_date - current_date logger.info( - f"Время истечения ключа: {expiry_time} (дата: {expiry_date}), Текущее время: {current_date}, Оставшееся время: {time_left}." + f"Время истечения ключа: {expiry_time} (дата: {expiry_date}), Текущее время: {current_date}, Оставшееся время: {time_left}" ) - message_expired = f"Ваш ключ {email} истек и был удален!\n\n Перейдите в профиль для создания нового ключа" + message_expired = f"Ваша подписка {email} истекла и была удалена!\n\n Перейдите в профиль для создания нового ключа" button_profile = types.InlineKeyboardButton( text="👤 Мой профиль", callback_data="view_profile" ) keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[button_profile]]) - if balance >= RENEWAL_PLANS["1"]["price"]: - await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"]) - new_expiry_time = int( - (datetime.utcnow() + timedelta(days=30)).timestamp() * 1000 - ) - await update_key_expiry(client_id, new_expiry_time) - logger.info( - f"Ключ для клиента {tg_id} продлен до {datetime.utcfromtimestamp(new_expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')}." - ) - - all_success = True - for server_id in SERVERS: - session = await login_with_credentials( - server_id, ADMIN_USERNAME, ADMIN_PASSWORD + try: + if balance >= RENEWAL_PLANS["1"]["price"]: + await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"]) + new_expiry_time = int( + (datetime.utcnow() + timedelta(days=30)).timestamp() * 1000 ) - success = await extend_client_key( - session, server_id, tg_id, client_id, email, new_expiry_time + await update_key_expiry(client_id, new_expiry_time) + logger.info( + f"Ключ для клиента {tg_id} продлен до {datetime.utcfromtimestamp(new_expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')}." ) - if not success: - all_success = False - logger.error( - f"Не удалось продлить ключ для пользователя {tg_id} на сервере {server_id}." - ) - if all_success: - try: - await bot.send_message(tg_id, KEY_RENEWED, reply_markup=keyboard) - logger.info( - f"Ключ для пользователя {tg_id} успешно продлен на месяц на всех серверах." + all_success = True + for server_id in SERVERS: + xui = AsyncApi( + SERVERS[server_id]["API_URL"], + username=ADMIN_USERNAME, + password=ADMIN_PASSWORD, ) - except Exception as e: - if "blocked" in str(e).lower(): - logger.warning( - f"Пользователь {tg_id} заблокирован. Ключ будет удален." + success = await extend_client_key( + xui, email, new_expiry_time, client_id + ) + if not success: + all_success = False + logger.error( + f"Не удалось продлить ключ для пользователя {tg_id} на сервере {server_id}." ) - await delete_key(client_id) - for server_id in SERVERS: - session = await login_with_credentials( - server_id, ADMIN_USERNAME, ADMIN_PASSWORD - ) - success = await delete_client(session, server_id, client_id) - if success: - logger.info( - f"Ключ для клиента {tg_id} успешно удален с сервера {server_id}." - ) - else: - logger.error( - f"Не удалось удалить ключ для клиента {tg_id} на сервере {server_id}." - ) - else: + + if all_success: + try: + await bot.send_message( + tg_id, KEY_RENEWED, reply_markup=keyboard + ) + logger.info( + f"Ключ для пользователя {tg_id} успешно продлен на месяц на всех серверах." + ) + except Exception as e: logger.error( f"Ошибка при отправке уведомления о продлении ключа пользователю {tg_id}: {e}" ) else: try: await bot.send_message( - tg_id, KEY_RENEWAL_FAILED, reply_markup=keyboard - ) - logger.error( - f"Не удалось продлить ключ для пользователя {tg_id} на одном или нескольких серверах." - ) - except Exception as e: - logger.error( - f"Ошибка при отправке уведомления о неудачном продлении ключа пользователю {tg_id}: {e}" - ) - else: - try: - await bot.send_message(tg_id, message_expired, reply_markup=keyboard) - await delete_key(client_id) - - for server_id in SERVERS: - session = await login_with_credentials( - server_id, ADMIN_USERNAME, ADMIN_PASSWORD - ) - success = await delete_client(session, server_id, client_id) - if success: - logger.info( - f"Ключ для клиента {tg_id} успешно удален с сервера {server_id}." - ) - else: - logger.error( - f"Не удалось удалить ключ для клиента {tg_id} на сервере {server_id}." - ) - except Exception as e: - if "blocked" in str(e).lower(): - logger.warning( - f"Пользователь {tg_id} заблокирован. Ключ будет удален." + tg_id, message_expired, reply_markup=keyboard ) await delete_key(client_id) + for server_id in SERVERS: - session = await login_with_credentials( - server_id, ADMIN_USERNAME, ADMIN_PASSWORD + xui = AsyncApi( + SERVERS[server_id]["API_URL"], + username=ADMIN_USERNAME, + password=ADMIN_PASSWORD, ) - success = await delete_client(session, server_id, client_id) + success = await delete_client(xui, email, client_id) if success: logger.info( f"Ключ для клиента {tg_id} успешно удален с сервера {server_id}." @@ -356,7 +315,10 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time: logger.error( f"Не удалось удалить ключ для клиента {tg_id} на сервере {server_id}." ) - else: + except Exception as e: logger.error(f"Ошибка при удалении ключа для клиента {tg_id}: {e}") + except Exception as e: + logger.error(f"Ошибка при обработке ключа для клиента {tg_id}: {e}") + await asyncio.sleep(1) diff --git a/handlers/start.py b/handlers/start.py index 40f76706..94ef8540 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -11,7 +11,7 @@ from bot import bot from config import APP_URL, CHANNEL_URL, DATABASE_URL, SUPPORT_CHAT_URL from database import add_connection, add_referral, check_connection_exists, get_trial from handlers.keys.trial_key import create_trial_key -from handlers.texts import ABOUT_VPN, INSTRUCTIONS_TRIAL, WELCOME_TEXT +from handlers.texts import INSTRUCTIONS_TRIAL, WELCOME_TEXT, get_about_vpn router = Router() @@ -137,11 +137,13 @@ async def handle_connect_vpn(callback_query: CallbackQuery): async def handle_about_vpn(callback_query: CallbackQuery): await callback_query.message.delete() + about_vpn_message = get_about_vpn("3.0.4") + builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu")) await callback_query.message.answer( - ABOUT_VPN, parse_mode="HTML", reply_markup=builder.as_markup() + about_vpn_message, parse_mode="HTML", reply_markup=builder.as_markup() ) await callback_query.answer() diff --git a/middlewares/logging.py b/middlewares/logging.py index a6471c5d..cc51f324 100644 --- a/middlewares/logging.py +++ b/middlewares/logging.py @@ -25,7 +25,6 @@ class UserActivityMiddleware(BaseMiddleware): username = event.from_user.username action = f"Обратный вызов: {event.data}" - # Логируем действие пользователя logger.info( f"Активность пользователя - " f"ID пользователя: {user_id}, " @@ -33,5 +32,4 @@ class UserActivityMiddleware(BaseMiddleware): f"Действие: {action}" ) - # Продолжаем выполнение обработчика return await handler(event, data)