From b1c73dc1a6780cb69c095aa9dcbf4723561e87c2 Mon Sep 17 00:00:00 2001 From: Vladless Date: Wed, 23 Apr 2025 22:27:53 +0300 Subject: [PATCH] cancel for utm/disable servers for change location/remnawave traffic --- bot.py | 2 +- handlers/admin/ads/ads_handler.py | 22 ++++++++- handlers/admin/ads/keyboard.py | 12 ++++- handlers/keys/key_mode/key_country_mode.py | 22 +++++++-- handlers/keys/key_utils.py | 56 ++++++++++++---------- 5 files changed, 80 insertions(+), 34 deletions(-) diff --git a/bot.py b/bot.py index ce621a3c..f1535263 100644 --- a/bot.py +++ b/bot.py @@ -19,7 +19,7 @@ bot = Bot(token=API_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTM storage = MemoryStorage() dp = Dispatcher(bot=bot, storage=storage) -version = "4.2-b220422" +version = "4.2-b220424" register_middleware(dp) diff --git a/handlers/admin/ads/ads_handler.py b/handlers/admin/ads/ads_handler.py index eb8c1647..601b58bd 100644 --- a/handlers/admin/ads/ads_handler.py +++ b/handlers/admin/ads/ads_handler.py @@ -20,6 +20,7 @@ from .keyboard import ( build_ads_list_kb, build_ads_stats_kb, build_ads_delete_confirm_kb, + build_cancel_input_kb ) router = Router() @@ -40,7 +41,11 @@ async def handle_ads_menu(callback_query: CallbackQuery): @router.callback_query(AdminAdsCallback.filter(F.action == "create"), IsAdminFilter()) async def handle_ads_create(callback_query: CallbackQuery, state: FSMContext): await state.set_state(AdminAdsState.waiting_for_new_name) - await callback_query.message.edit_text("📝 Введите название новой ссылки:") + await callback_query.message.edit_text( + "📝 Введите название новой ссылки:", + reply_markup=build_cancel_input_kb() + ) + @router.message(AdminAdsState.waiting_for_new_name, IsAdminFilter()) @@ -48,7 +53,11 @@ async def handle_ads_name_input(message: Message, state: FSMContext): name = message.text.strip() await state.update_data(name=name) await state.set_state(AdminAdsState.waiting_for_new_code) - await message.answer(f"🔗 Введите код ссылки для: {name}.") + await message.answer( + f"🔗 Введите код ссылки для: {name}.", + reply_markup=build_cancel_input_kb() + ) + @router.message(AdminAdsState.waiting_for_new_code, IsAdminFilter()) @@ -153,3 +162,12 @@ def format_ads_stats(stats: dict, username_bot: str) -> str: f"Просмотр статистики и управление рекламными ссылками." ) + + +@router.callback_query(AdminAdsCallback.filter(F.action == "cancel_input"), IsAdminFilter()) +async def handle_ads_cancel_input(callback_query: CallbackQuery, state: FSMContext): + await state.clear() + await callback_query.message.edit_text( + text="📊 Аналитика рекламы:", + reply_markup=build_ads_kb() + ) diff --git a/handlers/admin/ads/keyboard.py b/handlers/admin/ads/keyboard.py index 24a07afd..fb46a531 100644 --- a/handlers/admin/ads/keyboard.py +++ b/handlers/admin/ads/keyboard.py @@ -14,6 +14,7 @@ def build_ads_kb() -> InlineKeyboardMarkup: builder.button(text="➕ Новая ссылка", callback_data=AdminAdsCallback(action="create").pack()) builder.button(text="📊 Список", callback_data=AdminAdsCallback(action="list").pack()) builder.row(build_admin_back_btn()) + builder.adjust(1) return builder.as_markup() @@ -66,4 +67,13 @@ def build_ads_delete_confirm_kb(code: str) -> InlineKeyboardMarkup: callback_data=AdminAdsCallback(action="view", code=code).pack() ) builder.adjust(1) - return builder.as_markup() \ No newline at end of file + return builder.as_markup() + + +def build_cancel_input_kb() -> InlineKeyboardMarkup: + builder = InlineKeyboardBuilder() + builder.button( + text="❌ Отмена", + callback_data=AdminAdsCallback(action="cancel_input", code="none").pack() + ) + return builder.as_markup() diff --git a/handlers/keys/key_mode/key_country_mode.py b/handlers/keys/key_mode/key_country_mode.py index f8bda9e3..95f1ac6e 100644 --- a/handlers/keys/key_mode/key_country_mode.py +++ b/handlers/keys/key_mode/key_country_mode.py @@ -450,12 +450,26 @@ async def finalize_key_creation( await state.clear() -async def check_server_availability(server_info: dict) -> bool: +async def check_server_availability(server_info: dict, session: Any) -> bool: """ - Проверяет доступность сервера (3x-ui или Remnawave). + Проверяет доступность сервера (3x-ui или Remnawave), + а также включён ли он и не превышен ли лимит ключей (max_keys). """ - panel_type = server_info.get("panel_type", "3x-ui").lower() server_name = server_info.get("server_name", "unknown") + panel_type = server_info.get("panel_type", "3x-ui").lower() + enabled = server_info.get("enabled", True) + max_keys = server_info.get("max_keys") + + if not enabled: + logger.info(f"[Ping] Сервер {server_name} выключен (enabled = FALSE).") + return False + + if max_keys is not None: + count_query = "SELECT COUNT(*) FROM keys WHERE server_id = $1" + key_count = await session.fetchval(count_query, server_name) + if key_count >= max_keys: + logger.info(f"[Ping] Сервер {server_name} достиг лимита ключей: {key_count}/{max_keys}.") + return False try: if panel_type == "remnawave": @@ -480,4 +494,4 @@ async def check_server_availability(server_info: dict) -> bool: return False except Exception as e: logger.warning(f"[Ping] Ошибка при проверке сервера {server_name}: {e}") - return False + return False \ No newline at end of file diff --git a/handlers/keys/key_utils.py b/handlers/keys/key_utils.py index e3743219..86c5c458 100644 --- a/handlers/keys/key_utils.py +++ b/handlers/keys/key_utils.py @@ -551,14 +551,7 @@ async def update_subscription(tg_id: int, email: str, session: Any, cluster_over async def get_user_traffic(session: Any, tg_id: int, email: str) -> dict[str, Any]: """ Получает трафик пользователя на всех серверах, где у него есть ключ (3x-ui и Remnawave). - - Args: - session (Any): Сессия базы данных. - tg_id (int): ID пользователя Telegram. - email (str): Email пользователя. - - Returns: - dict[str, Any]: Структура с данными о трафике. + Для Remnawave трафик считается один раз и отображается как "Remnawave (общий):". """ query = "SELECT client_id, server_id FROM keys WHERE tg_id = $1 AND email = $2" rows = await session.fetch(query, tg_id, email) @@ -582,6 +575,10 @@ async def get_user_traffic(session: Any, tg_id: int, email: str) -> dict[str, An servers_map = {row["server_name"]: row for row in server_rows} user_traffic_data = {} + tasks = [] + + remnawave_client_id = None + remnawave_checked = False async def fetch_traffic(server_info: dict, client_id: str) -> tuple[str, Any]: server_name = server_info["server_name"] @@ -598,28 +595,11 @@ async def get_user_traffic(session: Any, tg_id: int, email: str) -> dict[str, An return server_name, round(used_gb, 2) else: return server_name, "Ошибка получения трафика" - - elif panel_type == "remnawave": - remna = RemnawaveAPI(api_url) - logged_in = await remna.login(REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD) - if not logged_in: - return server_name, "Не удалось авторизоваться" - - user_data = await remna.get_user_by_uuid(client_id) - if not user_data: - return server_name, "Клиент не найден" - - used_bytes = user_data.get("usedTrafficBytes", 0) - used_gb = used_bytes / 1073741824 - return server_name, round(used_gb, 2) - else: return server_name, f"Неизвестная панель: {panel_type}" - except Exception as e: return server_name, f"Ошибка: {e}" - tasks = [] for row in rows: client_id = row["client_id"] server_id = row["server_id"] @@ -628,12 +608,36 @@ async def get_user_traffic(session: Any, tg_id: int, email: str) -> dict[str, An s for s in servers_map.values() if s["server_name"] == server_id or s["cluster_name"] == server_id ] for server_info in matched_servers: - tasks.append(fetch_traffic(server_info, client_id)) + panel_type = server_info.get("panel_type", "3x-ui").lower() + + if panel_type == "remnawave" and not remnawave_checked: + remnawave_client_id = client_id + remnawave_api_url = server_info["api_url"] + remnawave_checked = True + elif panel_type == "3x-ui": + tasks.append(fetch_traffic(server_info, client_id)) results = await asyncio.gather(*tasks, return_exceptions=True) + for server, result in results: user_traffic_data[server] = result + if remnawave_client_id: + try: + remna = RemnawaveAPI(remnawave_api_url) + if not await remna.login(REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD): + user_traffic_data["Remnawave (общий)"] = "Не удалось авторизоваться" + else: + user_data = await remna.get_user_by_uuid(remnawave_client_id) + if not user_data: + user_traffic_data["Remnawave (общий)"] = "Клиент не найден" + else: + used_bytes = user_data.get("usedTrafficBytes", 0) + used_gb = round(used_bytes / 1073741824, 2) + user_traffic_data["Remnawave (общий)"] = used_gb + except Exception as e: + user_traffic_data["Remnawave (общий)"] = f"Ошибка: {e}" + return {"status": "success", "traffic": user_traffic_data}