3x-ui: add expiry days and start-after-first-use for clients
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+4
-4
@@ -264,7 +264,6 @@ class XuiClient:
|
||||
raise XuiApiError(f"Inbound #{inbound_id} — не VLESS (protocol={proto})")
|
||||
|
||||
uuid = await self.get_new_uuid()
|
||||
# Auto vision flow for Reality TCP if caller left flow empty
|
||||
if not flow:
|
||||
try:
|
||||
stream = inbound.get("streamSettings") or {}
|
||||
@@ -285,7 +284,7 @@ class XuiClient:
|
||||
"enable": True,
|
||||
"flow": flow,
|
||||
"totalGB": total_gb,
|
||||
"expiryTime": expiry_time,
|
||||
"expiryTime": int(expiry_time),
|
||||
"limitIp": limit_ip,
|
||||
"tgId": 0,
|
||||
"subId": _random_sub_id(),
|
||||
@@ -300,6 +299,7 @@ class XuiClient:
|
||||
"inbound_id": inbound_id,
|
||||
"uuid": uuid,
|
||||
"flow": flow,
|
||||
"expiryTime": int(expiry_time),
|
||||
"details": details,
|
||||
"links": links,
|
||||
}
|
||||
@@ -329,7 +329,7 @@ class XuiClient:
|
||||
"allowedIPs": ["0.0.0.0/0", "::/0"],
|
||||
"keepAlive": 0,
|
||||
"totalGB": total_gb,
|
||||
"expiryTime": expiry_time,
|
||||
"expiryTime": int(expiry_time),
|
||||
"limitIp": limit_ip,
|
||||
"tgId": 0,
|
||||
"subId": _random_sub_id(),
|
||||
@@ -337,13 +337,13 @@ class XuiClient:
|
||||
}
|
||||
await self.add_client_raw(client, [inbound_id])
|
||||
details = await self.get_client(email)
|
||||
# WG has no vless:// link — expose keys for conf building
|
||||
return {
|
||||
"protocol": "wireguard",
|
||||
"email": email,
|
||||
"inbound_id": inbound_id,
|
||||
"privateKey": keys["privateKey"],
|
||||
"publicKey": keys["publicKey"],
|
||||
"expiryTime": int(expiry_time),
|
||||
"details": details,
|
||||
"inbound": {
|
||||
"id": inbound.get("id"),
|
||||
|
||||
@@ -140,6 +140,24 @@ async def load_available_inbounds(
|
||||
return await client.list_available_inbounds(protocols=protocols, enabled_only=True)
|
||||
|
||||
|
||||
def compute_expiry_time(*, days: int, start_after_first_use: bool) -> int:
|
||||
"""
|
||||
3x-ui expiryTime encoding:
|
||||
0 — unlimited
|
||||
>0 — absolute deadline in unix ms
|
||||
<0 — duration ms; countdown starts after first use
|
||||
"""
|
||||
days = int(days or 0)
|
||||
if days <= 0:
|
||||
return 0
|
||||
duration_ms = days * 24 * 60 * 60 * 1000
|
||||
if start_after_first_use:
|
||||
return -duration_ms
|
||||
import time
|
||||
|
||||
return int(time.time() * 1000) + duration_ms
|
||||
|
||||
|
||||
async def add_xui_client(
|
||||
panel: XuiPanel,
|
||||
*,
|
||||
@@ -148,27 +166,36 @@ async def add_xui_client(
|
||||
inbound_id: int,
|
||||
total_gb: int = 0,
|
||||
limit_ip: int = 0,
|
||||
expiry_days: int = 0,
|
||||
start_after_first_use: bool = False,
|
||||
flow: str = "",
|
||||
comment: str = "",
|
||||
) -> dict:
|
||||
proto = protocol.strip().lower()
|
||||
bytes_limit = total_gb * 1024 * 1024 * 1024 if total_gb > 0 else 0
|
||||
expiry_time = compute_expiry_time(days=expiry_days, start_after_first_use=start_after_first_use)
|
||||
async with _client_for(panel) as client:
|
||||
if proto == "vless":
|
||||
return await client.add_vless_client(
|
||||
result = await client.add_vless_client(
|
||||
email=email,
|
||||
inbound_id=inbound_id,
|
||||
total_gb=bytes_limit,
|
||||
expiry_time=expiry_time,
|
||||
limit_ip=limit_ip,
|
||||
flow=flow,
|
||||
comment=comment,
|
||||
)
|
||||
if proto == "wireguard":
|
||||
return await client.add_wireguard_client(
|
||||
elif proto == "wireguard":
|
||||
result = await client.add_wireguard_client(
|
||||
email=email,
|
||||
inbound_id=inbound_id,
|
||||
total_gb=bytes_limit,
|
||||
expiry_time=expiry_time,
|
||||
limit_ip=limit_ip,
|
||||
comment=comment,
|
||||
)
|
||||
raise ValueError("Поддерживаются только vless и wireguard")
|
||||
else:
|
||||
raise ValueError("Поддерживаются только vless и wireguard")
|
||||
result["expiry_days"] = expiry_days
|
||||
result["start_after_first_use"] = start_after_first_use
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user