Merge pull request #1630 from Fr1ngg/s4pdi7-bedolaga/add-placeholder-for-user-name

Add configurable template for RemnaWave panel usernames
This commit is contained in:
Egor
2025-10-31 21:32:13 +03:00
committed by GitHub
7 changed files with 80 additions and 3 deletions

View File

@@ -67,6 +67,14 @@ REMNAWAVE_SECRET_KEY=
# {telegram_id} — ID Telegram
REMNAWAVE_USER_DESCRIPTION_TEMPLATE="Bot user: {full_name} {username}"
# Шаблон имени пользователя в панели Remnawave
# Доступные плейсхолдеры аналогичны описанию выше
# {full_name} — Имя, Фамилия из Telegram
# {username} — @логин из Telegram (c @)
# {username_clean} — логин из Telegram (без @)
# {telegram_id} — ID Telegram
REMNAWAVE_USER_USERNAME_TEMPLATE="user_{telegram_id}"
# Режим удаления пользователей из панели RemnaWave
# delete - полностью удалить пользователя из панели
# disable - только деактивировать пользователя

View File

@@ -538,6 +538,8 @@ REMNAWAVE_AUTO_SYNC_TIMES=03:00,15:00
# Шаблон описания пользователя
REMNAWAVE_USER_DESCRIPTION_TEMPLATE="Bot user: {full_name} {username}"
# Шаблон имени пользователя в панели
REMNAWAVE_USER_USERNAME_TEMPLATE="user_{telegram_id}"
REMNAWAVE_USER_DELETE_MODE=delete
# ===== ПОДПИСКИ =====

View File

@@ -73,6 +73,7 @@ class Settings(BaseSettings):
REMNAWAVE_PASSWORD: Optional[str] = None
REMNAWAVE_AUTH_TYPE: str = "api_key"
REMNAWAVE_USER_DESCRIPTION_TEMPLATE: str = "Bot user: {full_name} {username}"
REMNAWAVE_USER_USERNAME_TEMPLATE: str = "user_{telegram_id}"
REMNAWAVE_USER_DELETE_MODE: str = "delete" # "delete" или "disable"
REMNAWAVE_AUTO_SYNC_ENABLED: bool = False
REMNAWAVE_AUTO_SYNC_TIMES: str = "03:00"
@@ -551,6 +552,34 @@ class Settings(BaseSettings):
description = re.sub(r'\s+', ' ', description).strip()
return description
def format_remnawave_username(
self,
*,
full_name: str,
username: Optional[str],
telegram_id: int
) -> str:
template = self.REMNAWAVE_USER_USERNAME_TEMPLATE or "user_{telegram_id}"
username_clean = (username or "").lstrip("@")
full_name_value = full_name or ""
values = defaultdict(str, {
"full_name": full_name_value,
"username": username_clean,
"username_clean": username_clean,
"telegram_id": str(telegram_id),
})
raw_username = template.format_map(values).strip()
sanitized_username = re.sub(r"[^0-9A-Za-z._-]+", "_", raw_username)
sanitized_username = re.sub(r"_+", "_", sanitized_username).strip("._-")
if not sanitized_username:
sanitized_username = f"user_{telegram_id}"
return sanitized_username[:64]
@staticmethod
def parse_daily_time_list(raw_value: Optional[str]) -> List[time]:
if not raw_value:

View File

@@ -3878,7 +3878,11 @@ async def admin_buy_subscription_execute(
remnawave_user = await api.update_user(**update_kwargs)
else:
username = f"user_{target_user.telegram_id}"
username = settings.format_remnawave_username(
full_name=target_user.full_name,
username=target_user.username,
telegram_id=target_user.telegram_id,
)
async with remnawave_service.get_api_client() as api:
create_kwargs = dict(
username=username,

View File

@@ -1245,7 +1245,11 @@ class RemnaWaveService:
await api.update_user(**update_kwargs)
stats["updated"] += 1
else:
username = f"user_{user.telegram_id}"
username = settings.format_remnawave_username(
full_name=user.full_name,
username=user.username,
telegram_id=user.telegram_id,
)
create_kwargs = dict(
username=username,

View File

@@ -216,7 +216,11 @@ class SubscriptionService:
else:
logger.info(f"🆕 Создаем нового пользователя в панели для {user.telegram_id}")
username = f"user_{user.telegram_id}"
username = settings.format_remnawave_username(
full_name=user.full_name,
username=user.username,
telegram_id=user.telegram_id,
)
create_kwargs = dict(
username=username,
expire_at=subscription.end_date,

View File

@@ -269,6 +269,7 @@ class BotConfigurationService:
"VERSION_CHECK_INTERVAL_HOURS": "VERSION",
"TELEGRAM_STARS_RATE_RUB": "TELEGRAM",
"REMNAWAVE_USER_DESCRIPTION_TEMPLATE": "REMNAWAVE",
"REMNAWAVE_USER_USERNAME_TEMPLATE": "REMNAWAVE",
"REMNAWAVE_AUTO_SYNC_ENABLED": "REMNAWAVE",
"REMNAWAVE_AUTO_SYNC_TIMES": "REMNAWAVE",
}
@@ -552,6 +553,31 @@ class BotConfigurationService:
),
"dependencies": "REMNAWAVE_AUTO_SYNC_ENABLED",
},
"REMNAWAVE_USER_DESCRIPTION_TEMPLATE": {
"description": (
"Шаблон текста, который бот передает в поле Description при создании "
"или обновлении пользователя в панели RemnaWave."
),
"format": (
"Доступные плейсхолдеры: {full_name}, {username}, {username_clean}, {telegram_id}."
),
"example": "Bot user: {full_name} {username}",
"warning": "Плейсхолдер {username} автоматически очищается, если у пользователя нет @username.",
},
"REMNAWAVE_USER_USERNAME_TEMPLATE": {
"description": (
"Шаблон имени пользователя, которое создаётся в панели RemnaWave для "
"телеграм-пользователя."
),
"format": (
"Доступные плейсхолдеры: {full_name}, {username}, {username_clean}, {telegram_id}."
),
"example": "vpn_{username_clean}_{telegram_id}",
"warning": (
"Недопустимые символы автоматически заменяются на подчёркивания. "
"Если результат пустой, используется user_{telegram_id}."
),
},
"EXTERNAL_ADMIN_TOKEN": {
"description": "Приватный токен, который использует внешняя админка для проверки запросов.",
"format": "Значение генерируется автоматически из username бота и его токена и доступно только для чтения.",