From 0385e158158f744412d1f6a682eba917df2e2b63 Mon Sep 17 00:00:00 2001 From: Vladless Date: Sun, 16 Nov 2025 05:08:48 +0300 Subject: [PATCH] freezing subscriptions for admins/ organizing selection in country mode --- handlers/admin/users/keyboard.py | 20 ++- handlers/admin/users/users_keys.py | 140 ++++++++++++++++++++- handlers/keys/key_mode/key_country_mode.py | 27 ++-- 3 files changed, 175 insertions(+), 12 deletions(-) diff --git a/handlers/admin/users/keyboard.py b/handlers/admin/users/keyboard.py index 2026ee7d..85ed9f36 100644 --- a/handlers/admin/users/keyboard.py +++ b/handlers/admin/users/keyboard.py @@ -9,7 +9,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from config import HWID_RESET_BUTTON from database import get_clusters from database.models import Key, Tariff -from handlers.buttons import BACK +from handlers.buttons import BACK, FREEZE, UNFREEZE from handlers.utils import format_days from hooks.hook_buttons import insert_hook_buttons from hooks.hooks import run_hooks @@ -246,6 +246,8 @@ def build_user_key_kb(tg_id: int, email: str) -> InlineKeyboardMarkup: def build_key_edit_kb(key_details: dict, email: str) -> InlineKeyboardMarkup: builder = InlineKeyboardBuilder() + is_frozen = key_details.get("is_frozen") if isinstance(key_details, dict) else getattr(key_details, "is_frozen", False) + builder.button( text="⏳ Время истечения", callback_data=AdminUserEditorCallback( @@ -274,6 +276,22 @@ def build_key_edit_kb(key_details: dict, email: str) -> InlineKeyboardMarkup: action="users_reset_traffic", data=email, tg_id=key_details["tg_id"] ).pack(), ) + + if is_frozen: + builder.button( + text=UNFREEZE, + callback_data=AdminUserEditorCallback( + action="users_unfreeze", data=email, tg_id=key_details["tg_id"] + ).pack(), + ) + else: + builder.button( + text=FREEZE, + callback_data=AdminUserEditorCallback( + action="users_freeze", data=email, tg_id=key_details["tg_id"] + ).pack(), + ) + if HWID_RESET_BUTTON: builder.button( text="💻 HWID", diff --git a/handlers/admin/users/users_keys.py b/handlers/admin/users/users_keys.py index 3efa7295..047b0c13 100644 --- a/handlers/admin/users/users_keys.py +++ b/handlers/admin/users/users_keys.py @@ -1,4 +1,5 @@ import asyncio +import time import uuid from datetime import datetime, timedelta, timezone @@ -21,6 +22,8 @@ from database import ( get_servers, get_tariff_by_id, get_tariffs_for_cluster, + mark_key_as_frozen, + mark_key_as_unfrozen, update_key_expiry, ) from database.models import Key, Server, Tariff @@ -31,9 +34,10 @@ from handlers.keys.operations import ( get_user_traffic, renew_key_in_cluster, reset_traffic_in_cluster, + toggle_client_on_cluster, update_subscription, ) -from handlers.utils import generate_random_email +from handlers.utils import generate_random_email, handle_error from logger import logger from ..panel.keyboard import AdminPanelCallback, build_admin_back_btn, build_admin_back_kb @@ -72,7 +76,7 @@ async def handle_key_edit( if not key_obj: await callback_query.message.edit_text( - text="🚫 Информация о ключе не найдена.", + text="🚫 Информация о подписке не найдена.", reply_markup=build_editor_kb(callback_data.tg_id), ) return @@ -983,6 +987,138 @@ async def handle_reset_traffic( ) +@router.callback_query( + AdminUserEditorCallback.filter(F.action == "users_freeze"), + IsAdminFilter(), +) +async def handle_admin_freeze_subscription( + callback_query: CallbackQuery, + callback_data: AdminUserEditorCallback, + session: AsyncSession, +): + tg_id = callback_data.tg_id + email = str(callback_data.data) + + try: + record = await get_key_details(session, email) + if not record: + await callback_query.message.edit_text( + text="🚫 Информация о ключе не найдена.", + reply_markup=build_editor_kb(tg_id), + ) + return + + client_id = record["client_id"] + cluster_id = record["server_id"] + + result = await toggle_client_on_cluster(cluster_id, email, client_id, enable=False, session=session) + if result["status"] != "success": + text_error = ( + "Произошла ошибка при заморозке подписки.\n" + f"Детали: {result.get('error') or result.get('results')}" + ) + await callback_query.message.edit_text( + text_error, + reply_markup=build_editor_kb(tg_id, True), + ) + return + + now_ms = int(time.time() * 1000) + time_left = record["expiry_time"] - now_ms + if time_left < 0: + time_left = 0 + + await mark_key_as_frozen(session, record["tg_id"], client_id, time_left) + await session.commit() + + await callback_query.answer("✅ Подписка заморожена") + + await handle_key_edit( + callback_query=callback_query, + callback_data=callback_data, + session=session, + update=False, + ) + except Exception as e: + await handle_error(tg_id, callback_query, f"Ошибка при заморозке подписки: {e}") + + +@router.callback_query( + AdminUserEditorCallback.filter(F.action == "users_unfreeze"), + IsAdminFilter(), +) +async def handle_admin_unfreeze_subscription( + callback_query: CallbackQuery, + callback_data: AdminUserEditorCallback, + session: AsyncSession, +): + tg_id = callback_data.tg_id + email = str(callback_data.data) + + try: + record = await get_key_details(session, email) + if not record: + await callback_query.message.edit_text( + text="🚫 Информация о ключе не найдена.", + reply_markup=build_editor_kb(tg_id), + ) + return + + client_id = record["client_id"] + cluster_id = record["server_id"] + + result = await toggle_client_on_cluster(cluster_id, email, client_id, enable=True, session=session) + if result["status"] != "success": + text_error = ( + "Произошла ошибка при включении подписки.\n" + f"Детали: {result.get('error') or result.get('results')}" + ) + await callback_query.message.edit_text( + text_error, + reply_markup=build_editor_kb(tg_id, True), + ) + return + + tariff = await get_tariff_by_id(session, record["tariff_id"]) if record.get("tariff_id") else None + if not tariff: + total_gb = 0 + hwid_limit = 0 + else: + total_gb = int(tariff.get("traffic_limit") or 0) + hwid_limit = int(tariff.get("device_limit") or 0) + + now_ms = int(time.time() * 1000) + leftover = record["expiry_time"] + if leftover < 0: + leftover = 0 + new_expiry_time = now_ms + leftover + + await mark_key_as_unfrozen(session, record["tg_id"], client_id, new_expiry_time) + await session.commit() + + await renew_key_in_cluster( + cluster_id=cluster_id, + email=email, + client_id=client_id, + new_expiry_time=new_expiry_time, + total_gb=total_gb, + session=session, + hwid_device_limit=hwid_limit, + reset_traffic=False, + ) + + await callback_query.answer("✅ Подписка разморожена") + + await handle_key_edit( + callback_query=callback_query, + callback_data=callback_data, + session=session, + update=False, + ) + except Exception as e: + await handle_error(tg_id, callback_query, f"Ошибка при включении подписки: {e}") + + async def change_expiry_time(expiry_time: int, email: str, session: AsyncSession) -> Exception | None: result = await session.execute(select(Key.client_id, Key.tariff_id, Key.server_id).where(Key.email == email)) row = result.first() diff --git a/handlers/keys/key_mode/key_country_mode.py b/handlers/keys/key_mode/key_country_mode.py index 78cd6dca..10ddbb30 100644 --- a/handlers/keys/key_mode/key_country_mode.py +++ b/handlers/keys/key_mode/key_country_mode.py @@ -160,12 +160,16 @@ async def key_country_mode( builder = InlineKeyboardBuilder() ts = int(expiry_time.timestamp()) - for server_name in available_servers: - if old_key_name: - callback_data = f"select_country|{server_name}|{ts}|{old_key_name}" - else: - callback_data = f"select_country|{server_name}|{ts}" - builder.row(InlineKeyboardButton(text=server_name, callback_data=callback_data)) + + for i in range(0, len(available_servers), 2): + row_buttons = [] + for server_name in available_servers[i : i + 2]: + if old_key_name: + callback_data = f"select_country|{server_name}|{ts}|{old_key_name}" + else: + callback_data = f"select_country|{server_name}|{ts}" + row_buttons.append(InlineKeyboardButton(text=server_name, callback_data=callback_data)) + builder.row(*row_buttons) builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) @@ -263,9 +267,14 @@ async def change_location_callback(callback_query: CallbackQuery, session: Any): return builder = InlineKeyboardBuilder() - for country in available_servers: - callback_data = f"select_country|{country}|{ts}|{old_key_name}" - builder.row(InlineKeyboardButton(text=country, callback_data=callback_data)) + + for i in range(0, len(available_servers), 2): + row_buttons = [] + for country in available_servers[i : i + 2]: + callback_data = f"select_country|{country}|{ts}|{old_key_name}" + row_buttons.append(InlineKeyboardButton(text=country, callback_data=callback_data)) + builder.row(*row_buttons) + builder.row(InlineKeyboardButton(text=BACK, callback_data=f"view_key|{old_key_name}")) await edit_or_send_message(