import asyncio from datetime import datetime from sqlalchemy.ext.asyncio import AsyncSession from config import PUBLIC_LINK, REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD, SUPERNODE from database import filter_cluster_by_subgroup, filter_cluster_by_tariff, get_servers, get_tariff_by_id, store_key from database.users import mark_trial_started_if_eligible from hooks.processors import process_extract_cryptolink_from_result from logger import ( CLOGGER as logger, PANEL_REMNA, PANEL_XUI, ) from .aggregated_links import make_aggregated_link 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, selected_device_limit: int = None, selected_traffic_limit_gb: int = None, current_device_limit: int = None, current_traffic_limit_gb: int = None, selected_price_rub: int = None, ): from panels._3xui import ClientConfig, add_client, get_xui_instance from panels.remnawave import RemnawaveAPI, get_vless_link_for_remnawave_by_username from services.clusters import ALLOWED_GROUP_CODES, check_server_key_limit 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 tariff = None subgroup_title = None need_vless_key = False traffic_limit_bytes_value = 0 device_limit_value = 0 external_squad_uuid = None if plan is not None: tariff = await get_tariff_by_id(session, plan) if not tariff: raise ValueError(f"Тариф с id={plan} не найден.") if traffic_limit_bytes is None: raw_traffic_limit = tariff.get("traffic_limit") if raw_traffic_limit: traffic_limit_bytes_value = int(raw_traffic_limit) * 1024 * 1024 * 1024 else: traffic_limit_bytes_value = 0 else: traffic_limit_bytes_value = int(traffic_limit_bytes) if hwid_limit is None: raw_device_limit = tariff.get("device_limit") device_limit_value = int(raw_device_limit) if raw_device_limit is not None else 0 else: device_limit_value = int(hwid_limit) subgroup_title = tariff.get("subgroup_title") need_vless_key = bool(tariff.get("vless")) external_squad_uuid = tariff.get("external_squad") or None else: traffic_limit_bytes_value = int(traffic_limit_bytes or 0) device_limit_value = int(hwid_limit or 0) if plan is not None: filtered = await filter_cluster_by_tariff(session, enabled_servers, plan, cluster_id) if filtered is not enabled_servers: enabled_servers = filtered elif subgroup_title: enabled_servers = await filter_cluster_by_subgroup( session, enabled_servers, subgroup_title, cluster_id, tariff_id=plan ) elif subgroup_title: enabled_servers = await filter_cluster_by_subgroup( session, enabled_servers, subgroup_title, cluster_id, tariff_id=plan ) if not enabled_servers: logger.warning(f"[Key Creation] Нет серверов после фильтрации по привязкам в кластере {cluster_id}") return special = None if is_trial: special = "trial" elif tariff: gc = (tariff.get("group_code") or "").lower() if gc in ALLOWED_GROUP_CODES: special = gc if special: bound_servers = [s for s in enabled_servers if special in (s.get("special_groups") or [])] if bound_servers: enabled_servers = bound_servers else: logger.info( f"[Key Creation] В кластере {cluster_id} нет серверов со спецгруппой '{special}'. " f"Использую весь кластер." ) 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 remnawave_link_value = 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(f"{PANEL_REMNA} Не удалось войти в 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 inbound_ids: 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, "uuid": client_id, } if traffic_limit_bytes_value and traffic_limit_bytes_value > 0: user_data["trafficLimitBytes"] = traffic_limit_bytes_value if short_uuid: user_data["shortUuid"] = short_uuid user_data["hwidDeviceLimit"] = device_limit_value if external_squad_uuid: user_data["externalSquadUuid"] = external_squad_uuid logger.debug(f"{PANEL_REMNA} Данные для создания клиента: {user_data}") result = await remna.create_user(user_data) if result: remnawave_created = True remnawave_client_id = result.get("uuid") remnawave_link_value = result.get("subscriptionUrl") remnawave_key = None if need_vless_key: try: remnawave_key = await get_vless_link_for_remnawave_by_username(remna, email, email) except Exception as e: logger.error(f"{PANEL_REMNA} Ошибка сборки VLESS: {e}") else: crypto_link = await process_extract_cryptolink_from_result( result=result, cluster_id=server_id_to_store, plan=plan, session=session, email=email, tg_id=tg_id, need_vless_key=need_vless_key, ) if crypto_link: remnawave_key = crypto_link logger.info(f"{PANEL_REMNA} Пользователь создан: {result}") else: logger.warning(f"{PANEL_REMNA} Нет inbound_id у серверов") final_client_id = remnawave_client_id or client_id logger.debug(f"{PANEL_XUI} 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, total_traffic_limit_bytes=traffic_limit_bytes_value, device_limit_value=device_limit_value, ) else: tasks = [ create_client_on_server( server, tg_id, final_client_id, email, expiry_timestamp, semaphore, plan=plan, session=session, is_trial=is_trial, total_traffic_limit_bytes=traffic_limit_bytes_value, device_limit_value=device_limit_value, ) for server in xui_servers ] await asyncio.gather(*tasks, return_exceptions=True) cluster_all = enabled_servers subgroup_code = subgroup_title if subgroup_title else None public_link = await make_aggregated_link( session=session, cluster_all=cluster_all, cluster_id=server_id_to_store, email=email, client_id=final_client_id, tg_id=tg_id, subgroup_code=subgroup_code, remna_link_override=remnawave_key or remnawave_link_value, plan=plan, ) if not public_link: public_link = f"{PUBLIC_LINK}{email}/{tg_id}" if (remnawave_created and remnawave_client_id) or xui_servers: await store_key( session=session, legacy_user_ref=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_link_value if remnawave_created else None, tariff_id=plan, selected_device_limit=selected_device_limit, selected_traffic_limit=selected_traffic_limit_gb, current_device_limit=current_device_limit, current_traffic_limit=current_traffic_limit_gb, selected_price_rub=selected_price_rub, ) await mark_trial_started_if_eligible(session, tg_id) try: from database.web_notifications import notify_web await notify_web( session, tg_id=tg_id, type="key_created", template_vars={"email": email}, data={"email": email, "client_id": client_id}, ) except Exception as e: logger.warning("[KeyCreate] Ошибка web-уведомления о создании ключа tg_id={}: {}", tg_id, e) 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 = None, session: AsyncSession | None = None, is_trial: bool = False, total_traffic_limit_bytes: int = 0, device_limit_value: int = 0, ): from panels._3xui import ClientConfig, add_client, get_xui_instance logger.debug( f"{PANEL_XUI} [Client] Вход в create_client_on_server: " f"сервер={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"{PANEL_XUI} [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 if plan is not None and (total_traffic_limit_bytes == 0 or device_limit_value == 0): tariff = await get_tariff_by_id(session, plan) logger.debug(f"{PANEL_XUI} [Tariff Debug] Получен тариф: {tariff}") if not tariff: raise ValueError(f"{PANEL_XUI} Тариф с id={plan} не найден.") if total_traffic_limit_bytes == 0: raw_limit = tariff.get("traffic_limit") base_gb = int(raw_limit) if raw_limit else 0 total_traffic_limit_bytes = base_gb * 1024 * 1024 * 1024 if device_limit_value == 0: raw_device_limit = tariff.get("device_limit") device_limit_value = int(raw_device_limit) if raw_device_limit is not None else 0 try: logger.debug( f"{PANEL_XUI} [Client] Вызов add_client: email={email}, client_id={client_id}, " f"bytes={total_traffic_limit_bytes}, Devices={device_limit_value}" ) traffic_limit_bytes = total_traffic_limit_bytes 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"{PANEL_XUI} [Client] Клиент успешно добавлен на сервер {server_name}") except Exception as e: logger.error(f"{PANEL_XUI} [Client Error] Не удалось создать клиента на {server_name}: {e}") if SUPERNODE: await asyncio.sleep(0.7)