Issue invite configs as subscription URLs with redeem-based lifetime.

Admin picks inbound once, sets duration in days from Get config, and configures the 3x-ui /sub base URL.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
orohi
2026-07-26 01:34:40 +03:00
co-authored by Cursor
parent 7b3d8aac34
commit 7d7094bc38
13 changed files with 347 additions and 148 deletions
+46 -4
View File
@@ -32,9 +32,21 @@ def _settings_creds(settings: dict) -> dict:
'username': (sync.get('xui_username') or '').strip(),
'password': sync.get('xui_password') or '',
'inbound_id': sync.get('xui_inbound_id'),
'sub_url': (sync.get('xui_sub_url') or '').strip().rstrip('/'),
}
def build_subscription_url(settings: dict, sub_id: str) -> str:
"""Build public subscription URL from panel settings + client subId."""
sub_id = (sub_id or '').strip()
if not sub_id:
return ''
base = _settings_creds(settings).get('sub_url') or ''
if not base:
return ''
return f"{base}/{sub_id}"
class XuiApiError(RuntimeError):
pass
@@ -211,6 +223,7 @@ class XuiApi:
'email': email,
'config': vless or '',
'links': links,
'expiry_time': expiry_time,
}
async def get_client_links(self, email: str) -> list:
@@ -292,8 +305,14 @@ class XuiApi:
raise XuiApiError(f'Failed to toggle 3x-ui client {email}')
async def xui_create_vless_config(settings: dict, *, name: str, inbound_id: Optional[int] = None) -> dict:
"""Create a VLESS client on 3x-ui and return {client_id, config, links}."""
async def xui_create_vless_config(
settings: dict,
*,
name: str,
inbound_id: Optional[int] = None,
expiry_time: int = 0,
) -> dict:
"""Create a VLESS client on 3x-ui and return {client_id, config, subscription_url, links}."""
creds = _settings_creds(settings)
inbound = inbound_id if inbound_id is not None else creds.get('inbound_id')
try:
@@ -312,16 +331,39 @@ async def xui_create_vless_config(settings: dict, *, name: str, inbound_id: Opti
base = ''.join(ch if ch.isalnum() or ch in '._-+@' else '_' for ch in (name or 'user').strip())
base = base[:48] or f'user_{secrets.token_hex(4)}'
email = base
created = None
# Avoid collisions
for _ in range(5):
try:
return await api.add_vless_client(email=email, inbound_id=inbound, comment=name or email)
created = await api.add_vless_client(
email=email,
inbound_id=inbound,
comment=name or email,
expiry_time=int(expiry_time or 0),
)
break
except XuiApiError as e:
if 'exist' in str(e).lower() or 'duplicate' in str(e).lower() or 'already' in str(e).lower():
email = f'{base}_{secrets.token_hex(3)}'
continue
raise
return await api.add_vless_client(email=email, inbound_id=inbound, comment=name or email)
if created is None:
created = await api.add_vless_client(
email=email,
inbound_id=inbound,
comment=name or email,
expiry_time=int(expiry_time or 0),
)
sub_url = build_subscription_url(settings, created.get('sub_id') or '')
# Prefer subscription URL as the main share string when configured
if sub_url:
created['subscription_url'] = sub_url
created['config'] = sub_url
else:
created['subscription_url'] = ''
created['inbound_id'] = inbound
return created
async def xui_get_config(settings: dict, email: str) -> str: