# JavaScript API
[English](js.en.md) · [漫画源编写指南](comic_source.zh.md) · [本地调试](../development/source_debugging.zh.md) · [README](../../README.md#漫画源开发)
本文是 VeneraNext 扩展运行时参考,函数名与大小写以 [assets/init.js](../../assets/init.js) 为准;宿主实现见 [js_engine.dart](../../lib/foundation/js_engine.dart)。接口签名中的 `?` 表示可选参数或值,代码示例使用普通 JavaScript。
## 运行环境
源脚本在 QuickJS 中执行,具有标准 JavaScript 语言能力,以及本文列出的应用 API。它不是浏览器页面或 Node.js:没有可依赖的 DOM 全局对象、模块加载器、npm 包或完整 Web API。`fetch`、定时器等是应用提供的有限封装。
数据应使用普通对象、数组、字符串、数字和布尔值。二进制接口采用 `ArrayBuffer`;操作字节时可构造 `Uint8Array`,返回给宿主时使用完整 buffer,注意切片视图的 offset/length。UI API 需要活动的图形界面,后台加载和无头模式不应依赖用户交互。
## Network
所有网络请求函数都返回 Promise。传输错误会抛出异常;HTTP 4xx/5xx 通常仍返回响应,扩展必须自己检查 `status`。
| 调用 | 返回 |
|---|---|
| `Network.get(url, headers?, extra?)` | `{status, headers, body: string}` |
| `Network.delete(url, headers?, extra?)` | 同上 |
| `Network.post(url, headers?, data?, extra?)` | 同上 |
| `Network.put(url, headers?, data?, extra?)` | 同上 |
| `Network.patch(url, headers?, data?, extra?)` | 同上 |
| `Network.sendRequest(method, url, headers?, data?, extra?)` | 同上 |
| `Network.fetchBytes(method, url, headers?, data?, extra?)` | `{status, headers, body: ArrayBuffer}` |
`headers` 为普通对象,响应头为字符串值。`data` 取决于接口,可为文本、字节或宿主支持的请求体;提交 JSON 时显式 `JSON.stringify` 并设置 Content-Type。`extra` 是传给宿主拦截器的选项,不是通用浏览器 RequestInit,也不是任意 Dio 超时配置。
```javascript
(async () => {
const response = await Network.get("https://example.invalid/api/comics", {
"Accept": "application/json"
});
if (response.status !== 200) {
throw new Error("HTTP " + response.status);
}
return JSON.parse(response.body);
})()
```
读取接口可利用应用的 Cookie、代理和缓存基础能力。`headers["cache-time"] = "no"` 可要求跳过短期网络缓存;该设置不是阅读器图片磁盘缓存的清理指令。`headers["prevent-parallel"] = "true"` 会串行处理相同请求路径,应只在服务确有需要时使用。
### Cookie
| 调用 | 说明 |
|---|---|
| `new Cookie({name, value, domain?})` | 构造 Cookie 数据 |
| `Network.setCookies(url, cookies)` | 为 URL 保存 Cookie 数组 |
| `Network.getCookies(url)` | 返回当前 URL 的 Cookie 数组;当前宿主为同步返回 |
| `Network.deleteCookies(url)` | 删除对应 URL 的 Cookie |
Cookie 与登录状态由不同 API 管理;自行设置 Cookie 不等同于完成源的账号登录流程。
### fetch
`fetch(url, {method?, headers?, body?})` 返回 Promise,可读取 `ok`、`status`、`statusText`、普通对象 `headers`,以及异步方法 `text()`、`json()`、`arrayBuffer()`。
它只包装了这些能力,不是完整浏览器 fetch:没有标准 `Headers` 实例、流式 Response 或 `AbortSignal` 契约。不要照搬依赖这些能力的浏览器代码。
## HTML 解析
`new HtmlDocument(htmlString)` 解析 HTML 字符串,不执行页面脚本。
| 对象 | 方法或属性 |
|---|---|
| `HtmlDocument` | `querySelector(selector)`、`querySelectorAll(selector)`、`getElementById(id)`、`dispose()` |
| `HtmlElement` 查询 | `querySelector(selector)`、`querySelectorAll(selector)` |
| `HtmlElement` 文本与属性 | `text`、`innerHTML`、`attributes`、`classNames`、`id`、`localName` |
| `HtmlElement` 导航 | `children`、`nodes`、`parent`、`previousElementSibling`、`nextElementSibling` |
| `HtmlNode` | `text`、`type`、`toElement()` |
单节点查询、父节点/相邻节点、`toElement()` 可能返回 `null`;多节点查询返回数组。属性通过 `element.attributes["href"]` 读取,没有浏览器 DOM 的 `getAttribute`。注意是 `innerHTML`、`previousElementSibling`、`nextElementSibling`;`getElementById` 只在文档对象上。
```javascript
(() => {
const document = new HtmlDocument('Demo');
try {
return document.querySelectorAll("a.comic").map(element => ({
title: element.text,
href: element.attributes["href"]
}));
} finally {
document.dispose();
}
})()
```
先复制所需的字符串和数据再 `dispose()`;释放后不要保留节点继续查询。应用只保留有限数量的文档,及时释放可避免旧文档被回收。
## 源数据与应用信息
以下为源实例方法:
| API | 说明 |
|---|---|
| `this.loadData(key)` | 读取该源持久化数据 |
| `this.saveData(key, value)` | 请求保存可序列化数据;不是可等待的落盘完成承诺 |
| `this.deleteData(key)` | 删除该源数据项 |
| `this.loadSetting(key)` | 读取源设置 |
| `this.isLogged` | 应用记录的源登录状态 |
| `this.translate(text)` | 按当前 locale 查该源的翻译词典,找不到时返回原文 |
| `ComicSource.sources[key]` | 调试时访问已安装源实例;应用管理该注册表 |
`setting` 是保留的数据键,不能通过 `saveData` 写入。字段与设置定义见[漫画源编写指南](comic_source.zh.md#6-设置与翻译)。
源数据访问绑定具体运行时实例。替换、卸载或重载后,保留的旧实例及其异步回调不能访问同 `key` 的新源;请捕获本次操作的实例,不要在等待后重新按 `key` 取得另一个实例。解析构造字段时可以读取旧运行时的数据快照;首次装载尚无旧运行时时返回空值,持久化数据随后装载。构造期不能保存或删除数据,应在 `init()` 或注册后的回调中写入。
普通源脚本中的 `sendMessage` 已绑定该脚本实例,旧脚本使用它调用源数据协议仍会校验身份。全局调试器或 `globalThis.sendMessage` 发出的无实例数据消息会被拒绝;调试时请调用 `ComicSource.sources[key].loadData(...)` 等实例方法。HTTP、HTML 等其他消息协议不受此限制。已交给图片任务的原生回调由原任务释放,可继续处理纯图片结果,但不能通过旧实例读取或改写新源数据。
| 全局 API | 返回 |
|---|---|
| `APP.version` | 应用版本字符串 |
| `APP.locale` | 当前宿主的语言/地区字符串,简繁中文为 `zh_CN`、`zh_TW` |
| `APP.platform` | `android`、`ios`、`windows`、`macos`、`linux` |
| `setClipboard(text)` | `Promise` |
| `getClipboard()` | `Promise` |
当前宿主的无地区语言可能返回如 `en_null`;`this.translate` 按完整 locale 查找,而应用原生源标签翻译另有语言回退。需要多语言 UI 文案时请在目标语言实测。
## UI
| API | 返回与用法 |
|---|---|
| `UI.showMessage(message)` | 显示提示 |
| `UI.showDialog(title, content, actions)` | 显示对话框;当前 JS 包装不返回可等待的关闭 Promise |
| `UI.launchUrl(url)` | 打开外部 URL |
| `UI.showLoading(onCancel?)` | 返回加载框 ID;未提供回调时不能由用户取消 |
| `UI.cancelLoading(id)` | 关闭相应加载框 |
| `UI.showInputDialog(title, validator?, image?)` | `Promise`,取消返回 null |
| `UI.showSelectDialog(title, options, initialIndex?)` | `Promise`,索引从 0 开始 |
`actions` 为 `[{text, callback, style}]`,样式为 `text`、`filled` 或 `danger`;回调可返回 Promise。`validator` 是返回错误文本或 null 的函数,与源设置中正则字符串 validator 不同。`image` 可为图片 URL 或 ArrayBuffer。UI 字符串不自动翻译。
## Convert
以下函数为同步调用。`bytes` 和二进制密钥表示 ArrayBuffer,转换失败可能返回 null,应在使用结果前检查。
| API | 返回或用途 |
|---|---|
| `Convert.encodeUtf8(text)` / `decodeUtf8(bytes)` | UTF-8 字节 / 字符串 |
| `Convert.encodeGbk(text)` / `decodeGbk(bytes)` | GBK 字节 / 字符串 |
| `Convert.encodeBase64(bytes)` / `decodeBase64(text)` | Base64 字符串 / 字节 |
| `Convert.hexEncode(bytes)` | 十六进制字符串 |
| `Convert.md5(bytes)`、`sha1(bytes)`、`sha256(bytes)`、`sha512(bytes)` | 摘要字节;方法均在 Convert 上 |
| `Convert.hmac(key, bytes, hash)` | HMAC 字节,hash 使用 `md5/sha1/sha256/sha512` |
| `Convert.hmacString(key, bytes, hash)` | HMAC 十六进制字符串 |
| `Convert.encryptAesEcb(bytes, key)` / `decryptAesEcb(bytes, key)` | AES ECB |
| `Convert.encryptAesCbc(bytes, key, iv)` / `decryptAesCbc(bytes, key, iv)` | AES CBC |
| `Convert.encryptAesCfb(bytes, key, iv, blockSize)` / `decryptAesCfb(bytes, key, iv, blockSize)` | AES CFB |
| `Convert.encryptAesOfb(bytes, key, blockSize)` / `decryptAesOfb(bytes, key, blockSize)` | 当前 OFB 接口没有独立 iv 参数 |
| `Convert.decryptRsa(bytes, key)` | RSA PKCS#1 解密;key 是 Base64 编码的 PKCS#8 私钥 DER 字符串,不是带 PEM 头尾的文本 |
AES 接口不自动补齐/去除 padding;按服务协议准备块大小、IV 和填充。当前 OFB 包装没有独立的 IV 参数,也未向底层传入 IV;使用前应验证与目标协议的兼容性。
```javascript
Convert.hexEncode(Convert.sha256(Convert.encodeUtf8("example")))
```
## 图片处理
`ImageLoadingConfig` 推荐直接使用普通对象,不要求调用构造函数:
| 字段 | 用途 |
|---|---|
| `url?: string` | 实际请求地址,缺省使用 image key |
| `method?: string`、`data?`、`headers?: object` | 请求方法(默认 GET)、请求体、请求头 |
| `onResponse(bytes)` | 同步或异步返回处理后的图片字节;正文、缩略图均支持 |
| `modifyImage?: string` | 定义 `function modifyImage(image)` 的脚本字符串,返回 Image;仅正文 |
| `onLoadFailed()` | 同步或异步返回新配置,用于有限重试;仅正文 |
图片解码前可用 `onResponse` 做字节转换;解码后的图像重排使用 `modifyImage`。它在独立引擎运行,不能捕获源实例或主引擎闭包。`Image` API 只应在这个图片处理上下文使用。
| Image API | 功能 |
|---|---|
| `image.width`、`image.height` | 尺寸 |
| `image.copyRange(x, y, width, height)` | 复制矩形区域,得到新 Image |
| `image.copyAndRotate90()` | 复制并旋转 90 度 |
| `image.fillImageAt(x, y, other)` | 将另一张图写入当前位置 |
| `image.fillImageRangeAt(x, y, other, srcX, srcY, width, height)` | 复制另一张图的指定区域 |
| `Image.empty(width, height)` | 创建空白图片 |
坐标和尺寸需落在图片边界内。回调不应再进行无关网络请求或 UI 操作;一般阅读模式调整交由阅读器完成。
## 日志、计时与计算
| API | 行为 |
|---|---|
| `log(level, title, content)` | 写应用日志;级别 `info`、`warning`、`error` |
| `console.log(value)`、`console.warn(value)`、`console.error(value)` | 单参数包装,不是完整浏览器 console |
| `createUuid()` | 每次生成新的基于时间的 UUID;需复用时自行保存 |
| `randomInt(min, max)`、`randomDouble(min, max)` | 随机数工具,不用于安全令牌 |
| `setTimeout(callback, delayMs)` | 简单延迟,不返回浏览器定时器 ID,也没有配套 clearTimeout |
| `setInterval(callback, delayMs)` | 返回应用 timer 对象,通过 `timer.cancel()` 停止 |
| `compute(functionCode, ...args)` | Promise;函数代码字符串在后台 JS 引擎池运行,参数分别传入 |
```javascript
compute("(a, b) => a + b", 2, 3)
```
`compute` 应执行返回可传输数据的同步计算,不能传函数对象、访问主引擎闭包或假定源实例存在。不应以它启动长期异步网络任务。
加载接口可能被应用有限重试;不要在只读加载中提交远端写操作。超时不会强制中断同步死循环,详情见[本地调试的取消边界](../development/source_debugging.zh.md#追更调度与取消边界)。
## 业务数据类型
`Comic`、`ComicDetails`、`Comment`、章节结构、分页包装和跳转目标的字段见[漫画源编写指南](comic_source.zh.md#2-数据与章节规则)。特别注意:
- 详情必须有对象形状的 `tags`(没有标签时用 `{}`);列表 tags 使用数组。
- `loadEp` 返回 `{images: string[]}`。
- `onThumbnailLoad` 同步返回配置,而 `onImageLoad` 可异步。
- `search.loadNext` 返回 `next`;收藏的 `addOrDelFavorite` 当前接收三个参数。