Initial commit: VPN Telegram bot (sanitized defaults)
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
"""Статус и TCP-пинг нод Remnawave."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import socket
|
||||
from dataclasses import dataclass
|
||||
|
||||
from remnawave import RemnawaveSDK
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class NodeStatus:
|
||||
name: str
|
||||
address: str
|
||||
port: int | None
|
||||
country_code: str
|
||||
panel_connected: bool
|
||||
panel_disabled: bool
|
||||
tcp_ok: bool
|
||||
tcp_ms: float | None
|
||||
users_online: int | None
|
||||
last_status_message: str | None
|
||||
|
||||
@property
|
||||
def online(self) -> bool:
|
||||
if self.panel_disabled:
|
||||
return False
|
||||
# Онлайн только если панель видит ноду и TCP отвечает
|
||||
return self.panel_connected and self.tcp_ok
|
||||
|
||||
@property
|
||||
def label(self) -> str:
|
||||
if self.panel_disabled:
|
||||
return "Выключен"
|
||||
if self.online:
|
||||
return "Онлайн"
|
||||
return "Оффлайн"
|
||||
|
||||
|
||||
async def tcp_ping(host: str, port: int, timeout: float = 2.5) -> tuple[bool, float | None]:
|
||||
loop = asyncio.get_running_loop()
|
||||
started = loop.time()
|
||||
|
||||
def _connect() -> None:
|
||||
with socket.create_connection((host, port), timeout=timeout):
|
||||
return None
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(asyncio.to_thread(_connect), timeout=timeout + 0.5)
|
||||
ms = (loop.time() - started) * 1000
|
||||
return True, round(ms, 1)
|
||||
except Exception: # noqa: BLE001 — любой сбой = недоступен
|
||||
return False, None
|
||||
|
||||
|
||||
async def fetch_nodes_status(sdk: RemnawaveSDK) -> list[NodeStatus]:
|
||||
nodes = await sdk.nodes.get_all_nodes()
|
||||
results: list[NodeStatus] = []
|
||||
|
||||
async def _one(node) -> NodeStatus:
|
||||
port = node.port or 443
|
||||
tcp_ok, tcp_ms = await tcp_ping(node.address, port)
|
||||
return NodeStatus(
|
||||
name=node.name,
|
||||
address=node.address,
|
||||
port=node.port,
|
||||
country_code=node.country_code or "XX",
|
||||
panel_connected=bool(node.is_connected),
|
||||
panel_disabled=bool(node.is_disabled),
|
||||
tcp_ok=tcp_ok,
|
||||
tcp_ms=tcp_ms,
|
||||
users_online=node.users_online,
|
||||
last_status_message=node.last_status_message,
|
||||
)
|
||||
|
||||
tasks = [_one(node) for node in nodes]
|
||||
if tasks:
|
||||
results = list(await asyncio.gather(*tasks))
|
||||
# сохраняем порядок панели
|
||||
return results
|
||||
|
||||
|
||||
def format_nodes_status(nodes: list[NodeStatus], *, detailed: bool = False) -> str:
|
||||
if not nodes:
|
||||
return "Серверы не найдены в панели Remnawave."
|
||||
|
||||
online = sum(1 for n in nodes if n.online)
|
||||
lines = [
|
||||
f"<b>Статус серверов</b>",
|
||||
f"Онлайн: <b>{online}</b> / {len(nodes)}",
|
||||
"",
|
||||
]
|
||||
|
||||
for n in nodes:
|
||||
flag = _flag(n.country_code)
|
||||
if n.online:
|
||||
icon = "🟢"
|
||||
ping = f" · {n.tcp_ms:.0f} ms" if n.tcp_ms is not None else ""
|
||||
extra = f" · 👥 {n.users_online}" if n.users_online is not None else ""
|
||||
lines.append(f"{icon} {flag} <b>{n.name}</b> — Онлайн{ping}{extra}")
|
||||
elif n.panel_disabled:
|
||||
icon = "⚫"
|
||||
lines.append(f"{icon} {flag} <b>{n.name}</b> — Выключен")
|
||||
else:
|
||||
icon = "🔴"
|
||||
lines.append(f"{icon} {flag} <b>{n.name}</b> — Оффлайн")
|
||||
if detailed:
|
||||
reasons: list[str] = []
|
||||
if not n.panel_connected:
|
||||
reasons.append("панель не видит ноду")
|
||||
if not n.tcp_ok:
|
||||
reasons.append(f"нет ответа {n.address}:{n.port or 443}")
|
||||
if n.last_status_message:
|
||||
reasons.append(n.last_status_message)
|
||||
if reasons:
|
||||
lines.append(" └ " + "; ".join(reasons))
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _flag(code: str) -> str:
|
||||
code = (code or "XX").upper()
|
||||
if len(code) != 2 or not code.isalpha():
|
||||
return "🌐"
|
||||
return "".join(chr(0x1F1E6 + ord(c) - ord("A")) for c in code)
|
||||
Reference in New Issue
Block a user