Files
Solo_bot/services/operations/update.py
T
2026-04-14 07:19:14 +00:00

377 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import asyncio
from datetime import datetime, timezone
from sqlalchemy.ext.asyncio import AsyncSession
from config import PUBLIC_LINK, SUPERNODE
from database import filter_cluster_by_subgroup, filter_cluster_by_tariff, get_servers, get_tariff_by_id, store_key
from database.access.resolution import resolve_user_optional
from database.keys import delete_key_by_user_and_email, get_key_by_user_and_email
from database.models import Key
from database.tariffs import get_active_tariff_by_id
from logger import (
CLOGGER as logger,
PANEL_REMNA,
PANEL_XUI,
)
from panels._3xui import ClientConfig, add_client, get_xui_instance
from panels.remnawave_runtime import invalidate_remnawave_profile, with_remnawave_api
from services.clusters import ALLOWED_GROUP_CODES, select_cluster
from services.tariffs.tariff_display import GB, get_effective_limits_for_key
from .aggregated_links import make_aggregated_link
from .deletion import delete_key_from_cluster
async def update_key_on_cluster(
tg_id: int,
client_id: str,
email: str,
expiry_time: int,
cluster_id: str,
session: AsyncSession,
traffic_limit: int = None,
device_limit: int = None,
remnawave_link: str = None,
subgroup_code: str | None = None,
tariff_id: int | None = None,
external_squad_uuid: str | None = None,
):
try:
servers = await get_servers(session)
cluster = servers.get(cluster_id)
if not cluster:
found_servers = []
for _key, server_list in servers.items():
for server_info in server_list:
if server_info.get("server_name", "").lower() == cluster_id.lower():
found_servers.append(server_info)
if found_servers:
cluster = found_servers
else:
raise ValueError(f"Кластер или сервер с ID/именем {cluster_id} не найден.")
if tariff_id is not None:
filtered = await filter_cluster_by_tariff(session, cluster, tariff_id, cluster_id)
if filtered is not cluster:
cluster = filtered
elif subgroup_code:
cluster = await filter_cluster_by_subgroup(
session, cluster, subgroup_code, cluster_id, tariff_id=tariff_id
)
elif subgroup_code:
cluster = await filter_cluster_by_subgroup(session, cluster, subgroup_code, cluster_id, tariff_id=tariff_id)
if not cluster:
logger.warning(f"[Update] Нет серверов после фильтрации по привязкам в кластере {cluster_id}")
return client_id, remnawave_link
if tariff_id is not None:
tariff = await get_tariff_by_id(session, tariff_id)
if tariff:
gc = (tariff.get("group_code") or "").lower()
if gc in ALLOWED_GROUP_CODES:
bound_servers = [s for s in cluster if gc in (s.get("special_groups") or [])]
if bound_servers:
cluster = bound_servers
else:
logger.info(f"[Update] Нет серверов со спецгруппой '{gc}' в {cluster_id}")
if not cluster:
logger.warning(f"[Update] Нет серверов после фильтрации по спецгруппам в кластере {cluster_id}")
return client_id, remnawave_link
expire_iso = datetime.utcfromtimestamp(expiry_time / 1000).replace(tzinfo=timezone.utc).isoformat()
remnawave_servers = [s for s in cluster if s.get("panel_type", "3x-ui").lower() == "remnawave"]
xui_servers = [s for s in cluster if s.get("panel_type", "3x-ui").lower() == "3x-ui"]
remnawave_client_id = None
remnawave_link_value = None
if remnawave_servers:
inbound_ids = [s["inbound_id"] for s in remnawave_servers if s.get("inbound_id")]
group_code = remnawave_servers[0].get("tariff_group")
if not group_code:
raise ValueError("У Remnawave-сервера отсутствует tariff_group")
short_uuid = None
if remnawave_link and "/" in remnawave_link:
short_uuid = remnawave_link.rstrip("/").split("/")[-1]
logger.debug(f"{PANEL_REMNA} Извлечен short_uuid: {short_uuid}")
user_data = {
"username": email,
"trafficLimitStrategy": "NO_RESET",
"expireAt": expire_iso,
"telegramId": tg_id,
"activeInternalSquads": inbound_ids,
"uuid": client_id,
}
if external_squad_uuid:
user_data["activeExternalSquads"] = [external_squad_uuid]
user_data["activeExternalSquadUuids"] = [external_squad_uuid]
user_data["externalSquadUuid"] = external_squad_uuid
if traffic_limit is not None:
user_data["trafficLimitBytes"] = traffic_limit * 1024**3
if device_limit is not None:
user_data["hwidDeviceLimit"] = device_limit
if short_uuid:
user_data["shortUuid"] = short_uuid
logger.debug(f"{PANEL_REMNA} Добавлен short_uuid: {short_uuid}")
async def _recreate(api):
await api.delete_user(client_id)
return await api.create_user(user_data)
remna_result = await with_remnawave_api(
session,
str(remnawave_servers[0].get("server_name") or cluster_id),
_recreate,
fallback_any=True,
timeout_sec=12.0,
)
if remna_result:
remnawave_client_id = remna_result.get("uuid")
remnawave_link_value = remna_result.get("subscriptionUrl")
await invalidate_remnawave_profile(
session,
str(remnawave_servers[0].get("server_name") or cluster_id),
str(remnawave_client_id or client_id),
fallback_any=True,
)
logger.info(f"{PANEL_REMNA} Клиент заново создан, uuid={remnawave_client_id}")
else:
logger.error(f"{PANEL_REMNA} Не удалось авторизоваться/создать клиента")
if not remnawave_client_id:
logger.warning(f"{PANEL_REMNA} client_id не получен, используем исходный {client_id}")
remnawave_client_id = client_id
tasks = []
for server_info in xui_servers:
server_name = server_info.get("server_name", "unknown")
inbound_id = server_info.get("inbound_id")
if not inbound_id:
logger.warning(f"{PANEL_XUI} INBOUND_ID отсутствует для сервера {server_name}. Пропуск.")
continue
xui = await get_xui_instance(server_info["api_url"])
sub_id = email
unique_email = f"{email}_{server_name.lower()}" if SUPERNODE else email
group_code = server_info.get("tariff_group")
if not group_code:
raise ValueError(f"У сервера {server_name} отсутствует tariff_group")
total_gb_bytes = int(traffic_limit * 1024**3) if traffic_limit is not None else 0
device_limit_value = device_limit if device_limit is not None else 0
config = ClientConfig(
client_id=remnawave_client_id,
email=unique_email,
tg_id=tg_id,
limit_ip=device_limit_value,
total_gb=total_gb_bytes,
expiry_time=expiry_time,
enable=True,
flow="xtls-rprx-vision",
inbound_id=int(inbound_id),
sub_id=sub_id,
)
tasks.append(add_client(xui, config))
if tasks:
await asyncio.gather(*tasks, return_exceptions=True)
logger.info(f"[Update] Ключ {remnawave_client_id} обновлён на серверах подгруппы в {cluster_id}")
return remnawave_client_id, remnawave_link_value
except Exception as e:
logger.error(f"[Update Error] Ошибка при обновлении ключа {client_id} на {cluster_id}: {e}")
raise
async def update_subscription(
tg_id: int,
email: str,
session: AsyncSession,
cluster_override: str = None,
country_override: str = None,
remnawave_link: str = None,
) -> None:
u = await resolve_user_optional(session, tg_id)
if u is None:
raise ValueError(f"The key {email} does not exist in database")
uid = u.id
record: Key | None = await get_key_by_user_and_email(session, uid, email)
if not record:
raise ValueError(f"The key {email} does not exist in database")
expiry_time = record.expiry_time
client_id = record.client_id
old_cluster_id = record.server_id
tariff_id = record.tariff_id
alias = record.alias
remnawave_link = remnawave_link or record.remnawave_link
public_link = f"{PUBLIC_LINK}{email}/{tg_id}"
selected_device_limit = getattr(record, "selected_device_limit", None)
selected_traffic_limit = getattr(record, "selected_traffic_limit", None)
selected_price_rub = getattr(record, "selected_price_rub", None)
current_device_limit_db = getattr(record, "current_device_limit", None)
current_traffic_limit_db = getattr(record, "current_traffic_limit", None)
tariff = None
subgroup_code = getattr(record, "subgroup_code", None)
external_squad_uuid = None
if tariff_id:
tariff = await get_active_tariff_by_id(session, int(tariff_id))
if tariff is None:
logger.warning(f"[LOG] update_subscription: тариф с id={tariff_id} не найден!")
else:
if not subgroup_code:
subgroup_code = getattr(tariff, "subgroup_code", None) or getattr(tariff, "subgroup_title", None)
external_squad_uuid = tariff.external_squad
else:
logger.warning("[LOG] update_subscription: tariff_id отсутствует!")
from middlewares.session import release_session_early
await release_session_early(session)
await delete_key_from_cluster(old_cluster_id, email, client_id, session=session)
await delete_key_by_user_and_email(session, uid, email)
await session.commit()
if country_override or cluster_override:
new_cluster_id = country_override or cluster_override
else:
try:
result = await select_cluster(session)
new_cluster_id = result.cluster_name
except ValueError:
logger.warning("[Update] Нет доступных кластеров, оставляем на старом")
new_cluster_id = old_cluster_id
servers = await get_servers(session)
cluster_servers = servers.get(new_cluster_id)
if cluster_servers is None:
for server_list in servers.values():
for server_info in server_list:
if server_info.get("server_name", "").lower() == new_cluster_id.lower():
cluster_servers = [server_info]
break
if cluster_servers:
break
else:
cluster_servers = []
if tariff_id is not None:
filtered = await filter_cluster_by_tariff(session, cluster_servers, tariff_id, new_cluster_id)
if filtered is not cluster_servers:
cluster_servers = filtered
elif subgroup_code:
cluster_servers = await filter_cluster_by_subgroup(
session, cluster_servers, subgroup_code, new_cluster_id, tariff_id=tariff_id
)
elif subgroup_code:
cluster_servers = await filter_cluster_by_subgroup(
session, cluster_servers, subgroup_code, new_cluster_id, tariff_id=tariff_id
)
if not cluster_servers:
logger.warning(f"[Update] Пересоздание пропущено: нет серверов после фильтрации в {new_cluster_id}.")
return
if tariff:
gc = (getattr(tariff, "group_code", None) or "").lower()
if gc in ALLOWED_GROUP_CODES:
bound_servers = [s for s in cluster_servers if gc in (s.get("special_groups") or [])]
if bound_servers:
cluster_servers = bound_servers
else:
logger.info(f"[Update] Нет серверов со спецгруппой '{gc}' в {new_cluster_id}")
if not cluster_servers:
logger.warning(
f"[Update] Пересоздание пропущено: нет серверов после фильтрации по спецгруппам в {new_cluster_id}."
)
return
traffic_limit_gb = None
device_limit = 0
if tariff and tariff_id:
device_limit_effective, traffic_limit_bytes_effective = await get_effective_limits_for_key(
session=session,
tariff_id=int(tariff_id),
selected_device_limit=int(selected_device_limit) if selected_device_limit is not None else None,
selected_traffic_gb=int(selected_traffic_limit) if selected_traffic_limit is not None else None,
)
device_limit = int(device_limit_effective or 0)
traffic_limit_gb = int(traffic_limit_bytes_effective / GB) if traffic_limit_bytes_effective else None
elif tariff:
traffic_limit_gb = 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 0
if current_device_limit_db is not None:
device_limit = int(current_device_limit_db)
if current_traffic_limit_db is not None:
traffic_limit_gb = int(current_traffic_limit_db)
new_client_id, remnawave_link_value = await update_key_on_cluster(
tg_id=tg_id,
client_id=client_id,
email=email,
expiry_time=expiry_time,
cluster_id=new_cluster_id,
session=session,
traffic_limit=traffic_limit_gb,
device_limit=device_limit,
remnawave_link=remnawave_link,
subgroup_code=subgroup_code,
tariff_id=tariff_id,
external_squad_uuid=external_squad_uuid,
)
aggregated = await make_aggregated_link(
session=session,
cluster_all=cluster_servers,
cluster_id=new_cluster_id,
email=email,
client_id=new_client_id,
tg_id=tg_id,
subgroup_code=subgroup_code,
remna_link_override=None,
plan=tariff_id,
)
final_key_link = aggregated or public_link
await store_key(
session=session,
legacy_user_ref=tg_id,
client_id=new_client_id,
email=email,
expiry_time=expiry_time,
key=final_key_link,
remnawave_link=remnawave_link_value or remnawave_link,
server_id=new_cluster_id,
tariff_id=tariff_id,
alias=alias,
selected_device_limit=selected_device_limit,
selected_traffic_limit=selected_traffic_limit,
selected_price_rub=selected_price_rub,
current_device_limit=current_device_limit_db,
current_traffic_limit=current_traffic_limit_db,
)