import asyncio from datetime import datetime from sqlalchemy import update from sqlalchemy.ext.asyncio import AsyncSession from config import PUBLIC_LINK, REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD, SUPERNODE from database import get_servers, get_tariff_by_id, store_key from database.models import User from handlers.utils import check_server_key_limit from logger import logger from panels._3xui import ( ClientConfig, add_client, get_xui_instance, ) from panels.remnawave import RemnawaveAPI async def create_key_on_cluster( cluster_id: str, tg_id: int, client_id: str, email: str, expiry_timestamp: int, plan: int = None, session: AsyncSession = None, remnawave_link: str = None, hwid_limit: int = None, traffic_limit_bytes: int = None, is_trial: bool = False, ): try: servers = await get_servers(session) cluster = servers.get(cluster_id) server_id_to_store = cluster_id if not cluster: found_servers = [] for _key, server_list in servers.items(): for server_info in server_list: if server_info.get("server_name", "").lower() == cluster_id.lower(): found_servers.append(server_info) if found_servers: cluster = found_servers server_id_to_store = found_servers[0].get("server_name") else: raise ValueError(f"Кластер или сервер с ID/именем {cluster_id} не найден.") enabled_servers = [s for s in cluster if s.get("enabled", True)] if not enabled_servers: logger.warning(f"[Key Creation] Нет доступных серверов в кластере {cluster_id}") return if plan is not None and traffic_limit_bytes is None: tariff = await get_tariff_by_id(session, plan) if not tariff: raise ValueError(f"Тариф с id={plan} не найден.") traffic_limit_bytes = int(tariff["traffic_limit"]) if tariff["traffic_limit"] else None if hwid_limit is None and tariff.get("device_limit") is not None: hwid_limit = int(tariff["device_limit"]) remnawave_servers = [ s for s in enabled_servers if s.get("panel_type", "3x-ui").lower() == "remnawave" and await check_server_key_limit(s, session) ] xui_servers = [ s for s in enabled_servers if s.get("panel_type", "3x-ui").lower() == "3x-ui" and await check_server_key_limit(s, session) ] if not remnawave_servers and not xui_servers: logger.warning(f"[Key Creation] Нет серверов с доступным лимитом в кластере {cluster_id}") return semaphore = asyncio.Semaphore(2) remnawave_created = False remnawave_key = None remnawave_client_id = None if remnawave_servers: remna = RemnawaveAPI(remnawave_servers[0]["api_url"]) logged_in = await remna.login(REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD) if not logged_in: logger.error("Не удалось войти в Remnawave API") else: expire_at = datetime.utcfromtimestamp(expiry_timestamp / 1000).isoformat() + "Z" inbound_ids = [s.get("inbound_id") for s in remnawave_servers if s.get("inbound_id")] if not inbound_ids: logger.warning("Нет inbound_id у серверов Remnawave") else: short_uuid = None if remnawave_link and "/" in remnawave_link: short_uuid = remnawave_link.rstrip("/").split("/")[-1] user_data = { "username": email, "trafficLimitStrategy": "NO_RESET", "expireAt": expire_at, "telegramId": tg_id, "activeInternalSquads": inbound_ids, } if traffic_limit_bytes and traffic_limit_bytes > 0: user_data["trafficLimitBytes"] = traffic_limit_bytes * 1024 * 1024 * 1024 if short_uuid: user_data["shortUuid"] = short_uuid if hwid_limit is not None: user_data["hwidDeviceLimit"] = hwid_limit logger.info(f"[Key Creation] Данные для создания клиента в Remnawave: {user_data}") result = await remna.create_user(user_data) if not result: logger.error("Ошибка при создании пользователя в Remnawave") else: remnawave_created = True remnawave_key = result.get("subscriptionUrl") remnawave_client_id = result.get("uuid") logger.info(f"[Key Creation] Пользователь создан в Remnawave: {result}") public_link = f"{PUBLIC_LINK}{email}/{tg_id}" if xui_servers else None final_client_id = remnawave_client_id or client_id logger.info(f"[Debug] 3x-ui servers для кластера {cluster_id}: {[s['server_name'] for s in xui_servers]}") if xui_servers: if SUPERNODE: for server_info in xui_servers: await create_client_on_server( server_info, tg_id, final_client_id, email, expiry_timestamp, semaphore, plan=plan, session=session, is_trial=is_trial, ) else: await asyncio.gather( *[ create_client_on_server( server, tg_id, final_client_id, email, expiry_timestamp, semaphore, plan=plan, session=session, is_trial=is_trial, ) for server in xui_servers ], return_exceptions=True, ) if (remnawave_created and remnawave_client_id) or xui_servers: await store_key( session=session, tg_id=tg_id, client_id=final_client_id, email=email, expiry_time=expiry_timestamp, key=public_link, server_id=server_id_to_store, remnawave_link=remnawave_key, tariff_id=plan, ) await session.execute(update(User).where(User.tg_id == tg_id, User.trial.in_([0, -1])).values(trial=1)) await session.commit() except Exception as e: logger.error(f"Ошибка при создании ключа: {e}") raise e async def create_client_on_server( server_info: dict, tg_id: int, client_id: str, email: str, expiry_timestamp: int, semaphore: asyncio.Semaphore, plan: int = None, session=None, is_trial: bool = False, ): """ Создает клиента на указанном 3x-ui сервере с лимитом по тарифу или триалу. """ logger.info( f"[Client] Вход в create_client_on_server: сервер={server_info.get('server_name')}, план={plan}, is_trial={is_trial}" ) async with semaphore: xui = await get_xui_instance(server_info["api_url"]) inbound_id = server_info.get("inbound_id") server_name = server_info.get("server_name", "unknown") if not inbound_id: logger.warning(f"[Client] INBOUND_ID отсутствует для сервера {server_name}. Пропуск.") return if SUPERNODE: unique_email = f"{email}_{server_name.lower()}" sub_id = email else: unique_email = email sub_id = unique_email total_gb_value = 0 device_limit_value = 0 if plan is not None: tariff = await get_tariff_by_id(session, plan) logger.info(f"[Tariff Debug] Получен тариф: {tariff}") if not tariff: raise ValueError(f"Тариф с id={plan} не найден.") total_gb_value = int(tariff["traffic_limit"]) if tariff["traffic_limit"] else 0 device_limit_value = int(tariff["device_limit"]) if tariff.get("device_limit") is not None else 0 try: logger.info( f"[Client] Вызов add_client: email={email}, client_id={client_id}, GB={total_gb_value}, Devices={device_limit_value}" ) traffic_limit_bytes = total_gb_value * 1024 * 1024 * 1024 await add_client( xui, ClientConfig( client_id=client_id, email=unique_email, tg_id=tg_id, limit_ip=device_limit_value, total_gb=traffic_limit_bytes, expiry_time=expiry_timestamp, enable=True, flow="xtls-rprx-vision", inbound_id=int(inbound_id), sub_id=sub_id, ), ) logger.info(f"[Client] Клиент успешно добавлен на сервер {server_name}") except Exception as e: logger.error(f"[Client Error] Не удалось создать клиента на {server_name}: {e}") if SUPERNODE: await asyncio.sleep(0.7)