Initial commit: VPN Telegram bot (sanitized defaults)

This commit is contained in:
VPN Service
2026-07-26 23:21:41 +03:00
commit aaf04faa96
77 changed files with 33249 additions and 0 deletions
+134
View File
@@ -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)