added support for external squads

This commit is contained in:
Vladless
2025-11-30 18:37:18 +03:00
parent 1140a6c050
commit 9f10fed3e7
7 changed files with 48 additions and 3 deletions
+2 -2
View File
@@ -10,8 +10,8 @@ class UserBase(BaseModel):
last_name: str | None = None
language_code: str | None = None
is_bot: bool | None = False
balance: float = 0.0
trial: int = 0
balance: float | None = 0.0
trial: int | None = 0
source_code: str | None = None
+1
View File
@@ -101,6 +101,7 @@ class Tariff(DictLikeMixin, Base):
subgroup_title = Column(String, nullable=True)
sort_order = Column(Integer, nullable=True)
vless = Column(Boolean, default=False)
external_squad: Mapped[str | None] = mapped_column(String(64), nullable=True)
configurable = Column(Boolean, nullable=False, server_default="false")
+6
View File
@@ -290,6 +290,12 @@ def build_edit_tariff_fields_kb(tariff_id: int) -> InlineKeyboardMarkup:
callback_data=f"edit_field|{tariff_id}|vless",
)
],
[
InlineKeyboardButton(
text="Внешний сквад",
callback_data=f"edit_field|{tariff_id}|external_squad",
)
],
[InlineKeyboardButton(text="🔘 Активность", callback_data=f"toggle_active|{tariff_id}")],
[
InlineKeyboardButton(
+13
View File
@@ -481,6 +481,7 @@ async def ask_new_value(callback: CallbackQuery, state: FSMContext):
"traffic_limit": "лимит трафика в ГБ (0 — безлимит)",
"device_limit": "лимит устройств (0 — безлимит)",
"vless": "VLESS (да/нет)",
"external_squad": "внешний сквад (0 — убрать)",
}
await callback.message.edit_text(
@@ -535,6 +536,18 @@ async def apply_edit(message: Message, state: FSMContext, session: AsyncSession)
)
return
if field == "external_squad":
if value in ("", "0", "-"):
value = None
setattr(tariff, field, value)
tariff.updated_at = datetime.utcnow()
await session.commit()
await state.clear()
text, markup = render_tariff_card(tariff)
await message.answer(text=text, reply_markup=markup)
return
if field in ["duration_days", "price_rub", "traffic_limit", "device_limit"]:
try:
num = int(value)
+2
View File
@@ -41,6 +41,7 @@ def render_tariff_card(tariff: Tariff) -> tuple[str, InlineKeyboardMarkup]:
vless_text = "Да" if getattr(tariff, "vless", False) else "Нет"
configurable = bool(getattr(tariff, "configurable", False))
configurable_text = "Включен" if configurable else "Выключен"
external_squad_text = getattr(tariff, "external_squad", None) or "Не задан"
text = (
f"<b>📄 Тариф: {tariff.name}</b>\n\n"
@@ -51,6 +52,7 @@ def render_tariff_card(tariff: Tariff) -> tuple[str, InlineKeyboardMarkup]:
f"📱 Устройств: <b>{device_text}</b>\n"
f"🔗 VLESS: <b>{vless_text}</b>\n"
f"⚙️ Конфигуратор: <b>{configurable_text}</b>\n"
f"Внешний сквад: <b>{external_squad_text}</b>\n"
f"🔢 Позиция: <b>{sort_order}</b>\n"
f"{'✅ Активен' if tariff.is_active else '⛔ Отключен'}"
)
+5
View File
@@ -62,6 +62,7 @@ async def create_key_on_cluster(
traffic_limit_bytes_value = 0
device_limit_value = 0
external_squad_uuid = None
if plan is not None:
tariff = await get_tariff_by_id(session, plan)
@@ -85,6 +86,7 @@ async def create_key_on_cluster(
subgroup_title = tariff.get("subgroup_title")
need_vless_key = bool(tariff.get("vless"))
external_squad_uuid = tariff.get("external_squad") or None
else:
traffic_limit_bytes_value = int(traffic_limit_bytes or 0)
device_limit_value = int(hwid_limit or 0)
@@ -168,6 +170,9 @@ async def create_key_on_cluster(
user_data["hwidDeviceLimit"] = device_limit_value
if external_squad_uuid:
user_data["externalSquadUuid"] = external_squad_uuid
logger.debug(f"{PANEL_REMNA} Данные для создания клиента: {user_data}")
result = await remna.create_user(user_data)
if result:
+19 -1
View File
@@ -13,6 +13,7 @@ from database import (
resolve_device_limit_from_group,
update_key_expiry,
update_key_link,
get_tariff_by_id,
)
from hooks.processors import process_get_cryptolink_after_renewal
from logger import (
@@ -28,6 +29,7 @@ from .subgroup_migration import migrate_between_subgroups
async def resolve_cluster(session: AsyncSession, cluster_id: str):
"""Возвращает список серверов для кластера или конкретного сервера."""
servers = await get_servers(session)
cluster = servers.get(cluster_id)
if cluster:
@@ -53,7 +55,9 @@ async def renew_on_remnawave(
session: AsyncSession,
reset_traffic: bool,
target_server_name: str | None = None,
external_squad_uuid: str | None = None,
) -> bool:
"""Продлевает подписку на Remnawave-узлах кластера."""
remnawave_nodes = [
s for s in cluster if str(s.get("panel_type", "3x-ui")).lower() == "remnawave" and s.get("inbound_id")
]
@@ -70,13 +74,18 @@ async def renew_on_remnawave(
expire_iso = datetime.utcfromtimestamp(new_expiry_time // 1000).isoformat() + "Z"
traffic_limit_bytes = total_gb * 1024 * 1024 * 1024 if total_gb else 0
active_inbounds = [s["inbound_id"] for s in remnawave_nodes]
updated = await remna.update_user(
update_kwargs = dict(
uuid=client_id,
expire_at=expire_iso,
active_user_inbounds=active_inbounds,
traffic_limit_bytes=traffic_limit_bytes,
hwid_device_limit=hwid_device_limit,
)
if external_squad_uuid:
update_kwargs["external_squad_uuid"] = external_squad_uuid
updated = await remna.update_user(**update_kwargs)
if updated:
if reset_traffic:
try:
@@ -100,6 +109,7 @@ async def renew_on_3xui(
update_links: bool = False,
target_server_name: str | None = None,
):
"""Продлевает подписку на 3x-ui серверах кластера."""
tasks = []
for server_info in cluster:
if target_server_name and server_info.get("server_name") != target_server_name:
@@ -179,6 +189,7 @@ async def renew_key_in_cluster(
old_subgroup: str | None = None,
plan=None,
):
"""Продлевает ключ в кластере с учётом подгрупп и Remnawave/3x-ui."""
try:
servers_map = await get_servers(session)
@@ -211,6 +222,12 @@ async def renew_key_in_cluster(
if dl is not None:
hwid_device_limit = dl
external_squad_uuid = None
if plan is not None:
tariff = await get_tariff_by_id(session, plan)
if tariff:
external_squad_uuid = tariff.get("external_squad")
if (target_subgroup or "") != (old_subgroup or "") and not single_server:
new_client_id, remna_link = await migrate_between_subgroups(
session=session,
@@ -270,6 +287,7 @@ async def renew_key_in_cluster(
session=session,
reset_traffic=reset_traffic,
target_server_name=server_id if single_server else None,
external_squad_uuid=external_squad_uuid,
)
succeeded, _ = await renew_on_3xui(