From e7f91a971b06a0e6885c7a3af82a5ba2494dbee4 Mon Sep 17 00:00:00 2001 From: Capybara-z Date: Sun, 29 Jun 2025 17:27:07 +0300 Subject: [PATCH] add temporary Remnawave API functions for user import / add UTM code validation in ads creation --- handlers/admin/ads/ads_handler.py | 13 ++- .../admin/management/management_handler.py | 25 ++++- panels/remnawave_time.py | 98 +++++++++++++++++++ 3 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 panels/remnawave_time.py diff --git a/handlers/admin/ads/ads_handler.py b/handlers/admin/ads/ads_handler.py index 58ffac19..d7e254a6 100644 --- a/handlers/admin/ads/ads_handler.py +++ b/handlers/admin/ads/ads_handler.py @@ -4,6 +4,7 @@ from aiogram.fsm.state import State, StatesGroup from aiogram.types import CallbackQuery, Message from sqlalchemy import delete, select, update from sqlalchemy.ext.asyncio import AsyncSession +import re from config import USERNAME_BOT from database import create_tracking_source, get_tracking_source_stats @@ -50,7 +51,8 @@ async def handle_ads_name_input(message: Message, state: FSMContext): await state.update_data(name=name) await state.set_state(AdminAdsState.waiting_for_new_code) await message.answer( - f"🔗 Введите код ссылки для: {name}.", + f"🔗 Введите код ссылки для: {name}.\n\n" + f"💡 Правила: только латинские буквы и цифры", reply_markup=build_cancel_input_kb(), ) @@ -62,6 +64,15 @@ async def handle_ads_code_input( code = message.text.strip() data = await state.get_data() name = data["name"] + + if not re.match(r'^[a-zA-Z0-9]+$', code): + await message.answer( + "❌ Код может содержать только латинские буквы и цифры\n" + "Введите код заново:", + reply_markup=build_cancel_input_kb(), + ) + return + code_with_prefix = f"utm_{code}" try: diff --git a/handlers/admin/management/management_handler.py b/handlers/admin/management/management_handler.py index 76e7c8b6..65dd9424 100644 --- a/handlers/admin/management/management_handler.py +++ b/handlers/admin/management/management_handler.py @@ -12,6 +12,7 @@ import os, subprocess, sys import json from aiogram import Bot from panels.remnawave import RemnawaveAPI +from panels.remnawave_time import get_all_users_time, login_remnawave from tempfile import NamedTemporaryFile import traceback from datetime import datetime @@ -426,16 +427,32 @@ async def show_remnawave_clients(callback: CallbackQuery, session: AsyncSession) return server = servers[0] - api = RemnawaveAPI(base_url=server.api_url) +# api = RemnawaveAPI(base_url=server.api_url) +# +# if not await api.login(username=REMNAWAVE_LOGIN, password=REMNAWAVE_PASSWORD): +# await callback.message.edit_text( +# "❌ Не удалось авторизоваться на Remnawave панели.", +# reply_markup=build_back_to_db_menu(), +# ) +# return +# +# users = await api.get_all_users() +# if not users: +# await callback.message.edit_text( +# "📭 На панели нет клиентов.", +# reply_markup=build_back_to_db_menu(), +# ) +# return - if not await api.login(username=REMNAWAVE_LOGIN, password=REMNAWAVE_PASSWORD): + token = await login_remnawave(server.api_url, REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD) + if not token: await callback.message.edit_text( "❌ Не удалось авторизоваться на Remnawave панели.", reply_markup=build_back_to_db_menu(), ) return - users = await api.get_all_users() + users = await get_all_users_time(server.api_url, REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD) if not users: await callback.message.edit_text( "📭 На панели нет клиентов.", @@ -560,4 +577,4 @@ async def import_remnawave_keys(session: AsyncSession, users: list[dict], server await session.commit() logger.info(f"[IMPORT] Всего добавлено ключей: {added}") - return added + return added \ No newline at end of file diff --git a/panels/remnawave_time.py b/panels/remnawave_time.py new file mode 100644 index 00000000..12218469 --- /dev/null +++ b/panels/remnawave_time.py @@ -0,0 +1,98 @@ +import aiohttp +from typing import List, Dict, Any +from logger import logger +from config import REMNAWAVE_TOKEN_LOGIN_ENABLED, REMNAWAVE_ACCESS_TOKEN, REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD + + +async def login_remnawave(api_url: str, username: str, password: str) -> str | None: + if REMNAWAVE_TOKEN_LOGIN_ENABLED and REMNAWAVE_ACCESS_TOKEN: + logger.info("[Remnawave API] Используется авторизация по токену") + return REMNAWAVE_ACCESS_TOKEN + + async with aiohttp.ClientSession() as session: + auth_data = { + "username": username, + "password": password + } + + try: + auth_response = await session.post(f"{api_url}/auth/login", json=auth_data) + + if auth_response.status != 200: + logger.error(f"[Remnawave API] Ошибка HTTP статуса: {auth_response.status}") + return None + + auth_result = await auth_response.json() + token = None + if auth_result.get("success") and auth_result.get("data", {}).get("token"): + token = auth_result.get("data", {}).get("token") + elif auth_result.get("response", {}).get("accessToken"): + token = auth_result.get("response", {}).get("accessToken") + elif auth_result.get("token"): + token = auth_result.get("token") + + if not token: + logger.error(f"[Remnawave API] Токен не найден в ответе") + + return token + + except Exception as e: + logger.error(f"[Remnawave API] Ошибка при авторизации: {e}") + return None + + +async def get_all_users_time(api_url: str, username: str, password: str) -> List[Dict[str, Any]]: + all_users = [] + page_size = 250 + start = 0 + + token = await login_remnawave(api_url, username, password) + if not token: + logger.error("[Remnawave API] Не удалось получить токен авторизации") + return [] + + headers = {"Authorization": f"Bearer {token}"} + + async with aiohttp.ClientSession() as session: + while True: + params = { + "size": page_size, + "start": start + } + + users_endpoint = f"{api_url}/users" + + users_response = await session.get(users_endpoint, params=params, headers=headers) + + if users_response.status != 200: + break + + users_result = await users_response.json() + + if users_result.get("success") is False: + break + + users_data = None + if "response" in users_result: + users_data = users_result.get("response", {}) + elif "data" in users_result: + users_data = users_result.get("data", {}) + else: + users_data = users_result + + if not users_data: + break + + users = users_data.get("users", []) + total = users_data.get("total", 0) + + if not users: + break + + all_users.extend(users) + start += len(users) + + if len(users) < page_size or start >= total: + break + + return all_users \ No newline at end of file