Caliber.js Framework Library

一个旨在帮助开发者快速构建功能强大、可维护的现代油猴脚本的框架。它提供模块化架构、自动化的UI设置面板、响应式生命周期管理、高性能DOM调度器与网络请求拦截器等核心功能,让您专注于实现创意,而非繁琐的底层细节。

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/545792/1911233/Caliberjs%20Framework%20Library.js

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

लेखक
You Boy
आवृत्ती
1.2.1
बनवली
2025-08-14
अपडेट केली
2026-08-24
आकार
168 KB
License
MIT

Caliber.js 框架开发者手册 (v1.2.1)

Caliber.js 是用于油猴(Userscript)脚本的模块化开发框架,提供配置管理、设置面板渲染、DOM 分帧调度、网络请求拦截、宿主页面代码执行与模块生命周期管理等核心功能。


目录

  1. 快速上手
  2. 应用初始化 (createApp)
  3. 模块开发 (Module)
  4. 核心服务 API
  5. UI 守护服务 (uiGuard)
  6. 路由匹配规则 (match)
  7. 泄漏审计工具 (ModuleAuditor)
  8. 应用销毁 (destroy)
  9. 完整示例

1. 快速上手

在脚本元数据块中引入 Caliber.js,通过继承 Caliber.Module 编写功能模块,并调用 Caliber.createApp 启动:

// ==UserScript==
// @name         Demo Script
// @namespace    https://example.com/
// @version      1.0.0
// @match        https://example.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        unsafeWindow
// @require      https://update.greasyfork.org/scripts/545792/Caliberjs%20Framework%20Library.js
// ==/UserScript==

class DemoModule extends Caliber.Module {
  id = "demo-module";
  name = "示例模块";
  description = "用于演示基本功能的示例模块。";

  defaultConfig = {
    enabled: true,
    textColor: {
      type: "color",
      value: "#1a73e8",
      label: "文字颜色"
    }
  };

  async onEnable(context) {
    this._logger.log("模块已启用,URL 参数:", context.query);
  }

  async onDisable() {
    this._logger.log("模块已停用。");
  }
}

(async () => {
  await Caliber.createApp({
    appName: "示例脚本",
    modules: [DemoModule],
    services: {
      storage: {
        get: async () => JSON.parse((await GM_getValue("app_config", "{}"))),
        set: async (val) => GM_setValue("app_config", JSON.stringify(val))
      },
      command: {
        register: (name, cb) => GM_registerMenuCommand(name, cb)
      }
    }
  });
})();

2. 应用初始化 (createApp)

Caliber.createApp(options) 是应用的入口方法,负责依赖装配与生命周期初始化。

启动参数说明

参数名 类型 必填 默认值 说明
appName string - 应用名称,用于日志前缀、设置面板标题及菜单项命名
modules Array<typeof Module> - 注册的模块类数组(传入类本身)
services object - 平台环境适配器集合
services.storage StorageAdapter localStorage 包含 get(): Promise<object>set(val): Promise<void>
services.command CommandAdapter 空实现 包含 register(name, callback): void
services.hostWindow Window 自动识别 宿主窗口对象,默认优先使用 unsafeWindow,回退至 window
services.hostDocument Document document 宿主文档对象
services.style StyleAdapter 自动生成 样式注入适配器,提供 add(css, id)remove(id)
isDebug boolean false 是否开启调试模式与资源泄漏审计
settingsPanelEnabled boolean true 设置面板功能初始启用状态
settingsPanel SettingsPanelOptions {} 设置面板与触发器的配置对象
framework.domProcessorBatchSize number 20 DOM 调度器单帧处理的最大节点数

设置面板配置 (settingsPanel)

settingsPanel 支持配置面板的定位、拖拽、吸附、展开形态及自定义触发器。

interface SettingsPanelOptions {
  enabled?: boolean;                 // 是否启用设置面板 (默认: true)
  draggable?: boolean;               // 是否允许拖拽悬浮按钮 (默认: true)
  edgeSnapping?: boolean;            // 拖拽释放时是否吸附至屏幕边缘 (默认: true)
  snapThreshold?: number;            // 触发边缘吸附的距离阈值 (默认: 25px)

  right?: number;                    // 距屏幕右侧距离 (默认: 0)
  bottom?: number;                   // 距屏幕底部距离 (默认: 50)
  left?: number;                     // 距屏幕左侧距离 (设置时优先于 right)
  top?: number;                      // 距屏幕顶部距离 (设置时优先于 bottom)

