This commit is contained in:
root
2026-05-17 12:09:14 +00:00
parent a10ffe332d
commit ffdc887b8a
30 changed files with 728 additions and 111 deletions

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
import asyncio
import configparser
import json
import os
import re
@@ -304,6 +305,56 @@ def _sync_youtube_pro_runtime_files(process: str) -> list[str]:
return errors
def _normalize_textarea_content(text: str) -> str:
return (text or "").replace("\r\n", "\n").replace("\r", "\n").lstrip("\ufeff")
def _extract_stream_key_from_youtube_ini(text: str) -> str:
parser = configparser.ConfigParser()
try:
parser.read_string(_normalize_textarea_content(text) or "[youtube]\n")
except (configparser.Error, TypeError, ValueError):
return ""
if not parser.has_section("youtube"):
return ""
for key_name in ("key", "stream_key"):
value = str(parser.get("youtube", key_name, fallback="") or "").strip()
if value and not value.startswith("#"):
return value
return ""
def _persist_youtube_pro_channel_edits(
process: str,
*,
url_content: str | None = None,
youtube_ini_content: str | None = None,
) -> bool:
channels = get_youtube_pro_channels()
if not channels:
return False
target = (process or "").strip()
if not target:
return False
updated = False
next_channels: list[dict[str, Any]] = []
for channel in channels:
row = dict(channel) if isinstance(channel, dict) else {}
if channel_pm2_name(row) != target:
next_channels.append(row)
continue
if url_content is not None:
row["urlLines"] = _normalize_textarea_content(url_content)
updated = True
if youtube_ini_content is not None:
row["streamKey"] = _extract_stream_key_from_youtube_ini(youtube_ini_content)
updated = True
next_channels.append(row)
if updated:
set_youtube_pro_channels(next_channels)
return updated
def _validate_youtube_runtime_files(process: str) -> list[str]:
return validate_youtube_runtime_files(BASE_DIR, CONFIG_DIR, process, get_youtube_pro_channels())
@@ -501,6 +552,12 @@ app.add_middleware(
allow_headers=["*"],
)
_NO_STORE_HEADERS = {
"Cache-Control": "no-store, max-age=0, must-revalidate",
"Pragma": "no-cache",
"Expires": "0",
}
class ProcessNameBody(BaseModel):
process: str = Field(..., min_length=1, max_length=160)
@@ -1050,22 +1107,21 @@ async def get_config(process: str = Query(..., description="进程名")):
{"error": "仅 YouTube / 抖音→YouTube 类脚本支持 youtube.ini 编辑"}, status_code=400
)
sync_errors = _sync_youtube_pro_runtime_files(process)
if sync_errors:
return JSONResponse({"error": "; ".join(sync_errors)}, status_code=400)
config_file = CONFIG_DIR / rel
if not config_file.is_file():
sync_errors = _sync_youtube_pro_runtime_files(process)
if sync_errors:
return JSONResponse({"error": "; ".join(sync_errors)}, status_code=400)
legacy = CONFIG_DIR / "youtube.ini"
if legacy.is_file():
try:
return {"content": legacy.read_text(encoding="utf-8")}
return JSONResponse({"content": legacy.read_text(encoding="utf-8")}, headers=_NO_STORE_HEADERS)
except OSError as e:
return JSONResponse({"error": f"读取失败: {str(e)}"}, status_code=500)
return {"content": f"# {rel} 尚未创建,保存后将写入磁盘\n"}
return JSONResponse({"content": f"# {rel} 尚未创建,保存后将写入磁盘\n"}, headers=_NO_STORE_HEADERS)
try:
content = config_file.read_text(encoding="utf-8")
return {"content": content}
return JSONResponse({"content": content}, headers=_NO_STORE_HEADERS)
except OSError as e:
return JSONResponse({"error": f"读取失败: {str(e)}"}, status_code=500)
@@ -1086,7 +1142,8 @@ async def save_config(request: Request, process: str = Query(..., description="
try:
governed_save_managed_file("app-config", rel, content)
return {"message": "配置保存成功"}
_persist_youtube_pro_channel_edits(process, youtube_ini_content=content)
return JSONResponse({"message": "配置保存成功"}, headers=_NO_STORE_HEADERS)
except OSError as e:
return JSONResponse({"error": f"保存失败: {str(e)}"}, status_code=500)
@@ -1100,23 +1157,22 @@ async def get_url_config(process: str = Query(..., description="进程名")):
status_code=400,
)
sync_errors = _sync_youtube_pro_runtime_files(process)
if sync_errors:
return JSONResponse({"error": "; ".join(sync_errors)}, status_code=400)
rel = managed_url_config_relpath(process)
config_file = CONFIG_DIR / rel
if not config_file.is_file():
sync_errors = _sync_youtube_pro_runtime_files(process)
if sync_errors:
return JSONResponse({"error": "; ".join(sync_errors)}, status_code=400)
legacy = CONFIG_DIR / "URL_config.ini"
if legacy.is_file():
try:
return {"content": legacy.read_text(encoding="utf-8")}
return JSONResponse({"content": legacy.read_text(encoding="utf-8")}, headers=_NO_STORE_HEADERS)
except OSError as e:
return JSONResponse({"error": f"读取失败: {str(e)}"}, status_code=500)
return {"content": f"# {rel} 尚未创建,保存后将写入磁盘\n"}
return JSONResponse({"content": f"# {rel} 尚未创建,保存后将写入磁盘\n"}, headers=_NO_STORE_HEADERS)
try:
content = config_file.read_text(encoding="utf-8")
return {"content": content}
return JSONResponse({"content": content}, headers=_NO_STORE_HEADERS)
except OSError as e:
return JSONResponse({"error": f"读取失败: {str(e)}"}, status_code=500)
@@ -1140,7 +1196,8 @@ async def save_url_config(request: Request, process: str = Query(..., descriptio
try:
rel = managed_url_config_relpath(process)
governed_save_managed_file("app-config", rel, content)
return {"message": "配置保存成功"}
_persist_youtube_pro_channel_edits(process, url_content=content)
return JSONResponse({"message": "配置保存成功"}, headers=_NO_STORE_HEADERS)
except OSError as e:
return JSONResponse({"error": f"保存失败: {str(e)}"}, status_code=500)