add traffic statistic/share refferal link and more
This commit is contained in:
@@ -2,8 +2,8 @@ from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
import py3xui
|
||||
from config import LIMIT_IP, SUPERNODE
|
||||
|
||||
from config import LIMIT_IP, SUPERNODE
|
||||
from logger import logger
|
||||
|
||||
|
||||
@@ -155,3 +155,30 @@ async def delete_client(
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении клиента с ID {client_id}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def get_client_traffic(xui: py3xui.AsyncApi, client_id: str) -> dict[str, Any]:
|
||||
"""
|
||||
Получает информацию о трафике пользователя по client_id.
|
||||
|
||||
Args:
|
||||
xui: Экземпляр API клиента
|
||||
client_id: UUID клиента
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: Информация о трафике пользователя или ошибка
|
||||
"""
|
||||
try:
|
||||
await xui.login()
|
||||
traffic_data = await xui.client.get_traffic_by_id(client_id)
|
||||
|
||||
if not traffic_data:
|
||||
logger.warning(f"Трафик для клиента {client_id} не найден.")
|
||||
return {"status": "not_found", "client_id": client_id}
|
||||
|
||||
logger.info(f"Трафик для клиента {client_id} успешно получен.")
|
||||
return {"status": "success", "client_id": client_id, "traffic": traffic_data}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении трафика клиента {client_id}: {e}")
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
@@ -275,6 +275,7 @@ async def handle_servers_availability(
|
||||
await callback_query.message.edit_text(text=text)
|
||||
|
||||
result_text = f"🖥️ Проверка доступности серверов для кластера {cluster_name} завершена:\n\n"
|
||||
total_online_users = 0
|
||||
|
||||
for server in cluster_servers:
|
||||
xui = AsyncApi(server["api_url"], username=ADMIN_USERNAME, password=ADMIN_PASSWORD)
|
||||
@@ -282,11 +283,14 @@ async def handle_servers_availability(
|
||||
try:
|
||||
await xui.login()
|
||||
online_users = len(await xui.client.online())
|
||||
total_online_users += online_users
|
||||
result_text += f"🌍 {server['server_name']}: {online_users} активных пользователей.\n"
|
||||
|
||||
except Exception as e:
|
||||
result_text += f"❌ {server['server_name']}: Не удалось получить информацию. Ошибка: {e}\n"
|
||||
|
||||
result_text += f"\n👥 Общее количество активных пользователей в кластере: {total_online_users}."
|
||||
|
||||
await callback_query.message.edit_text(text=result_text, reply_markup=build_admin_back_kb("servers"))
|
||||
|
||||
|
||||
|
||||
@@ -20,11 +20,7 @@ from database import (
|
||||
update_trial,
|
||||
)
|
||||
from filters.admin import IsAdminFilter
|
||||
from handlers.keys.key_utils import (
|
||||
delete_key_from_cluster,
|
||||
renew_key_in_cluster,
|
||||
update_subscription,
|
||||
)
|
||||
from handlers.keys.key_utils import delete_key_from_cluster, get_user_traffic, renew_key_in_cluster, update_subscription
|
||||
from handlers.utils import sanitize_key_name
|
||||
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb
|
||||
from keyboards.admin.users_kb import (
|
||||
@@ -673,3 +669,33 @@ async def set_user_balance(tg_id: int, balance: int, session: Any) -> None:
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при установке баланса для пользователя {tg_id}: {e}")
|
||||
|
||||
|
||||
@router.callback_query(AdminUserEditorCallback.filter(F.action == "users_traffic"), IsAdminFilter())
|
||||
async def handle_user_traffic(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminUserEditorCallback, session: Any
|
||||
):
|
||||
"""
|
||||
Обработчик кнопки "📊 Трафик".
|
||||
Получает трафик пользователя и отправляет администратору.
|
||||
"""
|
||||
tg_id = callback_data.tg_id
|
||||
email = callback_data.data
|
||||
|
||||
await callback_query.message.edit_text("⏳ Получаем данные о трафике, пожалуйста, подождите...")
|
||||
|
||||
traffic_data = await get_user_traffic(session, tg_id, email)
|
||||
|
||||
if traffic_data["status"] == "error":
|
||||
await callback_query.message.edit_text(traffic_data["message"], reply_markup=build_editor_kb(tg_id, True))
|
||||
return
|
||||
|
||||
result_text = f"📊 Трафик ключа {email}:\n\n"
|
||||
|
||||
for server, traffic in traffic_data["traffic"].items():
|
||||
if isinstance(traffic, str):
|
||||
result_text += f"❌ {server}: {traffic}\n"
|
||||
else:
|
||||
result_text += f"🌍 {server}: {traffic} ГБ\n"
|
||||
|
||||
await callback_query.message.edit_text(result_text, reply_markup=build_editor_kb(tg_id, True))
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, PUBLIC_LINK, SUPERNODE, TOTAL_GB
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from client import ClientConfig, add_client, delete_client, extend_client_key
|
||||
from client import ClientConfig, add_client, delete_client, extend_client_key, get_client_traffic
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, PUBLIC_LINK, SUPERNODE, TOTAL_GB, USE_COUNTRY_SELECTION
|
||||
from database import get_servers, store_key
|
||||
from handlers.utils import get_least_loaded_cluster
|
||||
from logger import logger
|
||||
@@ -277,3 +277,69 @@ async def update_subscription(tg_id: int, email: str, session: Any) -> None:
|
||||
server_id=least_loaded_cluster_id,
|
||||
session=session,
|
||||
)
|
||||
|
||||
|
||||
async def get_user_traffic(session: Any, tg_id: int, email: str) -> dict[str, Any]:
|
||||
"""
|
||||
Получает трафик пользователя на всех серверах, где у него есть ключ.
|
||||
|
||||
Args:
|
||||
session (Any): Сессия базы данных.
|
||||
tg_id (int): ID пользователя Telegram.
|
||||
email (str): Email пользователя.
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: Структура с данными о трафике.
|
||||
"""
|
||||
logger.info(f"🔍 Получаем ключи для пользователя {email} (TG ID: {tg_id})")
|
||||
|
||||
query = "SELECT client_id, server_id FROM keys WHERE tg_id = $1 AND email = $2"
|
||||
rows = await session.fetch(query, tg_id, email)
|
||||
|
||||
if not rows:
|
||||
return {"status": "error", "message": "❌ У пользователя нет активных ключей."}
|
||||
|
||||
server_ids = {row["server_id"] for row in rows}
|
||||
logger.info(f"🖥️ Серверы/Кластеры пользователя: {server_ids}")
|
||||
|
||||
if USE_COUNTRY_SELECTION:
|
||||
query_servers = "SELECT server_name, api_url FROM servers WHERE server_name = ANY($1)"
|
||||
filter_ids = list(server_ids)
|
||||
else:
|
||||
query_servers = "SELECT server_name, api_url FROM servers WHERE cluster_name = ANY($1)"
|
||||
filter_ids = list(server_ids)
|
||||
|
||||
server_rows = await session.fetch(query_servers, filter_ids)
|
||||
|
||||
if not server_rows:
|
||||
logger.error(f"❌ Не найдено серверов для: {server_ids}")
|
||||
return {"status": "error", "message": f"❌ Серверы не найдены: {', '.join(server_ids)}"}
|
||||
|
||||
servers_map = {row["server_name"]: row["api_url"] for row in server_rows}
|
||||
logger.info(f"✅ Найденные серверы: {list(servers_map.keys())}")
|
||||
|
||||
user_traffic_data = {}
|
||||
|
||||
for row in rows:
|
||||
client_id = row["client_id"]
|
||||
|
||||
for server, api_url in servers_map.items():
|
||||
if not USE_COUNTRY_SELECTION and server not in servers_map:
|
||||
continue
|
||||
|
||||
xui = AsyncApi(api_url, username=ADMIN_USERNAME, password=ADMIN_PASSWORD)
|
||||
|
||||
try:
|
||||
traffic_info = await get_client_traffic(xui, client_id)
|
||||
|
||||
if traffic_info["status"] == "success" and traffic_info["traffic"]:
|
||||
client_data = traffic_info["traffic"][0]
|
||||
used_gb = (client_data.up + client_data.down) / 1073741824
|
||||
user_traffic_data[server] = round(used_gb, 2)
|
||||
else:
|
||||
user_traffic_data[server] = "Ошибка получения трафика"
|
||||
|
||||
except Exception as e:
|
||||
user_traffic_data[server] = f"Ошибка: {e}"
|
||||
|
||||
return {"status": "success", "traffic": user_traffic_data}
|
||||
|
||||
+1264
-1153
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+9
-4
@@ -189,15 +189,20 @@ async def view_tariffs_handler(callback_query: CallbackQuery):
|
||||
async def invite_handler(callback_query: CallbackQuery):
|
||||
chat_id = callback_query.message.chat.id
|
||||
referral_link = get_referral_link(chat_id)
|
||||
|
||||
referral_stats = await get_referral_stats(chat_id)
|
||||
invite_text = f"\nПриглашаю тебя пользоваться действительно быстрым VPN вместе:\n\n{referral_link}"
|
||||
|
||||
invite_message = invite_message_send(referral_link, referral_stats)
|
||||
|
||||
image_path = os.path.join("img", "pic_invite.jpg")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
builder.button(
|
||||
text="📢 Поделиться",
|
||||
switch_inline_query=invite_text
|
||||
)
|
||||
builder.button(text="👤 Личный кабинет", callback_data="profile")
|
||||
builder.adjust(1)
|
||||
|
||||
if os.path.isfile(image_path):
|
||||
async with aiofiles.open(image_path, "rb") as image_file:
|
||||
image_data = await image_file.read()
|
||||
@@ -210,4 +215,4 @@ async def invite_handler(callback_query: CallbackQuery):
|
||||
await callback_query.message.answer(
|
||||
text=invite_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
)
|
||||
@@ -3,8 +3,8 @@ from datetime import datetime, timezone
|
||||
from aiogram.filters.callback_data import CallbackData
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import RENEWAL_PRICES
|
||||
|
||||
from config import RENEWAL_PRICES
|
||||
from keyboards.admin.panel_kb import build_admin_back_btn
|
||||
|
||||
|
||||
@@ -166,6 +166,10 @@ def build_key_edit_kb(key_details: dict, email: str) -> InlineKeyboardMarkup:
|
||||
text="❌ Удалить",
|
||||
callback_data=AdminUserEditorCallback(action="users_delete_key", data=email, tg_id=key_details["tg_id"]).pack(),
|
||||
)
|
||||
builder.button(
|
||||
text="📊 Трафик",
|
||||
callback_data=AdminUserEditorCallback(action="users_traffic", data=email, tg_id=key_details["tg_id"]).pack(),
|
||||
)
|
||||
builder.row(build_editor_back_btn(key_details["tg_id"], True))
|
||||
builder.adjust(1)
|
||||
return builder.as_markup()
|
||||
|
||||
Reference in New Issue
Block a user