Files
remnawave-bedolaga-telegram…/app/utils/formatting.py
T
Fringg 9de34900a2 fix: comprehensive html.escape() for all user/admin data in Telegram HTML messages
Bot uses default HTML parse mode — all messages are HTML-parsed by Telegram.
Added html.escape() to all user-controlled and admin-controlled strings
before interpolation into HTML messages to prevent injection and parse errors.

49 files, ~250+ injection points fixed:
- user.full_name, first_name across all handlers and services
- tariff.name/description in purchase flow, admin panel, auto-purchase service
- campaign.name, start_parameter in admin and user-facing handlers
- group.name, promo_group.name across promo management
- contest.title, prize_text, leaderboard names (including public channels)
- transaction.description (contains raw user.full_name from referral service)
- restriction_reason across all balance and subscription handlers
- ticket.title, message_text, poll.title, poll.description
- welcome text template placeholders (first_name, username)
- maintenance reason, admin_name, selected_prize.display_name

New helpers in app/utils/formatting.py:
- safe_html_name() for escaping display names
- user_html_link() replacing 15+ duplicated inline link patterns
2026-03-23 08:06:15 +03:00

50 lines
1.4 KiB
Python

"""Shared formatting utilities for traffic, price, and period display."""
import html
def safe_html_name(name: str | None) -> str:
"""HTML-escape a display name for Telegram HTML messages."""
return html.escape(name or '')
def user_html_link(user) -> str:
"""Build an HTML-safe clickable user link for Telegram messages."""
safe = safe_html_name(user.full_name)
if getattr(user, 'telegram_id', None):
return f'<a href="tg://user?id={user.telegram_id}">{safe}</a>'
return f'<b>{safe}</b>'
def format_traffic(gb: int) -> str:
"""Форматирует трафик."""
if gb == 0:
return 'Безлимит'
return f'{gb} ГБ'
def format_price_kopeks(kopeks: int, compact: bool = False) -> str:
"""Форматирует цену из копеек в рубли."""
rubles = kopeks / 100
if compact:
# Компактный формат - округляем до рублей
return f'{int(round(rubles))}'
if rubles == int(rubles):
return f'{int(rubles)}'
return f'{rubles:.2f}'
def format_period(days: int) -> str:
"""Форматирует период."""
mod100 = days % 100
mod10 = days % 10
if 11 <= mod100 <= 19:
word = 'дней'
elif mod10 == 1:
word = 'день'
elif 2 <= mod10 <= 4:
word = 'дня'
else:
word = 'дней'
return f'{days} {word}'