重构无人直播助手:内嵌 youLIVE、摄像头直推、推流诊断与 UI 焕新
将 README 改写为产品文档;移除微信/EasyTier/工作台等遗留模块;新增 youLIVE 目标端监控、摄像头推流、质量守卫与转播历史能力。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,6 +11,7 @@ from pathlib import Path
|
||||
from typing import Any, Callable, Awaitable
|
||||
|
||||
from web2_youtube_ini import parse_youtube_ini, serialize_youtube_ini
|
||||
from web2_config_governance import backup_config_file
|
||||
|
||||
# 与 web2_client_overview 一致的去重键
|
||||
def _norm_douyin_url(url: str) -> str:
|
||||
@@ -182,9 +183,11 @@ def list_channels_for_pro(config_dir: Path) -> dict[str, Any]:
|
||||
"name": name,
|
||||
"douyinUrl": durl,
|
||||
"keyPreview": kp,
|
||||
"youtubeWatchUrl": get_youtube_watch_url(config_dir, cid),
|
||||
"configured": bool(kp or durl),
|
||||
}
|
||||
)
|
||||
return {"ok": True, "channels": out}
|
||||
return {"ok": True, "channels": out, "singleYoutubeWatchUrl": get_single_youtube_watch_url(config_dir)}
|
||||
|
||||
|
||||
def _mask_key_preview(key: str) -> str:
|
||||
@@ -219,6 +222,79 @@ def _all_keys_for_channel(item: dict) -> list[str]:
|
||||
return [k] if k else []
|
||||
|
||||
|
||||
def get_single_youtube_watch_url(config_dir: Path) -> str:
|
||||
doc, _ = load_channels_document(config_dir)
|
||||
if doc is None:
|
||||
return ""
|
||||
return str(doc.get("singleYoutubeWatchUrl") or doc.get("single_youtube_watch_url") or "").strip()
|
||||
|
||||
|
||||
def get_youtube_watch_url(config_dir: Path, channel_id: str) -> str:
|
||||
cid = (channel_id or "").strip()
|
||||
if not cid or cid == "single":
|
||||
return get_single_youtube_watch_url(config_dir)
|
||||
ch = get_channel_by_id(config_dir, cid)
|
||||
if not ch:
|
||||
return ""
|
||||
return str(ch.get("youtubeWatchUrl") or ch.get("youtube_watch_url") or "").strip()
|
||||
|
||||
|
||||
def set_youtube_watch_url(config_dir: Path, channel_id: str, url: str) -> tuple[bool, str]:
|
||||
url = (url or "").strip()
|
||||
cid = (channel_id or "").strip()
|
||||
if not cid:
|
||||
return False, "缺少 channelId"
|
||||
config_dir = Path(config_dir)
|
||||
if cid == "single":
|
||||
doc, errs = load_channels_document(config_dir)
|
||||
if doc is None:
|
||||
ensure_default_relay_config(config_dir)
|
||||
doc, _ = load_channels_document(config_dir)
|
||||
if doc is None:
|
||||
return False, errs[0] if errs else "无法读取配置"
|
||||
doc["singleYoutubeWatchUrl"] = url
|
||||
channels_json_path(config_dir).write_text(
|
||||
json.dumps(doc, ensure_ascii=False, indent=2), encoding="utf-8"
|
||||
)
|
||||
return True, "已保存"
|
||||
doc, errs = load_channels_document(config_dir)
|
||||
if doc is None:
|
||||
return False, errs[0] if errs else "无法读取配置"
|
||||
arr = doc.get("channels")
|
||||
if not isinstance(arr, list):
|
||||
return False, "channels 无效"
|
||||
for item in arr:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
if str(item.get("id") or "").strip() != cid:
|
||||
continue
|
||||
item["youtubeWatchUrl"] = url
|
||||
channels_json_path(config_dir).write_text(
|
||||
json.dumps(doc, ensure_ascii=False, indent=2), encoding="utf-8"
|
||||
)
|
||||
return True, "已保存"
|
||||
return False, "未找到该频道"
|
||||
|
||||
|
||||
def watch_url_for_relay_process(config_dir: Path, process: str) -> str:
|
||||
prefix = "youtube2__"
|
||||
if process.startswith(prefix):
|
||||
return get_youtube_watch_url(config_dir, process[len(prefix) :])
|
||||
return get_single_youtube_watch_url(config_dir)
|
||||
|
||||
|
||||
def channel_configured_for_monitor(config_dir: Path, channel_id: str) -> bool:
|
||||
ch = get_channel_by_id(config_dir, channel_id)
|
||||
if not ch:
|
||||
return False
|
||||
if str(ch.get("youtubeKey") or ch.get("key") or "").strip():
|
||||
return True
|
||||
if str(ch.get("douyinUrl") or ch.get("douyin_url") or "").strip():
|
||||
return True
|
||||
url_text = read_url_config_for_channel(config_dir, channel_id)
|
||||
return bool(extract_url_lines_from_ini(url_text))
|
||||
|
||||
|
||||
def get_channel_detail(config_dir: Path, channel_id: str) -> dict[str, Any] | None:
|
||||
ch = get_channel_by_id(config_dir, channel_id)
|
||||
if not ch:
|
||||
@@ -240,6 +316,7 @@ def get_channel_detail(config_dir: Path, channel_id: str) -> dict[str, Any] | No
|
||||
"youtubeKey": active,
|
||||
"youtubeKeys": keys,
|
||||
"youtubeActiveIndex": ai,
|
||||
"youtubeWatchUrl": get_youtube_watch_url(config_dir, str(ch.get("id") or "").strip()),
|
||||
}
|
||||
|
||||
|
||||
@@ -376,6 +453,8 @@ def write_url_config_for_channel(config_dir: Path, channel_id: str, content: str
|
||||
return False, errs[0]
|
||||
p = url_config_path_for_channel(config_dir, channel_id)
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
rel = f"relay_pro/{p.name}"
|
||||
backup_config_file(config_dir, rel, reason="relay_pro_url")
|
||||
p.write_text(content, encoding="utf-8")
|
||||
return True, "保存成功"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user