diff --git a/databases/mongodb.py b/databases/mongodb.py index d960454..7b06bb8 100644 --- a/databases/mongodb.py +++ b/databases/mongodb.py @@ -24,13 +24,13 @@ class MongoDBRepository: self.logger.debug(f"Тарифный план добавлен с ID: {result.inserted_id}") return result.inserted_id - async def get_subscription_plan(self, plan_id): + async def get_subscription_plan(self, plan_name): """Получает тарифный план по его имени.""" - plan = await self.plans_collection.find_one({"_id": plan_id}) + plan = await self.plans_collection.find_one({"name": plan_name}) if plan: self.logger.debug(f"Найден тарифный план: {plan}") else: - self.logger.error(f"Тарифный план {plan_id} не найден.") + self.logger.error(f"Тарифный план {plan_name} не найден.") return plan async def add_server(self, server_data): diff --git a/databases/postgresql.py b/databases/postgresql.py index 984625d..8b01a04 100644 --- a/databases/postgresql.py +++ b/databases/postgresql.py @@ -132,80 +132,75 @@ class DatabaseManager: self.logger.error(f"Тарифный план {plan_id} не найден.") return "ERROR" - # Проверка достаточности средств для покупки подписки + # Проверка достаточности средств cost = int(plan["price"]) - if result.balance >= cost: - result.balance -= cost - await session.commit() - - # Создание подписки для пользователя - expiry_date = datetime.utcnow() + relativedelta(months=plan["duration_months"]) - server = await self.mongo_repo.get_server_with_least_clients() - self.logger.info(f"{server}") - new_subscription = Subscription(user_id=result.id, vpn_server_id=str(server['server']["name"]), plan=plan_id, expiry_date=expiry_date) - session.add(new_subscription) - await session.commit() - - self.logger.info(f"Подписка успешно оформлена для пользователя {telegram_id} на план {plan_id}.") - return "OK" - else: + if result.balance < cost: self.logger.error(f"Недостаточно средств у пользователя {telegram_id} для покупки плана {plan_id}.") return "INSUFFICIENT_FUNDS" - except SQLAlchemyError as e: - self.logger.error(f"Ошибка при покупке подписки {plan_id} для пользователя {telegram_id}: {e}") - await session.rollback() - return "ERROR" - async def add_to_server(self, telegram_id: int): - """ - Метод для добавления пользователя на сервер. - """ - async for session in self.session_generator(): - try: - # Получаем подписку пользователя по telegram_id - result = await session.execute(select(Subscription).join(User).where(User.telegram_id == int(telegram_id))) - user_sub = result.scalars().first() + # Списываем средства + result.balance -= cost - if not user_sub: - self.logger.error(f"Не удалось найти подписку для пользователя с Telegram ID {telegram_id}.") - return "ERROR" + # Создаем подписку + expiry_date = datetime.utcnow() + relativedelta(months=plan["duration_months"]) + server = await self.mongo_repo.get_server_with_least_clients() + self.logger.info(f"Выбран сервер для подписки: {server}") + new_subscription = Subscription( + user_id=result.id, + vpn_server_id=str(server['server']["name"]), + plan=plan_id, + expiry_date=expiry_date + ) + session.add(new_subscription) + # Попытка добавить пользователя на сервер # Получаем информацию о пользователе - user_result = await session.execute(select(User).where(User.telegram_id == telegram_id)) - user = user_result.scalars().first() - - # Получаем сервер с MongoDB - server = await self.mongo_repo.get_server(user_sub.vpn_server_id) - - if not server: - self.logger.error(f"Не удалось найти сервер с ID {user_sub.vpn_server_id}.") + user = result # так как result уже содержит пользователя + if not user: + self.logger.error(f"Не удалось найти пользователя для добавления на сервер.") + await session.rollback() return "ERROR" - # Доступ к данным сервера для добавления клиента - server_info = server['server'] + # Получаем сервер из MongoDB + server_data = await self.mongo_repo.get_server(new_subscription.vpn_server_id) + if not server_data: + self.logger.error(f"Не удалось найти сервер с ID {new_subscription.vpn_server_id}.") + await session.rollback() + return "ERROR" + + server_info = server_data['server'] url_base = f"https://{server_info['ip']}:{server_info['port']}/{server_info['secretKey']}" login_data = { 'username': server_info['login'], 'password': server_info['password'], } - # Инициализируем взаимодействие с панелью управления - panel = PanelInteraction(url_base, login_data, self.logger) + panel = PanelInteraction(url_base, login_data, self.logger,) + expiry_date_iso = new_subscription.expiry_date.isoformat() - expiry_date_iso = user_sub.expiry_date.isoformat() + # Добавляем на сервер response = await panel.add_client(user.id, expiry_date_iso, user.username) - # Логируем результат - if response == "OK": - self.logger.info(f"Клиент {telegram_id} успешно добавлен на сервер.") - return "OK" - else: + if response != "OK": self.logger.error(f"Ошибка при добавлении клиента {telegram_id} на сервер: {response}") + # Если не получилось добавить на сервер, откатываем транзакцию + await session.rollback() return "ERROR" - except Exception as e: - self.logger.error(f"Ошибка при установке на сервер для пользователя {telegram_id}: {e}") + # Если мы здесь - значит и подписка, и добавление на сервер успешны + await session.commit() + self.logger.info(f"Подписка успешно оформлена для пользователя {telegram_id} на план {plan_id} и клиент добавлен на сервер.") + return "OK" + + except SQLAlchemyError as e: + self.logger.error(f"Ошибка при покупке подписки {plan_id} для пользователя {telegram_id}: {e}") + await session.rollback() return "ERROR" + except Exception as e: + self.logger.error(f"Непредвиденная ошибка: {e}") + await session.rollback() + return "ERROR" + @staticmethod def generate_string(length): diff --git a/Dockerfile b/docker/Dockerfile similarity index 100% rename from Dockerfile rename to docker/Dockerfile diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 4bd3705..eb0e09b 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -24,7 +24,4 @@ services: PLAN_COLLECTION: "plans" volumes: - logs_data:/app/logs - depends_on: - - postgres - - mongodb command: ["python", "main.py"] diff --git a/service/service.py b/service/service.py deleted file mode 100644 index 406b78f..0000000 --- a/service/service.py +++ /dev/null @@ -1,263 +0,0 @@ -from db import User, Subscription, Transaction, VPNServer -import string -import secrets -import json -from sqlalchemy import desc -from dateutil.relativedelta import relativedelta -from datetime import datetime -from db import get_db_session -from panel import PanelInteraction - -from utils.LogCon import setup_logger, load_config - -config = load_config() - -def generate_random_string(length=8): - characters = string.ascii_letters + string.digits - return ''.join(secrets.choice(characters) for _ in range(length)) - - - -class UserService: - def __init__(self, logger): - self.logger = logger - - def add_user(self, telegram_id: int): - session = next(get_db_session()) - try: - new_user = User(telegram_id=telegram_id, username=generate_random_string()) - session.add(new_user) - session.commit() - except Exception as e: - session.rollback() - self.logger.error(f"Ошибка при добавлении пользователя: {e}") - finally: - session.close() - - def get_user_by_telegram_id(self, telegram_id: int): - session = next(get_db_session()) - try: - return session.query(User).filter(User.telegram_id == telegram_id).first() - except Exception as e: - self.logger.error(f"Ошибка при получении пользователя: {e}") - finally: - session.close() - - def add_transaction(self, user_id: int, amount: float): - session = next(get_db_session()) - try: - transaction = Transaction(user_id=user_id, amount=amount) - session.add(transaction) - session.commit() - except Exception as e: - self.logger.error(f"Ошибка добавления транзакции: {e}") - finally: - session.close() - - def update_balance(self, telegram_id: int, amount: float): - session = next(get_db_session()) - try: - user = session.query(User).filter(User.telegram_id == telegram_id).first() - if user: - user.balance = amount - self.add_transaction(user.id, amount) - session.commit() - else: - self.logger.warning(f"Пользователь с Telegram ID {telegram_id} не найден.") - except Exception as e: - session.rollback() - self.logger.error(f"Ошибка при обновлении баланса: {e}") - finally: - session.close() - - def last_subscription(self, user): - session = next(get_db_session()) - try: - return ( - session.query(Subscription) - .filter(Subscription.user_id == user.id) - .order_by(desc(Subscription.created_at)) - .first() - ) - except Exception as e: - self.logger.error(f"Ошибка при получении последней подписки: {e}") - finally: - session.close() - - def tariff_setting(self, user, plan: str, expiry_duration: int): - session = next(get_db_session()) - try: - server = ( - session.query(VPNServer) - .filter(VPNServer.current_users < VPNServer.max_users) - .order_by(VPNServer.current_users.asc()) - .first() - ) - - if not server: - self.logger.error("Error: 120") - return "120" - - # Рассчитываем дату окончания подписки - expiry_ = datetime.utcnow() + relativedelta(months=expiry_duration) - self.logger.info(f"Create subscribe to {user.id} on server {server.id} with plan {plan} until {expiry_}") - - new_subscription = Subscription(user_id=user.id, vpn_server_id=server.id, plan=plan, expiry_date=expiry_) - session.add(new_subscription) - session.commit() - - self.logger.info(f"Subscribe successfully created for {user.id}") - return "OK" - except Exception as e: - self.logger.error(f"Error with created subscribe: {e}") - return "Ошибка" - finally: - session.close() - - def buy_sub(self, telegram_id: str, plan: str): - session = next(get_db_session()) - try: - user = session.query(User).filter(User.telegram_id == telegram_id).first() - if not user: - self.logger.error(f"User with Telegram ID {telegram_id} not found.") - return "error" - - current_plan = config['subscription_templates'].get(plan) - if not current_plan: - self.logger.error(f"Tarif {plan} not found.") - return "error" - - cost = current_plan['cost'] - if user.balance >= cost: - user.balance -= cost - session.commit() - result = self.tariff_setting(user, plan, current_plan['duration']) - if result == "OK": - add_server_result = self.add_to_server(telegram_id) - if add_server_result == "OK": - return "OK" - else: - return "ERROR " + add_server_result - else: - return "ERROR " + result - - self.logger.error(f"Nt enough money {telegram_id} for {plan}.") - return 100 - - except Exception as e: - self.logger.error(f"Error with buying sub {telegram_id}: {e}") - session.rollback() - finally: - session.close() - - def get_sub_list(self, count: int, user_id: int): - session = next(get_db_session()) - try: - return ( - session.query(Subscription) - .filter(Subscription.user_id == user_id) - .order_by(desc(Subscription.created_at)) - .limit(count) - .all() - ) - except Exception as e: - self.logger.error(f"Ошибка при получении списка подписок для пользователя {user_id}: {e}") - - def add_to_server(self, telegram_id: str): - session = next(get_db_session()) - try: - user_sub = ( - session.query(Subscription) - .join(User) - .filter(User.telegram_id == telegram_id) - .first() - ) - user = session.query(User).filter(User.telegram_id == telegram_id).first() - server = session.query(VPNServer).filter(VPNServer.id == user_sub.vpn_server_id).first() - - url_base = f"https://{server.ip_address}:{server.port}/{server.secret}" - login_data = { - 'username': server.login, - 'password': server.password, - } - - try: - server_config_dict = json.loads(server.config) - except json.JSONDecodeError as e: - self.logger.error(f"Ошибка разбора JSON: {e}") - return "180" - - client_id = server_config_dict['obj']['id'] - panel = PanelInteraction(url_base, login_data, self.logger) - panel.add_client(client_id, user_sub.expiry_date.isoformat(), user.username) - return "OK" - except Exception as e: - self.logger.error(f"Ошибка при установке на сервер для пользователя {telegram_id}: {e}") - return "ERROR" - - def create_uri(self, telegram_id: str): - session = next(get_db_session()) - try: - user = session.query(User).filter(User.telegram_id == telegram_id).first() - if not user: - self.logger.error(f"Пользователь с Telegram ID {telegram_id} не найден.") - return "error" - - sub = self.last_subscription(user) - if not sub: - self.logger.error("Подписка не найдена.") - return "error" - - vpn_server = session.query(VPNServer).filter_by(id=sub.vpn_server_id).first() - base_url = f"https://{vpn_server.ip_address}:{vpn_server.port}/{vpn_server.secret}" - login_data = { - 'username': vpn_server.login, - 'password': vpn_server.password - } - - server_config_dict = json.loads(vpn_server.config) - client_id = server_config_dict['obj']['id'] - - PI = PanelInteraction(base_url, login_data, self.logger) - CIF3 = PI.get_client_traffic(user.username) # Client Info From 3x-ui - VPNCIF3 = PI.getInboundInfo(client_id) - return self.generate_uri(vpn_config=VPNCIF3, CIF3=CIF3) - except Exception as e: - self.logger.error(f"Ошибка в создании URI: {e}") - return "error" - finally: - session.close() - - def generate_uri(self, vpn_config, CIF3): - try: - config_data = json.loads(vpn_config) if isinstance(vpn_config, str) else vpn_config - - obj = config_data["obj"] - port = obj["port"] - - clients = json.loads(obj["settings"])["clients"] if isinstance(obj["settings"], str) else obj["settings"]["clients"] - - for client in clients: - if client["email"] == CIF3['obj']['email']: - uuid = client["id"] - flow = client["flow"] - - stream_settings = json.loads(obj["streamSettings"]) if isinstance(obj["streamSettings"], str) else obj["streamSettings"] - dest = stream_settings["realitySettings"]["dest"] - server_names = stream_settings["realitySettings"]["serverNames"] - public_key = stream_settings["realitySettings"]["settings"]["publicKey"] - fingerprint = stream_settings["realitySettings"]["settings"]["fingerprint"] - short_id = stream_settings["realitySettings"]["shortIds"][0] # Первый короткий ID - - return ( - f"vless://{uuid}@{dest}:{port}?type=tcp&security=reality" - f"&pbk={public_key}&fp={fingerprint}&sni={server_names[0]}" - f"&sid={short_id}&spx=%2F&flow={flow}#user-{CIF3}" - ) - - self.logger.error(f"Клиент с email {CIF3} не найден.") - return None - - except Exception as e: - self.logger.error(f"Ошибка в методе создания URI: {e}") - return None diff --git a/temp_scripts/ca.crt b/temp_scripts/ca.crt new file mode 100644 index 0000000..040a2eb --- /dev/null +++ b/temp_scripts/ca.crt @@ -0,0 +1,37 @@ +-----BEGIN CERTIFICATE----- +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 +MYVC3EGeng== +-----END CERTIFICATE----- diff --git a/temp_scripts/insert_subscriptions.py b/temp_scripts/insert_subscriptions.py new file mode 100644 index 0000000..6d4e79b --- /dev/null +++ b/temp_scripts/insert_subscriptions.py @@ -0,0 +1,74 @@ +import argparse +from datetime import datetime +import json +import glob +from pymongo import MongoClient + +def connect_to_mongo(uri, db_name): + """Подключение к MongoDB.""" + client = MongoClient(uri) + db = client[db_name] + return db + +def load_all_json_from_folder(folder_path): + """Загружает все JSON-файлы из указанной папки.""" + all_data = [] + for file_path in glob.glob(f"{folder_path}/*.json"): + try: + with open(file_path, "r", encoding="utf-8") as f: + data = json.load(f) + all_data.append(data) + except Exception as e: + print(f"Ошибка при чтении файла {file_path}: {e}") + return all_data + + +def fetch_all_documents(mongo_uri, db_name, collection_name): + """Выводит все элементы из указанной коллекции MongoDB.""" + try: + client = MongoClient(mongo_uri) + db = client[db_name] + collection = db[collection_name] + + documents = collection.find() + + print(f"Содержимое коллекции '{collection_name}':") + for doc in documents: + print(doc) + + except Exception as e: + print(f"Ошибка при получении данных: {e}") + finally: + client.close() + +def insert_data(db, collection_name, data): + """Вставляет данные в указанную коллекцию MongoDB.""" + collection = db[collection_name] + for i in data: + collection.insert_one(i) + print(f"Данные '{i}'") + print(f"Данные успешно вставлены в коллекцию '{collection_name}'.") + + + +def main(): + parser = argparse.ArgumentParser(description="Insert JSON data into MongoDB with certificate") + parser.add_argument("--mongo-uri",default="mongodb://root:itOj4CE2miKR@mongodb:27017" ,required=True, help="MongoDB URI") + parser.add_argument("--db-name",default="MongoDBSub&Ser" ,required=True, help="MongoDB database name") + parser.add_argument("--collection",default="servers", required=True, help="Collection name") + parser.add_argument("--json-path", required=True, help="Path to the JSON file with data") + + + args = parser.parse_args() + + db = connect_to_mongo(args.mongo_uri, args.db_name) + + data = load_all_json_from_folder(args.json_path) + + insert_data(db, args.collection, data) + + fetch_all_documents(args.mongo_uri, args.db_name,args.collection) + +if __name__ == "__main__": + main() + diff --git a/temp_scripts/ser.json b/temp_scripts/ser.json new file mode 100644 index 0000000..48919bf --- /dev/null +++ b/temp_scripts/ser.json @@ -0,0 +1,68 @@ +{ + "server": { + "name": "Poland#1", + "ip": "mydomvlv.freemyip.com", + "port": 2053, + "secretKey": "Hd8OsqN5Jh", + "login": "nc1450nP", + "password": "KmajQOuf" + }, + "clients": [ + { + "email": "j8oajwd3", + "inboundId": "1", + "id": "a31d71a4-6afd-4f36-96f6-860691b52873", + "flow": "xtls-rprx-vision", + "limits": { + "ipLimit": 2, + "reset": 0, + "totalGB": 0 + }, + "subscriptions": { + "subId": "ox2awiqwduryuqnz", + "tgId": 1342351277 + } + }, + { + "email": "j8oajwd3121", + "inboundId": "1", + "id": "b6882942-d69d-4d5e-be9a-168ac89b20b6", + "flow": "xtls-rprx-direct", + "limits": { + "ipLimit": 1, + "reset": 0, + "totalGB": 0 + }, + "subscriptions": { + "subId": "jk289x00uf7vbr9x", + "tgId": 123144325 + } + } + ], + "connection": { + "destination": "google.com:443", + "serverNames": [ + "google.com", + "www.google.com" + ], + "security": "reality", + "publicKey": "Bha0eW7nfRc69CdZyF9HlmGVvtAeOJKammhwf4WShTU", + "fingerprint": "random", + "shortIds": [ + "edfaf8ab" + ], + "tcpSettings": { + "acceptProxyProtocol": false, + "headerType": "none" + }, + "sniffing": { + "enabled": true, + "destOverride": [ + "http", + "tls", + "quic", + "fakedns" + ] + } + } + } \ No newline at end of file diff --git a/temp_scripts/subs/sub1.json b/temp_scripts/subs/sub1.json new file mode 100644 index 0000000..f8e7a72 --- /dev/null +++ b/temp_scripts/subs/sub1.json @@ -0,0 +1,8 @@ +{ + "name": "Lark_Standart_1", + "normalName": "Lark Standart", + "type": "standart", + "duration_months": 1, + "ip_limit": 1, + "price": 200 + } \ No newline at end of file diff --git a/temp_scripts/subs/sub2.json b/temp_scripts/subs/sub2.json new file mode 100644 index 0000000..98a4b61 --- /dev/null +++ b/temp_scripts/subs/sub2.json @@ -0,0 +1,8 @@ +{ + "name": "Lark_Standart_6", + "normalName": "Lark Standart", + "type": "standart", + "duration_months": 6, + "ip_limit": 1, + "price": 1000 +} \ No newline at end of file diff --git a/temp_scripts/subs/sub3.json b/temp_scripts/subs/sub3.json new file mode 100644 index 0000000..2e0b277 --- /dev/null +++ b/temp_scripts/subs/sub3.json @@ -0,0 +1,8 @@ +{ + "name": "Lark_Standart_12", + "normalName": "Lark Standart", + "type": "standart", + "duration_months": 12, + "ip_limit": 1, + "price": 2000 + } \ No newline at end of file diff --git a/temp_scripts/subs/sub4.json b/temp_scripts/subs/sub4.json new file mode 100644 index 0000000..fa67c74 --- /dev/null +++ b/temp_scripts/subs/sub4.json @@ -0,0 +1,8 @@ +{ + "name": "Lark_Pro_1", + "normalName": "Lark Pro", + "type": "pro", + "duration_months": 1, + "ip_limit": 5, + "price": 600 + } \ No newline at end of file diff --git a/temp_scripts/subs/sub5.json b/temp_scripts/subs/sub5.json new file mode 100644 index 0000000..3fb1251 --- /dev/null +++ b/temp_scripts/subs/sub5.json @@ -0,0 +1,8 @@ +{ + "name": "Lark_Pro_6", + "normalName": "Lark Pro", + "type": "pro", + "duration_months": 6, + "ip_limit": 5, + "price": 3000 + } \ No newline at end of file diff --git a/temp_scripts/subs/sub6.json b/temp_scripts/subs/sub6.json new file mode 100644 index 0000000..2f24325 --- /dev/null +++ b/temp_scripts/subs/sub6.json @@ -0,0 +1,8 @@ +{ + "name": "Lark_Pro_12", + "normalName": "Lark Pro", + "type": "pro", + "duration_months": 12, + "ip_limit": 5, + "price": 5000 + } \ No newline at end of file diff --git a/utils/panel.py b/utils/panel.py index 7be350b..51b59c2 100644 --- a/utils/panel.py +++ b/utils/panel.py @@ -1,107 +1,187 @@ -import requests +import aiohttp import uuid -import string -import secrets -import json -from datetime import datetime, timedelta -from dateutil.relativedelta import relativedelta - - +import json +import base64 +from datetime import datetime +from dateutil.relativedelta import relativedelta def generate_uuid(): return str(uuid.uuid4()) + class PanelInteraction: - def __init__(self, base_url, login_data, logger_): + def __init__(self, base_url, login_data, logger, certificate=None, is_encoded=True): + """ + Initialize the PanelInteraction class. + + :param base_url: Base URL for the panel. + :param login_data: Login data (username/password or token). + :param logger: Logger for debugging. + :param certificate: Certificate content (Base64-encoded or raw string). + :param is_encoded: Indicates whether the certificate is Base64-encoded. + """ self.base_url = base_url self.login_data = login_data - self.logger = logger_ - self.session_id = self.login() - if self.session_id: - self.headers = { - 'Accept': 'application/json', - 'Cookie': f'3x-ui={self.session_id}', - 'Content-Type': 'application/json' - } - else: - raise ValueError("Login failed, session_id is None") + self.logger = logger + self.cert_content = self._decode_certificate(certificate, is_encoded) + self.session_id = None # Session ID will be initialized lazily + self.headers = None - def login(self): - login_url = self.base_url + "/login" + def _decode_certificate(self, certificate, is_encoded): + """ + Decode the provided certificate content. + + :param certificate: Certificate content (Base64-encoded or raw string). + :param is_encoded: Indicates whether the certificate is Base64-encoded. + :return: Decoded certificate content as bytes. + """ + if not certificate: + self.logger.error("No certificate provided.") + raise ValueError("Certificate is required.") + + return base64.b64decode(certificate) if is_encoded else certificate.encode() + + async def _ensure_logged_in(self): + """ + Ensure the session ID is available for authenticated requests. + """ + if not self.session_id: + self.session_id = await self.login() + if self.session_id: + self.headers = { + 'Accept': 'application/json', + 'Cookie': f'3x-ui={self.session_id}', + 'Content-Type': 'application/json' + } + else: + raise ValueError("Unable to log in and retrieve session ID.") + + async def login(self): + """ + Perform login to the panel. + + :return: Session ID or None. + """ + login_url = f"{self.base_url}/login" self.logger.info(f"Attempting to login at: {login_url}") - try: - response = requests.post(login_url, data=self.login_data, verify=False, timeout=10) - response.raise_for_status() - session_id = response.cookies.get("3x-ui") - if session_id: - return session_id - else: - self.logger.error(f"Login failed: {response.status_code}") - self.logger.debug(f"Response content: {response.text}") - return None - except requests.RequestException as e: - self.logger.error(f"Login request failed: {e}") - return None - def getInboundInfo(self, inboundId): - url = f"{self.base_url}/panel/api/inbounds/get/{inboundId}" - try: - response = requests.get(url, headers=self.headers, verify=False, timeout=10) - response.raise_for_status() - if response: - return response.json() - else: - self.logger.error(f"Failed to get inbound info: {response.status_code}") - self.logger.debug("Response:", response.text) + async with aiohttp.ClientSession() as session: + try: + async with session.post( + login_url, data=self.login_data, ssl=self.cert_content, timeout=10 + ) as response: + if response.status == 200: + session_id = response.cookies.get("3x-ui") + if session_id: + return session_id.value + else: + self.logger.error("Login failed: No session ID received.") + return None + else: + self.logger.error(f"Login failed: {response.status}") + return None + except aiohttp.ClientError as e: + self.logger.error(f"Login request failed: {e}") return None - except requests.RequestException as e: - self.logger.error(f"Get inbound request failed: {e}") - - def get_client_traffic(self, email): + + async def get_inbound_info(self, inbound_id): + """ + Fetch inbound information by ID. + + :param inbound_id: ID of the inbound. + :return: JSON response or None. + """ + await self._ensure_logged_in() + url = f"{self.base_url}/panel/api/inbounds/get/{inbound_id}" + async with aiohttp.ClientSession() as session: + try: + async with session.get( + url, headers=self.headers, ssl=self.cert_content, timeout=10 + ) as response: + if response.status == 200: + return await response.json() + else: + self.logger.error(f"Failed to get inbound info: {response.status}") + return None + except aiohttp.ClientError as e: + self.logger.error(f"Get inbound info request failed: {e}") + return None + + async def get_client_traffic(self, email): + """ + Fetch traffic information for a specific client. + + :param email: Client's email. + :return: JSON response or None. + """ + await self._ensure_logged_in() url = f"{self.base_url}/panel/api/inbounds/getClientTraffics/{email}" - try: - response = requests.get(url, headers=self.headers, verify=False, timeout=10) - response.raise_for_status() - if response: - return response.json() - else: - self.logger.error(f"Failed to get client traffic: {response.status_code}") - self.logger.debug("Response:", response.text) + async with aiohttp.ClientSession() as session: + try: + async with session.get( + url, headers=self.headers, ssl=self.cert_content, timeout=10 + ) as response: + if response.status == 200: + return await response.json() + else: + self.logger.error(f"Failed to get client traffic: {response.status}") + return None + except aiohttp.ClientError as e: + self.logger.error(f"Get client traffic request failed: {e}") return None - except requests.RequestException as e: - self.loggin.error(f"Get client request failed: {e}") - - def update_client_expiry(self, client_uuid, new_expiry_time, client_email): + + async def update_client_expiry(self, client_uuid, new_expiry_time, client_email): + """ + Update the expiry date of a specific client. + + :param client_uuid: UUID of the client. + :param new_expiry_time: New expiry date in ISO format. + :param client_email: Client's email. + :return: None. + """ + await self._ensure_logged_in() url = f"{self.base_url}/panel/api/inbounds/updateClient" update_data = { - "id": 1, - "settings": json.dumps({ + "id": 1, + "settings": { "clients": [ { "id": client_uuid, "alterId": 0, - "email": client_email, + "email": client_email, "limitIp": 2, - "totalGB": 0, + "totalGB": 0, "expiryTime": new_expiry_time, "enable": True, "tgId": "", "subId": "" } ] - }) + } } - try: - response = requests.post(url, headers=self.headers, json=update_data, verify=False) - response.raise_for_status() - if response: - self.logger.debug("Client expiry time updated successfully.") - else: - self.logger.error(f"Failed to update client: {response.status_code}, {response.text}") - except requests.RequestException as e: - self.logger.error(f"Update client request failed: {e}") - def add_client(self, inbound_id, expiry_date,email): + async with aiohttp.ClientSession() as session: + try: + async with session.post( + url, headers=self.headers, json=update_data, ssl=self.cert_content + ) as response: + if response.status == 200: + self.logger.info("Client expiry updated successfully.") + else: + self.logger.error(f"Failed to update client expiry: {response.status}") + except aiohttp.ClientError as e: + self.logger.error(f"Update client expiry request failed: {e}") + + async def add_client(self, inbound_id, expiry_date, email): + """ + Add a new client to an inbound. + + :param inbound_id: ID of the inbound. + :param expiry_date: Expiry date in ISO format. + :param email: Client's email. + :return: JSON response or None. + """ + await self._ensure_logged_in() url = f"{self.base_url}/panel/api/inbounds/addClient" client_info = { "clients": [ @@ -111,7 +191,7 @@ class PanelInteraction: "email": email, "limitIp": 2, "totalGB": 0, - "flow":"xtls-rprx-vision", + "flow": "xtls-rprx-vision", "expiryTime": expiry_date, "enable": True, "tgId": "", @@ -121,15 +201,19 @@ class PanelInteraction: } payload = { "id": inbound_id, - "settings": json.dumps(client_info) + "settings": client_info } - try: - response = requests.post(url, headers=self.headers, json=payload, verify=False) - if response.status_code == 200: - return response.json() - else: - self.logger.error(f"Failed to add client: {response.status_code}") - self.logger.debug("Response:", response.text) + + async with aiohttp.ClientSession() as session: + try: + async with session.post( + url, headers=self.headers, json=payload, ssl=self.cert_content + ) as response: + if response.status == 200: + return await response.status + else: + self.logger.error(f"Failed to add client: {response.status}") + return None + except aiohttp.ClientError as e: + self.logger.error(f"Add client request failed: {e}") return None - finally: - self.logger.info("Finished attempting to add client.")