跳到正文

FluxDown 0.5.0 原生 GPUI 客户端正式发布——作者来信:为什么我把整个界面从头重写了一遍。去看看

FluxDown
下载 文档 定价 更新日志 赞助
浏览器扩展 嗅探规则参考

英文原文在此译文之后有更新,内容可能已过时。 帮我们更新 →

浏览器扩展

嗅探规则参考

嗅探规则是什么、五个规则层级,以及规则每个字段的含义——匹配条件、阶段、动作、限制与吊销。

编辑此页

嗅探规则是一小段 JSON,告诉扩展:哪些网络请求算可下载资源、哪些地址其实是同一个文件、怎样区分视频轨和音轨、API 响应里的直链藏在哪、下载时需要带哪些请求头。绝大多数网站靠内置规则就能正常工作;只有当某个网站识别不对、你想自己修好它——或者把修复分享给所有人——时,才需要看这一页。分享流程见贡献嗅探规则。

打开扩展的设置页 → 嗅探规则,可以看到规则列表(带筛选,每条规则都有状态)、规则同步、你自己的本地规则,以及规则测试台——它会模拟一次响应,告诉你当前生效的规则会怎么判断。

规则列表可按层级筛选,也能按标题、ID、域名、扩展名、MIME 或地址搜索;可按求值顺序或命中次数排序,并显示每条规则的优先级、求值位置和本次会话的命中次数。每一层的规则都能点「查看规则」阅读全文(发布构建中的内置规则除外),也可以「复制为本地规则」后修改;本地规则的编辑、删除、提交也在列表的操作列里。规则测试台会列出所有命中的规则(标出生效的与被遮蔽的)并说明这条响应最终会不会被收录;在本地规则编辑器里还可以直接用未保存的草稿测试,并把测试结果「加为测试用例」随提交一起带上。

规则层级与谁能修改

每条规则属于五个层级之一。多条规则都可能生效时,层级决定谁先判断:

层级tier 取值来源谁能修改
本地local你在本机创建的规则(设置 → 嗅探规则 → 本地规则)你自己:编辑、导入、导出、删除
官方official由公开的规则仓库维护,扩展随包附带一份快照维护者;你只能开关某条规则
插件pluginFluxDown 插件声明的规则,经桌面端下发插件作者;你可以开关规则
社区community作者在自己登记的规则仓库里维护(id 为 community.<命名空间>.<名字>),或一次性提交(community.misc.*),见贡献嗅探规则作者通过自己的仓库或经审核更新;你只能开关某条规则
内置builtin随扩展一起发布随扩展更新;你只能开关某条规则

优先级:本地 > 官方 > 插件 > 社区 > 内置。 同一层级内,priority 数值大的先判断;priority 相同则按规则在包内的先后顺序。对于只需要一个结论的判断(例如「是资源还是忽略」),第一条命中的规则说了算。

各层级怎么保持最新:

  • 内置规则和官方规则快照随扩展附带。官方包可以通过桌面端热更新:当桌面端提供的规则包 sequence 比随扩展附带的快照更高时,就用更新的那份,无需等扩展发版。
  • 社区规则依赖桌面端:桌面端从规则仓库拉取规则包(规则仓每小时收录登记仓库的新 Release)并验证签名,扩展再从桌面端同步(每 6 小时一次,或点立即同步)。社区包有效期 30 天;一旦社区包已过期,或吊销列表缺失、已过期,整个社区包会暂停(规则状态显示「已过期」),直到拿到新的为止。
  • 启用社区规则开关可以一次性停用整个社区包。
  • 如果某个规则包需要比当前扩展更新的引擎,它会被跳过,规则状态显示「需更新扩展」。

一条规则长什么样

