diff --git a/app/external/remnawave_api.py b/app/external/remnawave_api.py index c90e0e80..ec553efb 100644 --- a/app/external/remnawave_api.py +++ b/app/external/remnawave_api.py @@ -331,7 +331,6 @@ class RemnaWaveAPI: async def update_user( self, uuid: str, - username: Optional[str] = None, status: Optional[UserStatus] = None, traffic_limit_bytes: Optional[int] = None, traffic_limit_strategy: Optional[TrafficLimitStrategy] = None, @@ -344,10 +343,7 @@ class RemnaWaveAPI: active_internal_squads: Optional[List[str]] = None ) -> RemnaWaveUser: data = {'uuid': uuid} - - if username is not None: - data['username'] = username - + if status: data['status'] = status.value if traffic_limit_bytes is not None: diff --git a/app/services/monitoring_service.py b/app/services/monitoring_service.py index f794d64d..4c8a4554 100644 --- a/app/services/monitoring_service.py +++ b/app/services/monitoring_service.py @@ -25,7 +25,6 @@ from app.database.models import MonitoringLog, SubscriptionStatus, Subscription, from app.services.subscription_service import SubscriptionService from app.services.payment_service import PaymentService from app.localization.texts import get_texts -from app.utils.user_utils import build_remnawave_username from app.external.remnawave_api import ( RemnaWaveUser, UserStatus, TrafficLimitStrategy, RemnaWaveAPIError @@ -146,22 +145,20 @@ class MonitoringService: is_active = (subscription.status == SubscriptionStatus.ACTIVE.value and subscription.end_date > current_time) - if (subscription.status == SubscriptionStatus.ACTIVE.value and + if (subscription.status == SubscriptionStatus.ACTIVE.value and subscription.end_date <= current_time): subscription.status = SubscriptionStatus.EXPIRED.value await db.commit() is_active = False logger.info(f"📝 Статус подписки {subscription.id} обновлен на 'expired'") - username_for_api = build_remnawave_username(user) - + async with self.api as api: updated_user = await api.update_user( uuid=user.remnawave_uuid, - username=username_for_api, 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, active_internal_squads=subscription.connected_squads ) diff --git a/app/services/remnawave_service.py b/app/services/remnawave_service.py index 570e2f48..32c4e7eb 100644 --- a/app/services/remnawave_service.py +++ b/app/services/remnawave_service.py @@ -7,16 +7,15 @@ import re from app.config import settings from app.external.remnawave_api import ( - RemnaWaveAPI, RemnaWaveUser, RemnaWaveInternalSquad, + RemnaWaveAPI, RemnaWaveUser, RemnaWaveInternalSquad, RemnaWaveNode, UserStatus, TrafficLimitStrategy, RemnaWaveAPIError ) from app.database.crud.user import get_users_list, get_user_by_telegram_id, update_user from app.database.crud.subscription import get_subscription_by_user_id, update_subscription_usage from app.database.models import ( - User, SubscriptionServer, Transaction, ReferralEarning, + User, SubscriptionServer, Transaction, ReferralEarning, PromoCodeUse, SubscriptionStatus ) -from app.utils.user_utils import build_remnawave_username logger = logging.getLogger(__name__) @@ -768,15 +767,13 @@ class RemnaWaveService: for user in users: if not user.subscription: continue - + try: subscription = user.subscription - username_for_api = build_remnawave_username(user) - + if user.remnawave_uuid: await api.update_user( uuid=user.remnawave_uuid, - username=username_for_api, status=UserStatus.ACTIVE if subscription.is_active else UserStatus.EXPIRED, expire_at=subscription.end_date, traffic_limit_bytes=subscription.traffic_limit_gb * (1024**3) if subscription.traffic_limit_gb > 0 else 0, @@ -786,8 +783,10 @@ class RemnaWaveService: ) stats["updated"] += 1 else: + username = f"user_{user.telegram_id}" + new_user = await api.create_user( - username=username_for_api, + username=username, expire_at=subscription.end_date, status=UserStatus.ACTIVE if subscription.is_active else UserStatus.EXPIRED, traffic_limit_bytes=subscription.traffic_limit_gb * (1024**3) if subscription.traffic_limit_gb > 0 else 0, @@ -797,11 +796,11 @@ class RemnaWaveService: description=f"Bot user: {user.full_name}", active_internal_squads=subscription.connected_squads ) - + await update_user(db, user, remnawave_uuid=new_user.uuid) subscription.remnawave_short_uuid = new_user.short_uuid await db.commit() - + stats["created"] += 1 except Exception as e: diff --git a/app/services/subscription_service.py b/app/services/subscription_service.py index c2424ccb..2f7ff453 100644 --- a/app/services/subscription_service.py +++ b/app/services/subscription_service.py @@ -6,7 +6,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings from app.database.models import Subscription, User, SubscriptionStatus from app.external.remnawave_api import ( - RemnaWaveAPI, RemnaWaveUser, UserStatus, + RemnaWaveAPI, RemnaWaveUser, UserStatus, TrafficLimitStrategy, RemnaWaveAPIError ) from app.database.crud.user import get_user_by_id @@ -16,7 +16,6 @@ from app.utils.pricing_utils import ( calculate_prorated_price, validate_pricing_calculation ) -from app.utils.user_utils import build_remnawave_username logger = logging.getLogger(__name__) @@ -49,8 +48,7 @@ class SubscriptionService: if not validation_success: logger.error(f"Ошибка валидации подписки для пользователя {user.telegram_id}") return None - username_for_api = build_remnawave_username(user) - + async with self.api as api: existing_users = await api.get_user_by_telegram_id(user.telegram_id) if existing_users: @@ -65,7 +63,6 @@ class SubscriptionService: updated_user = await api.update_user( uuid=remnawave_user.uuid, - username=username_for_api, status=UserStatus.ACTIVE, expire_at=subscription.end_date, traffic_limit_bytes=self._gb_to_bytes(subscription.traffic_limit_gb), @@ -76,8 +73,9 @@ class SubscriptionService: else: logger.info(f"🆕 Создаем нового пользователя в панели для {user.telegram_id}") + username = f"user_{user.telegram_id}" updated_user = await api.create_user( - username=username_for_api, + username=username, expire_at=subscription.end_date, status=UserStatus.ACTIVE, traffic_limit_bytes=self._gb_to_bytes(subscription.traffic_limit_gb), @@ -117,9 +115,9 @@ class SubscriptionService: if not user or not user.remnawave_uuid: logger.error(f"RemnaWave UUID не найден для пользователя {subscription.user_id}") return None - + current_time = datetime.utcnow() - is_actually_active = (subscription.status == SubscriptionStatus.ACTIVE.value and + is_actually_active = (subscription.status == SubscriptionStatus.ACTIVE.value and subscription.end_date > current_time) if (subscription.status == SubscriptionStatus.ACTIVE.value and @@ -131,16 +129,13 @@ class SubscriptionService: is_actually_active = False logger.info(f"🔔 Статус подписки {subscription.id} автоматически изменен на 'expired'") - username_for_api = build_remnawave_username(user) - async with self.api as api: updated_user = await api.update_user( uuid=user.remnawave_uuid, - username=username_for_api, 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, active_internal_squads=subscription.connected_squads ) diff --git a/app/utils/user_utils.py b/app/utils/user_utils.py index bd30910f..a1250f59 100644 --- a/app/utils/user_utils.py +++ b/app/utils/user_utils.py @@ -1,7 +1,6 @@ import logging import secrets import string -import re from datetime import datetime, timedelta from typing import Optional, Dict, List from sqlalchemy import select, func, and_ @@ -297,54 +296,3 @@ async def get_referral_analytics(db: AsyncSession, user_id: int) -> Dict: }, 'top_referrals': [] } - - -def build_remnawave_username(user: User) -> str: - """ - Формирует имя пользователя для панели RemnaWave. - - Собирает "Имя Фамилия username" из доступных данных Telegram. - Затем очищает результат от символов, не разрешённых в поле - RemnaWave (допустимы только буквы, цифры, подчёркивания и дефисы). - Если нет ни имени, ни фамилии, ни username, возвращается - "user_{telegram_id}". - """ - parts: List[str] = [] - if user.first_name: - parts.append(user.first_name) - if user.last_name: - parts.append(user.last_name) - if user.username: - parts.append(user.username) - - if parts: - raw = " ".join(parts) - transliterated = _transliterate_ru_to_latin(raw) - sanitized = re.sub(r"[^A-Za-z0-9_-]", "_", transliterated) - sanitized = re.sub(r"_+", "_", sanitized).strip("_") - return sanitized or f"user_{user.telegram_id}" - return f"user_{user.telegram_id}" - - -CYRILLIC_TO_LATIN = { - "а": "a", "б": "b", "в": "v", "г": "g", "д": "d", "е": "e", - "ё": "e", "ж": "zh", "з": "z", "и": "i", "й": "y", "к": "k", - "л": "l", "м": "m", "н": "n", "о": "o", "п": "p", "р": "r", - "с": "s", "т": "t", "у": "u", "ф": "f", "х": "h", "ц": "ts", - "ч": "ch", "ш": "sh", "щ": "shch", "ъ": "", "ы": "y", - "ь": "", "э": "e", "ю": "yu", "я": "ya", -} - - -def _transliterate_ru_to_latin(text: str) -> str: - result = [] - for ch in text: - lower = ch.lower() - if lower in CYRILLIC_TO_LATIN: - repl = CYRILLIC_TO_LATIN[lower] - if ch.isupper(): - repl = repl.capitalize() if len(repl) > 1 else repl.upper() - result.append(repl) - else: - result.append(ch) - return "".join(result)