# -*- coding: utf-8 -*- r'''按**行号**修 admin.py 里那段坏掉的 JS。 == 为什么不用字符串匹配 == 前两次都失败了:坏掉的内容里带着**真的换行**, 在 Python 源码里怎么写字面量都对不齐(缩进、空行的数量差一个就匹配不上)。 按行号定位反而稳:先找到 `data-update` 那个 forEach 的锚点行, 然后把它后面那几行跨行字符串整段换掉。 == 坏掉的是什么 == if (!confirm('让「' + name + '」现在更新到最新版?\n\n' + '它会自己下载……')) Python 三引号里 `\n` 变成真换行 -> JS 字符串字面量跨行 -> 语法非法 -> **整个 script 块解析失败 -> 管理页所有按钮全死**。 ''' from __future__ import annotations import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent ADMIN = ROOT / "relay" / "admin.py" NEW_LINES = [ " const name = btn.dataset.name || '这台电脑';", " // ⚠ 这里**不能用反斜杠 n** 拼多行文案。", " // ADMIN_JS 是 Python 三引号字符串,源码里写反斜杠 n 会变成", " // **真的换行**插进 JS,于是字符串字面量跨行 —— JS 语法非法,", " // 整个 script 块解析失败,管理页所有按钮全死。", " // (回归里的「管理页 JS 合法性」就是专门抓这个的。)", " //", " // 用 String.fromCharCode(10) 完全不依赖转义层,", " // 以后谁改这段都不会再踩。", " const NL = String.fromCharCode(10);", " const msg = ['让「' + name + '」现在更新到最新版?', '',", " '它会自己下载、静默安装并重启,全程不弹窗口,',", " '中转地址、配对码和各项设置都不变。'].join(NL);", " if (!confirm(msg)) {", " return;", " }", ] def main() -> int: print("=" * 58) print(" 按行号修 admin.py 的 JS") print("=" * 58) print() raw = ADMIN.read_bytes() bom = raw.startswith(b"\xef\xbb\xbf") text = raw.decode("utf-8-sig") if "String.fromCharCode(10)" in text: print(" 已经修好了") return 0 lines = text.split("\n") # 找 forEach 的锚点 start = None for i, ln in enumerate(lines): if "querySelectorAll('button[data-update]')" in ln: start = i break if start is None: print(" ✗ 找不到 data-update 的 forEach") return 1 print(" 锚点在第 %d 行" % (start + 1)) # 锚点后第一行应该是 btn.onclick = async () => { j = start + 1 while j < len(lines) and "btn.onclick" not in lines[j]: j += 1 if j >= len(lines): print(" ✗ 找不到 btn.onclick") return 1 # 下一个非空行是 const name = ... k = j + 1 while k < len(lines) and not lines[k].strip(): k += 1 if "const name" not in lines[k]: print(" ✗ const name 那行不在预期位置(第 %d 行):%r" % (k + 1, lines[k][:70])) return 1 # k 之后到底几个坏行?找到 "if (!confirm(" 开始,到那个跨行字符串结束 # (下一个以 ' return;' 结尾的块之前) end = k + 1 found_confirm = False while end < len(lines): s = lines[end].strip() if s.startswith("if (!confirm("): found_confirm = True if found_confirm and s.startswith("return;"): break end += 1 if not found_confirm: print(" ✗ 找不到 if (!confirm(") return 1 print(" 要替换第 %d 行 ~ 第 %d 行(%d 行)" % (k + 1, end, end - k + 1)) for n in range(k, min(end + 1, k + 8)): print(" 坏行 %d: %r" % (n + 1, lines[n][:64])) lines[k:end] = NEW_LINES ADMIN.write_bytes((b"\xef\xbb\xbf" if bom else b"") + "\n".join(lines).encode("utf-8")) print(" ✓ 替换完成") sys.path.insert(0, str(ROOT / "relay")) import admin # noqa: E402 js = admin.ADMIN_JS bad = [] for n, ln in enumerate(js.splitlines(), 1): s = ln.strip() if s.startswith("//"): continue if s.count("'") % 2: bad.append((n, ln[:70])) print(" 复核:单引号落单的行 = %d 个" % len(bad)) for n, ln in bad[:5]: print(" %4d %s" % (n, ln)) return 0 if not bad else 1 if __name__ == "__main__": sys.exit(main())