feat: /create command now check profile existed in vless and ss inbounds and if yes just send sub link
246 lines
10 KiB
Python
246 lines
10 KiB
Python
import json
|
||
import uuid
|
||
from typing import Tuple, Optional, Dict, Any
|
||
import aiohttp
|
||
|
||
from bot.config import (
|
||
XUI_FULL_ADDRESS,
|
||
INBOUND_VLESS_ID,
|
||
INBOUND_SS_ID,
|
||
SUB_BASE_URL
|
||
)
|
||
from bot.services.xui import XUIService
|
||
from bot.utils.logging import logger
|
||
|
||
|
||
class ClientService:
|
||
"""Сервис для работы с клиентами VPN."""
|
||
|
||
def __init__(self, xui_service: XUIService):
|
||
self.xui_service = xui_service
|
||
|
||
def generate_client_credentials(self, telegram_id: str) -> Tuple[str, str, str, str]:
|
||
"""Генерация учетных данных для клиента."""
|
||
vless_email = f"{telegram_id}_vl_ssl"
|
||
ss_email = f"{telegram_id}_ss"
|
||
vless_uuid = str(uuid.uuid4())
|
||
ss_password = str(uuid.uuid4())
|
||
|
||
return vless_email, ss_email, vless_uuid, ss_password
|
||
|
||
def get_subscription_link(self, telegram_id: str) -> str:
|
||
"""Получение ссылки на подписку."""
|
||
return f"{SUB_BASE_URL}{telegram_id}?name={telegram_id}"
|
||
|
||
async def create_shadowsocks_client(self, telegram_id: str, password: str) -> bool:
|
||
"""Создание Shadowsocks клиента через API."""
|
||
try:
|
||
self.xui_service.login()
|
||
except Exception as e:
|
||
logger.error(f"Ошибка авторизации в XUI: {e}")
|
||
return False
|
||
|
||
ss_email = f"{telegram_id}_ss"
|
||
|
||
settings_str = json.dumps({
|
||
"clients": [{
|
||
"method": "chacha20-ietf-poly1305",
|
||
"password": password,
|
||
"email": ss_email,
|
||
"totalGB": 0,
|
||
"expiryTime": 0,
|
||
"enable": True,
|
||
"tgId": telegram_id,
|
||
"subId": telegram_id,
|
||
"reset": 0
|
||
}]
|
||
}, separators=(',', ':'))
|
||
|
||
payload = {
|
||
"id": INBOUND_SS_ID,
|
||
"settings": settings_str
|
||
}
|
||
|
||
api_url = f"{XUI_FULL_ADDRESS}/xui/API/inbounds/addClient"
|
||
cookies = self.xui_service.get_cookies()
|
||
|
||
async with aiohttp.ClientSession() as session:
|
||
async def do_request():
|
||
async with session.post(
|
||
api_url,
|
||
json=payload,
|
||
cookies=cookies,
|
||
headers={
|
||
"Content-Type": "application/json",
|
||
"User-Agent": "Mozilla/5.0"
|
||
},
|
||
ssl=True
|
||
) as resp:
|
||
text = await resp.text()
|
||
logger.debug(f"Response status: {resp.status}")
|
||
logger.debug(f"Response text: {text}")
|
||
return resp.status, text
|
||
|
||
status, text = await do_request()
|
||
|
||
# Если сессия истекла, пробуем повторно залогиниться
|
||
if status == 401 or (status == 200 and "login" in text.lower()):
|
||
logger.info("Сессия XUI истекла, повторный вход...")
|
||
try:
|
||
self.xui_service.reset_session()
|
||
self.xui_service.login()
|
||
cookies = self.xui_service.get_cookies()
|
||
except Exception as e:
|
||
logger.error(f"Ошибка повторной авторизации в XUI: {e}")
|
||
return False
|
||
status, text = await do_request()
|
||
|
||
if status == 200:
|
||
if "successfully" in text.lower() or "success" in text.lower():
|
||
logger.info(f"Shadowsocks клиент успешно создан: {ss_email}")
|
||
return True
|
||
elif not text.strip():
|
||
logger.warning(f"Пустой ответ при статусе 200. Проверяем наличие клиента: {ss_email}")
|
||
return await self.verify_client_created(telegram_id, ss_email)
|
||
else:
|
||
logger.error(f"Неожиданный ответ при создании SS клиента: {text}")
|
||
return False
|
||
else:
|
||
logger.error(f"Ошибка создания Shadowsocks клиента: {status} {text}")
|
||
return False
|
||
|
||
async def verify_client_created(self, telegram_id: str, ss_email: str) -> bool:
|
||
"""Проверка, что клиент существует в inbound SS."""
|
||
try:
|
||
inbound_info = self.xui_service.get_inbound(INBOUND_SS_ID)
|
||
if not inbound_info:
|
||
logger.error(f"Inbound {INBOUND_SS_ID} не найден.")
|
||
return False
|
||
|
||
settings = json.loads(inbound_info.get('settings', '{}'))
|
||
clients = settings.get('clients', [])
|
||
|
||
for client in clients:
|
||
if client.get('email') == ss_email:
|
||
logger.info(f"Клиент {ss_email} найден.")
|
||
return True
|
||
|
||
logger.warning(f"Клиент {ss_email} не найден.")
|
||
return False
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при проверке клиента: {e}")
|
||
return False
|
||
|
||
async def create_client_profile(self, telegram_id: str) -> Tuple[bool, str]:
|
||
"""Создание полного профиля клиента (VLESS + Shadowsocks) с проверкой существующего."""
|
||
try:
|
||
# Сначала проверяем, есть ли уже клиенты
|
||
vless_client, ss_client = self.get_client_info(telegram_id)
|
||
if vless_client or ss_client:
|
||
subscription_link = self.get_subscription_link(telegram_id)
|
||
logger.info(f"Клиент для {telegram_id} уже существует.")
|
||
return True, (
|
||
f"ℹ Клиент для <b>{telegram_id}</b> уже существует.\n"
|
||
f"🔗 Подписочная ссылка:\n<code>{subscription_link}</code>"
|
||
)
|
||
|
||
# Генерируем новые креды
|
||
vless_email, ss_email, vless_uuid, ss_password = self.generate_client_credentials(telegram_id)
|
||
logger.info(f"Создаём клиентов для telegram_id={telegram_id}")
|
||
|
||
# Создаём VLESS клиента
|
||
vless_success = self.xui_service.add_vless_client(
|
||
inbound_id=INBOUND_VLESS_ID,
|
||
email=vless_email,
|
||
uuid=vless_uuid,
|
||
telegram_id=telegram_id
|
||
)
|
||
|
||
if not vless_success:
|
||
return False, "Ошибка при создании VLESS клиента"
|
||
|
||
# Создаём Shadowsocks клиента
|
||
ss_success = await self.create_shadowsocks_client(telegram_id, ss_password)
|
||
if not ss_success:
|
||
return False, "Ошибка при создании Shadowsocks клиента"
|
||
|
||
# Подписочная ссылка
|
||
subscription_link = self.get_subscription_link(telegram_id)
|
||
success_message = (
|
||
f"✅ Профиль для <b>{telegram_id}</b> успешно создан!\n"
|
||
f"🔗 Подписочная ссылка:\n<code>{subscription_link}</code>"
|
||
)
|
||
|
||
return True, success_message
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при создании профиля: {e}")
|
||
return False, "Произошла ошибка при создании профиля"
|
||
|
||
|
||
# async def create_client_profile(self, telegram_id: str) -> Tuple[bool, str]:
|
||
# """Создание полного профиля клиента (VLESS + Shadowsocks)."""
|
||
# try:
|
||
# vless_email, ss_email, vless_uuid, ss_password = self.generate_client_credentials(telegram_id)
|
||
#
|
||
# logger.info(f"Создаём клиентов для telegram_id={telegram_id}")
|
||
#
|
||
# # Создаём VLESS клиента
|
||
# vless_success = self.xui_service.add_vless_client(
|
||
# inbound_id=INBOUND_VLESS_ID,
|
||
# email=vless_email,
|
||
# uuid=vless_uuid,
|
||
# telegram_id=telegram_id
|
||
# )
|
||
#
|
||
# if not vless_success:
|
||
# return False, "Ошибка при создании VLESS клиента"
|
||
#
|
||
# # Создаём Shadowsocks клиента
|
||
# ss_success = await self.create_shadowsocks_client(telegram_id, ss_password)
|
||
#
|
||
# if not ss_success:
|
||
# return False, "Ошибка при создании Shadowsocks клиента"
|
||
#
|
||
# subscription_link = self.get_subscription_link(telegram_id)
|
||
# success_message = (
|
||
# f"✅ Профиль для {telegram_id} успешно создан!\n"
|
||
# f"🔗 Подписочная ссылка:\n<code>{subscription_link}</code>"
|
||
# )
|
||
#
|
||
# return True, success_message
|
||
#
|
||
# except Exception as e:
|
||
# logger.error(f"Ошибка при создании профиля: {e}")
|
||
# return False, "Произошла ошибка при создании профиля"
|
||
|
||
def get_client_info(self, telegram_id: str) -> Tuple[Optional[Dict[str, Any]], Optional[Dict[str, Any]]]:
|
||
"""Получение информации о клиентах."""
|
||
vless_email = f"{telegram_id}_vl_ssl"
|
||
ss_email = f"{telegram_id}_ss"
|
||
|
||
vless_client = self.xui_service.get_client(INBOUND_VLESS_ID, vless_email)
|
||
ss_client = self.xui_service.get_client(INBOUND_SS_ID, ss_email)
|
||
|
||
return vless_client, ss_client
|
||
|
||
def get_client_info_with_stats(self, telegram_id: str):
|
||
"""Возвращает клиентов и их статистику по Telegram ID."""
|
||
vless_client, ss_client = self.get_client_info(telegram_id)
|
||
|
||
vless_stats = None
|
||
ss_stats = None
|
||
|
||
if vless_client:
|
||
vless_stats = self.xui_service.get_client_stats(
|
||
inbound_id=INBOUND_VLESS_ID,
|
||
email=vless_client["email"]
|
||
)
|
||
|
||
if ss_client:
|
||
ss_stats = self.xui_service.get_client_stats(
|
||
inbound_id=INBOUND_SS_ID,
|
||
email=ss_client["email"]
|
||
)
|
||
|
||
return (vless_client, vless_stats), (ss_client, ss_stats) |