cancel for utm/disable servers for change location/remnawave traffic

This commit is contained in:
Vladless
2025-04-23 22:27:53 +03:00
parent 888812babd
commit b1c73dc1a6
5 changed files with 80 additions and 34 deletions
+1 -1
View File
@@ -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)
+20 -2
View File
@@ -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("📝 Введите <b>название</b> новой ссылки:")
await callback_query.message.edit_text(
"📝 Введите <b>название</b> новой ссылки:",
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"🔗 Введите <b>код ссылки</b> для: <code>{name}</code>.")
await message.answer(
f"🔗 Введите <b>код ссылки</b> для: <code>{name}</code>.",
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"<i>Просмотр статистики и управление рекламными ссылками</i>."
)
@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="📊 <b>Аналитика рекламы:</b>",
reply_markup=build_ads_kb()
)
+11 -1
View File
@@ -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()
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()
+18 -4
View File
@@ -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
+30 -26
View File
@@ -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}