Files
vpn-rules/scripts/gen_clash_verge_script.py
T
ao gong b4a4769dcf
CI / lite (push) Failing after 4s
fix(gen-script): strip inline '#' comment before classifying no-resolve suffix
to_rule() 在判断 entry.endswith(',no-resolve') 之前没有剥掉行尾 # 注释,
导致带 inline 注释的条目走 else 分支,把注释当成 entry 一部分拼进去,
最终生成出 mihomo 解析失败的规则:

  'IP-CIDR,162.254.192.0/18,no-resolve   # Akamai Steam CDN 主段,DIRECT'

修法:
- to_rule() 先 split('#', 1)[0].rstrip() 剥注释再判断
- direct.yaml 第 91 行 inline 注释挪到独立行(避免再次踩坑)

验证:gen_clash_verge_script.py 重跑后 Script.js 第 49 行已变成
  'IP-CIDR,162.254.192.0/18,DIRECT,no-resolve',
Clash Verge 重启内核后订阅配置校验失败消失。
2026-09-05 13:32:48 +08:00

123 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""把 direct.yaml(真源)生成为 Clash Verge 的全局增强脚本 Script.js。
用法:
python3 scripts/gen_clash_verge_script.py [--out 目标路径]
默认按 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
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]:
payload: list[str] = []
with path.open(encoding="utf-8") as f:
for line in f:
line = line.strip()
if line.startswith("- "):
payload.append(line[2:].strip())
if not payload:
raise SystemExit(f"[error] direct.yaml 里没有 payload 条目: {path}")
return payload
def to_rule(entry: str) -> str:
"""rule-provider 条目(无策略)→ 完整规则(补 DIRECT)。
处理:先剥掉行尾 `#` 注释,再补 DIRECT 策略,避免带注释的条目被错误拼接。
"""
# 剥掉 `#` 之后的行尾注释
entry = entry.split("#", 1)[0].rstrip()
if entry.endswith(",no-resolve"):
return f"{entry[:-len(',no-resolve')]},DIRECT,no-resolve"
return f"{entry},DIRECT"
def render_script(payload: list[str]) -> str:
rules = [f" {to_rule(e)!r}," for e in payload]
lines = "\n".join(rules)
return f"""// 由 vpn-rules/direct.yaml 生成 — 勿手改,改真源后重跑 gen_clash_verge_script.py。
// 全局直连规则:OneDrive、FRP 控制面、B站、国内 LLM、内网段、国区 iCloud、UGREEN 一律 DIRECT。
const prependRules = [
{lines}
];
// 国内可达的 DoH 同时作为 fallback,确保 OneDrive 等境外域名在 DIRECT 出站前可解析。
const directDnsFallbacks = [
'https://dns.alidns.com/dns-query',
'https://doh.pub/dns-query',
];
function main(config, profileName) {{
const existingRules = Array.isArray(config.rules) ? config.rules : [];
const existingRuleSet = new Set(existingRules);
const dns = config.dns && typeof config.dns === 'object' && !Array.isArray(config.dns)
? config.dns
: {{}};
const existingFallbacks = Array.isArray(dns.fallback)
? dns.fallback
: (typeof dns.fallback === 'string' ? [dns.fallback] : []);
const existingFallbackSet = new Set(existingFallbacks);
config.rules = [
...prependRules.filter((rule) => !existingRuleSet.has(rule)),
...existingRules,
];
dns.fallback = [
...directDnsFallbacks.filter((server) => !existingFallbackSet.has(server)),
...existingFallbacks,
];
config.dns = dns;
return config;
}}
"""
def main() -> None:
ap = argparse.ArgumentParser(description="direct.yaml → Clash Verge Script.js")
ap.add_argument("--out", default=str(DEFAULT_OUT), help="输出路径")
args = ap.parse_args()
payload = load_payload(REPO_ROOT / "direct.yaml")
script = render_script(payload)
out = Path(args.out)
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(script, encoding="utf-8")
print(f"[ok] 已生成 {out}({len(payload)} 条规则)")
print(" 请在 Clash Verge 界面点「重启内核」生效。")
if __name__ == "__main__":
main()