Compare commits
2 Commits
ebf1167fea
...
54e6f938e7
| Author | SHA1 | Date | |
|---|---|---|---|
| 54e6f938e7 | |||
| c2f7d2a88e |
@ -40,7 +40,7 @@ class ClientHandlers:
|
|||||||
await message.answer(f"❌ Произошла ошибка при создании профиля. Попробуйте позже.\n {e}")
|
await message.answer(f"❌ Произошла ошибка при создании профиля. Попробуйте позже.\n {e}")
|
||||||
|
|
||||||
async def cmd_info(self, message: types.Message):
|
async def cmd_info(self, message: types.Message):
|
||||||
"""Обработчик команды /info."""
|
"""Обработчик команды /info с выводом конфига и статистики."""
|
||||||
args = (message.text or "").strip().split(maxsplit=1)
|
args = (message.text or "").strip().split(maxsplit=1)
|
||||||
if len(args) < 2:
|
if len(args) < 2:
|
||||||
await message.answer(
|
await message.answer(
|
||||||
@ -52,23 +52,50 @@ class ClientHandlers:
|
|||||||
telegram_id = args[1].lstrip("@").strip()
|
telegram_id = args[1].lstrip("@").strip()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
vless_client, ss_client = self.client_service.get_client_info(telegram_id)
|
(vless_client, vless_stats), (ss_client, ss_stats) = \
|
||||||
|
self.client_service.get_client_info_with_stats(telegram_id)
|
||||||
|
|
||||||
def format_info(client, name):
|
def format_info(client, stats, name):
|
||||||
if not client:
|
if not client:
|
||||||
return f"❌ Клиент <b>{name}</b> не найден.\n"
|
return f"❌ Клиент <b>{name}</b> не найден.\n"
|
||||||
|
|
||||||
|
# Конфиг клиента
|
||||||
json_info = json.dumps(client, ensure_ascii=False, indent=2)
|
json_info = json.dumps(client, ensure_ascii=False, indent=2)
|
||||||
return f"🔹 <b>{name}</b>:\n<pre>{json_info}</pre>"
|
text = f"🔹 <b>{name}</b>:\n<pre>{json_info}</pre>"
|
||||||
|
|
||||||
|
# Статистика
|
||||||
|
if stats:
|
||||||
|
up = stats.get("up", 0)
|
||||||
|
down = stats.get("down", 0)
|
||||||
|
total = up + down
|
||||||
|
|
||||||
|
def human_size(num):
|
||||||
|
for unit in ["B", "KB", "MB", "GB", "TB"]:
|
||||||
|
if num < 1024:
|
||||||
|
return f"{num:.2f} {unit}"
|
||||||
|
num /= 1024
|
||||||
|
return f"{num:.2f} PB"
|
||||||
|
|
||||||
|
text += (
|
||||||
|
f"\n📊 Трафик: ↑ {human_size(up)} / ↓ {human_size(down)}"
|
||||||
|
f" (Σ {human_size(total)})"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
text += "\n📊 Статистика не найдена."
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
response = (
|
response = (
|
||||||
format_info(vless_client, "VLESS") +
|
format_info(vless_client, vless_stats, "VLESS")
|
||||||
"\n\n" +
|
+ "\n\n"
|
||||||
format_info(ss_client, "Shadowsocks")
|
+ format_info(ss_client, ss_stats, "Shadowsocks")
|
||||||
)
|
)
|
||||||
|
|
||||||
await message.answer(response, parse_mode=ParseMode.HTML)
|
await message.answer(response, parse_mode=ParseMode.HTML)
|
||||||
|
|
||||||
except BadLogin:
|
except BadLogin:
|
||||||
await message.answer("❌ Ошибка входа в панель XUI.")
|
await message.answer("❌ Ошибка входа в панель XUI.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка получения информации: {e}")
|
logger.error(f"Ошибка получения информации: {e}")
|
||||||
await message.answer("❌ Ошибка при получении информации. Проверь лог.")
|
await message.answer("❌ Ошибка при получении информации. Проверь лог.")
|
||||||
|
|
||||||
|
|||||||
@ -132,10 +132,20 @@ class ClientService:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def create_client_profile(self, telegram_id: str) -> Tuple[bool, str]:
|
async def create_client_profile(self, telegram_id: str) -> Tuple[bool, str]:
|
||||||
"""Создание полного профиля клиента (VLESS + Shadowsocks)."""
|
"""Создание полного профиля клиента (VLESS + Shadowsocks) с проверкой существующего."""
|
||||||
try:
|
try:
|
||||||
vless_email, ss_email, vless_uuid, ss_password = self.generate_client_credentials(telegram_id)
|
# Сначала проверяем, есть ли уже клиенты
|
||||||
|
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}")
|
logger.info(f"Создаём клиентов для telegram_id={telegram_id}")
|
||||||
|
|
||||||
# Создаём VLESS клиента
|
# Создаём VLESS клиента
|
||||||
@ -151,13 +161,13 @@ class ClientService:
|
|||||||
|
|
||||||
# Создаём Shadowsocks клиента
|
# Создаём Shadowsocks клиента
|
||||||
ss_success = await self.create_shadowsocks_client(telegram_id, ss_password)
|
ss_success = await self.create_shadowsocks_client(telegram_id, ss_password)
|
||||||
|
|
||||||
if not ss_success:
|
if not ss_success:
|
||||||
return False, "Ошибка при создании Shadowsocks клиента"
|
return False, "Ошибка при создании Shadowsocks клиента"
|
||||||
|
|
||||||
|
# Подписочная ссылка
|
||||||
subscription_link = self.get_subscription_link(telegram_id)
|
subscription_link = self.get_subscription_link(telegram_id)
|
||||||
success_message = (
|
success_message = (
|
||||||
f"✅ Профиль для {telegram_id} успешно создан!\n"
|
f"✅ Профиль для <b>{telegram_id}</b> успешно создан!\n"
|
||||||
f"🔗 Подписочная ссылка:\n<code>{subscription_link}</code>"
|
f"🔗 Подписочная ссылка:\n<code>{subscription_link}</code>"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -167,6 +177,43 @@ class ClientService:
|
|||||||
logger.error(f"Ошибка при создании профиля: {e}")
|
logger.error(f"Ошибка при создании профиля: {e}")
|
||||||
return False, "Произошла ошибка при создании профиля"
|
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]]]:
|
def get_client_info(self, telegram_id: str) -> Tuple[Optional[Dict[str, Any]], Optional[Dict[str, Any]]]:
|
||||||
"""Получение информации о клиентах."""
|
"""Получение информации о клиентах."""
|
||||||
vless_email = f"{telegram_id}_vl_ssl"
|
vless_email = f"{telegram_id}_vl_ssl"
|
||||||
@ -175,4 +222,25 @@ class ClientService:
|
|||||||
vless_client = self.xui_service.get_client(INBOUND_VLESS_ID, vless_email)
|
vless_client = self.xui_service.get_client(INBOUND_VLESS_ID, vless_email)
|
||||||
ss_client = self.xui_service.get_client(INBOUND_SS_ID, ss_email)
|
ss_client = self.xui_service.get_client(INBOUND_SS_ID, ss_email)
|
||||||
|
|
||||||
return vless_client, ss_client
|
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)
|
||||||
@ -40,14 +40,58 @@ class XUIService:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
def get_client(self, inbound_id: int, email: str) -> Optional[Dict[str, Any]]:
|
def get_client(self, inbound_id: int, email: str) -> Optional[Dict[str, Any]]:
|
||||||
"""Получение информации о клиенте."""
|
"""Получение информации о клиенте (работает и для SS, и для VLESS)."""
|
||||||
try:
|
try:
|
||||||
self.login()
|
self.login()
|
||||||
return self.xui.get_client(inbound_id=inbound_id, email=email)
|
inbounds = self.xui.get_inbounds()
|
||||||
|
if "obj" not in inbounds:
|
||||||
|
logger.error("Некорректный ответ от XUI при запросе inbounds")
|
||||||
|
return None
|
||||||
|
|
||||||
|
for inbound in inbounds["obj"]:
|
||||||
|
if inbound["id"] != inbound_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
settings = json.loads(inbound["settings"])
|
||||||
|
for client in settings.get("clients", []):
|
||||||
|
if client.get("email") == email:
|
||||||
|
# У SS-клиентов id нет — подставляем email
|
||||||
|
if "id" not in client:
|
||||||
|
client["id"] = client["email"]
|
||||||
|
return client
|
||||||
|
|
||||||
|
logger.warning(f"Клиент {email} не найден в inbound {inbound_id}")
|
||||||
|
return None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка получения клиента {email}: {e}")
|
logger.error(f"Ошибка получения клиента {email}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_client_stats(self, inbound_id: int, email: str) -> Optional[Dict[str, Any]]:
|
||||||
|
"""Получение статистики клиента по inbound и email."""
|
||||||
|
try:
|
||||||
|
self.login()
|
||||||
|
inbounds = self.xui.get_inbounds()
|
||||||
|
if "obj" not in inbounds:
|
||||||
|
logger.error("Некорректный ответ от XUI при запросе inbounds")
|
||||||
|
return None
|
||||||
|
|
||||||
|
for inbound in inbounds["obj"]:
|
||||||
|
if inbound["id"] != inbound_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for client_stat in inbound.get("clientStats", []):
|
||||||
|
if client_stat.get("email") == email:
|
||||||
|
return client_stat
|
||||||
|
|
||||||
|
logger.warning(f"Статистика по клиенту {email} не найдена в inbound {inbound_id}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка получения статистики клиента {email}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def add_vless_client(
|
def add_vless_client(
|
||||||
self,
|
self,
|
||||||
inbound_id: int,
|
inbound_id: int,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user