Files

408 lines
15 KiB
Python

from typing import Any
from sqlalchemy.ext.asyncio import AsyncSession
from config import REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD
from core.settings.tariffs_config import TARIFFS_CONFIG, normalize_tariff_config
from database import get_servers, get_tariff_by_id
from database.models import Key
from handlers.texts import key_message_success
from logger import logger
GB = 1024 * 1024 * 1024
async def get_effective_limits_for_key(
session: AsyncSession,
tariff_id: int | None,
selected_device_limit: int | None,
selected_traffic_gb: int | None,
) -> tuple[int, int]:
"""Возвращает лимиты устройств и трафика с учётом выбранных значений."""
tariff = await get_tariff_by_id(session, int(tariff_id)) if tariff_id else None
if tariff:
base_devices = tariff.get("device_limit")
base_traffic_gb = tariff.get("traffic_limit")
else:
base_devices = None
base_traffic_gb = None
if selected_device_limit is None:
device_limit = int(base_devices or 0)
elif selected_device_limit == 0:
device_limit = 0
else:
device_limit = int(selected_device_limit)
if selected_traffic_gb is None:
traffic_limit_bytes = int(base_traffic_gb or 0) * GB
elif selected_traffic_gb == 0:
traffic_limit_bytes = 0
else:
traffic_limit_bytes = int(selected_traffic_gb) * GB
return device_limit, traffic_limit_bytes
async def resolve_price_to_charge(session: AsyncSession, state_data: dict[str, Any]) -> int | None:
"""Считает цену к списанию по состоянию, с учётом конфигуратора и наценок."""
price = state_data.get("selected_price_rub")
if price is not None:
try:
return int(price)
except (TypeError, ValueError):
return None
tariff_id = state_data.get("tariff_id")
if not tariff_id:
return None
tariff = await get_tariff_by_id(session, int(tariff_id))
if not tariff:
return None
try:
base_price = int(tariff.get("price_rub") or 0)
except (TypeError, ValueError):
return None
if not bool(tariff.get("configurable")):
return base_price
cfg = normalize_tariff_config(tariff)
device_options = cfg.get("device_options") or []
traffic_options_gb = cfg.get("traffic_options_gb") or []
try:
base_device_limit = int(min(device_options)) if device_options else int(tariff.get("device_limit") or 0)
except (TypeError, ValueError):
base_device_limit = 0
try:
base_traffic_gb = int(min(traffic_options_gb)) if traffic_options_gb else int(tariff.get("traffic_limit") or 0)
except (TypeError, ValueError):
base_traffic_gb = 0
selected_device_limit = state_data.get("selected_device_limit")
selected_traffic_gb = state_data.get("selected_traffic_limit")
try:
device_target = int(selected_device_limit) if selected_device_limit is not None else base_device_limit
except (TypeError, ValueError):
device_target = base_device_limit
try:
traffic_target_gb = int(selected_traffic_gb) if selected_traffic_gb is not None else base_traffic_gb
except (TypeError, ValueError):
traffic_target_gb = base_traffic_gb
try:
device_step_rub = int(cfg.get("device_step_rub") or 0)
except (TypeError, ValueError):
device_step_rub = 0
try:
traffic_step_rub = int(cfg.get("traffic_step_rub") or 0)
except (TypeError, ValueError):
traffic_step_rub = 0
device_overrides = cfg.get("device_overrides") or {}
traffic_overrides = cfg.get("traffic_overrides") or {}
device_add_rub = 0
if device_target > base_device_limit:
override_value = device_overrides.get(str(device_target), device_overrides.get(device_target))
if override_value is not None:
try:
device_add_rub = int(override_value)
except (TypeError, ValueError):
device_add_rub = 0
else:
device_add_rub = (device_target - base_device_limit) * device_step_rub
traffic_add_rub = 0
if traffic_target_gb > base_traffic_gb:
override_value = traffic_overrides.get(str(traffic_target_gb), traffic_overrides.get(traffic_target_gb))
if override_value is not None:
try:
traffic_add_rub = int(override_value)
except (TypeError, ValueError):
traffic_add_rub = 0
else:
traffic_add_rub = (traffic_target_gb - base_traffic_gb) * traffic_step_rub
return int(base_price + device_add_rub + traffic_add_rub)
async def resolve_vless_enabled(session: AsyncSession, tariff_id: int | None) -> bool:
"""Проверяет, включён ли VLESS в тарифе."""
if not tariff_id:
return False
tariff = await get_tariff_by_id(session, int(tariff_id))
if not tariff:
return False
return bool(tariff.get("vless"))
async def get_key_tariff_display(
session: AsyncSession,
key_record: dict[str, Any],
selected_device_limit_override: int | None = None,
selected_traffic_gb_override: int | None = None,
) -> tuple[str, str, int, int, bool]:
"""Возвращает отображение тарифа и эффективные лимиты, приоритет — данные панели."""
tariff_id = key_record.get("tariff_id")
if not tariff_id:
return "", "", 0, 0, False
selected_device_limit = selected_device_limit_override
selected_traffic_gb = selected_traffic_gb_override
if selected_device_limit is None:
value = key_record.get("selected_device_limit")
if value is not None:
try:
selected_device_limit = int(value)
except (TypeError, ValueError):
selected_device_limit = None
if selected_traffic_gb is None:
value = key_record.get("selected_traffic_limit")
if value is not None:
try:
selected_traffic_gb = int(value)
except (TypeError, ValueError):
selected_traffic_gb = None
device_limit, traffic_limit_bytes = await get_effective_limits_for_key(
session=session,
tariff_id=int(tariff_id),
selected_device_limit=selected_device_limit,
selected_traffic_gb=selected_traffic_gb,
)
server_cluster_id = key_record.get("server_id")
client_id = key_record.get("client_id")
if server_cluster_id and client_id:
try:
servers = await get_servers(session)
cluster_servers = servers.get(server_cluster_id) or servers.get(str(server_cluster_id)) or []
remna_server = next((srv for srv in cluster_servers if srv.get("panel_type") == "remnawave"), None)
if not remna_server:
remna_server = next(
(srv for cl in servers.values() for srv in cl if srv.get("panel_type") == "remnawave"),
None,
)
if remna_server:
from panels.remnawave import RemnawaveAPI
api = RemnawaveAPI(remna_server["api_url"])
try:
ok = await api.login(REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD)
except Exception as e:
logger.warning(f"[KeyTariffDisplay] Remnawave login error for {client_id}: {e}")
ok = False
if ok:
try:
user_data = await api.get_user_by_uuid(client_id)
except Exception as e:
logger.warning(f"[KeyTariffDisplay] Remnawave get_user_by_uuid error for {client_id}: {e}")
user_data = None
if user_data:
panel_traffic_limit_bytes = user_data.get("trafficLimitBytes")
panel_device_limit = user_data.get("hwidDeviceLimit")
if panel_traffic_limit_bytes is not None:
try:
traffic_limit_bytes = int(panel_traffic_limit_bytes)
except (TypeError, ValueError):
logger.warning(
f"[KeyTariffDisplay] Invalid trafficLimitBytes from Remnawave for {client_id}: {panel_traffic_limit_bytes}"
)
if panel_device_limit is not None:
try:
device_limit = int(panel_device_limit)
except (TypeError, ValueError):
logger.warning(
f"[KeyTariffDisplay] Invalid hwidDeviceLimit from Remnawave for {client_id}: {panel_device_limit}"
)
try:
await api.aclose()
except Exception:
pass
except Exception as e:
logger.warning(f"[KeyTariffDisplay] Error while overriding limits from panel: {e}")
traffic_limit_gb = int(traffic_limit_bytes / GB) if traffic_limit_bytes else 0
tariff = await get_tariff_by_id(session, int(tariff_id))
if tariff:
tariff_name = tariff.get("name", "")
subgroup_title = tariff.get("subgroup_title") or ""
vless_enabled = bool(tariff.get("vless"))
else:
tariff_name = ""
subgroup_title = ""
vless_enabled = False
return tariff_name, subgroup_title, traffic_limit_gb, device_limit, vless_enabled
async def get_key_tariff_addons_state(
session: AsyncSession,
key_record: dict[str, Any],
db_key: Key | None,
) -> tuple[str, str, int, int, bool, bool, bool, bool]:
"""Возвращает параметры тарифа и допы для ключа."""
tariff_id = key_record.get("tariff_id")
if not tariff_id:
return "", "", 0, 0, False, False, False, False
selected_device_limit_override: int | None = None
selected_traffic_gb_override: int | None = None
if db_key:
if db_key.selected_device_limit is not None:
try:
selected_device_limit_override = int(db_key.selected_device_limit)
except (TypeError, ValueError):
selected_device_limit_override = None
if db_key.selected_traffic_limit is not None:
try:
selected_traffic_gb_override = int(db_key.selected_traffic_limit)
except (TypeError, ValueError):
selected_traffic_gb_override = None
(
tariff_name,
subgroup_title,
traffic_limit_gb,
device_limit,
vless_enabled,
) = await get_key_tariff_display(
session=session,
key_record=key_record,
selected_device_limit_override=selected_device_limit_override,
selected_traffic_gb_override=selected_traffic_gb_override,
)
unlimited_devices = device_limit == 0
unlimited_traffic = traffic_limit_gb == 0
if unlimited_devices or unlimited_traffic:
suffix_parts: list[str] = []
if unlimited_traffic:
suffix_parts.append("безлимит трафика")
if unlimited_devices:
suffix_parts.append("безлимит устройств")
tariff_name = f"{tariff_name} ({', '.join(suffix_parts)})"
is_tariff_configurable = False
addons_devices_enabled = False
addons_traffic_enabled = False
tariff = await get_tariff_by_id(session, int(tariff_id))
if tariff and tariff.get("configurable"):
is_tariff_configurable = True
cfg = normalize_tariff_config(tariff)
device_options = cfg.get("device_options") or []
traffic_options = cfg.get("traffic_options_gb") or []
addons_devices_enabled = bool(device_options)
addons_traffic_enabled = bool(traffic_options)
mode = TARIFFS_CONFIG.get("KEY_ADDONS_PACK_MODE") or ""
if not mode:
pass
elif mode == "traffic":
addons_devices_enabled = False
elif mode == "devices":
addons_traffic_enabled = False
elif mode == "all":
pass
else:
logger.warning(f"Некорректный KEY_ADDONS_PACK_MODE: {mode!r}")
if unlimited_devices:
addons_devices_enabled = False
if unlimited_traffic:
addons_traffic_enabled = False
return (
tariff_name,
subgroup_title,
traffic_limit_gb,
device_limit,
vless_enabled,
is_tariff_configurable,
addons_devices_enabled,
addons_traffic_enabled,
)
async def build_key_created_message(
session: AsyncSession,
key_record: dict[str, Any],
final_link: str,
selected_device_limit: int | None = None,
selected_traffic_gb: int | None = None,
) -> str:
"""Собирает сообщение об успешном создании ключа с отображением выбранных лимитов."""
tariff_id = key_record.get("tariff_id")
tariff = await get_tariff_by_id(session, int(tariff_id)) if tariff_id else None
if tariff:
tariff_name = tariff.get("name", "")
subgroup_title = tariff.get("subgroup_title") or ""
else:
tariff_name = ""
subgroup_title = ""
selected_device_limit_effective = selected_device_limit
if selected_device_limit_effective is None:
value = key_record.get("selected_device_limit")
if value is not None:
try:
selected_device_limit_effective = int(value)
except (TypeError, ValueError):
selected_device_limit_effective = None
selected_traffic_gb_effective = selected_traffic_gb
if selected_traffic_gb_effective is None:
value = key_record.get("selected_traffic_limit")
if value is not None:
try:
selected_traffic_gb_effective = int(value)
except (TypeError, ValueError):
selected_traffic_gb_effective = None
device_limit, traffic_limit_bytes = await get_effective_limits_for_key(
session=session,
tariff_id=int(tariff_id) if tariff_id else None,
selected_device_limit=selected_device_limit_effective,
selected_traffic_gb=selected_traffic_gb_effective,
)
traffic_to_show = int(traffic_limit_bytes / GB) if traffic_limit_bytes else 0
devices_to_show = int(device_limit) if device_limit else 0
return key_message_success(
final_link or "Ссылка не найдена",
tariff_name=tariff_name,
traffic_limit=traffic_to_show,
device_limit=devices_to_show,
subgroup_title=subgroup_title,
)