  modalWidth?: string;               // 居中弹窗形态下的最大宽度 (默认: "720px")

  trigger?: string | HTMLElement;    // 外部 DOM 绑定触发器(选择器或节点对象)
  floatingContent?: string | HTMLElement; // 自定义悬浮按钮内容(DOM 节点或 HTML)
}

1. 坐标定位与边界保护

  • 支持传入 lefttoprightbottom。若同时传入 leftright,优先使用 left;若同时传入 topbottom,优先使用 top
  • 内部使用 document.documentElement.clientWidth 计算视口可用宽度,并结合窗口 resize 事件限制坐标范围,避免元素溢出视口边界。

2. 拖拽与边缘吸附

  • draggable: true 时,悬浮按钮支持指针事件拖拽。
  • edgeSnapping: true 时,释放悬浮按钮后,若与屏幕左、右、上、下边缘的距离小于 snapThreshold,将自动贴靠至对应边缘并持久化坐标数据。

3. 展开形态逻辑

打开设置面板时,根据触发按钮的当前坐标自动选择展示形态:

  • 贴靠屏幕右边缘:展示右侧抽屉(宽 360px,高 100%,向左滑出);
  • 贴靠屏幕左边缘:展示左侧抽屉(宽 360px,高 100%,向右滑出);
  • 处于顶部、底部或屏幕内部:展示居中弹窗(最大宽度受 modalWidth 控制,高度固定为 min(78vh, 680px),包含关闭按钮)。

4. 触发器自定义

  • 外部 DOM 绑定 (trigger):传入外部选择器字符串或 DOM 元素后,默认悬浮按钮将被隐藏,点击指定的外部 DOM 即可打开设置面板: javascript settingsPanel: { trigger: "#navbar-settings-btn" }
  • 悬浮按钮内容自定义 (floatingContent):传入 HTMLElement 或 HTML 字符串后,默认的悬浮按钮将移除自带背景、边框和尺寸限制,转为包裹传入内容的容器,并保留拖拽与坐标逻辑: ```javascript const customNode = document.createElement("div"); customNode.textContent = "设置";

settingsPanel: { floatingContent: customNode }


#### 5. 全局事件控制
可通过全局事件总线控制面板开闭:

```javascript
this._eventBus.emit("command:open-settings-panel");   // 打开面板
this._eventBus.emit("command:close-settings-panel");  // 关闭面板
this._eventBus.emit("command:toggle-settings-panel"); // 切换状态

3. 模块开发 (Module)

所有功能模块须继承 Caliber.Module 基类。

模块定义规范

class MyModule extends Caliber.Module {
  id = "my-module";              // 唯一标识符(不可使用 "base-module")
  name = "模块名称";             // 面板中显示的名称
  description = "模块功能描述";   // 面板中显示的描述
  match = "/user/:id";           // 页面匹配规则(可选)
  uiGuard = null;                // UI 守护配置(可选)
}

配置项声明 (defaultConfig)

defaultConfig 用于定义模块可配置项及生成对应的设置面板表单控件。

defaultConfig = {
  enabled: true, // 必填/保留字段,控制模块启用状态

  // 字符串输入框
  textKey: {
    type: "string",
    value: "默认值",
    label: "文本配置项",
    description: "配置说明",
    inputProps: { placeholder: "请输入..." }
  },

  // 多行文本域
  textareaKey: {
    type: "textarea",
    value: "行1\n行2",
    label: "多行文本项",
    inputProps: { rows: 3 }
  },

  // 数值输入框
  numberKey: {
    type: "number",
    value: 10,
    label: "数值配置项",
    inputProps: { min: 0, max: 100, step: 1 }
  },

  // 布尔开关
  switchKey: {
    type: "boolean",
    value: false,
    label: "开关配置项"
  },

  // 下拉选择框
  selectKey: {
    type: "select",
    value: "opt1",
    label: "选择配置项",
    options: [
      { label: "选项一", value: "opt1" },
      { label: "选项二", value: "opt2" }
    ]
  },

  // 颜色选择器
  colorKey: {
    type: "color",
    value: "#000000",
    label: "颜色配置项"
  }
};

布局辅助属性

  • indentLevel (1 | 2 | 3): 配置项左侧层级缩进。
  • divider ("top" | "bottom" | "both"): 添加分割线。
  • inputProps (object): 透传给原生 HTML 标签的属性对象(如 minmaxrowsplaceholder 等)。

