GM_* API 速成:GM_setValue、GM_xmlhttpRequest、GM_addStyle

元数据头里的 @grant 申请来的「特权」,具体就是一组以 GM_ 开头的函数。它们解决页面脚本的三大痛点:数据存不住、跨域请求发不出、样式注入太啰嗦。这一篇讲清最核心的几个 API,其余照猫画虎即可。

GM_setValue / GM_getValue:脚本自己的存储

页面里的 localStorage 会被清浏览记录清掉,还和站点共享。GM 存储则跟着脚本走,跨站点可用、不怕清理:

// ==UserScript==
// @name     存储演示
// @match    https://example.com/*
// @grant    GM_setValue
// @grant    GM_getValue
// @grant    GM_deleteValue
// ==/UserScript==

GM_setValue("visited", 42);
const n = GM_getValue("visited", 0);  // 第二个参数是默认值
GM_deleteValue("visited");

// GM_listValues() 返回所有键名数组,适合遍历清理
for (const key of GM_listValues()) {
  console.log(key, GM_getValue(key));
}

典型用途:记录「已读过的帖子 ID」、保存用户的界面偏好、缓存接口返回。和扩展的 chrome.storage.local 对应关系几乎一一对应,学会这边,迁移到扩展时零成本。

GM_xmlhttpRequest:绕过同源策略的跨域请求

这是用户脚本最锋利的一把刀。页面的 fetch 受同源策略限制,GM_xmlhttpRequest 则由管理器代理发起,不受页面同源策略约束(当然仍受目标服务器与 CSP 的现实约束):

// ==UserScript==
// @name     跨域请求演示
// @match    https://example.com/*
// @grant    GM_xmlhttpRequest
// @connect  api.github.com
// ==/UserScript==

GM_xmlhttpRequest({
  method: "GET",
  url: "https://api.github.com/repos/tampermonkey/tampermonkey",
  headers: { Accept: "application/vnd.github+json" },
  onload(res) {
    const data = JSON.parse(res.responseText);
    console.log("star 数:", data.stargazers_count);
  },
  onerror() {
    console.error("请求失败");
  }
});

三个要点。第一,@connect 声明允许请求的域名,管理器遇到未声明的域名会弹窗询问用户,把 @connect 写全可以避免运行中被弹窗打断。第二,回调风格是 onload / onerror / ontimeout,不是 Promise;要异步组合就自己包一层 new Promise。第三,能力越大责任越大:这个 API 能把页面数据发往任意服务器,也是恶意脚本最爱用的通道——再次提醒,装别人的脚本先看它往哪里发请求。

GM_addStyle:一行注入样式

不用 createElement 也不用手动 append,一行搞定:

// @grant GM_addStyle
GM_addStyle(`
  .ad-banner, .promo-layer { display: none !important; }
  body { letter-spacing: 0.02em; }
`);

配合 @run-at document-start 使用,样式在页面渲染前就位,用户看不到闪变。更精细的时机控制可以直接操作 DOM,但对绝大多数「改外观」需求,GM_addStyle 就够了。

其他常用成员

API 用途
GM_registerMenuCommand 在油猴面板加自定义菜单项,点击触发回调
GM_setClipboard 写入系统剪贴板
GM_notification 弹出系统通知
GM_getResourceText / URL 取 @resource 引入的资源
GM_addValueChangeListener 监听存储变化,跨标签页联动

GM_registerMenuCommand 值得单独记一笔,它让脚本有了「设置面板」:注册一个「切换暗黑模式」菜单项,点击后翻转存储里的布尔值,比改代码切换友好得多。

组合实战:记状态的访问计数器

// ==UserScript==
// @name     站点访问计数
// @match    https://example.com/*
// @grant    GM_setValue
// @grant    GM_getValue
// @grant    GM_registerMenuCommand
// ==/UserScript==

const count = GM_getValue("count", 0) + 1;
GM_setValue("count", count);
console.log("这是你第", count, "次访问");

GM_registerMenuCommand("清零计数", () => {
  GM_setValue("count", 0);
  location.reload();
});

存储、菜单、刷新联动,二十行覆盖三个 API。把这 twenty 行变成自己的模板,之后写任何脚本都从它开始。文本统计与模式匹配做多了会自然想强化正则功底,正则匹配写法可参考本站《正则表达式从入门到精通》:正则实战。

需要说明:GM API 是 Tampermonkey 的实现约定,个别函数签名可能随版本微调,以 Tampermonkey 文档为准;与扩展 chrome API 的对照迁移,到第三章后你会更有体感。

回调转 Promise 的通用封装

GM API 偏回调风格,现代代码习惯 async/await。一个几行的封装函数就能统一风格,值得背下来:

function gmr(options) {
  return new Promise((resolve, reject) => {
    GM_xmlhttpRequest({
      ...options,
      onload: (res) => (res.status >= 200 && res.status < 300
        ? resolve(res)
        : reject(new Error(`HTTP ${res.status}`))),
      onerror: () => reject(new Error("network error")),
      ontimeout: () => reject(new Error("timeout"))
    });
  });
}

// 用起来就是标准 async 风格
async function main() {
  try {
    const res = await gmr({ method: "GET", url: "https://api.example.com/data" });
    console.log(JSON.parse(res.responseText));
  } catch (e) {
    console.warn("请求失败:", e.message);
  }
}

封装层还能顺带做重试、超时控制、JSON 解析。脚本写大之后,你会有一个自己的 gmr.js 小工具库,它是脚本工程化的第一块砖。

权限申请的最小化对照

把本篇 API 与 @grant 的对应关系列成表,写脚本时对照勾选,避免「全部 grant 一遍」的坏习惯:

需求 需要的 grant
记住用户偏好 GM_setValue / GM_getValue
调第三方接口 GM_xmlhttpRequest + @connect 域名
改页面外观 GM_addStyle(或不加,直接操作 DOM)
加菜单开关 GM_registerMenuCommand
读写剪贴板 GM_setClipboard
读取 @resource 资源 GM_getResourceText / GM_getResourceURL

一个常见误区:用了 GM_addStyle 却 grant 全家桶。多申请不会报错,但会让脚本审计变难——Greasy Fork 的审核也会提示过宽的权限。逐项对应,是脚本作者的基本素养。

迁移视角:GM 与 chrome 的对照

第三章会正式讲扩展 API,这里先给一张速查表,让你知道「搬家时每样东西去哪了」:

GM API 扩展对应物
GM_setValue / GM_getValue chrome.storage.local.set / get
GM_xmlhttpRequest 后台上下文的 fetch(配 host_permissions)
GM_addStyle content_scripts 的 css 数组或 scripting.insertCSS
GM_registerMenuCommand contextMenus 或 action 弹窗
GM_notification chrome.notifications.create

对照着看你会发现:脚本只是把这些能力「包装」得更顺手,底层模型与扩展一致。所以本篇学的不只是油猴,是两类工具共通的能力模型。等读到第四章,这张表会让迁移零成本。

需要说明:GM API 是 Tampermonkey 的实现约定,个别函数签名可能随版本微调,以 Tampermonkey 文档为准。

GM API 的函数名与参数拼写没有编译器兜底,写错即 undefined,编辑器的语法检查(如 HBuilderX 效率技巧 里介绍的工程化配置)能提前拦一道。

本章由 WorkBuddy 原创整理,API 行为依据 Chrome 扩展官方文档(Manifest V3)。