Motrix WebExtension:让浏览器下载自动走 Motrix

Published on
36 3.0~3.9 min 1350

写在前面

用过 Motrix 的都知道,它的下载速度确实比浏览器自带的强不少,尤其是大文件、多线程的场景。

但问题是——每次下载都得手动操作:

  1. 复制下载链接
  2. 打开 Motrix
  3. 粘贴链接
  4. 开始下载

四步,每次都来一遍,烦。

我就想,能不能做一个浏览器扩展,点击下载的时候直接把链接丢给 Motrix,浏览器这边自动取消,整个过程无感?

于是就有了这个项目。

一、整体思路

原理其实很简单,就三步:

  1. 监听浏览器的下载事件(chrome.downloads.onCreated
  2. 取消浏览器的原生下载(chrome.downloads.cancel
  3. 通过 aria2 JSON-RPC 把链接发给 Motrix(fetch POST 到 localhost:16800/jsonrpc

Motrix 底层用的就是 aria2,默认开了 RPC 服务在 16800 端口,我们直接对接这个接口就行。

整体架构

浏览器下载事件
    ↓
扩展拦截(background.js)
    ↓
取消浏览器下载
    ↓
构造 aria2 JSON-RPC 请求
    ↓
POST → localhost:16800/jsonrpc
    ↓
Motrix 接管下载

二、项目结构

motrix-webextension/
├── manifest.json      # MV3 扩展配置
├── background.js      # 核心:下载拦截 + RPC 通信
├── content.js         # 页面内 Toast 通知 + 唤起 Motrix
├── popup.html         # 弹窗界面
├── popup.css          # 样式
├── popup.js           # 弹窗交互
└── icons/             # 图标

三、核心实现

3.1 拦截浏览器下载

chrome.downloads.onCreated 监听所有下载事件,拿到 URL 之后先做过滤:

  • blob:data: 开头的跳过(aria2 处理不了)
  • 只拦截 http://https://
  • 支持按文件扩展名过滤(比如只拦截 .zip .exe .dmg
  • 支持最小文件大小过滤
chrome.downloads.onCreated.addListener(async (downloadItem) => {
  const config = await getConfig();
  if (!config.enabled) return;

  const url = downloadItem.url;

  // 跳过回退下载(防止循环拦截)
  if (shouldSkipUrl(url)) return;

  const filename = downloadItem.filename
    ? downloadItem.filename.split(/[/\\]/).pop()
    : extractFilename(url);

  if (!shouldIntercept(url, filename, config)) return;

  // 取消浏览器下载
  await chrome.downloads.cancel(downloadItem.id);
  chrome.downloads.erase({ id: downloadItem.id });

  // 发给 Motrix
  try {
    await sendToAria2(url, filename, downloadItem.referrer || '');
  } catch (err) {
    // 失败了?恢复浏览器下载
    if (config.fallbackToBrowser) {
      await resumeBrowserDownload(url, filename);
    }
  }
});

3.2 发送到 aria2 RPC

aria2 的 JSON-RPC 格式很简单:

// 请求体
{
  "jsonrpc": "2.0",
  "id": "1234567890",
  "method": "aria2.addUri",
  "params": [
    "token:你的密钥",   // 有密钥的话
    ["https://example.com/file.zip"],
    { "out": "file.zip" }
  ]
}

直接 fetch POST 过去就行:

const response = await fetch('http://localhost:16800/jsonrpc', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(rpcBody)
});

3.3 失败回退

这是一个很重要的细节——如果 Motrix 没开,或者密钥不对,下载就会丢失(浏览器那边已经取消了)。

所以我做了一个回退机制:发送失败时自动用 chrome.downloads.download() 重新发起浏览器下载。

但这里有个坑:重新发起的下载会再次被扩展拦截,形成死循环。

解决方法是维护一个临时的 URL 跳过表:

const skipUrlUntil = new Map();

// 回退下载前,标记这个 URL 15 秒内不拦截
function resumeBrowserDownload(url, filename) {
  skipUrlUntil.set(url, Date.now() + 15000);
  return chrome.downloads.download({ url, filename });
}

// 拦截时检查是否在跳过表里
function shouldSkipUrl(url) {
  const until = skipUrlUntil.get(url);
  if (!until) return false;
  if (Date.now() > until) {
    skipUrlUntil.delete(url);
    return false;
  }
  skipUrlUntil.delete(url);
  return true;  // 跳过这次拦截
}

3.4 页面内 Toast 通知

系统通知太重了,而且容易被忽略。所以我用 content script 在当前网页上直接弹 Toast:

  • 成功:绿色,4 秒消失
  • 回退:黄色,8 秒消失
  • 失败:红色,8 秒消失
报错.png

Toast 放在页面左下角,避免被浏览器右上角的下载框遮挡。

3.5 唤起 Motrix 到前台

下载成功后,通过 motrix:// 自定义协议唤起 Motrix 窗口:

// content script 里用隐藏 iframe 触发协议
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = 'motrix://';
document.body.appendChild(iframe);
setTimeout(() => iframe.remove(), 1000);

为什么不用 window.open?因为会弹新窗口。用 iframe 静默触发更优雅。

四、踩过的坑

  1. MV3 的 service worker 会休眠 → 配置不能存变量里,每次都要从 chrome.storage
  2. chrome.runtime.sendMessage 没有接收端会报错 → 必须用 .catch(() => {}) 吞掉
  3. chrome.tabs.create({ url: 'motrix://' }) 不生效 → 自定义协议在 tabs.create 里有限制,改用 content script 的 iframe
  4. Motrix 密钥错误返回 HTTP 400 而不是 401 → 需要读 response body 才能拿到真实错误信息
  5. 回退下载会被再次拦截 → 需要临时跳过表防循环

五、功能一览

预览.png
核心功能
├── 自动拦截浏览器下载 → 发送到 Motrix
├── 智能过滤(协议/扩展名/文件大小)
├── 右键菜单(下载链接/下载图片)
├── 手动输入 URL 下载
└── 测试 RPC 连接

安全与容错
├── RPC 密钥支持
├── 失败自动回退浏览器下载
└── 友好的中文错误提示

体验优化
├── 页面内 Toast 通知
├── 扩展图标 Badge 状态
├── 自动唤起 Motrix 到前台
└── 所有设置持久化(chrome.storage.sync)

六、安装使用

  1. 克隆仓库:git clone https://github.com/pansoul1/motrix-webextension.git
  2. 打开 chrome://extensionsedge://extensions
  3. 开启开发者模式 → 加载已解压的扩展
  4. 打开 Motrix,在偏好设置 → 进阶设置里复制 RPC 授权密钥
  5. 在扩展弹窗里填入密钥,点测试连接,搞定

建议设置 RPC 密钥,不然任何本地程序都能往你的 Motrix 里塞下载任务。

写在最后

整个扩展代码量不大,核心逻辑就在 background.js 里,但细节挺多——错误处理、循环拦截、协议唤起这些,不踩一遍坑真想不到。

项目地址:https://github.com/pansoul1/motrix-webextension

希望这篇文章对你有帮助。


Prev Post 52Pokemon 逆向分析:多账户订阅聚合系统开发记录