{
  "id": "community.example.video",
  "version": 1,
  "tier": "community",
  "title": "Example:把 .xyz 文件识别为视频",
  "stage": "response",
  "match": { "host": ["example.com"], "ext": ["xyz"] },
  "exclude": [{ "url": ["*/preview/*"] }],
  "action": { "kind": "resource", "type": "video", "confidence": "high" }
}
字段必填含义
id是唯一名称:层级、一个点,再加 2–63 个字符(小写字母、数字、.、_、-,且以字母或数字开头),例如 local.my-site.video。前缀必须与 tier 一致。插件规则的 id 总长不超过 64 个字符。
version是正整数。修改已发布的规则时请加一——吊销是按版本生效的。
tier是builtin、official、plugin、community 或 local,必须与规则所在的规则包一致。
title否在规则列表中显示的名称。
enabled否默认 true。你在规则列表里切换开关会覆盖它。
priority否数字,默认 0。同一层级内越大越先判断。
stage是规则在流程的哪个环节运行,同时决定允许哪些 action。见阶段与动作。
requires否只能是 "sniffImages":仅当设置里开启了图片嗅探时,这条规则才参与响应识别。
classifyOnly否true 表示规则只用于「这个地址 / MIME 是什么类型」的查询,不参与实时响应的判定。
match是请求要满足的条件。见匹配条件。
exclude否一组额外的 match 对象。任意一个命中,就视为本条规则不匹配。
action是要做什么。kind 必须是该规则 stage 允许的动作。

同一个规则包里 id 不能重复。校验不通过的规则会被剔除(本地规则编辑器和规则仓库都会告诉你原因)。

匹配条件

match 是一个对象,所有字段都可选。写了多个字段时,必须全部满足。 列表类字段(host、url、ext、mime、resourceType)里任意一项命中即可。exclude 是一组这样的对象,任意一个命中,本条规则就不生效。

字段类型含义
host字符串列表请求地址的域名。"example.com" 匹配 example.com 及其所有子域(如 cdn.example.com);"=example.com" 只匹配这一个域名。不支持通配写法 "*.example.com"。
pageHost字符串列表写法同上,作用于用户所在顶层页面的域名。
url通配串列表对整个 URL(含查询串)做通配匹配。* 是唯一的通配符;第一个 * 之前的部分必须是前缀,最后一个 * 之后的部分必须是后缀;不区分大小写。不含 * 时必须完全相同。
pathRegex字符串只对 URL 的路径(不含查询串)做正则匹配,不区分大小写。必须符合正则安全子集。
ext字符串列表URL 路径里的文件扩展名,小写、不带点("mp4")。没有扩展名的 URL 永远不匹配。
hasExt布尔true 表示 URL 路径带扩展名,false 表示不带。
mime字符串列表响应的 Content-Type,去掉参数并转小写后比较。以 * 结尾的是前缀写法("video/*"),否则必须完全一致("application/x-mpegurl")。没有 MIME 类型的响应永远不匹配。
resourceType字符串列表浏览器的请求类型("media"、"xmlhttprequest"、"image" 等)。从页面内部观察到的请求记作 "xmlhttprequest"。
status[下限, 上限]HTTP 状态码落在闭区间内,如 [200, 206]。需要已知状态码。
minSize / maxSize数字(字节)大小上下限。大小未知时,该条件视为满足。
attachment布尔响应是否带 Content-Disposition: attachment。
header{ "name", "regex" } 列表响应头;列出的每一个头都必须存在且匹配其正则(不区分大小写)。name 按小写比较。

示例:

{ "host": ["example.com"], "ext": ["m3u8", "mpd"] }
{ "pageHost": ["=www.example.com"], "mime": ["video/*"], "minSize": 1048576 }
{ "host": ["cdn.example.com"], "pathRegex": "^/v/[0-9a-f]{8}/" }
{ "url": ["https://example.com/download/*"], "attachment": true }
{ "host": ["example.com"], "header": [{ "name": "x-content-kind", "regex": "^video$" }] }

哪些字段有值取决于阶段。response 阶段能看到全部信息;其它阶段只知道请求地址(以及页面地址),所以 mime、status、大小、attachment、header、resourceType 这些条件只适合 response 阶段(track 阶段也能看到 MIME 类型)。在 body 阶段,host / url / pathRegex 描述的是被检查响应的 API 地址,pageHost 描述的是发出请求的页面。

正则安全子集

