From 89651a8e81a456b8b9f3b971840008c572bd305c Mon Sep 17 00:00:00 2001 From: Lev Date: Fri, 17 Apr 2026 23:07:47 +0300 Subject: [PATCH] fix + vershions configs --- API/models/config.py | 2 +- API/models/user.py | 2 +- API/routers/user.py | 5 +- API/utils/xui.py | 155 ++++++++++++++++++++++++------------------- docker-compose.yml | 1 + 5 files changed, 94 insertions(+), 71 deletions(-) diff --git a/API/models/config.py b/API/models/config.py index 4e81090..5439141 100644 --- a/API/models/config.py +++ b/API/models/config.py @@ -9,6 +9,6 @@ class Config(Base): id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False) name: Mapped[str] = mapped_column(String(64), nullable=False) - config: Mapped[str] = mapped_column(String(1024), nullable=False, unique=True) + config: Mapped[str] = mapped_column(String(1024), nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False) deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) diff --git a/API/models/user.py b/API/models/user.py index 76527aa..0686cd3 100644 --- a/API/models/user.py +++ b/API/models/user.py @@ -8,7 +8,7 @@ from .server import Server class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - email: Mapped[str] = mapped_column(String(256), index=True, nullable=False, unique=True) + email: Mapped[str] = mapped_column(String(256), index=True, nullable=False) pass_hash: Mapped[str] = mapped_column(String(512), nullable=False) server_id: Mapped[int] = mapped_column(ForeignKey("servers.id")) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False) diff --git a/API/routers/user.py b/API/routers/user.py index 5abf56f..0342bde 100644 --- a/API/routers/user.py +++ b/API/routers/user.py @@ -29,7 +29,10 @@ async def signup(body: UserCreate, db: AsyncSession = Depends(get_db)): detail="Promo-code is not valid" ) existing = await db.execute( - select(User).where(User.email == body.email) + select(User).where( + User.email == body.email, + User.deleted_at.is_(None) + ) ) if existing.scalar_one_or_none(): raise HTTPException( diff --git a/API/utils/xui.py b/API/utils/xui.py index c37ecb9..9badfd9 100644 --- a/API/utils/xui.py +++ b/API/utils/xui.py @@ -5,6 +5,84 @@ from urllib.parse import quote from httpx import AsyncClient from fastapi import HTTPException +class Configs: + def __init__(self, inbound_id: int, host: str, client_email: str, display_name: str): + self.inbound_id = inbound_id + self.host = host + self.client_email = client_email + self.display_name = display_name + + async def legacy_payload(self, data: dict,) -> dict: + inbounds = data.get("obj") + inbound = next((i for i in inbounds if i.get("id") == self.inbound_id), None) + if not inbound: + raise HTTPException( + status_code=404, + detail=f"Server not found" + ) + stream_settings = json.loads(inbound.get("streamSettings")) + reality = stream_settings.get("realitySettings") + short_ids = reality.get("shortIds") + suid = random.choice(short_ids) + client_uuid = str(uuid.uuid4()) + return { + "id": self.inbound_id, + "settings": json.dumps({ + "clients": [{ + "id": client_uuid, + "flow": "xtls-rprx-vision", + "email": self.client_email, + "limitIp": 0, + "totalGB": 0, + "expiryTime": 0, + "enable": True, + "tgId": "", + "subId": suid, + "comment": "", + "reset": 0 + }] + }) + } + + async def legacy_config(self, data: dict) -> str: + inbounds = data.get("obj") + inbound = next((i for i in inbounds if i.get("id") == self.inbound_id), None) + if not inbound: + raise HTTPException( + status_code=500, + detail="Server error" + ) + settings = json.loads(inbound.get("settings")) + clients = settings.get("clients") + current_client = next((i for i in clients if i.get("email") == self.client_email), None) + if not current_client: + raise HTTPException( + status_code=500, + detail="Server error" + ) + client_uuid = current_client.get("id") + sub_id = current_client.get("subId") + stream_settings = json.loads(inbound.get("streamSettings")) + reality = stream_settings.get("realitySettings") + server_names = reality.get("serverNames") + setting = reality.get("settings") + pbk = setting.get("publicKey") + fp = setting.get("fingerprint") + spx = quote(setting.get("spiderX"), safe='') + sni = random.choice(server_names) + port = inbound.get("port") + return ( + f"vless://{client_uuid}@{self.host}:{port}?" + f"security=reality&" + f"pbk={pbk}&" + f"fp={fp}&" + f"sni={sni}&" + f"sid={sub_id}&" + f"spx={spx}&" + f"flow=xtls-rprx-vision#" + f"{self.display_name}" + ) + class XUIClient: def __init__(self, host: str, base_url: str, username: str, password: str, inbound_id: int, version: str): self.host = host @@ -39,86 +117,27 @@ class XUIClient: ) async def add_client(self, client_email: str, display_name: str) -> str: + configs = Configs(self.inbound_id, self.host, client_email, display_name) await self._login() resp = await self.session.post(f"{self.base_url}/panel/inbound/list") resp.raise_for_status() - data = resp.json() - inbounds = data.get("obj") - inbound = next((i for i in inbounds if i.get("id") == self.inbound_id), None) - if not inbound: + if self.version == "legacy": + resp = await self.session.post(f"{self.base_url}/panel/inbound/addClient", json=configs.legacy_payload(resp.json())) + else: raise HTTPException( - status_code=404, - detail=f"Server not found" + status_code=500, + detail="Server error" ) - stream_settings = json.loads(inbound.get("streamSettings")) - reality = stream_settings.get("realitySettings") - short_ids = reality.get("shortIds") - server_names = reality.get("serverNames") - suid = random.choice(short_ids) - sni = random.choice(server_names) - pbk = reality.get("settings").get("publicKey") - client_uuid = str(uuid.uuid4()) - add_payload = { - "id": self.inbound_id, - "settings": json.dumps({ - "clients": [{ - "id": client_uuid, - "flow": "xtls-rprx-vision", - "email": client_email, - "limitIp": 0, - "totalGB": 0, - "expiryTime": 0, - "enable": True, - "tgId": "", - "subId": suid, - "comment": "", - "reset": 0 - }] - }) - } - resp = await self.session.post(f"{self.base_url}/panel/inbound/addClient", json=add_payload) resp.raise_for_status() resp = await self.session.post(f"{self.base_url}/panel/inbound/list") resp.raise_for_status() - data = resp.json() - inbounds = data.get("obj") - inbound = next((i for i in inbounds if i.get("id") == self.inbound_id), None) - if not inbound: + if self.version == "legacy": + return configs.legacy_config(resp.json()) + else: raise HTTPException( status_code=500, detail="Server error" ) - settings = json.loads(inbound.get("settings")) - clients = settings.get("clients") - current_client = next((i for i in clients if i.get("email") == client_email), None) - if not current_client: - raise HTTPException( - status_code=500, - detail="Server error" - ) - client_uuid = current_client.get("id") - sub_id = current_client.get("subId") - stream_settings = json.loads(inbound.get("streamSettings")) - reality = stream_settings.get("realitySettings") - server_names = reality.get("serverNames") - setting = reality.get("settings") - pbk = setting.get("publicKey") - fp = setting.get("fingerprint") - spx = quote(setting.get("spiderX"), safe='') - sni = random.choice(server_names) - port = inbound.get("port") - config_url = ( - f"vless://{client_uuid}@{self.host}:{port}?" - f"security=reality&" - f"pbk={pbk}&" - f"fp={fp}&" - f"sni={sni}&" - f"sid={sub_id}&" - f"spx={spx}&" - f"flow=xtls-rprx-vision#" - f"{display_name}" - ) - return config_url async def get_client_traffic(self, client_email: str) -> int: await self._login() diff --git a/docker-compose.yml b/docker-compose.yml index 07b627b..617d798 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,6 +34,7 @@ services: retries: 10 volumes: - db-data:/var/lib/postgresql + #TODO Удалить проброс портов ports: - 5432:5432