"""自动准备官方 scrcpy-server(与 vendor/scrcpy-server.version 版本一致),供 Web 真流 adb push 使用。""" from __future__ import annotations import os import sys import time import urllib.error import urllib.request from pathlib import Path # 官方发行资产名:scrcpy-server-v{version}(无扩展名,实为 JAR) _GITHUB_DL = "https://github.com/Genymobile/scrcpy/releases/download/v{tag}/scrcpy-server-v{tag}" _MIN_BYTES = 8_000 _PER_URL_ATTEMPTS = 3 _PER_URL_DELAY_S = 2.0 def _read_version(repo_root: Path) -> str: env = (os.environ.get("SCRCPY_SERVER_VERSION") or "").strip().lstrip("v") if env: return env vf = repo_root / "vendor" / "scrcpy-server.version" if vf.is_file(): line = vf.read_text(encoding="utf-8").splitlines()[0].strip().lstrip("v") if line: return line return "3.3.3" def _expand_download_url(template: str, tag: str) -> str: return template.replace("{tag}", tag).replace("{version}", tag) def _download_urls_to_try(tag: str) -> list[str]: """顺序:SCRCPY_SERVER_DOWNLOAD_URL → SCRCPY_SERVER_DOWNLOAD_URLS →(默认可选)GitHub。""" urls: list[str] = [] single = (os.environ.get("SCRCPY_SERVER_DOWNLOAD_URL") or "").strip() if single: urls.append(_expand_download_url(single, tag)) raw = (os.environ.get("SCRCPY_SERVER_DOWNLOAD_URLS") or "").strip() if raw: for part in raw.split(","): u = part.strip() if not u: continue uu = _expand_download_url(u, tag) if uu not in urls: urls.append(uu) no_github = os.environ.get("SCRCPY_SERVER_DOWNLOAD_NO_GITHUB", "").strip().lower() in ( "1", "true", "yes", "on", ) gh = _GITHUB_DL.format(tag=tag) if not no_github and gh not in urls: urls.append(gh) seen: set[str] = set() out: list[str] = [] for u in urls: if u not in seen: seen.add(u) out.append(u) return out def _fetch_url(url: str) -> tuple[bool, bytes | None, str]: req = urllib.request.Request( url, headers={"User-Agent": "douyinyoutube-live-hub/scrcpy-bootstrap"}, method="GET", ) last_err = "" for attempt in range(1, _PER_URL_ATTEMPTS + 1): try: with urllib.request.urlopen(req, timeout=120) as resp: data = resp.read() return True, data, "" except (urllib.error.HTTPError, urllib.error.URLError, TimeoutError, OSError) as e: last_err = str(e) if attempt < _PER_URL_ATTEMPTS: time.sleep(_PER_URL_DELAY_S) return False, None, last_err def ensure_scrcpy_jar(repo_root: Path) -> tuple[bool, str]: """ 若 vendor/scrcpy-server.jar 不存在或过小,则按 URL 列表下载。 环境变量: SKIP_SCRCPY_SERVER_FETCH=1 — 跳过 SCRCPY_SERVER_DOWNLOAD_URL — 优先下载地址,可用 {tag}/{version} SCRCPY_SERVER_DOWNLOAD_URLS — 逗号分隔多条,先于 GitHub SCRCPY_SERVER_DOWNLOAD_NO_GITHUB=1 — 仅用上面自定义 URL,不回退 GitHub """ if os.environ.get("SKIP_SCRCPY_SERVER_FETCH", "").strip().lower() in ("1", "true", "yes", "on"): return True, "skip fetch (SKIP_SCRCPY_SERVER_FETCH)" vendor_dir = repo_root / "vendor" dest = vendor_dir / "scrcpy-server.jar" env_jar = (os.environ.get("SCRCPY_SERVER_JAR") or "").strip() if env_jar: p = Path(env_jar).expanduser() if p.is_file() and p.stat().st_size >= _MIN_BYTES: return True, "using SCRCPY_SERVER_JAR" if dest.is_file() and dest.stat().st_size >= _MIN_BYTES: return True, "vendor/scrcpy-server.jar ok" tag = _read_version(repo_root) urls = _download_urls_to_try(tag) if not urls: return False, "未配置任何下载源(SCRCPY_SERVER_DOWNLOAD_NO_GITHUB=1 且未设置 SCRCPY_SERVER_DOWNLOAD_URL(S))" vendor_dir.mkdir(parents=True, exist_ok=True) tmp = dest.with_suffix(".jar.part") last_fail = "" for url in urls: ok, data, err = _fetch_url(url) if not ok or data is None: last_fail = f"{url}: {err}" continue if len(data) < _MIN_BYTES: last_fail = f"{url}: too small ({len(data)} bytes)" continue if not data.startswith(b"PK"): last_fail = f"{url}: not a JAR (missing PK header)" continue try: tmp.write_bytes(data) os.replace(tmp, dest) except OSError as e: return False, f"write vendor/scrcpy-server.jar failed: {e}" return True, f"fetched scrcpy-server v{tag} -> vendor/scrcpy-server.jar (via {url})" return False, f"download failed after {len(urls)} source(s): {last_fail}" def main() -> int: root = Path(__file__).resolve().parent.parent ok, msg = ensure_scrcpy_jar(root) print(msg, file=sys.stderr if not ok else sys.stdout) return 0 if ok else 1 if __name__ == "__main__": raise SystemExit(main())