规则里所有正则(pathRegex、header[].regex,以及 track 动作的 pattern)都必须:

  • 长度不超过 256 个字符,并且能被编译为 JavaScript 正则(匹配时不区分大小写);
  • 不使用反向引用(\1、\k<name>);
  • 不使用先行 / 后行断言((?=、(?!、(?<=、(?<!);
  • 不使用嵌套量词,例如 (a+)+。

CSS 选择器(dom、title、player)不能为空、不超过 512 个字符,并且必须合法。

阶段与动作

规则的 stage 决定它在什么时候运行,也限定了能用哪些 action.kind:

阶段含义(界面上的名称)允许的动作
response判断一个网络响应是什么(响应识别)resource、ignore
normalize判定地址是否同一文件、找扩展名、归并镜像域名(地址归一化)strip-params、filename、mirror
track区分音轨与视频轨(音视频分轨)track
body从 API 的 JSON 响应里取出直链(响应体提取)extract、manifest
dom从页面元素里取链接(页面元素)dom
page页面级提示(页面提示)title、player
request下载时补充请求头(下载请求)headers

resource:识别为资源(response 阶段)

把命中的响应标记为可下载资源。

字段含义
type取值之一:video、audio、document、archive、image、executable、torrent、stream、subtitle、magnet、other。
confidence可选:high、medium 或 low。
{
  "id": "local.example.xyz",
  "version": 1,
  "tier": "local",
  "title": "Example:.xyz 是视频",
  "stage": "response",
  "match": { "host": ["example.com"], "ext": ["xyz"] },
  "action": { "kind": "resource", "type": "video" }
}

响应规则按层级和 priority 的顺序检查,第一条命中的规则说了算——所以排在 ignore 规则前面的 resource 规则会赢,反之亦然。带 requires: "sniffImages" 的规则在图片嗅探关闭时会被跳过。当扩展只需要知道某个地址的类型时,会查询 resource 规则:先看带 mime 条件的,再看只有 ext 的,都不命中就是 other。所有生效的 resource 规则的 ext 合在一起,就是 filename 动作使用的「已知扩展名」集合。

ignore:不显示(response 阶段)

除了 kind 没有别的字段。命中的响应不会出现在资源列表里。

{
  "id": "local.example.no-thumbs",
  "version": 1,
  "tier": "local",
  "stage": "response",
  "match": { "host": ["img.example.com"], "pathRegex": "/thumb/" },
  "action": { "kind": "ignore" }
}

strip-params:比较地址时去掉查询参数(normalize 阶段)

params 是查询参数名列表(不区分大小写)。扩展判断两个地址是不是同一个文件时,会去掉这些参数、丢掉片段(#…),并把剩下的参数排序。match 为空(没有任何条件,也没有 exclude)的规则对所有地址生效——只有内置、官方和本地规则允许这样写。带范围的规则只对命中的地址生效。

{
  "id": "local.example.strip-token",
  "version": 1,
  "tier": "local",
  "stage": "normalize",
  "match": { "host": ["cdn.example.com"] },
  "action": { "kind": "strip-params", "params": ["token", "expires", "sign"] }
}

filename:从查询串里找扩展名(normalize 阶段)

用于路径里没有扩展名的地址(/download?file=a.zip),改从查询参数里取扩展名。extFromQuery 是参数名列表,按顺序尝试;参数值是已知扩展名时采用(见上文 resource)。如果都没有结果,就找任何以 .扩展名 结尾、且扩展名已知的查询参数值。路径自带的扩展名始终优先。

{
  "id": "local.example.dl-ext",
  "version": 1,
  "tier": "local",
  "stage": "normalize",
  "match": { "host": ["example.com"], "pathRegex": "^/download$" },
  "action": { "kind": "filename", "extFromQuery": ["format", "file"] }
}

mirror:镜像域名组(normalize 阶段)

hosts 至少列出两个域名模式(写法同 host)。这些域名上路径相同的文件会被视为同一个文件(用于合并清单和识别正在播放的流)。镜像组的判定不使用规则的 match,但社区和插件规则仍然必须写上 host 或 pageHost。

{
  "id": "community.example.mirrors",
  "version": 1,
  "tier": "community",
  "stage": "normalize",
  "match": { "pageHost": ["example.com"] },
  "action": { "kind": "mirror", "hosts": ["cdn1.example.net", "cdn2.example.net", "cdn3.example.net"] }
}

track:音轨还是视频轨(track 阶段)

很多网站把音频和视频分成两个文件提供。这个动作从文件名(或 URL)里读出一个编号,据此判断文件是哪种轨道。

字段含义
source固定为 "filenameOrUrl":对文件名做匹配,没有文件名时对 URL 匹配。
pattern正则(安全子集)。
group编号在第几个捕获组(非负整数;0 表示整个匹配)。
audio表示音频的编号列表。
video可选:表示视频的编号列表。省略时,不在 audio 里的编号都算视频。
fallback"video"、"audio" 或 "none":正则或编号对不上时的结论。

MIME 类型以 audio/ 开头的响应一律是音频。第一条给出结论的命中规则生效。

下面是 bilibili CDN 的官方规则(实际文件里还有一条用 pageHost 的孪生规则):

{
  "id": "official.bilibili.track",
  "version": 1,
  "tier": "official",
  "title": "哔哩哔哩:按 m4s 分片编号区分音轨 / 视频轨(CDN 域名)",
  "stage": "track",
  "match": { "host": ["bilibili.com", "bilivideo.com", "bilivideo.cn", "acgvideo.com"] },
  "action": {
    "kind": "track",
    "source": "filenameOrUrl",
    "pattern": "[-_](\\d{4,6})\\.m4s(?:[?#]|$)",
    "group": 1,
    "audio": ["30216", "30232", "30280", "30250", "30251"],
    "fallback": "video"
  }
}

对于 …/x/xxx-1-30280.m4s,捕获组是 30280,在 audio 里,所以判为音轨;…-30080.m4s 不在 audio 里,落到 fallback,算视频。

extract:从 API 响应里取直链(body 阶段)

有些网站不会把真实文件地址写在页面里,而是通过一次 API 调用返回 JSON。extract 读取这段 JSON 并产出资源。

字段含义
when可选,{ "path", "equals" } 条件列表。全部成立规则才会执行。equals 是字符串、数字或布尔值,严格比较(1 不等于 "1")。
each可选,指向 JSON 中某个数组的点路径;规则会对数组的每个元素各产出一个资源。不写 each 时,整个 JSON 就是唯一的元素。如果 each 指向的不是数组,则什么都不产出。
fields「字段名 → 点路径」的映射,从每个元素里读取。值必须是字符串、数字或布尔(会转成字符串),其它类型会让该字段处于未定义状态。
emit如何用这些字段构造资源。见用 emit 构造资源。

点路径用 . 逐层往下走:对象按键名、数组按下标——data.list.0.url 表示 data.list 第一项的 url。键名按字面匹配(含点号的键名无法被指定)。

蓝奏云的官方规则,把 ajaxm.php 的响应变成直链:

{
  "id": "official.lanzou.extract",
  "version": 1,
  "tier": "official",
  "title": "蓝奏云:从 ajaxm.php 响应提取直链",
  "stage": "body",
  "match": { "url": ["*/ajaxm.php*"] },
  "action": {
    "kind": "extract",
    "when": [{ "path": "zt", "equals": 1 }],
    "fields": { "dom": "dom", "url": "url", "name": "inf" },
    "emit": {
      "mode": "preempt",
      "url": { "resolve": { "base": "${dom}", "ref": "${url}", "bareQueryPrefix": "/file/" } },
      "filename": "${name}"
    }
  }
}

对于 { "zt": 1, "dom": "https://dl.cdn.com", "url": "?token", "inf": "a.zip" } 这样的响应,会得到名为 a.zip 的 https://dl.cdn.com/file/?token;zt 不等于 1,或者缺少字段时,什么都不产出。(社区规则不能用 mode: "preempt",请用 "resource"。)

manifest:声明 DASH 清单(body 阶段)

JSON 响应里包含 DASH 结构(带 baseUrl 的 video[] 与 audio[]),扩展会把它当作流媒体清单处理。path 是可选的点路径,指向包含该结构的子树(默认是整个 JSON)。

{
  "id": "community.example.dash-json",
  "version": 1,
  "tier": "community",
  "stage": "body",
  "match": { "host": ["api.example.com"], "pathRegex": "^/play/url$", "pageHost": ["example.com"] },
  "action": { "kind": "manifest", "path": "data.dash" }
}

dom:从页面元素里取链接(dom 阶段)

字段含义
selector要读取的元素的 CSS 选择器(不超过 512 个字符)。
attr要读取的属性,如 "data-src"。对于 URL 型属性(href、src、data、poster、action、currentSrc),取浏览器解析后的绝对地址。取值为空的元素会被跳过。
emit如何构造资源。可用的模板字段:value(属性值)、text(元素文本,去除首尾空白并截取前 200 个字符)、page(规则匹配所用的页面地址)。

如果该元素是页面上的媒体元素,或包含媒体元素,产出的资源会归属到这个元素(这样悬浮下载按钮才认得它)。

{
  "id": "community.example.dom-video",
  "version": 1,
  "tier": "community",
  "title": "Example:从 data-video-url 取视频地址",
  "stage": "dom",
  "match": { "pageHost": ["example.com"], "pathRegex": "^/watch/" },
  "action": {
    "kind": "dom",
    "selector": "div.player[data-video-url]",
    "attr": "data-video-url",
    "emit": { "mode": "resource", "url": "${value}", "filename": "${text}", "type": "video" }
  }
}

title:文件名从哪里来(page 阶段)

告诉扩展哪个元素里写着正在播放的视频或音频的标题。离播放元素最近的标题会成为候选名称和默认文件名。

字段含义
selectorCSS 选择器。使用离媒体元素最近的匹配节点。
attr可选:读取该属性,而不是元素文本。

这是抖音的官方规则:

{
  "id": "official.douyin.title",
  "version": 1,
  "tier": "official",
  "title": "抖音:视频描述作为文件名",
  "stage": "page",
  "match": { "pageHost": ["douyin.com"] },
  "action": { "kind": "title", "selector": "[data-e2e=\"video-desc\"]" }
}

player:隐藏悬浮按钮(page 阶段)

匹配 selector(或位于匹配元素内部)的媒体元素不会显示悬浮下载按钮——适合背景视频、广告和预览视频。float 必须是 "hide"。

{
  "id": "community.example.hide-bg",
  "version": 1,
  "tier": "community",
  "stage": "page",
  "match": { "pageHost": ["example.com"] },
  "action": { "kind": "player", "selector": ".hero-bg video, .ad-slot video", "float": "hide" }
}

headers:补充请求头(request 阶段)

为规则命中的地址在下载请求里补充请求头(典型的是为防盗链加上 Referer 或 Origin)。

字段含义
set「请求头名 → 值」的映射,1 到 16 个。名称:1–64 个字母、数字或 -;值:不超过 1024 个字符,不能含换行。

下面这些头是禁止设置的,因为它们由浏览器或下载引擎负责:Cookie、Authorization、Proxy-Authorization、Host、Content-Length、Range、Connection。多条规则设置同一个头时,先命中的规则优先。

{
  "id": "community.example.referer",
  "version": 1,
  "tier": "community",
  "title": "Example:CDN 要求带 Referer",
  "stage": "request",
  "match": { "host": ["cdn.example.net"] },
  "action": { "kind": "headers", "set": { "Referer": "https://example.com/" } }
}

用 emit 构造资源

extract 和 dom 通过 emit 对象构造资源:

字段含义
mode"resource" 把链接加入资源列表。"preempt" 用于一次性的下载地址(例如中转页):该链接不会列出来,扩展会把它记住 30 秒,避免浏览器自己对这个地址发起的下载被再次交给 FluxDown。只有内置和官方规则可以使用 preempt。
url链接:一个模板字符串,或一个 resolve 对象(见下)。
filename可选,文件名模板;渲染结果为空时忽略。
type可选,资源类型(取值同 resource)。省略时由 resource 规则根据 URL 推断类型。
quality可选,清晰度标签(如 1080p)的模板;渲染结果为空时忽略。

模板只支持 ${名称} 替换,不支持表达式。在 extract 里,名称 必须是 fields 的某个键;在 dom 里,必须是 value、text、page 之一。引用其它名称会让规则校验不通过。如果规则运行时某个字段缺失(比如 JSON 里没有这个值),模板就无法渲染,不会产出任何资源——只有 filename 或 quality 缺字段时,只是少了这个属性。

url 写成对象,适用于 API 返回相对引用的情况:

"url": { "resolve": { "base": "${dom}", "ref": "${url}", "bareQueryPrefix": "/file/" } }

base 和 ref 都是模板。结果取决于 ref 的形态:

  1. ref 以 http:// 或 https:// 开头——原样使用。
  2. ref 以 / 开头——接在 base 的协议和域名之后(https://dl.cdn.com + /file/?abc → https://dl.cdn.com/file/?abc)。
  3. 其它情况(例如只有查询串 ?token)——如果写了 bareQueryPrefix,结果是 base + bareQueryPrefix + ref(https://dl.cdn.com/file/?token);没写则不产出。

最终地址必须以 http 开头。

各层级的限制

限制builtinofficialplugincommunitylocal
match 必须包含 host 或 pageHost否否是是否
emit.mode: "preempt"可以可以不可以不可以不可以
全局 strip-params(没有任何匹配条件)可以可以不可以不可以可以
title / player 规则必须写 host 或 pageHost是是是是否
headers 规则须写 host 或 pageHost须写 host 或 pageHost不允许须写 host 或 pageHost须写 host 或 pageHost
extract / dom 产出的下载地址,域名必须落在规则自身 host / pageHost 范围内(否则丢弃)否否否是否
mirror.hosts 必须落在规则的 host / pageHost 范围内否否否是否

所有层级都适用的上限:

  • 每个规则包最多 2000 条规则。
  • 正则最长 256 个字符且在安全子集内;CSS 选择器最长 512 个字符。
  • 每条 headers 规则最多 16 个请求头,且不含禁止设置的头。
  • mirror 至少两个域名;id 必须唯一,前缀必须与 tier 一致。
  • 域名模式必须是合法域名;"*.example.com" 会被拒绝。

吊销

如果某条规则有害或已失效,维护者可以在吊销列表里为它发布一条吊销。任何层级都可以被吊销,包括内置规则。

字段含义
ruleId要吊销的规则。
versions"*" 表示所有版本,{ "max": N } 表示不超过 N 的版本。修复后版本号更高的规则不受影响。
action"soft" 或 "hard"。
reason原因——会显示在规则列表里。
ref可选,指向讨论的链接。
  • soft(软吊销):规则被关闭,显示为「已撤回」,但你读完原因后可以点仍然启用把它重新打开。这个确认是针对当前吊销列表的,吊销列表更新后可能需要再确认一次。
  • hard(硬吊销):规则被关闭,显示为「已撤回(不可操作)」,不能再手动启用。

吊销列表本身带签名并会过期;它缺失或已过期期间,社区包保持暂停。

更多示例

把某个网站的 .m3u8 / .mpd 识别为流媒体,同时忽略它的广告域名——两条规则。ignore 规则的 priority 更高,所以先判断,第一条命中的说了算:

{
  "id": "local.example.ads",
  "version": 1,
  "tier": "local",
  "priority": 10,
  "stage": "response",
  "match": { "pageHost": ["example.com"], "host": ["ads.example-cdn.com"] },
  "action": { "kind": "ignore" }
}
{
  "id": "local.example.stream",
  "version": 1,
  "tier": "local",
  "stage": "response",
  "match": { "pageHost": ["example.com"], "ext": ["m3u8", "mpd"] },
  "action": { "kind": "resource", "type": "stream", "confidence": "high" }
}

只把较大的 application/octet-stream 下载识别为压缩包——用 minSize 加一个 exclude:

{
  "id": "local.example.octet",
  "version": 1,
  "tier": "local",
  "stage": "response",
  "match": { "host": ["files.example.com"], "mime": ["application/octet-stream"], "minSize": 5242880 },
  "exclude": [{ "pathRegex": "/(thumb|preview)/" }],
  "action": { "kind": "resource", "type": "archive" }
}

上文的三条官方规则(bilibili 的 track、蓝奏云的 extract、抖音的 title)都是完整的真实示例,完整文件见规则仓库。想试一条规则,可以先在本地规则里新建,再用规则测试台验证;确认有效后欢迎分享,见贡献嗅探规则。

这页文档有问题?

页面
操作
↑↓选择打开