diff --git a/bot.py b/bot.py
index b8acd475..fae803d5 100644
--- a/bot.py
+++ b/bot.py
@@ -8,6 +8,7 @@ from aiogram.filters import ExceptionTypeFilter
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.types import BufferedInputFile, ErrorEvent
from aiogram.utils.markdown import hbold
+import subprocess
from config import ADMIN_ID, API_TOKEN
from filters.private import IsPrivateFilter
@@ -17,7 +18,16 @@ bot = Bot(token=API_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTM
storage = MemoryStorage()
dp = Dispatcher(bot=bot, storage=storage)
-version = "4.3-b240504 (ORM update)"
+
+def get_git_commit_number() -> str:
+ try:
+ count = subprocess.check_output(["git", "rev-list", "--count", "HEAD"])
+ return count.decode("utf-8").strip()
+ except Exception:
+ return "unknown"
+
+
+version = f"4.3-b240504 (commit #{get_git_commit_number()})"
dp.message.filter(IsPrivateFilter())
dp.callback_query.filter(IsPrivateFilter())
diff --git a/database/keys.py b/database/keys.py
index dae971e7..0a4048de 100644
--- a/database/keys.py
+++ b/database/keys.py
@@ -185,3 +185,13 @@ async def mark_key_as_unfrozen(
),
{"expiry": new_expiry_time, "tg_id": tg_id, "client_id": client_id},
)
+
+
+async def update_key_tariff(session: AsyncSession, client_id: str, tariff_id: int):
+ await session.execute(
+ update(Key)
+ .where(Key.client_id == client_id)
+ .values(tariff_id=tariff_id)
+ )
+ await session.commit()
+ logger.info(f"Тариф ключа {client_id} обновлён на {tariff_id}")
diff --git a/database/tariffs.py b/database/tariffs.py
index dd81fd5a..3d4aae07 100644
--- a/database/tariffs.py
+++ b/database/tariffs.py
@@ -45,6 +45,15 @@ async def get_tariffs_for_cluster(session: AsyncSession, cluster_name: str):
.limit(1)
)
row = server_row.first()
+
+ if not row:
+ server_row = await session.execute(
+ select(Server.tariff_group)
+ .where(Server.server_name == cluster_name)
+ .limit(1)
+ )
+ row = server_row.first()
+
if not row or not row[0]:
return []
diff --git a/handlers/admin/clusters/clusters_handler.py b/handlers/admin/clusters/clusters_handler.py
index f60f01f2..9b23e018 100644
--- a/handlers/admin/clusters/clusters_handler.py
+++ b/handlers/admin/clusters/clusters_handler.py
@@ -495,6 +495,7 @@ async def handle_sync_server(
Key.client_id,
Key.email,
Key.expiry_time,
+ Key.tariff_id,
)
.join(Key, Server.cluster_name == Key.server_id)
.where(Server.server_name == server_name)
@@ -530,6 +531,8 @@ async def handle_sync_server(
key["email"],
key["expiry_time"],
semaphore,
+ plan=key["tariff_id"],
+ session=session,
)
await asyncio.sleep(0.6)
except Exception as e:
diff --git a/handlers/admin/tariffs/tariffs_handler.py b/handlers/admin/tariffs/tariffs_handler.py
index 6fc486d4..70af9ff5 100644
--- a/handlers/admin/tariffs/tariffs_handler.py
+++ b/handlers/admin/tariffs/tariffs_handler.py
@@ -156,7 +156,7 @@ async def process_tariff_traffic(message: Message, state: FSMContext):
)
return
- await state.update_data(traffic_limit=traffic * 1024**3 if traffic > 0 else None)
+ await state.update_data(traffic_limit=traffic if traffic > 0 else None)
await state.set_state(TariffCreateState.device_limit)
await message.answer(
"📱 Введите лимит устройств (HWID) для тарифа (например: 3, 0 — безлимит):",
@@ -315,7 +315,7 @@ async def view_tariff(
return
traffic_text = (
- f"{tariff.traffic_limit // 1024**3} ГБ" if tariff.traffic_limit else "Безлимит"
+ f"{tariff.traffic_limit} ГБ" if tariff.traffic_limit else "Безлимит"
)
device_text = (
f"{tariff.device_limit}" if tariff.device_limit is not None else "Безлимит"
@@ -453,7 +453,7 @@ async def apply_edit(message: Message, state: FSMContext, session: AsyncSession)
if num < 0:
raise ValueError
if field == "traffic_limit":
- value = num * 1024**3 if num > 0 else None
+ value = num if num > 0 else None
elif field == "device_limit":
value = num if num > 0 else None
else:
diff --git a/handlers/admin/users/users_handler.py b/handlers/admin/users/users_handler.py
index 2b6cb007..b0e99b17 100644
--- a/handlers/admin/users/users_handler.py
+++ b/handlers/admin/users/users_handler.py
@@ -917,7 +917,12 @@ async def confirm_admin_key_reissue(
)
return
- await update_subscription(tg_id, email, session, cluster_override=cluster_id)
+ result = await session.execute(
+ select(Key.remnawave_link).where(Key.email == email)
+ )
+ remnawave_link = result.scalar_one_or_none()
+
+ await update_subscription(tg_id, email, session, cluster_override=cluster_id, remnawave_link=remnawave_link)
await handle_key_edit(
callback_query,
@@ -1149,58 +1154,51 @@ async def process_user_search(
async def change_expiry_time(
expiry_time: int, email: str, session: AsyncSession
) -> Exception | None:
- result = await session.execute(select(Key.client_id).where(Key.email == email))
- client_id = result.scalar_one_or_none()
- if client_id is None:
+ result = await session.execute(select(Key.client_id, Key.tariff_id, Key.server_id).where(Key.email == email))
+ row = result.first()
+ if not row:
return ValueError(f"User with email {email} was not found")
-
- result = await session.execute(
- select(Key.server_id).where(Key.client_id == client_id)
- )
- server_id = result.scalar_one_or_none()
+
+ client_id, tariff_id, server_id = row
if server_id is None:
return ValueError(f"Key with client_id {client_id} was not found")
- result = await session.execute(
- select(Server.tariff_group)
- .where(or_(Server.server_name == server_id, Server.cluster_name == server_id))
- .limit(1)
+ traffic_limit = 0
+ device_limit = None
+ if tariff_id:
+ result = await session.execute(
+ select(Tariff.traffic_limit, Tariff.device_limit)
+ .where(Tariff.id == tariff_id, Tariff.is_active.is_(True))
+ )
+ tariff = result.first()
+ if tariff:
+ traffic_limit = int(tariff[0]) if tariff[0] is not None else 0
+ device_limit = int(tariff[1]) if tariff[1] is not None else None
+
+ servers = await get_servers(session=session)
+
+ if server_id in servers:
+ target_cluster = server_id
+ else:
+ target_cluster = None
+ for cluster_name, cluster_servers in servers.items():
+ if any(s.get("server_name") == server_id for s in cluster_servers):
+ target_cluster = cluster_name
+ break
+
+ if not target_cluster:
+ return ValueError(f"No suitable cluster found for server {server_id}")
+
+ await renew_key_in_cluster(
+ cluster_id=target_cluster,
+ email=email,
+ client_id=client_id,
+ new_expiry_time=expiry_time,
+ total_gb=traffic_limit,
+ session=session,
+ hwid_device_limit=device_limit
)
- tariff_group = result.scalar_one_or_none()
- if not tariff_group:
- return ValueError(f"Tariff group not found for server_id={server_id}")
-
- result = await session.execute(
- select(Tariff.duration_days, Tariff.traffic_limit)
- .where(Tariff.group_code == tariff_group, Tariff.is_active.is_(True))
- .order_by(Tariff.duration_days)
- )
- tariffs = result.all()
- if not tariffs:
- return ValueError(f"No tariffs found for group {tariff_group}")
-
- added_days = max((expiry_time - int(time.time() * 1000)) / (1000 * 86400), 1)
- closest_tariff = min(tariffs, key=lambda t: abs(t[0] - added_days))
- traffic_limit = closest_tariff[1] or 0
-
- clusters = await get_servers(session=session)
-
- async def update_key_on_all_servers():
- tasks = [
- renew_key_in_cluster(
- cluster_id=cluster_name,
- email=email,
- client_id=client_id,
- new_expiry_time=expiry_time,
- total_gb=traffic_limit,
- session=session,
- )
- for cluster_name in clusters
- ]
- await asyncio.gather(*tasks, return_exceptions=True)
-
- await update_key_on_all_servers()
-
+
await update_key_expiry(session, client_id, expiry_time)
return None
diff --git a/handlers/keys/key_mode/key_cluster_mode.py b/handlers/keys/key_mode/key_cluster_mode.py
index b6ac41d9..17a8831e 100644
--- a/handlers/keys/key_mode/key_cluster_mode.py
+++ b/handlers/keys/key_mode/key_cluster_mode.py
@@ -77,15 +77,18 @@ async def key_cluster_mode(
is_trial = data.get("is_trial", False)
device_limit = 0
- traffic_limit_bytes = None
+ traffic_limit_gb = None
if is_trial:
device_limit = TRIAL_CONFIG.get("hwid_limit", 1)
- traffic_limit_bytes = int(TRIAL_CONFIG.get("traffic_limit_gb", 100) * 1024**3)
+ traffic_limit_gb = TRIAL_CONFIG.get("traffic_limit_gb", 100)
elif plan:
tariff = await get_tariff_by_id(session, plan)
- if tariff and tariff.get("device_limit") is not None:
- device_limit = int(tariff["device_limit"])
+ if tariff:
+ if tariff.get("device_limit") is not None:
+ device_limit = int(tariff["device_limit"])
+ if tariff.get("traffic_limit") is not None:
+ traffic_limit_gb = int(tariff["traffic_limit"])
least_loaded_cluster = await get_least_loaded_cluster(session)
await create_key_on_cluster(
@@ -97,7 +100,8 @@ async def key_cluster_mode(
plan=plan,
session=session,
hwid_limit=device_limit,
- traffic_limit_bytes=traffic_limit_bytes,
+ traffic_limit_bytes=traffic_limit_gb,
+ is_trial=is_trial,
)
logger.info(
diff --git a/handlers/keys/key_mode/key_country_mode.py b/handlers/keys/key_mode/key_country_mode.py
index 99d410ff..f145b17b 100644
--- a/handlers/keys/key_mode/key_country_mode.py
+++ b/handlers/keys/key_mode/key_country_mode.py
@@ -41,6 +41,7 @@ from handlers.buttons import (
PC_BUTTON,
SUPPORT,
TV_BUTTON,
+ MY_SUB
)
from handlers.keys.key_utils import create_client_on_server
from handlers.texts import SELECT_COUNTRY_MSG, key_message_success
@@ -349,7 +350,6 @@ async def finalize_key_creation(
client_id = old_key_details["client_id"]
email = old_key_details["email"]
expiry_timestamp = old_key_details["expiry_time"]
-
else:
while True:
key_name = generate_random_email()
@@ -360,6 +360,25 @@ async def finalize_key_creation(
email = key_name.lower()
expiry_timestamp = int(expiry_time.timestamp() * 1000)
+ traffic_limit_bytes = None
+ device_limit = 0
+ data = await state.get_data() if state else {}
+ is_trial = data.get("is_trial", False)
+
+ if is_trial:
+ from config import TRIAL_CONFIG
+ traffic_limit_bytes = int(TRIAL_CONFIG.get("traffic_limit_gb", 100)) * 1024**3
+ device_limit = TRIAL_CONFIG.get("hwid_limit", 1)
+ elif data.get("tariff_id") or tariff_id:
+ tariff_id = data.get("tariff_id") or tariff_id
+ result = await session.execute(select(Tariff).where(Tariff.id == tariff_id))
+ tariff = result.scalar_one_or_none()
+ if tariff:
+ if tariff.traffic_limit is not None:
+ traffic_limit_bytes = int(tariff.traffic_limit)
+ if tariff.device_limit is not None:
+ device_limit = int(tariff.device_limit)
+
public_link = None
remnawave_link = None
created_at = int(datetime.now(moscow_tz).timestamp() * 1000)
@@ -373,31 +392,22 @@ async def finalize_key_creation(
raise ValueError(f"Сервер {selected_country} не найден")
panel_type = server_info.panel_type.lower()
- cluster_info = await check_server_name_by_cluster(
- session, server_info.server_name
- )
+ cluster_info = await check_server_name_by_cluster(session, server_info.server_name)
if not cluster_info:
raise ValueError(f"Кластер для сервера {server_info.server_name} не найден")
- is_full_remnawave = await is_full_remnawave_cluster(
- cluster_info["cluster_name"], session
- )
+ is_full_remnawave = await is_full_remnawave_cluster(cluster_info["cluster_name"], session)
if old_key_name:
old_server_id = old_key_details["server_id"]
if old_server_id:
- result = await session.execute(
- select(Server).where(Server.server_name == old_server_id)
- )
+ result = await session.execute(select(Server).where(Server.server_name == old_server_id))
old_server_info = result.scalar_one_or_none()
if old_server_info:
try:
if old_server_info.panel_type.lower() == "3x-ui":
xui = await get_xui_instance(old_server_info.api_url)
- await delete_client(
- xui, old_server_info.inbound_id, email, client_id
- )
-
+ await delete_client(xui, old_server_info.inbound_id, email, client_id)
await session.execute(
update(Key)
.where(Key.tg_id == tg_id, Key.email == email)
@@ -418,21 +428,21 @@ async def finalize_key_creation(
if panel_type == "remnawave" or is_full_remnawave:
remna = RemnawaveAPI(server_info.api_url)
if not await remna.login(REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD):
- raise ValueError(
- f"❌ Не удалось авторизоваться в Remnawave ({server_info.server_name})"
- )
+ raise ValueError(f"❌ Не удалось авторизоваться в Remnawave ({server_info.server_name})")
- expire_at = (
- datetime.utcfromtimestamp(expiry_timestamp / 1000).isoformat() + "Z"
- )
+ expire_at = datetime.utcfromtimestamp(expiry_timestamp / 1000).isoformat() + "Z"
user_data = {
"username": email,
"trafficLimitStrategy": "NO_RESET",
"expireAt": expire_at,
"telegramId": tg_id,
"activeUserInbounds": [server_info.inbound_id],
- "hwidDeviceLimit": 0,
}
+ if traffic_limit_bytes:
+ user_data["trafficLimitBytes"] = traffic_limit_bytes
+ if device_limit:
+ user_data["hwidDeviceLimit"] = device_limit
+
result = await remna.create_user(user_data)
if not result:
raise ValueError("❌ Ошибка при создании пользователя в Remnawave")
@@ -442,9 +452,7 @@ async def finalize_key_creation(
if old_key_name:
await session.execute(
- update(Key)
- .where(Key.tg_id == tg_id, Key.email == email)
- .values(client_id=client_id)
+ update(Key).where(Key.tg_id == tg_id, Key.email == email).values(client_id=client_id)
)
if panel_type == "3x-ui":
@@ -461,12 +469,13 @@ async def finalize_key_creation(
email=email,
expiry_timestamp=expiry_timestamp,
semaphore=semaphore,
+ session=session,
+ plan=tariff_id,
+ is_trial=is_trial,
)
public_link = f"{PUBLIC_LINK}{email}/{tg_id}"
- logger.info(
- f"[Key Creation] Подписка создана для пользователя {tg_id} на сервере {selected_country}"
- )
+ logger.info(f"[Key Creation] Подписка создана для пользователя {tg_id} на сервере {selected_country}")
if old_key_name:
update_data = {"server_id": selected_country}
@@ -474,19 +483,8 @@ async def finalize_key_creation(
update_data["key"] = public_link
elif panel_type == "remnawave":
update_data["remnawave_link"] = remnawave_link
-
- await session.execute(
- update(Key)
- .where(Key.tg_id == tg_id, Key.email == email)
- .values(**update_data)
- )
+ await session.execute(update(Key).where(Key.tg_id == tg_id, Key.email == email).values(**update_data))
else:
- data = {}
- if state:
- data = await state.get_data()
-
- tariff_id = data.get("tariff_id") or tariff_id
-
new_key = Key(
tg_id=tg_id,
client_id=client_id,
@@ -496,19 +494,17 @@ async def finalize_key_creation(
key=public_link,
remnawave_link=remnawave_link,
server_id=selected_country,
- tariff_id=data.get("tariff_id"),
+ tariff_id=tariff_id,
)
session.add(new_key)
- data = await state.get_data()
- if data.get("is_trial"):
+ if is_trial:
trial_status = await get_trial(session, tg_id)
if trial_status in [0, -1]:
await update_trial(session, tg_id, 1)
- if data.get("tariff_id"):
- result = await session.execute(
- select(Tariff.price_rub).where(Tariff.id == data["tariff_id"])
- )
+
+ if tariff_id:
+ result = await session.execute(select(Tariff.price_rub).where(Tariff.id == tariff_id))
row = result.scalar_one_or_none()
if row:
await update_balance(session, tg_id, -row)
@@ -516,21 +512,13 @@ async def finalize_key_creation(
await session.commit()
except Exception as e:
- logger.error(
- f"[Key Finalize] Ошибка при создании ключа для пользователя {tg_id}: {e}"
- )
- await callback_query.message.answer(
- "❌ Произошла ошибка при создании подписки. Попробуйте снова."
- )
+ logger.error(f"[Key Finalize] Ошибка при создании ключа для пользователя {tg_id}: {e}")
+ await callback_query.message.answer("❌ Произошла ошибка при создании подписки. Попробуйте снова.")
return
builder = InlineKeyboardBuilder()
- is_full_remnawave = await is_full_remnawave_cluster(
- cluster_info["cluster_name"], session
- )
- if (panel_type == "remnawave" or is_full_remnawave) and (
- public_link or remnawave_link
- ):
+ is_full_remnawave = await is_full_remnawave_cluster(cluster_info["cluster_name"], session)
+ if (panel_type == "remnawave" or is_full_remnawave) and (public_link or remnawave_link):
builder.row(
InlineKeyboardButton(
text=CONNECT_DEVICE,
@@ -539,9 +527,7 @@ async def finalize_key_creation(
)
elif CONNECT_PHONE_BUTTON:
builder.row(
- InlineKeyboardButton(
- text=CONNECT_PHONE, callback_data=f"connect_phone|{key_name}"
- )
+ InlineKeyboardButton(text=CONNECT_PHONE, callback_data=f"connect_phone|{key_name}")
)
builder.row(
InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{email}"),
@@ -549,15 +535,10 @@ async def finalize_key_creation(
)
else:
builder.row(
- InlineKeyboardButton(
- text=CONNECT_DEVICE, callback_data=f"connect_device|{key_name}"
- )
+ InlineKeyboardButton(text=CONNECT_DEVICE, callback_data=f"connect_device|{key_name}")
)
- builder.row(
- InlineKeyboardButton(
- text="🔐 Моя подписка", callback_data=f"view_key|{key_name}"
- )
- )
+
+ builder.row(InlineKeyboardButton(text=MY_SUB, callback_data=f"view_key|{key_name}"))
builder.row(InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL))
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
diff --git a/handlers/keys/key_mode/key_create.py b/handlers/keys/key_mode/key_create.py
index 9a7b739d..a42d45a7 100644
--- a/handlers/keys/key_mode/key_create.py
+++ b/handlers/keys/key_mode/key_create.py
@@ -27,6 +27,7 @@ from handlers.buttons import MAIN_MENU, PAYMENT
from handlers.payments.robokassa_pay import handle_custom_amount_input
from handlers.payments.stars_pay import process_custom_amount_input_stars
from handlers.payments.yookassa_pay import process_custom_amount_input
+from handlers.payments.yoomoney_pay import process_custom_amount_input_yoomoney
from handlers.texts import (
CREATING_CONNECTION_MSG,
INSUFFICIENT_FUNDS_MSG,
@@ -165,6 +166,8 @@ async def select_tariff_plan(
await handle_custom_amount_input(callback_query, session)
elif USE_NEW_PAYMENT_FLOW == "STARS":
await process_custom_amount_input_stars(callback_query, session)
+ elif USE_NEW_PAYMENT_FLOW == "YOOMONEY":
+ await process_custom_amount_input_yoomoney(callback_query, session)
else:
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text=PAYMENT, callback_data="pay"))
diff --git a/handlers/keys/key_renew.py b/handlers/keys/key_renew.py
index b493a6db..e242a3a9 100644
--- a/handlers/keys/key_renew.py
+++ b/handlers/keys/key_renew.py
@@ -4,7 +4,7 @@ from typing import Any
from aiogram import F, Router
from aiogram.types import CallbackQuery, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
-from sqlalchemy import or_, select
+from sqlalchemy import or_, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from bot import bot
@@ -19,12 +19,13 @@ from database import (
update_balance,
update_key_expiry,
)
-from database.models import Server
+from database.models import Server, Key
from handlers.buttons import BACK, MAIN_MENU, PAYMENT
from handlers.keys.key_utils import renew_key_in_cluster
from handlers.payments.robokassa_pay import handle_custom_amount_input
from handlers.payments.stars_pay import process_custom_amount_input_stars
from handlers.payments.yookassa_pay import process_custom_amount_input
+from handlers.payments.yoomoney_pay import process_custom_amount_input_yoomoney
from handlers.texts import (
INSUFFICIENT_FUNDS_RENEWAL_MSG,
KEY_NOT_FOUND_MSG,
@@ -191,6 +192,8 @@ async def process_callback_renew_plan(callback_query: CallbackQuery, session: An
await handle_custom_amount_input(callback_query, session)
elif USE_NEW_PAYMENT_FLOW == "STARS":
await process_custom_amount_input_stars(callback_query, session)
+ elif USE_NEW_PAYMENT_FLOW == "YOOMONEY":
+ await process_custom_amount_input_yoomoney(callback_query, session)
else:
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text=PAYMENT, callback_data="pay"))
@@ -316,6 +319,11 @@ async def complete_key_renewal(
)
await update_key_expiry(session, client_id, new_expiry_time)
+ await session.execute(
+ update(Key)
+ .where(Key.client_id == client_id)
+ .values(tariff_id=tariff_id)
+ )
await update_balance(session, tg_id, -cost)
logger.info(
diff --git a/handlers/keys/key_utils.py b/handlers/keys/key_utils.py
index 42bff44e..d23e5468 100644
--- a/handlers/keys/key_utils.py
+++ b/handlers/keys/key_utils.py
@@ -5,7 +5,7 @@ from typing import Any
from sqlalchemy import delete, select
from sqlalchemy.ext.asyncio import AsyncSession
-from config import PUBLIC_LINK, REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD, SUPERNODE
+from config import PUBLIC_LINK, REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD, SUPERNODE, TRIAL_CONFIG
from database import delete_notification, get_servers, get_tariff_by_id, store_key
from database.models import Key, Server, Tariff
from handlers.utils import check_server_key_limit, get_least_loaded_cluster
@@ -32,7 +32,8 @@ async def create_key_on_cluster(
session: AsyncSession = None,
remnawave_link: str = None,
hwid_limit: int = None,
- traffic_limit_bytes: int = None,
+ traffic_limit_bytes: int = None,
+ is_trial: bool = False,
):
try:
servers = await get_servers(session, include_enabled=True)
@@ -125,7 +126,7 @@ async def create_key_on_cluster(
}
if traffic_limit_bytes and traffic_limit_bytes > 0:
- user_data["trafficLimitBytes"] = traffic_limit_bytes
+ user_data["trafficLimitBytes"] = traffic_limit_bytes * 1024 * 1024 * 1024
if short_uuid:
user_data["shortUuid"] = short_uuid
@@ -161,6 +162,7 @@ async def create_key_on_cluster(
semaphore,
plan=plan,
session=session,
+ is_trial=is_trial,
)
else:
await asyncio.gather(
@@ -174,6 +176,7 @@ async def create_key_on_cluster(
semaphore,
plan=plan,
session=session,
+ is_trial=is_trial,
)
for server in xui_servers
],
@@ -207,12 +210,13 @@ async def create_client_on_server(
semaphore: asyncio.Semaphore,
plan: int = None,
session=None,
+ is_trial: bool = False,
):
"""
- Создает клиента на указанном 3x-ui сервере с лимитом по тарифу (через ORM).
+ Создает клиента на указанном 3x-ui сервере с лимитом по тарифу или триалу.
"""
logger.info(
- f"[Client] Вход в create_client_on_server: сервер={server_info.get('server_name')}, план={plan}"
+ f"[Client] Вход в create_client_on_server: сервер={server_info.get('server_name')}, план={plan}, is_trial={is_trial}"
)
async with semaphore:
@@ -221,9 +225,7 @@ async def create_client_on_server(
server_name = server_info.get("server_name", "unknown")
if not inbound_id:
- logger.warning(
- f"INBOUND_ID отсутствует для сервера {server_name}. Пропуск."
- )
+ logger.warning(f"[Client] INBOUND_ID отсутствует для сервера {server_name}. Пропуск.")
return
if SUPERNODE:
@@ -236,26 +238,24 @@ async def create_client_on_server(
total_gb_value = 0
device_limit_value = None
- if plan is not None:
+ if is_trial:
+ total_gb_value = TRIAL_CONFIG.get("traffic_limit_gb", 0)
+ device_limit_value = TRIAL_CONFIG.get("hwid_limit")
+ logger.info(f"[Trial] Используются параметры триала: {total_gb_value} GB, {device_limit_value} устройств")
+ elif plan is not None:
tariff = await get_tariff_by_id(session, plan)
logger.info(f"[Tariff Debug] Получен тариф: {tariff}")
if not tariff:
raise ValueError(f"Тариф с id={plan} не найден.")
- total_gb_value = (
- int(tariff["traffic_limit"]) if tariff.get("traffic_limit") else 0
- )
- device_limit_value = (
- int(tariff["device_limit"])
- if tariff.get("device_limit") is not None
- else None
- )
+ total_gb_value = int(tariff["traffic_limit"]) if tariff["traffic_limit"] else 0
+ device_limit_value = int(tariff["device_limit"]) if tariff.get("device_limit") is not None else None
try:
logger.info(
- f"[Client] Вызов add_client: email={unique_email}, client_id={client_id}, GB={total_gb_value}, Devices={device_limit_value}"
+ f"[Client] Вызов add_client: email={email}, client_id={client_id}, GB={total_gb_value}, Devices={device_limit_value}"
)
-
+ traffic_limit_bytes = total_gb_value * 1024 * 1024 * 1024
await add_client(
xui,
ClientConfig(
@@ -263,7 +263,7 @@ async def create_client_on_server(
email=unique_email,
tg_id=tg_id,
limit_ip=device_limit_value,
- total_gb=total_gb_value,
+ total_gb=traffic_limit_bytes,
expiry_time=expiry_timestamp,
enable=True,
flow="xtls-rprx-vision",
@@ -273,9 +273,7 @@ async def create_client_on_server(
)
logger.info(f"[Client] Клиент успешно добавлен на сервер {server_name}")
except Exception as e:
- logger.error(
- f"[Client Error] Не удалось создать клиента на {server_name}: {e}"
- )
+ logger.error(f"[Client Error] Не удалось создать клиента на {server_name}: {e}")
if SUPERNODE:
await asyncio.sleep(0.7)
@@ -335,12 +333,6 @@ async def renew_key_in_cluster(
if tariff and tariff.device_limit is not None:
hwid_device_limit = int(tariff.device_limit)
- notification_prefixes = ["key_24h", "key_10h", "key_expired", "renew"]
- for notif in notification_prefixes:
- notification_id = f"{email}_{notif}"
- await delete_notification(session, tg_id, notification_id)
- logger.info(f"🧹 Уведомления для ключа {email} очищены при продлении.")
-
remnawave_inbound_ids = []
tasks = []
for server_info in cluster:
@@ -366,11 +358,12 @@ async def renew_key_in_cluster(
datetime.utcfromtimestamp(new_expiry_time // 1000).isoformat()
+ "Z"
)
+ traffic_limit_bytes = total_gb * 1024 * 1024 * 1024 if total_gb else 0
updated = await remna.update_user(
uuid=client_id,
expire_at=expire_iso,
active_user_inbounds=remnawave_inbound_ids,
- traffic_limit_bytes=total_gb,
+ traffic_limit_bytes=traffic_limit_bytes,
hwid_device_limit=hwid_device_limit,
)
if updated:
@@ -404,6 +397,7 @@ async def renew_key_in_cluster(
unique_email = email
sub_id = unique_email
+ traffic_bytes = total_gb * 1024 * 1024 * 1024 if total_gb else 0
tasks.append(
extend_client_key(
xui=xui,
@@ -411,7 +405,7 @@ async def renew_key_in_cluster(
email=unique_email,
new_expiry_time=new_expiry_time,
client_id=client_id,
- total_gb=total_gb,
+ total_gb=traffic_bytes,
sub_id=sub_id,
tg_id=tg_id,
limit_ip=hwid_device_limit,
@@ -420,6 +414,12 @@ async def renew_key_in_cluster(
await asyncio.gather(*tasks, return_exceptions=True)
+ notification_prefixes = ["key_24h", "key_10h", "key_expired", "renew"]
+ for notif in notification_prefixes:
+ notification_id = f"{email}_{notif}"
+ await delete_notification(session, tg_id, notification_id)
+ logger.info(f"🧹 Уведомления для ключа {email} очищены при продлении.")
+
except Exception as e:
logger.error(
f"Не удалось продлить ключ {client_id} в кластере/на сервере {cluster_id}: {e}"
@@ -510,6 +510,9 @@ async def update_key_on_cluster(
expiry_time: int,
cluster_id: str,
session: AsyncSession,
+ traffic_limit: int = None,
+ device_limit: int = None,
+ remnawave_link: str = None,
):
"""
Пересоздаёт ключ на всех серверах указанного кластера (или сервера, если передано имя).
@@ -568,6 +571,11 @@ async def update_key_on_cluster(
)
tariff = result.scalar_one_or_none()
+ short_uuid = None
+ if remnawave_link and "/" in remnawave_link:
+ short_uuid = remnawave_link.rstrip("/").split("/")[-1]
+ logger.info(f"[Update] Извлечен short_uuid из ссылки: {short_uuid}")
+
user_data = {
"username": email,
"trafficLimitStrategy": "NO_RESET",
@@ -575,12 +583,13 @@ async def update_key_on_cluster(
"telegramId": tg_id,
"activeUserInbounds": inbound_ids,
}
-
- if tariff:
- if tariff.traffic_limit is not None:
- user_data["trafficLimitBytes"] = int(tariff.traffic_limit)
- if tariff.device_limit is not None:
- user_data["hwidDeviceLimit"] = int(tariff.device_limit)
+ if traffic_limit is not None:
+ user_data["trafficLimitBytes"] = traffic_limit
+ if device_limit is not None:
+ user_data["hwidDeviceLimit"] = device_limit
+ if short_uuid:
+ user_data["shortUuid"] = short_uuid
+ logger.info(f"[Update] Добавлен short_uuid в user_data: {short_uuid}")
result = await remna.create_user(user_data)
if result:
@@ -628,20 +637,14 @@ async def update_key_on_cluster(
)
tariff = result.scalar_one_or_none()
- total_gb_bytes = (
- int(tariff.traffic_limit) if tariff and tariff.traffic_limit else 0
- )
- device_limit = (
- int(tariff.device_limit)
- if tariff and tariff.device_limit is not None
- else None
- )
+ total_gb_bytes = int(traffic_limit * 1024 ** 3) if traffic_limit else 0
+ device_limit_value = device_limit if device_limit is not None else None
config = ClientConfig(
client_id=remnawave_client_id,
email=unique_email,
tg_id=tg_id,
- limit_ip=device_limit,
+ limit_ip=device_limit_value,
total_gb=total_gb_bytes,
expiry_time=expiry_time,
enable=True,
@@ -673,6 +676,7 @@ async def update_subscription(
session: AsyncSession,
cluster_override: str = None,
country_override: str = None,
+ remnawave_link: str = None,
) -> None:
result = await session.execute(
select(Key).where(Key.tg_id == tg_id, Key.email == email)
@@ -685,8 +689,25 @@ async def update_subscription(
expiry_time = record.expiry_time
client_id = record.client_id
old_cluster_id = record.server_id
+ tariff_id = record.tariff_id
+ remnawave_link = remnawave_link or record.remnawave_link
public_link = f"{PUBLIC_LINK}{email}/{tg_id}"
+ traffic_limit = None
+ device_limit = None
+ if tariff_id:
+ result = await session.execute(
+ select(Tariff).where(Tariff.id == tariff_id, Tariff.is_active.is_(True))
+ )
+ tariff = result.scalar_one_or_none()
+ if tariff:
+ traffic_limit = int(tariff.traffic_limit) if tariff.traffic_limit is not None else None
+ device_limit = int(tariff.device_limit) if tariff.device_limit is not None else None
+ else:
+ logger.warning(f"[LOG] update_subscription: тариф с id={tariff_id} не найден!")
+ else:
+ logger.warning(f"[LOG] update_subscription: tariff_id отсутствует!")
+
await delete_key_from_cluster(old_cluster_id, email, client_id, session=session)
await session.execute(delete(Key).where(Key.tg_id == tg_id, Key.email == email))
@@ -703,6 +724,9 @@ async def update_subscription(
expiry_time=expiry_time,
cluster_id=new_cluster_id,
session=session,
+ traffic_limit=traffic_limit,
+ device_limit=device_limit,
+ remnawave_link=remnawave_link
)
servers = await get_servers(session)
@@ -720,6 +744,7 @@ async def update_subscription(
key=final_key_link,
remnawave_link=remnawave_key,
server_id=new_cluster_id,
+ tariff_id=tariff_id,
)
@@ -1012,4 +1037,4 @@ async def reset_traffic_in_cluster(
logger.error(
f"[Reset Traffic] Ошибка при сбросе трафика клиента {email} в кластере {cluster_id}: {e}"
)
- raise
+ raise
\ No newline at end of file
diff --git a/handlers/keys/keys.py b/handlers/keys/keys.py
index 50b5cc36..cfca39ff 100644
--- a/handlers/keys/keys.py
+++ b/handlers/keys/keys.py
@@ -76,6 +76,7 @@ async def process_callback_confirm_delete(
record = await get_key_details(session, email)
if record:
client_id = record["client_id"]
+ server_id = record["server_id"]
response_message = KEY_DELETED_MSG_SIMPLE
back_button = types.InlineKeyboardButton(
text=BACK, callback_data="view_keys"
@@ -90,21 +91,12 @@ async def process_callback_confirm_delete(
reply_markup=keyboard,
)
- servers = await get_servers(session)
-
- async def delete_key_from_servers():
- try:
- tasks = [
- delete_key_from_cluster(cluster_id, email, client_id, session)
- for cluster_id in servers
- ]
- await asyncio.gather(*tasks, return_exceptions=True)
- except Exception as e:
- logger.error(
- f"Ошибка при удалении ключа {client_id} с серверов: {e}"
- )
-
- asyncio.create_task(delete_key_from_servers())
+ try:
+ await delete_key_from_cluster(server_id, email, client_id, session)
+ except Exception as e:
+ logger.error(
+ f"Ошибка при удалении ключа {client_id} с сервера {server_id}: {e}"
+ )
else:
response_message = "Ключ не найден или уже удален."
diff --git a/handlers/notifications/general_notifications.py b/handlers/notifications/general_notifications.py
index 7bcee1f3..8ba52116 100644
--- a/handlers/notifications/general_notifications.py
+++ b/handlers/notifications/general_notifications.py
@@ -597,4 +597,4 @@ async def process_auto_renew_or_notify(
)
except Exception as e:
- logger.error(f"❌ Ошибка в process_auto_renew_or_notify: {e}")
+ logger.error(f"❌ Ошибка в process_auto_renew_or_notify: {e}")
\ No newline at end of file
diff --git a/handlers/payments/gift.cpython-312-x86_64-linux-gnu.so b/handlers/payments/gift.cpython-312-x86_64-linux-gnu.so
index 6916e266..b9fed8bd 100644
Binary files a/handlers/payments/gift.cpython-312-x86_64-linux-gnu.so and b/handlers/payments/gift.cpython-312-x86_64-linux-gnu.so differ
diff --git a/handlers/payments/utils.cpython-312-x86_64-linux-gnu.so b/handlers/payments/utils.cpython-312-x86_64-linux-gnu.so
index bf93bcff..2dfd817c 100644
Binary files a/handlers/payments/utils.cpython-312-x86_64-linux-gnu.so and b/handlers/payments/utils.cpython-312-x86_64-linux-gnu.so differ
diff --git a/handlers/payments/yoomoney_pay.cpython-312-x86_64-linux-gnu.so b/handlers/payments/yoomoney_pay.cpython-312-x86_64-linux-gnu.so
index 6b4514fa..0a47da51 100644
Binary files a/handlers/payments/yoomoney_pay.cpython-312-x86_64-linux-gnu.so and b/handlers/payments/yoomoney_pay.cpython-312-x86_64-linux-gnu.so differ