upd
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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)
|
||||
))
|
||||
|
||||
@@ -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="🗂 Трафик: <b>{traffic_gb} GB</b>", traffic_gb=traffic_display)
|
||||
period_line = _("admin_payment_traffic_label", traffic_gb=traffic_display)
|
||||
else:
|
||||
period_line = _("admin_payment_months_label", default="📅 Период: <b>{months} мес.</b>", months=payment.subscription_duration_months or 0)
|
||||
period_line = _("admin_payment_months_label", months=payment.subscription_duration_months or 0)
|
||||
|
||||
return (
|
||||
f"{status_emoji} <b>{payment.amount} {payment.currency}</b>\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="💰 <b>Все платежи</b>")]
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
@@ -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="🎟 <b>Массовое создание промокодов</b>\n\n<b>Шаг 1 из 4:</b> Количество\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="🎟 <b>Массовое создание промокодов</b>\n\n<b>Шаг 2 из 4:</b> Бонусные дни\n\nКоличество: <b>{quantity}</b>\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="🎟 <b>Массовое создание промокодов</b>\n\n<b>Шаг 3 из 4:</b> Лимит активаций\n\nКоличество: <b>{quantity}</b>\nБонусные дни: <b>{bonus_days}</b>\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="🎟 <b>Массовое создание промокодов</b>\n\n<b>Шаг 4 из 4:</b> Срок действия\n\nКоличество: <b>{quantity}</b>\nБонусные дни: <b>{bonus_days}</b>\nМакс. активаций: <b>{max_activations}</b>\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="🎟 <b>Массовое создание промокодов</b>\n\n<b>Шаг 4 из 4:</b> Срок действия\n\nКоличество: <b>{quantity}</b>\nБонусные дни: <b>{bonus_days}</b>\nМакс. активаций: <b>{max_activations}</b>\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="✅ <b>Массовое создание завершено!</b>\n"
|
||||
"admin_bulk_promo_created_title"
|
||||
),
|
||||
_(
|
||||
"admin_bulk_promo_created_stats",
|
||||
default="📊 Создано: <b>{created}</b> из <b>{total}</b>",
|
||||
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="🎁 Бонусные дни: <b>{bonus_days}</b>\n"
|
||||
"📊 Макс. активаций: <b>{max_activations}</b>\n"
|
||||
"⏰ Срок действия: <b>{validity}</b>",
|
||||
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()
|
||||
await callback.answer(_("admin_bulk_promo_creation_cancelled"))
|
||||
await state.clear()
|
||||
|
||||
@@ -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="🎟 <b>Создание промокода</b>\n\n<b>Шаг 1 из 4:</b> Код промокода\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="🎟 <b>Создание промокода</b>\n\n<b>Шаг 2 из 4:</b> Бонусные дни\n\nКод: <b>{code}</b>\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="🎟 <b>Создание промокода</b>\n\n<b>Шаг 3 из 4:</b> Лимит активаций\n\nКод: <b>{code}</b>\nБонусные дни: <b>{bonus_days}</b>\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="🎟 <b>Создание промокода</b>\n\n<b>Шаг 4 из 4:</b> Срок действия\n\nКод: <b>{code}</b>\nБонусные дни: <b>{bonus_days}</b>\nМакс. активаций: <b>{max_activations}</b>\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="🎟 <b>Создание промокода</b>\n\n<b>Шаг 4 из 4:</b> Срок действия\n\nКод: <b>{code}</b>\nБонусные дни: <b>{bonus_days}</b>\nМакс. активаций: <b>{max_activations}</b>\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="✅ <b>Промокод успешно создан!</b>\n\n"
|
||||
"🎟 Код: <code>{code}</code>\n"
|
||||
"🎁 Бонусные дни: <b>{bonus_days}</b>\n"
|
||||
"📊 Макс. активаций: <b>{max_activations}</b>\n"
|
||||
"⏰ Срок действия: <b>{valid_until_str}</b>",
|
||||
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()
|
||||
await callback.answer(_("admin_promo_creation_cancelled"))
|
||||
await state.clear()
|
||||
|
||||
@@ -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<b>👥 {_('admin_enhanced_users_stats_header', default='Пользователи')}</b>"
|
||||
f"\n<b>👥 {_('admin_enhanced_users_stats_header')}</b>"
|
||||
)
|
||||
stats_text_parts.append(
|
||||
f"📊 {_('admin_user_stats_total_label', default='Всего')}: <b>{user_stats['total_users']}</b>"
|
||||
f"📊 {_('admin_user_stats_total_label')}: <b>{user_stats['total_users']}</b>"
|
||||
)
|
||||
# Removed: Active today moved to panel stats
|
||||
stats_text_parts.append(
|
||||
f"💳 {_('admin_user_stats_paid_subs_label', default='С платной подпиской')}: <b>{user_stats['paid_subscriptions']}</b>"
|
||||
f"💳 {_('admin_user_stats_paid_subs_label')}: <b>{user_stats['paid_subscriptions']}</b>"
|
||||
)
|
||||
stats_text_parts.append(
|
||||
f"🆓 {_('admin_user_stats_trial_label', default='На пробном периоде')}: <b>{user_stats['trial_users']}</b>"
|
||||
f"🆓 {_('admin_user_stats_trial_label')}: <b>{user_stats['trial_users']}</b>"
|
||||
)
|
||||
stats_text_parts.append(
|
||||
f"😴 {_('admin_user_stats_inactive_label', default='Неактивных')}: <b>{user_stats['inactive_users']}</b>"
|
||||
f"😴 {_('admin_user_stats_inactive_label')}: <b>{user_stats['inactive_users']}</b>"
|
||||
)
|
||||
stats_text_parts.append(
|
||||
f"🚫 {_('admin_user_stats_banned_label', default='Заблокированных')}: <b>{user_stats['banned_users']}</b>"
|
||||
f"🚫 {_('admin_user_stats_banned_label')}: <b>{user_stats['banned_users']}</b>"
|
||||
)
|
||||
stats_text_parts.append(
|
||||
f"🎁 {_('admin_user_stats_referral_label', default='Привлечено по реферальной программе')}: <b>{user_stats['referral_users']}</b>"
|
||||
f"🎁 {_('admin_user_stats_referral_label')}: <b>{user_stats['referral_users']}</b>"
|
||||
)
|
||||
|
||||
# Panel Statistics - moved above financial
|
||||
stats_text_parts.append(f"\n<b>🖥 {_('admin_panel_stats_header', default='Статистика панели')}</b>")
|
||||
stats_text_parts.append(f"\n<b>🖥 {_('admin_panel_stats_header')}</b>")
|
||||
|
||||
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='Онлайн')}: <b>{online_now}</b>")
|
||||
stats_text_parts.append(f"📊 {_('admin_panel_active_label', default='Активных')}: <b>{active_users}</b>")
|
||||
stats_text_parts.append(f"🔴 {_('admin_panel_disabled_label', default='Отключенных')}: <b>{disabled_users}</b>")
|
||||
stats_text_parts.append(f"⏰ {_('admin_panel_expired_label', default='Истекшие')}: <b>{expired_users}</b>")
|
||||
stats_text_parts.append(f"⚠️ {_('admin_panel_limited_label', default='Ограниченные')}: <b>{limited_users}</b>")
|
||||
stats_text_parts.append(f"👥 {_('admin_panel_total_users_label', default='Всего пользователей')}: <b>{total_users}</b>")
|
||||
stats_text_parts.append(f"🟢 {_('admin_panel_online_label')}: <b>{online_now}</b>")
|
||||
stats_text_parts.append(f"📊 {_('admin_panel_active_label')}: <b>{active_users}</b>")
|
||||
stats_text_parts.append(f"🔴 {_('admin_panel_disabled_label')}: <b>{disabled_users}</b>")
|
||||
stats_text_parts.append(f"⏰ {_('admin_panel_expired_label')}: <b>{expired_users}</b>")
|
||||
stats_text_parts.append(f"⚠️ {_('admin_panel_limited_label')}: <b>{limited_users}</b>")
|
||||
stats_text_parts.append(f"👥 {_('admin_panel_total_users_label')}: <b>{total_users}</b>")
|
||||
|
||||
# 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')}: <b>{memory_usage:.1f}%</b>")
|
||||
stats_text_parts.append(f"💾 {_('admin_panel_memory_usage_label')}: <b>{memory_usage:.1f}%</b>")
|
||||
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='Трафик за неделю')}: <b>{week_total}</b>")
|
||||
stats_text_parts.append(f"📊 {_('admin_panel_traffic_week_label')}: <b>{week_total}</b>")
|
||||
|
||||
if month_traffic:
|
||||
month_total = month_traffic.get('current', '0 B')
|
||||
stats_text_parts.append(f"📊 {_('admin_panel_traffic_month_label', default='Трафик за месяц')}: <b>{month_total}</b>")
|
||||
stats_text_parts.append(f"📊 {_('admin_panel_traffic_month_label')}: <b>{month_total}</b>")
|
||||
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='Активных нод')}: <b>{total_nodes_count}/{total_nodes_count}</b>")
|
||||
stats_text_parts.append(f"🔗 {_('admin_panel_nodes_label')}: <b>{total_nodes_count}/{total_nodes_count}</b>")
|
||||
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='Активных нод')}: <b>{total_online}</b>")
|
||||
stats_text_parts.append(f"🔗 {_('admin_panel_nodes_label')}: <b>{total_online}</b>")
|
||||
|
||||
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<b>💰 {_('admin_financial_stats_header', default='Финансовая статистика')}</b>"
|
||||
f"\n<b>💰 {_('admin_financial_stats_header')}</b>"
|
||||
)
|
||||
stats_text_parts.append(
|
||||
f"📅 {_('admin_financial_today_label', default='За сегодня')}: <b>{financial_stats['today_revenue']:.2f} RUB</b> ({financial_stats['today_payments_count']} {_('admin_financial_payments_label', default='платежей')})"
|
||||
f"📅 {_('admin_financial_today_label')}: <b>{financial_stats['today_revenue']:.2f} RUB</b> ({financial_stats['today_payments_count']} {_('admin_financial_payments_label')})"
|
||||
)
|
||||
stats_text_parts.append(
|
||||
f"📅 {_('admin_financial_week_label', default='За неделю')}: <b>{financial_stats['week_revenue']:.2f} RUB</b>"
|
||||
f"📅 {_('admin_financial_week_label')}: <b>{financial_stats['week_revenue']:.2f} RUB</b>"
|
||||
)
|
||||
stats_text_parts.append(
|
||||
f"📅 {_('admin_financial_month_label', default='За месяц')}: <b>{financial_stats['month_revenue']:.2f} RUB</b>"
|
||||
f"📅 {_('admin_financial_month_label')}: <b>{financial_stats['month_revenue']:.2f} RUB</b>"
|
||||
)
|
||||
stats_text_parts.append(
|
||||
f"🏆 {_('admin_financial_all_time_label', default='За все время')}: <b>{financial_stats['all_time_revenue']:.2f} RUB</b>"
|
||||
f"🏆 {_('admin_financial_all_time_label')}: <b>{financial_stats['all_time_revenue']:.2f} RUB</b>"
|
||||
)
|
||||
|
||||
last_payments_models: List[
|
||||
|
||||
@@ -56,7 +56,6 @@ async def users_list_handler(callback: types.CallbackQuery,
|
||||
# Format message
|
||||
header_text = _(
|
||||
"admin_users_list_header",
|
||||
default="👥 <b>Список пользователей</b>\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"👤 <b>{_('admin_user_card_title', default='Карточка пользователя')}</b>\n")
|
||||
card_parts.append(f"👤 <b>{_('admin_user_card_title')}</b>\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='🆔 <b>ID:</b>')} {hcode(str(user.user_id))}")
|
||||
card_parts.append(f"{_('admin_user_name_label', default='👤 <b>Имя:</b>')} {hcode(user_name)}")
|
||||
card_parts.append(f"{_('admin_user_username_label', default='📱 <b>Username:</b>')} {hcode(username_display)}")
|
||||
card_parts.append(f"{_('admin_user_language_label', default='🌍 <b>Язык:</b>')} {hcode(user.language_code or na_value)}")
|
||||
card_parts.append(f"{_('admin_user_registration_label', default='📅 <b>Регистрация:</b>')} {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='🛡 <b>Статус:</b>')} {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='🎁 <b>Привлечен по реферальной программе от:</b>')} {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='🔗 <b>Panel UUID:</b>')} {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"💳 <b>{_('admin_user_subscription_info', default='Информация о подписке:')}</b>")
|
||||
card_parts.append(f"💳 <b>{_('admin_user_subscription_info')}</b>")
|
||||
|
||||
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='⏰ <b>Действует до:</b>')} {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='📊 <b>Статус на панели:</b>')} {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='📊 <b>Трафик:</b>')} {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='💼 <b>Подписка:</b>')} {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='💼 <b>Подписка:</b>')} {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='📜 <b>Всего действий:</b>')} {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='🏡 <b>Триал:</b>')} {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='💰 <b>Всего оплачено:</b>')} {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='💸 <b>Доход по рефералам:</b>')} {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='👥 <b>Приглашено друзей:</b>')} {hcode(str(invited_count))}")
|
||||
card_parts.append(f"{_('admin_user_ref_purchased_label', default='💳 <b>Купили подписку:</b>')} {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
|
||||
|
||||
@@ -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="📊 <b>Статистика Бота</b>\n👥 Пользователи\n\n"
|
||||
"📊 Всего: <b>{total}</b>\n"
|
||||
"💳 С платной подпиской: <b>{paid}</b>\n"
|
||||
"🆓 На пробном периоде: <b>{trial}</b>\n"
|
||||
"😴 Неактивных: <b>{inactive}</b>\n"
|
||||
"🚫 Заблокированных: <b>{banned}</b>\n"
|
||||
"🎁 Привлечено по реферальной программе: <b>{referral}</b>",
|
||||
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="💰 <b>Финансовая статистика</b>\n\n"
|
||||
"📅 За сегодня: <b>{today:.2f} RUB</b>\n"
|
||||
" ({today_count} платежей)\n"
|
||||
"📅 За неделю: <b>{week:.2f} RUB</b>\n"
|
||||
"📅 За месяц: <b>{month:.2f} RUB</b>\n"
|
||||
"🏆 За все время: <b>{all_time:.2f} RUB</b>",
|
||||
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="🖥 <b>Статистика панели</b>\n\n"
|
||||
"🟢 Онлайн: <b>{online}</b>\n"
|
||||
"📊 Активных: <b>{active}</b>\n"
|
||||
"🔴 Отключенных: <b>{disabled}</b>\n"
|
||||
"⏰ Истекшие: <b>{expired}</b>\n"
|
||||
"⚠️ Ограниченные: <b>{limited}</b>\n"
|
||||
"👥 Всего пользователей: <b>{total}</b>\n"
|
||||
"💾 Использование RAM: <b>{memory:.1f}%</b>\n"
|
||||
"📊 Трафик за неделю: <b>{week_traffic}</b>\n"
|
||||
"📊 Трафик за месяц: <b>{month_traffic}</b>\n"
|
||||
"🔗 Активных нод: <b>{active_nodes}/{total_nodes}</b>",
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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}"))
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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="👤 <b>Новый пользователь</b>\n\n"
|
||||
"🆔 ID: <code>{user_id}</code>\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} <b>Получен платеж</b>\n\n"
|
||||
"👤 Пользователь: {user_display}\n"
|
||||
"💰 Сумма: <b>{amount} {currency}</b>\n"
|
||||
"🗂 Трафик: <b>{traffic_gb} GB</b>\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} <b>Получен платеж</b>\n\n"
|
||||
"👤 Пользователь: {user_display}\n"
|
||||
"💰 Сумма: <b>{amount} {currency}</b>\n"
|
||||
"📅 Период: <b>{months} мес.</b>\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="🎁 <b>Активирован промокод</b>\n\n"
|
||||
"👤 Пользователь: {user_display}\n"
|
||||
"🏷 Код: <code>{promo_code}</code>\n"
|
||||
"🎯 Бонус: <b>+{bonus_days} дн.</b>\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="🆓 <b>Активирован триал</b>\n\n"
|
||||
"👤 Пользователь: {user_display}\n"
|
||||
"⏰ Действует до: <b>{end_date}</b>\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} <b>Синхронизация с панелью</b>\n\n"
|
||||
"📊 Статус: <b>{status}</b>\n"
|
||||
"👥 Обработано пользователей: <b>{users_processed}</b>\n"
|
||||
"📋 Синхронизировано подписок: <b>{subs_synced}</b>\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="⚠️ <b>Подозрительная попытка ввода промокода</b>\n\n"
|
||||
"👤 Пользователь: {user_display}\n"
|
||||
"🆔 ID: <code>{user_id}</code>\n"
|
||||
"📝 Ввод: <pre>{suspicious_input}</pre>\n"
|
||||
"🕐 Время: {timestamp}",
|
||||
user_display=hd.quote(user_display),
|
||||
user_id=user_id,
|
||||
suspicious_input=hd.quote(suspicious_input),
|
||||
|
||||
+1
-1
@@ -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] = {}
|
||||
|
||||
+1
-1
@@ -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": "🎟 Промокод",
|
||||
|
||||
Reference in New Issue
Block a user