Merge pull request #2114 from BEDOLAGA-DEV/qhbf7v-bedolaga/add-custom-tag-feature-for-trial-users
Add configurable RemnaWave user tags for trial and paid subscriptions
This commit is contained in:
+41
-2
@@ -19,6 +19,8 @@ DEFAULT_DISPLAY_NAME_BANNED_KEYWORDS = [
|
||||
"joingroup",
|
||||
]
|
||||
|
||||
USER_TAG_PATTERN = re.compile(r"^[A-Z0-9_]{1,16}$")
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -88,6 +90,7 @@ class Settings(BaseSettings):
|
||||
TRIAL_ADD_REMAINING_DAYS_TO_PAID: bool = False
|
||||
TRIAL_PAYMENT_ENABLED: bool = False
|
||||
TRIAL_ACTIVATION_PRICE: int = 0
|
||||
TRIAL_USER_TAG: Optional[str] = None
|
||||
DEFAULT_TRAFFIC_LIMIT_GB: int = 100
|
||||
DEFAULT_DEVICE_LIMIT: int = 1
|
||||
DEFAULT_TRAFFIC_RESET_STRATEGY: str = "MONTH"
|
||||
@@ -119,6 +122,7 @@ class Settings(BaseSettings):
|
||||
PRICE_90_DAYS: int = 269000
|
||||
PRICE_180_DAYS: int = 499000
|
||||
PRICE_360_DAYS: int = 899000
|
||||
PAID_SUBSCRIPTION_USER_TAG: Optional[str] = None
|
||||
|
||||
PRICE_TRAFFIC_5GB: int = 2000
|
||||
PRICE_TRAFFIC_10GB: int = 3500
|
||||
@@ -776,13 +780,48 @@ class Settings(BaseSettings):
|
||||
|
||||
def kopeks_to_rubles(self, kopeks: int) -> float:
|
||||
return kopeks / 100
|
||||
|
||||
|
||||
def rubles_to_kopeks(self, rubles: float) -> int:
|
||||
return int(rubles * 100)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _normalize_user_tag(value: Optional[str], setting_name: str) -> Optional[str]:
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
cleaned = str(value).strip().upper()
|
||||
if not cleaned:
|
||||
return None
|
||||
|
||||
if len(cleaned) > 16:
|
||||
logger.warning(
|
||||
"Некорректная длина %s: максимум 16 символов, получено %s",
|
||||
setting_name,
|
||||
len(cleaned),
|
||||
)
|
||||
return None
|
||||
|
||||
if not USER_TAG_PATTERN.fullmatch(cleaned):
|
||||
logger.warning(
|
||||
"Некорректный формат %s: допустимы только A-Z, 0-9 и подчёркивание",
|
||||
setting_name,
|
||||
)
|
||||
return None
|
||||
|
||||
return cleaned
|
||||
|
||||
def get_trial_warning_hours(self) -> int:
|
||||
return self.TRIAL_WARNING_HOURS
|
||||
|
||||
def get_trial_user_tag(self) -> Optional[str]:
|
||||
return self._normalize_user_tag(self.TRIAL_USER_TAG, "TRIAL_USER_TAG")
|
||||
|
||||
def get_paid_subscription_user_tag(self) -> Optional[str]:
|
||||
return self._normalize_user_tag(
|
||||
self.PAID_SUBSCRIPTION_USER_TAG,
|
||||
"PAID_SUBSCRIPTION_USER_TAG",
|
||||
)
|
||||
|
||||
def get_bot_username(self) -> Optional[str]:
|
||||
username = getattr(self, "BOT_USERNAME", None)
|
||||
if not username:
|
||||
|
||||
@@ -132,6 +132,13 @@ class SubscriptionService:
|
||||
|
||||
self._last_config_signature = config_signature
|
||||
|
||||
@staticmethod
|
||||
def _resolve_user_tag(subscription: Subscription) -> Optional[str]:
|
||||
if getattr(subscription, "is_trial", False):
|
||||
return settings.get_trial_user_tag()
|
||||
|
||||
return settings.get_paid_subscription_user_tag()
|
||||
|
||||
@property
|
||||
def is_configured(self) -> bool:
|
||||
return self._config_error is None
|
||||
@@ -173,7 +180,9 @@ class SubscriptionService:
|
||||
if not validation_success:
|
||||
logger.error(f"Ошибка валидации подписки для пользователя {user.telegram_id}")
|
||||
return None
|
||||
|
||||
|
||||
user_tag = self._resolve_user_tag(subscription)
|
||||
|
||||
async with self.get_api_client() as api:
|
||||
hwid_limit = resolve_hwid_device_limit_for_payload(subscription)
|
||||
existing_users = await api.get_user_by_telegram_id(user.telegram_id)
|
||||
@@ -201,6 +210,9 @@ class SubscriptionService:
|
||||
active_internal_squads=subscription.connected_squads,
|
||||
)
|
||||
|
||||
if user_tag is not None:
|
||||
update_kwargs['tag'] = user_tag
|
||||
|
||||
if hwid_limit is not None:
|
||||
update_kwargs['hwid_device_limit'] = hwid_limit
|
||||
|
||||
@@ -236,6 +248,9 @@ class SubscriptionService:
|
||||
active_internal_squads=subscription.connected_squads,
|
||||
)
|
||||
|
||||
if user_tag is not None:
|
||||
create_kwargs['tag'] = user_tag
|
||||
|
||||
if hwid_limit is not None:
|
||||
create_kwargs['hwid_device_limit'] = hwid_limit
|
||||
|
||||
@@ -288,15 +303,17 @@ class SubscriptionService:
|
||||
is_actually_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
|
||||
subscription.updated_at = current_time
|
||||
await db.commit()
|
||||
is_actually_active = False
|
||||
logger.info(f"🔔 Статус подписки {subscription.id} автоматически изменен на 'expired'")
|
||||
|
||||
|
||||
user_tag = self._resolve_user_tag(subscription)
|
||||
|
||||
async with self.get_api_client() as api:
|
||||
hwid_limit = resolve_hwid_device_limit_for_payload(subscription)
|
||||
|
||||
@@ -314,6 +331,9 @@ class SubscriptionService:
|
||||
active_internal_squads=subscription.connected_squads,
|
||||
)
|
||||
|
||||
if user_tag is not None:
|
||||
update_kwargs['tag'] = user_tag
|
||||
|
||||
if hwid_limit is not None:
|
||||
update_kwargs['hwid_device_limit'] = hwid_limit
|
||||
|
||||
|
||||
@@ -220,6 +220,7 @@ class BotConfigurationService:
|
||||
"PRICE_90_DAYS": "SUBSCRIPTION_PRICES",
|
||||
"PRICE_180_DAYS": "SUBSCRIPTION_PRICES",
|
||||
"PRICE_360_DAYS": "SUBSCRIPTION_PRICES",
|
||||
"PAID_SUBSCRIPTION_USER_TAG": "SUBSCRIPTION_PRICES",
|
||||
"TRAFFIC_PACKAGES_CONFIG": "TRAFFIC_PACKAGES",
|
||||
"BASE_PROMO_GROUP_PERIOD_DISCOUNTS_ENABLED": "SUBSCRIPTIONS_CORE",
|
||||
"BASE_PROMO_GROUP_PERIOD_DISCOUNTS": "SUBSCRIPTIONS_CORE",
|
||||
@@ -227,6 +228,7 @@ class BotConfigurationService:
|
||||
"DEFAULT_AUTOPAY_DAYS_BEFORE": "AUTOPAY",
|
||||
"MIN_BALANCE_FOR_AUTOPAY_KOPEKS": "AUTOPAY",
|
||||
"TRIAL_WARNING_HOURS": "TRIAL",
|
||||
"TRIAL_USER_TAG": "TRIAL",
|
||||
"SUPPORT_USERNAME": "SUPPORT",
|
||||
"SUPPORT_MENU_ENABLED": "SUPPORT",
|
||||
"SUPPORT_SYSTEM_MODE": "SUPPORT",
|
||||
@@ -643,6 +645,24 @@ class BotConfigurationService:
|
||||
"warning": "Несовпадение ID блокирует обновление токена, предотвращая его подмену на другом боте.",
|
||||
"dependencies": "Результат вызова getMe() в Telegram Bot API",
|
||||
},
|
||||
"TRIAL_USER_TAG": {
|
||||
"description": (
|
||||
"Тег, который бот передаст пользователю при активации триальной подписки в панели RemnaWave."
|
||||
),
|
||||
"format": "До 16 символов: заглавные A-Z, цифры и подчёркивание.",
|
||||
"example": "TRIAL_USER",
|
||||
"warning": "Неверный формат будет проигнорирован при создании пользователя.",
|
||||
"dependencies": "Активация триала и включенная интеграция с RemnaWave",
|
||||
},
|
||||
"PAID_SUBSCRIPTION_USER_TAG": {
|
||||
"description": (
|
||||
"Тег, который бот ставит пользователю при покупке платной подписки в панели RemnaWave."
|
||||
),
|
||||
"format": "До 16 символов: заглавные A-Z, цифры и подчёркивание.",
|
||||
"example": "PAID_USER",
|
||||
"warning": "Если тег не задан или невалиден, существующий тег не будет изменён.",
|
||||
"dependencies": "Оплата подписки и интеграция с RemnaWave",
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
||||
Reference in New Issue
Block a user