Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
112374e2a1 | ||
|
|
b4a4769dcf | ||
|
|
907fe9d46a | ||
|
|
854b8e1cdd | ||
|
|
de26a0edc0 |
@@ -20,22 +20,26 @@ vpn-rules/
|
|||||||
## 为什么需要它
|
## 为什么需要它
|
||||||
|
|
||||||
- 香港代理节点有流量配额(如 BygCloud 100GiB 用满),国内站点/内网流量被隧道进代理会白白烧配额。
|
- 香港代理节点有流量配额(如 BygCloud 100GiB 用满),国内站点/内网流量被隧道进代理会白白烧配额。
|
||||||
- 必须直连的对象:FRP 控制面(mTLS)、B 站全系、DeepSeek/MiniMax 等国内 LLM API、
|
- 必须直连的对象:Windows OneDrive 客户端、FRP 控制面(mTLS)、B 站全系、DeepSeek/MiniMax 等国内 LLM API、
|
||||||
内网/私有 IP 段、国区 iCloud、UGREEN 绿联 NAS 云。
|
内网/私有 IP 段、国区 iCloud、UGREEN 绿联 NAS 云。
|
||||||
- **踩坑**:Clash Verge 全局 `Merge.yaml` 的 `prepend-rules` 会被内核忽略(不生效),
|
- **踩坑**:Clash Verge 全局 `Merge.yaml` 的 `prepend-rules` 会被内核忽略(不生效),
|
||||||
全局直连规则的唯一正确机制是 `profiles/Script.js`(`main(config)` 把规则 prepend 进 `config.rules`)。
|
全局直连规则的唯一正确机制是 `profiles/Script.js`(`main(config)` 把规则 prepend 进 `config.rules`)。
|
||||||
|
|
||||||
## 消费端接入
|
## 消费端接入
|
||||||
|
|
||||||
### Clash Verge(macOS)
|
### Clash Verge(Windows / macOS)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 生成并写入 Clash Verge 数据目录的 Script.js
|
# 按当前平台生成并写入 Clash Verge 数据目录的全局 Script.js
|
||||||
python3 scripts/gen_clash_verge_script.py
|
python3 scripts/gen_clash_verge_script.py
|
||||||
# 然后在 Clash Verge 界面点「重启内核」生效
|
# 然后在 Clash Verge 界面点「重启内核」生效
|
||||||
```
|
```
|
||||||
|
|
||||||
对当前及以后新增/切换的所有订阅生效;订阅更新不会覆盖它。
|
对当前及以后新增/切换的所有订阅生效;订阅更新不会覆盖它。
|
||||||
|
脚本还会把国内可达的阿里 DNS 和腾讯 DNS DoH 端点补入 `dns.fallback`,避免境外域名
|
||||||
|
命中 `DIRECT` 后因供应商的境外 DNS fallback 不可达而解析失败。
|
||||||
|
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)
|
### 路由器 mihomo(Magic Catling2 / OpenWrt)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
profile: lite
|
||||||
|
runtime_contract: v1
|
||||||
|
language: generic
|
||||||
|
local_test_command: "python3 tests/test_module_spec.py"
|
||||||
|
requires_database: false
|
||||||
|
integration_profile: none
|
||||||
+21
@@ -20,6 +20,9 @@
|
|||||||
# 路由器端则等 rule-provider 的 interval 到期或手动 reload。
|
# 路由器端则等 rule-provider 的 interval 到期或手动 reload。
|
||||||
|
|
||||||
payload:
|
payload:
|
||||||
|
# Windows OneDrive 客户端始终直连,避免被供应商 Microsoft 规则送入代理
|
||||||
|
- PROCESS-NAME,OneDrive.exe
|
||||||
|
|
||||||
# FRP 控制面(mTLS)保持直连
|
# FRP 控制面(mTLS)保持直连
|
||||||
- DOMAIN,frp.puyuanfh.cn
|
- DOMAIN,frp.puyuanfh.cn
|
||||||
- IP-CIDR,8.133.177.3/32,no-resolve
|
- IP-CIDR,8.133.177.3/32,no-resolve
|
||||||
@@ -73,3 +76,21 @@ payload:
|
|||||||
- IP-CIDR,110.42.0.0/16,no-resolve
|
- IP-CIDR,110.42.0.0/16,no-resolve
|
||||||
- IP-CIDR,43.248.128.0/17,no-resolve
|
- IP-CIDR,43.248.128.0/17,no-resolve
|
||||||
- IP-CIDR,111.170.0.0/16,no-resolve
|
- IP-CIDR,111.170.0.0/16,no-resolve
|
||||||
|
|
||||||
|
# Steam 全局直连(社区/商店/CDN/Valve 官方网段;避免下载/联机绕美节点)
|
||||||
|
- DOMAIN-SUFFIX,steampowered.com
|
||||||
|
- DOMAIN-SUFFIX,steamcommunity.com
|
||||||
|
- DOMAIN-SUFFIX,steamgames.com
|
||||||
|
- DOMAIN-SUFFIX,steamusercontent.com
|
||||||
|
- DOMAIN-SUFFIX,steam-chat.com
|
||||||
|
- DOMAIN-SUFFIX,steamstatic.com
|
||||||
|
- DOMAIN-SUFFIX,steamcontent.com
|
||||||
|
- DOMAIN-KEYWORD,csgo.
|
||||||
|
- PROCESS-NAME,steam
|
||||||
|
- PROCESS-NAME,steamwebhelper
|
||||||
|
- IP-CIDR,162.254.192.0/18,no-resolve
|
||||||
|
# Akamai Steam CDN 主段
|
||||||
|
- IP-CIDR,103.10.124.0/23,no-resolve
|
||||||
|
- IP-CIDR,103.28.54.0/23,no-resolve
|
||||||
|
- IP-CIDR,146.66.152.0/21,no-resolve
|
||||||
|
- IP-CIDR,155.133.224.0/19,no-resolve
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
PROCESS-NAME,OneDrive.exe
|
||||||
DOMAIN,frp.puyuanfh.cn
|
DOMAIN,frp.puyuanfh.cn
|
||||||
IP-CIDR,8.133.177.3/32,no-resolve
|
IP-CIDR,8.133.177.3/32,no-resolve
|
||||||
DOMAIN-SUFFIX,bilibili.com
|
DOMAIN-SUFFIX,bilibili.com
|
||||||
@@ -32,3 +33,18 @@ DOMAIN-SUFFIX,syncthing.net
|
|||||||
IP-CIDR,110.42.0.0/16,no-resolve
|
IP-CIDR,110.42.0.0/16,no-resolve
|
||||||
IP-CIDR,43.248.128.0/17,no-resolve
|
IP-CIDR,43.248.128.0/17,no-resolve
|
||||||
IP-CIDR,111.170.0.0/16,no-resolve
|
IP-CIDR,111.170.0.0/16,no-resolve
|
||||||
|
DOMAIN-SUFFIX,steampowered.com
|
||||||
|
DOMAIN-SUFFIX,steamcommunity.com
|
||||||
|
DOMAIN-SUFFIX,steamgames.com
|
||||||
|
DOMAIN-SUFFIX,steamusercontent.com
|
||||||
|
DOMAIN-SUFFIX,steam-chat.com
|
||||||
|
DOMAIN-SUFFIX,steamstatic.com
|
||||||
|
DOMAIN-SUFFIX,steamcontent.com
|
||||||
|
DOMAIN-KEYWORD,csgo.
|
||||||
|
PROCESS-NAME,steam
|
||||||
|
PROCESS-NAME,steamwebhelper
|
||||||
|
IP-CIDR,162.254.192.0/18,no-resolve
|
||||||
|
IP-CIDR,103.10.124.0/23,no-resolve
|
||||||
|
IP-CIDR,103.28.54.0/23,no-resolve
|
||||||
|
IP-CIDR,146.66.152.0/21,no-resolve
|
||||||
|
IP-CIDR,155.133.224.0/19,no-resolve
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
# 内网源:http://192.168.50.2:3000/ageorge156/vpn-rules/raw/branch/main/direct.yaml
|
# 内网源:http://192.168.50.2:3000/ageorge156/vpn-rules/raw/branch/main/direct.yaml
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
|
- PROCESS-NAME,OneDrive.exe,DIRECT
|
||||||
- DOMAIN,frp.puyuanfh.cn,DIRECT
|
- DOMAIN,frp.puyuanfh.cn,DIRECT
|
||||||
- IP-CIDR,8.133.177.3/32,DIRECT,no-resolve
|
- IP-CIDR,8.133.177.3/32,DIRECT,no-resolve
|
||||||
- DOMAIN-SUFFIX,bilibili.com,DIRECT
|
- DOMAIN-SUFFIX,bilibili.com,DIRECT
|
||||||
@@ -45,3 +46,18 @@ rules:
|
|||||||
- IP-CIDR,110.42.0.0/16,DIRECT,no-resolve
|
- IP-CIDR,110.42.0.0/16,DIRECT,no-resolve
|
||||||
- IP-CIDR,43.248.128.0/17,DIRECT,no-resolve
|
- IP-CIDR,43.248.128.0/17,DIRECT,no-resolve
|
||||||
- IP-CIDR,111.170.0.0/16,DIRECT,no-resolve
|
- IP-CIDR,111.170.0.0/16,DIRECT,no-resolve
|
||||||
|
- DOMAIN-SUFFIX,steampowered.com,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,steamcommunity.com,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,steamgames.com,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,steamusercontent.com,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,steam-chat.com,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,steamstatic.com,DIRECT
|
||||||
|
- DOMAIN-SUFFIX,steamcontent.com,DIRECT
|
||||||
|
- DOMAIN-KEYWORD,csgo.,DIRECT
|
||||||
|
- PROCESS-NAME,steam,DIRECT
|
||||||
|
- PROCESS-NAME,steamwebhelper,DIRECT
|
||||||
|
- IP-CIDR,162.254.192.0/18,DIRECT,no-resolve
|
||||||
|
- IP-CIDR,103.10.124.0/23,DIRECT,no-resolve
|
||||||
|
- IP-CIDR,103.28.54.0/23,DIRECT,no-resolve
|
||||||
|
- IP-CIDR,146.66.152.0/21,DIRECT,no-resolve
|
||||||
|
- IP-CIDR,155.133.224.0/19,DIRECT,no-resolve
|
||||||
|
|||||||
@@ -4,14 +4,39 @@
|
|||||||
用法:
|
用法:
|
||||||
python3 scripts/gen_clash_verge_script.py [--out 目标路径]
|
python3 scripts/gen_clash_verge_script.py [--out 目标路径]
|
||||||
|
|
||||||
默认输出到 Clash Verge 数据目录 profiles/Script.js(全局直连规则的唯一正确机制,
|
默认按 Windows/macOS 平台输出到 Clash Verge 数据目录 profiles/Script.js(全局直连
|
||||||
见 clash-verge-config 记忆)。生成后需在 Clash Verge 界面点「重启内核」生效。
|
规则的唯一正确机制)。生成后需在 Clash Verge 界面点「重启内核」生效。
|
||||||
"""
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
|
||||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
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]:
|
def load_payload(path: Path) -> list[str]:
|
||||||
@@ -22,12 +47,17 @@ def load_payload(path: Path) -> list[str]:
|
|||||||
if line.startswith("- "):
|
if line.startswith("- "):
|
||||||
payload.append(line[2:].strip())
|
payload.append(line[2:].strip())
|
||||||
if not payload:
|
if not payload:
|
||||||
raise SystemExit(f"❌ direct.yaml 里没有 payload 条目: {path}")
|
raise SystemExit(f"[error] direct.yaml 里没有 payload 条目: {path}")
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
|
|
||||||
def to_rule(entry: str) -> str:
|
def to_rule(entry: str) -> str:
|
||||||
"""rule-provider 条目(无策略)→ 完整规则(补 DIRECT)。"""
|
"""rule-provider 条目(无策略)→ 完整规则(补 DIRECT)。
|
||||||
|
|
||||||
|
处理:先剥掉行尾 `#` 注释,再补 DIRECT 策略,避免带注释的条目被错误拼接。
|
||||||
|
"""
|
||||||
|
# 剥掉 `#` 之后的行尾注释
|
||||||
|
entry = entry.split("#", 1)[0].rstrip()
|
||||||
if entry.endswith(",no-resolve"):
|
if entry.endswith(",no-resolve"):
|
||||||
return f"{entry[:-len(',no-resolve')]},DIRECT,no-resolve"
|
return f"{entry[:-len(',no-resolve')]},DIRECT,no-resolve"
|
||||||
return f"{entry},DIRECT"
|
return f"{entry},DIRECT"
|
||||||
@@ -37,19 +67,37 @@ def render_script(payload: list[str]) -> str:
|
|||||||
rules = [f" {to_rule(e)!r}," for e in payload]
|
rules = [f" {to_rule(e)!r}," for e in payload]
|
||||||
lines = "\n".join(rules)
|
lines = "\n".join(rules)
|
||||||
return f"""// 由 vpn-rules/direct.yaml 生成 — 勿手改,改真源后重跑 gen_clash_verge_script.py。
|
return f"""// 由 vpn-rules/direct.yaml 生成 — 勿手改,改真源后重跑 gen_clash_verge_script.py。
|
||||||
// 全局直连规则:FRP 控制面、B站、国内 LLM、内网段、国区 iCloud、UGREEN 一律 DIRECT。
|
// 全局直连规则:OneDrive、FRP 控制面、B站、国内 LLM、内网段、国区 iCloud、UGREEN 一律 DIRECT。
|
||||||
const prependRules = [
|
const prependRules = [
|
||||||
{lines}
|
{lines}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 国内可达的 DoH 同时作为 fallback,确保 OneDrive 等境外域名在 DIRECT 出站前可解析。
|
||||||
|
const directDnsFallbacks = [
|
||||||
|
'https://dns.alidns.com/dns-query',
|
||||||
|
'https://doh.pub/dns-query',
|
||||||
|
];
|
||||||
|
|
||||||
function main(config, profileName) {{
|
function main(config, profileName) {{
|
||||||
const existingRules = Array.isArray(config.rules) ? config.rules : [];
|
const existingRules = Array.isArray(config.rules) ? config.rules : [];
|
||||||
const existingRuleSet = new Set(existingRules);
|
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 = [
|
config.rules = [
|
||||||
...prependRules.filter((rule) => !existingRuleSet.has(rule)),
|
...prependRules.filter((rule) => !existingRuleSet.has(rule)),
|
||||||
...existingRules,
|
...existingRules,
|
||||||
];
|
];
|
||||||
|
dns.fallback = [
|
||||||
|
...directDnsFallbacks.filter((server) => !existingFallbackSet.has(server)),
|
||||||
|
...existingFallbacks,
|
||||||
|
];
|
||||||
|
config.dns = dns;
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}}
|
}}
|
||||||
@@ -66,7 +114,7 @@ def main() -> None:
|
|||||||
out = Path(args.out)
|
out = Path(args.out)
|
||||||
out.parent.mkdir(parents=True, exist_ok=True)
|
out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
out.write_text(script, encoding="utf-8")
|
out.write_text(script, encoding="utf-8")
|
||||||
print(f"✅ 已生成 {out}({len(payload)} 条规则)")
|
print(f"[ok] 已生成 {out}({len(payload)} 条规则)")
|
||||||
print(" 请在 Clash Verge 界面点「重启内核」生效。")
|
print(" 请在 Clash Verge 界面点「重启内核」生效。")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+107
-1
@@ -1,7 +1,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
from pathlib import Path, PurePosixPath
|
from pathlib import Path, PurePosixPath
|
||||||
|
|
||||||
@@ -10,10 +13,60 @@ ROOT = Path(__file__).resolve().parents[1]
|
|||||||
SPEC_PATH = ROOT / "MODULE_SPEC.yaml"
|
SPEC_PATH = ROOT / "MODULE_SPEC.yaml"
|
||||||
sys.path.insert(0, str(ROOT / "scripts"))
|
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,
|
||||||
|
render_script,
|
||||||
|
to_rule,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ModuleSpecTests(unittest.TestCase):
|
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:
|
def test_module_spec_declares_rule_authority_and_safe_context(self) -> None:
|
||||||
spec = json.loads(SPEC_PATH.read_text(encoding="utf-8"))
|
spec = json.loads(SPEC_PATH.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
@@ -141,6 +194,59 @@ class ModuleSpecTests(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
self.assertTrue(all(",DIRECT" not in entry for entry in payload))
|
self.assertTrue(all(",DIRECT" not in entry for entry in payload))
|
||||||
|
|
||||||
|
def test_onedrive_process_is_forced_to_use_direct_routing(self) -> None:
|
||||||
|
payload = load_payload(ROOT / "direct.yaml")
|
||||||
|
|
||||||
|
self.assertIn("PROCESS-NAME,OneDrive.exe", payload)
|
||||||
|
self.assertEqual(
|
||||||
|
to_rule("PROCESS-NAME,OneDrive.exe"),
|
||||||
|
"PROCESS-NAME,OneDrive.exe,DIRECT",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_to_rule_strips_inline_hash_comment_before_classifying_no_resolve(self) -> None:
|
||||||
|
# 回归测试 (b4a4769):to_rule() 在判断 entry.endswith(',no-resolve') 之前
|
||||||
|
# 没有剥掉行尾 # 注释,导致带 inline 注释的 Akamai Steam CDN 规则
|
||||||
|
# 走错分支,把注释当成 entry 的一部分拼进去,生成 mihomo 解析失败的规则。
|
||||||
|
self.assertEqual(
|
||||||
|
to_rule("IP-CIDR,162.254.192.0/18,no-resolve # Akamai Steam CDN 主段"),
|
||||||
|
"IP-CIDR,162.254.192.0/18,DIRECT,no-resolve",
|
||||||
|
)
|
||||||
|
# 注释里出现 "no-resolve" 不能误判为 suffix
|
||||||
|
self.assertEqual(
|
||||||
|
to_rule("DOMAIN-SUFFIX,example.com # 走 no-resolve 路径"),
|
||||||
|
"DOMAIN-SUFFIX,example.com,DIRECT",
|
||||||
|
)
|
||||||
|
# 注释里出现 ",DIRECT" 不能误拼到 proxy 字段
|
||||||
|
self.assertEqual(
|
||||||
|
to_rule("IP-CIDR,10.0.0.0/8 # 不是 DIRECT, 是 REJECT"),
|
||||||
|
"IP-CIDR,10.0.0.0/8,DIRECT",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_to_rule_preserves_canonical_forms(self) -> None:
|
||||||
|
# 不带注释、不带 no-resolve
|
||||||
|
self.assertEqual(
|
||||||
|
to_rule("DOMAIN-SUFFIX,bilibili.com"),
|
||||||
|
"DOMAIN-SUFFIX,bilibili.com,DIRECT",
|
||||||
|
)
|
||||||
|
# 带 no-resolve、无注释
|
||||||
|
self.assertEqual(
|
||||||
|
to_rule("IP-CIDR,127.0.0.0/8,no-resolve"),
|
||||||
|
"IP-CIDR,127.0.0.0/8,DIRECT,no-resolve",
|
||||||
|
)
|
||||||
|
# 带 no-resolve + 行尾空白
|
||||||
|
self.assertEqual(
|
||||||
|
to_rule("IP-CIDR,127.0.0.0/8,no-resolve "),
|
||||||
|
"IP-CIDR,127.0.0.0/8,DIRECT,no-resolve",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_clash_verge_script_adds_reachable_direct_dns_fallbacks(self) -> None:
|
||||||
|
script = render_script(["PROCESS-NAME,OneDrive.exe"])
|
||||||
|
|
||||||
|
self.assertIn("const directDnsFallbacks", script)
|
||||||
|
self.assertIn("https://dns.alidns.com/dns-query", script)
|
||||||
|
self.assertIn("https://doh.pub/dns-query", script)
|
||||||
|
self.assertIn("config.dns = dns", script)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user