生命周期方法

所有异步生命周期方法均按排他队列串行执行。

方法名 异步支持 触发条件 参数
onEnable(context) 模块启用且当前 URL 匹配成功时执行 context: { params: object, query: object }
onDisable() 模块停用或页面离开匹配范围时执行。
需在此方法中注销事件监听与定时器
onConfigChange(key, newVal, oldVal) 模块专属配置项变更时触发 key: 配置键名
newVal: 新值
oldVal: 旧值
onNavigate(context) 模块处于激活状态且当前 SPA 页面发生路由切换时触发 context: { params: object, query: object }
onRender(targetElement) uiGuard 目标节点存在且组件丢失时触发 targetElement: 挂载父节点
onCleanup() uiGuard 组件被卸载时触发

内置服务句柄

模块实例内部可通过 this.xxx 访问框架服务:

this._logger      // 日志输出实例
this._eventBus     // 全局事件总线 (on, off, emit)
this._hostWindow   // 宿主 Window 对象
this._hostDocument // 宿主 Document 对象
this._scheduler    // DOM 批量调度器
this._interceptor  // 网络请求拦截器
this._sanitizer    // DOM 净化与安全注入服务
this._executor     // 页面作用域执行器
this._utils        // 实用工具集合 (包含 checkMatch 路由检查)
this._module       // 模块操作门面 (包含 requestReset 重启方法)

4. 核心服务 API

1. DOM 调度器 (_scheduler)

基于统一 MutationObserverrequestAnimationFrame 实现的批量 DOM 监听器。

// 注册任务
const taskId = this._scheduler.register(
  ".target-selector",
  (element) => {
    // 匹配元素处理逻辑
  },
  {
    add: true,               // 监听节点插入 (默认: true)
    attributes: false,       // 监听属性变更 (默认: false)
    attributeFilter: ["class"], // 指定监听的属性列表 (可选)
    root: "#container",      // 限制监听范围的根选择器或节点 (可选)
    processExisting: true    // 注册时立即处理页面已存在的匹配节点 (默认: false)
  }
);

// 注销任务
this._scheduler.unregister(taskId);

2. 网络拦截器 (_interceptor)

用于拦截和修改页面的 Fetch 与 XMLHttpRequest 网络请求。

this._interceptor
  .target({
    url: "https://api.example.com/data", // 目标 URL
    method: "GET",                       // 请求方法 (大写)
    match: "/path/*"                     // 生效路由 (可选)
  })
  .onRequest(`
    // 运行于页面上下文的纯函数体字符串
    urlObject.searchParams.set('from', 'caliber');
    return { url: urlObject.toString(), config };
  `)
  .onResponse((responseData) => {
    // 运行于沙箱上下文的响应数据回调
    console.log("响应数据:", responseData);
  })
  .register(this.id);

// 移除指定 ID 的拦截器
this._interceptor.removeHook(this.id);

3. 页面执行器 (_executor)

在宿主页面的真实作用域中执行代码并异步返回可序列化的结果,内置 10 秒超时机制。

try {
  const result = await this._executor.execute(`
    (() => {
      return window.__PAGE_DATA__ || null;
    })()
  `);
} catch (error) {
  this._logger.error("代码执行失败:", error.message);
}

4. DOM 净化服务 (_sanitizer)

用于处理 CSP (Content Security Policy) 和 Trusted Types 环境下的安全 DOM 操作。

// 1. 设置 innerHTML
this._sanitizer.setInnerHTML(element, "<span>内容</span>");

// 2. 创建 TrustedHTML
const html = this._sanitizer.createTrustedHTML("<div>内容</div>");

// 3. 注入脚本
this._sanitizer.injectScript(this._hostDocument, "console.log('injected')");

// 4. 注入样式
this._sanitizer.injectStyle(this._hostDocument, ".rule { color: red; }", "style-id");

5. 模块自控门面 (_module)

用于请求对当前模块执行完整的停用与重新启用流程。

await this._module.requestReset();

5. UI 守护服务 (uiGuard)

用于在 SPA 单页应用中维持自定义 UI 组件的挂载状态。

class CustomUIModule extends Caliber.Module {
  id = "custom-ui";
  name = "界面注入模块";

  uiGuard = {
    target: "#header",       // 挂载目标的容器选择器
    component: ".injected-ui" // 组件自身选择器,用于检测是否存在
  };

