diff --git a/.env.example b/.env.example index a94b7448..88cb4e23 100644 --- a/.env.example +++ b/.env.example @@ -46,6 +46,14 @@ REMNAWAVE_PASSWORD= # Для панелей установленных скриптом eGames прописывать ключ в формате XXXXXXX:DDDDDDDD REMNAWAVE_SECRET_KEY= +# Шаблон описания пользователя в панели Remnawave +# Доступные плейсхолдеры: +# {full_name} — Имя, Фамилия из Telegram +# {username} — @логин из Telegram (c @) +# {username_clean} — логин из Telegram (без @) +# {telegram_id} — ID Telegram +REMNAWAVE_USER_DESCRIPTION_TEMPLATE="Bot user: {full_name} {username}" + # ========= ПОДПИСКИ ========= # ===== ТРИАЛ ПОДПИСКА ===== TRIAL_DURATION_DAYS=3 diff --git a/app/config.py b/app/config.py index f69ddd61..3fb7536c 100644 --- a/app/config.py +++ b/app/config.py @@ -1,4 +1,6 @@ import os +import re +from collections import defaultdict from typing import List, Optional, Union, Dict from pydantic_settings import BaseSettings from pydantic import field_validator, Field @@ -36,6 +38,7 @@ class Settings(BaseSettings): REMNAWAVE_USERNAME: Optional[str] = None REMNAWAVE_PASSWORD: Optional[str] = None REMNAWAVE_AUTH_TYPE: str = "api_key" + REMNAWAVE_USER_DESCRIPTION_TEMPLATE: str = "Bot user: {full_name} {username}" TRIAL_DURATION_DAYS: int = 3 TRIAL_TRAFFIC_LIMIT_GB: int = 10 @@ -252,6 +255,33 @@ class Settings(BaseSettings): "password": self.REMNAWAVE_PASSWORD, "auth_type": self.REMNAWAVE_AUTH_TYPE } + + def format_remnawave_user_description( + self, + *, + full_name: str, + username: Optional[str], + telegram_id: int + ) -> str: + template = self.REMNAWAVE_USER_DESCRIPTION_TEMPLATE or "Bot user: {full_name} {username}" + template_for_formatting = template.replace("@{username}", "{username}") + + username_clean = (username or "").lstrip("@") + values = defaultdict(str, { + "full_name": full_name, + "username": f"@{username_clean}" if username_clean else "", + "username_clean": username_clean, + "telegram_id": str(telegram_id) + }) + + description = template_for_formatting.format_map(values) + + if not username_clean: + description = re.sub(r'@(?=\W|$)', '', description) + description = re.sub(r'\(\s*\)', '', description) + + description = re.sub(r'\s+', ' ', description).strip() + return description def get_autopay_warning_days(self) -> List[int]: try: diff --git a/app/handlers/admin/users.py b/app/handlers/admin/users.py index 0a88b1c7..d0e858e4 100644 --- a/app/handlers/admin/users.py +++ b/app/handlers/admin/users.py @@ -1644,7 +1644,12 @@ async def toggle_user_server( async with remnawave_service.api as api: await api.update_user( uuid=user.remnawave_uuid, - active_internal_squads=current_squads + active_internal_squads=current_squads, + description=settings.format_remnawave_user_description( + full_name=user.full_name, + username=user.username, + telegram_id=user.telegram_id + ) ) logger.info(f"✅ Обновлены серверы в RemnaWave для пользователя {user.telegram_id}") except Exception as rw_error: @@ -2023,7 +2028,12 @@ async def _update_user_devices(db: AsyncSession, user_id: int, devices: int, adm async with remnawave_service.api as api: await api.update_user( uuid=user.remnawave_uuid, - hwid_device_limit=devices + hwid_device_limit=devices, + description=settings.format_remnawave_user_description( + full_name=user.full_name, + username=user.username, + telegram_id=user.telegram_id + ) ) logger.info(f"✅ Обновлен лимит устройств в RemnaWave для пользователя {user.telegram_id}") except Exception as rw_error: @@ -2061,7 +2071,12 @@ async def _update_user_traffic(db: AsyncSession, user_id: int, traffic_gb: int, await api.update_user( uuid=user.remnawave_uuid, traffic_limit_bytes=traffic_gb * (1024**3) if traffic_gb > 0 else 0, - traffic_limit_strategy=TrafficLimitStrategy.MONTH + traffic_limit_strategy=TrafficLimitStrategy.MONTH, + description=settings.format_remnawave_user_description( + full_name=user.full_name, + username=user.username, + telegram_id=user.telegram_id + ) ) logger.info(f"✅ Обновлен лимит трафика в RemnaWave для пользователя {user.telegram_id}") except Exception as rw_error: diff --git a/app/middlewares/auth.py b/app/middlewares/auth.py index c89836bc..f4840230 100644 --- a/app/middlewares/auth.py +++ b/app/middlewares/auth.py @@ -1,3 +1,4 @@ +import asyncio import logging from datetime import datetime from typing import Callable, Dict, Any, Awaitable @@ -8,11 +9,30 @@ from aiogram.fsm.context import FSMContext from app.config import settings from app.database.database import get_db from app.database.crud.user import get_user_by_telegram_id, create_user +from app.services.remnawave_service import RemnaWaveService from app.states import RegistrationStates logger = logging.getLogger(__name__) +async def _refresh_remnawave_description( + remnawave_uuid: str, + description: str, + telegram_id: int +) -> None: + try: + remnawave_service = RemnaWaveService() + async with remnawave_service.api as api: + await api.update_user(uuid=remnawave_uuid, description=description) + logger.info( + f"✅ [Middleware] Описание пользователя {telegram_id} обновлено в RemnaWave" + ) + except Exception as remnawave_error: + logger.error( + f"❌ [Middleware] Ошибка обновления RemnaWave для {telegram_id}: {remnawave_error}" + ) + + class AuthMiddleware(BaseMiddleware): async def __call__( @@ -151,11 +171,25 @@ class AuthMiddleware(BaseMiddleware): profile_updated = True db_user.last_activity = datetime.utcnow() - + if profile_updated: db_user.updated_at = datetime.utcnow() logger.info(f"💾 [Middleware] Профиль пользователя {user.id} обновлен в middleware") - + + if db_user.remnawave_uuid: + description = settings.format_remnawave_user_description( + full_name=db_user.full_name, + username=db_user.username, + telegram_id=db_user.telegram_id + ) + asyncio.create_task( + _refresh_remnawave_description( + remnawave_uuid=db_user.remnawave_uuid, + description=description, + telegram_id=db_user.telegram_id + ) + ) + await db.commit() data['db'] = db diff --git a/app/services/monitoring_service.py b/app/services/monitoring_service.py index 4c8a4554..2d0b96c7 100644 --- a/app/services/monitoring_service.py +++ b/app/services/monitoring_service.py @@ -158,8 +158,13 @@ class MonitoringService: status=UserStatus.ACTIVE if is_active else UserStatus.EXPIRED, expire_at=subscription.end_date, traffic_limit_bytes=self._gb_to_bytes(subscription.traffic_limit_gb), - traffic_limit_strategy=TrafficLimitStrategy.MONTH, + traffic_limit_strategy=TrafficLimitStrategy.MONTH, hwid_device_limit=subscription.device_limit, + description=settings.format_remnawave_user_description( + full_name=user.full_name, + username=user.username, + telegram_id=user.telegram_id + ), active_internal_squads=subscription.connected_squads ) diff --git a/app/services/remnawave_service.py b/app/services/remnawave_service.py index 32c4e7eb..dd6ee50c 100644 --- a/app/services/remnawave_service.py +++ b/app/services/remnawave_service.py @@ -779,6 +779,11 @@ class RemnaWaveService: traffic_limit_bytes=subscription.traffic_limit_gb * (1024**3) if subscription.traffic_limit_gb > 0 else 0, traffic_limit_strategy=TrafficLimitStrategy.MONTH, hwid_device_limit=subscription.device_limit, + description=settings.format_remnawave_user_description( + full_name=user.full_name, + username=user.username, + telegram_id=user.telegram_id + ), active_internal_squads=subscription.connected_squads ) stats["updated"] += 1 @@ -793,7 +798,11 @@ class RemnaWaveService: traffic_limit_strategy=TrafficLimitStrategy.MONTH, telegram_id=user.telegram_id, hwid_device_limit=subscription.device_limit, - description=f"Bot user: {user.full_name}", + description=settings.format_remnawave_user_description( + full_name=user.full_name, + username=user.username, + telegram_id=user.telegram_id + ), active_internal_squads=subscription.connected_squads ) diff --git a/app/services/subscription_service.py b/app/services/subscription_service.py index 2f7ff453..a2edfd64 100644 --- a/app/services/subscription_service.py +++ b/app/services/subscription_service.py @@ -68,6 +68,11 @@ class SubscriptionService: traffic_limit_bytes=self._gb_to_bytes(subscription.traffic_limit_gb), traffic_limit_strategy=TrafficLimitStrategy.MONTH, hwid_device_limit=subscription.device_limit, + description=settings.format_remnawave_user_description( + full_name=user.full_name, + username=user.username, + telegram_id=user.telegram_id + ), active_internal_squads=subscription.connected_squads ) @@ -82,7 +87,11 @@ class SubscriptionService: traffic_limit_strategy=TrafficLimitStrategy.MONTH, telegram_id=user.telegram_id, hwid_device_limit=subscription.device_limit, - description=f"Bot user: {user.full_name}", + description=settings.format_remnawave_user_description( + full_name=user.full_name, + username=user.username, + telegram_id=user.telegram_id + ), active_internal_squads=subscription.connected_squads ) @@ -135,8 +144,13 @@ class SubscriptionService: status=UserStatus.ACTIVE if is_actually_active else UserStatus.EXPIRED, expire_at=subscription.end_date, traffic_limit_bytes=self._gb_to_bytes(subscription.traffic_limit_gb), - traffic_limit_strategy=TrafficLimitStrategy.MONTH, + traffic_limit_strategy=TrafficLimitStrategy.MONTH, hwid_device_limit=subscription.device_limit, + description=settings.format_remnawave_user_description( + full_name=user.full_name, + username=user.username, + telegram_id=user.telegram_id + ), active_internal_squads=subscription.connected_squads )