From 94cd06302a8271ca01655cd426d047ee85c2ccdd Mon Sep 17 00:00:00 2001 From: gy9vin Date: Fri, 16 Jan 2026 15:58:22 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81=D1=8B=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../subscription_auto_purchase_service.py | 187 ++---------------- app/services/system_settings_service.py | 70 +++++++ 2 files changed, 82 insertions(+), 175 deletions(-) diff --git a/app/services/subscription_auto_purchase_service.py b/app/services/subscription_auto_purchase_service.py index a3aac239..57ae9316 100644 --- a/app/services/subscription_auto_purchase_service.py +++ b/app/services/subscription_auto_purchase_service.py @@ -752,95 +752,13 @@ async def auto_activate_subscription_after_topup( subscription = await get_subscription_by_user_id(db, user.id) - # Если автоактивация отключена - только отправляем уведомление + # Если автоактивация отключена - уведомление отправится из _send_payment_success_notification if not settings.is_auto_activate_after_topup_enabled(): - # Если включен яркий промпт активации, НЕ отправляем уведомление здесь - # т.к. оно будет отправлено через _send_payment_success_notification - if settings.SHOW_ACTIVATION_PROMPT_AFTER_TOPUP: - logger.info( - "⚠️ Пропущена отправка уведомления пользователю %s (SHOW_ACTIVATION_PROMPT_AFTER_TOPUP=true, уведомление будет из payment service)", - user.telegram_id, - ) - return (False, False) - - # Старая логика уведомлений для режима без яркого промпта - notification_sent = False - if bot: - try: - texts = get_texts(getattr(user, "language", "ru")) - has_active_subscription = ( - subscription - and subscription.status in ("active", "ACTIVE") - ) - - # Формируем строку с суммой пополнения - topup_line = "" - if topup_amount: - topup_line = f"➕ Пополнено: {settings.format_price(topup_amount)}\n" - - # Определяем состояние подписки - is_trial = subscription and getattr(subscription, 'is_trial', False) - - if has_active_subscription and not is_trial: - # Активная платная подписка — 2 кнопки - warning_message = ( - f"✅ Баланс пополнен!\n\n" - f"{topup_line}" - f"💳 Текущий баланс: {settings.format_price(user.balance_kopeks)}\n\n" - f"👇 Выберите действие:" - ) - keyboard = InlineKeyboardMarkup( - inline_keyboard=[ - [InlineKeyboardButton( - text="💎 Продлить подписку", - callback_data="subscription_extend", - )], - [InlineKeyboardButton( - text="📱 Изменить устройства", - callback_data="subscription_change_devices", - )], - ] - ) - else: - # Триал или подписка закончилась — 1 кнопка - warning_message = ( - f"✅ Баланс пополнен!\n\n" - f"{topup_line}" - f"💳 Текущий баланс: {settings.format_price(user.balance_kopeks)}\n\n" - f"{'━' * 20}\n\n" - f"🚨🚨🚨 ВНИМАНИЕ! 🚨🚨🚨\n\n" - f"🔴 ПОДПИСКА НЕ АКТИВНА!\n\n" - f"⚠️ Пополнение баланса НЕ активирует подписку автоматически!\n\n" - f"👇 Обязательно оформите подписку:" - ) - keyboard = InlineKeyboardMarkup( - inline_keyboard=[ - [InlineKeyboardButton( - text="🚀 КУПИТЬ ПОДПИСКУ", - callback_data="menu_buy", - )], - ] - ) - - await bot.send_message( - chat_id=user.telegram_id, - text=warning_message, - reply_markup=keyboard, - parse_mode="HTML", - ) - notification_sent = True - logger.info( - "⚠️ Отправлено уведомление о пополнении баланса пользователю %s (автоактивация выключена, подписка %s)", - user.telegram_id, - "активна" if has_active_subscription else "неактивна", - ) - except Exception as notify_error: - logger.warning( - "⚠️ Не удалось отправить уведомление пользователю %s: %s", - user.telegram_id, - notify_error, - ) - return (False, notification_sent) + logger.info( + "⚠️ Автоактивация отключена для пользователя %s, уведомление будет отправлено из payment service", + user.telegram_id, + ) + return (False, False) # Если подписка активна — ничего не делаем (автоактивация включена, но подписка уже есть) if subscription and subscription.status == "ACTIVE" and subscription.end_date > datetime.utcnow(): @@ -921,93 +839,12 @@ async def auto_activate_subscription_after_topup( user.telegram_id, balance, ) - # Если включен яркий промпт активации, НЕ отправляем уведомление здесь - # т.к. оно будет отправлено через _send_payment_success_notification - if settings.SHOW_ACTIVATION_PROMPT_AFTER_TOPUP: - logger.info( - "⚠️ Пропущена отправка уведомления пользователю %s (SHOW_ACTIVATION_PROMPT_AFTER_TOPUP=true, уведомление будет из payment service)", - user.telegram_id, - ) - return (False, False) - - # Старая логика уведомлений для режима без яркого промпта - notification_sent = False - if bot: - try: - texts = get_texts(getattr(user, "language", "ru")) - has_active_subscription = ( - subscription - and subscription.status in ("active", "ACTIVE") - ) - - # Формируем строку с суммой пополнения - topup_line2 = "" - if topup_amount: - topup_line2 = f"➕ Пополнено: {settings.format_price(topup_amount)}\n" - - # Определяем состояние подписки - is_trial2 = subscription and getattr(subscription, 'is_trial', False) - - if has_active_subscription and not is_trial2: - # Активная платная подписка — 2 кнопки - warning_message = ( - f"✅ Баланс пополнен!\n\n" - f"{topup_line2}" - f"💳 Текущий баланс: {settings.format_price(balance)}\n\n" - f"👇 Выберите действие:" - ) - keyboard = InlineKeyboardMarkup( - inline_keyboard=[ - [InlineKeyboardButton( - text="💎 Продлить подписку", - callback_data="subscription_extend", - )], - [InlineKeyboardButton( - text="📱 Изменить устройства", - callback_data="subscription_change_devices", - )], - ] - ) - else: - # Триал или подписка закончилась — 1 кнопка - warning_message = ( - f"✅ Баланс пополнен!\n\n" - f"{topup_line2}" - f"💳 Текущий баланс: {settings.format_price(balance)}\n\n" - f"{'━' * 20}\n\n" - f"🚨🚨🚨 ВНИМАНИЕ! 🚨🚨🚨\n\n" - f"🔴 ПОДПИСКА НЕ АКТИВНА!\n\n" - f"⚠️ Пополнение баланса НЕ активирует подписку автоматически!\n\n" - f"👇 Обязательно оформите подписку:" - ) - keyboard = InlineKeyboardMarkup( - inline_keyboard=[ - [InlineKeyboardButton( - text="🚀 КУПИТЬ ПОДПИСКУ", - callback_data="menu_buy", - )], - ] - ) - - await bot.send_message( - chat_id=user.telegram_id, - text=warning_message, - reply_markup=keyboard, - parse_mode="HTML", - ) - notification_sent = True - logger.info( - "⚠️ Отправлено уведомление о пополнении баланса пользователю %s (недостаточно средств, подписка %s)", - user.telegram_id, - "активна" if has_active_subscription else "неактивна", - ) - except Exception as notify_error: - logger.warning( - "⚠️ Не удалось отправить уведомление пользователю %s: %s", - user.telegram_id, - notify_error, - ) - return (False, notification_sent) + # Уведомление отправится из _send_payment_success_notification + logger.info( + "⚠️ Недостаточно средств для автоактивации пользователя %s, уведомление будет отправлено из payment service", + user.telegram_id, + ) + return (False, False) texts = get_texts(getattr(user, "language", "ru")) diff --git a/app/services/system_settings_service.py b/app/services/system_settings_service.py index 31d39da3..1f1187b0 100644 --- a/app/services/system_settings_service.py +++ b/app/services/system_settings_service.py @@ -259,6 +259,7 @@ class BotConfigurationService: "PAYMENT_BALANCE_TEMPLATE": "PAYMENT", "PAYMENT_SUBSCRIPTION_TEMPLATE": "PAYMENT", "AUTO_PURCHASE_AFTER_TOPUP_ENABLED": "PAYMENT", + "SHOW_ACTIVATION_PROMPT_AFTER_TOPUP": "PAYMENT", "SIMPLE_SUBSCRIPTION_ENABLED": "SIMPLE_SUBSCRIPTION", "SIMPLE_SUBSCRIPTION_PERIOD_DAYS": "SIMPLE_SUBSCRIPTION", "SIMPLE_SUBSCRIPTION_DEVICE_LIMIT": "SIMPLE_SUBSCRIPTION", @@ -271,6 +272,10 @@ class BotConfigurationService: "NOTIFICATION_CACHE_HOURS": "NOTIFICATIONS", "MONITORING_LOGS_RETENTION_DAYS": "MONITORING", "MONITORING_INTERVAL": "MONITORING", + "TRAFFIC_MONITORING_ENABLED": "MONITORING", + "TRAFFIC_MONITORING_INTERVAL_HOURS": "MONITORING", + "TRAFFIC_MONITORED_NODES": "MONITORING", + "TRAFFIC_SNAPSHOT_TTL_HOURS": "MONITORING", "ENABLE_LOGO_MODE": "INTERFACE_BRANDING", "LOGO_FILE": "INTERFACE_BRANDING", "HIDE_SUBSCRIPTION_LINK": "INTERFACE_SUBSCRIPTION", @@ -570,6 +575,19 @@ class BotConfigurationService: "Используйте с осторожностью: средства будут списаны мгновенно, если корзина найдена." ), }, + "SHOW_ACTIVATION_PROMPT_AFTER_TOPUP": { + "description": ( + "Включает режим яркого промпта активации подписки после пополнения баланса. " + "Вместо обычного уведомления пользователь получит яркое сообщение с восклицательными знаками " + "и кнопками для активации/продления подписки или изменения количества устройств." + ), + "format": "Булево значение.", + "example": "true", + "warning": ( + "При включении пользователи будут получать только яркое уведомление без кнопок баланса и главного меню. " + "Эти кнопки появятся после выполнения действия (активация/продление/изменение устройств)." + ), + }, "SUPPORT_TICKET_SLA_MINUTES": { "description": "Лимит времени для ответа модераторов на тикет в минутах.", "format": "Целое число от 1 до 1440.", @@ -710,6 +728,58 @@ class BotConfigurationService: "warning": "Убедитесь, что конфигурация существует в панели и содержит нужные приложения.", "dependencies": "Настроенное подключение к RemnaWave API", }, + "TRAFFIC_MONITORING_ENABLED": { + "description": ( + "Включает автоматический мониторинг трафика пользователей. " + "Система отслеживает изменения трафика (дельту) и сохраняет snapshot в Redis. " + "При превышении порогов отправляются уведомления пользователям и админам." + ), + "format": "Булево значение.", + "example": "true", + "warning": ( + "Требует настроенного подключения к Redis. " + "При включении будет запущен фоновый мониторинг трафика по расписанию." + ), + "dependencies": "Redis, TRAFFIC_MONITORING_INTERVAL_HOURS, TRAFFIC_SNAPSHOT_TTL_HOURS", + }, + "TRAFFIC_MONITORING_INTERVAL_HOURS": { + "description": ( + "Интервал проверки трафика в часах. " + "Каждые N часов система проверяет трафик всех активных пользователей и сравнивает с предыдущим snapshot." + ), + "format": "Целое число часов (минимум 1).", + "example": "24", + "warning": ( + "Слишком маленький интервал может создать большую нагрузку на RemnaWave API. " + "Рекомендуется 24 часа для ежедневного мониторинга." + ), + "dependencies": "TRAFFIC_MONITORING_ENABLED", + }, + "TRAFFIC_MONITORED_NODES": { + "description": ( + "Список UUID нод для мониторинга трафика через запятую. " + "Если пусто - мониторятся все ноды. " + "Позволяет ограничить мониторинг только определенными серверами." + ), + "format": "UUID через запятую или пусто для всех нод.", + "example": "d4aa2b8c-9a36-4f31-93a2-6f07dad05fba, a1b2c3d4-5678-90ab-cdef-1234567890ab", + "warning": "UUID должны существовать в RemnaWave, иначе мониторинг не будет работать.", + "dependencies": "TRAFFIC_MONITORING_ENABLED", + }, + "TRAFFIC_SNAPSHOT_TTL_HOURS": { + "description": ( + "Время жизни (TTL) snapshot трафика в Redis в часах. " + "Snapshot используется для вычисления дельты (изменения трафика) между проверками. " + "После истечения TTL snapshot удаляется и создается новый." + ), + "format": "Целое число часов (минимум 1).", + "example": "24", + "warning": ( + "TTL должен быть >= интервала мониторинга. " + "Если TTL меньше интервала, snapshot будет удален до следующей проверки." + ), + "dependencies": "TRAFFIC_MONITORING_ENABLED, Redis", + }, } @classmethod