from __future__ import annotations import html import math from datetime import datetime from typing import Any def format_bytes(value: int | float | None) -> str: if value is None or value <= 0: return "0 B" units = ["B", "KB", "MB", "GB", "TB", "PB"] size = float(value) unit_index = 0 while size >= 1024 and unit_index < len(units) - 1: size /= 1024 unit_index += 1 if unit_index == 0: return f"{int(size)} {units[unit_index]}" return f"{size:.2f} {units[unit_index]}" def format_datetime(value: datetime | None) -> str: if value is None: return "не указано" return value.strftime("%d.%m.%Y %H:%M") def format_traffic_limit(limit_bytes: int | None) -> str: if limit_bytes is None or limit_bytes <= 0: return "без лимита" return format_bytes(limit_bytes) def _format_days_word(value: int) -> str: remainder_100 = value % 100 remainder_10 = value % 10 if 11 <= remainder_100 <= 14: return "дней" if remainder_10 == 1: return "день" if 2 <= remainder_10 <= 4: return "дня" return "дней" def format_datetime_with_days_left(value: datetime | None) -> str: if value is None: return "не указано" now = datetime.now(value.tzinfo) if value.tzinfo is not None else datetime.now() seconds_left = (value - now).total_seconds() if seconds_left <= 0: return f"{format_datetime(value)} (истёк)" days_left = max(1, math.ceil(seconds_left / 86400)) return f"{format_datetime(value)} ({days_left} {_format_days_word(days_left)})" def build_help_text(*, is_admin: bool) -> str: lines = [ "Команды", "/start - открыть главное меню", "", "Дальше используйте кнопки внутри интерфейса бота.", ] return "\n".join(lines) def format_payment_plan( *, title: str, description: str, price_stars: int, duration_days: int, traffic_limit_bytes: int, ) -> str: lines = [ "Тариф", "", f"{html.escape(title)}", html.escape(description), "", f"Срок: {duration_days} дн.", f"Трафик: {format_traffic_limit(traffic_limit_bytes)}", f"Цена: {price_stars} ₽", ] return "\n".join(lines) def format_issued_access( *, subscription_url: str, expire_at: datetime, traffic_limit_bytes: int, brand_name: str = "", is_renewal: bool = False, ) -> str: normalized_brand_name = html.escape(brand_name.strip()) if brand_name.strip() else "VPN" title = ( f"🔥 {normalized_brand_name} — подписка продлена" if is_renewal else f"🔥 {normalized_brand_name} — доступ готов" ) status_text = ( "Подписка продлена. Откройте её кнопкой ниже " "или импортируйте ссылку вручную в ваш VPN-клиент." if is_renewal else "Подписка активирована. Откройте её кнопкой ниже " "или импортируйте ссылку вручную в ваш VPN-клиент." ) lines = [ title, "", f"
{status_text}
", "", "🔗 Ключ / подписка", html.escape(subscription_url), "", ( "
" f"⏳ Действует до: {format_datetime_with_days_left(expire_at)}\n" f"📶 Трафик: {format_traffic_limit(traffic_limit_bytes)}" "
" ), "", "📲 Что делать дальше", "1. Нажмите кнопку «Открыть подписку» ниже или откройте ссылку выше.", "2. Импортируйте подписку или ключ в приложение VPN-клиента.", "3. Управлять доступом и продлевать его можно в панели бота.", "", ( "
" "💡 О белых списках: подписки с белыми списками предназначены " "для использования в условиях полной блокировки. Если полной блокировки нет — " "рекомендуем пользоваться VPN" "
" ), "", "Если ссылка не открывается автоматически, скопируйте её в клиент вручную.", ] return "\n".join(lines) def format_user_card( *, user: Any, squad_names: list[str], recent_requests: list[Any] | None = None, ) -> str: lines = [ f"{html.escape(user.username)} {html.escape(user.status)}", f"UUID: {html.escape(user.rw_uuid)}", f"Short UUID: {html.escape(user.short_uuid)}", f"Telegram ID: {user.telegram_id if user.telegram_id is not None else '-'}", f"Истекает: {format_datetime(user.expire_at)}", ( "Трафик: " f"{format_bytes(user.used_traffic_bytes)} / " f"{format_traffic_limit(user.traffic_limit_bytes)}" ), f"За все время: {format_bytes(user.lifetime_used_traffic_bytes)}", f"Последний UA: {html.escape(user.sub_last_user_agent or 'неизвестно')}", f"Последнее открытие подписки: {format_datetime(user.sub_last_opened_at)}", f"Подписка: {html.escape(user.subscription_url)}", ] if user.email: lines.append(f"Email: {html.escape(user.email)}") if user.tag: lines.append(f"Tag: {html.escape(user.tag)}") if user.description: lines.append(f"Описание: {html.escape(user.description)}") if squad_names: lines.append(f"Squads: {html.escape(', '.join(sorted(squad_names)))}") if recent_requests: lines.append("") lines.append("Последние запросы подписки") for request in recent_requests: lines.append( f"- {format_datetime(request.request_at)} | " f"IP: {html.escape(request.request_ip or '-')} | " f"UA: {html.escape((request.user_agent or '-')[:80])}" ) return "\n".join(lines)