diff --git a/app/database/crud/server_squad.py b/app/database/crud/server_squad.py
index 66ebc177..cc7ec4a1 100644
--- a/app/database/crud/server_squad.py
+++ b/app/database/crud/server_squad.py
@@ -306,9 +306,8 @@ async def sync_with_remnawave(db: AsyncSession, remnawave_squads: list[dict]) ->
await create_server_squad(
db=db,
squad_uuid=squad_uuid,
- display_name=_generate_display_name(original_name),
+ display_name=original_name,
original_name=original_name,
- country_code=_extract_country_code(original_name),
price_kopeks=1000,
is_available=False,
)
@@ -482,219 +481,6 @@ async def get_random_trial_squad_uuid(
return None
-def _generate_display_name(original_name: str) -> str:
- """Генерирует отображаемое название сервера на основе оригинального имени."""
-
- country_names = {
- # Европа
- 'NL': '🇳🇱 Нидерланды',
- 'DE': '🇩🇪 Германия',
- 'FR': '🇫🇷 Франция',
- 'GB': '🇬🇧 Великобритания',
- 'UK': '🇬🇧 Великобритания',
- 'IT': '🇮🇹 Италия',
- 'ES': '🇪🇸 Испания',
- 'PT': '🇵🇹 Португалия',
- 'PL': '🇵🇱 Польша',
- 'CZ': '🇨🇿 Чехия',
- 'AT': '🇦🇹 Австрия',
- 'CH': '🇨🇭 Швейцария',
- 'SE': '🇸🇪 Швеция',
- 'NO': '🇳🇴 Норвегия',
- 'FI': '🇫🇮 Финляндия',
- 'DK': '🇩🇰 Дания',
- 'BE': '🇧🇪 Бельгия',
- 'IE': '🇮🇪 Ирландия',
- 'RO': '🇷🇴 Румыния',
- 'BG': '🇧🇬 Болгария',
- 'HU': '🇭🇺 Венгрия',
- 'GR': '🇬🇷 Греция',
- 'LV': '🇱🇻 Латвия',
- 'LT': '🇱🇹 Литва',
- 'EE': '🇪🇪 Эстония',
- 'SK': '🇸🇰 Словакия',
- 'SI': '🇸🇮 Словения',
- 'HR': '🇭🇷 Хорватия',
- 'RS': '🇷🇸 Сербия',
- 'UA': '🇺🇦 Украина',
- 'MD': '🇲🇩 Молдова',
- 'BY': '🇧🇾 Беларусь',
- 'LU': '🇱🇺 Люксембург',
- # СНГ и Азия
- 'RU': '🇷🇺 Россия',
- 'KZ': '🇰🇿 Казахстан',
- 'UZ': '🇺🇿 Узбекистан',
- 'GE': '🇬🇪 Грузия',
- 'AM': '🇦🇲 Армения',
- 'AZ': '🇦🇿 Азербайджан',
- # Америка
- 'US': '🇺🇸 США',
- 'CA': '🇨🇦 Канада',
- 'MX': '🇲🇽 Мексика',
- 'BR': '🇧🇷 Бразилия',
- 'AR': '🇦🇷 Аргентина',
- 'CL': '🇨🇱 Чили',
- 'CO': '🇨🇴 Колумбия',
- # Азия
- 'JP': '🇯🇵 Япония',
- 'KR': '🇰🇷 Южная Корея',
- 'CN': '🇨🇳 Китай',
- 'HK': '🇭🇰 Гонконг',
- 'TW': '🇹🇼 Тайвань',
- 'SG': '🇸🇬 Сингапур',
- 'TH': '🇹🇭 Таиланд',
- 'VN': '🇻🇳 Вьетнам',
- 'MY': '🇲🇾 Малайзия',
- 'ID': '🇮🇩 Индонезия',
- 'PH': '🇵🇭 Филиппины',
- 'IN': '🇮🇳 Индия',
- 'PK': '🇵🇰 Пакистан',
- # Ближний Восток
- 'IL': '🇮🇱 Израиль',
- 'TR': '🇹🇷 Турция',
- 'AE': '🇦🇪 ОАЭ',
- 'SA': '🇸🇦 Саудовская Аравия',
- 'QA': '🇶🇦 Катар',
- 'BH': '🇧🇭 Бахрейн',
- 'KW': '🇰🇼 Кувейт',
- # Океания
- 'AU': '🇦🇺 Австралия',
- 'NZ': '🇳🇿 Новая Зеландия',
- # Африка
- 'ZA': '🇿🇦 ЮАР',
- 'EG': '🇪🇬 Египет',
- 'NG': '🇳🇬 Нигерия',
- 'KE': '🇰🇪 Кения',
- }
-
- name_upper = original_name.upper()
-
- # Сначала ищем код как отдельный элемент (через - или _)
- for code, display_name in country_names.items():
- if f'-{code}' in name_upper or f'_{code}' in name_upper:
- return display_name
- if name_upper.startswith(code + '-') or name_upper.startswith(code + '_'):
- return display_name
- if name_upper.endswith('-' + code) or name_upper.endswith('_' + code):
- return display_name
- if name_upper == code:
- return display_name
-
- # Потом ищем просто вхождение кода
- for code, display_name in country_names.items():
- if code in name_upper:
- return display_name
-
- return f'🌍 {original_name}'
-
-
-def _extract_country_code(original_name: str) -> str | None:
- """Извлекает код страны из оригинального названия."""
-
- # Полный список кодов стран
- codes = [
- # Европа
- 'NL',
- 'DE',
- 'FR',
- 'GB',
- 'UK',
- 'IT',
- 'ES',
- 'PT',
- 'PL',
- 'CZ',
- 'AT',
- 'CH',
- 'SE',
- 'NO',
- 'FI',
- 'DK',
- 'BE',
- 'IE',
- 'RO',
- 'BG',
- 'HU',
- 'GR',
- 'LV',
- 'LT',
- 'EE',
- 'SK',
- 'SI',
- 'HR',
- 'RS',
- 'UA',
- 'MD',
- 'BY',
- 'LU',
- # СНГ
- 'RU',
- 'KZ',
- 'UZ',
- 'GE',
- 'AM',
- 'AZ',
- # Америка
- 'US',
- 'CA',
- 'MX',
- 'BR',
- 'AR',
- 'CL',
- 'CO',
- # Азия
- 'JP',
- 'KR',
- 'CN',
- 'HK',
- 'TW',
- 'SG',
- 'TH',
- 'VN',
- 'MY',
- 'ID',
- 'PH',
- 'IN',
- 'PK',
- # Ближний Восток
- 'IL',
- 'TR',
- 'AE',
- 'SA',
- 'QA',
- 'BH',
- 'KW',
- # Океания
- 'AU',
- 'NZ',
- # Африка
- 'ZA',
- 'EG',
- 'NG',
- 'KE',
- ]
-
- name_upper = original_name.upper()
-
- # Сначала ищем код как отдельный элемент
- for code in codes:
- if f'-{code}' in name_upper or f'_{code}' in name_upper:
- return code
- if name_upper.startswith(code + '-') or name_upper.startswith(code + '_'):
- return code
- if name_upper.endswith('-' + code) or name_upper.endswith('_' + code):
- return code
- if name_upper == code:
- return code
-
- # Потом просто ищем вхождение
- for code in codes:
- if code in name_upper:
- return code
-
- return None
-
-
async def get_server_statistics(db: AsyncSession) -> dict:
total_result = await db.execute(select(func.count(ServerSquad.id)))
total_servers = total_result.scalar()
diff --git a/app/handlers/admin/promo_offers.py b/app/handlers/admin/promo_offers.py
index 3a38dfb2..e614af0e 100644
--- a/app/handlers/admin/promo_offers.py
+++ b/app/handlers/admin/promo_offers.py
@@ -977,7 +977,7 @@ async def _render_squad_selection(
if not selected_server:
selected_server = await get_server_squad_by_uuid(db, selected_uuid)
if selected_server:
- selected_server_name = selected_server.display_name
+ selected_server_name = html.escape(selected_server.display_name)
header = texts.t('ADMIN_PROMO_OFFER_SELECT_SQUAD_TITLE', '🌍 Выберите сквад')
if selected_server_name:
diff --git a/app/handlers/admin/remnawave.py b/app/handlers/admin/remnawave.py
index 26fb9226..d05a4283 100644
--- a/app/handlers/admin/remnawave.py
+++ b/app/handlers/admin/remnawave.py
@@ -1,3 +1,4 @@
+import html
import math
from datetime import UTC, datetime, timedelta
from typing import Any
@@ -175,7 +176,7 @@ def _format_migration_server_label(texts, server) -> str:
return texts.t(
'ADMIN_SQUAD_MIGRATION_SERVER_LABEL',
'{name} — 👥 {users} ({status})',
- ).format(name=server.display_name, users=server.current_users, status=status)
+ ).format(name=html.escape(server.display_name), users=server.current_users, status=status)
def _build_migration_keyboard(
diff --git a/app/handlers/admin/servers.py b/app/handlers/admin/servers.py
index 148b4e0d..e19e3ad1 100644
--- a/app/handlers/admin/servers.py
+++ b/app/handlers/admin/servers.py
@@ -44,8 +44,8 @@ def _build_server_edit_view(server):
Информация:
• ID: {server.id}
• UUID: {server.squad_uuid}
-• Название: {server.display_name}
-• Оригинальное: {server.original_name or 'Не указано'}
+• Название: {html.escape(server.display_name)}
+• Оригинальное: {html.escape(server.original_name) if server.original_name else 'Не указано'}
• Статус: {status_emoji}
Настройки:
@@ -172,7 +172,7 @@ async def show_servers_list(callback: types.CallbackQuery, db_user: User, db: As
status_emoji = '✅' if server.is_available else '❌'
price_text = f'{int(server.price_rubles)} ₽' if server.price_kopeks > 0 else 'Бесплатно'
- text += f'{i}. {status_emoji} {server.display_name}\n'
+ text += f'{i}. {status_emoji} {html.escape(server.display_name)}\n'
text += f' 💰 Цена: {price_text}'
if server.max_users:
@@ -559,7 +559,7 @@ async def start_server_edit_name(callback: types.CallbackQuery, state: FSMContex
await callback.message.edit_text(
f'✏️ Редактирование названия\n\n'
- f'Текущее название: {server.display_name}\n\n'
+ f'Текущее название: {html.escape(server.display_name)}\n\n'
f'Отправьте новое название для сервера:',
reply_markup=types.InlineKeyboardMarkup(
inline_keyboard=[
@@ -621,7 +621,7 @@ async def delete_server_confirm(callback: types.CallbackQuery, db_user: User, db
🗑️ Удаление сервера
Вы действительно хотите удалить сервер:
-{server.display_name}
+{html.escape(server.display_name)}
⚠️ Внимание!
Сервер можно удалить только если к нему нет активных подключений.
@@ -658,7 +658,7 @@ async def delete_server_execute(callback: types.CallbackQuery, db_user: User, db
await cache.delete_pattern('available_countries*')
await callback.message.edit_text(
- f'✅ Сервер {server.display_name} успешно удален!',
+ f'✅ Сервер {html.escape(server.display_name)} успешно удален!',
reply_markup=types.InlineKeyboardMarkup(
inline_keyboard=[
[types.InlineKeyboardButton(text='📋 К списку серверов', callback_data='admin_servers_list')]
@@ -668,7 +668,7 @@ async def delete_server_execute(callback: types.CallbackQuery, db_user: User, db
)
else:
await callback.message.edit_text(
- f'❌ Не удалось удалить сервер {server.display_name}\n\nВозможно, к нему есть активные подключения.',
+ f'❌ Не удалось удалить сервер {html.escape(server.display_name)}\n\nВозможно, к нему есть активные подключения.',
reply_markup=types.InlineKeyboardMarkup(
inline_keyboard=[
[types.InlineKeyboardButton(text='🔙 К серверу', callback_data=f'admin_server_edit_{server_id}')]
@@ -706,7 +706,7 @@ async def show_server_detailed_stats(callback: types.CallbackQuery, db_user: Use
for i, server in enumerate(sorted_servers[:5], 1):
price_text = f'{int(server.price_rubles)} ₽' if server.price_kopeks > 0 else 'Бесплатно'
- text += f'{i}. {server.display_name} - {price_text}\n'
+ text += f'{i}. {html.escape(server.display_name)} - {price_text}\n'
if not sorted_servers:
text += 'Нет доступных серверов\n'
@@ -968,7 +968,7 @@ async def start_server_edit_promo_groups(
text = (
'🎯 Настройка промогрупп\n\n'
- f'Сервер: {server.display_name}\n\n'
+ f'Сервер: {html.escape(server.display_name)}\n\n'
'Выберите промогруппы, которым будет доступен этот сервер.\n'
'Должна быть выбрана минимум одна промогруппа.'
)
diff --git a/app/handlers/admin/users.py b/app/handlers/admin/users.py
index 1e892921..7b097366 100644
--- a/app/handlers/admin/users.py
+++ b/app/handlers/admin/users.py
@@ -1,3 +1,4 @@
+import html
import re
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
@@ -868,7 +869,7 @@ async def _render_user_subscription_overview(callback: types.CallbackQuery, db:
try:
server = await get_server_squad_by_uuid(db, squad_uuid)
if server:
- text += f'• {server.display_name}\n'
+ text += f'• {html.escape(server.display_name)}\n'
else:
text += f'• {squad_uuid[:8]}... (неизвестный)\n'
except Exception as e:
diff --git a/app/handlers/balance/main.py b/app/handlers/balance/main.py
index bb5dc343..18663d40 100644
--- a/app/handlers/balance/main.py
+++ b/app/handlers/balance/main.py
@@ -404,10 +404,7 @@ async def handle_balance_history_pagination(callback: types.CallbackQuery, db_us
@error_handler
async def show_payment_methods(callback: types.CallbackQuery, db_user: User, db: AsyncSession, state: FSMContext):
from app.config import settings
- from app.database.crud.subscription import get_subscription_by_user_id
- from app.services.subscription_service import SubscriptionService
from app.utils.payment_utils import get_payment_methods_text
- from app.utils.pricing_utils import apply_percentage_discount, calculate_months_from_days
texts = get_texts(db_user.language)
@@ -430,139 +427,7 @@ async def show_payment_methods(callback: types.CallbackQuery, db_user: User, db:
payment_text = get_payment_methods_text(db_user.language)
- # Добавляем информацию о текущем тарифе пользователя
- subscription = await get_subscription_by_user_id(db, db_user.id)
- tariff_info = ''
- if subscription and not subscription.is_trial:
- # Рассчитываем приблизительную стоимость продления на 30 дней
- duration_days = 30 # Берем для примера 30 дней
- current_traffic = subscription.traffic_limit_gb
- current_connected_squads = subscription.connected_squads or []
- current_device_limit = subscription.device_limit or settings.DEFAULT_DEVICE_LIMIT
-
- try:
- # Получаем цены для текущих параметров
- from app.config import PERIOD_PRICES
- from app.database.crud.tariff import get_tariff_by_id
-
- # В режиме тарифов берём цену из тарифа пользователя
- tariff = None
- tariff_price_found = False
- base_price_original = 0
- if settings.is_tariffs_mode() and subscription.tariff_id:
- tariff = await get_tariff_by_id(db, subscription.tariff_id)
- if tariff and tariff.period_prices:
- base_price_original = tariff.period_prices.get(str(duration_days), 0)
- if base_price_original > 0:
- tariff_price_found = True
-
- # Если не нашли в тарифе - используем PERIOD_PRICES
- if base_price_original <= 0:
- base_price_original = PERIOD_PRICES.get(duration_days, 0)
-
- if tariff_price_found:
- # Тарифный режим: серверы и трафик включены в цену тарифа.
- # Порядок: база + устройства → скидка на полную сумму (как в calculate_renewal_price).
- from app.utils.promo_offer import get_user_active_promo_discount_percent
-
- original_price = base_price_original
-
- tariff_device_limit = tariff.device_limit if tariff.device_limit is not None else 0
- device_limit = (
- subscription.device_limit if subscription.device_limit is not None else tariff_device_limit
- )
- extra_devices = max(0, device_limit - tariff_device_limit)
- device_price_per_unit = (
- tariff.device_price_kopeks
- if tariff and tariff.device_price_kopeks is not None
- else settings.PRICE_PER_DEVICE
- )
- months_in_period = calculate_months_from_days(duration_days)
- devices_price = extra_devices * device_price_per_unit * months_in_period
- original_price += devices_price
-
- # Скидка промогруппы на полную сумму (база + устройства)
- period_discount_percent = db_user.get_promo_discount('period', duration_days)
- discount_total = original_price * period_discount_percent // 100
- total_price = original_price - discount_total
-
- # Promo-offer скидка (временная)
- promo_offer_percent = get_user_active_promo_discount_percent(db_user)
- if promo_offer_percent > 0:
- promo_offer_discount = total_price * promo_offer_percent // 100
- total_price = total_price - promo_offer_discount
- else:
- # Классический режим: серверы + трафик + устройства считаются отдельно
- period_discount_percent = db_user.get_promo_discount('period', duration_days)
- base_price, base_discount_total = apply_percentage_discount(
- base_price_original,
- period_discount_percent,
- )
- from app.services.subscription_service import SubscriptionService
-
- subscription_service = SubscriptionService()
- (
- servers_price_per_month,
- per_server_monthly_prices,
- ) = await subscription_service.get_countries_price_by_uuids(
- current_connected_squads,
- db,
- promo_group_id=db_user.promo_group_id,
- )
- servers_discount_percent = db_user.get_promo_discount('servers', duration_days)
- total_servers_price = 0
- for server_price in per_server_monthly_prices:
- discounted_per_month, discount_per_month = apply_percentage_discount(
- server_price,
- servers_discount_percent,
- )
- total_servers_price += discounted_per_month
-
- traffic_price_per_month = settings.get_traffic_price(current_traffic)
- traffic_discount_percent = db_user.get_promo_discount('traffic', duration_days)
- traffic_discounted_per_month, traffic_discount_per_month = apply_percentage_discount(
- traffic_price_per_month,
- traffic_discount_percent,
- )
-
- additional_devices = max(0, (current_device_limit or 0) - settings.DEFAULT_DEVICE_LIMIT)
- devices_price_per_month = additional_devices * settings.PRICE_PER_DEVICE
- devices_discount_percent = db_user.get_promo_discount('devices', duration_days)
- devices_discounted_per_month, devices_discount_per_month = apply_percentage_discount(
- devices_price_per_month,
- devices_discount_percent,
- )
-
- months_in_period = calculate_months_from_days(duration_days)
- total_price = (
- base_price
- + total_servers_price * months_in_period
- + traffic_discounted_per_month * months_in_period
- + devices_discounted_per_month * months_in_period
- )
-
- traffic_value = current_traffic or 0
- if traffic_value <= 0:
- traffic_display = texts.t('TRAFFIC_UNLIMITED_SHORT', 'Безлимит')
- else:
- traffic_display = texts.format_traffic(traffic_value)
-
- current_tariff_desc = (
- f'📱 Подписка: {len(current_connected_squads)} серверов, '
- f'{traffic_display}, {current_device_limit} устр.'
- )
- estimated_price_info = (
- f'💰 Стоимость продления (примерно): {texts.format_price(total_price)} за {duration_days} дней'
- )
-
- tariff_info = f'\n\n📋 Ваш текущий тариф:\n{current_tariff_desc}\n{estimated_price_info}'
- except Exception as e:
- logger.warning(
- 'Не удалось рассчитать стоимость текущей подписки для пользователя', db_user_id=db_user.id, error=e
- )
- tariff_info = ''
-
- full_text = payment_text + tariff_info
+ full_text = payment_text
keyboard = get_payment_methods_keyboard(0, db_user.language)
diff --git a/app/handlers/server_status.py b/app/handlers/server_status.py
index a2e551f8..72f1b947 100644
--- a/app/handlers/server_status.py
+++ b/app/handlers/server_status.py
@@ -1,3 +1,4 @@
+import html
from datetime import UTC, datetime
import structlog
@@ -189,7 +190,7 @@ def _format_server_lines(
else:
latency_text = texts.t('SERVER_STATUS_OFFLINE', 'нет ответа')
- name = server.display_name or server.name
+ name = html.escape(server.display_name or server.name)
flag_prefix = f'{server.flag} ' if server.flag else ''
server_line = f'{flag_prefix}{name} — {latency_text}'
lines.append(f'
{server_line}') diff --git a/app/handlers/stars_payments.py b/app/handlers/stars_payments.py index c7b6f42d..a0c8a066 100644 --- a/app/handlers/stars_payments.py +++ b/app/handlers/stars_payments.py @@ -301,7 +301,7 @@ async def _handle_guest_purchase_payment( from app.services.payment.common import try_fulfill_guest_purchase try: - purchase_token = payload[len('guest_purchase_'):] + purchase_token = payload[len('guest_purchase_') :] if not purchase_token or not _PURCHASE_TOKEN_RE.match(purchase_token): logger.error('Invalid purchase_token format in guest_purchase payload', payload=payload) await message.answer('❌ Ошибка: неверный формат платежа.') @@ -356,8 +356,7 @@ async def _handle_guest_purchase_payment( ) elif result is False: await message.answer( - '❌ Произошла ошибка при обработке подарочной подписки. ' - 'Обратитесь в поддержку.', + '❌ Произошла ошибка при обработке подарочной подписки. Обратитесь в поддержку.', ) else: logger.error('try_fulfill_guest_purchase returned None for Stars gift', payload=payload) diff --git a/app/handlers/subscription/countries.py b/app/handlers/subscription/countries.py index f611594d..088eed83 100644 --- a/app/handlers/subscription/countries.py +++ b/app/handlers/subscription/countries.py @@ -1,3 +1,4 @@ +import html from datetime import UTC, datetime from aiogram import types @@ -66,7 +67,7 @@ async def handle_add_countries(callback: types.CallbackQuery, db_user: User, db: current_countries_names = [] for country in countries: if country['uuid'] in current_countries: - current_countries_names.append(country['name']) + current_countries_names.append(html.escape(country['name'])) current_list = ( '\n'.join(f'• {name}' for name in current_countries_names) @@ -659,8 +660,8 @@ def _build_countries_selection_text(countries: list[dict], base_text: str) -> st continue desc = country.get('description', '').strip() if desc: - name = country.get('name', '') - descriptions.append(f'{name}\n{desc}') + name = html.escape(country.get('name', '')) + descriptions.append(f'{name}\n{html.escape(desc)}') if not descriptions: return base_text @@ -841,9 +842,9 @@ async def confirm_add_countries_to_subscription( total_price += charged_price total_discount_value += int(discount_per_month * charged_days / 30) - new_countries_names.append(country['name']) + new_countries_names.append(html.escape(country['name'])) if country['uuid'] in removed_countries: - removed_countries_names.append(country['name']) + removed_countries_names.append(html.escape(country['name'])) if new_countries and db_user.balance_kopeks < total_price: missing_kopeks = total_price - db_user.balance_kopeks diff --git a/app/handlers/subscription/devices.py b/app/handlers/subscription/devices.py index 5e84d36b..d5597c7b 100644 --- a/app/handlers/subscription/devices.py +++ b/app/handlers/subscription/devices.py @@ -82,7 +82,7 @@ async def get_servers_display_names(squad_uuids: list[str]) -> str: for uuid in squad_uuids: server = await get_server_squad_by_uuid(db, uuid) if server: - server_names.append(server.display_name) + server_names.append(html_mod.escape(server.display_name)) logger.debug('Найден сервер в БД', uuid=uuid, display_name=server.display_name) else: logger.warning('Сервер с UUID не найден в БД', uuid=uuid) @@ -92,7 +92,7 @@ async def get_servers_display_names(squad_uuids: list[str]) -> str: for uuid in squad_uuids: for country in countries: if country['uuid'] == uuid: - server_names.append(country['name']) + server_names.append(html_mod.escape(country['name'])) logger.debug('Найден сервер в кэше', uuid=uuid, country=country['name']) break diff --git a/app/handlers/subscription/pricing.py b/app/handlers/subscription/pricing.py index bcbd8114..e5f7d667 100644 --- a/app/handlers/subscription/pricing.py +++ b/app/handlers/subscription/pricing.py @@ -1,3 +1,4 @@ +import html from datetime import UTC, datetime from typing import Any @@ -74,7 +75,7 @@ async def _prepare_subscription_summary( if country['uuid'] in selected_country_ids: server_price_per_month = country['price_kopeks'] countries_price_per_month += server_price_per_month - selected_countries_names.append(country['name']) + selected_countries_names.append(html.escape(country['name'])) server_monthly_prices.append(server_price_per_month) servers_discount_percent = db_user.get_promo_discount( diff --git a/app/handlers/subscription/purchase.py b/app/handlers/subscription/purchase.py index 2ca22c35..1b7f7cf6 100644 --- a/app/handlers/subscription/purchase.py +++ b/app/handlers/subscription/purchase.py @@ -1,3 +1,4 @@ +import html from datetime import UTC, datetime, timedelta from typing import Any @@ -623,7 +624,7 @@ async def show_trial_offer(callback: types.CallbackQuery, db_user: User, db: Asy tariff_squads = await get_server_squads_by_uuids(db, trial_tariff.allowed_squads) if tariff_squads: if len(tariff_squads) == 1: - trial_server_name = tariff_squads[0].display_name + trial_server_name = html.escape(tariff_squads[0].display_name) else: trial_server_name = texts.t( 'TRIAL_SERVER_RANDOM_POOL', @@ -633,7 +634,7 @@ async def show_trial_offer(callback: types.CallbackQuery, db_user: User, db: Asy trial_squads = await get_trial_eligible_server_squads(db, include_unavailable=True) if trial_squads: if len(trial_squads) == 1: - trial_server_name = trial_squads[0].display_name + trial_server_name = html.escape(trial_squads[0].display_name) else: trial_server_name = texts.t( 'TRIAL_SERVER_RANDOM_POOL',