import asyncio from datetime import datetime, timezone from sqlalchemy.ext.asyncio import AsyncSession from config import PUBLIC_LINK, SUPERNODE from database import filter_cluster_by_subgroup, filter_cluster_by_tariff, get_servers, get_tariff_by_id, store_key from database.access.resolution import resolve_user_optional from database.keys import delete_key_by_user_and_email, get_key_by_user_and_email from database.models import Key from database.tariffs import get_active_tariff_by_id from logger import ( CLOGGER as logger, PANEL_REMNA, PANEL_XUI, ) from panels._3xui import ClientConfig, add_client, get_xui_instance from panels.remnawave_runtime import invalidate_remnawave_profile, with_remnawave_api from services.clusters import ALLOWED_GROUP_CODES, select_cluster from services.tariffs.tariff_display import GB, get_effective_limits_for_key from .aggregated_links import make_aggregated_link from .deletion import delete_key_from_cluster async def update_key_on_cluster( tg_id: int, client_id: str, email: str, expiry_time: int, cluster_id: str, session: AsyncSession, traffic_limit: int = None, device_limit: int = None, remnawave_link: str = None, subgroup_code: str | None = None, tariff_id: int | None = None, external_squad_uuid: str | None = None, ): try: servers = await get_servers(session) cluster = servers.get(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 else: raise ValueError(f"Кластер или сервер с ID/именем {cluster_id} не найден.") if tariff_id is not None: filtered = await filter_cluster_by_tariff(session, cluster, tariff_id, cluster_id) if filtered is not cluster: cluster = filtered elif subgroup_code: cluster = await filter_cluster_by_subgroup( session, cluster, subgroup_code, cluster_id, tariff_id=tariff_id ) elif subgroup_code: cluster = await filter_cluster_by_subgroup(session, cluster, subgroup_code, cluster_id, tariff_id=tariff_id) if not cluster: logger.warning(f"[Update] Нет серверов после фильтрации по привязкам в кластере {cluster_id}") return client_id, remnawave_link if tariff_id is not None: tariff = await get_tariff_by_id(session, tariff_id) if tariff: gc = (tariff.get("group_code") or "").lower() if gc in ALLOWED_GROUP_CODES: bound_servers = [s for s in cluster if gc in (s.get("special_groups") or [])] if bound_servers: cluster = bound_servers else: logger.info(f"[Update] Нет серверов со спецгруппой '{gc}' в {cluster_id}") if not cluster: logger.warning(f"[Update] Нет серверов после фильтрации по спецгруппам в кластере {cluster_id}") return client_id, remnawave_link expire_iso = datetime.utcfromtimestamp(expiry_time / 1000).replace(tzinfo=timezone.utc).isoformat() remnawave_servers = [s for s in cluster if s.get("panel_type", "3x-ui").lower() == "remnawave"] xui_servers = [s for s in cluster if s.get("panel_type", "3x-ui").lower() == "3x-ui"] remnawave_client_id = None remnawave_link_value = None if remnawave_servers: inbound_ids = [s["inbound_id"] for s in remnawave_servers if s.get("inbound_id")] group_code = remnawave_servers[0].get("tariff_group") if not group_code: raise ValueError("У Remnawave-сервера отсутствует tariff_group") short_uuid = None if remnawave_link and "/" in remnawave_link: short_uuid = remnawave_link.rstrip("/").split("/")[-1] logger.debug(f"{PANEL_REMNA} Извлечен short_uuid: {short_uuid}") user_data = { "username": email, "trafficLimitStrategy": "NO_RESET", "expireAt": expire_iso, "telegramId": tg_id, "activeInternalSquads": inbound_ids, "uuid": client_id, } if external_squad_uuid: user_data["activeExternalSquads"] = [external_squad_uuid] user_data["activeExternalSquadUuids"] = [external_squad_uuid] user_data["externalSquadUuid"] = external_squad_uuid if traffic_limit is not None: user_data["trafficLimitBytes"] = traffic_limit * 1024**3 if device_limit is not None: user_data["hwidDeviceLimit"] = device_limit if short_uuid: user_data["shortUuid"] = short_uuid logger.debug(f"{PANEL_REMNA} Добавлен short_uuid: {short_uuid}") async def _recreate(api): await api.delete_user(client_id) return await api.create_user(user_data) remna_result = await with_remnawave_api( session, str(remnawave_servers[0].get("server_name") or cluster_id), _recreate, fallback_any=True, timeout_sec=12.0, ) if remna_result: remnawave_client_id = remna_result.get("uuid") remnawave_link_value = remna_result.get("subscriptionUrl") await invalidate_remnawave_profile( session, str(remnawave_servers[0].get("server_name") or cluster_id), str(remnawave_client_id or client_id), fallback_any=True, ) logger.info(f"{PANEL_REMNA} Клиент заново создан, uuid={remnawave_client_id}") else: logger.error(f"{PANEL_REMNA} Не удалось авторизоваться/создать клиента") if not remnawave_client_id: logger.warning(f"{PANEL_REMNA} client_id не получен, используем исходный {client_id}") remnawave_client_id = client_id tasks = [] for server_info in xui_servers: server_name = server_info.get("server_name", "unknown") inbound_id = server_info.get("inbound_id") if not inbound_id: logger.warning(f"{PANEL_XUI} INBOUND_ID отсутствует для сервера {server_name}. Пропуск.") continue xui = await get_xui_instance(server_info["api_url"]) sub_id = email unique_email = f"{email}_{server_name.lower()}" if SUPERNODE else email group_code = server_info.get("tariff_group") if not group_code: raise ValueError(f"У сервера {server_name} отсутствует tariff_group") total_gb_bytes = int(traffic_limit * 1024**3) if traffic_limit is not None else 0 device_limit_value = device_limit if device_limit is not None else 0 config = ClientConfig( client_id=remnawave_client_id, email=unique_email, tg_id=tg_id, limit_ip=device_limit_value, total_gb=total_gb_bytes, expiry_time=expiry_time, enable=True, flow="xtls-rprx-vision", inbound_id=int(inbound_id), sub_id=sub_id, ) tasks.append(add_client(xui, config)) if tasks: await asyncio.gather(*tasks, return_exceptions=True) logger.info(f"[Update] Ключ {remnawave_client_id} обновлён на серверах подгруппы в {cluster_id}") return remnawave_client_id, remnawave_link_value except Exception as e: logger.error(f"[Update Error] Ошибка при обновлении ключа {client_id} на {cluster_id}: {e}") raise async def update_subscription( tg_id: int, email: str, session: AsyncSession, cluster_override: str = None, country_override: str = None, remnawave_link: str = None, ) -> None: u = await resolve_user_optional(session, tg_id) if u is None: raise ValueError(f"The key {email} does not exist in database") uid = u.id record: Key | None = await get_key_by_user_and_email(session, uid, email) if not record: raise ValueError(f"The key {email} does not exist in database") expiry_time = record.expiry_time client_id = record.client_id old_cluster_id = record.server_id tariff_id = record.tariff_id alias = record.alias remnawave_link = remnawave_link or record.remnawave_link public_link = f"{PUBLIC_LINK}{email}/{tg_id}" selected_device_limit = getattr(record, "selected_device_limit", None) selected_traffic_limit = getattr(record, "selected_traffic_limit", None) selected_price_rub = getattr(record, "selected_price_rub", None) current_device_limit_db = getattr(record, "current_device_limit", None) current_traffic_limit_db = getattr(record, "current_traffic_limit", None) tariff = None subgroup_code = getattr(record, "subgroup_code", None) external_squad_uuid = None if tariff_id: tariff = await get_active_tariff_by_id(session, int(tariff_id)) if tariff is None: logger.warning(f"[LOG] update_subscription: тариф с id={tariff_id} не найден!") else: if not subgroup_code: subgroup_code = getattr(tariff, "subgroup_code", None) or getattr(tariff, "subgroup_title", None) external_squad_uuid = tariff.external_squad else: logger.warning("[LOG] update_subscription: tariff_id отсутствует!") from middlewares.session import release_session_early await release_session_early(session) await delete_key_from_cluster(old_cluster_id, email, client_id, session=session) await delete_key_by_user_and_email(session, uid, email) await session.commit() if country_override or cluster_override: new_cluster_id = country_override or cluster_override else: try: result = await select_cluster(session) new_cluster_id = result.cluster_name except ValueError: logger.warning("[Update] Нет доступных кластеров, оставляем на старом") new_cluster_id = old_cluster_id servers = await get_servers(session) cluster_servers = servers.get(new_cluster_id) if cluster_servers is None: for server_list in servers.values(): for server_info in server_list: if server_info.get("server_name", "").lower() == new_cluster_id.lower(): cluster_servers = [server_info] break if cluster_servers: break else: cluster_servers = [] if tariff_id is not None: filtered = await filter_cluster_by_tariff(session, cluster_servers, tariff_id, new_cluster_id) if filtered is not cluster_servers: cluster_servers = filtered elif subgroup_code: cluster_servers = await filter_cluster_by_subgroup( session, cluster_servers, subgroup_code, new_cluster_id, tariff_id=tariff_id ) elif subgroup_code: cluster_servers = await filter_cluster_by_subgroup( session, cluster_servers, subgroup_code, new_cluster_id, tariff_id=tariff_id ) if not cluster_servers: logger.warning(f"[Update] Пересоздание пропущено: нет серверов после фильтрации в {new_cluster_id}.") return if tariff: gc = (getattr(tariff, "group_code", None) or "").lower() if gc in ALLOWED_GROUP_CODES: bound_servers = [s for s in cluster_servers if gc in (s.get("special_groups") or [])] if bound_servers: cluster_servers = bound_servers else: logger.info(f"[Update] Нет серверов со спецгруппой '{gc}' в {new_cluster_id}") if not cluster_servers: logger.warning( f"[Update] Пересоздание пропущено: нет серверов после фильтрации по спецгруппам в {new_cluster_id}." ) return traffic_limit_gb = None device_limit = 0 if tariff and tariff_id: device_limit_effective, traffic_limit_bytes_effective = await get_effective_limits_for_key( session=session, tariff_id=int(tariff_id), selected_device_limit=int(selected_device_limit) if selected_device_limit is not None else None, selected_traffic_gb=int(selected_traffic_limit) if selected_traffic_limit is not None else None, ) device_limit = int(device_limit_effective or 0) traffic_limit_gb = int(traffic_limit_bytes_effective / GB) if traffic_limit_bytes_effective else None elif tariff: traffic_limit_gb = int(tariff.traffic_limit) if tariff.traffic_limit is not None else None device_limit = int(tariff.device_limit) if tariff.device_limit is not None else 0 if current_device_limit_db is not None: device_limit = int(current_device_limit_db) if current_traffic_limit_db is not None: traffic_limit_gb = int(current_traffic_limit_db) new_client_id, remnawave_link_value = await update_key_on_cluster( tg_id=tg_id, client_id=client_id, email=email, expiry_time=expiry_time, cluster_id=new_cluster_id, session=session, traffic_limit=traffic_limit_gb, device_limit=device_limit, remnawave_link=remnawave_link, subgroup_code=subgroup_code, tariff_id=tariff_id, external_squad_uuid=external_squad_uuid, ) aggregated = await make_aggregated_link( session=session, cluster_all=cluster_servers, cluster_id=new_cluster_id, email=email, client_id=new_client_id, tg_id=tg_id, subgroup_code=subgroup_code, remna_link_override=None, plan=tariff_id, ) final_key_link = aggregated or public_link await store_key( session=session, legacy_user_ref=tg_id, client_id=new_client_id, email=email, expiry_time=expiry_time, key=final_key_link, remnawave_link=remnawave_link_value or remnawave_link, server_id=new_cluster_id, tariff_id=tariff_id, alias=alias, selected_device_limit=selected_device_limit, selected_traffic_limit=selected_traffic_limit, selected_price_rub=selected_price_rub, current_device_limit=current_device_limit_db, current_traffic_limit=current_traffic_limit_db, )