103 lines
3.3 KiB
Python
103 lines
3.3 KiB
Python
"""
|
|
Минимальная проверка новой версии через GitHub Releases API (без сторонних зависимостей).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from itertools import zip_longest
|
|
from typing import Any, Dict, Optional
|
|
from urllib.error import HTTPError, URLError
|
|
from urllib.request import Request, urlopen
|
|
|
|
REPO = "Flowseal/tg-ws-proxy"
|
|
RELEASES_LATEST_API = f"https://api.github.com/repos/{REPO}/releases/latest"
|
|
RELEASES_PAGE_URL = f"https://github.com/{REPO}/releases/latest"
|
|
|
|
_state: Dict[str, Any] = {
|
|
"checked": False,
|
|
"has_update": False,
|
|
"ahead_of_release": False,
|
|
"latest": None,
|
|
"html_url": None,
|
|
"error": None,
|
|
}
|
|
|
|
|
|
def _parse_version_tuple(s: str) -> tuple:
|
|
s = (s or "").strip().lstrip("vV")
|
|
if not s:
|
|
return (0,)
|
|
parts = []
|
|
for seg in s.split("."):
|
|
digits = "".join(c for c in seg if c.isdigit())
|
|
if digits:
|
|
try:
|
|
parts.append(int(digits))
|
|
except ValueError:
|
|
parts.append(0)
|
|
else:
|
|
parts.append(0)
|
|
return tuple(parts) if parts else (0,)
|
|
|
|
|
|
def _version_gt(a: str, b: str) -> bool:
|
|
"""True, если версия a новее b (простое сравнение по сегментам)."""
|
|
ta = _parse_version_tuple(a)
|
|
tb = _parse_version_tuple(b)
|
|
for x, y in zip_longest(ta, tb, fillvalue=0):
|
|
if x > y:
|
|
return True
|
|
if x < y:
|
|
return False
|
|
return False
|
|
|
|
|
|
def fetch_latest_release(timeout: float = 12.0) -> Optional[dict]:
|
|
req = Request(
|
|
RELEASES_LATEST_API,
|
|
headers={
|
|
"Accept": "application/vnd.github+json",
|
|
"User-Agent": "tg-ws-proxy-update-check",
|
|
},
|
|
method="GET",
|
|
)
|
|
with urlopen(req, timeout=timeout) as resp:
|
|
raw = resp.read().decode("utf-8", errors="replace")
|
|
return json.loads(raw)
|
|
|
|
|
|
def run_check(current_version: str) -> None:
|
|
"""Запрашивает последний релиз и обновляет внутреннее состояние."""
|
|
global _state
|
|
_state["checked"] = True
|
|
_state["error"] = None
|
|
try:
|
|
data = fetch_latest_release()
|
|
tag = (data.get("tag_name") or "").strip()
|
|
html_url = (data.get("html_url") or "").strip() or RELEASES_PAGE_URL
|
|
if not tag:
|
|
_state["has_update"] = False
|
|
_state["ahead_of_release"] = False
|
|
_state["latest"] = None
|
|
_state["html_url"] = html_url
|
|
return
|
|
latest_clean = tag.lstrip("vV")
|
|
cur = (current_version or "").strip().lstrip("vV")
|
|
_state["latest"] = latest_clean
|
|
_state["html_url"] = html_url
|
|
_state["has_update"] = _version_gt(latest_clean, cur)
|
|
_state["ahead_of_release"] = bool(latest_clean) and _version_gt(
|
|
cur, latest_clean
|
|
)
|
|
except (HTTPError, URLError, OSError, TimeoutError, ValueError, json.JSONDecodeError) as e:
|
|
_state["error"] = str(e)
|
|
_state["has_update"] = False
|
|
_state["ahead_of_release"] = False
|
|
_state["latest"] = None
|
|
_state["html_url"] = RELEASES_PAGE_URL
|
|
|
|
|
|
def get_status() -> Dict[str, Any]:
|
|
"""Снимок состояния после run_check (для подписей в настройках)."""
|
|
return dict(_state)
|