CI / lite (push) Failing after 4s
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 重启内核后订阅配置校验失败消失。
123 lines
4.1 KiB
Python
123 lines
4.1 KiB
Python
#!/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()
|