  onRender(targetElement) {
    const node = this._hostDocument.createElement("div");
    node.className = "injected-ui";
    node.textContent = "挂载组件";
    targetElement.appendChild(node);
  }

  onCleanup() {
    this._hostDocument.querySelector(this.uiGuard.component)?.remove();
  }
}

6. 路由匹配规则 (match)

match 属性支持匹配 URL 路径并提取动态参数:

// 1. 动态 RESTful 命名参数 -> context.params.userId
match = "/user/:userId";

// 2. 可选参数
match = "/page/:id?";

// 3. 通配符 -> context.params._
match = "/article/*";

// 4. 正则表达式
match = /^\/post\/\d+$/;

// 5. 规则数组
match = ["/home", "/dashboard", "/user/*"];

7. 泄漏审计工具 (ModuleAuditor)

createApp 配置 isDebug: true 时启用,在模块执行 onDisable() 完成后检测未清理的资源:

  1. 未注销的 DOM 事件监听器
  2. 未清除的 setInterval 定时器
  3. 未注销的 _scheduler 任务

检测到未清理资源时,将在控制台输出错误日志。


8. 应用销毁 (destroy)

用于注销应用实例并清理框架占用的全局资源:

const kernelKey = Object.keys(window).find(k => k.startsWith("CALIBER_INSTANCE_"));
const kernel = window[kernelKey];

if (kernel) {
  await kernel.destroy();
}

执行后将依次触发已激活模块的 onDisable()、移除设置面板、清理网络拦截 Hook 并释放全局引用。


9. 完整示例

// ==UserScript==
// @name         VideoHelper
// @namespace    https://example.com/
// @version      1.0.0
// @match        https://example.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        unsafeWindow
// @require      https://update.greasyfork.org/scripts/545792/Caliberjs%20Framework%20Library.js
// ==/UserScript==

class VideoDownloadModule extends Caliber.Module {
  id = "video-downloader";
  name = "视频下载工具";
  description = "获取视频源地址并添加下载按钮。";
  match = "/video/:id";

  defaultConfig = {
    enabled: true,
    themeColor: {
      type: "color",
      value: "#1a73e8",
      label: "按钮颜色"
    },
    filterRules: {
      type: "textarea",
      value: "rule1\nrule2",
      label: "过滤规则",
      inputProps: { rows: 2 }
    }
  };

  uiGuard = {
    target: ".video-actions",
    component: ".btn-download"
  };

  #sourceUrl = null;
  #taskId = null;

  async onEnable(context) {
    this._logger.log("当前视频 ID:", context.params.id);

    this._interceptor
      .target({
        url: window.location.origin + "/api/video/source",
        method: "GET"
      })
      .onResponse((data) => {
        if (data?.url) {
          this.#sourceUrl = data.url;
        }
      })
      .register(this.id);

    this.#taskId = this._scheduler.register(
      ".video-title",
      (el) => {
        el.style.color = this._config.themeColor;
      },
      { processExisting: true }
    );
  }

  onRender(targetElement) {
    const btn = this._hostDocument.createElement("button");
    btn.className = "btn-download";
    btn.textContent = "下载视频";
    btn.style.cssText = `padding: 4px 8px; color: #fff; background: ${this._config.themeColor}; border: none; border-radius: 4px; cursor: pointer;`;

    btn.onclick = () => {
      if (this.#sourceUrl) {
        window.open(this.#sourceUrl, "_blank");
      }
    };

    targetElement.appendChild(btn);
  }

  onCleanup() {
    this._hostDocument.querySelector(this.uiGuard.component)?.remove();
  }

  onConfigChange(key, newValue) {
    if (key === "themeColor") {
      const btn = this._hostDocument.querySelector(this.uiGuard.component);
      if (btn) btn.style.backgroundColor = newValue;
    }
  }

  async onDisable() {
    this._interceptor.removeHook(this.id);
    if (this.#taskId) {
      this._scheduler.unregister(this.#taskId);
      this.#taskId = null;
    }
    this.onCleanup();
    this.#sourceUrl = null;
  }
}

(async () => {
  await Caliber.createApp({
    appName: "VideoHelperApp",
    isDebug: true,
    modules: [VideoDownloadModule],
    settingsPanel: {
      draggable: true,
      edgeSnapping: true,
      bottom: 50,
      right: 0
    }
  });
})();