Add client toggle functionality for 3x-ui servers
This commit is contained in:
@@ -3,8 +3,8 @@ import subprocess
|
||||
from datetime import datetime
|
||||
|
||||
from aiogram.types import BufferedInputFile
|
||||
|
||||
from config import ADMIN_ID, BACK_DIR, DB_NAME, DB_PASSWORD, DB_USER, PG_HOST, PG_PORT
|
||||
|
||||
from logger import logger
|
||||
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ from aiogram.filters import ExceptionTypeFilter
|
||||
from aiogram.fsm.storage.memory import MemoryStorage
|
||||
from aiogram.types import BufferedInputFile, ErrorEvent
|
||||
from aiogram.utils.markdown import hbold
|
||||
|
||||
from config import ADMIN_ID, API_TOKEN
|
||||
|
||||
from filters.private import IsPrivateFilter
|
||||
from logger import logger
|
||||
from middlewares import register_middleware
|
||||
|
||||
@@ -2,8 +2,8 @@ from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
import py3xui
|
||||
|
||||
from config import LIMIT_IP, SUPERNODE
|
||||
|
||||
from logger import logger
|
||||
|
||||
|
||||
@@ -182,3 +182,46 @@ async def get_client_traffic(xui: py3xui.AsyncApi, client_id: str) -> dict[str,
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении трафика клиента {client_id}: {e}")
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
async def toggle_client(xui: py3xui.AsyncApi, inbound_id: int, email: str, client_id: str, enable: bool = True) -> bool:
|
||||
"""
|
||||
Функция для включения/отключения клиента на сервере 3x-ui.
|
||||
|
||||
Args:
|
||||
xui: Экземпляр API клиента
|
||||
inbound_id: ID инбаунда
|
||||
email: Email клиента
|
||||
client_id: UUID клиента
|
||||
enable: True для включения, False для отключения
|
||||
|
||||
Returns:
|
||||
bool: True при успешном выполнении, False при ошибке
|
||||
"""
|
||||
try:
|
||||
await xui.login()
|
||||
|
||||
# Получаем клиента по email
|
||||
client = await xui.client.get_by_email(email)
|
||||
|
||||
if not client:
|
||||
logger.warning(f"Клиент с email {email} и ID {client_id} не найден.")
|
||||
return False
|
||||
|
||||
# Обновляем параметры клиента
|
||||
client.enable = enable
|
||||
client.id = client_id
|
||||
client.flow = "xtls-rprx-vision"
|
||||
client.limit_ip = LIMIT_IP
|
||||
client.inbound_id = inbound_id
|
||||
|
||||
# Обновляем клиента
|
||||
await xui.client.update(client.id, client)
|
||||
status = "включен" if enable else "отключен"
|
||||
logger.info(f"Клиент с email {email} и ID {client_id} успешно {status}.")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
status = "включении" if enable else "отключении"
|
||||
logger.error(f"Ошибка при {status} клиента с email {email} и ID {client_id}: {e}")
|
||||
return False
|
||||
|
||||
+1
-1
@@ -4,8 +4,8 @@ from typing import Any
|
||||
|
||||
import asyncpg
|
||||
import pytz
|
||||
|
||||
from config import CASHBACK, CHECK_REFERRAL_REWARD_ISSUED, DATABASE_URL, REFERRAL_BONUS_PERCENTAGES
|
||||
|
||||
from logger import logger
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from aiogram.filters import BaseFilter
|
||||
from aiogram.types import Message
|
||||
|
||||
from config import ADMIN_ID
|
||||
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ from aiogram import F, Router, types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
|
||||
from config import USERNAME_BOT
|
||||
|
||||
from database import create_coupon, delete_coupon, get_all_coupons
|
||||
from filters.admin import IsAdminFilter
|
||||
from keyboards.admin.coupons_kb import AdminCouponDeleteCallback, build_coupons_kb, build_coupons_list_kb
|
||||
|
||||
@@ -6,10 +6,10 @@ from aiogram import F, Router, types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from backup import create_backup_and_send_to_admins
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
|
||||
from database import check_unique_server_name, delete_server, get_servers
|
||||
from filters.admin import IsAdminFilter
|
||||
from handlers.keys.key_utils import create_key_on_cluster
|
||||
|
||||
@@ -7,8 +7,8 @@ from aiogram.exceptions import TelegramBadRequest
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
|
||||
from config import TOTAL_GB
|
||||
|
||||
from database import (
|
||||
delete_key,
|
||||
delete_user_data,
|
||||
|
||||
+1
-1
@@ -6,8 +6,8 @@ from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import CAPTCHA_EMOJIS
|
||||
|
||||
from logger import logger
|
||||
|
||||
router = Router()
|
||||
|
||||
+1
-1
@@ -3,8 +3,8 @@ from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, LabeledPrice, Message, PreCheckoutQuery
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import RUB_TO_XTR
|
||||
|
||||
from logger import logger
|
||||
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import aiofiles
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.types import BufferedInputFile, CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import CONNECT_MACOS, CONNECT_WINDOWS, SUPPORT_CHAT_URL
|
||||
|
||||
from database import get_key_details
|
||||
from handlers.texts import (
|
||||
CONNECT_TV_TEXT,
|
||||
|
||||
@@ -8,10 +8,6 @@ from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from bot import bot
|
||||
from client import delete_client
|
||||
from config import (
|
||||
ADMIN_PASSWORD,
|
||||
ADMIN_USERNAME,
|
||||
@@ -28,6 +24,10 @@ from config import (
|
||||
USE_COUNTRY_SELECTION,
|
||||
USE_NEW_PAYMENT_FLOW,
|
||||
)
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from bot import bot
|
||||
from client import delete_client
|
||||
from database import (
|
||||
add_connection,
|
||||
check_connection_exists,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, PUBLIC_LINK, SUPERNODE, TOTAL_GB, USE_COUNTRY_SELECTION
|
||||
from py3xui import AsyncApi
|
||||
|
||||
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 client import ClientConfig, add_client, delete_client, extend_client_key, get_client_traffic, toggle_client
|
||||
from database import get_servers, store_key
|
||||
from handlers.utils import get_least_loaded_cluster
|
||||
from logger import logger
|
||||
@@ -364,3 +364,80 @@ async def get_user_traffic(session: Any, tg_id: int, email: str) -> dict[str, An
|
||||
user_traffic_data[server] = result
|
||||
|
||||
return {"status": "success", "traffic": user_traffic_data}
|
||||
|
||||
|
||||
async def toggle_client_on_cluster(cluster_id: str, email: str, client_id: str, enable: bool = True) -> dict[str, Any]:
|
||||
"""
|
||||
Включает или отключает клиента на всех серверах указанного кластера.
|
||||
|
||||
Args:
|
||||
cluster_id (str): ID кластера или имя сервера
|
||||
email (str): Email клиента
|
||||
client_id (str): UUID клиента
|
||||
enable (bool): True для включения, False для отключения
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: Результат операции с информацией по каждому серверу
|
||||
"""
|
||||
try:
|
||||
servers = await get_servers()
|
||||
cluster = servers.get(cluster_id)
|
||||
|
||||
if not cluster:
|
||||
# Поиск по имени сервера, если не найден кластер
|
||||
found_servers = []
|
||||
for _, server_list in servers.items():
|
||||
for server_info in server_list:
|
||||
if server_info.get("server_name", "").lower() == cluster_id.lower():
|
||||
found_servers.append(server_info)
|
||||
|
||||
if found_servers:
|
||||
cluster = found_servers
|
||||
else:
|
||||
raise ValueError(f"Кластер или сервер с ID/именем {cluster_id} не найден.")
|
||||
|
||||
results = {}
|
||||
tasks = []
|
||||
|
||||
for server_info in cluster:
|
||||
xui = AsyncApi(
|
||||
server_info["api_url"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
|
||||
inbound_id = server_info.get("inbound_id")
|
||||
server_name = server_info.get("server_name", "unknown")
|
||||
|
||||
if not inbound_id:
|
||||
logger.warning(f"INBOUND_ID отсутствует для сервера {server_name}. Пропуск.")
|
||||
results[server_name] = False
|
||||
continue
|
||||
|
||||
if SUPERNODE:
|
||||
unique_email = f"{email}_{server_name.lower()}"
|
||||
else:
|
||||
unique_email = email
|
||||
|
||||
tasks.append(toggle_client(xui, int(inbound_id), unique_email, client_id, enable))
|
||||
|
||||
# Выполняем все задачи параллельно
|
||||
task_results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
# Формируем результаты для каждого сервера
|
||||
for server_info, result in zip(cluster, task_results, strict=False):
|
||||
server_name = server_info.get("server_name", "unknown")
|
||||
if isinstance(result, Exception):
|
||||
logger.error(f"Ошибка на сервере {server_name}: {result}")
|
||||
results[server_name] = False
|
||||
else:
|
||||
results[server_name] = result
|
||||
|
||||
status = "включен" if enable else "отключен"
|
||||
logger.info(f"Клиент {email} {status} на серверах кластера {cluster_id}")
|
||||
|
||||
return {"status": "success" if any(results.values()) else "error", "results": results}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при изменении состояния клиента {email} в кластере {cluster_id}: {e}")
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
@@ -10,8 +10,6 @@ import pytz
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.types import BufferedInputFile, CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from bot import bot
|
||||
from config import (
|
||||
CONNECT_ANDROID,
|
||||
CONNECT_IOS,
|
||||
@@ -27,6 +25,8 @@ from config import (
|
||||
USE_COUNTRY_SELECTION,
|
||||
USE_NEW_PAYMENT_FLOW,
|
||||
)
|
||||
|
||||
from bot import bot
|
||||
from database import (
|
||||
check_server_name_by_cluster,
|
||||
create_temporary_data,
|
||||
|
||||
@@ -5,8 +5,8 @@ from datetime import datetime
|
||||
import aiohttp
|
||||
import asyncpg
|
||||
from aiohttp import web
|
||||
|
||||
from config import DATABASE_URL, PROJECT_NAME, SUB_MESSAGE, SUPERNODE, TRANSITION_DATE_STR, USE_COUNTRY_SELECTION
|
||||
|
||||
from database import get_key_details, get_servers
|
||||
from logger import logger
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ from datetime import datetime, timedelta
|
||||
import asyncpg
|
||||
import pytz
|
||||
from aiogram import Bot, Router
|
||||
|
||||
from config import (
|
||||
DATABASE_URL,
|
||||
NOTIFICATION_TIME,
|
||||
@@ -17,6 +16,7 @@ from config import (
|
||||
TOTAL_GB,
|
||||
TRIAL_TIME_DISABLE,
|
||||
)
|
||||
|
||||
from database import (
|
||||
add_notification,
|
||||
check_notification_time,
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import asyncpg
|
||||
import pytz
|
||||
from aiogram import Bot, Router, types
|
||||
from aiogram.exceptions import TelegramForbiddenError
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import NOTIFY_EXTRA_DAYS, NOTIFY_INACTIVE, NOTIFY_INACTIVE_TRAFFIC, SUPPORT_CHAT_URL, TRIAL_TIME
|
||||
|
||||
from database import (
|
||||
add_notification,
|
||||
check_notification_time,
|
||||
@@ -13,14 +15,12 @@ from database import (
|
||||
)
|
||||
from handlers.keys.key_utils import get_user_traffic
|
||||
from logger import logger
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytz
|
||||
|
||||
router = Router()
|
||||
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
|
||||
|
||||
async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
|
||||
"""
|
||||
Проверяет пользователей, не активировавших пробный период, и отправляет им напоминания.
|
||||
@@ -200,4 +200,4 @@ async def notify_users_no_traffic(bot: Bot, conn: asyncpg.Connection, current_ti
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка обновления notified для пользователя {tg_id}: {e}")
|
||||
|
||||
logger.info("✅ Обработка пользователей с нулевым трафиком завершена.")
|
||||
logger.info("✅ Обработка пользователей с нулевым трафиком завершена.")
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from aiogram import F, Router
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import (
|
||||
CRYPTO_BOT_ENABLE,
|
||||
DONATIONS_ENABLE,
|
||||
|
||||
+1
-1
@@ -7,8 +7,8 @@ from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import BufferedInputFile, CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import DATABASE_URL, INSTRUCTIONS_BUTTON, NEWS_MESSAGE, RENEWAL_PLANS
|
||||
|
||||
from database import get_balance, get_key_count, get_last_payments, get_referral_stats, get_trial
|
||||
from handlers.buttons.profile import (
|
||||
ADD_SUB,
|
||||
|
||||
+2
-2
@@ -12,8 +12,6 @@ from aiogram.types import (
|
||||
Message,
|
||||
)
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from bot import bot
|
||||
from config import (
|
||||
CAPTCHA_ENABLE,
|
||||
CHANNEL_EXISTS,
|
||||
@@ -23,6 +21,8 @@ from config import (
|
||||
DONATIONS_ENABLE,
|
||||
SUPPORT_CHAT_URL,
|
||||
)
|
||||
|
||||
from bot import bot
|
||||
from database import (
|
||||
add_connection,
|
||||
add_referral,
|
||||
|
||||
+1
-1
@@ -5,9 +5,9 @@ import string
|
||||
|
||||
import aiohttp
|
||||
import asyncpg
|
||||
from config import DATABASE_URL
|
||||
|
||||
from bot import bot
|
||||
from config import DATABASE_URL
|
||||
from database import get_all_keys, get_servers
|
||||
from logger import logger
|
||||
|
||||
|
||||
@@ -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 keyboards.admin.panel_kb import build_admin_back_btn
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ from typing import Any
|
||||
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
from config import ADMIN_ID
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ from typing import Any
|
||||
import asyncpg
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
from config import DATABASE_URL
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -5,10 +5,10 @@ from datetime import datetime, timedelta
|
||||
import asyncpg
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import ADMIN_ID, DATABASE_URL, PING_TIME
|
||||
from ping3 import ping
|
||||
|
||||
from bot import bot
|
||||
from config import ADMIN_ID, DATABASE_URL, PING_TIME
|
||||
from database import check_unique_server_name, create_server, get_servers
|
||||
from logger import logger
|
||||
|
||||
|
||||
Reference in New Issue
Block a user