Initial commit: VPN Telegram bot (sanitized defaults)
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
"""Лимиты и пакеты трафика."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
# База на подписку + докупка
|
||||
BASE_TRAFFIC_GB = 1000.0
|
||||
PACK_GB = 500.0
|
||||
PACK_PRICE_RUB = 50.0
|
||||
|
||||
GB = 1024 * 1024 * 1024
|
||||
|
||||
|
||||
def gb_to_bytes(gb: float) -> float:
|
||||
return float(gb) * GB
|
||||
|
||||
|
||||
def bytes_to_gb(value: float | int | None) -> float:
|
||||
if not value:
|
||||
return 0.0
|
||||
return float(value) / GB
|
||||
|
||||
|
||||
def format_gb(gb: float, *, digits: int = 1) -> str:
|
||||
if gb >= 100 and abs(gb - round(gb)) < 0.05:
|
||||
return f"{int(round(gb))} ГБ"
|
||||
if digits <= 0:
|
||||
return f"{int(round(gb))} ГБ"
|
||||
text = f"{gb:.{digits}f}".rstrip("0").rstrip(".")
|
||||
return f"{text} ГБ"
|
||||
|
||||
|
||||
def effective_limit_bytes(limit_bytes: float | int | None) -> float:
|
||||
"""0 / None в панели = безлимит; для нас база = 1000 ГБ."""
|
||||
raw = float(limit_bytes or 0)
|
||||
if raw <= 0:
|
||||
return gb_to_bytes(BASE_TRAFFIC_GB)
|
||||
return raw
|
||||
|
||||
|
||||
def ensure_base_limit_bytes(current_limit_bytes: float | int | None) -> float:
|
||||
"""Не меньше базовых 1000 ГБ; безлимит (0) → база."""
|
||||
raw = float(current_limit_bytes or 0)
|
||||
base = gb_to_bytes(BASE_TRAFFIC_GB)
|
||||
if raw <= 0:
|
||||
return base
|
||||
return max(raw, base)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class TrafficPack:
|
||||
packs: int
|
||||
gb: float
|
||||
price: float
|
||||
|
||||
@property
|
||||
def id(self) -> str:
|
||||
return f"pack_{self.packs}"
|
||||
|
||||
@property
|
||||
def title(self) -> str:
|
||||
return f"+{format_gb(self.gb, digits=0)}"
|
||||
|
||||
def format_price(self) -> str:
|
||||
if self.price == int(self.price):
|
||||
return f"{int(self.price)} ₽/мес"
|
||||
return f"{self.price:.2f} ₽/мес"
|
||||
|
||||
|
||||
# Готовые пакеты: 1 / 2 / 4 / 10 × 500 ГБ
|
||||
TRAFFIC_PACKS: tuple[TrafficPack, ...] = tuple(
|
||||
TrafficPack(
|
||||
packs=n,
|
||||
gb=PACK_GB * n,
|
||||
price=PACK_PRICE_RUB * n,
|
||||
)
|
||||
for n in (1, 2, 4, 10)
|
||||
)
|
||||
|
||||
|
||||
def get_traffic_pack(pack_id: str) -> TrafficPack | None:
|
||||
for p in TRAFFIC_PACKS:
|
||||
if p.id == pack_id or str(p.packs) == pack_id:
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class TrafficUsage:
|
||||
used_bytes: float
|
||||
limit_bytes: float
|
||||
|
||||
@property
|
||||
def used_gb(self) -> float:
|
||||
return bytes_to_gb(self.used_bytes)
|
||||
|
||||
@property
|
||||
def limit_gb(self) -> float:
|
||||
return bytes_to_gb(self.limit_bytes)
|
||||
|
||||
@property
|
||||
def left_gb(self) -> float:
|
||||
return max(0.0, self.limit_gb - self.used_gb)
|
||||
|
||||
@property
|
||||
def percent(self) -> float:
|
||||
if self.limit_bytes <= 0:
|
||||
return 0.0
|
||||
return min(100.0, max(0.0, 100.0 * self.used_bytes / self.limit_bytes))
|
||||
|
||||
def format_line(self) -> str:
|
||||
return (
|
||||
f"📦 Трафик: <b>{format_gb(self.used_gb)} / {format_gb(self.limit_gb)}</b> "
|
||||
f"({self.percent:.0f}%)\n"
|
||||
f"Осталось: <b>{format_gb(self.left_gb)}</b>"
|
||||
)
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
return {
|
||||
"used_gb": round(self.used_gb, 2),
|
||||
"limit_gb": round(self.limit_gb, 2),
|
||||
"left_gb": round(self.left_gb, 2),
|
||||
"percent": round(self.percent, 1),
|
||||
"used_label": format_gb(self.used_gb),
|
||||
"limit_label": format_gb(self.limit_gb),
|
||||
"left_label": format_gb(self.left_gb),
|
||||
}
|
||||
|
||||
|
||||
def traffic_from_panel_user(user) -> TrafficUsage:
|
||||
used = float(getattr(user, "used_traffic_bytes", 0) or 0)
|
||||
limit = effective_limit_bytes(getattr(user, "traffic_limit_bytes", 0))
|
||||
return TrafficUsage(used_bytes=used, limit_bytes=limit)
|
||||
Reference in New Issue
Block a user