From c96ebea077697e0eb6f504c54ffbdda3e02f0d98 Mon Sep 17 00:00:00 2001 From: machka pasla Date: Thu, 11 Dec 2025 13:51:54 +0300 Subject: [PATCH] upd --- bot/handlers/admin/ads.py | 4 +- bot/handlers/admin/broadcast.py | 10 - bot/handlers/admin/logs_admin.py | 28 ++- bot/handlers/admin/payments.py | 43 +++-- bot/handlers/admin/promo/bulk.py | 53 ++---- bot/handlers/admin/promo/create.py | 50 ++--- bot/handlers/admin/statistics.py | 56 +++--- bot/handlers/admin/user_management.py | 177 ++++++------------ bot/handlers/inline_mode.py | 55 +----- bot/handlers/user/subscription/core.py | 34 ++-- .../user/subscription/payment_methods.py | 20 +- .../subscription/payments_subscription.py | 56 ++++-- bot/keyboards/inline/admin_keyboards.py | 36 ++-- bot/services/notification_service.py | 39 ---- config/settings.py | 2 +- locales/ru.json | 2 +- 16 files changed, 258 insertions(+), 407 deletions(-) diff --git a/bot/handlers/admin/ads.py b/bot/handlers/admin/ads.py index 33bd539..ddd6027 100644 --- a/bot/handlers/admin/ads.py +++ b/bot/handlers/admin/ads.py @@ -178,7 +178,7 @@ async def ads_delete_cancel(callback: types.CallbackQuery, settings: Settings, i camp = await ad_dal.get_campaign_by_id(session, camp_id) if not camp: - await callback.answer(_("admin_ads_not_found", default="Кампания не найдена."), show_alert=True) + await callback.answer(_("admin_ads_not_found"), show_alert=True) return try: stats = await ad_dal.get_campaign_stats(session, camp_id) @@ -224,7 +224,7 @@ async def ads_delete_confirm(callback: types.CallbackQuery, settings: Settings, existed = await ad_dal.delete_campaign(session, camp_id) if not existed: - await callback.answer(_("admin_ads_not_found", default="Кампания не найдена."), show_alert=True) + await callback.answer(_("admin_ads_not_found"), show_alert=True) return await session.commit() diff --git a/bot/handlers/admin/broadcast.py b/bot/handlers/admin/broadcast.py index be425c9..221886b 100644 --- a/bot/handlers/admin/broadcast.py +++ b/bot/handlers/admin/broadcast.py @@ -122,7 +122,6 @@ async def process_broadcast_message_handler( await message.answer( _( "admin_broadcast_invalid_html", - default="❌ Некорректный HTML в сообщении. Пожалуйста, отправьте корректный HTML (поддерживаются теги Telegram) или уберите теги.\nОшибка: {error}", error=str(e), ) ) @@ -347,15 +346,6 @@ async def confirm_broadcast_callback_handler( total_failed = failed_count + dynamic_failed return _( "broadcast_queue_result", - default=( - "🚀 Рассылка поставлена в очередь!\n" - "📤 В очередь добавлено: {sent_count}\n" - "❌ Ошибок: {failed_count}\n\n" - "📊 Статус очередей:\n" - "👥 Очередь пользователей: {user_queue_size} сообщений\n" - "📢 Очередь групп: {group_queue_size} сообщений\n\n" - "ℹ️ Сообщения будут отправлены автоматически с соблюдением лимитов Telegram." - ), sent_count=sent_count, failed_count=total_failed, user_queue_size=stats["user_queue_size"], diff --git a/bot/handlers/admin/logs_admin.py b/bot/handlers/admin/logs_admin.py index f12034b..7fa37cb 100644 --- a/bot/handlers/admin/logs_admin.py +++ b/bot/handlers/admin/logs_admin.py @@ -338,8 +338,7 @@ async def export_logs_csv_handler(callback: types.CallbackQuery, _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) await callback.answer(_( - "admin_logs_csv_export_started", - default="🔄 Начинаю экспорт логов в CSV..." + "admin_logs_csv_export_started" )) try: @@ -349,8 +348,7 @@ async def export_logs_csv_handler(callback: types.CallbackQuery, if not logs_models: await callback.message.answer(_( - "admin_logs_csv_no_data", - default="❌ Нет данных для экспорта" + "admin_logs_csv_no_data" )) return @@ -360,16 +358,16 @@ async def export_logs_csv_handler(callback: types.CallbackQuery, # Write header headers = [ - _("admin_csv_header_log_id", default="Log ID"), - _("admin_csv_header_timestamp", default="Timestamp"), - _("admin_csv_header_user_id", default="User ID"), - _("admin_csv_header_telegram_username", default="Telegram Username"), - _("admin_csv_header_telegram_first_name", default="Telegram First Name"), - _("admin_csv_header_event_type", default="Event Type"), - _("admin_csv_header_content", default="Content"), - _("admin_csv_header_is_admin_event", default="Is Admin Event"), - _("admin_csv_header_target_user_id", default="Target User ID"), - _("admin_csv_header_raw_update_preview", default="Raw Update Preview") + _("admin_csv_header_log_id"), + _("admin_csv_header_timestamp"), + _("admin_csv_header_user_id"), + _("admin_csv_header_telegram_username"), + _("admin_csv_header_telegram_first_name"), + _("admin_csv_header_event_type"), + _("admin_csv_header_content"), + _("admin_csv_header_is_admin_event"), + _("admin_csv_header_target_user_id"), + _("admin_csv_header_raw_update_preview") ] csv_writer.writerow(headers) @@ -414,7 +412,6 @@ async def export_logs_csv_handler(callback: types.CallbackQuery, csv_file, caption=_( "admin_logs_csv_export_success", - default="✅ Экспорт логов завершен!\n\n📊 Записей: {count}\n📅 Дата экспорта: {date}", count=len(logs_models), date=now.strftime('%Y-%m-%d %H:%M:%S') ) @@ -424,6 +421,5 @@ async def export_logs_csv_handler(callback: types.CallbackQuery, logging.error(f"Error exporting logs to CSV: {e}", exc_info=True) await callback.message.answer(_( "admin_logs_csv_export_failed", - default="❌ Ошибка при экспорте логов: {error}", error=str(e) )) diff --git a/bot/handlers/admin/payments.py b/bot/handlers/admin/payments.py index 9f29d8c..f7ba8eb 100644 --- a/bot/handlers/admin/payments.py +++ b/bot/handlers/admin/payments.py @@ -71,9 +71,9 @@ def format_payment_text(payment: Payment, i18n: JsonI18n, lang: str, settings: S if traffic_mode: traffic_val = payment.subscription_duration_months or 0 traffic_display = str(int(traffic_val)) if float(traffic_val).is_integer() else f"{traffic_val:g}" - period_line = _("admin_payment_traffic_label", default="🗂 Трафик: {traffic_gb} GB", traffic_gb=traffic_display) + period_line = _("admin_payment_traffic_label", traffic_gb=traffic_display) else: - period_line = _("admin_payment_months_label", default="📅 Период: {months} мес.", months=payment.subscription_duration_months or 0) + period_line = _("admin_payment_months_label", months=payment.subscription_duration_months or 0) return ( f"{status_emoji} {payment.amount} {payment.currency}\n" @@ -102,7 +102,7 @@ async def view_payments_handler(callback: types.CallbackQuery, i18n_data: dict, if not payments and page == 0: await callback.message.edit_text( - _("admin_no_payments_found", default="Платежи не найдены."), + _("admin_no_payments_found"), reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n), parse_mode="HTML" ) @@ -110,7 +110,7 @@ async def view_payments_handler(callback: types.CallbackQuery, i18n_data: dict, return # Format payments text - text_parts = [_("admin_payments_header", default="💰 Все платежи")] + text_parts = [_("admin_payments_header")] text_parts.append(_("admin_payments_pagination_info", shown=len(payments), total=total_count, @@ -140,11 +140,11 @@ async def view_payments_handler(callback: types.CallbackQuery, i18n_data: dict, # Export and refresh buttons builder.row( InlineKeyboardButton( - text=_("admin_export_payments_csv", default="📊 Экспорт CSV"), + text=_("admin_export_payments_csv"), callback_data="payments_export_csv" ), InlineKeyboardButton( - text=_("admin_refresh_payments", default="🔄 Обновить"), + text=_("admin_refresh_payments"), callback_data=f"payments_page:{page}" ) ) @@ -191,7 +191,7 @@ async def export_payments_csv_handler(callback: types.CallbackQuery, i18n_data: if not all_payments: await callback.answer( - _("admin_no_payments_to_export", default="Нет платежей для экспорта."), + _("admin_no_payments_to_export"), show_alert=True ) return @@ -202,18 +202,18 @@ async def export_payments_csv_handler(callback: types.CallbackQuery, i18n_data: # Write header writer.writerow([ - _("admin_csv_payment_id", default="ID"), - _("admin_csv_user_id", default="User ID"), - _("admin_csv_username", default="Username"), - _("admin_csv_first_name", default="First Name"), - _("admin_csv_amount", default="Amount"), - _("admin_csv_currency", default="Currency"), - _("admin_csv_provider", default="Provider"), - _("admin_csv_status", default="Status"), - _("admin_csv_description", default="Description"), - _("admin_csv_units", default="Months/GB"), - _("admin_csv_created_at", default="Created At"), - _("admin_csv_provider_payment_id", default="Provider Payment ID") + _("admin_csv_payment_id"), + _("admin_csv_user_id"), + _("admin_csv_username"), + _("admin_csv_first_name"), + _("admin_csv_amount"), + _("admin_csv_currency"), + _("admin_csv_provider"), + _("admin_csv_status"), + _("admin_csv_description"), + _("admin_csv_units"), + _("admin_csv_created_at"), + _("admin_csv_provider_payment_id") ]) traffic_mode = getattr(settings, "traffic_sale_mode", False) @@ -255,13 +255,12 @@ async def export_payments_csv_handler(callback: types.CallbackQuery, i18n_data: await callback.message.reply_document( document=file, - caption=_("admin_payments_export_success", - default="📊 Payments export completed!\nTotal records: {count}", + caption=_("admin_payments_export_success", count=len(all_payments)) ) await callback.answer( - _("admin_export_sent", default="File sent!"), + _("admin_export_sent"), show_alert=False ) diff --git a/bot/handlers/admin/promo/bulk.py b/bot/handlers/admin/promo/bulk.py index 1b6b302..7013275 100644 --- a/bot/handlers/admin/promo/bulk.py +++ b/bot/handlers/admin/promo/bulk.py @@ -34,8 +34,7 @@ async def create_bulk_promo_prompt_handler(callback: types.CallbackQuery, # Step 1: Ask for quantity prompt_text = _( - "admin_bulk_promo_step1_quantity", - default="🎟 Массовое создание промокодов\n\nШаг 1 из 4: Количество\n\nВведите количество промокодов для создания (1-100):" + "admin_bulk_promo_step1_quantity" ) try: @@ -77,8 +76,7 @@ async def process_bulk_promo_quantity_handler(message: types.Message, quantity = int(message.text.strip()) if not (1 <= quantity <= 100): await message.answer(_( - "admin_bulk_promo_invalid_quantity", - default="❌ Количество промокодов должно быть от 1 до 100" + "admin_bulk_promo_invalid_quantity" )) return @@ -87,7 +85,6 @@ async def process_bulk_promo_quantity_handler(message: types.Message, # Step 2: Ask for bonus days prompt_text = _( "admin_bulk_promo_step2_bonus_days", - default="🎟 Массовое создание промокодов\n\nШаг 2 из 4: Бонусные дни\n\nКоличество: {quantity}\n\nВведите количество бонусных дней для каждого промокода (1-365):", quantity=quantity ) @@ -100,8 +97,7 @@ async def process_bulk_promo_quantity_handler(message: types.Message, except ValueError: await message.answer(_( - "admin_promo_invalid_number", - default="❌ Введите корректное число" + "admin_promo_invalid_number" )) except Exception as e: logging.error(f"Error processing bulk promo quantity: {e}") @@ -125,8 +121,7 @@ async def process_bulk_promo_bonus_days_handler(message: types.Message, bonus_days = int(message.text.strip()) if not (1 <= bonus_days <= 365): await message.answer(_( - "admin_promo_invalid_bonus_days", - default="❌ Количество бонусных дней должно быть от 1 до 365" + "admin_promo_invalid_bonus_days" )) return @@ -136,7 +131,6 @@ async def process_bulk_promo_bonus_days_handler(message: types.Message, data = await state.get_data() prompt_text = _( "admin_bulk_promo_step3_max_activations", - default="🎟 Массовое создание промокодов\n\nШаг 3 из 4: Лимит активаций\n\nКоличество: {quantity}\nБонусные дни: {bonus_days}\n\nВведите максимальное количество активаций для каждого промокода (1-10000):", quantity=data.get("quantity"), bonus_days=bonus_days ) @@ -150,8 +144,7 @@ async def process_bulk_promo_bonus_days_handler(message: types.Message, except ValueError: await message.answer(_( - "admin_promo_invalid_number", - default="❌ Введите корректное число" + "admin_promo_invalid_number" )) except Exception as e: logging.error(f"Error processing bulk promo bonus days: {e}") @@ -175,8 +168,7 @@ async def process_bulk_promo_max_activations_handler(message: types.Message, max_activations = int(message.text.strip()) if not (1 <= max_activations <= 10000): await message.answer(_( - "admin_promo_invalid_max_activations", - default="❌ Максимальное количество активаций должно быть от 1 до 10000" + "admin_promo_invalid_max_activations" )) return @@ -186,7 +178,6 @@ async def process_bulk_promo_max_activations_handler(message: types.Message, data = await state.get_data() prompt_text = _( "admin_bulk_promo_step4_validity", - default="🎟 Массовое создание промокодов\n\nШаг 4 из 4: Срок действия\n\nКоличество: {quantity}\nБонусные дни: {bonus_days}\nМакс. активаций: {max_activations}\n\nВыберите срок действия промокодов:", quantity=data.get("quantity"), bonus_days=data.get("bonus_days"), max_activations=max_activations @@ -196,19 +187,19 @@ async def process_bulk_promo_max_activations_handler(message: types.Message, builder = InlineKeyboardBuilder() builder.row( InlineKeyboardButton( - text=_("admin_promo_unlimited_validity", default="🔄 Без ограничений"), + text=_("admin_promo_unlimited_validity"), callback_data="bulk_promo_unlimited_validity" ) ) builder.row( InlineKeyboardButton( - text=_("admin_promo_set_validity_days", default="📅 Указать дни"), + text=_("admin_promo_set_validity_days"), callback_data="bulk_promo_set_validity" ) ) builder.row( InlineKeyboardButton( - text=_("admin_back_to_panel", default="🔙 В админ панель"), + text=_("admin_back_to_panel"), callback_data="admin_action:main" ) ) @@ -222,8 +213,7 @@ async def process_bulk_promo_max_activations_handler(message: types.Message, except ValueError: await message.answer(_( - "admin_promo_invalid_number", - default="❌ Введите корректное число" + "admin_promo_invalid_number" )) except Exception as e: logging.error(f"Error processing bulk promo max activations: {e}") @@ -257,7 +247,6 @@ async def process_bulk_promo_set_validity(callback: types.CallbackQuery, data = await state.get_data() prompt_text = _( "admin_bulk_promo_enter_validity_days", - default="🎟 Массовое создание промокодов\n\nШаг 4 из 4: Срок действия\n\nКоличество: {quantity}\nБонусные дни: {bonus_days}\nМакс. активаций: {max_activations}\n\nВведите количество дней действия промокодов (1-365):", quantity=data.get("quantity"), bonus_days=data.get("bonus_days"), max_activations=data.get("max_activations") @@ -296,8 +285,7 @@ async def process_bulk_promo_validity_days_handler(message: types.Message, validity_days = int(message.text.strip()) if not (1 <= validity_days <= 365): await message.answer(_( - "admin_promo_invalid_validity_days", - default="❌ Срок действия должен быть от 1 до 365 дней" + "admin_promo_invalid_validity_days" )) return @@ -306,8 +294,7 @@ async def process_bulk_promo_validity_days_handler(message: types.Message, except ValueError: await message.answer(_( - "admin_promo_invalid_number", - default="❌ Введите корректное число" + "admin_promo_invalid_number" )) except Exception as e: logging.error(f"Error processing bulk promo validity days: {e}") @@ -333,7 +320,6 @@ async def create_bulk_promo_codes_final(callback_or_message, # Show progress message progress_text = _( "admin_bulk_promo_creating", - default="🔄 Создание {quantity} промокодов...", quantity=quantity ) @@ -395,12 +381,10 @@ async def create_bulk_promo_codes_final(callback_or_message, # Success message success_lines = [ _( - "admin_bulk_promo_created_title", - default="✅ Массовое создание завершено!\n" + "admin_bulk_promo_created_title" ), _( "admin_bulk_promo_created_stats", - default="📊 Создано: {created} из {total}", created=len(created_codes), total=quantity ) @@ -409,14 +393,11 @@ async def create_bulk_promo_codes_final(callback_or_message, if data.get("validity_days"): validity_text = f"{data['validity_days']} дней" else: - validity_text = _("admin_promo_unlimited", default="Без ограничений") + validity_text = _("admin_promo_unlimited") success_lines.append( _( "admin_bulk_promo_settings", - default="🎁 Бонусные дни: {bonus_days}\n" - "📊 Макс. активаций: {max_activations}\n" - "⏰ Срок действия: {validity}", bonus_days=data["bonus_days"], max_activations=data["max_activations"], validity=validity_text @@ -521,7 +502,7 @@ async def create_bulk_promo_codes_final(callback_or_message, except Exception as e: logging.error(f"Error creating bulk promo codes: {e}") - error_text = _("error_occurred_try_again", default="❌ Произошла ошибка. Попробуйте снова.") + error_text = _("error_occurred_try_again") if hasattr(callback_or_message, 'message'): # CallbackQuery await callback_or_message.message.answer(error_text) @@ -564,5 +545,5 @@ async def cancel_bulk_promo_creation_state_to_menu(callback: types.CallbackQuery reply_markup=get_admin_panel_keyboard(i18n, current_lang, settings) ) - await callback.answer(_("admin_bulk_promo_creation_cancelled", default="Массовое создание промокодов отменено")) - await state.clear() \ No newline at end of file + await callback.answer(_("admin_bulk_promo_creation_cancelled")) + await state.clear() diff --git a/bot/handlers/admin/promo/create.py b/bot/handlers/admin/promo/create.py index 6044913..e26cea6 100644 --- a/bot/handlers/admin/promo/create.py +++ b/bot/handlers/admin/promo/create.py @@ -30,8 +30,7 @@ async def create_promo_prompt_handler(callback: types.CallbackQuery, # Step 1: Ask for promo code prompt_text = _( - "admin_promo_step1_code", - default="🎟 Создание промокода\n\nШаг 1 из 4: Код промокода\n\nВведите код промокода (3-30 символов, только буквы и цифры):" + "admin_promo_step1_code" ) try: @@ -68,8 +67,7 @@ async def process_promo_code_handler(message: types.Message, code_str = message.text.strip().upper() if not (3 <= len(code_str) <= 30 and code_str.isalnum()): await message.answer(_( - "admin_promo_invalid_code_format", - default="❌ Код промокода должен содержать 3-30 символов (только буквы и цифры)" + "admin_promo_invalid_code_format" )) return @@ -77,8 +75,7 @@ async def process_promo_code_handler(message: types.Message, existing_promo = await promo_code_dal.get_promo_code_by_code(session, code_str) if existing_promo: await message.answer(_( - "admin_promo_code_already_exists", - default="❌ Промокод с таким кодом уже существует" + "admin_promo_code_already_exists" )) return @@ -87,7 +84,6 @@ async def process_promo_code_handler(message: types.Message, # Step 2: Ask for bonus days prompt_text = _( "admin_promo_step2_bonus_days", - default="🎟 Создание промокода\n\nШаг 2 из 4: Бонусные дни\n\nКод: {code}\n\nВведите количество бонусных дней (1-365):", code=code_str ) @@ -120,8 +116,7 @@ async def process_promo_bonus_days_handler(message: types.Message, bonus_days = int(message.text.strip()) if not (1 <= bonus_days <= 365): await message.answer(_( - "admin_promo_invalid_bonus_days", - default="❌ Количество бонусных дней должно быть от 1 до 365" + "admin_promo_invalid_bonus_days" )) return @@ -131,7 +126,6 @@ async def process_promo_bonus_days_handler(message: types.Message, data = await state.get_data() prompt_text = _( "admin_promo_step3_max_activations", - default="🎟 Создание промокода\n\nШаг 3 из 4: Лимит активаций\n\nКод: {code}\nБонусные дни: {bonus_days}\n\nВведите максимальное количество активаций (1-10000):", code=data.get("promo_code"), bonus_days=bonus_days ) @@ -145,8 +139,7 @@ async def process_promo_bonus_days_handler(message: types.Message, except ValueError: await message.answer(_( - "admin_promo_invalid_number", - default="❌ Введите корректное число" + "admin_promo_invalid_number" )) except Exception as e: logging.error(f"Error processing promo bonus days: {e}") @@ -170,8 +163,7 @@ async def process_promo_max_activations_handler(message: types.Message, max_activations = int(message.text.strip()) if not (1 <= max_activations <= 10000): await message.answer(_( - "admin_promo_invalid_max_activations", - default="❌ Максимальное количество активаций должно быть от 1 до 10000" + "admin_promo_invalid_max_activations" )) return @@ -181,7 +173,6 @@ async def process_promo_max_activations_handler(message: types.Message, data = await state.get_data() prompt_text = _( "admin_promo_step4_validity", - default="🎟 Создание промокода\n\nШаг 4 из 4: Срок действия\n\nКод: {code}\nБонусные дни: {bonus_days}\nМакс. активаций: {max_activations}\n\nВыберите срок действия промокода:", code=data.get("promo_code"), bonus_days=data.get("bonus_days"), max_activations=max_activations @@ -191,19 +182,19 @@ async def process_promo_max_activations_handler(message: types.Message, builder = InlineKeyboardBuilder() builder.row( InlineKeyboardButton( - text=_("admin_promo_unlimited_validity", default="🔄 Без ограничений"), + text=_("admin_promo_unlimited_validity"), callback_data="promo_unlimited_validity" ) ) builder.row( InlineKeyboardButton( - text=_("admin_promo_set_validity_days", default="📅 Указать дни"), + text=_("admin_promo_set_validity_days"), callback_data="promo_set_validity" ) ) builder.row( InlineKeyboardButton( - text=_("admin_back_to_panel", default="🔙 В админ панель"), + text=_("admin_back_to_panel"), callback_data="admin_action:main" ) ) @@ -217,8 +208,7 @@ async def process_promo_max_activations_handler(message: types.Message, except ValueError: await message.answer(_( - "admin_promo_invalid_number", - default="❌ Введите корректное число" + "admin_promo_invalid_number" )) except Exception as e: logging.error(f"Error processing promo max activations: {e}") @@ -252,7 +242,6 @@ async def process_promo_set_validity(callback: types.CallbackQuery, data = await state.get_data() prompt_text = _( "admin_promo_enter_validity_days", - default="🎟 Создание промокода\n\nШаг 4 из 4: Срок действия\n\nКод: {code}\nБонусные дни: {bonus_days}\nМакс. активаций: {max_activations}\n\nВведите количество дней действия промокода (1-365):", code=data.get("promo_code"), bonus_days=data.get("bonus_days"), max_activations=data.get("max_activations") @@ -291,8 +280,7 @@ async def process_promo_validity_days_handler(message: types.Message, validity_days = int(message.text.strip()) if not (1 <= validity_days <= 365): await message.answer(_( - "admin_promo_invalid_validity_days", - default="❌ Срок действия должен быть от 1 до 365 дней" + "admin_promo_invalid_validity_days" )) return @@ -301,8 +289,7 @@ async def process_promo_validity_days_handler(message: types.Message, except ValueError: await message.answer(_( - "admin_promo_invalid_number", - default="❌ Введите корректное число" + "admin_promo_invalid_number" )) except Exception as e: logging.error(f"Error processing promo validity days: {e}") @@ -349,14 +336,9 @@ async def create_promo_code_final(callback_or_message, logging.info(f"Promo code '{data['promo_code']}' created with ID {created_promo.promo_code_id}") # Success message - valid_until_str = _("admin_promo_unlimited", default="Без ограничений") if not data.get("validity_days") else f"{data['validity_days']} дней" + valid_until_str = _("admin_promo_unlimited") if not data.get("validity_days") else f"{data['validity_days']} дней" success_text = _( "admin_promo_created_success", - default="✅ Промокод успешно создан!\n\n" - "🎟 Код: {code}\n" - "🎁 Бонусные дни: {bonus_days}\n" - "📊 Макс. активаций: {max_activations}\n" - "⏰ Срок действия: {valid_until_str}", code=data["promo_code"], bonus_days=data["bonus_days"], max_activations=data["max_activations"], @@ -388,7 +370,7 @@ async def create_promo_code_final(callback_or_message, except Exception as e: logging.error(f"Error creating promo code: {e}") - error_text = _("error_occurred_try_again", default="❌ Произошла ошибка. Попробуйте снова.") + error_text = _("error_occurred_try_again") if hasattr(callback_or_message, 'message'): # CallbackQuery await callback_or_message.message.answer(error_text) @@ -432,5 +414,5 @@ async def cancel_promo_creation_state_to_menu(callback: types.CallbackQuery, reply_markup=get_admin_panel_keyboard(i18n, current_lang, settings) ) - await callback.answer(_("admin_promo_creation_cancelled", default="Создание промокода отменено")) - await state.clear() \ No newline at end of file + await callback.answer(_("admin_promo_creation_cancelled")) + await state.clear() diff --git a/bot/handlers/admin/statistics.py b/bot/handlers/admin/statistics.py index ac58d30..f136b9c 100644 --- a/bot/handlers/admin/statistics.py +++ b/bot/handlers/admin/statistics.py @@ -34,30 +34,30 @@ async def show_statistics_handler(callback: types.CallbackQuery, user_stats = await user_dal.get_enhanced_user_statistics(session) stats_text_parts.append( - f"\n👥 {_('admin_enhanced_users_stats_header', default='Пользователи')}" + f"\n👥 {_('admin_enhanced_users_stats_header')}" ) stats_text_parts.append( - f"📊 {_('admin_user_stats_total_label', default='Всего')}: {user_stats['total_users']}" + f"📊 {_('admin_user_stats_total_label')}: {user_stats['total_users']}" ) # Removed: Active today moved to panel stats stats_text_parts.append( - f"💳 {_('admin_user_stats_paid_subs_label', default='С платной подпиской')}: {user_stats['paid_subscriptions']}" + f"💳 {_('admin_user_stats_paid_subs_label')}: {user_stats['paid_subscriptions']}" ) stats_text_parts.append( - f"🆓 {_('admin_user_stats_trial_label', default='На пробном периоде')}: {user_stats['trial_users']}" + f"🆓 {_('admin_user_stats_trial_label')}: {user_stats['trial_users']}" ) stats_text_parts.append( - f"😴 {_('admin_user_stats_inactive_label', default='Неактивных')}: {user_stats['inactive_users']}" + f"😴 {_('admin_user_stats_inactive_label')}: {user_stats['inactive_users']}" ) stats_text_parts.append( - f"🚫 {_('admin_user_stats_banned_label', default='Заблокированных')}: {user_stats['banned_users']}" + f"🚫 {_('admin_user_stats_banned_label')}: {user_stats['banned_users']}" ) stats_text_parts.append( - f"🎁 {_('admin_user_stats_referral_label', default='Привлечено по реферальной программе')}: {user_stats['referral_users']}" + f"🎁 {_('admin_user_stats_referral_label')}: {user_stats['referral_users']}" ) # Panel Statistics - moved above financial - stats_text_parts.append(f"\n🖥 {_('admin_panel_stats_header', default='Статистика панели')}") + stats_text_parts.append(f"\n🖥 {_('admin_panel_stats_header')}") try: async with PanelApiService(settings) as panel_service: @@ -80,12 +80,12 @@ async def show_statistics_handler(callback: types.CallbackQuery, total_users = users.get('totalUsers', 0) online_now = online_stats.get('onlineNow', 0) - stats_text_parts.append(f"🟢 {_('admin_panel_online_label', default='Онлайн')}: {online_now}") - stats_text_parts.append(f"📊 {_('admin_panel_active_label', default='Активных')}: {active_users}") - stats_text_parts.append(f"🔴 {_('admin_panel_disabled_label', default='Отключенных')}: {disabled_users}") - stats_text_parts.append(f"⏰ {_('admin_panel_expired_label', default='Истекшие')}: {expired_users}") - stats_text_parts.append(f"⚠️ {_('admin_panel_limited_label', default='Ограниченные')}: {limited_users}") - stats_text_parts.append(f"👥 {_('admin_panel_total_users_label', default='Всего пользователей')}: {total_users}") + stats_text_parts.append(f"🟢 {_('admin_panel_online_label')}: {online_now}") + stats_text_parts.append(f"📊 {_('admin_panel_active_label')}: {active_users}") + stats_text_parts.append(f"🔴 {_('admin_panel_disabled_label')}: {disabled_users}") + stats_text_parts.append(f"⏰ {_('admin_panel_expired_label')}: {expired_users}") + stats_text_parts.append(f"⚠️ {_('admin_panel_limited_label')}: {limited_users}") + stats_text_parts.append(f"👥 {_('admin_panel_total_users_label')}: {total_users}") # System resources memory = system_stats.get('memory', {}) @@ -93,9 +93,9 @@ async def show_statistics_handler(callback: types.CallbackQuery, memory_total = memory.get('total', 1) memory_used = memory.get('used', 0) memory_usage = (memory_used / memory_total) * 100 if memory_total > 0 else 0 - stats_text_parts.append(f"💾 {_('admin_panel_memory_usage_label', default='Использование RAM')}: {memory_usage:.1f}%") + stats_text_parts.append(f"💾 {_('admin_panel_memory_usage_label')}: {memory_usage:.1f}%") else: - stats_text_parts.append(f"⚠️ {_('admin_panel_system_stats_error', default='Ошибка получения системной статистики')}") + stats_text_parts.append(f"⚠️ {_('admin_panel_system_stats_error')}") # Bandwidth stats if bandwidth_stats: @@ -107,13 +107,13 @@ async def show_statistics_handler(callback: types.CallbackQuery, if week_traffic: week_total = week_traffic.get('current', '0 B') - stats_text_parts.append(f"📊 {_('admin_panel_traffic_week_label', default='Трафик за неделю')}: {week_total}") + stats_text_parts.append(f"📊 {_('admin_panel_traffic_week_label')}: {week_total}") if month_traffic: month_total = month_traffic.get('current', '0 B') - stats_text_parts.append(f"📊 {_('admin_panel_traffic_month_label', default='Трафик за месяц')}: {month_total}") + stats_text_parts.append(f"📊 {_('admin_panel_traffic_month_label')}: {month_total}") else: - stats_text_parts.append(f"⚠️ {_('admin_panel_bandwidth_stats_error', default='Ошибка получения статистики трафика')}") + stats_text_parts.append(f"⚠️ {_('admin_panel_bandwidth_stats_error')}") # Nodes stats if nodes_stats and 'lastSevenDays' in nodes_stats: @@ -124,35 +124,35 @@ async def show_statistics_handler(callback: types.CallbackQuery, unique_nodes.add(node_data.get('nodeName', '')) total_nodes_count = len(unique_nodes) # Assume all nodes are active since we don't have status info - stats_text_parts.append(f"🔗 {_('admin_panel_nodes_label', default='Активных нод')}: {total_nodes_count}/{total_nodes_count}") + stats_text_parts.append(f"🔗 {_('admin_panel_nodes_label')}: {total_nodes_count}/{total_nodes_count}") else: # Use nodes total from system stats as fallback nodes_info = system_stats.get('nodes', {}) if system_stats else {} total_online = nodes_info.get('totalOnline', 0) - stats_text_parts.append(f"🔗 {_('admin_panel_nodes_label', default='Активных нод')}: {total_online}") + stats_text_parts.append(f"🔗 {_('admin_panel_nodes_label')}: {total_online}") except Exception as e: logging.error(f"Failed to fetch panel statistics: {e}", exc_info=True) - stats_text_parts.append(f"❌ {_('admin_panel_stats_fetch_error', default='Ошибка получения данных с панели')}") - stats_text_parts.append(f"⚠️ {_('admin_panel_stats_error_details', default='Детали')}: {str(e)}") + stats_text_parts.append(f"❌ {_('admin_panel_stats_fetch_error')}") + stats_text_parts.append(f"⚠️ {_('admin_panel_stats_error_details')}: {str(e)}") # Financial statistics financial_stats = await payment_dal.get_financial_statistics(session) stats_text_parts.append( - f"\n💰 {_('admin_financial_stats_header', default='Финансовая статистика')}" + f"\n💰 {_('admin_financial_stats_header')}" ) stats_text_parts.append( - f"📅 {_('admin_financial_today_label', default='За сегодня')}: {financial_stats['today_revenue']:.2f} RUB ({financial_stats['today_payments_count']} {_('admin_financial_payments_label', default='платежей')})" + f"📅 {_('admin_financial_today_label')}: {financial_stats['today_revenue']:.2f} RUB ({financial_stats['today_payments_count']} {_('admin_financial_payments_label')})" ) stats_text_parts.append( - f"📅 {_('admin_financial_week_label', default='За неделю')}: {financial_stats['week_revenue']:.2f} RUB" + f"📅 {_('admin_financial_week_label')}: {financial_stats['week_revenue']:.2f} RUB" ) stats_text_parts.append( - f"📅 {_('admin_financial_month_label', default='За месяц')}: {financial_stats['month_revenue']:.2f} RUB" + f"📅 {_('admin_financial_month_label')}: {financial_stats['month_revenue']:.2f} RUB" ) stats_text_parts.append( - f"🏆 {_('admin_financial_all_time_label', default='За все время')}: {financial_stats['all_time_revenue']:.2f} RUB" + f"🏆 {_('admin_financial_all_time_label')}: {financial_stats['all_time_revenue']:.2f} RUB" ) last_payments_models: List[ diff --git a/bot/handlers/admin/user_management.py b/bot/handlers/admin/user_management.py index 07f092d..4ecf6e1 100644 --- a/bot/handlers/admin/user_management.py +++ b/bot/handlers/admin/user_management.py @@ -56,7 +56,6 @@ async def users_list_handler(callback: types.CallbackQuery, # Format message header_text = _( "admin_users_list_header", - default="👥 Список пользователей\n\nСтраница {current}/{total} ({total_users} пользователей)", current=page + 1, total=total_pages, total_users=total_users @@ -88,8 +87,7 @@ async def user_search_prompt_handler(callback: types.CallbackQuery, _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) prompt_text = _( - "admin_user_management_prompt", - default="👤 Управление пользователями\n\nВведите ID пользователя или @username для поиска:" + "admin_user_management_prompt" ) try: @@ -116,56 +114,54 @@ def get_user_card_keyboard(user_id: int, i18n_instance, lang: str, # Row 1: Trial and Subscription actions builder.button( - text=_(key="admin_user_reset_trial_button", default="🔄 Сбросить триал"), + text=_(key="admin_user_reset_trial_button"), callback_data=f"user_action:reset_trial:{user_id}" ) builder.button( - text=_(key="admin_user_add_subscription_button", default="➕ Добавить дни"), + text=_(key="admin_user_add_subscription_button"), callback_data=f"user_action:add_subscription:{user_id}" ) # Row 2: Block/Unblock and Message builder.button( - text=_(key="admin_user_toggle_ban_button", default="🚫 Заблокировать/Разблокировать"), + text=_(key="admin_user_toggle_ban_button"), callback_data=f"user_action:toggle_ban:{user_id}" ) builder.button( - text=_(key="admin_user_send_message_button", default="✉️ Отправить сообщение"), + text=_(key="admin_user_send_message_button"), callback_data=f"user_action:send_message:{user_id}" ) # Row 3: View actions builder.button( - text=_(key="admin_user_view_logs_button", default="📜 Действия пользователя"), + text=_(key="admin_user_view_logs_button"), callback_data=f"user_action:view_logs:{user_id}" ) builder.button( - text=_(key="admin_user_refresh_button", default="🔄 Обновить"), + text=_(key="admin_user_refresh_button"), callback_data=f"user_action:refresh:{user_id}" ) # Row 4: Quick links builder.button( - text=_(key="user_card_open_profile_button", - default="👤 Открыть профиль"), + text=_(key="user_card_open_profile_button"), url=f"tg://user?id={user_id}" ) if referrer_id: builder.button( - text=_(key="user_card_open_referrer_profile_button", - default="👤 Открыть профиль пригласившего"), + text=_(key="user_card_open_referrer_profile_button"), url=f"tg://user?id={referrer_id}" ) # Row 5: Destructive action builder.button( - text=_(key="admin_user_delete_button", default="❌ Удалить пользователя"), + text=_(key="admin_user_delete_button"), callback_data=f"user_action:delete_user:{user_id}" ) # Row 6: Navigation builder.button( - text=_(key="admin_user_search_new_button", default="🔍 Найти другого"), + text=_(key="admin_user_search_new_button"), callback_data="admin_action:users_management" ) builder.button( @@ -215,10 +211,10 @@ async def format_user_card(user: User, session: AsyncSession, # Basic user info card_parts = [] - card_parts.append(f"👤 {_('admin_user_card_title', default='Карточка пользователя')}\n") + card_parts.append(f"👤 {_('admin_user_card_title')}\n") # User details - na_value = _("admin_user_na_value", default="N/A") + na_value = _("admin_user_na_value") safe_first_name = sanitize_display_name(user.first_name) if user.first_name else None user_name = safe_first_name or na_value if user.username: @@ -231,23 +227,23 @@ async def format_user_card(user: User, session: AsyncSession, username_display = na_value registration_date = user.registration_date.strftime('%Y-%m-%d %H:%M') if user.registration_date else na_value - card_parts.append(f"{_('admin_user_id_label', default='🆔 ID:')} {hcode(str(user.user_id))}") - card_parts.append(f"{_('admin_user_name_label', default='👤 Имя:')} {hcode(user_name)}") - card_parts.append(f"{_('admin_user_username_label', default='📱 Username:')} {hcode(username_display)}") - card_parts.append(f"{_('admin_user_language_label', default='🌍 Язык:')} {hcode(user.language_code or na_value)}") - card_parts.append(f"{_('admin_user_registration_label', default='📅 Регистрация:')} {hcode(registration_date)}") + card_parts.append(f"{_('admin_user_id_label')} {hcode(str(user.user_id))}") + card_parts.append(f"{_('admin_user_name_label')} {hcode(user_name)}") + card_parts.append(f"{_('admin_user_username_label')} {hcode(username_display)}") + card_parts.append(f"{_('admin_user_language_label')} {hcode(user.language_code or na_value)}") + card_parts.append(f"{_('admin_user_registration_label')} {hcode(registration_date)}") # Ban status - ban_status = _("admin_user_status_banned", default="🚫 Заблокирован") if user.is_banned else _("admin_user_status_active", default="✅ Активен") - card_parts.append(f"{_('admin_user_status_label', default='🛡 Статус:')} {ban_status}") + ban_status = _("admin_user_status_banned") if user.is_banned else _("admin_user_status_active") + card_parts.append(f"{_('admin_user_status_label')} {ban_status}") # Referral info if user.referred_by_id: - card_parts.append(f"{_('admin_user_referral_label', default='🎁 Привлечен по реферальной программе от:')} {hcode(str(user.referred_by_id))}") + card_parts.append(f"{_('admin_user_referral_label')} {hcode(str(user.referred_by_id))}") # Panel info if user.panel_user_uuid: - card_parts.append(f"{_('admin_user_panel_uuid_label', default='🔗 Panel UUID:')} {hcode(user.panel_user_uuid[:8] + '...' if len(user.panel_user_uuid) > 8 else user.panel_user_uuid)}") + card_parts.append(f"{_('admin_user_panel_uuid_label')} {hcode(user.panel_user_uuid[:8] + '...' if len(user.panel_user_uuid) > 8 else user.panel_user_uuid)}") card_parts.append("") # Empty line @@ -255,38 +251,38 @@ async def format_user_card(user: User, session: AsyncSession, try: subscription_details = await subscription_service.get_active_subscription_details(session, user.user_id) if subscription_details: - card_parts.append(f"💳 {_('admin_user_subscription_info', default='Информация о подписке:')}") + card_parts.append(f"💳 {_('admin_user_subscription_info')}") end_date = subscription_details.get('end_date') if end_date: end_date_str = end_date.strftime('%Y-%m-%d %H:%M') if isinstance(end_date, datetime) else str(end_date) - card_parts.append(f"{_('admin_user_subscription_active_until', default='⏰ Действует до:')} {hcode(end_date_str)}") + card_parts.append(f"{_('admin_user_subscription_active_until')} {hcode(end_date_str)}") status = subscription_details.get('status_from_panel', 'UNKNOWN') - card_parts.append(f"{_('admin_user_panel_status_label', default='📊 Статус на панели:')} {hcode(status)}") + card_parts.append(f"{_('admin_user_panel_status_label')} {hcode(status)}") traffic_limit = subscription_details.get('traffic_limit_bytes') traffic_used = subscription_details.get('traffic_used_bytes') if traffic_limit and traffic_used is not None: traffic_limit_gb = traffic_limit / (1024**3) traffic_used_gb = traffic_used / (1024**3) - card_parts.append(f"{_('admin_user_traffic_label', default='📊 Трафик:')} {hcode(f'{traffic_used_gb:.2f}GB / {traffic_limit_gb:.2f}GB')}") + card_parts.append(f"{_('admin_user_traffic_label')} {hcode(f'{traffic_used_gb:.2f}GB / {traffic_limit_gb:.2f}GB')}") else: - card_parts.append(f"{_('admin_user_subscription_label', default='💼 Подписка:')} {hcode(_('admin_user_subscription_none', default='Нет активной подписки'))}") + card_parts.append(f"{_('admin_user_subscription_label')} {hcode(_('admin_user_subscription_none'))}") except Exception as e: logging.error(f"Error getting subscription details for user {user.user_id}: {e}") - card_parts.append(f"{_('admin_user_subscription_label', default='💼 Подписка:')} {hcode(_('admin_user_subscription_error', default='Ошибка загрузки'))}") + card_parts.append(f"{_('admin_user_subscription_label')} {hcode(_('admin_user_subscription_error'))}") # Statistics try: # Count user logs logs_count = await message_log_dal.count_user_message_logs(session, user.user_id) - card_parts.append(f"{_('admin_user_actions_count_label', default='📜 Всего действий:')} {hcode(str(logs_count))}") + card_parts.append(f"{_('admin_user_actions_count_label')} {hcode(str(logs_count))}") # Check if user had any subscriptions had_subscriptions = await subscription_service.has_had_any_subscription(session, user.user_id) - trial_status = _("admin_user_trial_used", default="Использовал") if had_subscriptions else _("admin_user_trial_not_used", default="Не использовал") - card_parts.append(f"{_('admin_user_trial_label', default='🏡 Триал:')} {hcode(trial_status)}") + trial_status = _("admin_user_trial_used") if had_subscriptions else _("admin_user_trial_not_used") + card_parts.append(f"{_('admin_user_trial_label')} {hcode(trial_status)}") # Financial analytics (admin-only) try: @@ -294,11 +290,11 @@ async def format_user_card(user: User, session: AsyncSession, # Total amount paid by this user total_paid = await payment_dal.get_user_total_paid(session, user.user_id) - card_parts.append(f"{_('admin_user_total_paid_label', default='💰 Всего оплачено:')} {hcode(f'{total_paid:.2f} RUB')}") + card_parts.append(f"{_('admin_user_total_paid_label')} {hcode(f'{total_paid:.2f} RUB')}") # Total revenue from referrals referral_revenue = await payment_dal.get_referral_revenue(session, user.user_id) - card_parts.append(f"{_('admin_user_referral_revenue_label', default='💸 Доход по рефералам:')} {hcode(f'{referral_revenue:.2f} RUB')}") + card_parts.append(f"{_('admin_user_referral_revenue_label')} {hcode(f'{referral_revenue:.2f} RUB')}") except Exception as e_fin: logging.error(f"Failed to build financial analytics for admin card {user.user_id}: {e_fin}") @@ -308,8 +304,8 @@ async def format_user_card(user: User, session: AsyncSession, stats = await referral_service.get_referral_stats(session, user.user_id) invited_count = stats.get('invited_count', 0) purchased_count = stats.get('purchased_count', 0) - card_parts.append(f"{_('admin_user_invited_friends_label', default='👥 Приглашено друзей:')} {hcode(str(invited_count))}") - card_parts.append(f"{_('admin_user_ref_purchased_label', default='💳 Купили подписку:')} {hcode(str(purchased_count))}") + card_parts.append(f"{_('admin_user_invited_friends_label')} {hcode(str(invited_count))}") + card_parts.append(f"{_('admin_user_ref_purchased_label')} {hcode(str(purchased_count))}") except Exception as e_rs: logging.error(f"Failed to build referral stats for admin card {user.user_id}: {e_rs}") @@ -349,7 +345,6 @@ async def process_user_search_handler(message: types.Message, state: FSMContext, if not user_model: await message.answer(_( "admin_user_not_found", - default="❌ Пользователь не найден: {input}", input=hcode(input_text) )) return @@ -379,8 +374,7 @@ async def process_user_search_handler(message: types.Message, state: FSMContext, except Exception as e: logging.error(f"Error displaying user card for {user_model.user_id}: {e}") await message.answer(_( - "admin_user_card_error", - default="❌ Ошибка отображения карточки пользователя" + "admin_user_card_error" )) @@ -410,8 +404,7 @@ async def user_action_handler(callback: types.CallbackQuery, state: FSMContext, user = await user_dal.get_user_by_id(session, user_id) if not user: await callback.answer(_( - "admin_user_not_found_action", - default="Пользователь не найден" + "admin_user_not_found_action" ), show_alert=True) return @@ -447,8 +440,7 @@ async def handle_reset_trial(callback: types.CallbackQuery, user: User, await session.commit() await callback.answer(_( - "admin_user_trial_reset_success", - default="✅ Триал сброшен! Пользователь может активировать триал заново." + "admin_user_trial_reset_success" ), show_alert=True) # Refresh user card @@ -458,8 +450,7 @@ async def handle_reset_trial(callback: types.CallbackQuery, user: User, logging.error(f"Error resetting trial for user {user.user_id}: {e}") await session.rollback() await callback.answer(_( - "admin_user_trial_reset_error", - default="❌ Ошибка сброса триала" + "admin_user_trial_reset_error" ), show_alert=True) @@ -473,7 +464,6 @@ async def handle_add_subscription_prompt(callback: types.CallbackQuery, state: F prompt_text = _( "admin_user_add_subscription_prompt", - default="➕ Добавление дней подписки для пользователя {user_id}\n\nВведите количество дней для добавления:", user_id=user.user_id ) @@ -504,10 +494,9 @@ async def handle_toggle_ban(callback: types.CallbackQuery, user: User, await session.commit() - status_text = _("admin_user_ban_action_banned", default="заблокирован") if new_ban_status else _("admin_user_ban_action_unbanned", default="разблокирован") + status_text = _("admin_user_ban_action_banned") if new_ban_status else _("admin_user_ban_action_unbanned") await callback.answer(_( "admin_user_ban_toggle_success", - default="✅ Пользователь {status}", status=status_text ), show_alert=True) @@ -524,8 +513,7 @@ async def handle_toggle_ban(callback: types.CallbackQuery, user: User, logging.error(f"Error toggling ban for user {user.user_id}: {e}") await session.rollback() await callback.answer(_( - "admin_user_ban_toggle_error", - default="❌ Ошибка изменения статуса блокировки" + "admin_user_ban_toggle_error" ), show_alert=True) @@ -539,7 +527,6 @@ async def handle_send_message_prompt(callback: types.CallbackQuery, state: FSMCo prompt_text = _( "admin_user_send_message_prompt", - default="✉️ Отправка сообщения пользователю {user_id}\n\nВведите текст сообщения:", user_id=user.user_id ) @@ -563,13 +550,12 @@ async def handle_view_user_logs(callback: types.CallbackQuery, user: User, if not logs: await callback.answer(_( - "admin_user_no_logs", - default="📜 У пользователя нет действий" + "admin_user_no_logs" ), show_alert=True) return logs_text_parts = [ - f"{_('admin_user_recent_actions_title', default='📜 Последние действия пользователя {user_id}:', user_id=user.user_id)}\n" + f"{_('admin_user_recent_actions_title', user_id=user.user_id)}\n" ] for log in logs: @@ -587,11 +573,11 @@ async def handle_view_user_logs(callback: types.CallbackQuery, user: User, # Create inline keyboard for full logs builder = InlineKeyboardBuilder() builder.button( - text=_(key="admin_user_view_all_logs_button", default="📋 Все действия"), + text=_(key="admin_user_view_all_logs_button"), callback_data=f"admin_logs:view_user:{user.user_id}:0" ) builder.button( - text=_(key="admin_user_back_to_card_button", default="🔙 К карточке"), + text=_(key="admin_user_back_to_card_button"), callback_data=f"user_action:refresh:{user.user_id}" ) builder.adjust(1) @@ -614,8 +600,7 @@ async def handle_view_user_logs(callback: types.CallbackQuery, user: User, except Exception as e: logging.error(f"Error viewing logs for user {user.user_id}: {e}") await callback.answer(_( - "admin_user_logs_error", - default="❌ Ошибка загрузки действий пользователя" + "admin_user_logs_error" ), show_alert=True) @@ -682,7 +667,6 @@ async def handle_delete_user_prompt(callback: types.CallbackQuery, state: FSMCon await callback.answer( _( "admin_user_delete_not_allowed", - default="❌ У вас нет прав для удаления пользователей.", ), show_alert=True, ) @@ -696,11 +680,6 @@ async def handle_delete_user_prompt(callback: types.CallbackQuery, state: FSMCon prompt_text = _( "admin_user_delete_confirmation_prompt", - default=( - "⚠️ Вы хотите полностью удалить пользователя {user_id}.\n\n" - "Отправьте точный Telegram ID этого пользователя, чтобы подтвердить удаление.\n" - "Любой другой ответ отменит операцию." - ), user_id=hcode(str(user.user_id)), ) @@ -771,7 +750,6 @@ async def process_delete_user_confirmation_handler(message: types.Message, await message.answer( _( "admin_user_delete_not_allowed", - default="❌ У вас нет прав для удаления пользователей.", ) ) await state.clear() @@ -783,7 +761,6 @@ async def process_delete_user_confirmation_handler(message: types.Message, await message.answer( _( "admin_user_delete_state_missing", - default="⚠️ Нет активной операции удаления. Начните заново.", ) ) await state.clear() @@ -794,7 +771,6 @@ async def process_delete_user_confirmation_handler(message: types.Message, await message.answer( _( "admin_user_delete_cancelled", - default="Операция удаления отменена по запросу.", ) ) await state.clear() @@ -804,7 +780,6 @@ async def process_delete_user_confirmation_handler(message: types.Message, await message.answer( _( "admin_user_delete_mismatch", - default="⚠️ ID не совпадает. Удаление отменено.", ) ) await state.clear() @@ -815,7 +790,6 @@ async def process_delete_user_confirmation_handler(message: types.Message, await message.answer( _( "admin_user_delete_already_removed", - default="ℹ️ Пользователь уже удален.", ) ) await state.clear() @@ -830,10 +804,6 @@ async def process_delete_user_confirmation_handler(message: types.Message, await message.answer( _( "admin_user_delete_panel_error", - default=( - "❌ Не удалось удалить пользователя на панели. " - "Операция прервана." - ), ) ) await session.rollback() @@ -847,7 +817,6 @@ async def process_delete_user_confirmation_handler(message: types.Message, await message.answer( _( "admin_user_delete_already_removed", - default="ℹ️ Пользователь уже удален.", ) ) await state.clear() @@ -859,7 +828,6 @@ async def process_delete_user_confirmation_handler(message: types.Message, await message.answer( _( "admin_user_delete_success", - default="✅ Пользователь {user_id} удален из бота и панели.", user_id=hcode(str(target_user_id)), ), parse_mode="HTML", @@ -870,7 +838,6 @@ async def process_delete_user_confirmation_handler(message: types.Message, await message.answer( _( "admin_user_delete_error", - default="❌ Не удалось завершить удаление пользователя. Попробуйте позже.", ) ) finally: @@ -903,8 +870,7 @@ async def process_subscription_days_handler(message: types.Message, state: FSMCo raise ValueError("Invalid days count") except ValueError: await message.answer(_( - "admin_user_invalid_days", - default="❌ Неверное количество дней. Введите число от 1 до 3650." + "admin_user_invalid_days" )) return @@ -918,7 +884,6 @@ async def process_subscription_days_handler(message: types.Message, state: FSMCo await session.commit() await message.answer(_( "admin_user_subscription_added_success", - default="✅ Успешно добавлено {days} дней подписки пользователю {user_id}", days=days_to_add, user_id=target_user_id )) @@ -945,16 +910,14 @@ async def process_subscription_days_handler(message: types.Message, state: FSMCo else: await session.rollback() await message.answer(_( - "admin_user_subscription_added_error", - default="❌ Ошибка добавления дней подписки" + "admin_user_subscription_added_error" )) except Exception as e: logging.error(f"Error adding subscription days for user {target_user_id}: {e}") await session.rollback() await message.answer(_( - "admin_user_subscription_added_error", - default="❌ Ошибка добавления дней подписки" + "admin_user_subscription_added_error" )) await state.clear() @@ -983,8 +946,7 @@ async def process_direct_message_handler(message: types.Message, state: FSMConte text = (message.text or message.caption or "").strip() if len(text) > 4000: await message.answer(_( - "admin_user_message_too_long", - default="❌ Сообщение слишком длинное (максимум 4000 символов)" + "admin_user_message_too_long" )) return @@ -998,16 +960,14 @@ async def process_direct_message_handler(message: types.Message, state: FSMConte # Prepare admin signature and get content admin_signature = _( - "admin_direct_message_signature", - default="\n\n---\n💬 Сообщение от администратора" + "admin_direct_message_signature" ) content = get_message_content(message) if not content.text and not content.file_id: await message.answer(_( - "admin_direct_empty_message", - default="❌ Пустое сообщение. Отправьте текст или медиа." + "admin_direct_empty_message" )) return @@ -1026,7 +986,6 @@ async def process_direct_message_handler(message: types.Message, state: FSMConte except TelegramBadRequest as e: await message.answer(_( "admin_broadcast_invalid_html", - default="❌ Некорректный HTML в сообщении. Пожалуйста, отправьте корректный HTML (поддерживаются теги Telegram) или уберите теги.\nОшибка: {error}", error=str(e), )) return @@ -1034,7 +993,6 @@ async def process_direct_message_handler(message: types.Message, state: FSMConte # Confirm to admin await message.answer(_( "admin_user_message_sent_success", - default="✅ Сообщение отправлено пользователю {user_id}", user_id=target_user_id )) @@ -1062,8 +1020,7 @@ async def process_direct_message_handler(message: types.Message, state: FSMConte except Exception as e: logging.error(f"Error sending direct message to user {target_user_id}: {e}") await message.answer(_( - "admin_user_message_sent_error", - default="❌ Ошибка отправки сообщения" + "admin_user_message_sent_error" )) await state.clear() @@ -1081,8 +1038,7 @@ async def ban_user_prompt_handler(callback: types.CallbackQuery, _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) prompt_text = _( - "admin_ban_user_prompt", - default="🚫 Блокировка пользователя\n\nВведите ID пользователя или @username для блокировки:" + "admin_ban_user_prompt" ) try: @@ -1113,8 +1069,7 @@ async def unban_user_prompt_handler(callback: types.CallbackQuery, _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) prompt_text = _( - "admin_unban_user_prompt", - default="✅ Разблокировка пользователя\n\nВведите ID пользователя или @username для разблокировки:" + "admin_unban_user_prompt" ) try: @@ -1150,8 +1105,7 @@ async def view_banned_users_handler(callback: types.CallbackQuery, if not banned_users: message_text = _( - "admin_banned_users_empty", - default="📋 Заблокированные пользователи\n\nСписок пуст" + "admin_banned_users_empty" ) else: user_list = [] @@ -1163,7 +1117,6 @@ async def view_banned_users_handler(callback: types.CallbackQuery, message_text = _( "admin_banned_users_list", - default="📋 Заблокированные пользователи ({count}):\n\n{users}", count=len(banned_users), users="\n".join(user_list) ) @@ -1208,7 +1161,6 @@ async def process_ban_user_handler(message: types.Message, state: FSMContext, if not user_model: await message.answer(_( "admin_user_not_found", - default="❌ Пользователь не найден: {input}", input=hcode(input_text) )) return @@ -1217,8 +1169,7 @@ async def process_ban_user_handler(message: types.Message, state: FSMContext, # Check if user is already banned if user_model.is_banned: await message.answer(_( - "admin_user_already_banned", - default="⚠️ Пользователь уже заблокирован" + "admin_user_already_banned" )) await state.clear() return @@ -1234,7 +1185,6 @@ async def process_ban_user_handler(message: types.Message, state: FSMContext, await message.answer(_( "admin_user_ban_success", - default="✅ Пользователь {input} заблокирован", input=hcode(input_text) )) @@ -1242,8 +1192,7 @@ async def process_ban_user_handler(message: types.Message, state: FSMContext, logging.error(f"Error banning user {user_model.user_id}: {e}") await session.rollback() await message.answer(_( - "admin_user_ban_error", - default="❌ Ошибка блокировки пользователя" + "admin_user_ban_error" )) await state.clear() @@ -1279,7 +1228,6 @@ async def process_unban_user_handler(message: types.Message, state: FSMContext, if not user_model: await message.answer(_( "admin_user_not_found", - default="❌ Пользователь не найден: {input}", input=hcode(input_text) )) return @@ -1288,8 +1236,7 @@ async def process_unban_user_handler(message: types.Message, state: FSMContext, # Check if user is not banned if not user_model.is_banned: await message.answer(_( - "admin_user_not_banned", - default="⚠️ Пользователь не заблокирован" + "admin_user_not_banned" )) await state.clear() return @@ -1305,7 +1252,6 @@ async def process_unban_user_handler(message: types.Message, state: FSMContext, await message.answer(_( "admin_user_unban_success", - default="✅ Пользователь {input} разблокирован", input=hcode(input_text) )) @@ -1313,8 +1259,7 @@ async def process_unban_user_handler(message: types.Message, state: FSMContext, logging.error(f"Error unbanning user {user_model.user_id}: {e}") await session.rollback() await message.answer(_( - "admin_user_unban_error", - default="❌ Ошибка разблокировки пользователя" + "admin_user_unban_error" )) await state.clear() @@ -1357,7 +1302,7 @@ async def user_card_from_list_handler(callback: types.CallbackQuery, user.referred_by_id ) keyboard.button( - text=_("admin_user_back_to_list_button", default="⬅️ К списку"), + text=_("admin_user_back_to_list_button"), callback_data=f"admin_action:users_list:{page}" ) quick_links_width = 2 if user.referred_by_id else 1 diff --git a/bot/handlers/inline_mode.py b/bot/handlers/inline_mode.py index 145fbfb..feb1897 100644 --- a/bot/handlers/inline_mode.py +++ b/bot/handlers/inline_mode.py @@ -103,20 +103,16 @@ async def create_referral_result( # Create message content (use same text as friend message) message_text = _( "referral_friend_message", - default="🚀 Привет! Попробуй этот VPN - быстрый, надёжный и доступный!\n\n" - "🎁 По моей ссылке тебе дадут бонусные дни к подписке!\n\n{referral_link}", referral_link=referral_link ) return InlineQueryResultArticle( id="referral_link", title=_( - "inline_referral_title", - default="🎁 Пригласить друга" + "inline_referral_title" ), description=_( - "inline_referral_description", - default="Поделиться реферальной ссылкой для получения бонусов" + "inline_referral_description" ), input_message_content=InputTextMessageContent( message_text=message_text, @@ -167,13 +163,6 @@ async def create_user_stats_result(session: AsyncSession, i18n_instance, lang: s stats_text = _( "inline_user_stats_message", - default="📊 Статистика Бота\n👥 Пользователи\n\n" - "📊 Всего: {total}\n" - "💳 С платной подпиской: {paid}\n" - "🆓 На пробном периоде: {trial}\n" - "😴 Неактивных: {inactive}\n" - "🚫 Заблокированных: {banned}\n" - "🎁 Привлечено по реферальной программе: {referral}", total=user_stats['total_users'], active_today=user_stats['active_today'], paid=user_stats['paid_subscriptions'], @@ -186,12 +175,10 @@ async def create_user_stats_result(session: AsyncSession, i18n_instance, lang: s return InlineQueryResultArticle( id="admin_user_stats", title=_( - "inline_admin_user_stats_title", - default="📊 Статистика пользователей" + "inline_admin_user_stats_title" ), description=_( "inline_user_stats_description", - default="Всего: {total}, Платных: {active}", total=user_stats['total_users'], active=user_stats['paid_subscriptions'] ), @@ -217,12 +204,6 @@ async def create_financial_stats_result(session: AsyncSession, i18n_instance, la stats_text = _( "inline_financial_stats_message", - default="💰 Финансовая статистика\n\n" - "📅 За сегодня: {today:.2f} RUB\n" - " ({today_count} платежей)\n" - "📅 За неделю: {week:.2f} RUB\n" - "📅 За месяц: {month:.2f} RUB\n" - "🏆 За все время: {all_time:.2f} RUB", today=financial_stats['today_revenue'], today_count=financial_stats['today_payments_count'], week=financial_stats['week_revenue'], @@ -233,12 +214,10 @@ async def create_financial_stats_result(session: AsyncSession, i18n_instance, la return InlineQueryResultArticle( id="admin_financial_stats", title=_( - "inline_admin_financial_stats_title", - default="💰 Финансовая статистика" + "inline_admin_financial_stats_title" ), description=_( "inline_financial_description", - default="Сегодня: {today} RUB", today=f"{financial_stats['today_revenue']:.2f}" ), input_message_content=InputTextMessageContent( @@ -311,17 +290,6 @@ async def create_system_stats_result(session: AsyncSession, i18n_instance, lang: stats_text = _( "inline_system_stats_message", - default="🖥 Статистика панели\n\n" - "🟢 Онлайн: {online}\n" - "📊 Активных: {active}\n" - "🔴 Отключенных: {disabled}\n" - "⏰ Истекшие: {expired}\n" - "⚠️ Ограниченные: {limited}\n" - "👥 Всего пользователей: {total}\n" - "💾 Использование RAM: {memory:.1f}%\n" - "📊 Трафик за неделю: {week_traffic}\n" - "📊 Трафик за месяц: {month_traffic}\n" - "🔗 Активных нод: {active_nodes}/{total_nodes}", online=online_now, active=active_users, disabled=disabled_users, @@ -335,17 +303,15 @@ async def create_system_stats_result(session: AsyncSession, i18n_instance, lang: total_nodes=total_nodes ) else: - stats_text = _("inline_panel_stats_error", default="❌ Ошибка получения данных с панели") + stats_text = _("inline_panel_stats_error") return InlineQueryResultArticle( id="admin_system_stats", title=_( - "inline_admin_system_stats_title", - default="🖥 Системная статистика" + "inline_admin_system_stats_title" ), description=_( "inline_system_description", - default="🟢 Онлайн: {online}, 📊 Активных: {active}", online=online_now, active=active_users ), @@ -359,15 +325,14 @@ async def create_system_stats_result(session: AsyncSession, i18n_instance, lang: except Exception as e: logging.error(f"Error creating system stats result: {e}") # Fallback error message - error_text = _("inline_panel_stats_error", default="❌ Ошибка получения данных с панели") + error_text = _("inline_panel_stats_error") return InlineQueryResultArticle( id="admin_system_stats", title=_( - "inline_admin_system_stats_title", - default="🖥 Системная статистика" + "inline_admin_system_stats_title" ), - description=_("inline_system_error", default="Ошибка получения данных"), + description=_("inline_system_error"), input_message_content=InputTextMessageContent( message_text=error_text, parse_mode="HTML" @@ -375,5 +340,3 @@ async def create_system_stats_result(session: AsyncSession, i18n_instance, lang: thumbnail_url=settings.INLINE_SYSTEM_STATS_THUMBNAIL_URL ) return None - - diff --git a/bot/handlers/user/subscription/core.py b/bot/handlers/user/subscription/core.py index 108bf25..d40ece5 100644 --- a/bot/handlers/user/subscription/core.py +++ b/bot/handlers/user/subscription/core.py @@ -40,17 +40,29 @@ async def display_subscription_options(event: Union[types.Message, types.Callbac return currency_symbol_val = settings.DEFAULT_CURRENCY_SYMBOL - traffic_mode = bool(getattr(settings, "traffic_sale_mode", False)) - options = settings.traffic_packages if traffic_mode else settings.subscription_options - text_content = ( - get_text("select_traffic_package") if traffic_mode else get_text("select_subscription_period") - ) if options else get_text("no_subscription_options_available") + traffic_packages = getattr(settings, "traffic_packages", {}) or {} + stars_traffic_packages = getattr(settings, "stars_traffic_packages", {}) or {} + traffic_mode = bool(getattr(settings, "traffic_sale_mode", False) or stars_traffic_packages) - reply_markup = ( - get_subscription_options_keyboard(options, currency_symbol_val, current_lang, i18n, traffic_mode=traffic_mode) - if options - else get_back_to_main_menu_markup(current_lang, i18n) - ) + if traffic_mode: + if traffic_packages: + options = traffic_packages + elif stars_traffic_packages: + options = stars_traffic_packages + currency_symbol_val = "⭐" + else: + options = {} + else: + options = settings.subscription_options + + if options: + text_content = get_text("select_traffic_package") if traffic_mode else get_text("select_subscription_period") + reply_markup = get_subscription_options_keyboard( + options, currency_symbol_val, current_lang, i18n, traffic_mode=traffic_mode + ) + else: + text_content = get_text("no_subscription_options_available") + reply_markup = get_back_to_main_menu_markup(current_lang, i18n) target_message_obj = event.message if isinstance(event, types.CallbackQuery) else event if not target_message_obj: @@ -108,7 +120,7 @@ async def my_subscription_command_handler( text = get_text("subscription_not_active") buy_button = InlineKeyboardButton( - text=get_text("menu_subscribe_inline", default="Купить"), callback_data="main_action:subscribe" + text=get_text("menu_subscribe_inline"), callback_data="main_action:subscribe" ) back_markup = get_back_to_main_menu_markup(current_lang, i18n) diff --git a/bot/handlers/user/subscription/payment_methods.py b/bot/handlers/user/subscription/payment_methods.py index 947d592..efea859 100644 --- a/bot/handlers/user/subscription/payment_methods.py +++ b/bot/handlers/user/subscription/payment_methods.py @@ -51,9 +51,9 @@ async def payment_methods_manage(callback: types.CallbackQuery, settings: Settin return get_text("payment_method_wallet_title", last4=l4) return get_text("payment_method_wallet_title", last4="****") if last4: - network_name = network or get_text("payment_network_card", default="Card") + network_name = network or get_text("payment_network_card") return get_text("payment_method_card_title", network=network_name, last4=last4) - network_name = network or get_text("payment_network_generic", default="Payment method") + network_name = network or get_text("payment_network_generic") return get_text("payment_method_generic_title", network=network_name) for m in methods: @@ -177,9 +177,9 @@ async def payment_method_delete(callback: types.CallbackQuery, settings: Setting return _("payment_method_wallet_title", last4=l4) return _("payment_method_wallet_title", last4="****") if last4: - network_name = network or _("payment_network_card", default="Card") + network_name = network or _("payment_network_card") return _("payment_method_card_title", network=network_name, last4=last4) - network_name = network or _("payment_network_generic", default="Payment method") + network_name = network or _("payment_network_generic") return _("payment_method_generic_title", network=network_name) title = _format_pm_title(m.card_network, m.card_last4) cards.append((str(m.method_id), title if not m.is_default else f"⭐ {title}")) @@ -239,9 +239,9 @@ async def payment_method_view(callback: types.CallbackQuery, settings: Settings, return _("payment_method_wallet_title", last4=l4) return _("payment_method_wallet_title", last4="****") if last4: - network_name = network or _("payment_network_card", default="Card") + network_name = network or _("payment_network_card") return _("payment_method_card_title", network=network_name, last4=last4) - network_name = network or _("payment_network_generic", default="Payment method") + network_name = network or _("payment_network_generic") return _("payment_method_generic_title", network=network_name) title = _format_pm_title(sel.card_network, sel.card_last4) @@ -307,9 +307,9 @@ async def payment_method_view(callback: types.CallbackQuery, settings: Settings, return _("payment_method_wallet_title", last4=l4) return _("payment_method_wallet_title", last4="****") if last4: - network_name = network or _("payment_network_card", default="Card") + network_name = network or _("payment_network_card") return _("payment_method_card_title", network=network_name, last4=last4) - network_name = network or _("payment_network_generic", default="Payment method") + network_name = network or _("payment_network_generic") return _("payment_method_generic_title", network=network_name) title = _format_pm_title(billing.card_network, billing.card_last4) @@ -440,9 +440,9 @@ async def payment_methods_list(callback: types.CallbackQuery, settings: Settings return get_text("payment_method_wallet_title", last4=l4) return get_text("payment_method_wallet_title", last4="****") if last4: - network_name = network or get_text("payment_network_card", default="Card") + network_name = network or get_text("payment_network_card") return get_text("payment_method_card_title", network=network_name, last4=last4) - network_name = network or get_text("payment_network_generic", default="Payment method") + network_name = network or get_text("payment_network_generic") return get_text("payment_method_generic_title", network=network_name) title = _format_pm_title(m.card_network, m.card_last4) cards.append((str(m.method_id), title if not m.is_default else f"⭐ {title}")) diff --git a/bot/handlers/user/subscription/payments_subscription.py b/bot/handlers/user/subscription/payments_subscription.py index e6145e1..7a63e31 100644 --- a/bot/handlers/user/subscription/payments_subscription.py +++ b/bot/handlers/user/subscription/payments_subscription.py @@ -29,7 +29,9 @@ async def select_subscription_period_callback_handler( pass return - traffic_mode = bool(getattr(settings, "traffic_sale_mode", False)) + traffic_packages = getattr(settings, "traffic_packages", {}) or {} + stars_traffic_packages = getattr(settings, "stars_traffic_packages", {}) or {} + traffic_mode = bool(getattr(settings, "traffic_sale_mode", False) or stars_traffic_packages) try: months = float(callback.data.split(":")[-1]) except (ValueError, IndexError): @@ -40,23 +42,47 @@ async def select_subscription_period_callback_handler( pass return - price_source = settings.traffic_packages if traffic_mode else settings.subscription_options - stars_price_source = settings.stars_traffic_packages if traffic_mode else settings.stars_subscription_options + price_source = traffic_packages if traffic_mode else settings.subscription_options + stars_price_source = stars_traffic_packages if traffic_mode else settings.stars_subscription_options price_rub = price_source.get(months) - if price_rub is None: - logging.error( - f"Price not found for option {months} using {'traffic_packages' if traffic_mode else 'subscription_options'}." - ) - try: - await callback.answer(get_text("error_try_again"), show_alert=True) - except Exception: - pass - return - - currency_symbol_val = settings.DEFAULT_CURRENCY_SYMBOL - text_content = get_text("choose_payment_method_traffic") if traffic_mode else get_text("choose_payment_method") stars_price = stars_price_source.get(months) + currency_symbol_val = settings.DEFAULT_CURRENCY_SYMBOL + + if price_rub is None: + if traffic_mode and not price_source and stars_price is not None: + currency_methods_enabled = any( + [ + settings.FREEKASSA_ENABLED, + settings.PLATEGA_ENABLED, + settings.SEVERPAY_ENABLED, + settings.YOOKASSA_ENABLED, + settings.CRYPTOPAY_ENABLED, + ] + ) + if currency_methods_enabled: + logging.error( + "Currency price missing for traffic option %s while fiat providers are enabled.", + months, + ) + try: + await callback.answer(get_text("error_try_again"), show_alert=True) + except Exception: + pass + return + price_rub = 0.0 + currency_symbol_val = "⭐" + else: + logging.error( + f"Price not found for option {months} using {'traffic_packages' if traffic_mode else 'subscription_options'}." + ) + try: + await callback.answer(get_text("error_try_again"), show_alert=True) + except Exception: + pass + return + + text_content = get_text("choose_payment_method_traffic") if traffic_mode else get_text("choose_payment_method") reply_markup = get_payment_method_keyboard( months, price_rub, diff --git a/bot/keyboards/inline/admin_keyboards.py b/bot/keyboards/inline/admin_keyboards.py index c27a3bf..1b38a66 100644 --- a/bot/keyboards/inline/admin_keyboards.py +++ b/bot/keyboards/inline/admin_keyboards.py @@ -26,7 +26,7 @@ def get_admin_panel_keyboard(i18n_instance, lang: str, callback_data="admin_section:promo_marketing") # Реклама - builder.button(text=_(key="admin_ads_section", default="📈 Реклама"), + builder.button(text=_(key="admin_ads_section"), callback_data="admin_action:ads") # Системные функции @@ -43,7 +43,7 @@ def get_stats_monitoring_keyboard(i18n_instance, lang: str) -> InlineKeyboardMar builder.button(text=_(key="admin_stats_button"), callback_data="admin_action:stats") - builder.button(text=_(key="admin_view_payments_button", default="💰 Платежи"), + builder.button(text=_(key="admin_view_payments_button"), callback_data="admin_action:view_payments") builder.button(text=_(key="admin_view_logs_menu_button"), callback_data="admin_action:view_logs_menu") @@ -125,7 +125,7 @@ def get_system_functions_keyboard(i18n_instance, lang: str) -> InlineKeyboardMar def get_ads_menu_keyboard(i18n_instance, lang: str) -> InlineKeyboardMarkup: _ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs) builder = InlineKeyboardBuilder() - builder.button(text=_(key="admin_ads_create_button", default="➕ Создать кампанию"), + builder.button(text=_(key="admin_ads_create_button"), callback_data="admin_action:ads_create") builder.button(text=_(key="back_to_admin_panel_button"), callback_data="admin_action:main") @@ -156,7 +156,7 @@ def get_ads_list_keyboard( if current_page > 0: row.append( InlineKeyboardButton( - text="⬅️ " + _("prev_page_button", default="Prev"), + text="⬅️ " + _("prev_page_button"), callback_data=f"admin_ads:page:{current_page - 1}", ) ) @@ -169,14 +169,14 @@ def get_ads_list_keyboard( if current_page < total_pages - 1: row.append( InlineKeyboardButton( - text=_("next_page_button", default="Next") + " ➡️", + text=_("next_page_button") + " ➡️", callback_data=f"admin_ads:page:{current_page + 1}", ) ) if row: builder.row(*row) - builder.button(text=_(key="admin_ads_create_button", default="➕ Создать кампанию"), + builder.button(text=_(key="admin_ads_create_button"), callback_data="admin_action:ads_create") builder.button(text=_(key="back_to_admin_panel_button"), callback_data="admin_action:main") @@ -188,9 +188,9 @@ def get_ad_card_keyboard(i18n_instance, lang: str, campaign_id: int, back_page: _ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs) builder = InlineKeyboardBuilder() # Dangerous action: Delete campaign - builder.button(text=_(key="admin_ads_delete_button", default="🗑 Удалить кампанию"), + builder.button(text=_(key="admin_ads_delete_button"), callback_data=f"admin_ads:delete:{campaign_id}:{back_page}") - builder.button(text=_(key="back_to_ads_list_button", default="⬅️ К списку"), + builder.button(text=_(key="back_to_ads_list_button"), callback_data=f"admin_ads:page:{back_page}") builder.button(text=_(key="back_to_admin_panel_button"), callback_data="admin_action:main") @@ -227,12 +227,12 @@ def get_logs_pagination_keyboard( if current_page > 0: row_buttons.append( InlineKeyboardButton( - text="⬅️ " + _("prev_page_button", default="Prev"), + text="⬅️ " + _("prev_page_button"), callback_data=f"{base_callback_data}:{current_page - 1}")) if current_page < total_pages - 1: row_buttons.append( InlineKeyboardButton( - text=_("next_page_button", default="Next") + " ➡️", + text=_("next_page_button") + " ➡️", callback_data=f"{base_callback_data}:{current_page + 1}")) if row_buttons: builder.row(*row_buttons) @@ -386,8 +386,7 @@ def get_user_card_keyboard(user_id: int, callback_data=f"admin_ban_confirm:{user_id}:{banned_list_page}") builder.button( text=_( - key="user_card_open_profile_button", - default="👤 Open profile" + key="user_card_open_profile_button" ), url=f"tg://user?id={user_id}" ) @@ -418,16 +417,13 @@ def get_broadcast_confirmation_keyboard(lang: str, # Row: target selection (all / active / inactive) target_all_label = _( - key="broadcast_target_all_button", - default="👥 Все" + key="broadcast_target_all_button" ) target_active_label = _( - key="broadcast_target_active_button", - default="✅ Активные" + key="broadcast_target_active_button" ) target_inactive_label = _( - key="broadcast_target_inactive_button", - default="⌛ Неактивные" + key="broadcast_target_inactive_button" ) # Highlight current selection with a prefix @@ -449,9 +445,9 @@ def get_broadcast_confirmation_keyboard(lang: str, builder.adjust(3) # Row: confirmation - builder.button(text=_(key="confirm_broadcast_send_button", default="🚀 Отправить"), + builder.button(text=_(key="confirm_broadcast_send_button"), callback_data="broadcast_final_action:send") - builder.button(text=_(key="cancel_broadcast_button", default="❌ Отмена"), + builder.button(text=_(key="cancel_broadcast_button"), callback_data="broadcast_final_action:cancel") builder.adjust(2) return builder.as_markup() diff --git a/bot/services/notification_service.py b/bot/services/notification_service.py index 53b4ec5..8c79903 100644 --- a/bot/services/notification_service.py +++ b/bot/services/notification_service.py @@ -52,7 +52,6 @@ class NotificationService: InlineKeyboardButton( text=translate( "log_open_profile_link", - default="👤 Открыть профиль", ), url=f"tg://user?id={user_id}", ) @@ -64,7 +63,6 @@ class NotificationService: InlineKeyboardButton( text=translate( "log_open_referrer_profile_button", - default="👤 Открыть профиль пригласившего", ), url=f"tg://user?id={referrer_id}", ) @@ -200,16 +198,11 @@ class NotificationService: referrer_link = hd.link(str(referred_by_id), f"tg://user?id={referred_by_id}") referral_text = _( "log_referral_suffix", - default=" (реферал от {referrer_link})", referrer_link=referrer_link, ) message = _( "log_new_user_registration", - default="👤 Новый пользователь\n\n" - "🆔 ID: {user_id}\n" - "👤 Имя: {user_display}{referral_text}\n" - "📅 Время: {timestamp}", user_id=user_id, user_display=user_display, referral_text=referral_text, @@ -249,12 +242,6 @@ class NotificationService: traffic_label = str(int(traffic_gb)) if float(traffic_gb).is_integer() else f"{traffic_gb:g}" message = _( "log_payment_received_traffic", - default="{provider_emoji} Получен платеж\n\n" - "👤 Пользователь: {user_display}\n" - "💰 Сумма: {amount} {currency}\n" - "🗂 Трафик: {traffic_gb} GB\n" - "🏦 Провайдер: {payment_provider}\n" - "🕐 Время: {timestamp}", provider_emoji=provider_emoji, user_display=user_display, amount=amount, @@ -266,12 +253,6 @@ class NotificationService: else: message = _( "log_payment_received", - default="{provider_emoji} Получен платеж\n\n" - "👤 Пользователь: {user_display}\n" - "💰 Сумма: {amount} {currency}\n" - "📅 Период: {months} мес.\n" - "🏦 Провайдер: {payment_provider}\n" - "🕐 Время: {timestamp}", provider_emoji=provider_emoji, user_display=user_display, amount=amount, @@ -301,11 +282,6 @@ class NotificationService: message = _( "log_promo_activation", - default="🎁 Активирован промокод\n\n" - "👤 Пользователь: {user_display}\n" - "🏷 Код: {promo_code}\n" - "🎯 Бонус: +{bonus_days} дн.\n" - "🕐 Время: {timestamp}", user_display=user_display, promo_code=promo_code, bonus_days=bonus_days, @@ -332,10 +308,6 @@ class NotificationService: message = _( "log_trial_activation", - default="🆓 Активирован триал\n\n" - "👤 Пользователь: {user_display}\n" - "⏰ Действует до: {end_date}\n" - "🕐 Время: {timestamp}", user_display=user_display, end_date=end_date.strftime("%Y-%m-%d %H:%M"), timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S") @@ -364,12 +336,6 @@ class NotificationService: message = _( "log_panel_sync", - default="{status_emoji} Синхронизация с панелью\n\n" - "📊 Статус: {status}\n" - "👥 Обработано пользователей: {users_processed}\n" - "📋 Синхронизировано подписок: {subs_synced}\n" - "🕐 Время: {timestamp}\n\n" - "📝 Детали:\n{details}", status_emoji=status_emoji, status=status, users_processed=users_processed, @@ -400,11 +366,6 @@ class NotificationService: message = _( "log_suspicious_promo", - default="⚠️ Подозрительная попытка ввода промокода\n\n" - "👤 Пользователь: {user_display}\n" - "🆔 ID: {user_id}\n" - "📝 Ввод:
{suspicious_input}
\n" - "🕐 Время: {timestamp}", user_display=hd.quote(user_display), user_id=user_id, suspicious_input=hd.quote(suspicious_input), diff --git a/config/settings.py b/config/settings.py index 535efb9..ce33b1b 100644 --- a/config/settings.py +++ b/config/settings.py @@ -423,7 +423,7 @@ class Settings(BaseSettings): @property def traffic_sale_mode(self) -> bool: """When true, the bot sells traffic packages instead of time-based subscriptions.""" - return bool(self.traffic_packages) + return bool(self.traffic_packages or self.stars_traffic_packages) def referral_bonus_inviter(self) -> Dict[int, int]: bonuses: Dict[int, int] = {} diff --git a/locales/ru.json b/locales/ru.json index 42b3e2b..de5f44d 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -9,7 +9,7 @@ "menu_activate_trial_button": "🆓 Пробный период", "menu_subscribe_inline": "🚀 Купить", "menu_my_subscription_inline": "🔐 Моя подписка", - "no_subscription_options_available": "Выдача подписки не натроена администратором бота", + "no_subscription_options_available": "Выдача подписки не настроена администратором бота", "menu_referral_inline": "🎁 Рефералы", "referral_no_bonuses_configured": "Извините, реферальная программа в данный момент отключена", "menu_apply_promo_button": "🎟 Промокод",