Files
gitlab-instance-0a899031_d2…/web2_youtube_preflight.py
2026-07-10 05:15:29 -05:00

151 lines
4.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""YouTube 开播前网络预检(参照 d2ypp2 network_preflightWindows/Electron 适配)。"""
from __future__ import annotations
import os
from typing import Any, Optional
import httpx
from web2_network import (
CONNECT_TIMEOUT,
DOUYIN_CHECK_URL,
GOOGLE_CHECK_URL,
READ_TIMEOUT,
_latency_get,
)
from web2_proxy_config import read_proxy_config
YOUTUBE_CHECK_URL = "https://www.youtube.com/generate_204"
_PROBE_TARGETS: tuple[dict[str, str], ...] = (
{"id": "google", "label": "Google", "url": GOOGLE_CHECK_URL, "role": "global"},
{"id": "youtube", "label": "YouTube", "url": YOUTUBE_CHECK_URL, "role": "global"},
{"id": "douyin", "label": "抖音源站", "url": DOUYIN_CHECK_URL, "role": "source"},
)
def _preflight_disabled() -> bool:
raw = os.environ.get("YOUTUBE_NETWORK_PREFLIGHT", "1").strip().lower()
return raw in {"0", "false", "no", "off"}
def _global_ok(probes: list[dict[str, Any]]) -> bool:
return any(p.get("ok") for p in probes if p.get("role") == "global")
def _douyin_ok(probes: list[dict[str, Any]]) -> bool:
return any(p.get("ok") for p in probes if p.get("id") == "douyin")
async def _probe_all(client: httpx.AsyncClient) -> list[dict[str, Any]]:
rows: list[dict[str, Any]] = []
for target in _PROBE_TARGETS:
ok, ms, err = await _latency_get(client, target["url"])
rows.append(
{
**target,
"ok": ok,
"latency_ms": round(ms, 1) if ms >= 0 else None,
"error": err,
}
)
return rows
async def run_youtube_preflight() -> dict[str, Any]:
"""返回完整预检报告,供 /youtube/preflight 与开播拦截共用。"""
if _preflight_disabled():
return {
"ok": True,
"mode": "disabled",
"message": "网络预检已关闭YOUTUBE_NETWORK_PREFLIGHT=0",
"checks": {},
"direct": [],
"proxy": [],
"proxy_config": read_proxy_config(),
}
timeout = httpx.Timeout(READ_TIMEOUT, connect=CONNECT_TIMEOUT)
proxy_cfg = read_proxy_config()
async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client:
direct = await _probe_all(client)
proxy_rows: list[dict[str, Any]] = []
proxy_http = (proxy_cfg.get("http") or "").strip()
if proxy_cfg.get("enabled") and proxy_http:
try:
async with httpx.AsyncClient(
timeout=timeout,
follow_redirects=True,
proxy=proxy_http,
) as client:
proxy_rows = await _probe_all(client)
except Exception as e:
proxy_rows = [
{**t, "ok": False, "latency_ms": None, "error": str(e)}
for t in _PROBE_TARGETS
]
global_direct = _global_ok(direct)
global_proxy = _global_ok(proxy_rows)
douyin_direct = _douyin_ok(direct)
ok = bool(global_direct or global_proxy)
mode = "direct" if global_direct else ("proxy" if global_proxy else "failed")
checks = {
"google": next((p for p in direct if p.get("id") == "google"), {}).get("ok", False),
"youtube": next((p for p in direct if p.get("id") == "youtube"), {}).get("ok", False),
"douyin": douyin_direct,
"global_direct": global_direct,
"global_proxy": global_proxy,
"proxy_configured": bool(proxy_cfg.get("enabled") and proxy_http),
}
if ok:
if global_direct:
message = "直连可达 Google/YouTube可尝试开播"
else:
message = f"直连失败,但录制代理 {proxy_http} 可达 Google/YouTube"
if not douyin_direct:
message += ";抖音源站当前不可达,可能无法拉流"
else:
parts = []
if not global_direct:
parts.append("Google/YouTube 直连不可达")
if proxy_cfg.get("enabled"):
if not global_proxy:
parts.append("录制代理也未打通 Google/YouTube")
else:
parts.append("未启用录制代理")
if not douyin_direct:
parts.append("抖音源站不可达")
message = "".join(parts)
blocking_message: Optional[str] = None
if not ok:
blocking_message = (
f"{message}。请先在「网络」页检测并配置代理,或在下方「录制代理」中填写 Clash 混合端口后再开播。"
)
return {
"ok": ok,
"mode": mode,
"message": message,
"blocking_message": blocking_message,
"checks": checks,
"direct": direct,
"proxy": proxy_rows,
"proxy_config": proxy_cfg,
"douyin_direct_ok": douyin_direct,
}
async def youtube_start_preflight_message() -> str | None:
result = await run_youtube_preflight()
if result.get("ok"):
return None
return str(result.get("blocking_message") or result.get("message") or "网络预检未通过")