跳转到主要内容

插件 API 完整参考

插件脚本通过全局对象 window.__PLUGIN_API__ 与启动器交互。本文档列出全部可调用方法、签名、权限要求与示例。

全局变量

PLUGIN_API_BASE — 插件文件地址前缀

L2 沙箱内,启动器会自动注入 window.__PLUGIN_API_BASE__,值为当前插件的文件访问基地址:
可用于动态拼包内资源地址(如图标、图片):
内联渲染(无 l2)和悬浮窗(overlay iframe)时该变量不存在,相对路径需通过其他方式解析。

调用方式

权限机制

每个 API 方法对应一个权限。调用时启动器会检查插件 manifest 的 permissions 是否包含该权限,否则报错:

完整权限表

「风险」用于安装详情弹窗的视觉提示:普通=蓝、警告=黄、危险=红。声明权限时遵循最小权限原则,只声明你真正用到的。

方法参考

getSettings — 读取插件配置

读取插件自己的 settings.json。
  • 权限:config:read
  • 返回:Record<string, unknown>(无文件时返回 {})
  • 存储位置:{数据目录}/plugins/{插件id}/settings.json

setSettings — 写入插件配置

按 key 合并写入配置(合并而非覆盖整个文件)。
  • 权限:config:write
  • 参数:(key: string, value: any),value 可为任意 JSON 值
  • 并发写入由后端加锁保护

setCache / getCache — 插件缓存

按 key 读写插件自己的缓存文件,支持 TTL 过期。
  • 权限:cache:access
  • 存储位置:{数据目录}/plugins/{插件id}/cache.json
  • 内部结构 { key: { v: 值, e: 过期时间戳|null } },无需插件关心

callBackend — 调用启动器后端 API

