- 前端
- 开发者工具
- 插件系统
【免费下载链接】scriptcat
ScriptCat, a browser extension that can execute userscript; 脚本猫,一个可以执行用户脚本的浏览器扩展
ScriptCat 的 Cloud Script Export 模块(packages/cloudscript/)负责把单个用户脚本连同其GM_*values、cookies 和元数据一起打包为一个可独立执行的 zip 归档,供脚本迁移或离线执行使用。读完本文,你将掌握该模块的架构与类型定义、导出参数(value/cookie 表达式)的语法规则、打包产物结构与运行方式,并能通过源码级证据理解"本地打包执行"与"云端同步备份"两条路径的根本区别。
定位:本地打包,而非云端同步
packages/cloudscript/README.md开篇就强调了一个容易混淆的关键点:Cloud Script Export 是把单个脚本连同其数据打包为可独立执行的归档,而不是同步到远端云存储,它与packages/filesystem提供的云同步完全是两回事。
- Cloud Script Export(本文主题):产出物是一个 zip 归档,落地到本地(当前唯一 target 为
local),不涉及任何远端上传,目的是脚本迁移与离线执行; - 云端同步/备份:由 packages/filesystem 提供,支持 WebDAV、OneDrive、Google Drive、Dropbox、百度网盘、S3 等存储后端,语义细节见 docs/cloud-sync.md。
两者可以这样区分:一个负责"把脚本包成一个可运行的程序带走",另一个负责"把脚本仓库同步到远端存储"。下文所有内容均围绕前者展开。
架构总览:工厂 + Target 实现
cloudscript包采用"工厂 + 目标实现"的结构,核心文件有三个:
| 文件 | 职责 |
|---|---|
| packages/cloudscript/cloudscript.ts | 定义导出接口CloudScript、参数类型ExportParams、cookie/value 表达式解析函数 |
| packages/cloudscript/factory.ts | CloudScriptFactory工厂,按ExportTarget创建对应实现 |
| packages/cloudscript/local.ts | LocalCloudScript,将脚本与数据写入 zip 的本地打包实现 |
在 src/app/repo/export.ts 中可以看到导出目标的类型定义:
export type ExportTarget = "local" | "tencentCloud";ExportTarget目前声明了local与tencentCloud两个可能值,但工厂侧只落地了local。在 packages/cloudscript/factory.ts 中:
export default class CloudScriptFactory { static create(type: ExportTarget, params: ExportParams) { switch (type) { case "local": return new LocalCloudScript(params); default: throw new Error(`unknown type ${type}`); } } static params(): { [key: string]: CloudScriptParams } { return { local: {} }; } }从源码结构看,tencentCloud是预留的扩展点,目前调用CloudScriptFactory.create("tencentCloud", ...)会直接抛出unknown type异常。扩展一个新的导出目标只需:在ExportTarget中追加类型、在create()的 switch 中注册对应实现类、在params()中补充该目标需要的 UI 参数声明(title/type: "select"/options)。
LocalCloudScript实现了统一的CloudScript接口:
export default interface CloudScript { exportCloud(script: Script, code: string, values: Value[], cookies: ExportCookies[]): Promise<void>; }该接口的四个入参正是导出包的四大数据来源:脚本对象、脚本源码、已筛选的 values、已筛选的 cookies。
导出参数与表达式语法
ExportParams是导出行为的所有可调参数(packages/cloudscript/cloudscript.ts):
export type ExportParams = { [key: string]: any; exportValue: string; // value 导出表达式 exportCookie: string; // cookie 导出表达式 overwriteValue: boolean; // 导入时是否覆盖原 value overwriteCookie: boolean; // 导入时是否覆盖原 cookie };参数明细如下:
| 参数 | 类型 | 默认值 | 含义 |
|---|---|---|---|
exportValue | string | 脚本@exportvalue元数据(无则为空字符串) | 指定要导出哪些GM_*values 的 key 表达式 |
exportCookie | string | 脚本@exportcookie元数据(无则为空字符串) | 指定要导出哪些域下 cookies 的表达式 |
overwriteValue | boolean | false | 运行时(utils.js导入逻辑)是否覆盖原有 value |
overwriteCookie | boolean | false | 运行时(utils.js导入逻辑)是否覆盖原有 cookie |
value 表达式:一行一个 key,逗号分隔
parseExportValue(script, exportValue)负责解析 value 表达式。语法规则:按换行分隔,每一行内再用逗号分隔多个 key,只导出当前脚本中真实存在的 key。
export async function parseExportValue(script: Script, exportValue: string): Promise<Value[]> { const lines = exportValue.split("\n"); const values = await valueClient.getScriptValue(script); for (const line of lines) { if (line.trim()) { const s = line.split(","); for (const key of s) { const k = key.trim(); if (k && values[k]) { result.push(values[k]); } } } } return result; }示例表达式(三行,第一行含两个 key):
apiToken theme,language lastLoginAt解析时theme与language都会被导出。由于有values[k]的存在性校验,不存在的 key 会被静默跳过。
cookie 表达式:一行一个站点,分号分隔参数
parseExportCookie(exportCookie)的语法规则:按换行分隔站点,每个站点行内用分号分隔key=value参数,必须提供url或domain之一才会触发抓取,抓取结果挂在cookies字段上。
export function parseExportCookie(exportCookie: string): Promise<ExportCookies[]> { const lines = exportCookie.split("\n"); for (const line of lines) { const detail: ExportCookies = {}; for (const param of line.split(";")) { const s = param.split("="); if (s.length !== 2) continue; detail[s[0].trim()] = s[1].trim(); } if (detail.url || detail.domain) { result.push( new Promise<ExportCookies>((resolve) => { getCookies(detail).then((cookies) => { detail.cookies = cookies; resolve(detail); }); }) ); } } return Promise.all(result); }getCookies内部走chrome.cookies.getAll,并处理了chrome.runtime.lastError(记录错误后继续执行,不阻断导出流程)。实际抓取时传入的参数即chrome.cookies.GetAllDetails所支持的条件(url、domain、name等)。
示例表达式(两行):
url=https://example.com;name=session_id domain=.example.orgExportCookies的结构为:
export type ExportCookies = { [key: string]: any; domain?: string; url?: string; cookies?: chrome.cookies.Cookie[]; };默认值来源:脚本元数据
在 src/pages/components/CloudScriptPlan/index.tsx 中,导出对话框打开时会从脚本 metadata 读取默认表达式:
export function cloudDefaultParams(script: Script): Pick<ExportParams, "exportValue" | "exportCookie"> { return { exportValue: script.metadata.exportvalue?.[0] ?? "", exportCookie: script.metadata.exportcookie?.[0] ?? "", }; }即脚本头部元数据@exportvalue与@exportcookie可以作为导出表达式的默认值,用户仍可在导出对话框中手动覆盖。
打包产物:zip 内部结构
LocalCloudScript.exportCloud是打包的核心(packages/cloudscript/local.ts),它把六类内容写入 zip:
exportCloud(script: Script, code: string, values: Value[], cookies: ExportCookies[]): Promise<void> { this.zip.file("userScript.js", code); this.zip.file("cookies.js", `exports.cookies = ${JSON.stringify(cookies)}`); this.zip.file("values.js", `exports.values = ${JSON.stringify(values)}`); this.zip.file("config.js", `export default ${JSON.stringify({ version: ExtVersion, uuid: script.uuid, overwrite: { value: this.params.overwriteValue, cookie: this.params.overwriteCookie, }, })}`); this.zip.file("package.json", <string>packageTpl); this.zip.file("utils.js", <string>utilsTpl); this.zip.file("index.js", <string>indexTpl); return Promise.resolve(); }| 归档内文件 | 内容 | 来源 |
|---|---|---|
userScript.js | 用户脚本完整源码 | ScriptCodeDAO读取的脚本代码 |
cookies.js | exports.cookies = [...],即按表达式筛选出的 cookies | parseExportCookie的产物 |
values.js | exports.values = [...],即按表达式筛选出的 values | parseExportValue的产物 |
config.js | 元数据:扩展版本、脚本 uuid、overwrite.value/overwrite.cookie | ExtVersion与导出参数 |
package.json | npm 包声明,依赖scriptcat-nodejs | src/template/cloudcat-package/package.tpl |
utils.js | 运行时引导逻辑:读取源码与数据,驱动ScriptCat执行 | src/template/cloudcat-package/utils.tpl |
index.js | npm 入口:require('./utils')后调用utils.run() | src/template/cloudcat-package/index.tpl |
元数据 config.js
config.js记录了三个关键信息:
{ "version": "<扩展版本号>", "uuid": "<脚本uuid>", "overwrite": { "value": false, "cookie": false } }version取自@App/app/const中的ExtVersion,标识打包时使用的 ScriptCat 扩展版本;uuid是脚本的唯一标识,供恢复/迁移时关联原始脚本;overwrite来自导出参数overwriteValue/overwriteCookie,由运行时导入逻辑消费。
package.json 与外部依赖
src/template/cloudcat-package/package.tpl 声明了包的基本信息:
{ "name": "cloudcat-package", "version": "1.0.0", "description": "scriptcat后台脚本打包项目", "main": "index.js", "scripts": { "run": "node index.js" }, "license": "MIT", "dependencies": { "scriptcat-nodejs": "^0.1.7" } }关键点:package.json声明了外部依赖scriptcat-nodejs(^0.1.7),因此产出的包不是解压后即可直接node index.js运行的。
运行时引导 utils.js
src/template/cloudcat-package/utils.tpl 展示了实际的离线执行逻辑:
const fs = require('fs'); const { ScriptCat } = require("scriptcat-nodejs/dist/src/scriptcat"); const { ModelValues } = require("scriptcat-nodejs/dist/src/storage/values"); const { cookies } = require('./cookies'); const { values } = require('./values'); exports.run = function () { const code = fs.readFileSync('userScript.js', 'utf8'); const run = new ScriptCat(); run.RunOnce(code, { cookies: cookies, values: new ModelValues(values), }).then((res) => { console.log(res); }); }执行时:
- 读取同目录
userScript.js作为脚本源码; - 读取
cookies.js/values.js中的数据; - 通过
scriptcat-nodejs提供的ScriptCat.RunOnce在 Node.js 环境中运行脚本一次,values 用ModelValues包装以模拟GM_*value 存储; - 执行结果
res输出到控制台。
运行方式:两步走
由于依赖scriptcat-nodejs,产出的 zip 需要两步才能运行:
# 1. 解压归档 unzip <script-uuid>.zip -d cloudcat-package cd cloudcat-package # 2. 安装外部依赖 npm install # 3. 运行(等价于 node index.js) npm run runnpm run run由 index.tpl 驱动,最终调用utils.run()。这种"先安装依赖、再执行"的设计,使得归档可以在任意具备 Node.js 与 npm 的环境中离线运行脚本,达到脚本迁移或离线执行的目的。
完整调用链:从导出对话框到 zip 下载
从 src/pages/components/CloudScriptPlan/index.tsx 可以还原完整的导出流程:
- 读取导出计划:
ExportDAO.findByScriptID(script.uuid)查询该脚本是否已有保存的导出计划(目标、参数); - 组装参数:优先取已保存参数,否则回退到
cloudDefaultParams(@exportvalue/@exportcookie元数据)与默认开关值; - 解析数据:
parseExportValue(script, params.exportValue)筛选 values,parseExportCookie(params.exportCookie)筛选 cookies; - 创建 zip 与导出:
CloudScriptFactory.create("local", { zip, ...params })创建LocalCloudScript,ScriptCodeDAO.findByUUID读取脚本源码,然后exportCloud(script, code, values, cookies)写入全部文件; - 压缩与下载:
zipFile.generateAsync({ type: "blob", compression: "DEFLATE", compressionOptions: { level: 9 }, comment: "Created by Scriptcat" })生成 Blob,再通过chrome.downloads.download以saveAs: true、文件名${script.uuid}.zip触发下载。
值得注意的压缩细节:导出使用 DEFLATE 压缩、等级 9(最高压缩比),zip 包注释为Created by Scriptcat,相关实现见 src/pkg/utils/jszip-x.ts。
导出计划的持久化由ExportDAO(src/app/repo/export.ts)负责,以脚本uuid为主键保存target与各目标的参数快照,下次打开对话框时自动回填。
与 filesystem 云同步的边界(再次强调)
最后再次明确边界,避免误用:
- 需要把脚本打包带走、离线执行或迁移:使用 Cloud Script Export(本文),产物是 zip,本地下载;
- 需要把脚本同步/备份到 WebDAV、OneDrive、Google Drive、Dropbox、百度网盘、S3:使用 packages/filesystem,语义细节见 docs/cloud-sync.md。
Cloud Script Export 全程不产生任何远端网络上传,产出物完全由用户本地持有,这一点是它与云同步路径最本质的区别。
- 前端
- 开发者工具
- 插件系统
【免费下载链接】scriptcat
ScriptCat, a browser extension that can execute userscript; 脚本猫,一个可以执行用户脚本的浏览器扩展
相关推荐
Nexe项目完整指南:将Node.js应用打包为独立可执行文件
Nexe项目完整指南:将Node.js应用打包为独立可执行文件 Nexe是一个功能强大的命令行工具,能够将Node.js应用程序编译成单个可执行文件。通过使用N
构建工具开发工具CLIGemstash调试技巧:解决私有gem服务器常见问题的实用方法
Gemstash调试技巧:解决私有gem服务器常见问题的实用方法 你是否在使用Gemstash作为RubyGems缓存和私有gem服务器时遇到了问题?🤔 作为
PyInstaller终极指南:3步将Python脚本打包成独立可执行文件
PyInstaller终极指南:3步将Python脚本打包成独立可执行文件 PyInstaller是一个功能强大的Python应用程序打包工具,它能够将你的Py
开发工具构建工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考