// Runtime API

运行时 API

插件运行在 QuickJS 里,宿主在脚本之前注入一小组全局对象。这一页列出能用的全部东西,以及每一项的限制。

全局对象一览

名称说明
cerumusic宿主 API:网络、存储、配置、工具函数。本页的主角。
consolelog、info、debug、warn、error 写入插件日志;group 系列是空操作。
fetch基于 cerumusic.request 的最小实现:返回 { ok, status, headers, json(), text() }。
setTimeout / clearTimeout可用,同时最多 128 个待触发的定时器。setInterval 调用会直接抛错。
atob / btoaBase64 编解码。btoa 只接受 Latin-1 字符。
Buffer.from精简版:Buffer.from(string | array, 'base64' | 'hex'),返回带 toString('hex' | 'base64') 的 Uint8Array。
window、global、globalThis都指向全局对象。
module、exportsCommonJS 导出入口。
require不可用,调用即抛错「当前宿主不支持 require」。

标准的 ECMAScript 内建对象(Promise、JSON、Math、Date、Map、RegExp、encodeURIComponent 等)由 QuickJS 提供。没有 DOM、XMLHttpRequest、localStorage、文件与进程接口。

cerumusic.request

const res = await cerumusic.request(url, options);
// res = { statusCode, headers, body }

发起一次 HTTP(S) 请求,返回 Promise。也支持回调写法:cerumusic.request(url, options, (err, res) => {})。

options

字段说明
method默认 GET。
headers对象,值必须是字符串,最多 64 个。
body字符串;传对象时会被序列化为 JSON 字符串,但不会自动设置 Content-Type,请自己加。最大 1 MiB。
timeout毫秒,默认并且最大为 15000。同时受单次调用 30 秒总时限约束。
responseType设为 'base64' 时,body 是响应字节的 Base64 字符串(用于二进制内容)。

返回值

字段说明
statusCodeHTTP 状态码。非 2xx 不会抛错,请自己检查。
headers响应头,键为小写。
body响应体。如果内容是合法的 JSON,已被解析成对象 / 数组 / 数字,否则是字符串。
body 可能已经被解析过 因为 body 会被自动 JSON.parse,一个内容恰好是 123 或 "abc" 的响应会变成数字或字符串。写一个小工具来统一处理,内置插件里都有类似的写法:
function json(res) {
  let body = res.body;
  if (typeof body === 'string') {
    try { body = JSON.parse(body); } catch (_) { throw new Error('服务器返回的不是 JSON'); }
  }
  return body;
}

地址与失败

cerumusic.requestAll

const replies = await cerumusic.requestAll([
  { url: urlA, options: {} },
  { url: urlB, options: { headers: { Accept: 'application/json' } } },
]);
for (const r of replies) {
  if (r.error) { /* 这一个失败了 */ continue; }
  const { statusCode, body } = r.response;
}

并发发出多个请求(同时最多 8 个),结果按输入顺序返回,每一项是 { response } 或 { error },Promise 本身不会因单个请求失败而 reject。一次最多 24 项,并且每一项都计入这次调用的请求次数上限。适合「先取列表,再并发取每一项的详情」这类场景,比串行 await 快得多。

cerumusic.storage

cerumusic.storage.set('session', { token, expires: Date.now() + 3600e3 });
const s = cerumusic.storage.get('session');   // 没有时是 undefined
cerumusic.storage.remove('session');

每个插件私有的持久化键值存储,同步调用。典型用途是缓存登录会话。

cerumusic.config

用户在「配置」表单里填的值,一个普通对象,键就是 config 里声明的 key。没有声明表单时是 {}。始终防御性地读取:

const c = cerumusic.config || {};
const baseUrl = String(c.baseUrl || '').trim();
if (!baseUrl) throw new Error('请先在插件“配置”中填写服务器地址');

用户可能还没填,也可能填了带空格或尾部斜杠的值。校验并规范化它们是你的责任。

cerumusic.utils

crypto

