GoForum › 🌐 V2EX

给开源下载器 FluxDown 做了一套插件系统:网页解析、拦截下载、自定义界面,桌面和 Web 端通用

clow · 2026-10-22 15:48 · 0 次点赞 · 0 条回复

大家好,我是 FluxDown 的作者。FluxDown 是一个开源( AGPL-3.0 )的多协议下载器,引擎用 Rust 写,支持 HTTP / BT / HLS / DASH / FTP / eD2K ,有桌面端、NAS 版(带 Web 界面)和原生 Android / iOS 。

十月份主要的工作是插件系统,已经随 v0.6.0 发布。下面讲讲它能做什么,以及我在安全方面是怎么设计的。

插件能做什么

一个插件就是一个文件夹(打包后是 .fxplug),里面放一份 manifest.json 和几个 JS 脚本。目前支持这些能力:

能力 用途举例
resolver 把分享页链接解析成直链,例如网盘、视频站
hooks 下载完成后自动调用 ffmpeg / yt-dlp 做后处理
interceptors 在下载的各个阶段改写请求头、Cookie 、分片、凭据,并决定失败后是否重试
auth 扫码 / 验证码登录站点,之后复用登录态
订阅源 在内置 RSS 之外提供新的订阅来源,例如 UP 主投稿
界面 新增页面、设置页签、任务详情页签、命令面板命令、任务右键菜单
浏览器嗅探规则 插件给浏览器扩展下发嗅探规则
页面脚本 插件经浏览器扩展在指定网站上运行 JS ,并能与插件本体通信

插件市场里已经有社区作者写的插件,比如夸克网盘分享转存、B 站视频下载(番剧、投稿订阅、扫码登录)、M3U8 解析、GitHub 下载加速:

插件管理与市场

一份界面代码,桌面端和 Web 端都能显示

插件界面用 JSX 编写,界面数据交给宿主渲染。同一份代码可以在 GPUI 桌面端和 NAS 的 Web 界面中显示。下图是示例插件在 Web 端的面板:插件通过 flux.tasks 创建任务,表格里能看到实时进度和状态。

插件面板:任务表

内置的组件有:标题、徽标、进度条、callout 、表格、表单、Markdown 、代码块等,也支持定时器和事件订阅。

插件面板:组件

有特殊需求的插件,还可以写桌面原生界面( GPUI 脚本),但这需要单独申请 desktop 权限。

安全:插件默认能做的事情很少

下载器插件要接触 Cookie 、网络和文件,所以安全是我花时间最多的部分:

  • 沙箱:脚本运行在 QuickJS 里,基础能力只有 flux.*( HTTP 、键值存储、临时文件、日志),不能创建任务,也不能读写任意文件。
  • 权限需要申请:创建任务、拦截下载、登录、浏览器规则、页面脚本、桌面界面、后台常驻都属于单独的权限。请求了权限的插件,装上后保持停用,用户逐项看过并授权后才会启用。插件更新后如果新增了权限,会再次询问。
  • 网络边界:沙箱里的 flux.fetch 默认只能访问公网地址(只有开发模式下的插件才允许访问内网,方便调试);桌面原生脚本只能连接授权时列出的主机。
  • 风险分级:授权对话框会显示「标准 / 较高权限 / 完全信任」三档,带「完全信任」的插件必须勾选「我已了解」才能授权。

授权对话框

  • 审计日志:授权、安装、网络请求、创建任务、写文件等操作都有记录,可以导出为 JSON 。

审计日志

  • 熔断:resolver 、拦截器等连续 3 次超时或超出内存限制,插件会被自动禁用。
  • 解析失败直接报错( fail-closed ):解析失败时任务进入错误状态,不会退回去下载原始页面,避免把一个 HTML 页面当成视频保存下来。
  • 页面脚本符合浏览器商店政策:页面脚本只通过浏览器的 userScripts API 执行,扩展里不使用 eval,也不加载远程 <script>。每个插件第一次启用页面脚本时,需要用户在扩展选项页确认要匹配的站点。

写一个插件

SDK 已经发布到 npm:

npx fluxdown-plugin-sdk create my-plugin --identity me@my-plugin --name "My Plugin"
cd my-plugin && npm install
npm run dev      # 保存即重新构建
npm run pack     # 打包成 .fxplug

开发时,在「设置 → 扩展 → 插件 → 从目录安装」中填入 dist/ 目录。改了 resolver 、hook 不用重启;改了 manifest 后点一下「重新加载」即可。

SDK 里带的 manifest 校验器,和应用、插件索引仓用的是同一套规则(跨实现共用测试夹具),本地校验通过,就不会在审核时才发现问题。

最简单的 resolver 大概长这样:

// manifest.json: "resolvers": [{ "match": { "urls": ["*://example-files.com/share/*"] }, "entry": "resolver.js" }]
async function resolve(ctx) {
  const id = ctx.url.split("/share/")[1];
  if (!id) return null; // 放行,按原 URL 下载
  const res = await flux.fetch({
    url: "https://example-files.com/api/file/" + id,
    headers: { Referer: ctx.url },
  });
  if (res.status !== 200) throw new Error("API returned " + res.status);
  const data = JSON.parse(res.body);
  return { url: data.directUrl, fileName: data.name };
}

文档(中英双语)写得比较全,从「写第一个插件」到拦截器、页面脚本、桌面界面都有:

文档

链接

欢迎来写插件,也欢迎提需求:你们最希望下载器支持哪个站点?有人感兴趣的话,我可以先写一篇完整的插件开发教程。

0 条回复
添加回复
你还需要 登录 后发表回复

登录后可发帖和回复

登录 注册
主题信息
作者: clow
发布: 2026-10-22
点赞: 0
回复: 0