This commit is contained in:
eric
2026-05-20 23:50:43 -05:00
parent 882ea9268c
commit e6e4f7a02e
17 changed files with 570 additions and 188 deletions

View File

@@ -42,6 +42,13 @@ def _safe_channel_file_id(channel_id: str) -> str:
return s or "default"
def _display_channel_name(raw_name: str, display_index: int) -> str:
name = (raw_name or "").strip()
if not name or re.fullmatch(r"频道\s*\d+", name):
return f"频道 {display_index}"
return name
def url_config_path_for_channel(config_dir: Path, channel_id: str) -> Path:
return relay_pro_dir(config_dir) / f"url_config.{_safe_channel_file_id(channel_id)}.ini"
@@ -170,11 +177,11 @@ def list_channels_for_pro(config_dir: Path) -> dict[str, Any]:
if not isinstance(item, dict):
continue
cid = str(item.get("id") or "").strip()
name = str(item.get("name") or "").strip() or "未命名"
durl = str(item.get("douyinUrl") or item.get("douyin_url") or "").strip()
key = str(item.get("youtubeKey") or item.get("key") or "").strip()
if not cid:
if not cid or not key:
continue
name = _display_channel_name(str(item.get("name") or ""), len(out) + 1)
kp = _mask_key_preview(key)
out.append(
{
@@ -265,20 +272,25 @@ def update_channel_keys_list(
return False, "同一线路内不能重复同一串流密钥"
ai = max(0, min(int(active_index), len(cleaned) - 1))
active_raw = cleaned[ai]
claimed_norms = set(norms)
moved_from: list[str] = []
for item in arr:
if not isinstance(item, dict):
continue
oid = str(item.get("id") or "").strip()
if oid == channel_id:
continue
for ok in _all_keys_for_channel(item):
okn = _norm_stream_key(ok)
if not okn:
continue
for ck in cleaned:
ckn = _norm_stream_key(ck)
if ckn and ckn == okn:
return False, "禁止多条线路共用同一 YouTube 串流密钥"
old_keys = _all_keys_for_channel(item)
kept = [ok for ok in old_keys if _norm_stream_key(ok) not in claimed_norms]
if len(kept) == len(old_keys):
continue
moved_from.append(str(item.get("name") or oid or "未命名线路"))
if kept:
item["youtubeKeys"] = kept
item["youtubeKey"] = kept[0]
else:
item["youtubeKey"] = ""
item.pop("youtubeKeys", None)
for item in arr:
if not isinstance(item, dict):
continue
@@ -287,6 +299,10 @@ def update_channel_keys_list(
item["youtubeKeys"] = cleaned
item["youtubeKey"] = active_raw
channels_json_path(config_dir).write_text(json.dumps(doc, ensure_ascii=False, indent=2), encoding="utf-8")
if moved_from:
names = "".join(moved_from[:3])
more = f"{len(moved_from)} 条线路" if len(moved_from) > 3 else ""
return True, f"已保存;该 YouTube key 已从其它线路移除:{names}{more}"
return True, "已保存"
return False, "未找到该频道"
@@ -297,10 +313,13 @@ def extract_url_lines_from_ini(text: str) -> list[str]:
t = raw.strip()
if not t or t.startswith("#"):
continue
if "," in t:
t = t.split(",")[-1].strip()
if "douyin" in t.lower() or "v.douyin.com" in t.lower() or "live.douyin.com" in t.lower():
lines.append(t)
m = re.search(r"https?://(?:live|v|www)\.douyin\.com/[^\s,#;]+", t, re.I)
if m:
lines.append(m.group(0).replace("http://", "https://", 1))
continue
m = re.search(r"(?:live|v|www)\.douyin\.com/[^\s,#;]+", t, re.I)
if m:
lines.append("https://" + m.group(0))
return lines
@@ -421,6 +440,60 @@ def sync_channel_to_legacy_files(config_dir: Path, channel_id: str) -> tuple[boo
return True, "已同步到 youtube.ini 与 URL_config.ini"
def first_relay_channel_id(config_dir: Path) -> str:
doc, _ = load_channels_document(config_dir)
if doc is None:
return ""
arr = doc.get("channels")
if not isinstance(arr, list):
return ""
for item in arr:
if not isinstance(item, dict):
continue
cid = str(item.get("id") or "").strip()
if cid:
return cid
return ""
def sync_legacy_files_to_first_channel(config_dir: Path) -> tuple[bool, str, str | None]:
"""将单频道 youtube.ini + URL_config.ini 同步到 Pro 第一线路。"""
cid = first_relay_channel_id(config_dir)
if not cid:
return False, "缺少 Pro 第一线路", None
ini = Path(config_dir) / "youtube.ini"
key = ""
if ini.exists():
try:
parsed = parse_youtube_ini(ini.read_text(encoding="utf-8-sig"))
keys = parsed.get("keys") if isinstance(parsed, dict) else []
ai = parsed.get("activeIndex", 0) if isinstance(parsed, dict) else 0
if isinstance(keys, list) and keys:
idx = max(0, min(int(ai or 0), len(keys) - 1))
key = str(keys[idx] or "").strip()
except Exception:
key = ""
if not key:
return False, "单频道未配置 YouTube 串流密钥", cid
ok, msg = update_channel_keys_list(config_dir, cid, [key], 0)
if not ok:
return False, msg, cid
url_ini = Path(config_dir) / "URL_config.ini"
url_body = ""
if url_ini.exists():
try:
url_body = url_ini.read_text(encoding="utf-8-sig")
except Exception as e:
return False, f"读取单频道地址失败: {e}", cid
ok, msg = write_url_config_for_channel(config_dir, cid, url_body)
if not ok:
return False, msg, cid
return True, "已将单频道配置同步到 Pro 第一线路", cid
def parse_relay_meta_lines(log_text: str) -> list[dict[str, Any]]:
out: list[dict[str, Any]] = []
for line in log_text.splitlines():
@@ -531,15 +604,19 @@ def _match_channel_by_key_suffix(config_dir: Path, suffix: str) -> tuple[str, st
if not doc or not isinstance(doc.get("channels"), list):
return "", ""
su = suffix.strip().lower()
display_index = 0
for item in doc["channels"]:
if not isinstance(item, dict):
continue
if not _all_keys_for_channel(item):
continue
display_index += 1
for k in _all_keys_for_channel(item):
tail = _key_tail_for_match(k)
if not tail:
continue
if su == tail or (len(su) >= 4 and tail.endswith(su)):
return str(item.get("id") or ""), str(item.get("name") or "")
return str(item.get("id") or ""), _display_channel_name(str(item.get("name") or ""), display_index)
return "", ""