函数说明
md5(text)返回 32 位小写十六进制。Subsonic 令牌认证用它。
sha1(text)返回 40 位小写十六进制。
aes128EcbHex(text, key)AES-128-ECB、PKCS7,key 是 16 个字符的文本,返回大写十六进制。
aes128EcbBase64(op, data, keyHex)AES-128-ECB、PKCS7。op 为 'encrypt'(data 是文本,返回 Base64)或 'decrypt'(data 是 Base64,返回文本);keyHex 是 32 位十六进制。输入非法时抛错。
aesEncrypt、rsaEncrypt、randomBytes未实现,调用会抛错。需要随机数请用 Math.random()。

buffer

cerumusic.utils.buffer.from(data, encoding) 与 bufToString(buffer, encoding),与全局 Buffer.from 同源,encoding 可为 'base64' 或 'hex'。

lyric

函数说明
validTtml(text)判断一段文本是不是可解析且至少包含一行有内容的 TTML(不超过 2 MB)。在返回 ttml 之前用它自检。

环境信息

字段值
cerumusic.env'browser'
cerumusic.platform桌面端为 'desktop',移动端为 'mobile'
cerumusic.version当前为 '1.0.3'。这是兼容澜音插件接口的版本号,不是应用版本

zlib / encoding

通用的字节工具,给需要自行解码来源专有歌词或数据格式的插件用。宿主不含任何来源专用的解码,XOR 密钥、分段格式与解析都写在你的脚本里。

函数说明
zlib.inflate(data)解压一段 zlib 数据。data 是 Uint8Array 或 base64 字符串,返回 Uint8Array,解压后最多 8 MiB,数据无效时抛错。
encoding.decode(data, label)按字符集 label('utf-8'、'gb18030'、'big5' 等 WHATWG 标签)把字节解码成字符串;非法字节变成 U+FFFD,未知标签抛错。QuickJS 没有 TextDecoder,GB18030 等多字节编码只能由宿主解。

wasm

QuickJS 没有 WebAssembly,需要运行 WebAssembly 模块的列表插件(例如音频指纹算法)通过宿主提供的受限桥执行。宿主不知道模块做什么:它只实例化你给出的字节,按你的要求调用导出函数或表项,并把模块对导入函数的每次调用交给你脚本里的处理函数回答。仅列表插件可用。

const inst = cerumusic.utils.wasm.instantiate(base64OrBytes, {
  a: { // 导入的模块名 → 函数名 → 处理函数
    b(x, y) { /* this 就是 inst;返回数字,不返回则视为 0 */ return 0; },
  },
}, { fuel: 600000000, maxMemory: 64 * 1024 * 1024 });
try {
  inst.call('exportName', 1, 2);          // 调用导出函数,返回第一个结果(数字)
  inst.callIndirect('tableName', 3, 4);   // 调用导出表的第 3 项,其余是参数
  const bytes = inst.read('memory', ptr, length);  // Uint8Array
  inst.write('memory', ptr, bytes);       // Uint8Array 或 base64 字符串
} finally {
  inst.close();
}

限制一览

所有限制都是为了保证一个失控的脚本不会拖垮应用。超过任何一项都会让这次调用失败。

项目列表插件音源插件
单次调用总时限30 秒(超时后脚本被中断)
单次调用的请求数2416
单个请求的超时15 秒,且不超过剩余的总时限
响应体大小16 MiB4 MiB
请求体大小1 MiB
请求头数量64
重定向最多 5 次
内存96 MiB32 MiB
栈512 KiB
脚本体积1 MiB
定时器同时 128 个,无 setInterval
存储256 KiB,键 ≤128 字符无存储
日志保留最近 500 行,每行最多 2000 个字符
单页结果每页最多 500 项—
在 24 次请求内做完一次调用 列表插件常见的坑是为每首歌各发一次请求。能一次取回的就一次取回;必须逐个取时,用 requestAll 并发,并且按需分页——歌手页的 singerSongs 一次只展开几张专辑(内置的 Navidrome 插件每页只取 10 张)而不是整个曲库。