Motrix WebExtension:让浏览器下载自动走 Motrix
写在前面
用过 Motrix 的都知道,它的下载速度确实比浏览器自带的强不少,尤其是大文件、多线程的场景。
但问题是——每次下载都得手动操作:
- 复制下载链接
- 打开 Motrix
- 粘贴链接
- 开始下载
四步,每次都来一遍,烦。
我就想,能不能做一个浏览器扩展,点击下载的时候直接把链接丢给 Motrix,浏览器这边自动取消,整个过程无感?
于是就有了这个项目。
一、整体思路
原理其实很简单,就三步:
- 监听浏览器的下载事件(
chrome.downloads.onCreated) - 取消浏览器的原生下载(
chrome.downloads.cancel) - 通过 aria2 JSON-RPC 把链接发给 Motrix(
fetchPOST 到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 秒消失

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 静默触发更优雅。
四、踩过的坑
- MV3 的 service worker 会休眠 → 配置不能存变量里,每次都要从
chrome.storage读 chrome.runtime.sendMessage没有接收端会报错 → 必须用.catch(() => {})吞掉chrome.tabs.create({ url: 'motrix://' })不生效 → 自定义协议在tabs.create里有限制,改用 content script 的 iframe- Motrix 密钥错误返回 HTTP 400 而不是 401 → 需要读 response body 才能拿到真实错误信息
- 回退下载会被再次拦截 → 需要临时跳过表防循环
五、功能一览

核心功能
├── 自动拦截浏览器下载 → 发送到 Motrix
├── 智能过滤(协议/扩展名/文件大小)
├── 右键菜单(下载链接/下载图片)
├── 手动输入 URL 下载
└── 测试 RPC 连接
安全与容错
├── RPC 密钥支持
├── 失败自动回退浏览器下载
└── 友好的中文错误提示
体验优化
├── 页面内 Toast 通知
├── 扩展图标 Badge 状态
├── 自动唤起 Motrix 到前台
└── 所有设置持久化(chrome.storage.sync)
六、安装使用
- 克隆仓库:
git clone https://github.com/pansoul1/motrix-webextension.git - 打开
chrome://extensions或edge://extensions - 开启开发者模式 → 加载已解压的扩展
- 打开 Motrix,在偏好设置 → 进阶设置里复制 RPC 授权密钥
- 在扩展弹窗里填入密钥,点测试连接,搞定
建议设置 RPC 密钥,不然任何本地程序都能往你的 Motrix 里塞下载任务。
写在最后
整个扩展代码量不大,核心逻辑就在 background.js 里,但细节挺多——错误处理、循环拦截、协议唤起这些,不踩一遍坑真想不到。
项目地址:https://github.com/pansoul1/motrix-webextension
希望这篇文章对你有帮助。