added support for external squads
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 '⛔ Отключен'}"
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user