模块是一份纯文本。开头几行是元数据,之后按 [段名] 分段。

最小例子

#!name=YouTube 去广告
#!desc=拦截 YouTube 的广告接口与播放埋点。需开启 HTTPS 解密。

[Rule]
AND,((DOMAIN-SUFFIX,googlevideo.com),(PROTOCOL,UDP)),REJECT

[URL Rewrite]
^https?:\/\/(?:www|s)\.youtube\.com\/api\/stats\/ads _ reject-200

[MITM]
hostname = %APPEND% *.googlevideo.com, s.youtube.com

行的规则

  • 空行忽略。
  • # 或 ; 开头的整行是注释。例外:#! 开头的是元数据指令,会被解析。
  • 不支持行尾注释。 DOMAIN-SUFFIX,a.com,DIRECT # 走直连 中的 DIRECT # 走直连 会被完整解析为出口名。该名称未定义时,规则会使用「默认」出口,且不会显示错误。注释必须独占一行并写在规则上一行。
  • [段名] 单独占一行,开始一个新段。段名大小写不敏感。
  • 一条规则必须写在一行里,没有续行语法。
  • 段的先后顺序不影响解析,同名段出现多次时内容累加。

元数据头

指令 作用
#!name=模块名 模块显示名
#!desc=一句话说明 模块描述
#!arguments=A:默认值,B:默认值 定义占位符 {{{A}}} 的默认值

其余 #! 指令(#!system、#!category、#!managed-config 等)不解析,写了也不报错。

占位符 {{{A}}} 在解析末尾展开,可用在模块名、描述、[MITM] 的 hostname、以及所有改写和脚本字段里。[Rule]、[Proxy Group]、[Host] 三段不展开占位符,写在那里就是字面的 {{{A}}}。客户端没有参数编辑界面,永远使用 #!arguments= 里写的默认值。所以除非要保留对其它客户端的兼容,直接把值写死更省事。

从文件导入的模块,列表里显示的是文件名(去掉扩展名),不是 #!name=。取个好文件名。

段落清单

段 作用 语法详见
[Rule] 分流规则:什么流量走什么出口 routing.md
[Proxy Group] 策略组:出口本身怎么组织 routing.md
[Host] 域名的解析方式:静态映射,或指定用哪台 DNS 去查 routing.md
[General] 只认 dns-server 一个键:改直连域名用哪台 DNS routing.md
[Remote Rule] 外部规则集订阅(Loon 写法) routing.md
[Remote Filter] 按正则筛节点的具名分组(Loon 写法) routing.md
[MITM] 对哪些域名做 HTTPS 解密 mitm.md
[URL Rewrite] 改写或拦截请求 URL mitm.md
[Header Rewrite] 增删改请求/响应头 mitm.md
[Body Rewrite] 改写请求/响应正文 mitm.md
[Map Local] 用本地内容直接回一个响应 mitm.md
[Script] 用 JavaScript 处理请求/响应 mitm.md

不在这张表里的段一律被忽略,不报错、不影响其它段。常见的被忽略段:[Proxy]、[Panel]、[Replica]、[SSID Setting],以及各家客户端的其它私有段。[General] 除了 dns-server 之外的键也在此列。所以模块里不能用 [Proxy] 定义节点 —— 节点在客户端里管理。

模块中的 DNS 配置只控制目标域名的解析方式:[Host] 可为特定域名指定 DNS,也可使用保留值 server:fakeip 分配虚拟 IP,使 [Rule] 决定其出口;[General] dns-server 可替换直连域名使用的解析器。节点和入口域名的解析不受模块影响,始终使用客户端内置的解析路径。

格式与扩展名

导入时按扩展名判断源格式,认不出再看内容。

扩展名 格式
.sgmodule Surge
.plugin Loon
.stoverride Stash(YAML)
.snippet Quantumult X
.conf / .module Shadowrocket

自己写就写 Surge 格式,存成 .sgmodule。 本手册全部语法以 Surge 为准。Loon / Stash / Quantumult X 的模块可以直接导入,但它们各自的私有写法只做到「不报错」,不保证逐字段等价。

导入与优先级

两种导入方式:

  • 从文件导入:选本机的 .sgmodule 文件。
  • 从链接导入:填一个能公开访问的 URL,内容就是模块原文。之后可以按这个链接重新拉取更新。

同一个链接重复导入会原地覆盖,不会出现两份。

模块列表里越靠上优先级越高,可以拖动排序。多个模块的规则合在一起后,上面模块的规则排在下面模块之前。模块内部则是从上到下,先命中先生效。

模块的规则排在客户端自带的分流规则之前,所以模块可以刻意覆盖内置的国内直连之类的行为。

但客户端还有几条前置规则排在所有模块规则之前,模块盖不掉:

前置规则 后果
私网地址 → 直连 模块里针对 192.168.x.x、10.x.x.x 这类地址的规则永远不命中
BitTorrent → 直连 模块管不了 BT 流量
ICMP → 直连 模块不能修改 ping 流量的出口
「直连」/「全局」模式 用户切到这两个模式时,所有模块规则整体失效,流量一律按模式走

排查「规则写了没反应」时先想想是不是撞上了这几条。

独立规则列表

一份没有任何 [段名]、每行只有 类型,值 而没有出口列的纯规则文件(常见扩展名 .list)也能直接导入。导入时客户端会让你为整份列表选一个出口。

DOMAIN-SUFFIX,openai.com
DOMAIN-SUFFIX,chatgpt.com
DOMAIN-KEYWORD,openai

这种文件里的注释、payload: 头、以及 Clash YAML 的 - DOMAIN-SUFFIX,x 列表写法都能识别。

能用的类型比 [Rule] 少:只认 routing.md 规则类型全表里的那些,PROTOCOL、AND / OR、FINAL 在这种文件里一律被忽略。RULE-SET 与 [Remote Rule] 拉回来的外部规则集内容同理。