插件开发
Motrix 插件是打包成单个 ES2020 module 的 TypeScript 或 JavaScript。它运行在隔离沙箱里:没有 Node.js API,没有 require,不能直接访问文件或网络。与外界的一切交互都要经过你在 manifest 里声明的 capability,而 Motrix 会在安装前把你声明的内容原原本本展示给用户。所以请按最小权限设计——你多申请一项权限,用户的授权界面上就多一行;过宽的 host pattern 只会让你的插件看起来比实际更可疑。
插件能做什么
插件挂在下载的生命周期上。每个 hook 都会收到一个 context 对象,你可以检查它,并在允许的范围内修改它。
| Hook | 运行时机 | 能做什么 |
|---|---|---|
beforeCreate | 下载创建之前 | 检查 uris、headers、saveDir;ctx.update({ uris, filename, connections, headers, proxy }) |
beforeFinalize | 数据下载完、文件尚未定稿 | 用 ctx.update({ filePath }) 重命名 |
afterComplete | 文件已落盘 | 做副作用:发通知、后处理、交接给其他工具 |
onError | 任务失败 | 读取 error.code / error.message,记日志或发通知 |
除了 hook,插件还可以:
- 贡献 command——注册可被其他插件或 host 调用的入口,参数与返回值都由 JSON Schema 校验。
- 携带设置——声明一份 configuration schema,用户直接在 Motrix 界面上该插件的 「设置」 标签页里编辑,你的代码通过
configcapability 读取。
每个 hook context 都带 AbortSignal(ctx.signal)——长耗时操作请尊重它——以及一个 metadata 存储,可在同一任务生命周期内的各个 hook 之间传值。
快速开始
pnpm create motrix-plugin my-plugin # basic-resolver 模板
pnpm create motrix-plugin my-plugin post-action # 模板作为第二个参数
cd my-plugin && pnpm install
| 模板 | 初始内容 |
|---|---|
basic-resolver(默认) | 一个检查并改写下载的 beforeCreate hook(site-resolver category) |
post-action | 一个下载完成后发桌面通知的 afterComplete hook(post-action category) |
脚手架结构:
my-plugin/
├── motrix-plugin.json # manifest——身份、权限、hook
├── src/index.ts # 入口文件
├── locales/ # en-US.json、zh-CN.json(UI 字符串)
├── esbuild.config.mjs # 独立构建配置
├── package.json # scripts: build / pack / dev
└── tsconfig.json
第一个 hook 已经接好了:
import { hooks, log } from 'motrix:plugin-api'
hooks.beforeCreate(async (ctx) => {
log.info('resolving', { uri: ctx.uris[0] })
// rewrite the download before it starts:
// ctx.update({ filename: 'nicer-name.zip' })
return ctx
})
脚手架生成的插件 id 初始为 me.<name>。要设置真实的 publisher 前缀,可以直接编辑 motrix-plugin.json 里的 id,或改用支持 flag 的完整 CLI 来脚手架:
pnpm --package=@motrix/plugin-cli dlx motrix-plugin init my-plugin -t post-action -p acme
开发循环
pnpm dev # 等价于 motrix-plugin dev
dev 会 watch-build src/index.ts(带 inline sourcemap),并启动你本机的 Motrix、直接从工作目录加载插件。改代码,bundle 自动重建;改 motrix-plugin.json 或 locales/,host 会重载插件。Motrix 从标准安装路径自动定位;如果你跑的是别处的构建,用 MOTRIX_BIN=/path/to/motrix 显式指定。
开着 dev 时,你的 log.* 输出会实时进入 Motrix 里该插件的 「日志」 标签页,可按 「日志级别」 过滤。在那里打开 「详细模式」 会在 1 小时内捕获完整 URL 与 header——调试 resolver 时很好用,但它会把真实 URL 写进日志,平时请保持关闭。
发布之前:
pnpm exec motrix-plugin validate # 用官方 schema 校验 manifest
pnpm run pack # 压缩后的 bundle + .moext 包
pnpm exec motrix-plugin lint # 对打包产物做静态检查
Tip
注意是 pnpm run pack 而不是 pnpm pack——裸的 pnpm pack 会调用 pnpm 内置的 tarball 命令,而不是脚手架里的 script。lint 检查的是打包产物,所以要先 pack。
Manifest
motrix-plugin.json 是插件与 host 之间的契约。一个最小的 resolver:
{
"manifestVersion": 1,
"id": "acme.example-resolver",
"name": "Example Resolver",
"version": "0.1.0",
"description": "Rewrites example.com download links",
"categories": ["site-resolver"],
"engines": { "motrix": ">=2.0.0 <3.0.0" },
"main": "dist/plugin.js",
"permissions": ["http"],
"hostPermissions": ["https://example.com/*"],
"activationEvents": ["onTaskType:http"],
"contributes": {
"hooks": { "beforeCreate": { "role": "resolve" } }
},
"l10n": "locales"
}
字段规则,全部由 motrix-plugin validate 强制:
| 字段 | 规则 |
|---|---|
id | <publisher>.<name>,小写 a-z0-9-。publisher 前缀 motrix、verified、official、system 为保留字。 |
version | Semver(1.2.3,允许 prerelease/build 后缀)。 |
categories | 从 site-resolver、post-action、theme、productivity、integration 中选 1–8 个。hook 的 role 与 category 挂钩。 |
engines.motrix | 支持的 Motrix 版本 semver range。engines.ffmpeg 可选。 |
permissions | 你需要的 capability。自动注入的那些(log、i18n、config、lifecycle、commands、app、crypto)不允许列出。最多 32 个。 |
optionalPermissions | 缺了也能用的 capability——用户拒绝时安装照样成功;运行时检查 .available。 |
hostPermissions | 限定 http 能访问哪些地址的 URL match pattern(https://*.example.com/*、<all_urls>)——超出范围的请求会以 plugin.http.host_not_permitted 失败。它同时决定按 URL 触发的 activation 何时生效,并会原样展示在授权界面上。最多 64 个。 |
activationEvents | host 何时加载你(onTaskType:http、onStartup 等)。必填。 |
contributes.hooks | 你实现了哪些生命周期 hook,每个都要带 role。 |
contributes.commands | 你注册的 command,id 形如 <publisher>.<plugin>.<command>,最多 64 个。标记 public: true 的必须声明 argsSchema + resultSchema(受限的 JSON-Schema 子集:不支持 $ref/oneOf,≤ 8 KiB,深度 ≤ 8)。 |
contributes.configuration | 你的设置 schema,同一受限子集。 |
requestedHeapMB | 沙箱堆大小,32(默认)到 64。 |
l10n | locale JSON 文件所在目录。 |
当多个插件实现同一个 hook 时,role 决定执行顺序:resolve → enrich → post-process → audit。其中两个与 category 挂钩——resolve 要求 site-resolver,post-process 要求 post-action。代码里注册但没声明 role 的 hook 按 enrich 执行。(pre-resolve 保留给内置插件。)
UI 字符串放在 locales/<lang>.json,运行时用 i18n.t('key') 读取。pack 会校验 locale 覆盖率——缺 key 在你构建时就失败,而不是留到用户会话里。
Capability 与权限
一切都从 motrix:plugin-api 这个 virtual module import。类型来自 @motrix/plugin-api 包,实现由 host 注入,所以永远不要把它打进 bundle——脚手架的 esbuild 配置已将其标记为 external。
以下 capability 始终可用,且不允许出现在 permissions 里:
| Namespace | 提供什么 |
|---|---|
log | 结构化日志:trace/debug/info/warn/error/fatal(msg, fields?) |
i18n | t(key, params?)、当前 language/dir、语言切换事件 |
config | 读取 contributes.configuration 的值,并可 onChange 订阅 |
lifecycle | onActivate / onDeactivate,做初始化与清理 |
commands | 跨插件的 register(id, handler) / execute(id, args) |
app | Host 的 version、platform、arch、runtime(electron/server)、locale |
crypto | hash、hmac、randomBytes、aes(cbc/gcm) |
以下 capability 需要申请权限。声明之后,使用前先检查 .available:
| Namespace | 权限 | 提供什么 |
|---|---|---|
http | http、http.cookies | request/get/post,响应类型化(text/json/bytes),支持 timeout、range、按需开启的 cookie jar——仅限 http(s),且被限制在你声明的 hostPermissions 之内:超出范围的 URL(包括重定向目标)会抛出 plugin.http.host_not_permitted |
storage | storage | 带版本号的 key-value 存储,compareAndSet 保证并发更新安全 |
fs.task | fs.task.read、fs.task.write | 读取、stat、hash 或重命名当前 hook 正在处理的任务文件 |
fs.storage | fs.storage | 插件私有的 scratch 目录 |
notify | notify | 桌面通知 |
ffmpeg | ffmpeg | probe、transcode、extractAudio、mergeStreams、generateThumbnail,带 progress 流——需要用户系统里装有 FFmpeg,所以请声明为 optional |
用户看到的不是你写的权限字符串,Motrix 会在安装界面为每项权限渲染一行大白话:
| 你声明的 | 用户读到的 |
|---|---|
http | 「读取网页」——“打开此插件支持的网页。” |
http.cookies | 「使用 Cookie」——“用于访问已登录的网站。” |
fs.task.read | 「读取任务文件」——“查看下载任务里的文件。” |
fs.task.write | 「修改任务文件」——“可添加或更新任务文件。” |
fs.storage | 「保存插件文件」——“只存此插件自己的文件。” |
storage | 「保存设置」——“记住此插件的选项。” |
notify | 「发送通知」——“显示完成提醒。” |
ffmpeg | 「使用 FFmpeg」——“处理音频或视频。” |
Warning
<all_urls>、*://*/*、http://*/*、https://*/* 这几个 pattern 都算广泛访问。只要用了其中任意一个,安装对话框就会多出一块红色的 「广泛主机访问」 提示,告诉用户这个插件“可读取并修改任意 URL 的下载”。请改成逐个列出你真正需要的 host;motrix-plugin validate-host-permissions 能帮你检查 pattern 里的常见错误。
可选 capability 的标准写法是优雅降级:
import { notify } from 'motrix:plugin-api'
if (notify.available) {
await notify.show({ title: 'Done', body: ctx.filePath })
}
沙箱规则
- **禁止 top-level 副作用。**在顶层只注册 hook 和 command handler,实际工作放进 handler 里。
lint会把 top-level 的副作用调用判为 error——import 时就开始干活的插件会拖慢每一次 Motrix 启动。 - **没有 Node.js。**没有
fs、net、process、require。上面列出的 capability 就是全部的外界入口。 - **管好堆内存。**你有 32 MB,经
requestedHeapMB最多 64。用流代替整块缓冲:fs.task.openReader和http的 range 请求就是为这个准备的。 - **尊重
ctx.signal。**用户会取消下载;无视 abort signal 的 hook 会把整条流水线扣为人质。 - **目标 ES2020,单个 ESM 文件。**若使用自己的 bundler,记得保持
motrix:plugin-apiexternal。
SDK 的四个包
| 包 | 用途 |
|---|---|
create-motrix-plugin | pnpm create motrix-plugin 脚手架 |
@motrix/plugin-cli | motrix-plugin CLI:init、dev、validate、pack、lint、validate-host-permissions |
@motrix/plugin-api | 面向插件的类型 + motrix:plugin-api virtual-module 声明(在你的插件里是 devDependency) |
@motrix/plugin-manifest-schema | manifest 的 Zod schema——CLI 与 Motrix host 共同校验所依据的唯一事实来源 |
正因为 CLI 和 host 用的是同一份 schema,能通过 motrix-plugin validate 的 manifest,Motrix 就一定接受。
分发插件
motrix-plugin pack 产出 dist/<id>-<version>.moext——一个可复现的 zip,内含 motrix-plugin.json、dist/plugin.js、locale 文件,以及存在时的 icon.png / LICENSE / CHANGELOG.md。它同时强制分发上限:bundle ≤ 1 MiB、archive ≤ 5 MiB。
用户在 Motrix 的 「插件」 页点 「添加插件」 来安装这个文件。同一个输入框接受三种形式,并自动识别你给的是哪一种:
| 形式 | 粘贴什么 |
|---|---|
| GitHub | owner/repo[@tag]——Motrix 自己去解析 release 附件 |
| URL | 指向 .moext 的 https 直链 |
| 本地文件 | 选择或拖入一个 .moext 文件 |
接着 Motrix 会根据你包内的 manifest 生成授权界面:插件名与描述、每项权限一行(可选权限是开关,用户可以不打开)、必要时的 「广泛主机访问」 提示,以及折叠在 「高级详情」 里的 host pattern。点 「安装插件」 完成安装。
想让插件出现在应用内商店的 「可安装的插件」 里,需要向公开的插件 registry 提交条目:向 motrixapp/plugin-registry 发一个 PR,新增 plugins/<your.plugin-id>.json,指向一个 https release 附件并附上它的 sha256 与 size。Motrix 会在解包前校验这个哈希,并且会拒绝 manifest 与 registry 条目不一致的包——所以每发一个新版本,都要再提一次改版本号的 PR。
Important
registry 条目里的权限只是给商店列表看的预览,Motrix 真正授予的权限永远来自插件包内的 manifest。从 registry 直接安装目前只在桌面版可用;web 与服务器版本还在开发中,用户请改用 URL 或本地文件安装。
延伸阅读
- 插件——用户怎么安装、审查、停用和删除插件,以及调试时在哪找每个插件的 「设置」 和 「日志」 标签页。
- 内置插件——官方插件,可以当作现成的 manifest 范例来读。
- motrixapp/plugin-sdk——SDK 仓库与完整 README(含中文版),包含完整的 CLI 与 capability 参考。
- motrixapp/plugin-registry——registry 的 schema、提交规则,以及商店背后的数据。