diff --git a/handlers/keys/key_mode/key_country_mode.py b/handlers/keys/key_mode/key_country_mode.py index 9316ce79..b39c3078 100644 --- a/handlers/keys/key_mode/key_country_mode.py +++ b/handlers/keys/key_mode/key_country_mode.py @@ -39,7 +39,7 @@ from database import ( update_trial, ) from database.models import Key, Server, Tariff -from handlers.buttons import BACK, CONNECT_DEVICE, CONNECT_PHONE, MAIN_MENU, MY_SUB, PC_BUTTON, SUPPORT, TV_BUTTON +from handlers.buttons import BACK, CONNECT_DEVICE, CONNECT_PHONE, MAIN_MENU, MY_SUB, PC_BUTTON, SUPPORT, TV_BUTTON, ROUTER_BUTTON from handlers.keys.operations import create_client_on_server from handlers.keys.operations.aggregated_links import make_aggregated_link from handlers.texts import SELECT_COUNTRY_MSG, key_message_success @@ -567,9 +567,15 @@ async def finalize_key_creation( builder = InlineKeyboardBuilder() is_full_remnawave = await is_full_remnawave_cluster(cluster_name, session) - if (panel_type == "remnawave" or is_full_remnawave) and public_link and REMNAWAVE_WEBAPP: - builder.row(InlineKeyboardButton(text=CONNECT_DEVICE, web_app=WebAppInfo(url=public_link))) - builder.row(InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{email}")) + is_vless = bool(public_link and public_link.lower().startswith("vless://")) or bool(need_vless_key) + + if panel_type == "remnawave" or is_full_remnawave: + if is_vless: + builder.row(InlineKeyboardButton(text=ROUTER_BUTTON, callback_data=f"connect_router|{key_name}")) + else: + if REMNAWAVE_WEBAPP: + builder.row(InlineKeyboardButton(text=CONNECT_DEVICE, web_app=WebAppInfo(url=REMNAWAVE_WEBAPP))) + builder.row(InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{email}")) elif CONNECT_PHONE_BUTTON: builder.row(InlineKeyboardButton(text=CONNECT_PHONE, callback_data=f"connect_phone|{key_name}")) builder.row( diff --git a/handlers/keys/key_view.py b/handlers/keys/key_view.py index 4af41a97..09b9e97d 100644 --- a/handlers/keys/key_view.py +++ b/handlers/keys/key_view.py @@ -324,16 +324,18 @@ async def render_key_info(message: Message, session: Any, key_name: str, image_p builder.row(InlineKeyboardButton(text=ROUTER_BUTTON, callback_data=f"connect_router|{key_name}")) else: builder.row(InlineKeyboardButton(text=CONNECT_DEVICE, web_app=WebAppInfo(url=final_link))) - builder.row(InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{key_name}")) + builder.row(InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{key_name}")) else: if CONNECT_PHONE_BUTTON: builder.row(InlineKeyboardButton(text=CONNECT_PHONE, callback_data=f"connect_phone|{key_name}")) - builder.row( - InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{key_name}"), - InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{key_name}"), - ) if vless_enabled: + builder.row(InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{key_name}")) builder.row(InlineKeyboardButton(text=ROUTER_BUTTON, callback_data=f"connect_router|{key_name}")) + else: + builder.row( + InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{key_name}"), + InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{key_name}"), + ) else: if vless_enabled: builder.row(InlineKeyboardButton(text=ROUTER_BUTTON, callback_data=f"connect_router|{key_name}")) diff --git a/handlers/keys/operations/update.py b/handlers/keys/operations/update.py index 3ec07c93..80684a1e 100644 --- a/handlers/keys/operations/update.py +++ b/handlers/keys/operations/update.py @@ -1,23 +1,19 @@ import asyncio - from datetime import datetime, timezone from sqlalchemy import delete, select from sqlalchemy.ext.asyncio import AsyncSession from config import PUBLIC_LINK, REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD, SUPERNODE -from database import get_servers, store_key +from database import get_servers, store_key, filter_cluster_by_subgroup from database.models import Key, Tariff from handlers.utils import get_least_loaded_cluster -from logger import ( - CLOGGER as logger, - PANEL_REMNA, - PANEL_XUI, -) +from logger import CLOGGER as logger, PANEL_REMNA, PANEL_XUI from panels._3xui import ClientConfig, add_client, get_xui_instance from panels.remnawave import RemnawaveAPI from .deletion import delete_key_from_cluster +from .aggregated_links import make_aggregated_link async def update_key_on_cluster( @@ -30,6 +26,7 @@ async def update_key_on_cluster( traffic_limit: int = None, device_limit: int = None, remnawave_link: str = None, + subgroup_code: str | None = None, ): try: servers = await get_servers(session) @@ -46,6 +43,13 @@ async def update_key_on_cluster( else: raise ValueError(f"Кластер или сервер с ID/именем {cluster_id} не найден.") + if subgroup_code: + filtered = await filter_cluster_by_subgroup(session, cluster, subgroup_code, cluster_id) + if not filtered: + logger.warning(f"[Update] Нет серверов для подгруппы {subgroup_code} в кластере {cluster_id}.") + return client_id, remnawave_link + cluster = filtered + 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"] @@ -64,13 +68,12 @@ async def update_key_on_cluster( if not group_code: raise ValueError("У Remnawave-сервера отсутствует tariff_group") - result = await session.execute( + _ = await session.execute( select(Tariff) .where(Tariff.group_code == group_code, Tariff.is_active.is_(True)) .order_by(Tariff.duration_days.desc()) .limit(1) ) - result.scalar_one_or_none() short_uuid = None if remnawave_link and "/" in remnawave_link: @@ -124,13 +127,12 @@ async def update_key_on_cluster( if not group_code: raise ValueError(f"У сервера {server_name} отсутствует tariff_group") - result = await session.execute( + _ = await session.execute( select(Tariff) .where(Tariff.group_code == group_code, Tariff.is_active.is_(True)) .order_by(Tariff.duration_days.desc()) .limit(1) ) - result.scalar_one_or_none() 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 @@ -153,7 +155,7 @@ async def update_key_on_cluster( if tasks: await asyncio.gather(*tasks, return_exceptions=True) - logger.info(f"[Update] Ключ {remnawave_client_id} обновлён на всех серверах кластера {cluster_id}") + logger.info(f"[Update] Ключ {remnawave_client_id} обновлён на серверах подгруппы в {cluster_id}") return remnawave_client_id, remnawave_key except Exception as e: @@ -171,7 +173,6 @@ async def update_subscription( ) -> None: result = await session.execute(select(Key).where(Key.tg_id == tg_id, Key.email == email)) record = result.scalar_one_or_none() - if not record: raise ValueError(f"The key {email} does not exist in database") @@ -183,16 +184,17 @@ async def update_subscription( remnawave_link = remnawave_link or record.remnawave_link public_link = f"{PUBLIC_LINK}{email}/{tg_id}" - traffic_limit = None - device_limit = None + tariff = None + subgroup_code = getattr(record, "subgroup_code", None) + if tariff_id: - result = await session.execute(select(Tariff).where(Tariff.id == tariff_id, Tariff.is_active.is_(True))) - tariff = result.scalar_one_or_none() - if tariff: - traffic_limit = 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 - else: + q = await session.execute(select(Tariff).where(Tariff.id == tariff_id, Tariff.is_active.is_(True))) + tariff = q.scalar_one_or_none() + 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) else: logger.warning("[LOG] update_subscription: tariff_id отсутствует!") @@ -209,18 +211,6 @@ async def update_subscription( logger.warning("[Update] Нет доступных кластеров, оставляем на старом") new_cluster_id = old_cluster_id - new_client_id, remnawave_key = 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, - device_limit=device_limit, - remnawave_link=remnawave_link, - ) - servers = await get_servers(session) cluster_servers = servers.get(new_cluster_id) @@ -235,8 +225,45 @@ async def update_subscription( else: cluster_servers = [] - has_xui = any(s.get("panel_type", "").lower() == "3x-ui" for s in cluster_servers) - final_key_link = public_link if has_xui else None + if subgroup_code: + prefiltered = await filter_cluster_by_subgroup(session, cluster_servers, subgroup_code, new_cluster_id) + if not prefiltered: + logger.warning(f"[Update] Пересоздание пропущено: нет серверов под подгруппу {subgroup_code} в {new_cluster_id}.") + return + cluster_servers = prefiltered + + traffic_limit = None + device_limit = None + if tariff: + traffic_limit = 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 + + new_client_id, remnawave_key = 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, + device_limit=device_limit, + remnawave_link=remnawave_link, + subgroup_code=subgroup_code, + ) + + 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=remnawave_key, + plan=tariff_id, + ) + + final_key_link = aggregated or public_link await store_key( session=session,