From f05bffd5871e007c034f6eb3c2983b91c6750214 Mon Sep 17 00:00:00 2001 From: ageorge156 Date: Sun, 30 Aug 2026 14:31:06 +0800 Subject: [PATCH] feat: support Clash Verge global script on Windows --- README.md | 6 ++-- scripts/gen_clash_verge_script.py | 35 +++++++++++++++++--- tests/test_module_spec.py | 54 ++++++++++++++++++++++++++++++- 3 files changed, 87 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0dfda7f..7a844b6 100644 --- a/README.md +++ b/README.md @@ -27,15 +27,17 @@ vpn-rules/ ## 消费端接入 -### Clash Verge(macOS) +### Clash Verge(Windows / macOS) ```bash -# 生成并写入 Clash Verge 数据目录的 Script.js +# 按当前平台生成并写入 Clash Verge 数据目录的全局 Script.js python3 scripts/gen_clash_verge_script.py # 然后在 Clash Verge 界面点「重启内核」生效 ``` 对当前及以后新增/切换的所有订阅生效;订阅更新不会覆盖它。 +Windows 默认写入 `%APPDATA%\io.github.clash-verge-rev.clash-verge-rev\profiles\Script.js`; +macOS 默认写入 `~/Library/Application Support/io.github.clash-verge-rev.clash-verge-rev/profiles/Script.js`。 ### 路由器 mihomo(Magic Catling2 / OpenWrt) diff --git a/scripts/gen_clash_verge_script.py b/scripts/gen_clash_verge_script.py index 122853f..99f28fa 100644 --- a/scripts/gen_clash_verge_script.py +++ b/scripts/gen_clash_verge_script.py @@ -4,14 +4,39 @@ 用法: python3 scripts/gen_clash_verge_script.py [--out 目标路径] -默认输出到 Clash Verge 数据目录 profiles/Script.js(全局直连规则的唯一正确机制, -见 clash-verge-config 记忆)。生成后需在 Clash Verge 界面点「重启内核」生效。 +默认按 Windows/macOS 平台输出到 Clash Verge 数据目录 profiles/Script.js(全局直连 +规则的唯一正确机制)。生成后需在 Clash Verge 界面点「重启内核」生效。 """ import argparse +import os from pathlib import Path +import sys REPO_ROOT = Path(__file__).resolve().parent.parent -DEFAULT_OUT = Path.home() / "Library/Application Support/io.github.clash-verge-rev.clash-verge-rev/profiles/Script.js" +APP_ID = "io.github.clash-verge-rev.clash-verge-rev" + + +def default_output_path( + platform_name: str = sys.platform, + *, + home: Path | None = None, + appdata: Path | None = None, +) -> Path: + """返回当前平台的 Clash Verge 全局 Script.js 路径。""" + home = home or Path.home() + if platform_name == "win32": + if appdata is None: + roaming = os.environ.get("APPDATA") + if not roaming: + raise SystemExit("[error] Windows 环境缺少 APPDATA,无法定位 Clash Verge 数据目录") + appdata = Path(roaming) + return appdata / APP_ID / "profiles" / "Script.js" + if platform_name == "darwin": + return home / "Library/Application Support" / APP_ID / "profiles" / "Script.js" + return home / ".local/share" / APP_ID / "profiles" / "Script.js" + + +DEFAULT_OUT = default_output_path() def load_payload(path: Path) -> list[str]: @@ -22,7 +47,7 @@ def load_payload(path: Path) -> list[str]: if line.startswith("- "): payload.append(line[2:].strip()) if not payload: - raise SystemExit(f"❌ direct.yaml 里没有 payload 条目: {path}") + raise SystemExit(f"[error] direct.yaml 里没有 payload 条目: {path}") return payload @@ -66,7 +91,7 @@ def main() -> None: out = Path(args.out) out.parent.mkdir(parents=True, exist_ok=True) out.write_text(script, encoding="utf-8") - print(f"✅ 已生成 {out}({len(payload)} 条规则)") + print(f"[ok] 已生成 {out}({len(payload)} 条规则)") print(" 请在 Clash Verge 界面点「重启内核」生效。") diff --git a/tests/test_module_spec.py b/tests/test_module_spec.py index d82cec2..2c5fe3b 100644 --- a/tests/test_module_spec.py +++ b/tests/test_module_spec.py @@ -1,7 +1,10 @@ from __future__ import annotations import json +import os +import subprocess import sys +import tempfile import unittest from pathlib import Path, PurePosixPath @@ -10,10 +13,59 @@ ROOT = Path(__file__).resolve().parents[1] SPEC_PATH = ROOT / "MODULE_SPEC.yaml" sys.path.insert(0, str(ROOT / "scripts")) -from gen_clash_verge_script import load_payload, to_rule # noqa: E402 +from gen_clash_verge_script import ( # noqa: E402 + default_output_path, + load_payload, + to_rule, +) class ModuleSpecTests(unittest.TestCase): + def test_clash_verge_default_output_path_is_platform_aware(self) -> None: + home = Path("C:/Users/example") + appdata = Path("C:/Users/example/AppData/Roaming") + + self.assertEqual( + default_output_path("win32", home=home, appdata=appdata), + appdata + / "io.github.clash-verge-rev.clash-verge-rev" + / "profiles" + / "Script.js", + ) + self.assertEqual( + default_output_path("darwin", home=home), + home + / "Library/Application Support" + / "io.github.clash-verge-rev.clash-verge-rev" + / "profiles" + / "Script.js", + ) + + def test_clash_verge_generator_supports_windows_gbk_console(self) -> None: + with tempfile.TemporaryDirectory() as temp_dir: + output = Path(temp_dir) / "Script.js" + env = os.environ.copy() + env["PYTHONIOENCODING"] = "gbk" + result = subprocess.run( + [ + sys.executable, + str(ROOT / "scripts/gen_clash_verge_script.py"), + "--out", + str(output), + ], + cwd=ROOT, + env=env, + capture_output=True, + check=False, + ) + + self.assertEqual( + result.returncode, + 0, + result.stderr.decode("gbk", errors="replace"), + ) + self.assertTrue(output.is_file()) + def test_module_spec_declares_rule_authority_and_safe_context(self) -> None: spec = json.loads(SPEC_PATH.read_text(encoding="utf-8"))