diff --git a/handlers/admin/clusters/clusters_handler.py b/handlers/admin/clusters/clusters_handler.py
index 2c60e25c..9d9693a5 100644
--- a/handlers/admin/clusters/clusters_handler.py
+++ b/handlers/admin/clusters/clusters_handler.py
@@ -620,7 +620,7 @@ async def handle_sync_cluster(
raise Exception("Не удалось авторизоваться в Remnawave")
traffic_limit_bytes = 0
- hwid_limit = None
+ hwid_limit = 0
if key["tariff_id"]:
tariff = await session.get(Tariff, key["tariff_id"])
if tariff:
diff --git a/handlers/admin/panel/keyboard.py b/handlers/admin/panel/keyboard.py
index a3380112..f88d05d7 100644
--- a/handlers/admin/panel/keyboard.py
+++ b/handlers/admin/panel/keyboard.py
@@ -24,7 +24,7 @@ def build_panel_kb(admin_role: str) -> InlineKeyboardMarkup:
callback_data=AdminPanelCallback(action="search_user").pack(),
)
builder.button(
- text="🔑 Поиск по ключу",
+ text="🔑 Поиск по подписке",
callback_data=AdminPanelCallback(action="search_key").pack(),
)
diff --git a/handlers/admin/users/keyboard.py b/handlers/admin/users/keyboard.py
index 94cf7b8f..96b62229 100644
--- a/handlers/admin/users/keyboard.py
+++ b/handlers/admin/users/keyboard.py
@@ -36,63 +36,79 @@ def build_user_edit_kb(
builder = InlineKeyboardBuilder()
current_time = datetime.now(tz=timezone.utc)
- builder.button(
- text="➕ Создать ключ",
- callback_data=AdminUserEditorCallback(
- action="users_create_key", tg_id=tg_id
- ).pack(),
+ builder.row(
+ InlineKeyboardButton(
+ text="➕ Создать подписку",
+ callback_data=AdminUserEditorCallback(
+ action="users_create_key", tg_id=tg_id
+ ).pack(),
+ )
)
for record in key_records:
email = record.email
expiry = datetime.fromtimestamp(record.expiry_time / 1000, tz=timezone.utc)
days = (expiry - current_time).days
- builder.button(
- text=f"🔑 {email} ({'<1' if days < 1 else days} дн.)",
- callback_data=AdminUserEditorCallback(
- action="users_key_edit", tg_id=tg_id, data=str(email)
- ).pack(),
+ builder.row(
+ InlineKeyboardButton(
+ text=f"🔑 {email} ({'<1' if days < 1 else days} дн.)",
+ callback_data=AdminUserEditorCallback(
+ action="users_key_edit", tg_id=tg_id, data=str(email)
+ ).pack(),
+ )
)
- builder.button(
- text="✉️ Сообщение",
- callback_data=AdminUserEditorCallback(
- action="users_send_message", tg_id=tg_id
- ).pack(),
+ builder.row(
+ InlineKeyboardButton(
+ text="✉️ Сообщение",
+ callback_data=AdminUserEditorCallback(
+ action="users_send_message", tg_id=tg_id
+ ).pack(),
+ ),
+ InlineKeyboardButton(
+ text="💸 Баланс",
+ callback_data=AdminUserEditorCallback(
+ action="users_balance_edit", tg_id=tg_id
+ ).pack(),
+ ),
)
- builder.button(
- text="💸 Изменить баланс",
- callback_data=AdminUserEditorCallback(
- action="users_balance_edit", tg_id=tg_id
- ).pack(),
+
+ builder.row(
+ InlineKeyboardButton(
+ text="🤝 Выгрузить рефералов",
+ callback_data=AdminUserEditorCallback(
+ action="users_export_referrals", tg_id=tg_id
+ ).pack(),
+ )
)
- builder.button(
- text="🤝 Выгрузить рефералов",
- callback_data=AdminUserEditorCallback(
- action="users_export_referrals", tg_id=tg_id
- ).pack(),
+
+ builder.row(
+ InlineKeyboardButton(
+ text="♻️ Восстановить триал",
+ callback_data=AdminUserEditorCallback(
+ action="users_trial_restore", tg_id=tg_id
+ ).pack(),
+ )
)
- builder.button(
- text="♻️ Восстановить триал",
- callback_data=AdminUserEditorCallback(
- action="users_trial_restore", tg_id=tg_id
- ).pack(),
- )
- builder.button(
- text="❌ Удалить клиента",
- callback_data=AdminUserEditorCallback(
- action="users_delete_user", tg_id=tg_id
- ).pack(),
- )
- builder.button(
- text="✅ Разблокировать" if is_banned else "🚫 Заблокировать",
- callback_data=AdminUserEditorCallback(
- action="users_unban" if is_banned else "users_ban", tg_id=tg_id
- ).pack(),
+
+ builder.row(
+ InlineKeyboardButton(
+ text="❌ Удалить",
+ callback_data=AdminUserEditorCallback(
+ action="users_delete_user", tg_id=tg_id
+ ).pack(),
+ ),
+ InlineKeyboardButton(
+ text="✅ Разблокировать" if is_banned else "🚫 Заблокировать",
+ callback_data=AdminUserEditorCallback(
+ action="users_unban" if is_banned else "users_ban", tg_id=tg_id
+ ).pack(),
+ ),
)
+
builder.row(build_editor_btn("🔄 Обновить данные", tg_id, edit=True))
builder.row(build_admin_back_btn())
- builder.adjust(1)
+
return builder.as_markup()
diff --git a/handlers/admin/users/users_handler.py b/handlers/admin/users/users_handler.py
index 99853d94..7c9a9886 100644
--- a/handlers/admin/users/users_handler.py
+++ b/handlers/admin/users/users_handler.py
@@ -595,7 +595,15 @@ async def handle_key_edit(
key_value = key_details.get("key") or key_details.get("remnawave_link") or "—"
alias = key_details.get("alias")
+ utc_tz = pytz.utc
+ created_at_raw = key_details.get("created_at")
+ if created_at_raw:
+ created_at_dt = datetime.fromtimestamp(int(created_at_raw) / 1000, tz=utc_tz).astimezone(MOSCOW_TZ)
+ created_at = created_at_dt.strftime("%d %B %Y года %H:%M")
+ else:
+ created_at = "—"
+ expiry_date = key_details.get("expiry_date") or "—"
tariff_name = "—"
if key_details.get("tariff_id"):
result = await session.execute(
@@ -606,16 +614,18 @@ async def handle_key_edit(
tariff_name = f"{row[0]} ({row[1]})"
text = (
- f"🔑 Информация о ключе"
- f"\n\n{key_value}"
- f"\n\n⏰ Дата истечения: {key_details['expiry_date']} (UTC)"
- f"\n🌐 Кластер: {key_details['cluster_name']}"
- f"\n🆔 ID клиента: {key_details['tg_id']}"
- f"\n📦 Тариф: {tariff_name}"
+ "🔑 Информация о подписке\n\n"
+ "
" + f"🔗 Ключ: {key_value}\n" + f"📆 Создан: {created_at} (МСК)\n" + f"⏰ Истекает: {expiry_date} (МСК)\n" + f"🌐 Кластер: {key_details.get('cluster_name', '—')}\n" + f"🆔 ID клиента: {key_details.get('tg_id', '—')}\n" + f"📦 Тариф: {tariff_name}\n" ) - if alias: - text += f"\n🏷️ Имя ключа: {alias}" + text += f"🏷️ Имя подписки: {alias}\n" + text += "" if not update or not callback_data.edit: await callback_query.message.edit_text( @@ -1252,6 +1262,23 @@ async def process_user_search( result_ref = await session.execute(stmt_ref_count) referral_count = result_ref.scalar_one() + stmt_ref_by = ( + select(Referral.referrer_tg_id) + .where(Referral.referred_tg_id == tg_id) + .limit(1) + ) + result_ref_by = await session.execute(stmt_ref_by) + referrer_tg_id = result_ref_by.scalar_one_or_none() + + referrer_text = "" + if referrer_tg_id: + stmt_referrer = select(User.username).where(User.tg_id == referrer_tg_id) + result_referrer = await session.execute(stmt_referrer) + ref_username = result_referrer.scalar_one_or_none() + if ref_username: + referrer_text = f"\n🤝 Пригласил: @{ref_username}" + else: + referrer_text = f"\n🤝 Пригласил: {referrer_tg_id}" stmt_keys = select(Key).where(Key.tg_id == tg_id) result_keys = await session.execute(stmt_keys) key_records = result_keys.scalars().all() @@ -1270,14 +1297,14 @@ async def process_user_search( full_name = user_obj.first_name if user_obj else None text = ( - f"📊 Информация о пользователе\n" + f"📊 Информация о пользователе\n\n" f"
" f"🆔 ID: {tg_id}\n" f"📄 Логин: @{username}{f' ({full_name})' if full_name else ''}\n" f"📅 Дата регистрации: {created_at_str}\n" f"🏃 Дата активности: {updated_at_str}\n" f"💰 Баланс: {balance}\n" - f"👥 Количество рефералов: {referral_count}" + f"👥 Количество рефералов: {referral_count}{referrer_text}" f"" ) diff --git a/handlers/buttons.py b/handlers/buttons.py index 91e5116d..4eb79b97 100644 --- a/handlers/buttons.py +++ b/handlers/buttons.py @@ -94,3 +94,4 @@ STARS_BOT = "🤖 Бот для покупки звезд" WATA_RU = "🇷🇺 WATA: Карты РФ" WATA_SBP = "🏦 WATA: СБП" WATA_INT = "🌍 WATA: Международные карты" +STARS_BOT_URL = "https://t.me/PremiumBot" diff --git a/handlers/keys/key_mode/key_cluster_mode.py b/handlers/keys/key_mode/key_cluster_mode.py index 8cfde5ad..81a72635 100644 --- a/handlers/keys/key_mode/key_cluster_mode.py +++ b/handlers/keys/key_mode/key_cluster_mode.py @@ -82,7 +82,7 @@ async def key_cluster_mode( traffic_limit_gb = None if is_trial: - device_limit = TRIAL_CONFIG.get("hwid_limit", 1) + device_limit = TRIAL_CONFIG.get("hwid_limit", 0) traffic_limit_gb = TRIAL_CONFIG.get("traffic_limit_gb", 100) elif plan: tariff = await get_tariff_by_id(session, plan) @@ -209,7 +209,7 @@ async def key_cluster_mode( final_link, tariff_name=tariff_duration, traffic_limit=TRIAL_CONFIG.get("traffic_limit_gb", 100), - device_limit=TRIAL_CONFIG.get("hwid_limit", 1) + device_limit=TRIAL_CONFIG.get("hwid_limit", 0) ) else: tariff_duration = tariff_info["name"] diff --git a/handlers/keys/key_mode/key_country_mode.py b/handlers/keys/key_mode/key_country_mode.py index 05e8f2c9..4c117480 100644 --- a/handlers/keys/key_mode/key_country_mode.py +++ b/handlers/keys/key_mode/key_country_mode.py @@ -372,7 +372,7 @@ async def finalize_key_creation( if is_trial: traffic_limit_bytes = int(TRIAL_CONFIG.get("traffic_limit_gb", 100)) * 1024**3 - device_limit = TRIAL_CONFIG.get("hwid_limit", 1) + device_limit = TRIAL_CONFIG.get("hwid_limit", 0) elif data.get("tariff_id") or tariff_id: tariff_id = data.get("tariff_id") or tariff_id result = await session.execute(select(Tariff).where(Tariff.id == tariff_id)) @@ -567,7 +567,7 @@ async def finalize_key_creation( link_to_show, tariff_name=tariff_duration, traffic_limit=TRIAL_CONFIG.get("traffic_limit_gb", 100), - device_limit=TRIAL_CONFIG.get("hwid_limit", 1) + device_limit=TRIAL_CONFIG.get("hwid_limit", 0) ) else: tariff_duration = tariff_info["name"] if tariff_info else None diff --git a/handlers/keys/key_utils.py b/handlers/keys/key_utils.py index c893c2ef..4a0d726e 100644 --- a/handlers/keys/key_utils.py +++ b/handlers/keys/key_utils.py @@ -31,7 +31,7 @@ async def create_key_on_cluster( plan: int = None, session: AsyncSession = None, remnawave_link: str = None, - hwid_limit: int = None, + hwid_limit: int = 0, traffic_limit_bytes: int = None, is_trial: bool = False, ): @@ -286,7 +286,7 @@ async def renew_key_in_cluster( new_expiry_time: int, total_gb: int, session: AsyncSession, - hwid_device_limit: int = None, + hwid_device_limit: int = 0, reset_traffic: bool = True, ): try: diff --git a/handlers/payments/stars_pay.cpython-312-x86_64-linux-gnu.so b/handlers/payments/stars_pay.cpython-312-x86_64-linux-gnu.so index 108fcb93..8ee5834b 100644 Binary files a/handlers/payments/stars_pay.cpython-312-x86_64-linux-gnu.so and b/handlers/payments/stars_pay.cpython-312-x86_64-linux-gnu.so differ