直接请求启动器后端(http://localhost:5000/api/...),绕过 CORS 限制。
  • 权限:network:fetch
  • 参数:(endpoint: string, data?: any)
  • 有 data → POST;无 data → GET
  • data 对象中可传入 _method 字段覆盖 HTTP 方法(如 { _method: 'PUT', State: 'active' }),_method 不会被发送到后端
  • endpoint 以 / 开头(如 /instance),不含 /api 前缀
  • 返回:后端 JSON 响应;非 2xx 抛错
  • 可用端点清单:见启动器后端 OpenAPI(开发模式 /openapi/v1.json),常用如 /instance、/resources/search、/settings 等
L2 沙箱(sandbox="allow-scripts",无 allow-popups)里 window.open(url, '_blank') 会被拦截。要打开外部链接,应通过后端端点:
该端点使用系统默认浏览器打开 http/https 链接(仅接受这两种协议,其余返回 400)。

proxyFetch — CORS 代理请求

经启动器后端转发请求任意外部 URL,绕开浏览器 CORS 限制,且带 SSRF 防护。
  • 权限:network:cors_proxy
  • 请求 ProxyRequest:
  • 响应 ProxyResponse:
  • SSRF 防护:仅 http/https;禁止内网/保留地址(localhost、127.x、10.x、172.16-31.x、192.168.x、169.254.x 等),返回 PROXY_PRIVATE_ADDRESS(400)

proxyFetchStream — 流式代理请求

消费 SSE 流(如 AI 对话逐字输出),逐块回调。
  • 权限:network:cors_proxy
  • 后端自动带 stream: true 转发上游流
  • signal(AbortSignal)用于中断;req.signal 经 postMessage 前自动剥离,改走 __plugin_api_abort 消息
  • ⚠️ 已知限制:proxyFetchStream 返回的 Promise 在流结束时 resolve,但 chunk 回调可能在 resolve 之后仍触发,建议以 onError / 流内 [DONE] 标记判断完成

registerMethod — 注册插件方法

将当前插件的方法注册到全局注册表,供其他插件通过 callPlugin 调用。
  • 权限:config:write
  • 参数:(method: string, fn: Function),fn 返回值为值或 Promise
  • 插件停用时自动注销所有方法
  • 详细见 插件依赖与互调用

callPlugin — 调用其他插件方法

调用目标插件已注册的方法。
  • 权限:network:fetch
  • 参数:(pluginId: string, method: string, ...args)
  • 目标未安装 / 未激活 / 方法未注册 → reject

registerHook — 注册启动器方法 Hook

将当前插件注册为启动器某方法的拦截者,在该方法执行前后注入逻辑(修改参数/返回值、阻止执行、完全替换实现)。
  • 权限:hook:register(危险)
  • 参数:(method: string, handler: (ctx, next) => Promise<void>)
  • handler 为 Koa 式洋葱中间件:next() 前改 ctx.args,next() 后改 ctx.result,ctx.prevent() 阻止默认实现
  • 插件停用时自动注销其全部 hooks
  • 详细见 Hook 系统

callWasm — 调用 WASM 插件导出函数

调用 L3 WASM 插件导出的函数(on_load / on_unload / 自定义导出)。经启动器 Rust 网关(wasmtime)执行。
  • 权限:wasm:execute
  • 参数:(pluginId: string, exportName?: string),exportName 缺省 on_load
  • 返回:{ ok: true, result: ... } 或错误
  • 详细见 WASM 插件

listWasmPlugins — 列出已加载的 WASM 插件

  • 权限:wasm:execute

readText — 读取文本文件(授权制)

读取指定路径的文本文件(UTF-8),支持分段读取。首次访问需用户授权(弹窗确认)。
  • 权限:filesystem:read
  • 参数:(path: string, options?: { start?: number; length?: number })
  • start:起始位置(字符),默认 0
  • length:读取长度(字符),默认读到文件尾
  • 返回:{ path, content: string }
  • 授权机制:首次访问未授权路径返回 403 FS_AUTHORIZATION_REQUIRED,启动器自动弹窗询问「是否允许插件访问该路径」,用户同意后授权(按路径前缀持久化),同插件同路径不再弹窗
  • 文件不存在 → FS_FILE_NOT_FOUND(404)

readBytes — 读取二进制文件(授权制)

读取指定路径的二进制文件,返回 base64,支持分段读取。
  • 权限:filesystem:read
  • 参数:(path: string, options?: { start?: number; length?: number })
  • start:起始位置(字节),默认 0
  • length:读取长度(字节),默认读到文件尾
  • 返回:{ path, contentBase64: string }
  • 授权机制同 readText

writeText — 写入文本文件(授权制)

  • 权限:filesystem:write
  • 参数:(path: string, content: string),UTF-8 写入
  • 授权机制同 readText;自动创建父目录
  • 返回:{ path }

writeBytes — 写入二进制文件(授权制)

  • 权限:filesystem:write
  • 参数:(path: string, bytes: Uint8Array),自动转 base64 传输
  • 授权机制同 readText;自动创建父目录
  • 返回:{ path }

deleteFile — 删除文件(授权制)

  • 权限:filesystem:write
  • 参数:(path: string),仅删除文件;目录请用 deleteDir
  • 授权机制同 readText(破坏性操作同样需授权确认)
  • 文件不存在 → FS_FILE_NOT_FOUND(404)
  • 返回:{ path }

deleteDir — 删除目录(授权制)

  • 权限:filesystem:write
  • 参数:(path: string, options?: { recursive?: boolean })
  • 授权机制同 readText;路径必须在已授权前缀内
  • 请求 { recursive: false } 时只接受空目录,非空返回 FS_DIR_NOT_EMPTY(400)
  • 危险根永远拒绝:盘符根(D:、D:\)与文件系统根返回 FS_REFUSE_ROOT(403)。 授权是按路径前缀覆盖的,误把整个盘授权给插件后一次递归删除就是灾难—— 插件侧仍建议自己做业务层校验(例如拒绝层级过浅的目录)
  • 目录不存在 → FS_DIR_NOT_FOUND(404)
  • 返回:{ path, removed }
典型用途:删除实例/服务端目录。此前后端只能删单个文件,插件想删目录只能 递归 deleteFile 再把空壳 rename 挪走,会在磁盘上留下无法清理的垃圾目录。

execCommand — 执行 shell 命令

执行系统命令,返回退出码与输出。Windows 用 powershell,Linux/macOS 用 /bin/sh。
  • 权限:shell:execute(危险权限,安装时红色提示)
  • 参数:(command: string, timeoutMs?: number),timeoutMs 默认 15000,范围 1000–120000
  • 返回:{ exitCode: number, stdout: string, stderr: string }
  • 超时 → SHELL_TIMEOUT(400),命令被强制终止
  • 命令为空 → SHELL_COMMAND_REQUIRED(400)
⚠️ execCommand 是一次性执行模型:stdout/stderr 全量缓冲到进程结束才返回、无进程句柄、无 stdin。不要用它拉起常驻进程(服务端、守护进程等)——超时会被强制杀掉。这类场景用下面的 process.*。

process.spawn — 启动常驻进程

拉起一个长期运行的外部进程(如 Minecraft 服务端 java -jar server.jar nogui),返回进程句柄。
  • 权限:process:manage(危险权限)
  • 参数:{ command: string, args?: string[], cwd?: string, env?: Record<string, string> }
  • 返回:{ pid: number, startedAtMs: number }
  • stdout/stderr 逐行进入环形缓冲(2000 行),经 process.logs 游标增量拉取
  • 归属隔离:插件只能管理自己 spawn 的进程(其他插件的 pid 一律 404 PROCESS_NOT_FOUND)
  • 每插件同时管理的进程数上限 32(PROCESS_SPAWN_FAILED)
  • 进程退出后句柄保留 10 分钟供收尾日志,之后自动回收

process.stdin — 向进程标准输入写入

  • 权限:process:manage
  • 参数:(pid: number, text: string, opts?: { appendNewline?: boolean })
  • 交互式控制台命令(如服务端 stop / say / whitelist add)由此实现
  • 进程已退出 → PROCESS_STDIN_FAILED

process.kill — 终止进程树

  • 权限:process:manage
  • 终止整个进程树(Windows taskkill /T /F,Unix 逐个 kill 子孙进程)
  • 对已退出进程幂等返回成功

process.alive — 查询进程状态

  • 权限:process:manage
  • alive: false 时 exitCode 为退出码(被信号终止或无法获取时为 null)

process.logs — 游标式增量日志

  • 权限:process:manage
  • 参数:(pid: number, afterSeq?: number),缺省 afterSeq 从缓冲最旧行开始
  • 返回:{ lines: [{ seq, stream, text }], nextSeq, oldestSeq, alive, exitCode }
  • oldestSeq:环形缓冲淘汰后仍在的最旧 seq —— 若你的游标落后超过缓冲容量(2000 行),中间的行已丢失,建议据此提示用户
  • 进程退出后日志仍可拉取(保留期内)

process.list — 列出本插件的全部受管进程

  • 权限:process:manage
  • 典型用途:插件页面重载/启动器重启后,凭此找回仍在运行的进程(配合持久化的 pid)

pickFile — 文件选择对话框

弹出系统文件选择对话框(主窗口 Tauri 原生实现)。沙箱里 <input type="file"> 拿不到绝对路径,window.showOpenFilePicker 不可用 —— 需要绝对路径时必须用此方法。
  • 权限:dialog:open
  • 参数:{ title?, defaultPath?, filters?: [{ name, extensions: string[] }], multiple? } 全部可选
  • 返回:string | null(选中的绝对路径;取消返回 null)

pickDirectory — 目录选择对话框

  • 权限:dialog:open
  • 返回:string | null

extractArchive — 解压本地 zip 到目录(授权制)

  • 权限:filesystem:write
  • 参数:(archivePath: string, destPath: string, opts?: { prefix?: string, stripPrefix?: boolean }),目标目录自动创建
  • prefix 只解压该子目录下的条目;搭配 stripPrefix: true 时剥掉前缀——整合包的 overrides/ 要提到服务器根目录就靠这两个选项(overrides/mods/a.jar → mods/a.jar)
  • 防护:zip-slip(非法路径条目拒绝)+ 条目数 / 单文件 / 总量体积上限(复用整合包同一解压器)
  • 授权机制:压缩包路径需读授权、目标目录需写授权 —— 首次使用弹一次确认、同时授权两个路径前缀
  • 仅支持 zip(rar/7z 暂不支持)

listDir — 列举目录条目(授权制)

  • 权限:filesystem:read
  • 参数:(path: string, opts?: { recursive?: boolean, maxEntries?: number })
  • maxEntries 默认 1000,上限 10000
  • 授权机制同 readText
  • 典型用途:导入后探测服务端 jar、检查 eula.txt / server.properties 是否存在

sendNotification — 系统级通知

发送系统级桌面通知(WebView Notification API,首次调用触发系统授权询问)。插件页面不在前台时也能触达用户;失败/被拒返回 false,可降级为 showToast。
  • 权限:system:notification
  • 参数:{ title: string, body?: string }
  • 返回:boolean(通知已发出为 true;API 不可用 / 未授权 / 被拒为 false)

openUrl — 打开外部链接

用系统默认浏览器打开外部 URL。L2 沙箱内 window.open 会被 sandbox 拦截,请用此方法打开外链。
  • 权限:system:notification
  • 后台走 POST /api/system/open-url,仅接受 http:// / https:// 协议,非法 URL 返回 400

getSystemInfo — 读取系统与启动器信息

读取操作系统、启动器与内存信息,可用于 minLauncherVersion 兼容检查或运行时判断平台。
  • 权限:system:info
  • 后台走 GET /api/systeminfo(与 /api/system/info 等价)
  • GitCommit 为启动器 Git 提交哈希;Memory / AvailableMemory 为字节数
  • DataDir:启动器数据目录(如 %LOCALAPPDATA%/qomicex-launcher)。插件需要默认工作区(存放自己的服务器/存档/下载等数据)时以此定位,如 {DataDir}/Servers
  • 启动器元信息字段:
  • LauncherName:启动器名称(如 Qomicex Launcher)
  • LauncherVersion:启动器版本号(程序集版本,如 0.1.1)
  • VersionType:版本类型,按版本号预发布标签动态判断(alpha / beta / rc / dev,否则 stable)
  • Developers:[{ name, role }] 开发者列表
  • CoreDependencies:[{ name, version, license }] 核心库列表(.NET / Tauri / Rust / Qomicex.Core 等)

listPlugins — 读取已安装插件列表

返回已安装插件列表,可用于依赖检测或商店联动。
  • 权限:plugin:list
  • 后台走 GET /api/plugins/

uploadPlugin — 安装插件

上传 .qplugin 包安装插件。
  • 权限:plugin:install(危险权限)
  • 参数:(fileData: number[], fileName: string),multipart 字段名 plugin
跳转到启动器内部路由。
  • 权限:config:read
  • 注意:应使用启动器内部路由,勿用外部 URL

openPluginSettings — 打开插件设置页

跳转到「设置 → 插件 → 插件设置」并定位到指定插件的设置 tab。适合在插件页面 / 悬浮窗里提供”设置”按钮。
  • 权限:config:read
  • 参数:(targetPluginId?: string),缺省定位到当前插件
  • 目标插件须在 manifest 声明 contributes.settingsPages 才有对应 tab(见 manifest);目标无设置页时仍会打开插件设置区,停留在默认位置
  • 同插件有多个设置页时定位到第一个

showToast — 应用内通知

弹出一条 toast 提示。
  • 权限:ui:toast
  • 参数:(message: string, type?: 'info' | 'error' | 'success'),默认 info

log — 输出插件日志

把日志写入启动器日志,便于在「设置 → 日志」中统一排查。
  • 权限:plugin:log
  • 参数:(message: string, level?: 'debug' | 'info' | 'warn' | 'error'),默认 info

文件拖放事件(file-drop)

把文件拖入启动器窗口时,主窗口广播 file-drop 事件,payload 为拖入文件的绝对路径数组。前端(主界面)可用 @tauri-apps/api/event 监听:
  • 由启动器 Rust WindowEvent::DragDrop::Drop { paths, .. } 触发(dragDropEnabled 开启时)
  • 路径为系统绝对路径,适合”拖文件自动填入路径”等交互
  • 注意:前端原生 drop 事件的 File 对象拿不到 .path(Tauri 拦截系统拖放),必须走该事件
  • L2 沙箱插件如需使用:由主界面前端监听后经 __pluginRegistry.call(pluginId, '方法', [paths]) / callPlugin 转发,插件侧 registerMethod 接收

getThemeColor — 获取当前主题种子色

返回当前启动器主题的种子色 hex 值,供插件根据主题色调整自身 UI。
  • 权限:config:read
  • 返回:string | null(#rrggbb 格式,或 null)
  • 逻辑:优先取外观设置中的静态 hex 色值;若为 Monet 动态模式(background),取当前 --primary CSS 变量转换为 hex

applyThemeOverride — 应用主题覆盖

动态覆盖启动器 UI 的 CSS 变量(颜色主题),实现插件驱动的全局换肤。
  • 权限:config:write
  • 参数:(vars: Record<string, string>) — 键为语义 token(支持点分或横杠命名),值为 H S% L% 格式字符串
  • 安全限制:
  • token 白名单:仅允许 background、foreground、primary、accent、border 等 30 个预定义语义 token
  • HSL 格式校验:值必须严格匹配 ^\d{1,3} \d{1,3}% \d{1,3}%$,拒绝任意 CSS 注入
  • 实现:注入 <style id="qomicex-plugin-theme-override"> 到 document.head,选择器 :root:root 保证高优先级
  • 重复调用会覆盖前一次的 override 样式
  • 插件停用时不会自动清除,需手动调用 clearThemeOverride

clearThemeOverride — 清除主题覆盖

移除由 applyThemeOverride 注入的覆盖样式,恢复到原主题。
  • 权限:config:write
  • 移除 id="qomicex-plugin-theme-override" 的 <style> 元素
  • 建议在插件停用钩子或用户取消自定义主题时调用

overlay.create — 创建悬浮窗

创建一个可拖拽的独立悬浮窗,返回悬浮窗 id。
  • 权限:ui:sub_window
  • 返回:悬浮窗 id(string)
  • 详细见 悬浮窗开发

overlay.show / hide / destroy — 悬浮窗控制

  • 权限:ui:sub_window

overlay.setHtml — 更新悬浮窗内容

  • 权限:ui:sub_window

overlay.setPosition — 移动悬浮窗

  • 权限:ui:sub_window

addMenuItem — 动态注册侧边栏菜单项

运行时动态向侧边栏添加菜单项,无需修改 manifest。
  • 权限:ui:inject_sidebar
  • 参数:PluginMenuItem(同 contributes.menuItems 元素)
  • 插件停用时自动移除动态注册的菜单项

onLauncherExit — 启动器退出事件

注册启动器关窗前的回调(尽力而为)。适合做同步快速收尾:保存状态、提示用户插件拉起的进程的去留等。
  • 无需权限(被动事件)
  • 回调是同步、尽力而为的:启动器不会等待插件完成,关闭流程照常进行 —— 回调里不能发网络请求或做耗时异步操作(不保证执行完)
  • 仅在启动器主窗口正常关闭流程中触发;进程被强杀 / 崩溃不保证触发

getPluginStrings — 读取插件自己的 i18n 包

返回本插件 l10n/ 目录合并后的字符串包(bundle.json 打底、当前 locale 的 bundle.<locale>.json 覆盖)。与 manifest %key% 解析同源,插件运行时 UI 用它渲染自己的文案。
  • 无需权限(读取的是插件自带数据)
  • 需在 manifest 声明 "l10n": "l10n"(见 manifest)

getLocale — 当前启动器语言

  • 无需权限;与 getPluginStrings 的覆盖层一致

图标

推荐:<Glyph> + 单图标子路径(零 IPC、可 tree-shake)

插件页面里的图标不要走桥。图标数据随 @qomicex/plugin-ui 分发,静态导入后同步渲染:
子路径 = 类名去掉 fa-solid fa- 前缀(fa-solid fa-folder-open → @qomicex/plugin-ui/icons/folder-open)。品牌图标加 brand- 前缀 (fa-brands fa-github → @qomicex/plugin-ui/icons/brand-github)。导出名是 PascalCase + Icon。 打包器只保留真正导入的图标(27 个图标实测 0 条冗余),且不产生任何 IPC 往返。

<Icon name> — 图标名来自运行期数据时

因为 name 是运行期字符串,打包器无法静态分析,整张图标表(约 160KB)会作为 独立 chunk 懒加载——只在真的渲染 <Icon> 时才下载。图标名在编译期已知时请用 上面的 <Glyph>。 未登记的类名渲染 null(不留空档)并在开发构建 console.warn。

发现可用图标:listIcons

  • 无需权限
  • 返回内置表 + 当前颜色主题映射 + 本插件 contributes.iconTheme 的全部可用键
  • 做图标选择器或自检时以它为准,不要硬编码清单(内置表随启动器演进)

底层:getIconSvg

  • 无需权限
  • 查找顺序:本插件 contributes.iconTheme → 当前颜色主题映射 → 启动器内置表; source 字段标明命中来源(plugin / theme / builtin)
  • 未命中不再静默返回 null:控制台会给出告警,附 listIcons() 与 contributes.iconTheme 的修复提示
  • 仅当图标名来自运行期数据、又不方便用 <Icon> 时才直接调它

图标名必须在启动器内置表内

fa-solid fa-* / fa-brands fa-* 写错会渲染成空白(历史上是静默的,问题能一路 发到用户侧)。现在两道闸门:
  1. qomicex verify / qomicex lint 扫描源码字面量与 manifest icon 字段, 表外类名报 error 并给出相近的内置图标建议;
  2. <Icon> 运行时未命中会 console.warn。
需要内置表里没有的图标时,用 contributes.iconTheme 声明私有图标(见 manifest — icon 字段规范), 或随包附一个 .svg 用 <img> 渲染。

语言切换事件(plugin:locale-changed)

启动器切换语言时向所有插件沙箱广播;插件收到后重拉 getPluginStrings 并重渲染(与主题跟随对齐):
  • 无需权限(被动事件)

下载与安装

download.addTask — 创建下载任务

创建下载任务,任务自动出现在启动器「下载中心」,进度由 SSE 推送驱动,可取消。
  • 权限:download:manage
  • 返回:{ taskId, status, targetPath }
  • 后端复用 DownloadSessionManager(type "resource"),与 mod 下载同一条线

download.progress — 查询下载进度

  • 权限:download:manage
  • taskId 不存在返回 null

download.list — 列出全部下载会话

  • 权限:download:manage
  • 返回:download.progress 同结构的快照数组

download.cancel — 取消下载

  • 权限:download:manage

download.addBatch — 批量下载(一张卡片)

一次调用下载多个文件,但只占下载中心一张卡片——下载几百个 mod 不会刷屏。底层与安装管线同一套下载器(并发、同目标去重、镜像兜底、.part 原子改名)。
  • 权限:download:manage
  • 返回:{ taskId, batchId, totalFiles }
  • 下载在后台进行,完成状态用 download.batchStatus(batchId) 轮询
  • 镜像兜底自动生效:Modrinth/CF 文件挂 MCIM、Forge/NeoForge maven 挂 ForgeCDN/BMCLAPI、原版库挂 BMCLAPI——插件传官方 URL 即可

download.batchStatus — 批量下载批次状态

  • 权限:download:manage
  • 轮询建议间隔 800ms~1s;failed 时 error 带首个失败原因

download.registerInstall — 在下载中心登记安装任务

仅在前端下载中心登记一个「游戏安装」任务(type 'game'),不创建真实下载。适合插件发起安装后让用户能在下载中心看到进度。
  • 权限:instance:write

插件自报任务(分步进度)

插件自己编排的长流程(解析 → 下载 → 安装 → 写配置)用 task.* 上报:任务进 InstallTracker,自动出现在下载中心,与内置安装任务同一套分步卡片渲染。

task.register — 注册分步任务

  • 权限:download:manage
  • weight 只影响合成总进度的占比(内部按 Σweight 归一化);label 为卡片上的步骤文案(插件自带,无需改启动器 i18n)
  • 同 taskId 已有非终态任务时抛 TASK_BUSY(409)

task.update — 推进步骤/状态

  • 权限:download:manage
  • 所有字段可选,只更新提供的部分;status 传终态(completed/failed/cancelled)会被 400 拒绝——终态必须走 task.complete

task.complete — 任务收尾

  • 权限:download:manage
  • 任务上有 blockedFiles(受限文件数)时成功收尾会转为 awaiting-files 而非 completed(提醒用户补文件)

task.progress — 查询任务进度

  • 权限:download:manage

curseforge.files — CurseForge 元数据代理

把 CurseForge 的 projectID:fileID 批量换成下载直链。API key 始终留在启动器进程内,插件拿不到——这是插件生态里唯一的 CF 查询通道。
  • 权限:network:fetch
  • 单次最多 20000 个文件(CF_TOO_MANY_FILES)

文件系统扩展

readArchiveEntry — 读 zip 内单个条目(授权制)

不解压整包,直接读压缩包里一个条目的文本。模组/整合包的元数据都在包里(fabric.mod.json、META-INF/mods.toml、modrinth.index.json),这是读它们的最快路径。
  • 权限:filesystem:read,且压缩包路径需逐路径授权(与 readText 同一套授权流程,首次弹确认)
  • maxBytes 缺省 4 MiB、上限 32 MiB,超限报 ARCHIVE_ENTRY_TOO_LARGE
  • 仅支持 zip 系(jar/mrpack/litemod 均为 zip 容器)

renamePath — 移动/重命名(授权制)

  • 权限:filesystem:write,源与目标两个路径都需授权(一次确认同时授两端)
  • 支持目录;目标父目录自动创建
  • 没有「移动」时插件只能 readBytes + writeBytes + deleteFile 三倍搬运大文件——把 client-only 模组隔离到别的目录这类操作就该用它

modpack.install — 一键安装整合包

直接安装整合包,走启动器正常整合包安装流程(与前端「整合包」页同一管线):解析来源 → 创建实例 → 下载安装。
  • 权限:instance:write
  • 走 POST /api/modpack/install-direct,复用 ModpackService(ParseFileAsync/ResolveOnlineAsync/InstallAsync)与 InstallTracker
  • 返回:{ instanceId },安装异步进行(进度在下载中心 / SSE 查看)
  • 错误码:MODPACK_NAME_REQUIRED、MODPACK_GAME_DIR_REQUIRED、MODPACK_FILE_NOT_FOUND、MODPACK_SOURCE_REQUIRED、MODPACK_SOURCE_INVALID
  • 需要下载中心显示安装进度时,可配合 download.registerInstall 登记

整合包一键开服(参考实现)

「把整合包装成可运行的 Minecraft 服务端」没有专用端点——启动器只提供通用能力,流程由插件用上面这些 API 编排。官方插件 top.qomicex.server-manager(src/api/setup.js)是完整范例,管线与要点:
  1. 解析包:readArchiveEntry 读包内 modrinth.index.json(Modrinth)或 manifest.json(CurseForge),得到 MC 版本 / 加载器 / 文件清单。服务端侧按 env.server != "unsupported" 过滤(与客户端的 env.client == "required" 不同)。
  2. 装运行时(download.addBatch + process.spawn):
    • vanilla:proxyFetch 拿版本清单 → 下载 server.jar(download.addBatch 自动挂 BMCLAPI 兜底)
    • fabric:fabric meta → 下载 server-launcher jar
    • forge / neoforge:下载官方安装器 → 先预填 libraries/(用 readArchiveEntry 读安装器内嵌 install_profile.json / version.json 里的库清单,经 download.addBatch 并发下载,NeoForge maven 因此拿到 ForgeCDN 兜底)→ 再 process.spawn 执行 java -jar installer --installServer,轮询 process.alive 等退出码。安装器对已存在且 sha1 匹配的文件直接跳过,--mirror 参数对 neoforged.net 无效(实测)
  3. 下载 mods:Modrinth 清单自带直链;CurseForge 经 curseforge.files 换直链,取不到直链(作者关闭分发)的记入 {dir}/.qml-server/blocked-mods.json,进度经 task.update 上报
  4. 释放 overrides:extractArchive(packPath, dir, { prefix: 'overrides', stripPrefix: true })
  5. 隔离 client-only:listDir 扫 mods/*.jar,readArchiveEntry 读 fabric.mod.json 的 environment 或 (neo)forge.mods.toml 的 clientSideOnly,命中的 renamePath 进 {dir}/mods-client-only/(不删,可恢复)。注意:元数据没声明 side 的漏标 mod 无法静态识别,只能靠启动崩溃后手动隔离
  6. 写基础配置:writeText 写 eula.txt(false,同意流程由用户负责)/ server.properties / user_jvm_args.txt;已存在的文件不覆盖(overrides 可能带正式配置)
  7. 写启动清单:{dir}/.qml-server/launch.json(readText 读取需该目录授权):
  • mode=argfile:java @<windowsArgfile 或 unixArgfile>(按平台选);mode=jar:java -jar <jar>
  • 服务端务必带 nogui:GUI 会接管控制台输出且不读 stdin 管道(stop 命令失效)
  • 进度全程经 task.register / task.update / task.complete 上报,下载中心可看分步卡片

错误处理

所有 API 调用失败会 reject,错误信息形如:
常见错误:
  • Permission denied: requires xxx — manifest 权限未包含
  • Backend error: 404 — callBackend 端点不存在
  • Proxy failed: 400 — proxyFetch 参数错误(含 SSRF 拦截)
  • 插件 xxx 未提供方法 yyy(可能未安装或未激活) — callPlugin 目标不可用