Files
Solo_bot/handlers/keys/operations/renewal.py
T

240 lines
11 KiB
Python

import asyncio
from datetime import datetime
from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession
from config import REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD, SUPERNODE
from database import delete_notification, get_servers
from database.models import Key, Server, Tariff
from database.notifications import clear_hot_lead_notifications
from logger import logger
from panels._3xui import ClientConfig, add_client, extend_client_key, get_xui_instance
from panels.remnawave import RemnawaveAPI
async def renew_key_in_cluster(
cluster_id: str,
email: str,
client_id: str,
new_expiry_time: int,
total_gb: int,
session: AsyncSession,
hwid_device_limit: int = 0,
reset_traffic: bool = True,
):
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} не найден.")
result = await session.execute(select(Key.tg_id, Key.server_id).where(Key.client_id == client_id).limit(1))
row = result.first()
if not row:
logger.error(f"Не найден пользователь с client_id={client_id} в таблице keys.")
return False
tg_id, server_id = row
result = await session.execute(select(Server.tariff_group).where(Server.server_name == server_id))
tariff_group_row = result.scalar_one_or_none()
if tariff_group_row:
result = await session.execute(
select(Tariff)
.where(Tariff.group_code == tariff_group_row, Tariff.is_active.is_(True))
.order_by(Tariff.duration_days.desc())
.limit(1)
)
tariff = result.scalar_one_or_none()
if tariff and tariff.device_limit is not None:
hwid_device_limit = int(tariff.device_limit)
remnawave_inbound_ids = []
tasks = []
for server_info in cluster:
if server_info.get("panel_type", "3x-ui").lower() == "remnawave":
inbound_id = server_info.get("inbound_id")
if inbound_id:
remnawave_inbound_ids.append(inbound_id)
if remnawave_inbound_ids:
remnawave_server = next(
(
s
for s in cluster
if s.get("panel_type", "").lower() == "remnawave" and s.get("inbound_id") in remnawave_inbound_ids
),
None,
)
if remnawave_server:
remna = RemnawaveAPI(remnawave_server["api_url"])
if await remna.login(REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD):
expire_iso = datetime.utcfromtimestamp(new_expiry_time // 1000).isoformat() + "Z"
traffic_limit_bytes = total_gb * 1024 * 1024 * 1024 if total_gb else 0
updated = await remna.update_user(
uuid=client_id,
expire_at=expire_iso,
active_user_inbounds=remnawave_inbound_ids,
traffic_limit_bytes=traffic_limit_bytes,
hwid_device_limit=hwid_device_limit,
)
if updated:
logger.info(f"Подписка Remnawave {client_id} успешно продлена")
if reset_traffic:
await remna.reset_user_traffic(client_id)
else:
logger.warning(f"Не удалось продлить подписку Remnawave {client_id}, пробуем создать")
result = await session.execute(
select(Key.remnawave_link, Key.key).where(Key.client_id == client_id)
)
row = result.one_or_none()
remnawave_link = row[0] if row else None
row[1] if row else None
user_data = {
"username": email,
"trafficLimitStrategy": "NO_RESET",
"expireAt": expire_iso,
"telegramId": tg_id,
"activeInternalSquads": remnawave_inbound_ids,
}
if remnawave_link and "/" in remnawave_link:
user_data["shortUuid"] = remnawave_link.rstrip("/").split("/")[-1]
if traffic_limit_bytes and traffic_limit_bytes > 0:
user_data["trafficLimitBytes"] = traffic_limit_bytes
if hwid_device_limit is not None:
user_data["hwidDeviceLimit"] = hwid_device_limit
result = await remna.create_user(user_data)
if result:
new_client_id = result.get("uuid")
new_remnawave_link = result.get("subscriptionUrl")
logger.info(f"Пользователь Remnawave {client_id} успешно создан")
await session.execute(
update(Key)
.where(Key.client_id == client_id)
.values(client_id=new_client_id, remnawave_link=new_remnawave_link)
)
await session.commit()
else:
logger.error(f"Не удалось создать пользователя Remnawave {client_id}")
else:
logger.error("Не удалось войти в Remnawave API")
tasks = []
for server_info in cluster:
if server_info.get("panel_type", "3x-ui").lower() != "3x-ui":
continue
inbound_id = server_info.get("inbound_id")
server_name = server_info.get("server_name", "unknown")
if not inbound_id:
logger.warning(f"INBOUND_ID отсутствует для сервера {server_name}. Пропуск.")
continue
if SUPERNODE:
unique_email = f"{email}_{server_name.lower()}"
sub_id = email
else:
unique_email = email
sub_id = unique_email
traffic_bytes = total_gb * 1024 * 1024 * 1024 if total_gb else 0
async def process_server(server_info, inbound_id, unique_email, sub_id, server_name):
try:
xui = await get_xui_instance(server_info["api_url"])
except Exception as e:
logger.warning(f"[{server_name}] недоступна панель 3x-ui: {e}")
return server_name, False, f"api_unavailable: {e}"
try:
updated = await extend_client_key(
xui=xui,
inbound_id=int(inbound_id),
email=unique_email,
new_expiry_time=new_expiry_time,
client_id=client_id,
total_gb=traffic_bytes,
sub_id=sub_id,
tg_id=tg_id,
limit_ip=hwid_device_limit,
)
except Exception as e:
logger.warning(f"[{server_name}] ошибка при продлении: {e}")
updated = False
if updated:
return server_name, True, None
logger.warning(f"[{server_name}] не удалось обновить {unique_email}, пробуем создать")
try:
config = ClientConfig(
client_id=client_id,
email=unique_email,
tg_id=tg_id,
limit_ip=hwid_device_limit if hwid_device_limit is not None else 0,
total_gb=traffic_bytes,
expiry_time=new_expiry_time,
enable=True,
flow="xtls-rprx-vision",
inbound_id=int(inbound_id),
sub_id=sub_id,
)
await add_client(xui, config)
return server_name, True, None
except Exception as e:
logger.warning(f"[{server_name}] не удалось создать клиента: {e}")
return server_name, False, f"create_failed: {e}"
tasks.append(process_server(server_info, inbound_id, unique_email, sub_id, server_name))
results = await asyncio.gather(*tasks, return_exceptions=True)
failed = []
succeeded = []
for r in results:
if isinstance(r, Exception):
failed.append(("unknown", f"task_exception: {r}"))
continue
name, ok, err = r
if ok:
succeeded.append(name)
else:
failed.append((name, err or "unknown_error"))
if succeeded:
logger.info(f"3x-ui продлено на: {', '.join(succeeded)}")
if failed:
logger.warning("3x-ui не продлено на: " + ", ".join([f"{n} ({e})" for n, e in failed]))
await asyncio.gather(*tasks, return_exceptions=True)
notification_prefixes = ["key_24h", "key_10h", "key_expired", "renew"]
for notif in notification_prefixes:
notification_id = f"{email}_{notif}"
await delete_notification(session, tg_id, notification_id)
logger.info(f"🧹 Уведомления для ключа {email} очищены при продлении.")
try:
await clear_hot_lead_notifications(session, tg_id)
except Exception as e:
logger.warning(f"Не удалось очистить уведомления о скидках для {tg_id} при продлении: {e}")
except Exception as e:
logger.error(f"Не удалось продлить ключ {client_id} в кластере/на сервере {cluster_id}: {e}")
raise