@nsnanocat/util
v2.5.17
Published
Pure JS's util module for well-known iOS network tools
Maintainers
Readme
@nsnanocat/util
用于统一 Quantumult X / Loon / Shadowrocket / Worker / Node.js / Egern / Surge / Stash 脚本接口的通用工具库。
核心目标:
- 统一不同平台的 HTTP、通知、持久化、结束脚本等调用方式。
- 在一个脚本里尽量少写平台分支。
- 提供一组可直接复用的 polyfill(
fetch/Storage/Console/Lodash/qs)。
目录
安装与导入
发布源:
- npm(推荐):https://www.npmjs.com/package/@nsnanocat/util
- GitHub Packages(同步发布):https://github.com/NSNanoCat/util/pkgs/npm/util
如果你不确定该选哪个,直接用 npm 源即可。 如果你从 GitHub Packages 安装,需要先配置 GitHub 认证(PAT Token)。
1) 使用 npm 源(推荐,最省事)
# 首次安装:拉取并安装这个包
npm i @nsnanocat/util
# 更新到最新版本:升级已安装的 util
npm i @nsnanocat/util@latest
# 你也可以使用 update(效果类似)
# npm update @nsnanocat/util2) 使用 GitHub Packages 源(同步源,需要 GitHub 鉴权)
# 把 @nsnanocat 作用域的包下载源切到 GitHub Packages
npm config set @nsnanocat:registry https://npm.pkg.github.com
# 配置 GitHub Token(用于下载 GitHub Packages)
# 建议把 YOUR_GITHUB_PAT 换成你的真实 Token,再执行
# echo "//npm.pkg.github.com/:_authToken=YOUR_GITHUB_PAT" >> ~/.npmrc
# 首次安装:从 GitHub Packages 安装 util
npm i @nsnanocat/util
# 更新到最新版本:从 GitHub Packages 拉取最新 util
npm i @nsnanocat/util@latestimport {
$app, // 当前平台名(如 "Surge" / "Loon" / "Quantumult X" / "Worker" / "Node.js")
$argument, // 已标准化的模块参数对象(导入包时自动处理字符串 -> 对象)
done, // 统一结束脚本函数(内部自动适配各平台 $done 差异)
fetch, // 统一 HTTP 请求函数(内部自动适配 $httpClient / $task / fetch)
notification, // 统一通知函数(内部自动适配 $notify / $notification.post)
time, // 时间格式化工具
wait, // 延时等待工具(Promise)
Console, // 统一日志工具(支持 logLevel)
Lodash as _, // Lodash 建议按官方示例惯例使用 `_` 作为工具对象别名
qs, // 查询字符串工具(parse / stringify)
Storage, // 统一持久化存储接口(适配 $prefs / $persistentStore / 内存 / 文件)
} from "@nsnanocat/util";导出清单
包主入口(index.js)已导出
lib/app.mjslib/argument.mjs($argument参数标准化模块,导入时自动执行)lib/done.mjslib/notification.mjslib/time.mjslib/wait.mjspolyfill/Console.mjspolyfill/fetch.mjspolyfill/Lodash.mjspolyfill/qs.mjspolyfill/StatusTexts.mjspolyfill/Storage.mjs
仓库中存在但未从主入口导出
lib/environment.mjslib/runScript.mjsgetStorage.mjs(薯条项目自用,仅当你的存储结构与薯条项目一致时再使用;请通过子路径@nsnanocat/util/getStorage.mjs导入)
模块依赖关系
说明:
- 下表只描述“模块之间”的依赖关系、调用到的函数/常量、以及依赖原因。
- 你在业务脚本中通常只需要调用对外 API;底层跨平台差异已在这些依赖链里处理。
| 模块 | 依赖模块 | 使用的函数/常量 | 为什么依赖 |
| --- | --- | --- | --- |
| lib/app.mjs | 无 | 无 | 核心平台识别源头,供其他差异模块分流 |
| lib/environment.mjs | lib/app.mjs | $app | 按平台生成统一 $environment(尤其补齐 app 字段) |
| lib/argument.mjs | polyfill/Console.mjs, polyfill/qs.mjs | Console.debug, Console.logLevel, qs.parse | 统一 $argument 结构,并委托 qs.parse 处理字符串/对象/空值输入 |
| lib/done.mjs | lib/app.mjs, polyfill/Console.mjs, polyfill/Lodash.mjs, polyfill/StatusTexts.mjs | $app, Console.log, Lodash.set, Lodash.pick, StatusTexts | 将各平台 $done 参数格式拉平并兼容状态码/策略字段 |
| lib/notification.mjs | lib/app.mjs, polyfill/Console.mjs | $app, Console.group, Console.log, Console.groupEnd, Console.error | 将通知参数映射到各平台通知接口并统一日志输出 |
| lib/runScript.mjs | polyfill/Console.mjs, polyfill/fetch.mjs, polyfill/Storage.mjs, polyfill/Lodash.mjs | Console.error, fetch, Storage.getItem(Lodash 当前版本未实际调用) | 读取 BoxJS 配置并发起统一 HTTP 调用执行脚本 |
| getStorage.mjs | lib/argument.mjs, polyfill/Console.mjs, polyfill/Lodash.mjs, polyfill/Storage.mjs | Console.debug, Console.logLevel, Lodash.merge, Storage.getItem | 先标准化 $argument,再合并默认配置/持久化配置/运行参数 |
| polyfill/Console.mjs | lib/app.mjs | $app | 日志在 Worker / Node.js 与 iOS 脚本环境使用不同错误输出策略 |
| polyfill/fetch.mjs | lib/app.mjs, polyfill/Lodash.mjs, polyfill/StatusTexts.mjs, polyfill/Console.mjs | $app, Lodash.set, StatusTexts(Console 当前版本未实际调用) | 按平台选请求引擎并做参数映射、响应结构统一 |
| polyfill/Storage.mjs | lib/app.mjs, polyfill/Lodash.mjs | $app, Lodash.get, Lodash.set, Lodash.unset | 按平台选持久化后端并支持 @key.path 读写 |
| polyfill/Lodash.mjs | 无 | 无 | 提供路径/合并等基础能力,被多个模块复用 |
| polyfill/qs.mjs | polyfill/Lodash.mjs | Lodash.get, Lodash.set, Lodash.toPath | 提供查询字符串与对象之间的解析/序列化能力 |
| polyfill/StatusTexts.mjs | 无 | 无 | 提供 HTTP 状态文案,供 fetch/done 使用 |
| index.js / lib/index.js / polyfill/index.js | 多个模块 | export * | 聚合导出,不含业务逻辑 |
API 参考(按 mjs 文件)
lib/app.mjs 与 lib/environment.mjs(平台识别与环境)
$app
- 类型:
"Quantumult X" | "Loon" | "Shadowrocket" | "Egern" | "Surge" | "Stash" | "Worker" | "Node.js" | undefined - 角色:核心模块。库内所有存在平台行为差异的模块都会先读取
$app再分流(如done、notification、fetch、Storage、Console、environment)。 - 读取方式:
import { $app } from "@nsnanocat/util";
const appName = $app; // 读取 $app,返回平台字符串
console.log(appName);- 识别顺序(
lib/app.mjs):
- 存在
$task->Quantumult X - 存在
$loon->Loon - 存在
$rocket->Shadowrocket - 存在
Egern->Egern - 存在
$environment且有surge-version->Surge - 存在
$environment且有stash-version->Stash - 存在
Cloudflare->Worker - 存在
process.versions.node->Node.js - 默认回落 ->
undefined
- 实现细节:内部使用
'key' in globalThis检测平台标记,避免Object.keys(globalThis)漏掉不可枚举全局变量;当前 Worker 识别以Cloudflare全局标记为准。
$environment / environment()
- 路径:
lib/environment.mjs(未从包主入口导出) - 签名:
environment(): object - 调用方式:
import { $environment, environment } from "@nsnanocat/util/lib/environment.mjs";
console.log($environment.app); // 统一平台名
console.log(environment()); // 当前环境对象- 规则:会为已识别平台统一生成
$environment.app = "平台名称"。
| 平台 | 调用路径(读取来源) | 读取结果示例 |
| --- | --- | --- |
| Surge | 读取全局 $environment,再写入 app | { ..., "surge-version": "x", app: "Surge" } |
| Stash | 读取全局 $environment,再写入 app | { ..., "stash-version": "x", app: "Stash" } |
| Egern | 读取全局 $environment,再写入 app | { ..., app: "Egern" } |
| Loon | 读取全局 $loon 字符串并拆分 | { device, ios, "loon-version", app: "Loon" } |
| Quantumult X | 不读取额外环境字段,直接构造对象 | { app: "Quantumult X" } |
| Worker | 直接构造对象 | { app: "Worker" } |
| Node.js | 读取 process.env 并写入 process.env.app | { ..., app: "Node.js" } |
| 其他 | 无 | {} |
lib/argument.mjs($argument 参数标准化模块)
此文件无显式导出;import 后立即执行。这是为了统一各平台 $argument 的输入差异。
行为
- 通过包入口导入(
import ... from "@nsnanocat/util")时会自动执行本模块。 - JSCore 环境不支持
await import,请使用静态导入或直接走包入口导入。 - 读取到的
$argument会按 URL Params 样式格式化为对象,并支持深路径。 - 内部实现统一委托给
qs.parse(globalThis.$argument)。 - 你也可以通过
import { $argument } from "@nsnanocat/util"读取当前已标准化的$argument快照。 - 平台输入差异说明:
- Surge / Stash / Egern:脚本参数通常以字符串形式传入(如
a=1&b=2)。 - Loon:支持字符串和对象两种
$argument形式。 - Quantumult X / Shadowrocket:不提供
$argument。
- Surge / Stash / Egern:脚本参数通常以字符串形式传入(如
- 当全局
$argument为string(如"a.b=1&x=2")时:- 按
&/=切分。 - 去掉值中的双引号。
- 使用点路径展开对象(
a.b=1 -> { a: { b: "1" } })。
- 按
- 当全局
$argument为object时:- 将 key 当路径写回新对象(
{"a.b":"1"}->{a:{b:"1"}})。
- 将 key 当路径写回新对象(
- 当
$argument为null或undefined:会归一化为{}。 - 若
$argument.LogLevel存在:同步到Console.logLevel。
用法
import { $argument } from "@nsnanocat/util";
// $argument = "mode=on&a.b=1"; // 示例入参,实际由模块参数注入
console.log($argument); // { mode: "on", a: { b: "1" } }lib/done.mjs
done(object = {})
- 签名:
done(object?: object): void - 作用:统一不同平台的脚本结束接口(
$done/ Worker 日志结束 / Node 退出)。
说明:下表描述的是各 App 原生接口差异与本库内部映射逻辑。调用方只需要按 done 的统一参数传值即可,不需要自己再写平台分支。
支持字段(输入):
status:number | stringurl:stringheaders:objectbody:string | ArrayBuffer | TypedArraybodyBytes:ArrayBufferpolicy:string
平台行为差异:
| 平台 | policy 处理 | status 处理 | body/bodyBytes 处理 | 最终行为 |
| --- | --- | --- | --- | --- |
| Surge | 写入 headers.X-Surge-Policy | 透传 | 透传 | $done(object) |
| Loon | object.node = policy | 透传 | 透传 | $done(object) |
| Stash | 写入 headers.X-Stash-Selected-Proxy(URL 编码) | 透传 | 透传 | $done(object) |
| Egern | 不转换 | 透传 | 透传 | $done(object) |
| Shadowrocket | 不转换 | 透传 | 透传 | $done(object) |
| Quantumult X | 写入 opts.policy | number 会转 HTTP/1.1 200 OK 字符串 | 仅保留 status/url/headers/body/bodyBytes;ArrayBuffer/TypedArray 转 bodyBytes | $done(object) |
| Worker | 不适用 | 不适用 | 不适用 | 仅记录结束日志 |
| Node.js | 不适用 | 不适用 | 不适用 | process.exit(1) |
不可用/差异点:
policy在 Egern / Shadowrocket 分支不做映射。- Quantumult X 会丢弃未在白名单内的字段。
- Quantumult X 的
status在部分场景要求完整状态行(如HTTP/1.1 200 OK),本库会在传入数字状态码时自动拼接(依赖StatusTexts)。 - Worker 不调用
$done,仅记录结束日志。 - Node.js 不调用
$done,而是直接退出进程,且退出码固定为1。 - 未识别平台(
$app === undefined)只记录结束日志,不会尝试调用$done或退出进程。
lib/notification.mjs
notification(title, subtitle, body, content)
- 签名:
title?: stringsubtitle?: stringbody?: stringcontent?: string | number | boolean | object
- 默认值:
title = "ℹ️ ${$app} 通知" - 作用:统一
notify/notification参数格式并发送通知。
content 可用 key(对象形式):
- 跳转:
open/open-url/url/openUrl - 复制:
copy/update-pasteboard/updatePasteboard - 媒体:
media/media-url/mediaUrl - 其他:
auto-dismiss、sound、mime
平台映射:
| 平台 | 调用接口 | 字符串 content 行为 | 对象字段支持 |
| --- | --- | --- | --- |
| Surge | $notification.post | { url: content } | open-url/clipboard 动作、media-url、media-base64、auto-dismiss、sound |
| Stash | $notification.post | { url: content } | 同 Surge 分支(是否全部展示取决于 Stash 支持) |
| Egern | $notification.post | { url: content } | 同 Surge 分支(是否全部展示取决于 Egern 支持) |
| Shadowrocket | $notification.post | { openUrl: content } | 走 Surge 分支的 action/url/text/media 字段 |
| Loon | $notification.post | { openUrl: content } | openUrl、mediaUrl(仅 http/https) |
| Quantumult X | $notify | { "open-url": content } | open-url、media-url(仅 http/https)、update-pasteboard |
| Worker | 不发送通知(非 iOS App 环境) | 无 | 无 |
| Node.js | 不发送通知(非 iOS App 环境) | 无 | 无 |
不可用/差异点:
copy/update-pasteboard在 Loon 分支不会生效。- Loon / Quantumult X 对
media仅接受网络 URL;Base64 媒体不会自动映射。 - Worker 不是 iOS App 脚本环境,不支持 iOS 通知行为;当前分支仅日志输出。
- Node.js 不是 iOS App 脚本环境,不支持 iOS 通知行为;当前分支仅日志输出。
lib/time.mjs
time(format, ts)
- 签名:
time(format: string, ts?: number): string ts:可选时间戳(传给new Date(ts))。- 支持占位符:
YY、yyyy、MM、dd、HH、mm、ss、sss、S(季度)。
time("yyyy-MM-dd HH:mm:ss.sss");
time("yyyyMMddHHmmss", Date.now());注意:当前实现对每个 token 只替换一次(String.replace 非全局)。
lib/wait.mjs
wait(delay = 1000)
- 签名:
wait(delay?: number): Promise<void> - 用法:
await wait(500);lib/runScript.mjs(未主入口导出)
runScript(script, runOpts)
- 签名:
runScript(script: string, runOpts?: { timeout?: number }): Promise<void> - 作用:通过 BoxJS
httpapi调用本地脚本执行接口:/v1/scripting/evaluate。 - 读取存储键:
@chavy_boxjs_userCfgs.httpapi@chavy_boxjs_userCfgs.httpapi_timeout
- 请求体:
script_textmock_type: "cron"timeout
示例:
import { runScript } from "./lib/runScript.mjs";
await runScript("$done({})", { timeout: 20 });注意:
- 依赖你本地已正确配置
httpapi(password@host:port)。 - 函数不返回接口响应,仅在失败时
Console.error。
getStorage.mjs
⚠️ 注意:该模块主要为薯条项目的存储结构设计,不作为通用默认 API。
仅当你的持久化结构与薯条项目一致时才建议使用。
getStorage(key, names, database)
- 签名:
key: string(持久化主键)names: string | string[](平台名/配置组名,可嵌套数组)database: object(默认数据库)
- 返回:
{ Settings, Configs, Caches }
合并顺序由 $argument.Storage 控制(持久化读取统一使用 PersistentStore = Storage.getItem(key, {});支持别名):
- 可用值(大小写敏感):
undefined|Argument|$argument|PersistentStore|BoxJs|boxjs|$persistentStore|database
undefined:database[name]->$argument->PersistentStore[name]Argument/$argument:database[name]->PersistentStore[name]->$argumentPersistentStore/BoxJs/$persistentStore(默认):database[name]->PersistentStore[name]database:仅database[name]
注意:Configs 与 Caches 始终按每个 name 合并(与 $argument.Storage 无关)。
自动类型转换(Root.Settings):
- 字符串
"true"/"false"->boolean - 纯数字字符串 ->
number - 含逗号字符串 ->
array,并尝试逐项转数字
示例:
import getStorage from "@nsnanocat/util/getStorage.mjs";
const store = getStorage("@my_box", ["YouTube", "Global"], database);命名导出(辅助函数)
getStorage.mjs 同时导出以下辅助函数:
traverseObject(o, c):深度遍历对象并替换叶子值string2number(string):将纯数字字符串转换为数字value2array(value):字符串按逗号拆分;数字/布尔值会被包装为单元素数组
示例:
import getStorage, {
traverseObject,
string2number,
value2array,
} from "@nsnanocat/util/getStorage.mjs";
const store = getStorage("@my_box", ["YouTube", "Global"], database);polyfill/fetch.mjs
fetch 是仿照 Web API Window.fetch 设计的跨平台适配实现:
- 参考文档:https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch
- 中文文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/fetch
- 目标:尽量保持 Web
fetch调用习惯,同时补齐各平台扩展参数映射
fetch(resource, options = {})
- 签名:
fetch(resource: object | string, options?: object): Promise<object> - 参数合并:
resource为对象:{ ...options, ...resource }resource为字符串:{ ...options, url: resource }
- 默认方法:无
method时,若有body/bodyBytes->POST,否则GET - 会删除 headers:
Host、:authority、Content-Length/content-length timeout规则:- 缺省 ->
5(秒) > 500视为毫秒并转为秒
- 缺省 ->
通用请求字段:
urlmethodheadersbodybodyBytestimeoutpolicyredirection/auto-redirectauto-cookie(Worker / Node.js 共享分支识别;默认启用,传入false/0/-1可关闭)
说明:下表是各 App 原生 HTTP 接口的差异补充,以及本库 fetch 的内部映射方式。调用方使用统一入参即可。
平台行为差异:
| 平台 | 请求发送接口 | timeout 单位 | policy 映射 | 重定向字段 | 二进制处理 |
| --- | --- | --- | --- | --- | --- |
| Surge | $httpClient[method] | 秒 | 无专门映射 | auto-redirect | Accept 命中二进制类型时设置 binary-mode |
| Loon | $httpClient[method] | 毫秒(内部乘 1000) | node = policy | auto-redirect | 同上 |
| Stash | $httpClient[method] | 秒 | headers.X-Stash-Selected-Proxy | auto-redirect | 同上 |
| Egern | $httpClient[method] | 秒 | 无专门映射 | auto-redirect | 同上 |
| Shadowrocket | $httpClient[method] | 秒 | headers.X-Surge-Proxy | auto-redirect | 同上 |
| Quantumult X | $task.fetch | 毫秒(内部乘 1000) | opts.policy | opts.redirection | body(ArrayBuffer/TypedArray) 转 bodyBytes;响应按 Content-Type 恢复到 body |
| Worker | globalThis.fetch(不存在时回退 node-fetch);共享 auto-cookie 处理 | 毫秒(内部乘 1000) | 无 | redirect: follow/manual | 返回 body(UTF-8 string) + bodyBytes(ArrayBuffer) |
| Node.js | globalThis.fetch(不存在时回退 node-fetch);默认按需包裹 fetch-cookie | 毫秒(内部乘 1000) | 无 | redirect: follow/manual | 返回 body(UTF-8 string) + bodyBytes(ArrayBuffer) |
返回对象(统一后)常见字段:
okstatusstatusCodestatusTextheadersbodybodyBytes
不可用/差异点:
policy在 Surge / Egern / Worker / Node.js 分支没有额外适配逻辑。redirection在部分平台会映射为auto-redirect或opts.redirection。- Worker / Node.js 共享基于
fetch的请求分支;若globalThis.fetch不存在则回退到node-fetch,并在auto-cookie未关闭时按需包裹fetch-cookie。 - 传入
timeout时,5和5000都会被接受;库会先将用户输入归一化,再按平台要求转换为秒或毫秒。 - 返回结构是统一兼容结构,不等同于浏览器
Response对象。
polyfill/Storage.mjs
Storage 是仿照 Web Storage 接口(Storage)设计的跨平台持久化适配器:
- 参考文档:https://developer.mozilla.org/en-US/docs/Web/API/Storage
- 中文文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Storage
- 目标:统一 VPN App 脚本环境中的持久化读写接口,并尽量贴近 Web Storage 行为
Storage.getItem(keyName, defaultValue = null)
- 支持普通 key:按平台读持久化。
- 支持路径 key:
@root.path.to.key。
Storage.setItem(keyName, keyValue)
- 普通 key:按平台写持久化。
- 路径 key:
@root.path写入嵌套对象。 keyValue为对象时自动JSON.stringify。
Storage.removeItem(keyName)
- Quantumult X:可用(
$prefs.removeValueForKey)。 - Surge:通过
$persistentStore.write(null, keyName)删除。 - Worker:可用(仅删除内存缓存中的对应 key,不持久化)。
- Node.js:可用(删除
box.dat中对应 key 并落盘)。 - Loon / Stash / Egern / Shadowrocket:返回
false。
Storage.clear()
- Quantumult X:可用(
$prefs.removeAllValues)。 - Worker:可用(仅清空内存缓存,不持久化)。
- Node.js:可用(清空
box.dat并落盘)。 - 其他平台:返回
false。
Worker / Node.js 特性
- Worker:使用进程内内存缓存,不写文件。
- 数据文件默认:
box.dat。 - 读取路径优先级:当前目录 ->
process.cwd()。
与 Web Storage 的行为差异:
- 支持
@key.path深路径读写(Web Storage 原生不支持)。 removeItem/clear仅部分平台可用(目前为 Quantumult X、Worker、Node.js,以及 Surge 的removeItem)。getItem会尝试JSON.parse,setItem写入对象会JSON.stringify。
平台后端映射:
| 平台 | 读写接口 |
| --- | --- |
| Surge / Loon / Stash / Egern / Shadowrocket | $persistentStore.read/write |
| Quantumult X | $prefs.valueForKey/setValueForKey |
| Worker | 进程内内存缓存 |
| Node.js | 本地 box.dat |
polyfill/Console.mjs
Console 是统一日志工具(静态类)。
日志级别
Console.logLevel可读写。- 支持:
OFF(0)/ERROR(1)/WARN(2)/INFO(3)/DEBUG(4)/ALL(5)。
logLevel 用法示例:
import { Console } from "@nsnanocat/util";
Console.logLevel = "debug"; // 或 4
Console.debug("debug message");
Console.logLevel = 2; // WARN
Console.info("won't print at WARN level");
Console.warn("will print");
console.log(Console.logLevel); // "WARN"方法
clear()count(label = "default")countReset(label = "default")debug(...msg)error(...msg)exception(...msg)group(label)groupEnd()info(...msg)log(...msg)time(label = "default")timeLog(label = "default")timeEnd(label = "default")warn(...msg)
参数与返回值:
| 方法 | 参数 | 返回值 | 说明 |
| --- | --- | --- | --- |
| clear() | 无 | void | 当前实现为空函数 |
| count(label) | label?: string | void | 计数并输出 |
| countReset(label) | label?: string | void | 重置计数器 |
| debug(...msg) | ...msg: any[] | void | 仅 DEBUG/ALL 级别输出 |
| error(...msg) | ...msg: any[] | void | Worker / Node.js 优先输出 stack |
| exception(...msg) | ...msg: any[] | void | error 别名 |
| group(label) | label: string | void | 压栈分组 |
| groupEnd() | 无 | void | 出栈分组 |
| info(...msg) | ...msg: any[] | void | INFO 及以上 |
| log(...msg) | ...msg: any[] | void | 通用日志 |
| time(label) | label?: string | void | 记录起始时间 |
| timeLog(label) | label?: string | void | 输出耗时 |
| timeEnd(label) | label?: string | void | 清除计时器 |
| warn(...msg) | ...msg: any[] | void | WARN 及以上 |
平台差异:
- Worker / Node.js 下
error会优先打印Error.stack。 - 其他平台统一加前缀符号输出(
❌/⚠️/ℹ️/🅱️)。
polyfill/Lodash.mjs
Lodash 为“部分方法的简化实现”,不是完整 Lodash。各方法语义可参考:
- https://www.lodashjs.com
- https://lodash.com
导入约定(建议):
- 这是 lodash 官方示例中常见的惯例写法:使用
_作为工具对象别名。
import { Lodash as _ } from "@nsnanocat/util";
const data = {};
_.set(data, "a.b", 1);
console.log(data); // { a: { b: 1 } }
const value = _.get(data, "a.b", 0);
console.log(value); // 1示例对应的 lodash 官方文档页面:
set(object, path, value)- 官方文档:https://lodash.com/docs/#set
- 中文文档:https://www.lodashjs.com/docs/lodash.set
get(object, path, defaultValue)- 官方文档:https://lodash.com/docs/#get
- 中文文档:https://www.lodashjs.com/docs/lodash.get
当前实现包含:
escape(string)unescape(string)toPath(value)get(object, path, defaultValue)set(object, path, value)unset(object, path)pick(object, paths)omit(object, paths)merge(object, ...sources)
对应 lodash 官方文档页面:
escape(string)- 官方文档:https://lodash.com/docs/#escape
- 中文文档:https://www.lodashjs.com/docs/lodash.escape
unescape(string)- 官方文档:https://lodash.com/docs/#unescape
- 中文文档:https://www.lodashjs.com/docs/lodash.unescape
toPath(value)- 官方文档:https://lodash.com/docs/#toPath
- 中文文档:https://www.lodashjs.com/docs/lodash.toPath
get(object, path, defaultValue)- 官方文档:https://lodash.com/docs/#get
- 中文文档:https://www.lodashjs.com/docs/lodash.get
set(object, path, value)- 官方文档:https://lodash.com/docs/#set
- 中文文档:https://www.lodashjs.com/docs/lodash.set
unset(object, path)- 官方文档:https://lodash.com/docs/#unset
- 中文文档:https://www.lodashjs.com/docs/lodash.unset
pick(object, paths)- 官方文档:https://lodash.com/docs/#pick
- 中文文档:https://www.lodashjs.com/docs/lodash.pick
omit(object, paths)- 官方文档:https://lodash.com/docs/#omit
- 中文文档:https://www.lodashjs.com/docs/lodash.omit
merge(object, ...sources)- 官方文档:https://lodash.com/docs/#merge
- 中文文档:https://www.lodashjs.com/docs/lodash.merge
参数与返回值:
| 方法 | 参数 | 返回值 | 说明 |
| --- | --- | --- | --- |
| escape | string: string | string | HTML 转义 |
| unescape | string: string | string | HTML 反转义 |
| toPath | value: string | string[] | a[0].b -> ['a','0','b'] |
| get | object?: object, path?: string\\|string[], defaultValue?: any | any | 路径读取 |
| set | object: object, path: string\\|string[], value: any | object | 路径写入(会创建中间层) |
| unset | object?: object, path?: string\\|string[] | boolean | 删除路径并返回结果 |
| pick | object?: object, paths?: string\\|string[] | object | 挑选 key(仅第一层) |
| omit | object?: object, paths?: string\\|string[] | object | 删除 key(会修改原对象) |
| merge | object: object, ...sources: object[] | object | 深合并(非完整 lodash 行为) |
merge 行为(与 lodash 官方有差异):
- 深度合并 Plain Object。
- Array 直接覆盖;空数组不覆盖已存在值。
- Map/Set 支持同类型合并;空 Map/Set 不覆盖已存在值。
undefined不覆盖,null会覆盖。- 直接修改目标对象(mutates target)。
polyfill/qs.mjs
当前实现为项目内使用的轻量子集,不追求与官方 qs 完全一致。
qs.parse(query)
- 签名:
qs.parse(query?: string | object | null): object - 作用:将查询字符串或对象输入归一化为支持深路径的对象。
- 依赖:内部使用
Lodash.set展开路径。
当前行为:
- 当
query为string时:- 按
&/=切分。 - 去掉值中的双引号。
- 使用点路径或数组下标路径展开对象。
- 按
- 当
query为object时:- 将 key 当路径写入新对象(
{"a.b":"1"}->{ a: { b: "1" } })。
- 将 key 当路径写入新对象(
- 当
query为null或undefined时:- 返回
{}。
- 返回
import { qs } from "@nsnanocat/util";
console.log(qs.parse("mode=on&a.b=1"));
// { mode: "on", a: { b: "1" } }
console.log(qs.parse({ "list[0]": "x", "list[1]": "y" }));
// { list: ["x", "y"] }qs.stringify(object)
- 签名:
qs.stringify(object?: object): string - 作用:将对象按深路径展开为查询字符串。
- 依赖:内部使用
Lodash.get与Lodash.toPath读取并格式化路径。
当前行为:
- 普通对象输出为点路径(
a.b=1)。 - 数组输出为索引路径(
list[0]=x)。 - 值始终执行
encodeURIComponent编码。 null会序列化为空值(key=),undefined会跳过。
import { qs } from "@nsnanocat/util";
console.log(qs.stringify({ a: { b: "1" }, list: ["x", "y"] }));
// a.b=1&list%5B0%5D=x&list%5B1%5D=ypolyfill/StatusTexts.mjs
StatusTexts
- 类型:
Record<number, string> - 内容:HTTP 状态码到状态文本映射(100~511 的常见码)。
- 主要用途:给 Quantumult X 的
$done状态行补全文本(如HTTP/1.1 200 OK)。 - 参考示例:https://github.com/crossutility/Quantumult-X/raw/refs/heads/master/sample-rewrite-response-header.js
平台差异总览
说明:本节展示的是各平台原生脚本接口差异。实际在本库中,这些差异已由 done、fetch、notification、Storage 等模块做了统一适配。
| 能力 | Quantumult X | Loon | Surge | Stash | Egern | Shadowrocket | Worker | Node.js |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| HTTP 请求 | $task.fetch | $httpClient | $httpClient | $httpClient | $httpClient | $httpClient | fetch | fetch |
| 通知 | $notify | $notification.post | $notification.post | $notification.post | $notification.post | $notification.post | 无 | 无 |
| 持久化 | $prefs | $persistentStore | $persistentStore | $persistentStore | $persistentStore | $persistentStore | 内存缓存 | box.dat |
| 结束脚本 | $done | $done | $done | $done | $done | $done | 仅日志 | process.exit(1) |
| removeItem/clear | 可用 | 不可用 | removeItem 可用 / clear 不可用 | 不可用 | 不可用 | 不可用 | 可用 | 可用 |
| policy 注入(fetch/done) | opts.policy | node | X-Surge-Policy(done) | X-Stash-Selected-Proxy | 无专门映射 | X-Surge-Proxy(fetch) | 无 | 无 |
已知限制与注意事项
lib/argument.mjs为$argument标准化模块,import时会按规则重写全局$argument。lib/done.mjs在 Worker 仅记录结束日志。lib/done.mjs在 Node.js 固定process.exit(1)。Storage.removeItem("@a.b")分支存在未声明变量写入风险;如要大量使用路径删除,建议先本地验证。lib/runScript.mjs未从包主入口导出,需要按文件路径直接导入。
参考资料
以下资料用于对齐不同平台 $ API 语义;README 的“平台差异”优先以本仓库实现为准。
Surge
Stash
Loon
Quantumult X
- crossutility/Quantumult-X - sample-task.js
- crossutility/Quantumult-X - sample-rewrite-with-script.js
- crossutility/Quantumult-X - sample-fetch-opts-policy.js
- crossutility/Quantumult-X - sample-rewrite-response-header.js
Worker
- 以
Cloudflare全局标记识别 Worker 运行时。
Node.js
Web API / Lodash
Egern / Shadowrocket
说明:Egern 与 Shadowrocket 暂未检索到等价于 Surge/Loon/Stash 的完整公开脚本 API 页面;相关差异说明以本库实际代码分支行为为准。
