add device_limit in tariffs

This commit is contained in:
Vladless
2025-05-16 07:09:18 +03:00
parent 9a042c638a
commit 4d337e3df5
9 changed files with 84 additions and 30 deletions
+3
View File
@@ -200,12 +200,15 @@ CREATE TABLE IF NOT EXISTS tariffs (
duration_days INTEGER NOT NULL CHECK (duration_days > 0),
price_rub INTEGER NOT NULL CHECK (price_rub >= 0),
traffic_limit BIGINT,
device_limit INTEGER,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
ALTER TABLE servers ADD COLUMN IF NOT EXISTS tariff_group TEXT;
ALTER TABLE tariffs
ADD COLUMN IF NOT EXISTS device_limit INTEGER;
DO $$
+1
View File
@@ -79,6 +79,7 @@ def build_edit_tariff_fields_kb(tariff_id: int) -> InlineKeyboardMarkup:
[InlineKeyboardButton(text="📅 Длительность", callback_data=f"edit_field|{tariff_id}|duration_days")],
[InlineKeyboardButton(text="💰 Цена", callback_data=f"edit_field|{tariff_id}|price_rub")],
[InlineKeyboardButton(text="📦 Трафик (ГБ или 0)", callback_data=f"edit_field|{tariff_id}|traffic_limit")],
[InlineKeyboardButton(text="📱 Лимит устройств", callback_data=f"edit_field|{tariff_id}|device_limit")],
[InlineKeyboardButton(text="🔘 Активность", callback_data=f"toggle_active|{tariff_id}")],
[InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view|{tariff_id}")],
]
+25 -3
View File
@@ -28,6 +28,7 @@ class TariffCreateState(StatesGroup):
price = State()
traffic = State()
confirm_more = State()
device_limit = State()
class TariffEditState(StatesGroup):
@@ -115,7 +116,7 @@ async def process_tariff_price(message: Message, state: FSMContext):
@router.message(TariffCreateState.traffic, IsAdminFilter())
async def process_tariff_traffic(message: Message, state: FSMContext, session):
async def process_tariff_traffic(message: Message, state: FSMContext):
try:
traffic = int(message.text.strip())
if traffic < 0:
@@ -124,8 +125,25 @@ async def process_tariff_traffic(message: Message, state: FSMContext, session):
await message.answer("❌ Введите корректный лимит трафика (целое число 0 или больше):")
return
await state.update_data(traffic_limit=traffic * 1024**3 if traffic > 0 else None)
await state.set_state(TariffCreateState.device_limit)
await message.answer(
"📱 Введите <b>лимит устройств (HWID)</b> для тарифа (например: <i>3</i>, 0 — безлимит):",
reply_markup=build_cancel_kb(),
)
@router.message(TariffCreateState.device_limit, IsAdminFilter())
async def process_tariff_device_limit(message: Message, state: FSMContext, session):
try:
device_limit = int(message.text.strip())
if device_limit < 0:
raise ValueError
except ValueError:
await message.answer("❌ Введите корректный лимит устройств (целое число 0 или больше):")
return
data = await state.get_data()
data["traffic_limit"] = traffic * 1024**3 if traffic > 0 else None
new_tariff = await create_tariff(
session,
@@ -135,6 +153,7 @@ async def process_tariff_traffic(message: Message, state: FSMContext, session):
"duration_days": data["duration_days"],
"price_rub": data["price_rub"],
"traffic_limit": data["traffic_limit"],
"device_limit": device_limit if device_limit > 0 else None,
},
)
@@ -268,6 +287,7 @@ async def ask_new_value(callback: CallbackQuery, state: FSMContext):
"duration_days": "длительность в днях",
"price_rub": "цену в рублях",
"traffic_limit": "лимит трафика в ГБ (0 — безлимит)",
"device_limit": "лимит устройств (0 — безлимит)",
}
await callback.message.edit_text(
@@ -282,13 +302,15 @@ async def apply_edit(message: Message, state: FSMContext, session):
field = data["field"]
value = message.text.strip()
if field in ["duration_days", "price_rub", "traffic_limit"]:
if field in ["duration_days", "price_rub", "traffic_limit", "device_limit"]:
try:
num = int(value)
if num < 0:
raise ValueError
if field == "traffic_limit":
value = num * 1024**3 if num > 0 else None
elif field == "device_limit":
value = num if num > 0 else None
else:
value = num
except ValueError:
+1 -1
View File
@@ -10,7 +10,7 @@ CANCEL = "❌ Отмена"
# Профиль
ADD_SUB = " Подписка"
ADD_SUB = " Добавить новую подписку"
MY_SUBS = "📱 Мои подписки"
BALANCE = "💰 Баланс"
INVITE = "👥 Пригласить"
+8 -2
View File
@@ -9,7 +9,7 @@ from aiogram.types import CallbackQuery, FSInputFile, InlineKeyboardButton, Mess
from aiogram.utils.keyboard import InlineKeyboardBuilder
from bot import bot
from config import CONNECT_PHONE_BUTTON, DEFAULT_HWID_LIMIT, SUPPORT_CHAT_URL
from config import CONNECT_PHONE_BUTTON, SUPPORT_CHAT_URL
from database import (
get_key_details,
get_trial,
@@ -56,6 +56,12 @@ async def key_cluster_mode(
expiry_timestamp = int(expiry_time.timestamp() * 1000)
try:
device_limit = 0
if plan:
row = await session.fetchrow("SELECT device_limit FROM tariffs WHERE id = $1", plan)
if row and row["device_limit"] is not None:
device_limit = int(row["device_limit"])
least_loaded_cluster = await get_least_loaded_cluster()
await create_key_on_cluster(
least_loaded_cluster,
@@ -65,7 +71,7 @@ async def key_cluster_mode(
expiry_timestamp,
plan,
session,
hwid_limit=DEFAULT_HWID_LIMIT,
hwid_limit=device_limit,
)
logger.info(f"[Key Creation] Ключ создан на кластере {least_loaded_cluster} для пользователя {tg_id}")
+1 -2
View File
@@ -18,7 +18,6 @@ from config import (
ADMIN_USERNAME,
CONNECT_PHONE_BUTTON,
DATABASE_URL,
DEFAULT_HWID_LIMIT,
PUBLIC_LINK,
REMNAWAVE_LOGIN,
REMNAWAVE_PASSWORD,
@@ -352,7 +351,7 @@ async def finalize_key_creation(
"expireAt": expire_at,
"telegramId": tg_id,
"activeUserInbounds": [server_info["inbound_id"]],
"hwidDeviceLimit": DEFAULT_HWID_LIMIT,
"hwidDeviceLimit": 0,
}
result = await remna.create_user(user_data)
if not result:
+42 -18
View File
@@ -7,8 +7,6 @@ import asyncpg
from config import (
DATABASE_URL,
DEFAULT_HWID_LIMIT,
LIMIT_IP,
PUBLIC_LINK,
REMNAWAVE_LOGIN,
REMNAWAVE_PASSWORD,
@@ -38,7 +36,7 @@ async def create_key_on_cluster(
plan: int = None,
session=None,
remnawave_link: str = None,
hwid_limit: int | None = DEFAULT_HWID_LIMIT,
hwid_limit: int = None,
):
try:
servers = await get_servers(include_enabled=True)
@@ -66,10 +64,12 @@ async def create_key_on_cluster(
async with pool.acquire() as conn:
traffic_limit_bytes = None
if plan is not None:
tariff = await conn.fetchrow("SELECT traffic_limit FROM tariffs WHERE id = $1", plan)
tariff = await conn.fetchrow("SELECT traffic_limit, device_limit FROM tariffs WHERE id = $1", plan)
if not tariff:
raise ValueError(f"Тариф с id={plan} не найден.")
traffic_limit_bytes = int(tariff["traffic_limit"])
traffic_limit_bytes = int(tariff["traffic_limit"]) if tariff["traffic_limit"] else None
hwid_limit = int(tariff["device_limit"]) if tariff["device_limit"] is not None else None
remnawave_servers = [
s
@@ -199,13 +199,16 @@ async def create_client_on_server(
sub_id = unique_email
total_gb_value = 0
device_limit_value = None
if plan is not None:
async with asyncpg.create_pool(DATABASE_URL) as pool:
async with pool.acquire() as conn:
tariff = await conn.fetchrow("SELECT traffic_limit FROM tariffs WHERE id = $1", plan)
tariff = await conn.fetchrow("SELECT traffic_limit, device_limit FROM tariffs WHERE id = $1", plan)
if not tariff:
raise ValueError(f"Тариф с id={plan} не найден.")
total_gb_value = int(tariff["traffic_limit"])
total_gb_value = int(tariff["traffic_limit"]) if tariff["traffic_limit"] else 0
device_limit_value = int(tariff["device_limit"]) if tariff["device_limit"] is not None else None
await add_client(
xui,
@@ -213,7 +216,7 @@ async def create_client_on_server(
client_id=client_id,
email=unique_email,
tg_id=tg_id,
limit_ip=LIMIT_IP,
limit_ip=device_limit_value,
total_gb=total_gb_value,
expiry_time=expiry_timestamp,
enable=True,
@@ -228,7 +231,7 @@ async def create_client_on_server(
async def renew_key_in_cluster(
cluster_id, email, client_id, new_expiry_time, total_gb, hwid_device_limit=DEFAULT_HWID_LIMIT
cluster_id, email, client_id, new_expiry_time, total_gb, hwid_device_limit=None
):
try:
servers = await get_servers()
@@ -247,14 +250,31 @@ async def renew_key_in_cluster(
async with asyncpg.create_pool(DATABASE_URL) as pool:
async with pool.acquire() as conn:
tg_id_query = "SELECT tg_id FROM keys WHERE client_id = $1 LIMIT 1"
tg_id_record = await conn.fetchrow(tg_id_query, client_id)
tg_id_record = await conn.fetchrow(
"SELECT tg_id, server_id FROM keys WHERE client_id = $1 LIMIT 1", client_id
)
if not tg_id_record:
logger.error(f"Не найден пользователь с client_id={client_id} в таблице keys.")
return False
tg_id = tg_id_record["tg_id"]
server_id = tg_id_record["server_id"]
tariff_group_row = await conn.fetchrow(
"SELECT tariff_group FROM servers WHERE server_name = $1", server_id
)
if tariff_group_row and tariff_group_row["tariff_group"]:
tariff_row = await conn.fetchrow(
"""
SELECT device_limit FROM tariffs
WHERE group_code = $1 AND is_active = TRUE
ORDER BY duration_days DESC LIMIT 1
""",
tariff_group_row["tariff_group"],
)
if tariff_row and tariff_row["device_limit"] is not None:
hwid_device_limit = int(tariff_row["device_limit"])
notification_prefixes = ["key_24h", "key_10h", "key_expired", "renew"]
for notif in notification_prefixes:
@@ -298,7 +318,7 @@ async def renew_key_in_cluster(
expire_at=expire_iso,
active_user_inbounds=remnawave_inbound_ids,
traffic_limit_bytes=total_gb,
hwid_device_limit=DEFAULT_HWID_LIMIT,
hwid_device_limit=hwid_device_limit,
)
if updated:
logger.info(f"Подписка Remnawave {client_id} успешно продлена")
@@ -314,7 +334,6 @@ async def renew_key_in_cluster(
if panel_type == "3x-ui":
xui = await get_xui_instance(server_info["api_url"])
inbound_id = server_info.get("inbound_id")
if not inbound_id:
@@ -457,11 +476,14 @@ async def update_key_on_cluster(tg_id, client_id, email, expiry_time, cluster_id
if group_id is None:
raise ValueError("У Remnawave-сервера отсутствует tariff_group")
tariff = await conn.fetchrow(
"SELECT traffic_limit FROM tariffs WHERE tariff_group = $1 ORDER BY traffic_limit_gb DESC LIMIT 1",
"SELECT traffic_limit, device_limit FROM tariffs WHERE group_code = $1 ORDER BY duration_days DESC LIMIT 1",
group_id,
)
if tariff:
user_data["trafficLimitBytes"] = int(tariff["traffic_limit"] * 1024**3)
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"])
result = await remna.create_user(user_data)
if result:
@@ -494,23 +516,25 @@ async def update_key_on_cluster(tg_id, client_id, email, expiry_time, cluster_id
unique_email = email
total_gb_bytes = 0
device_limit = None
async with asyncpg.create_pool(DATABASE_URL) as pool:
async with pool.acquire() as conn:
group_id = server_info.get("tariff_group")
if group_id is None:
raise ValueError(f"У сервера {server_name} отсутствует tariff_group")
tariff = await conn.fetchrow(
"SELECT traffic_limit FROM tariffs WHERE tariff_group = $1 ORDER BY traffic_limit_gb DESC LIMIT 1",
"SELECT traffic_limit, device_limit FROM tariffs WHERE group_code = $1 ORDER BY duration_days DESC LIMIT 1",
group_id,
)
if tariff:
total_gb_bytes = int(tariff["traffic_limit_gb"] * 1024**3)
total_gb_bytes = int(tariff["traffic_limit"]) if tariff["traffic_limit"] else 0
device_limit = int(tariff["device_limit"]) if tariff["device_limit"] is not None else None
config = ClientConfig(
client_id=remnawave_client_id,
email=unique_email,
tg_id=tg_id,
limit_ip=LIMIT_IP,
limit_ip=device_limit,
total_gb=total_gb_bytes,
expiry_time=expiry_time,
enable=True,
+1 -1
View File
@@ -84,7 +84,7 @@ async def process_callback_view_profile(
profile_message = profile_message_send(username, chat_id, int(balance), key_count)
if key_count == 0:
profile_message += (
"\n<blockquote>🔧 <i>Нажмите кнопку ➕ Подписка, чтобы настроить VPN-подключение</i></blockquote>"
"\n<blockquote>🔧 <i>Нажмите кнопку ➕ Добавить новую подписку, чтобы настроить VPN-подключение</i></blockquote>"
)
else:
profile_message += f"\n<blockquote> <i>{NEWS_MESSAGE}</i></blockquote>"
+2 -3
View File
@@ -11,7 +11,6 @@ from py3xui import AsyncApi
from config import (
ADMIN_PASSWORD,
ADMIN_USERNAME,
LIMIT_IP,
SUPERNODE,
USE_XUI_TOKEN,
XUI_TOKEN,
@@ -122,7 +121,7 @@ async def extend_client_key(
client.sub_id = sub_id
client.total_gb = total_gb
client.enable = True
client.limit_ip = LIMIT_IP
client.limit_ip
client.inbound_id = inbound_id
client.tg_id = tg_id
@@ -213,7 +212,7 @@ async def toggle_client(xui: py3xui.AsyncApi, inbound_id: int, email: str, clien
client.enable = enable
client.id = client_id
client.flow = "xtls-rprx-vision"
client.limit_ip = LIMIT_IP
client.limit_ip
client.inbound_id = inbound_id
await xui.client.update(client.id, client)