v2.5.0: add shared 443 and per-user traffic

This commit is contained in:
Виталий Литвинов
2026-04-25 14:07:47 +03:00
parent c1b5ffc5a7
commit 63b564f70f
12 changed files with 990 additions and 34 deletions

View File

@@ -105,6 +105,7 @@ logger = logging.getLogger(__name__)
GOTELEGRAM_VERSION = "2.5.0"
GOTELEGRAM_CONFIG = "/opt/gotelegram/config.json"
DISABLED_USERS_FILE = "/opt/gotelegram/disabled_users.json"
USER_STATS_HISTORY = "/opt/gotelegram/user_stats_history.csv"
USER_LOCK_FILE = "/run/gotelegram/admin-users.lock"
TELEMT_CONFIG = "/etc/telemt/config.toml"
TELEMT_SERVICE = "telemt"
@@ -124,6 +125,17 @@ ENV_FILE = "/opt/gotelegram-bot/.env"
ADMIN_WEB_SERVICE = "gotelegram-admin"
ADMIN_WEB_PORT = 1984
def format_bytes_human(value: int) -> str:
value = max(0, int(value or 0))
if value < 1024:
return f"{value} B"
if value < 1024 * 1024:
return f"{value / 1024:.1f} KB"
if value < 1024 * 1024 * 1024:
return f"{value / 1024 / 1024:.1f} MB"
return f"{value / 1024 / 1024 / 1024:.1f} GB"
# ── Загрузка ALLOWED_IDS ────────────────────────────────────────────────────
# Поддерживает запятую, пробел, или их комбинацию как разделитель
ALLOWED_IDS: set = set()
@@ -1568,6 +1580,42 @@ def _extract_traffic_value(data: Any, keys: List[str]) -> int:
return 0
def user_traffic_history_summary(name: str) -> str:
rows: List[Dict[str, int]] = []
try:
with open(USER_STATS_HISTORY, "r", encoding="utf-8", errors="ignore") as f:
reader = csv.DictReader(f)
previous = None
for row in reader:
if row.get("user") != name:
continue
try:
item = {
"epoch": int(row.get("epoch") or 0),
"total_octets": int(row.get("total_octets") or 0),
}
except ValueError:
continue
item["total_delta"] = max(0, item["total_octets"] - previous["total_octets"]) if previous else 0
rows.append(item)
previous = item
except Exception:
rows = []
if not rows:
return "\n<i>История по ключу пока не накоплена.</i>"
latest = max(row["epoch"] for row in rows)
periods = [("15 мин", 15 * 60), ("1 час", 60 * 60), ("24 часа", 24 * 60 * 60), ("Месяц", 30 * 24 * 60 * 60)]
lines = ["\n<b>История трафика:</b>", "<pre>", f"{'Период':<8}{'Трафик':>10}", "" * 23]
for label, seconds in periods:
window = [row for row in rows if row["epoch"] >= latest - seconds]
total = sum(max(0, row.get("total_delta", 0)) for row in window)
lines.append(f"{label:<8}{format_bytes_human(total):>10}")
lines.append("</pre>")
return "\n".join(lines)
async def get_proxy_link_for_secret(secret: str) -> Optional[str]:
"""Generate a fake-TLS proxy link for an arbitrary telemt user secret."""
config = load_json(GOTELEGRAM_CONFIG) or {}
@@ -1747,16 +1795,16 @@ async def _user_detail_text(name: str, secret: str, enabled: bool = True) -> str
details = ""
if api:
data = api.get("data", api)
up = _extract_traffic_value(data, ["upload_bytes", "uplink_bytes", "tx_bytes", "sent_bytes", "up"])
down = _extract_traffic_value(data, ["download_bytes", "downlink_bytes", "rx_bytes", "received_bytes", "down"])
active_ips = _extract_traffic_value(data, ["active_ips", "unique_ips"])
total = int(data.get("total_octets") or 0) if isinstance(data, dict) else 0
conns = int(data.get("current_connections") or 0) if isinstance(data, dict) else 0
active_ips = int(data.get("active_unique_ips") or 0) if isinstance(data, dict) else 0
recent_ips = int(data.get("recent_unique_ips") or 0) if isinstance(data, dict) else 0
parts = []
if up:
parts.append(f"{up} B")
if down:
parts.append(f"{down} B")
if active_ips:
parts.append(f"active IPs: {active_ips}")
parts.append(f"Трафик всего: <b>{format_bytes_human(total)}</b>")
parts.append(f"Подключения: <code>{conns}</code>")
parts.append(f"Активные IP: <code>{active_ips}</code>")
if recent_ips:
parts.append(f"Недавние IP: <code>{recent_ips}</code>")
if parts:
details = "\n" + "\n".join(parts)
else:
@@ -1766,6 +1814,7 @@ async def _user_detail_text(name: str, secret: str, enabled: bool = True) -> str
details = "\n<i>Runtime API недоступен. Новые установки goTelegram Pro включают его автоматически.</i>"
else:
details = "\n<i>Ключ отключён и сейчас не принимается telemt.</i>"
details += user_traffic_history_summary(name)
link_line = html.escape(link) if link else "link unavailable"
status_line = "🟢 enabled" if enabled else "⏸ disabled"