add device_limit in tariffs
This commit is contained in:
@@ -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 $$
|
||||
|
||||
@@ -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}")],
|
||||
]
|
||||
|
||||
@@ -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
@@ -10,7 +10,7 @@ CANCEL = "❌ Отмена"
|
||||
|
||||
# Профиль
|
||||
|
||||
ADD_SUB = "➕ Подписка"
|
||||
ADD_SUB = "➕ Добавить новую подписку"
|
||||
MY_SUBS = "📱 Мои подписки"
|
||||
BALANCE = "💰 Баланс"
|
||||
INVITE = "👥 Пригласить"
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user