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)
|
camp = await ad_dal.get_campaign_by_id(session, camp_id)
|
||||||
if not camp:
|
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
|
return
|
||||||
try:
|
try:
|
||||||
stats = await ad_dal.get_campaign_stats(session, camp_id)
|
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)
|
existed = await ad_dal.delete_campaign(session, camp_id)
|
||||||
if not existed:
|
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
|
return
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,6 @@ async def process_broadcast_message_handler(
|
|||||||
await message.answer(
|
await message.answer(
|
||||||
_(
|
_(
|
||||||
"admin_broadcast_invalid_html",
|
"admin_broadcast_invalid_html",
|
||||||
default="❌ Некорректный HTML в сообщении. Пожалуйста, отправьте корректный HTML (поддерживаются теги Telegram) или уберите теги.\nОшибка: {error}",
|
|
||||||
error=str(e),
|
error=str(e),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -347,15 +346,6 @@ async def confirm_broadcast_callback_handler(
|
|||||||
total_failed = failed_count + dynamic_failed
|
total_failed = failed_count + dynamic_failed
|
||||||
return _(
|
return _(
|
||||||
"broadcast_queue_result",
|
"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,
|
sent_count=sent_count,
|
||||||
failed_count=total_failed,
|
failed_count=total_failed,
|
||||||
user_queue_size=stats["user_queue_size"],
|
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)
|
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
|
||||||
|
|
||||||
await callback.answer(_(
|
await callback.answer(_(
|
||||||
"admin_logs_csv_export_started",
|
"admin_logs_csv_export_started"
|
||||||
default="🔄 Начинаю экспорт логов в CSV..."
|
|
||||||
))
|
))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -349,8 +348,7 @@ async def export_logs_csv_handler(callback: types.CallbackQuery,
|
|||||||
|
|
||||||
if not logs_models:
|
if not logs_models:
|
||||||
await callback.message.answer(_(
|
await callback.message.answer(_(
|
||||||
"admin_logs_csv_no_data",
|
"admin_logs_csv_no_data"
|
||||||
default="❌ Нет данных для экспорта"
|
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -360,16 +358,16 @@ async def export_logs_csv_handler(callback: types.CallbackQuery,
|
|||||||
|
|
||||||
# Write header
|
# Write header
|
||||||
headers = [
|
headers = [
|
||||||
_("admin_csv_header_log_id", default="Log ID"),
|
_("admin_csv_header_log_id"),
|
||||||
_("admin_csv_header_timestamp", default="Timestamp"),
|
_("admin_csv_header_timestamp"),
|
||||||
_("admin_csv_header_user_id", default="User ID"),
|
_("admin_csv_header_user_id"),
|
||||||
_("admin_csv_header_telegram_username", default="Telegram Username"),
|
_("admin_csv_header_telegram_username"),
|
||||||
_("admin_csv_header_telegram_first_name", default="Telegram First Name"),
|
_("admin_csv_header_telegram_first_name"),
|
||||||
_("admin_csv_header_event_type", default="Event Type"),
|
_("admin_csv_header_event_type"),
|
||||||
_("admin_csv_header_content", default="Content"),
|
_("admin_csv_header_content"),
|
||||||
_("admin_csv_header_is_admin_event", default="Is Admin Event"),
|
_("admin_csv_header_is_admin_event"),
|
||||||
_("admin_csv_header_target_user_id", default="Target User ID"),
|
_("admin_csv_header_target_user_id"),
|
||||||
_("admin_csv_header_raw_update_preview", default="Raw Update Preview")
|
_("admin_csv_header_raw_update_preview")
|
||||||
]
|
]
|
||||||
csv_writer.writerow(headers)
|
csv_writer.writerow(headers)
|
||||||
|
|
||||||
@@ -414,7 +412,6 @@ async def export_logs_csv_handler(callback: types.CallbackQuery,
|
|||||||
csv_file,
|
csv_file,
|
||||||
caption=_(
|
caption=_(
|
||||||
"admin_logs_csv_export_success",
|
"admin_logs_csv_export_success",
|
||||||
default="✅ Экспорт логов завершен!\n\n📊 Записей: {count}\n📅 Дата экспорта: {date}",
|
|
||||||
count=len(logs_models),
|
count=len(logs_models),
|
||||||
date=now.strftime('%Y-%m-%d %H:%M:%S')
|
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)
|
logging.error(f"Error exporting logs to CSV: {e}", exc_info=True)
|
||||||
await callback.message.answer(_(
|
await callback.message.answer(_(
|
||||||
"admin_logs_csv_export_failed",
|
"admin_logs_csv_export_failed",
|
||||||
default="❌ Ошибка при экспорте логов: {error}",
|
|
||||||
error=str(e)
|
error=str(e)
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ def format_payment_text(payment: Payment, i18n: JsonI18n, lang: str, settings: S
|
|||||||
if traffic_mode:
|
if traffic_mode:
|
||||||
traffic_val = payment.subscription_duration_months or 0
|
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}"
|
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:
|
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 (
|
return (
|
||||||
f"{status_emoji} <b>{payment.amount} {payment.currency}</b>\n"
|
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:
|
if not payments and page == 0:
|
||||||
await callback.message.edit_text(
|
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),
|
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
|
||||||
parse_mode="HTML"
|
parse_mode="HTML"
|
||||||
)
|
)
|
||||||
@@ -110,7 +110,7 @@ async def view_payments_handler(callback: types.CallbackQuery, i18n_data: dict,
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Format payments text
|
# Format payments text
|
||||||
text_parts = [_("admin_payments_header", default="💰 <b>Все платежи</b>")]
|
text_parts = [_("admin_payments_header")]
|
||||||
text_parts.append(_("admin_payments_pagination_info",
|
text_parts.append(_("admin_payments_pagination_info",
|
||||||
shown=len(payments),
|
shown=len(payments),
|
||||||
total=total_count,
|
total=total_count,
|
||||||
@@ -140,11 +140,11 @@ async def view_payments_handler(callback: types.CallbackQuery, i18n_data: dict,
|
|||||||
# Export and refresh buttons
|
# Export and refresh buttons
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=_("admin_export_payments_csv", default="📊 Экспорт CSV"),
|
text=_("admin_export_payments_csv"),
|
||||||
callback_data="payments_export_csv"
|
callback_data="payments_export_csv"
|
||||||
),
|
),
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=_("admin_refresh_payments", default="🔄 Обновить"),
|
text=_("admin_refresh_payments"),
|
||||||
callback_data=f"payments_page:{page}"
|
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:
|
if not all_payments:
|
||||||
await callback.answer(
|
await callback.answer(
|
||||||
_("admin_no_payments_to_export", default="Нет платежей для экспорта."),
|
_("admin_no_payments_to_export"),
|
||||||
show_alert=True
|
show_alert=True
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -202,18 +202,18 @@ async def export_payments_csv_handler(callback: types.CallbackQuery, i18n_data:
|
|||||||
|
|
||||||
# Write header
|
# Write header
|
||||||
writer.writerow([
|
writer.writerow([
|
||||||
_("admin_csv_payment_id", default="ID"),
|
_("admin_csv_payment_id"),
|
||||||
_("admin_csv_user_id", default="User ID"),
|
_("admin_csv_user_id"),
|
||||||
_("admin_csv_username", default="Username"),
|
_("admin_csv_username"),
|
||||||
_("admin_csv_first_name", default="First Name"),
|
_("admin_csv_first_name"),
|
||||||
_("admin_csv_amount", default="Amount"),
|
_("admin_csv_amount"),
|
||||||
_("admin_csv_currency", default="Currency"),
|
_("admin_csv_currency"),
|
||||||
_("admin_csv_provider", default="Provider"),
|
_("admin_csv_provider"),
|
||||||
_("admin_csv_status", default="Status"),
|
_("admin_csv_status"),
|
||||||
_("admin_csv_description", default="Description"),
|
_("admin_csv_description"),
|
||||||
_("admin_csv_units", default="Months/GB"),
|
_("admin_csv_units"),
|
||||||
_("admin_csv_created_at", default="Created At"),
|
_("admin_csv_created_at"),
|
||||||
_("admin_csv_provider_payment_id", default="Provider Payment ID")
|
_("admin_csv_provider_payment_id")
|
||||||
])
|
])
|
||||||
|
|
||||||
traffic_mode = getattr(settings, "traffic_sale_mode", False)
|
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(
|
await callback.message.reply_document(
|
||||||
document=file,
|
document=file,
|
||||||
caption=_("admin_payments_export_success",
|
caption=_("admin_payments_export_success",
|
||||||
default="📊 Payments export completed!\nTotal records: {count}",
|
|
||||||
count=len(all_payments))
|
count=len(all_payments))
|
||||||
)
|
)
|
||||||
|
|
||||||
await callback.answer(
|
await callback.answer(
|
||||||
_("admin_export_sent", default="File sent!"),
|
_("admin_export_sent"),
|
||||||
show_alert=False
|
show_alert=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,7 @@ async def create_bulk_promo_prompt_handler(callback: types.CallbackQuery,
|
|||||||
|
|
||||||
# Step 1: Ask for quantity
|
# Step 1: Ask for quantity
|
||||||
prompt_text = _(
|
prompt_text = _(
|
||||||
"admin_bulk_promo_step1_quantity",
|
"admin_bulk_promo_step1_quantity"
|
||||||
default="🎟 <b>Массовое создание промокодов</b>\n\n<b>Шаг 1 из 4:</b> Количество\n\nВведите количество промокодов для создания (1-100):"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -77,8 +76,7 @@ async def process_bulk_promo_quantity_handler(message: types.Message,
|
|||||||
quantity = int(message.text.strip())
|
quantity = int(message.text.strip())
|
||||||
if not (1 <= quantity <= 100):
|
if not (1 <= quantity <= 100):
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_bulk_promo_invalid_quantity",
|
"admin_bulk_promo_invalid_quantity"
|
||||||
default="❌ Количество промокодов должно быть от 1 до 100"
|
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -87,7 +85,6 @@ async def process_bulk_promo_quantity_handler(message: types.Message,
|
|||||||
# Step 2: Ask for bonus days
|
# Step 2: Ask for bonus days
|
||||||
prompt_text = _(
|
prompt_text = _(
|
||||||
"admin_bulk_promo_step2_bonus_days",
|
"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
|
quantity=quantity
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -100,8 +97,7 @@ async def process_bulk_promo_quantity_handler(message: types.Message,
|
|||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_promo_invalid_number",
|
"admin_promo_invalid_number"
|
||||||
default="❌ Введите корректное число"
|
|
||||||
))
|
))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error processing bulk promo quantity: {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())
|
bonus_days = int(message.text.strip())
|
||||||
if not (1 <= bonus_days <= 365):
|
if not (1 <= bonus_days <= 365):
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_promo_invalid_bonus_days",
|
"admin_promo_invalid_bonus_days"
|
||||||
default="❌ Количество бонусных дней должно быть от 1 до 365"
|
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -136,7 +131,6 @@ async def process_bulk_promo_bonus_days_handler(message: types.Message,
|
|||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
prompt_text = _(
|
prompt_text = _(
|
||||||
"admin_bulk_promo_step3_max_activations",
|
"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"),
|
quantity=data.get("quantity"),
|
||||||
bonus_days=bonus_days
|
bonus_days=bonus_days
|
||||||
)
|
)
|
||||||
@@ -150,8 +144,7 @@ async def process_bulk_promo_bonus_days_handler(message: types.Message,
|
|||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_promo_invalid_number",
|
"admin_promo_invalid_number"
|
||||||
default="❌ Введите корректное число"
|
|
||||||
))
|
))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error processing bulk promo bonus days: {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())
|
max_activations = int(message.text.strip())
|
||||||
if not (1 <= max_activations <= 10000):
|
if not (1 <= max_activations <= 10000):
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_promo_invalid_max_activations",
|
"admin_promo_invalid_max_activations"
|
||||||
default="❌ Максимальное количество активаций должно быть от 1 до 10000"
|
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -186,7 +178,6 @@ async def process_bulk_promo_max_activations_handler(message: types.Message,
|
|||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
prompt_text = _(
|
prompt_text = _(
|
||||||
"admin_bulk_promo_step4_validity",
|
"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"),
|
quantity=data.get("quantity"),
|
||||||
bonus_days=data.get("bonus_days"),
|
bonus_days=data.get("bonus_days"),
|
||||||
max_activations=max_activations
|
max_activations=max_activations
|
||||||
@@ -196,19 +187,19 @@ async def process_bulk_promo_max_activations_handler(message: types.Message,
|
|||||||
builder = InlineKeyboardBuilder()
|
builder = InlineKeyboardBuilder()
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=_("admin_promo_unlimited_validity", default="🔄 Без ограничений"),
|
text=_("admin_promo_unlimited_validity"),
|
||||||
callback_data="bulk_promo_unlimited_validity"
|
callback_data="bulk_promo_unlimited_validity"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=_("admin_promo_set_validity_days", default="📅 Указать дни"),
|
text=_("admin_promo_set_validity_days"),
|
||||||
callback_data="bulk_promo_set_validity"
|
callback_data="bulk_promo_set_validity"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=_("admin_back_to_panel", default="🔙 В админ панель"),
|
text=_("admin_back_to_panel"),
|
||||||
callback_data="admin_action:main"
|
callback_data="admin_action:main"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -222,8 +213,7 @@ async def process_bulk_promo_max_activations_handler(message: types.Message,
|
|||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_promo_invalid_number",
|
"admin_promo_invalid_number"
|
||||||
default="❌ Введите корректное число"
|
|
||||||
))
|
))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error processing bulk promo max activations: {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()
|
data = await state.get_data()
|
||||||
prompt_text = _(
|
prompt_text = _(
|
||||||
"admin_bulk_promo_enter_validity_days",
|
"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"),
|
quantity=data.get("quantity"),
|
||||||
bonus_days=data.get("bonus_days"),
|
bonus_days=data.get("bonus_days"),
|
||||||
max_activations=data.get("max_activations")
|
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())
|
validity_days = int(message.text.strip())
|
||||||
if not (1 <= validity_days <= 365):
|
if not (1 <= validity_days <= 365):
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_promo_invalid_validity_days",
|
"admin_promo_invalid_validity_days"
|
||||||
default="❌ Срок действия должен быть от 1 до 365 дней"
|
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -306,8 +294,7 @@ async def process_bulk_promo_validity_days_handler(message: types.Message,
|
|||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_promo_invalid_number",
|
"admin_promo_invalid_number"
|
||||||
default="❌ Введите корректное число"
|
|
||||||
))
|
))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error processing bulk promo validity days: {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
|
# Show progress message
|
||||||
progress_text = _(
|
progress_text = _(
|
||||||
"admin_bulk_promo_creating",
|
"admin_bulk_promo_creating",
|
||||||
default="🔄 Создание {quantity} промокодов...",
|
|
||||||
quantity=quantity
|
quantity=quantity
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -395,12 +381,10 @@ async def create_bulk_promo_codes_final(callback_or_message,
|
|||||||
# Success message
|
# Success message
|
||||||
success_lines = [
|
success_lines = [
|
||||||
_(
|
_(
|
||||||
"admin_bulk_promo_created_title",
|
"admin_bulk_promo_created_title"
|
||||||
default="✅ <b>Массовое создание завершено!</b>\n"
|
|
||||||
),
|
),
|
||||||
_(
|
_(
|
||||||
"admin_bulk_promo_created_stats",
|
"admin_bulk_promo_created_stats",
|
||||||
default="📊 Создано: <b>{created}</b> из <b>{total}</b>",
|
|
||||||
created=len(created_codes),
|
created=len(created_codes),
|
||||||
total=quantity
|
total=quantity
|
||||||
)
|
)
|
||||||
@@ -409,14 +393,11 @@ async def create_bulk_promo_codes_final(callback_or_message,
|
|||||||
if data.get("validity_days"):
|
if data.get("validity_days"):
|
||||||
validity_text = f"{data['validity_days']} дней"
|
validity_text = f"{data['validity_days']} дней"
|
||||||
else:
|
else:
|
||||||
validity_text = _("admin_promo_unlimited", default="Без ограничений")
|
validity_text = _("admin_promo_unlimited")
|
||||||
|
|
||||||
success_lines.append(
|
success_lines.append(
|
||||||
_(
|
_(
|
||||||
"admin_bulk_promo_settings",
|
"admin_bulk_promo_settings",
|
||||||
default="🎁 Бонусные дни: <b>{bonus_days}</b>\n"
|
|
||||||
"📊 Макс. активаций: <b>{max_activations}</b>\n"
|
|
||||||
"⏰ Срок действия: <b>{validity}</b>",
|
|
||||||
bonus_days=data["bonus_days"],
|
bonus_days=data["bonus_days"],
|
||||||
max_activations=data["max_activations"],
|
max_activations=data["max_activations"],
|
||||||
validity=validity_text
|
validity=validity_text
|
||||||
@@ -521,7 +502,7 @@ async def create_bulk_promo_codes_final(callback_or_message,
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error creating bulk promo codes: {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
|
if hasattr(callback_or_message, 'message'): # CallbackQuery
|
||||||
await callback_or_message.message.answer(error_text)
|
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)
|
reply_markup=get_admin_panel_keyboard(i18n, current_lang, settings)
|
||||||
)
|
)
|
||||||
|
|
||||||
await callback.answer(_("admin_bulk_promo_creation_cancelled", default="Массовое создание промокодов отменено"))
|
await callback.answer(_("admin_bulk_promo_creation_cancelled"))
|
||||||
await state.clear()
|
await state.clear()
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ async def create_promo_prompt_handler(callback: types.CallbackQuery,
|
|||||||
|
|
||||||
# Step 1: Ask for promo code
|
# Step 1: Ask for promo code
|
||||||
prompt_text = _(
|
prompt_text = _(
|
||||||
"admin_promo_step1_code",
|
"admin_promo_step1_code"
|
||||||
default="🎟 <b>Создание промокода</b>\n\n<b>Шаг 1 из 4:</b> Код промокода\n\nВведите код промокода (3-30 символов, только буквы и цифры):"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -68,8 +67,7 @@ async def process_promo_code_handler(message: types.Message,
|
|||||||
code_str = message.text.strip().upper()
|
code_str = message.text.strip().upper()
|
||||||
if not (3 <= len(code_str) <= 30 and code_str.isalnum()):
|
if not (3 <= len(code_str) <= 30 and code_str.isalnum()):
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_promo_invalid_code_format",
|
"admin_promo_invalid_code_format"
|
||||||
default="❌ Код промокода должен содержать 3-30 символов (только буквы и цифры)"
|
|
||||||
))
|
))
|
||||||
return
|
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)
|
existing_promo = await promo_code_dal.get_promo_code_by_code(session, code_str)
|
||||||
if existing_promo:
|
if existing_promo:
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_promo_code_already_exists",
|
"admin_promo_code_already_exists"
|
||||||
default="❌ Промокод с таким кодом уже существует"
|
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -87,7 +84,6 @@ async def process_promo_code_handler(message: types.Message,
|
|||||||
# Step 2: Ask for bonus days
|
# Step 2: Ask for bonus days
|
||||||
prompt_text = _(
|
prompt_text = _(
|
||||||
"admin_promo_step2_bonus_days",
|
"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
|
code=code_str
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -120,8 +116,7 @@ async def process_promo_bonus_days_handler(message: types.Message,
|
|||||||
bonus_days = int(message.text.strip())
|
bonus_days = int(message.text.strip())
|
||||||
if not (1 <= bonus_days <= 365):
|
if not (1 <= bonus_days <= 365):
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_promo_invalid_bonus_days",
|
"admin_promo_invalid_bonus_days"
|
||||||
default="❌ Количество бонусных дней должно быть от 1 до 365"
|
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -131,7 +126,6 @@ async def process_promo_bonus_days_handler(message: types.Message,
|
|||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
prompt_text = _(
|
prompt_text = _(
|
||||||
"admin_promo_step3_max_activations",
|
"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"),
|
code=data.get("promo_code"),
|
||||||
bonus_days=bonus_days
|
bonus_days=bonus_days
|
||||||
)
|
)
|
||||||
@@ -145,8 +139,7 @@ async def process_promo_bonus_days_handler(message: types.Message,
|
|||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_promo_invalid_number",
|
"admin_promo_invalid_number"
|
||||||
default="❌ Введите корректное число"
|
|
||||||
))
|
))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error processing promo bonus days: {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())
|
max_activations = int(message.text.strip())
|
||||||
if not (1 <= max_activations <= 10000):
|
if not (1 <= max_activations <= 10000):
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_promo_invalid_max_activations",
|
"admin_promo_invalid_max_activations"
|
||||||
default="❌ Максимальное количество активаций должно быть от 1 до 10000"
|
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -181,7 +173,6 @@ async def process_promo_max_activations_handler(message: types.Message,
|
|||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
prompt_text = _(
|
prompt_text = _(
|
||||||
"admin_promo_step4_validity",
|
"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"),
|
code=data.get("promo_code"),
|
||||||
bonus_days=data.get("bonus_days"),
|
bonus_days=data.get("bonus_days"),
|
||||||
max_activations=max_activations
|
max_activations=max_activations
|
||||||
@@ -191,19 +182,19 @@ async def process_promo_max_activations_handler(message: types.Message,
|
|||||||
builder = InlineKeyboardBuilder()
|
builder = InlineKeyboardBuilder()
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=_("admin_promo_unlimited_validity", default="🔄 Без ограничений"),
|
text=_("admin_promo_unlimited_validity"),
|
||||||
callback_data="promo_unlimited_validity"
|
callback_data="promo_unlimited_validity"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=_("admin_promo_set_validity_days", default="📅 Указать дни"),
|
text=_("admin_promo_set_validity_days"),
|
||||||
callback_data="promo_set_validity"
|
callback_data="promo_set_validity"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=_("admin_back_to_panel", default="🔙 В админ панель"),
|
text=_("admin_back_to_panel"),
|
||||||
callback_data="admin_action:main"
|
callback_data="admin_action:main"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -217,8 +208,7 @@ async def process_promo_max_activations_handler(message: types.Message,
|
|||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_promo_invalid_number",
|
"admin_promo_invalid_number"
|
||||||
default="❌ Введите корректное число"
|
|
||||||
))
|
))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error processing promo max activations: {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()
|
data = await state.get_data()
|
||||||
prompt_text = _(
|
prompt_text = _(
|
||||||
"admin_promo_enter_validity_days",
|
"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"),
|
code=data.get("promo_code"),
|
||||||
bonus_days=data.get("bonus_days"),
|
bonus_days=data.get("bonus_days"),
|
||||||
max_activations=data.get("max_activations")
|
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())
|
validity_days = int(message.text.strip())
|
||||||
if not (1 <= validity_days <= 365):
|
if not (1 <= validity_days <= 365):
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_promo_invalid_validity_days",
|
"admin_promo_invalid_validity_days"
|
||||||
default="❌ Срок действия должен быть от 1 до 365 дней"
|
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -301,8 +289,7 @@ async def process_promo_validity_days_handler(message: types.Message,
|
|||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_promo_invalid_number",
|
"admin_promo_invalid_number"
|
||||||
default="❌ Введите корректное число"
|
|
||||||
))
|
))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error processing promo validity days: {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}")
|
logging.info(f"Promo code '{data['promo_code']}' created with ID {created_promo.promo_code_id}")
|
||||||
|
|
||||||
# Success message
|
# 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 = _(
|
success_text = _(
|
||||||
"admin_promo_created_success",
|
"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"],
|
code=data["promo_code"],
|
||||||
bonus_days=data["bonus_days"],
|
bonus_days=data["bonus_days"],
|
||||||
max_activations=data["max_activations"],
|
max_activations=data["max_activations"],
|
||||||
@@ -388,7 +370,7 @@ async def create_promo_code_final(callback_or_message,
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error creating promo code: {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
|
if hasattr(callback_or_message, 'message'): # CallbackQuery
|
||||||
await callback_or_message.message.answer(error_text)
|
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)
|
reply_markup=get_admin_panel_keyboard(i18n, current_lang, settings)
|
||||||
)
|
)
|
||||||
|
|
||||||
await callback.answer(_("admin_promo_creation_cancelled", default="Создание промокода отменено"))
|
await callback.answer(_("admin_promo_creation_cancelled"))
|
||||||
await state.clear()
|
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)
|
user_stats = await user_dal.get_enhanced_user_statistics(session)
|
||||||
|
|
||||||
stats_text_parts.append(
|
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(
|
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
|
# Removed: Active today moved to panel stats
|
||||||
stats_text_parts.append(
|
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(
|
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(
|
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(
|
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(
|
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
|
# 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:
|
try:
|
||||||
async with PanelApiService(settings) as panel_service:
|
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)
|
total_users = users.get('totalUsers', 0)
|
||||||
online_now = online_stats.get('onlineNow', 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_online_label')}: <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_active_label')}: <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_disabled_label')}: <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_expired_label')}: <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_limited_label')}: <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_total_users_label')}: <b>{total_users}</b>")
|
||||||
|
|
||||||
# System resources
|
# System resources
|
||||||
memory = system_stats.get('memory', {})
|
memory = system_stats.get('memory', {})
|
||||||
@@ -93,9 +93,9 @@ async def show_statistics_handler(callback: types.CallbackQuery,
|
|||||||
memory_total = memory.get('total', 1)
|
memory_total = memory.get('total', 1)
|
||||||
memory_used = memory.get('used', 0)
|
memory_used = memory.get('used', 0)
|
||||||
memory_usage = (memory_used / memory_total) * 100 if memory_total > 0 else 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:
|
else:
|
||||||
stats_text_parts.append(f"⚠️ {_('admin_panel_system_stats_error', default='Ошибка получения системной статистики')}")
|
stats_text_parts.append(f"⚠️ {_('admin_panel_system_stats_error')}")
|
||||||
|
|
||||||
# Bandwidth stats
|
# Bandwidth stats
|
||||||
if bandwidth_stats:
|
if bandwidth_stats:
|
||||||
@@ -107,13 +107,13 @@ async def show_statistics_handler(callback: types.CallbackQuery,
|
|||||||
|
|
||||||
if week_traffic:
|
if week_traffic:
|
||||||
week_total = week_traffic.get('current', '0 B')
|
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:
|
if month_traffic:
|
||||||
month_total = month_traffic.get('current', '0 B')
|
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:
|
else:
|
||||||
stats_text_parts.append(f"⚠️ {_('admin_panel_bandwidth_stats_error', default='Ошибка получения статистики трафика')}")
|
stats_text_parts.append(f"⚠️ {_('admin_panel_bandwidth_stats_error')}")
|
||||||
|
|
||||||
# Nodes stats
|
# Nodes stats
|
||||||
if nodes_stats and 'lastSevenDays' in 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', ''))
|
unique_nodes.add(node_data.get('nodeName', ''))
|
||||||
total_nodes_count = len(unique_nodes)
|
total_nodes_count = len(unique_nodes)
|
||||||
# Assume all nodes are active since we don't have status info
|
# 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:
|
else:
|
||||||
# Use nodes total from system stats as fallback
|
# Use nodes total from system stats as fallback
|
||||||
nodes_info = system_stats.get('nodes', {}) if system_stats else {}
|
nodes_info = system_stats.get('nodes', {}) if system_stats else {}
|
||||||
total_online = nodes_info.get('totalOnline', 0)
|
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:
|
except Exception as e:
|
||||||
logging.error(f"Failed to fetch panel statistics: {e}", exc_info=True)
|
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_fetch_error')}")
|
||||||
stats_text_parts.append(f"⚠️ {_('admin_panel_stats_error_details', default='Детали')}: {str(e)}")
|
stats_text_parts.append(f"⚠️ {_('admin_panel_stats_error_details')}: {str(e)}")
|
||||||
|
|
||||||
# Financial statistics
|
# Financial statistics
|
||||||
financial_stats = await payment_dal.get_financial_statistics(session)
|
financial_stats = await payment_dal.get_financial_statistics(session)
|
||||||
|
|
||||||
stats_text_parts.append(
|
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(
|
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(
|
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(
|
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(
|
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[
|
last_payments_models: List[
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ async def users_list_handler(callback: types.CallbackQuery,
|
|||||||
# Format message
|
# Format message
|
||||||
header_text = _(
|
header_text = _(
|
||||||
"admin_users_list_header",
|
"admin_users_list_header",
|
||||||
default="👥 <b>Список пользователей</b>\n\nСтраница {current}/{total} ({total_users} пользователей)",
|
|
||||||
current=page + 1,
|
current=page + 1,
|
||||||
total=total_pages,
|
total=total_pages,
|
||||||
total_users=total_users
|
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)
|
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
|
||||||
|
|
||||||
prompt_text = _(
|
prompt_text = _(
|
||||||
"admin_user_management_prompt",
|
"admin_user_management_prompt"
|
||||||
default="👤 Управление пользователями\n\nВведите ID пользователя или @username для поиска:"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -116,56 +114,54 @@ def get_user_card_keyboard(user_id: int, i18n_instance, lang: str,
|
|||||||
|
|
||||||
# Row 1: Trial and Subscription actions
|
# Row 1: Trial and Subscription actions
|
||||||
builder.button(
|
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}"
|
callback_data=f"user_action:reset_trial:{user_id}"
|
||||||
)
|
)
|
||||||
builder.button(
|
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}"
|
callback_data=f"user_action:add_subscription:{user_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Row 2: Block/Unblock and Message
|
# Row 2: Block/Unblock and Message
|
||||||
builder.button(
|
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}"
|
callback_data=f"user_action:toggle_ban:{user_id}"
|
||||||
)
|
)
|
||||||
builder.button(
|
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}"
|
callback_data=f"user_action:send_message:{user_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Row 3: View actions
|
# Row 3: View actions
|
||||||
builder.button(
|
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}"
|
callback_data=f"user_action:view_logs:{user_id}"
|
||||||
)
|
)
|
||||||
builder.button(
|
builder.button(
|
||||||
text=_(key="admin_user_refresh_button", default="🔄 Обновить"),
|
text=_(key="admin_user_refresh_button"),
|
||||||
callback_data=f"user_action:refresh:{user_id}"
|
callback_data=f"user_action:refresh:{user_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Row 4: Quick links
|
# Row 4: Quick links
|
||||||
builder.button(
|
builder.button(
|
||||||
text=_(key="user_card_open_profile_button",
|
text=_(key="user_card_open_profile_button"),
|
||||||
default="👤 Открыть профиль"),
|
|
||||||
url=f"tg://user?id={user_id}"
|
url=f"tg://user?id={user_id}"
|
||||||
)
|
)
|
||||||
if referrer_id:
|
if referrer_id:
|
||||||
builder.button(
|
builder.button(
|
||||||
text=_(key="user_card_open_referrer_profile_button",
|
text=_(key="user_card_open_referrer_profile_button"),
|
||||||
default="👤 Открыть профиль пригласившего"),
|
|
||||||
url=f"tg://user?id={referrer_id}"
|
url=f"tg://user?id={referrer_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Row 5: Destructive action
|
# Row 5: Destructive action
|
||||||
builder.button(
|
builder.button(
|
||||||
text=_(key="admin_user_delete_button", default="❌ Удалить пользователя"),
|
text=_(key="admin_user_delete_button"),
|
||||||
callback_data=f"user_action:delete_user:{user_id}"
|
callback_data=f"user_action:delete_user:{user_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Row 6: Navigation
|
# Row 6: Navigation
|
||||||
builder.button(
|
builder.button(
|
||||||
text=_(key="admin_user_search_new_button", default="🔍 Найти другого"),
|
text=_(key="admin_user_search_new_button"),
|
||||||
callback_data="admin_action:users_management"
|
callback_data="admin_action:users_management"
|
||||||
)
|
)
|
||||||
builder.button(
|
builder.button(
|
||||||
@@ -215,10 +211,10 @@ async def format_user_card(user: User, session: AsyncSession,
|
|||||||
|
|
||||||
# Basic user info
|
# Basic user info
|
||||||
card_parts = []
|
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
|
# 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
|
safe_first_name = sanitize_display_name(user.first_name) if user.first_name else None
|
||||||
user_name = safe_first_name or na_value
|
user_name = safe_first_name or na_value
|
||||||
if user.username:
|
if user.username:
|
||||||
@@ -231,23 +227,23 @@ async def format_user_card(user: User, session: AsyncSession,
|
|||||||
username_display = na_value
|
username_display = na_value
|
||||||
registration_date = user.registration_date.strftime('%Y-%m-%d %H:%M') if user.registration_date else 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_id_label')} {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_name_label')} {hcode(user_name)}")
|
||||||
card_parts.append(f"{_('admin_user_username_label', default='📱 <b>Username:</b>')} {hcode(username_display)}")
|
card_parts.append(f"{_('admin_user_username_label')} {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_language_label')} {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_registration_label')} {hcode(registration_date)}")
|
||||||
|
|
||||||
# Ban status
|
# Ban status
|
||||||
ban_status = _("admin_user_status_banned", default="🚫 Заблокирован") if user.is_banned else _("admin_user_status_active", default="✅ Активен")
|
ban_status = _("admin_user_status_banned") if user.is_banned else _("admin_user_status_active")
|
||||||
card_parts.append(f"{_('admin_user_status_label', default='🛡 <b>Статус:</b>')} {ban_status}")
|
card_parts.append(f"{_('admin_user_status_label')} {ban_status}")
|
||||||
|
|
||||||
# Referral info
|
# Referral info
|
||||||
if user.referred_by_id:
|
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
|
# Panel info
|
||||||
if user.panel_user_uuid:
|
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
|
card_parts.append("") # Empty line
|
||||||
|
|
||||||
@@ -255,38 +251,38 @@ async def format_user_card(user: User, session: AsyncSession,
|
|||||||
try:
|
try:
|
||||||
subscription_details = await subscription_service.get_active_subscription_details(session, user.user_id)
|
subscription_details = await subscription_service.get_active_subscription_details(session, user.user_id)
|
||||||
if subscription_details:
|
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')
|
end_date = subscription_details.get('end_date')
|
||||||
if 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)
|
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')
|
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_limit = subscription_details.get('traffic_limit_bytes')
|
||||||
traffic_used = subscription_details.get('traffic_used_bytes')
|
traffic_used = subscription_details.get('traffic_used_bytes')
|
||||||
if traffic_limit and traffic_used is not None:
|
if traffic_limit and traffic_used is not None:
|
||||||
traffic_limit_gb = traffic_limit / (1024**3)
|
traffic_limit_gb = traffic_limit / (1024**3)
|
||||||
traffic_used_gb = traffic_used / (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:
|
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:
|
except Exception as e:
|
||||||
logging.error(f"Error getting subscription details for user {user.user_id}: {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
|
# Statistics
|
||||||
try:
|
try:
|
||||||
# Count user logs
|
# Count user logs
|
||||||
logs_count = await message_log_dal.count_user_message_logs(session, user.user_id)
|
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
|
# Check if user had any subscriptions
|
||||||
had_subscriptions = await subscription_service.has_had_any_subscription(session, user.user_id)
|
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="Не использовал")
|
trial_status = _("admin_user_trial_used") if had_subscriptions else _("admin_user_trial_not_used")
|
||||||
card_parts.append(f"{_('admin_user_trial_label', default='🏡 <b>Триал:</b>')} {hcode(trial_status)}")
|
card_parts.append(f"{_('admin_user_trial_label')} {hcode(trial_status)}")
|
||||||
|
|
||||||
# Financial analytics (admin-only)
|
# Financial analytics (admin-only)
|
||||||
try:
|
try:
|
||||||
@@ -294,11 +290,11 @@ async def format_user_card(user: User, session: AsyncSession,
|
|||||||
|
|
||||||
# Total amount paid by this user
|
# Total amount paid by this user
|
||||||
total_paid = await payment_dal.get_user_total_paid(session, user.user_id)
|
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
|
# Total revenue from referrals
|
||||||
referral_revenue = await payment_dal.get_referral_revenue(session, user.user_id)
|
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:
|
except Exception as e_fin:
|
||||||
logging.error(f"Failed to build financial analytics for admin card {user.user_id}: {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)
|
stats = await referral_service.get_referral_stats(session, user.user_id)
|
||||||
invited_count = stats.get('invited_count', 0)
|
invited_count = stats.get('invited_count', 0)
|
||||||
purchased_count = stats.get('purchased_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_invited_friends_label')} {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_ref_purchased_label')} {hcode(str(purchased_count))}")
|
||||||
except Exception as e_rs:
|
except Exception as e_rs:
|
||||||
logging.error(f"Failed to build referral stats for admin card {user.user_id}: {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:
|
if not user_model:
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_user_not_found",
|
"admin_user_not_found",
|
||||||
default="❌ Пользователь не найден: {input}",
|
|
||||||
input=hcode(input_text)
|
input=hcode(input_text)
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
@@ -379,8 +374,7 @@ async def process_user_search_handler(message: types.Message, state: FSMContext,
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error displaying user card for {user_model.user_id}: {e}")
|
logging.error(f"Error displaying user card for {user_model.user_id}: {e}")
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_user_card_error",
|
"admin_user_card_error"
|
||||||
default="❌ Ошибка отображения карточки пользователя"
|
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
@@ -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)
|
user = await user_dal.get_user_by_id(session, user_id)
|
||||||
if not user:
|
if not user:
|
||||||
await callback.answer(_(
|
await callback.answer(_(
|
||||||
"admin_user_not_found_action",
|
"admin_user_not_found_action"
|
||||||
default="Пользователь не найден"
|
|
||||||
), show_alert=True)
|
), show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -447,8 +440,7 @@ async def handle_reset_trial(callback: types.CallbackQuery, user: User,
|
|||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
await callback.answer(_(
|
await callback.answer(_(
|
||||||
"admin_user_trial_reset_success",
|
"admin_user_trial_reset_success"
|
||||||
default="✅ Триал сброшен! Пользователь может активировать триал заново."
|
|
||||||
), show_alert=True)
|
), show_alert=True)
|
||||||
|
|
||||||
# Refresh user card
|
# 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}")
|
logging.error(f"Error resetting trial for user {user.user_id}: {e}")
|
||||||
await session.rollback()
|
await session.rollback()
|
||||||
await callback.answer(_(
|
await callback.answer(_(
|
||||||
"admin_user_trial_reset_error",
|
"admin_user_trial_reset_error"
|
||||||
default="❌ Ошибка сброса триала"
|
|
||||||
), show_alert=True)
|
), show_alert=True)
|
||||||
|
|
||||||
|
|
||||||
@@ -473,7 +464,6 @@ async def handle_add_subscription_prompt(callback: types.CallbackQuery, state: F
|
|||||||
|
|
||||||
prompt_text = _(
|
prompt_text = _(
|
||||||
"admin_user_add_subscription_prompt",
|
"admin_user_add_subscription_prompt",
|
||||||
default="➕ Добавление дней подписки для пользователя {user_id}\n\nВведите количество дней для добавления:",
|
|
||||||
user_id=user.user_id
|
user_id=user.user_id
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -504,10 +494,9 @@ async def handle_toggle_ban(callback: types.CallbackQuery, user: User,
|
|||||||
|
|
||||||
await session.commit()
|
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(_(
|
await callback.answer(_(
|
||||||
"admin_user_ban_toggle_success",
|
"admin_user_ban_toggle_success",
|
||||||
default="✅ Пользователь {status}",
|
|
||||||
status=status_text
|
status=status_text
|
||||||
), show_alert=True)
|
), 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}")
|
logging.error(f"Error toggling ban for user {user.user_id}: {e}")
|
||||||
await session.rollback()
|
await session.rollback()
|
||||||
await callback.answer(_(
|
await callback.answer(_(
|
||||||
"admin_user_ban_toggle_error",
|
"admin_user_ban_toggle_error"
|
||||||
default="❌ Ошибка изменения статуса блокировки"
|
|
||||||
), show_alert=True)
|
), show_alert=True)
|
||||||
|
|
||||||
|
|
||||||
@@ -539,7 +527,6 @@ async def handle_send_message_prompt(callback: types.CallbackQuery, state: FSMCo
|
|||||||
|
|
||||||
prompt_text = _(
|
prompt_text = _(
|
||||||
"admin_user_send_message_prompt",
|
"admin_user_send_message_prompt",
|
||||||
default="✉️ Отправка сообщения пользователю {user_id}\n\nВведите текст сообщения:",
|
|
||||||
user_id=user.user_id
|
user_id=user.user_id
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -563,13 +550,12 @@ async def handle_view_user_logs(callback: types.CallbackQuery, user: User,
|
|||||||
|
|
||||||
if not logs:
|
if not logs:
|
||||||
await callback.answer(_(
|
await callback.answer(_(
|
||||||
"admin_user_no_logs",
|
"admin_user_no_logs"
|
||||||
default="📜 У пользователя нет действий"
|
|
||||||
), show_alert=True)
|
), show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
logs_text_parts = [
|
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:
|
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
|
# Create inline keyboard for full logs
|
||||||
builder = InlineKeyboardBuilder()
|
builder = InlineKeyboardBuilder()
|
||||||
builder.button(
|
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"
|
callback_data=f"admin_logs:view_user:{user.user_id}:0"
|
||||||
)
|
)
|
||||||
builder.button(
|
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}"
|
callback_data=f"user_action:refresh:{user.user_id}"
|
||||||
)
|
)
|
||||||
builder.adjust(1)
|
builder.adjust(1)
|
||||||
@@ -614,8 +600,7 @@ async def handle_view_user_logs(callback: types.CallbackQuery, user: User,
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error viewing logs for user {user.user_id}: {e}")
|
logging.error(f"Error viewing logs for user {user.user_id}: {e}")
|
||||||
await callback.answer(_(
|
await callback.answer(_(
|
||||||
"admin_user_logs_error",
|
"admin_user_logs_error"
|
||||||
default="❌ Ошибка загрузки действий пользователя"
|
|
||||||
), show_alert=True)
|
), show_alert=True)
|
||||||
|
|
||||||
|
|
||||||
@@ -682,7 +667,6 @@ async def handle_delete_user_prompt(callback: types.CallbackQuery, state: FSMCon
|
|||||||
await callback.answer(
|
await callback.answer(
|
||||||
_(
|
_(
|
||||||
"admin_user_delete_not_allowed",
|
"admin_user_delete_not_allowed",
|
||||||
default="❌ У вас нет прав для удаления пользователей.",
|
|
||||||
),
|
),
|
||||||
show_alert=True,
|
show_alert=True,
|
||||||
)
|
)
|
||||||
@@ -696,11 +680,6 @@ async def handle_delete_user_prompt(callback: types.CallbackQuery, state: FSMCon
|
|||||||
|
|
||||||
prompt_text = _(
|
prompt_text = _(
|
||||||
"admin_user_delete_confirmation_prompt",
|
"admin_user_delete_confirmation_prompt",
|
||||||
default=(
|
|
||||||
"⚠️ Вы хотите полностью удалить пользователя {user_id}.\n\n"
|
|
||||||
"Отправьте точный Telegram ID этого пользователя, чтобы подтвердить удаление.\n"
|
|
||||||
"Любой другой ответ отменит операцию."
|
|
||||||
),
|
|
||||||
user_id=hcode(str(user.user_id)),
|
user_id=hcode(str(user.user_id)),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -771,7 +750,6 @@ async def process_delete_user_confirmation_handler(message: types.Message,
|
|||||||
await message.answer(
|
await message.answer(
|
||||||
_(
|
_(
|
||||||
"admin_user_delete_not_allowed",
|
"admin_user_delete_not_allowed",
|
||||||
default="❌ У вас нет прав для удаления пользователей.",
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
await state.clear()
|
await state.clear()
|
||||||
@@ -783,7 +761,6 @@ async def process_delete_user_confirmation_handler(message: types.Message,
|
|||||||
await message.answer(
|
await message.answer(
|
||||||
_(
|
_(
|
||||||
"admin_user_delete_state_missing",
|
"admin_user_delete_state_missing",
|
||||||
default="⚠️ Нет активной операции удаления. Начните заново.",
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
await state.clear()
|
await state.clear()
|
||||||
@@ -794,7 +771,6 @@ async def process_delete_user_confirmation_handler(message: types.Message,
|
|||||||
await message.answer(
|
await message.answer(
|
||||||
_(
|
_(
|
||||||
"admin_user_delete_cancelled",
|
"admin_user_delete_cancelled",
|
||||||
default="Операция удаления отменена по запросу.",
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
await state.clear()
|
await state.clear()
|
||||||
@@ -804,7 +780,6 @@ async def process_delete_user_confirmation_handler(message: types.Message,
|
|||||||
await message.answer(
|
await message.answer(
|
||||||
_(
|
_(
|
||||||
"admin_user_delete_mismatch",
|
"admin_user_delete_mismatch",
|
||||||
default="⚠️ ID не совпадает. Удаление отменено.",
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
await state.clear()
|
await state.clear()
|
||||||
@@ -815,7 +790,6 @@ async def process_delete_user_confirmation_handler(message: types.Message,
|
|||||||
await message.answer(
|
await message.answer(
|
||||||
_(
|
_(
|
||||||
"admin_user_delete_already_removed",
|
"admin_user_delete_already_removed",
|
||||||
default="ℹ️ Пользователь уже удален.",
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
await state.clear()
|
await state.clear()
|
||||||
@@ -830,10 +804,6 @@ async def process_delete_user_confirmation_handler(message: types.Message,
|
|||||||
await message.answer(
|
await message.answer(
|
||||||
_(
|
_(
|
||||||
"admin_user_delete_panel_error",
|
"admin_user_delete_panel_error",
|
||||||
default=(
|
|
||||||
"❌ Не удалось удалить пользователя на панели. "
|
|
||||||
"Операция прервана."
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
await session.rollback()
|
await session.rollback()
|
||||||
@@ -847,7 +817,6 @@ async def process_delete_user_confirmation_handler(message: types.Message,
|
|||||||
await message.answer(
|
await message.answer(
|
||||||
_(
|
_(
|
||||||
"admin_user_delete_already_removed",
|
"admin_user_delete_already_removed",
|
||||||
default="ℹ️ Пользователь уже удален.",
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
await state.clear()
|
await state.clear()
|
||||||
@@ -859,7 +828,6 @@ async def process_delete_user_confirmation_handler(message: types.Message,
|
|||||||
await message.answer(
|
await message.answer(
|
||||||
_(
|
_(
|
||||||
"admin_user_delete_success",
|
"admin_user_delete_success",
|
||||||
default="✅ Пользователь {user_id} удален из бота и панели.",
|
|
||||||
user_id=hcode(str(target_user_id)),
|
user_id=hcode(str(target_user_id)),
|
||||||
),
|
),
|
||||||
parse_mode="HTML",
|
parse_mode="HTML",
|
||||||
@@ -870,7 +838,6 @@ async def process_delete_user_confirmation_handler(message: types.Message,
|
|||||||
await message.answer(
|
await message.answer(
|
||||||
_(
|
_(
|
||||||
"admin_user_delete_error",
|
"admin_user_delete_error",
|
||||||
default="❌ Не удалось завершить удаление пользователя. Попробуйте позже.",
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
@@ -903,8 +870,7 @@ async def process_subscription_days_handler(message: types.Message, state: FSMCo
|
|||||||
raise ValueError("Invalid days count")
|
raise ValueError("Invalid days count")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_user_invalid_days",
|
"admin_user_invalid_days"
|
||||||
default="❌ Неверное количество дней. Введите число от 1 до 3650."
|
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -918,7 +884,6 @@ async def process_subscription_days_handler(message: types.Message, state: FSMCo
|
|||||||
await session.commit()
|
await session.commit()
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_user_subscription_added_success",
|
"admin_user_subscription_added_success",
|
||||||
default="✅ Успешно добавлено {days} дней подписки пользователю {user_id}",
|
|
||||||
days=days_to_add,
|
days=days_to_add,
|
||||||
user_id=target_user_id
|
user_id=target_user_id
|
||||||
))
|
))
|
||||||
@@ -945,16 +910,14 @@ async def process_subscription_days_handler(message: types.Message, state: FSMCo
|
|||||||
else:
|
else:
|
||||||
await session.rollback()
|
await session.rollback()
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_user_subscription_added_error",
|
"admin_user_subscription_added_error"
|
||||||
default="❌ Ошибка добавления дней подписки"
|
|
||||||
))
|
))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error adding subscription days for user {target_user_id}: {e}")
|
logging.error(f"Error adding subscription days for user {target_user_id}: {e}")
|
||||||
await session.rollback()
|
await session.rollback()
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_user_subscription_added_error",
|
"admin_user_subscription_added_error"
|
||||||
default="❌ Ошибка добавления дней подписки"
|
|
||||||
))
|
))
|
||||||
|
|
||||||
await state.clear()
|
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()
|
text = (message.text or message.caption or "").strip()
|
||||||
if len(text) > 4000:
|
if len(text) > 4000:
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_user_message_too_long",
|
"admin_user_message_too_long"
|
||||||
default="❌ Сообщение слишком длинное (максимум 4000 символов)"
|
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -998,16 +960,14 @@ async def process_direct_message_handler(message: types.Message, state: FSMConte
|
|||||||
|
|
||||||
# Prepare admin signature and get content
|
# Prepare admin signature and get content
|
||||||
admin_signature = _(
|
admin_signature = _(
|
||||||
"admin_direct_message_signature",
|
"admin_direct_message_signature"
|
||||||
default="\n\n---\n💬 Сообщение от администратора"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
content = get_message_content(message)
|
content = get_message_content(message)
|
||||||
|
|
||||||
if not content.text and not content.file_id:
|
if not content.text and not content.file_id:
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_direct_empty_message",
|
"admin_direct_empty_message"
|
||||||
default="❌ Пустое сообщение. Отправьте текст или медиа."
|
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1026,7 +986,6 @@ async def process_direct_message_handler(message: types.Message, state: FSMConte
|
|||||||
except TelegramBadRequest as e:
|
except TelegramBadRequest as e:
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_broadcast_invalid_html",
|
"admin_broadcast_invalid_html",
|
||||||
default="❌ Некорректный HTML в сообщении. Пожалуйста, отправьте корректный HTML (поддерживаются теги Telegram) или уберите теги.\nОшибка: {error}",
|
|
||||||
error=str(e),
|
error=str(e),
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
@@ -1034,7 +993,6 @@ async def process_direct_message_handler(message: types.Message, state: FSMConte
|
|||||||
# Confirm to admin
|
# Confirm to admin
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_user_message_sent_success",
|
"admin_user_message_sent_success",
|
||||||
default="✅ Сообщение отправлено пользователю {user_id}",
|
|
||||||
user_id=target_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:
|
except Exception as e:
|
||||||
logging.error(f"Error sending direct message to user {target_user_id}: {e}")
|
logging.error(f"Error sending direct message to user {target_user_id}: {e}")
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_user_message_sent_error",
|
"admin_user_message_sent_error"
|
||||||
default="❌ Ошибка отправки сообщения"
|
|
||||||
))
|
))
|
||||||
|
|
||||||
await state.clear()
|
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)
|
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
|
||||||
|
|
||||||
prompt_text = _(
|
prompt_text = _(
|
||||||
"admin_ban_user_prompt",
|
"admin_ban_user_prompt"
|
||||||
default="🚫 Блокировка пользователя\n\nВведите ID пользователя или @username для блокировки:"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -1113,8 +1069,7 @@ async def unban_user_prompt_handler(callback: types.CallbackQuery,
|
|||||||
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
|
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
|
||||||
|
|
||||||
prompt_text = _(
|
prompt_text = _(
|
||||||
"admin_unban_user_prompt",
|
"admin_unban_user_prompt"
|
||||||
default="✅ Разблокировка пользователя\n\nВведите ID пользователя или @username для разблокировки:"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -1150,8 +1105,7 @@ async def view_banned_users_handler(callback: types.CallbackQuery,
|
|||||||
|
|
||||||
if not banned_users:
|
if not banned_users:
|
||||||
message_text = _(
|
message_text = _(
|
||||||
"admin_banned_users_empty",
|
"admin_banned_users_empty"
|
||||||
default="📋 Заблокированные пользователи\n\nСписок пуст"
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
user_list = []
|
user_list = []
|
||||||
@@ -1163,7 +1117,6 @@ async def view_banned_users_handler(callback: types.CallbackQuery,
|
|||||||
|
|
||||||
message_text = _(
|
message_text = _(
|
||||||
"admin_banned_users_list",
|
"admin_banned_users_list",
|
||||||
default="📋 Заблокированные пользователи ({count}):\n\n{users}",
|
|
||||||
count=len(banned_users),
|
count=len(banned_users),
|
||||||
users="\n".join(user_list)
|
users="\n".join(user_list)
|
||||||
)
|
)
|
||||||
@@ -1208,7 +1161,6 @@ async def process_ban_user_handler(message: types.Message, state: FSMContext,
|
|||||||
if not user_model:
|
if not user_model:
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_user_not_found",
|
"admin_user_not_found",
|
||||||
default="❌ Пользователь не найден: {input}",
|
|
||||||
input=hcode(input_text)
|
input=hcode(input_text)
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
@@ -1217,8 +1169,7 @@ async def process_ban_user_handler(message: types.Message, state: FSMContext,
|
|||||||
# Check if user is already banned
|
# Check if user is already banned
|
||||||
if user_model.is_banned:
|
if user_model.is_banned:
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_user_already_banned",
|
"admin_user_already_banned"
|
||||||
default="⚠️ Пользователь уже заблокирован"
|
|
||||||
))
|
))
|
||||||
await state.clear()
|
await state.clear()
|
||||||
return
|
return
|
||||||
@@ -1234,7 +1185,6 @@ async def process_ban_user_handler(message: types.Message, state: FSMContext,
|
|||||||
|
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_user_ban_success",
|
"admin_user_ban_success",
|
||||||
default="✅ Пользователь {input} заблокирован",
|
|
||||||
input=hcode(input_text)
|
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}")
|
logging.error(f"Error banning user {user_model.user_id}: {e}")
|
||||||
await session.rollback()
|
await session.rollback()
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_user_ban_error",
|
"admin_user_ban_error"
|
||||||
default="❌ Ошибка блокировки пользователя"
|
|
||||||
))
|
))
|
||||||
|
|
||||||
await state.clear()
|
await state.clear()
|
||||||
@@ -1279,7 +1228,6 @@ async def process_unban_user_handler(message: types.Message, state: FSMContext,
|
|||||||
if not user_model:
|
if not user_model:
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_user_not_found",
|
"admin_user_not_found",
|
||||||
default="❌ Пользователь не найден: {input}",
|
|
||||||
input=hcode(input_text)
|
input=hcode(input_text)
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
@@ -1288,8 +1236,7 @@ async def process_unban_user_handler(message: types.Message, state: FSMContext,
|
|||||||
# Check if user is not banned
|
# Check if user is not banned
|
||||||
if not user_model.is_banned:
|
if not user_model.is_banned:
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_user_not_banned",
|
"admin_user_not_banned"
|
||||||
default="⚠️ Пользователь не заблокирован"
|
|
||||||
))
|
))
|
||||||
await state.clear()
|
await state.clear()
|
||||||
return
|
return
|
||||||
@@ -1305,7 +1252,6 @@ async def process_unban_user_handler(message: types.Message, state: FSMContext,
|
|||||||
|
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_user_unban_success",
|
"admin_user_unban_success",
|
||||||
default="✅ Пользователь {input} разблокирован",
|
|
||||||
input=hcode(input_text)
|
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}")
|
logging.error(f"Error unbanning user {user_model.user_id}: {e}")
|
||||||
await session.rollback()
|
await session.rollback()
|
||||||
await message.answer(_(
|
await message.answer(_(
|
||||||
"admin_user_unban_error",
|
"admin_user_unban_error"
|
||||||
default="❌ Ошибка разблокировки пользователя"
|
|
||||||
))
|
))
|
||||||
|
|
||||||
await state.clear()
|
await state.clear()
|
||||||
@@ -1357,7 +1302,7 @@ async def user_card_from_list_handler(callback: types.CallbackQuery,
|
|||||||
user.referred_by_id
|
user.referred_by_id
|
||||||
)
|
)
|
||||||
keyboard.button(
|
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}"
|
callback_data=f"admin_action:users_list:{page}"
|
||||||
)
|
)
|
||||||
quick_links_width = 2 if user.referred_by_id else 1
|
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)
|
# Create message content (use same text as friend message)
|
||||||
message_text = _(
|
message_text = _(
|
||||||
"referral_friend_message",
|
"referral_friend_message",
|
||||||
default="🚀 Привет! Попробуй этот VPN - быстрый, надёжный и доступный!\n\n"
|
|
||||||
"🎁 По моей ссылке тебе дадут бонусные дни к подписке!\n\n{referral_link}",
|
|
||||||
referral_link=referral_link
|
referral_link=referral_link
|
||||||
)
|
)
|
||||||
|
|
||||||
return InlineQueryResultArticle(
|
return InlineQueryResultArticle(
|
||||||
id="referral_link",
|
id="referral_link",
|
||||||
title=_(
|
title=_(
|
||||||
"inline_referral_title",
|
"inline_referral_title"
|
||||||
default="🎁 Пригласить друга"
|
|
||||||
),
|
),
|
||||||
description=_(
|
description=_(
|
||||||
"inline_referral_description",
|
"inline_referral_description"
|
||||||
default="Поделиться реферальной ссылкой для получения бонусов"
|
|
||||||
),
|
),
|
||||||
input_message_content=InputTextMessageContent(
|
input_message_content=InputTextMessageContent(
|
||||||
message_text=message_text,
|
message_text=message_text,
|
||||||
@@ -167,13 +163,6 @@ async def create_user_stats_result(session: AsyncSession, i18n_instance, lang: s
|
|||||||
|
|
||||||
stats_text = _(
|
stats_text = _(
|
||||||
"inline_user_stats_message",
|
"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'],
|
total=user_stats['total_users'],
|
||||||
active_today=user_stats['active_today'],
|
active_today=user_stats['active_today'],
|
||||||
paid=user_stats['paid_subscriptions'],
|
paid=user_stats['paid_subscriptions'],
|
||||||
@@ -186,12 +175,10 @@ async def create_user_stats_result(session: AsyncSession, i18n_instance, lang: s
|
|||||||
return InlineQueryResultArticle(
|
return InlineQueryResultArticle(
|
||||||
id="admin_user_stats",
|
id="admin_user_stats",
|
||||||
title=_(
|
title=_(
|
||||||
"inline_admin_user_stats_title",
|
"inline_admin_user_stats_title"
|
||||||
default="📊 Статистика пользователей"
|
|
||||||
),
|
),
|
||||||
description=_(
|
description=_(
|
||||||
"inline_user_stats_description",
|
"inline_user_stats_description",
|
||||||
default="Всего: {total}, Платных: {active}",
|
|
||||||
total=user_stats['total_users'],
|
total=user_stats['total_users'],
|
||||||
active=user_stats['paid_subscriptions']
|
active=user_stats['paid_subscriptions']
|
||||||
),
|
),
|
||||||
@@ -217,12 +204,6 @@ async def create_financial_stats_result(session: AsyncSession, i18n_instance, la
|
|||||||
|
|
||||||
stats_text = _(
|
stats_text = _(
|
||||||
"inline_financial_stats_message",
|
"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=financial_stats['today_revenue'],
|
||||||
today_count=financial_stats['today_payments_count'],
|
today_count=financial_stats['today_payments_count'],
|
||||||
week=financial_stats['week_revenue'],
|
week=financial_stats['week_revenue'],
|
||||||
@@ -233,12 +214,10 @@ async def create_financial_stats_result(session: AsyncSession, i18n_instance, la
|
|||||||
return InlineQueryResultArticle(
|
return InlineQueryResultArticle(
|
||||||
id="admin_financial_stats",
|
id="admin_financial_stats",
|
||||||
title=_(
|
title=_(
|
||||||
"inline_admin_financial_stats_title",
|
"inline_admin_financial_stats_title"
|
||||||
default="💰 Финансовая статистика"
|
|
||||||
),
|
),
|
||||||
description=_(
|
description=_(
|
||||||
"inline_financial_description",
|
"inline_financial_description",
|
||||||
default="Сегодня: {today} RUB",
|
|
||||||
today=f"{financial_stats['today_revenue']:.2f}"
|
today=f"{financial_stats['today_revenue']:.2f}"
|
||||||
),
|
),
|
||||||
input_message_content=InputTextMessageContent(
|
input_message_content=InputTextMessageContent(
|
||||||
@@ -311,17 +290,6 @@ async def create_system_stats_result(session: AsyncSession, i18n_instance, lang:
|
|||||||
|
|
||||||
stats_text = _(
|
stats_text = _(
|
||||||
"inline_system_stats_message",
|
"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,
|
online=online_now,
|
||||||
active=active_users,
|
active=active_users,
|
||||||
disabled=disabled_users,
|
disabled=disabled_users,
|
||||||
@@ -335,17 +303,15 @@ async def create_system_stats_result(session: AsyncSession, i18n_instance, lang:
|
|||||||
total_nodes=total_nodes
|
total_nodes=total_nodes
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
stats_text = _("inline_panel_stats_error", default="❌ Ошибка получения данных с панели")
|
stats_text = _("inline_panel_stats_error")
|
||||||
|
|
||||||
return InlineQueryResultArticle(
|
return InlineQueryResultArticle(
|
||||||
id="admin_system_stats",
|
id="admin_system_stats",
|
||||||
title=_(
|
title=_(
|
||||||
"inline_admin_system_stats_title",
|
"inline_admin_system_stats_title"
|
||||||
default="🖥 Системная статистика"
|
|
||||||
),
|
),
|
||||||
description=_(
|
description=_(
|
||||||
"inline_system_description",
|
"inline_system_description",
|
||||||
default="🟢 Онлайн: {online}, 📊 Активных: {active}",
|
|
||||||
online=online_now,
|
online=online_now,
|
||||||
active=active_users
|
active=active_users
|
||||||
),
|
),
|
||||||
@@ -359,15 +325,14 @@ async def create_system_stats_result(session: AsyncSession, i18n_instance, lang:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error creating system stats result: {e}")
|
logging.error(f"Error creating system stats result: {e}")
|
||||||
# Fallback error message
|
# Fallback error message
|
||||||
error_text = _("inline_panel_stats_error", default="❌ Ошибка получения данных с панели")
|
error_text = _("inline_panel_stats_error")
|
||||||
|
|
||||||
return InlineQueryResultArticle(
|
return InlineQueryResultArticle(
|
||||||
id="admin_system_stats",
|
id="admin_system_stats",
|
||||||
title=_(
|
title=_(
|
||||||
"inline_admin_system_stats_title",
|
"inline_admin_system_stats_title"
|
||||||
default="🖥 Системная статистика"
|
|
||||||
),
|
),
|
||||||
description=_("inline_system_error", default="Ошибка получения данных"),
|
description=_("inline_system_error"),
|
||||||
input_message_content=InputTextMessageContent(
|
input_message_content=InputTextMessageContent(
|
||||||
message_text=error_text,
|
message_text=error_text,
|
||||||
parse_mode="HTML"
|
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
|
thumbnail_url=settings.INLINE_SYSTEM_STATS_THUMBNAIL_URL
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -40,17 +40,29 @@ async def display_subscription_options(event: Union[types.Message, types.Callbac
|
|||||||
return
|
return
|
||||||
|
|
||||||
currency_symbol_val = settings.DEFAULT_CURRENCY_SYMBOL
|
currency_symbol_val = settings.DEFAULT_CURRENCY_SYMBOL
|
||||||
traffic_mode = bool(getattr(settings, "traffic_sale_mode", False))
|
traffic_packages = getattr(settings, "traffic_packages", {}) or {}
|
||||||
options = settings.traffic_packages if traffic_mode else settings.subscription_options
|
stars_traffic_packages = getattr(settings, "stars_traffic_packages", {}) or {}
|
||||||
text_content = (
|
traffic_mode = bool(getattr(settings, "traffic_sale_mode", False) or stars_traffic_packages)
|
||||||
get_text("select_traffic_package") if traffic_mode else get_text("select_subscription_period")
|
|
||||||
) if options else get_text("no_subscription_options_available")
|
|
||||||
|
|
||||||
reply_markup = (
|
if traffic_mode:
|
||||||
get_subscription_options_keyboard(options, currency_symbol_val, current_lang, i18n, traffic_mode=traffic_mode)
|
if traffic_packages:
|
||||||
if options
|
options = traffic_packages
|
||||||
else get_back_to_main_menu_markup(current_lang, i18n)
|
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
|
target_message_obj = event.message if isinstance(event, types.CallbackQuery) else event
|
||||||
if not target_message_obj:
|
if not target_message_obj:
|
||||||
@@ -108,7 +120,7 @@ async def my_subscription_command_handler(
|
|||||||
text = get_text("subscription_not_active")
|
text = get_text("subscription_not_active")
|
||||||
|
|
||||||
buy_button = InlineKeyboardButton(
|
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)
|
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=l4)
|
||||||
return get_text("payment_method_wallet_title", last4="****")
|
return get_text("payment_method_wallet_title", last4="****")
|
||||||
if 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)
|
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)
|
return get_text("payment_method_generic_title", network=network_name)
|
||||||
|
|
||||||
for m in methods:
|
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=l4)
|
||||||
return _("payment_method_wallet_title", last4="****")
|
return _("payment_method_wallet_title", last4="****")
|
||||||
if 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)
|
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)
|
return _("payment_method_generic_title", network=network_name)
|
||||||
title = _format_pm_title(m.card_network, m.card_last4)
|
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}"))
|
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=l4)
|
||||||
return _("payment_method_wallet_title", last4="****")
|
return _("payment_method_wallet_title", last4="****")
|
||||||
if 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)
|
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)
|
return _("payment_method_generic_title", network=network_name)
|
||||||
|
|
||||||
title = _format_pm_title(sel.card_network, sel.card_last4)
|
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=l4)
|
||||||
return _("payment_method_wallet_title", last4="****")
|
return _("payment_method_wallet_title", last4="****")
|
||||||
if 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)
|
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)
|
return _("payment_method_generic_title", network=network_name)
|
||||||
|
|
||||||
title = _format_pm_title(billing.card_network, billing.card_last4)
|
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=l4)
|
||||||
return get_text("payment_method_wallet_title", last4="****")
|
return get_text("payment_method_wallet_title", last4="****")
|
||||||
if 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)
|
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)
|
return get_text("payment_method_generic_title", network=network_name)
|
||||||
title = _format_pm_title(m.card_network, m.card_last4)
|
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}"))
|
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
|
pass
|
||||||
return
|
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:
|
try:
|
||||||
months = float(callback.data.split(":")[-1])
|
months = float(callback.data.split(":")[-1])
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
@@ -40,23 +42,47 @@ async def select_subscription_period_callback_handler(
|
|||||||
pass
|
pass
|
||||||
return
|
return
|
||||||
|
|
||||||
price_source = settings.traffic_packages if traffic_mode else settings.subscription_options
|
price_source = traffic_packages if traffic_mode else settings.subscription_options
|
||||||
stars_price_source = settings.stars_traffic_packages if traffic_mode else settings.stars_subscription_options
|
stars_price_source = stars_traffic_packages if traffic_mode else settings.stars_subscription_options
|
||||||
|
|
||||||
price_rub = price_source.get(months)
|
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)
|
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(
|
reply_markup = get_payment_method_keyboard(
|
||||||
months,
|
months,
|
||||||
price_rub,
|
price_rub,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ def get_admin_panel_keyboard(i18n_instance, lang: str,
|
|||||||
callback_data="admin_section:promo_marketing")
|
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")
|
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"),
|
builder.button(text=_(key="admin_stats_button"),
|
||||||
callback_data="admin_action:stats")
|
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")
|
callback_data="admin_action:view_payments")
|
||||||
builder.button(text=_(key="admin_view_logs_menu_button"),
|
builder.button(text=_(key="admin_view_logs_menu_button"),
|
||||||
callback_data="admin_action:view_logs_menu")
|
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:
|
def get_ads_menu_keyboard(i18n_instance, lang: str) -> InlineKeyboardMarkup:
|
||||||
_ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
|
_ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
|
||||||
builder = InlineKeyboardBuilder()
|
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")
|
callback_data="admin_action:ads_create")
|
||||||
builder.button(text=_(key="back_to_admin_panel_button"),
|
builder.button(text=_(key="back_to_admin_panel_button"),
|
||||||
callback_data="admin_action:main")
|
callback_data="admin_action:main")
|
||||||
@@ -156,7 +156,7 @@ def get_ads_list_keyboard(
|
|||||||
if current_page > 0:
|
if current_page > 0:
|
||||||
row.append(
|
row.append(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text="⬅️ " + _("prev_page_button", default="Prev"),
|
text="⬅️ " + _("prev_page_button"),
|
||||||
callback_data=f"admin_ads:page:{current_page - 1}",
|
callback_data=f"admin_ads:page:{current_page - 1}",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -169,14 +169,14 @@ def get_ads_list_keyboard(
|
|||||||
if current_page < total_pages - 1:
|
if current_page < total_pages - 1:
|
||||||
row.append(
|
row.append(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=_("next_page_button", default="Next") + " ➡️",
|
text=_("next_page_button") + " ➡️",
|
||||||
callback_data=f"admin_ads:page:{current_page + 1}",
|
callback_data=f"admin_ads:page:{current_page + 1}",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if row:
|
if row:
|
||||||
builder.row(*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")
|
callback_data="admin_action:ads_create")
|
||||||
builder.button(text=_(key="back_to_admin_panel_button"),
|
builder.button(text=_(key="back_to_admin_panel_button"),
|
||||||
callback_data="admin_action:main")
|
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)
|
_ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
|
||||||
builder = InlineKeyboardBuilder()
|
builder = InlineKeyboardBuilder()
|
||||||
# Dangerous action: Delete campaign
|
# 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}")
|
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}")
|
callback_data=f"admin_ads:page:{back_page}")
|
||||||
builder.button(text=_(key="back_to_admin_panel_button"),
|
builder.button(text=_(key="back_to_admin_panel_button"),
|
||||||
callback_data="admin_action:main")
|
callback_data="admin_action:main")
|
||||||
@@ -227,12 +227,12 @@ def get_logs_pagination_keyboard(
|
|||||||
if current_page > 0:
|
if current_page > 0:
|
||||||
row_buttons.append(
|
row_buttons.append(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text="⬅️ " + _("prev_page_button", default="Prev"),
|
text="⬅️ " + _("prev_page_button"),
|
||||||
callback_data=f"{base_callback_data}:{current_page - 1}"))
|
callback_data=f"{base_callback_data}:{current_page - 1}"))
|
||||||
if current_page < total_pages - 1:
|
if current_page < total_pages - 1:
|
||||||
row_buttons.append(
|
row_buttons.append(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=_("next_page_button", default="Next") + " ➡️",
|
text=_("next_page_button") + " ➡️",
|
||||||
callback_data=f"{base_callback_data}:{current_page + 1}"))
|
callback_data=f"{base_callback_data}:{current_page + 1}"))
|
||||||
|
|
||||||
if row_buttons: builder.row(*row_buttons)
|
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}")
|
callback_data=f"admin_ban_confirm:{user_id}:{banned_list_page}")
|
||||||
builder.button(
|
builder.button(
|
||||||
text=_(
|
text=_(
|
||||||
key="user_card_open_profile_button",
|
key="user_card_open_profile_button"
|
||||||
default="👤 Open profile"
|
|
||||||
),
|
),
|
||||||
url=f"tg://user?id={user_id}"
|
url=f"tg://user?id={user_id}"
|
||||||
)
|
)
|
||||||
@@ -418,16 +417,13 @@ def get_broadcast_confirmation_keyboard(lang: str,
|
|||||||
|
|
||||||
# Row: target selection (all / active / inactive)
|
# Row: target selection (all / active / inactive)
|
||||||
target_all_label = _(
|
target_all_label = _(
|
||||||
key="broadcast_target_all_button",
|
key="broadcast_target_all_button"
|
||||||
default="👥 Все"
|
|
||||||
)
|
)
|
||||||
target_active_label = _(
|
target_active_label = _(
|
||||||
key="broadcast_target_active_button",
|
key="broadcast_target_active_button"
|
||||||
default="✅ Активные"
|
|
||||||
)
|
)
|
||||||
target_inactive_label = _(
|
target_inactive_label = _(
|
||||||
key="broadcast_target_inactive_button",
|
key="broadcast_target_inactive_button"
|
||||||
default="⌛ Неактивные"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Highlight current selection with a prefix
|
# Highlight current selection with a prefix
|
||||||
@@ -449,9 +445,9 @@ def get_broadcast_confirmation_keyboard(lang: str,
|
|||||||
builder.adjust(3)
|
builder.adjust(3)
|
||||||
|
|
||||||
# Row: confirmation
|
# 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")
|
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")
|
callback_data="broadcast_final_action:cancel")
|
||||||
builder.adjust(2)
|
builder.adjust(2)
|
||||||
return builder.as_markup()
|
return builder.as_markup()
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ class NotificationService:
|
|||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=translate(
|
text=translate(
|
||||||
"log_open_profile_link",
|
"log_open_profile_link",
|
||||||
default="👤 Открыть профиль",
|
|
||||||
),
|
),
|
||||||
url=f"tg://user?id={user_id}",
|
url=f"tg://user?id={user_id}",
|
||||||
)
|
)
|
||||||
@@ -64,7 +63,6 @@ class NotificationService:
|
|||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=translate(
|
text=translate(
|
||||||
"log_open_referrer_profile_button",
|
"log_open_referrer_profile_button",
|
||||||
default="👤 Открыть профиль пригласившего",
|
|
||||||
),
|
),
|
||||||
url=f"tg://user?id={referrer_id}",
|
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}")
|
referrer_link = hd.link(str(referred_by_id), f"tg://user?id={referred_by_id}")
|
||||||
referral_text = _(
|
referral_text = _(
|
||||||
"log_referral_suffix",
|
"log_referral_suffix",
|
||||||
default=" (реферал от {referrer_link})",
|
|
||||||
referrer_link=referrer_link,
|
referrer_link=referrer_link,
|
||||||
)
|
)
|
||||||
|
|
||||||
message = _(
|
message = _(
|
||||||
"log_new_user_registration",
|
"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_id=user_id,
|
||||||
user_display=user_display,
|
user_display=user_display,
|
||||||
referral_text=referral_text,
|
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}"
|
traffic_label = str(int(traffic_gb)) if float(traffic_gb).is_integer() else f"{traffic_gb:g}"
|
||||||
message = _(
|
message = _(
|
||||||
"log_payment_received_traffic",
|
"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,
|
provider_emoji=provider_emoji,
|
||||||
user_display=user_display,
|
user_display=user_display,
|
||||||
amount=amount,
|
amount=amount,
|
||||||
@@ -266,12 +253,6 @@ class NotificationService:
|
|||||||
else:
|
else:
|
||||||
message = _(
|
message = _(
|
||||||
"log_payment_received",
|
"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,
|
provider_emoji=provider_emoji,
|
||||||
user_display=user_display,
|
user_display=user_display,
|
||||||
amount=amount,
|
amount=amount,
|
||||||
@@ -301,11 +282,6 @@ class NotificationService:
|
|||||||
|
|
||||||
message = _(
|
message = _(
|
||||||
"log_promo_activation",
|
"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,
|
user_display=user_display,
|
||||||
promo_code=promo_code,
|
promo_code=promo_code,
|
||||||
bonus_days=bonus_days,
|
bonus_days=bonus_days,
|
||||||
@@ -332,10 +308,6 @@ class NotificationService:
|
|||||||
|
|
||||||
message = _(
|
message = _(
|
||||||
"log_trial_activation",
|
"log_trial_activation",
|
||||||
default="🆓 <b>Активирован триал</b>\n\n"
|
|
||||||
"👤 Пользователь: {user_display}\n"
|
|
||||||
"⏰ Действует до: <b>{end_date}</b>\n"
|
|
||||||
"🕐 Время: {timestamp}",
|
|
||||||
user_display=user_display,
|
user_display=user_display,
|
||||||
end_date=end_date.strftime("%Y-%m-%d %H:%M"),
|
end_date=end_date.strftime("%Y-%m-%d %H:%M"),
|
||||||
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
@@ -364,12 +336,6 @@ class NotificationService:
|
|||||||
|
|
||||||
message = _(
|
message = _(
|
||||||
"log_panel_sync",
|
"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_emoji=status_emoji,
|
||||||
status=status,
|
status=status,
|
||||||
users_processed=users_processed,
|
users_processed=users_processed,
|
||||||
@@ -400,11 +366,6 @@ class NotificationService:
|
|||||||
|
|
||||||
message = _(
|
message = _(
|
||||||
"log_suspicious_promo",
|
"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_display=hd.quote(user_display),
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
suspicious_input=hd.quote(suspicious_input),
|
suspicious_input=hd.quote(suspicious_input),
|
||||||
|
|||||||
+1
-1
@@ -423,7 +423,7 @@ class Settings(BaseSettings):
|
|||||||
@property
|
@property
|
||||||
def traffic_sale_mode(self) -> bool:
|
def traffic_sale_mode(self) -> bool:
|
||||||
"""When true, the bot sells traffic packages instead of time-based subscriptions."""
|
"""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]:
|
def referral_bonus_inviter(self) -> Dict[int, int]:
|
||||||
bonuses: Dict[int, int] = {}
|
bonuses: Dict[int, int] = {}
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
"menu_activate_trial_button": "🆓 Пробный период",
|
"menu_activate_trial_button": "🆓 Пробный период",
|
||||||
"menu_subscribe_inline": "🚀 Купить",
|
"menu_subscribe_inline": "🚀 Купить",
|
||||||
"menu_my_subscription_inline": "🔐 Моя подписка",
|
"menu_my_subscription_inline": "🔐 Моя подписка",
|
||||||
"no_subscription_options_available": "Выдача подписки не натроена администратором бота",
|
"no_subscription_options_available": "Выдача подписки не настроена администратором бота",
|
||||||
"menu_referral_inline": "🎁 Рефералы",
|
"menu_referral_inline": "🎁 Рефералы",
|
||||||
"referral_no_bonuses_configured": "Извините, реферальная программа в данный момент отключена",
|
"referral_no_bonuses_configured": "Извините, реферальная программа в данный момент отключена",
|
||||||
"menu_apply_promo_button": "🎟 Промокод",
|
"menu_apply_promo_button": "🎟 Промокод",
|
||||||
|
|||||||
Reference in New Issue
Block a user