五分钟上手
脚本就是一段跑在网页里的 JavaScript,头部加一小块注释告诉平台「它叫什么、在哪些站点跑、要用什么权限」。整个过程不涉及编译和打包。
想清楚改什么
只解决一个问题:去掉广告位、加个下载按钮、记住倍速。范围越小越容易过审。
写元数据头
至少 @name、@version、@match 三项,写在 ==UserScript== 注释里。
写逻辑
操作 DOM、加样式、读写本地存储。需要跨域请求或通知时再申请对应权限。
本地试跑
扩展面板「导入本地脚本」拖入 .user.js,刷新目标页面看效果。
document.write。元数据块
脚本开头用 ==UserScript== / ==/UserScript== 包裹一段注释,平台会把它解析成市场信息与运行配置。字段名大小写不敏感,一行一个。
| 字段 | 必填 | 说明 |
|---|---|---|
@name | 是 | 脚本名称,展示在列表与安装确认页 |
@version | 是 | 语义化版本(如 1.2.0)。更新必须递增,否则用户侧不会触发更新 |
@description | 推荐 | 一句话说明,列表页会截断展示 |
@match / @include | 是 | 注入规则,可写多条;两者都支持通配与正则 |
@exclude | 否 | 排除规则,优先级高于 @match |
@grant | 否 | 申请的权限清单,一行一个;写 none 表示不需要任何特权 |
@connect | 否 | 允许跨域访问的域名白名单;声明后未列出的域名会被拦截 |
@run-at | 否 | document-start / document-end / document-idle(默认) |
@require | 否 | 远程依赖脚本地址,安装时下载并内联执行,可写多条 |
@resource | 否 | 静态资源(图片、样式),配合 GM_getResourceURL 取用 |
@author | 否 | 作者署名,默认取发布账号 |
@namespace | 否 | 区分同名脚本,建议用你的域名或仓库地址 |
@inject-into / @sandbox | 否 | 强制注入位置:page(页面主世界)或 content(扩展沙箱) |
@updateURL / @downloadURL | 否 | 自建更新源时使用;本站发布的脚本由平台托管更新,不需要写 |
@noframes | 否 | 加上后脚本只在顶层页面运行,不注入任何 iframe |
@license | 推荐 | 开源协议(如 MIT),展示在详情页「许可」栏;不写视为保留所有权利 |
@homepage / @supportURL | 否 | 脚本主页与问题反馈地址,展示在详情页「脚本链接」卡;必须是 http(s) 链接 |
@antifeature | 视情况 | 脚本包含广告、返佣跳转等不欢迎的功能时必须声明:// @antifeature ads 脚本拥有广告。瞒报会被下架,声明后详情页会展示黄条提醒用户 |
@match 与作用范围
采用 scheme://host/path 三段式通配,* 表示任意段。下面几条覆盖绝大多数场景:
| 写法 | 匹配范围 |
|---|---|
*://*/* | 所有 http/https 页面(审核重点,非必要别用) |
*://*.bilibili.com/* | bilibili.com 及其所有子域 |
https://github.com/*/* | 仅 GitHub 仓库页 |
*://item.jd.com/* | 京东商品详情页 |
@include /正则/ | 用正则匹配,例如 /^https:\/\/example\.com\/(a|b)\// |
@exclude *://*/login* | 排除登录页,避免和登录流程打架 |
*://*/* 加 @grant 特权会被人工复核。单页应用(SPA)切换路由时不会重新加载页面,平台会在 URL 变化后按新地址重新匹配并注入一次,脚本里不需要自己监听 history。
支持的权限
权限由 @grant 声明。平台会按声明内容在安装页展示「这个脚本能做什么」,用户确认后才启用。常见组合:
| 场景 | 建议 @grant |
|---|---|
| 纯改样式、加按钮,不动存储与网络 | none(或只写 GM_addStyle) |
| 记住用户偏好、保存开关状态 | GM_getValue GM_setValue GM_deleteValue |
| 调用第三方接口补数据 | GM_xmlhttpRequest + @connect 目标域名 |
| 加一个「设置」入口 | GM_registerMenuCommand |
| 导出/下载文件 | GM_download |
GM_cookie,并且会在审核阶段被重点检查。读取登录凭据、把 Cookie 外发属于禁止项,见发布规范。GM API 清单
下表是当前扩展完整支持的 API。它们按脚本隔离,不会污染页面全局;没声明 @grant 的脚本也能调用,但会在安装确认页被视为「未申报权限」,影响过审。
样式与元素
| API | 用途 |
|---|---|
GM_addStyle(css) | 向页面 head 注入样式,返回 style 元素。改外观首选它,别直接改行内样式 |
GM_addElement(tag, attrs) | 创建并插入元素,{textContent, src, ...} |
本地存储
| API | 用途 |
|---|---|
GM_getValue(key, default) | 读取脚本私有存储,支持对象 |
GM_setValue(key, value) | 写入,值会 JSON 序列化 |
GM_deleteValue(key) | 删除单个键 |
GM_listValues() | 枚举该脚本的全部键 |
GM_addValueChangeListener(key, fn) | 监听值变化,多标签页同步时 remote=true |
GM_removeValueChangeListener(id) | 取消监听 |
网络与下载
| API | 用途 |
|---|---|
GM_xmlhttpRequest(opts) | 跨域请求,支持 method / headers / data / responseType / onload / onerror / onprogress / timeout / abort() |
GM_download(url, name) | 触发下载并命名文件,可传 {url, name, onload, onerror} |
GM_getResourceText(name) / GM_getResourceURL(name) | 读取 @resource 声明过的文本资源或 data URL |
界面与系统
| API | 用途 |
|---|---|
GM_notification(opts) | 桌面通知:{title, text, timeout, onclick} |
GM_setClipboard(text, type) | 写入剪贴板,建议由点击事件触发 |
GM_openInTab(url, opts) | 打开新标签页,{active, insert, setParent} |
GM_registerMenuCommand(label, fn, accessKey) | 在当前页面注册命令,显示为右下角浮动菜单 / 浏览器右键菜单 |
GM_unregisterMenuCommand(id) | 移除命令 |
GM_log(...) | 输出到扩展日志,方便远程排查 |
GM_info | 脚本自身信息:名称、版本、运行环境 |
GM_cookie.list/set/delete | 读写当前域 Cookie,需显式声明 |
GM_getTab/saveTab/getTabs | 按标签页共享临时数据 |
unsafeWindow | 页面真实 window。仅当脚本以「页面主世界」方式运行时可用,访问页面框架内部对象时需要 |
运行机制与沙箱
- 两种运行位置:不依赖特权 API 的脚本直接注入页面主世界,速度最快,也能拿到
unsafeWindow;声明了特权的脚本跑在扩展沙箱里,GM API 以参数形式注入,页面拿不到你的内部状态。 - 权限声明的作用:决定运行位置与安装页展示的权限清单。想强制指定位置可用
@inject-into page|content。 - 存储隔离:每个脚本的
GM_getValue空间互相独立,卸载脚本时可选择一并清除。 - 失败隔离:单个脚本抛错只影响它自己,页面和其他脚本继续运行;错误会写进扩展日志。
- 执行时机:
document-start在 DOM 建好前执行,适合改fetch/XHR;改页面内容用默认的document-idle最稳。
完整示例
入门:给页面加一条提示
// ==UserScript==
// @name 页面提示条
// @namespace https://example.com/
// @version 1.0.0
// @description 在页面左上角显示一条提示
// @match *://example.com/*
// @grant GM_addStyle
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
if (document.getElementById('tip')) return; // 幂等:防止重复注入
GM_addStyle('#tip{position:fixed;left:16px;top:16px;padding:8px 14px;' +
'border-radius:9px;background:#12161d;color:#c6f24e;font:13px sans-serif;z-index:99999}');
const el = document.createElement('div');
el.id = 'tip';
el.textContent = '脚本已注入';
document.documentElement.appendChild(el);
})();
进阶:开关 + 存储 + 跨域请求
// ==UserScript==
// @name 站点增强(示例)
// @version 1.1.0
// @description 记忆开关状态,并把页面标题上报到自己的接口
// @match *://example.com/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_xmlhttpRequest
// @connect api.example.com
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
const KEY = 'enabled';
const on = GM_getValue(KEY, true);
function apply(state) {
document.documentElement.toggleAttribute('data-enhance', state);
}
apply(on);
GM_registerMenuCommand(on ? '关闭增强' : '开启增强', () => {
const next = !GM_getValue(KEY, true);
GM_setValue(KEY, next);
apply(next);
location.reload();
});
// 跨域请求:域名必须在 @connect 里声明,否则会被拦下
GM_xmlhttpRequest({
method: 'POST',
url: 'https://api.example.com/stat',
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({ title: document.title, version: GM_info.script.version }),
onload: (res) => console.log('上报成功', res.status),
onerror: () => console.warn('上报失败,不影响主流程'),
});
})();
粘贴到发布页后,元数据会被自动解析出来,填好名称、分类并上传脚本图标即可提交。
写脚本的 8 条提示
- 先判断再插入:注入前检查目标节点是否存在,或用
document.getElementById做幂等保护,避免反复刷新时叠加多层。 - 用
GM_addStyle而不是行内样式:一处集中管理,站点改版时好维护,也不容易和页面自身样式互相覆盖。 - 选择器写得「钝」一点:别依赖编译后的类名(如
.css-1x2y3z),优先用语义化的data-*、aria-label、结构位置。 - 等元素出现再操作:懒加载页面用
MutationObserver监听,记得节流(100ms 以上),否则主线程会被拖慢。 - 版本号必须递增:只要改了代码就必须改
@version(如 1.0.1),否则已安装的用户永远收不到这次更新 —— 发布页和接口都会拦下这种提交。 - 请求失败要兜底:网络请求、存储读写都放在
try/catch里,不能让异常打断页面主流程。 - 别把逻辑藏起来:不混淆、不压缩成一行、不用 base64 包主逻辑。审核看不懂的代码会被打回。
- 申请最小权限:只用
GM_addStyle就别顺手写GM_cookie,权限越少,用户越愿意装。
调试与排错
| 现象 | 可能原因与处理 |
|---|---|
| 脚本完全没跑 | 看 @match 是否匹配当前 URL(含协议、端口);确认脚本在面板里处于开启状态;SPA 页面刷新一次 |
GM_xxx is not defined | 沙箱模式下 API 来自声明;补上对应的 @grant;或改到页面主世界运行(@inject-into page) |
| 跨域请求被拦 | 补齐 @connect 目标域名;注意只写域名,不带 https:// 与路径 |
| 样式不生效 | 选择器优先级不够或被站点 !important 覆盖;提高优先级或加 !important;CSP 严格的站点建议用 GM_addStyle |
| 改版后彻底失效 | 常见于依赖混淆类名;改用语义选择器,或加多重兜底查询 |
| 更新没生效 | 检查 @version 是否递增、是否重新提交审核通过 |
| 控制台报 Permission denied | 脚本以沙箱模式访问页面对象;需要页面上下文时用 unsafeWindow 或改为页面主世界 |
排查时优先看两处:页面控制台(脚本自身报错)与扩展管理面板的「运行日志」(注入结果、API 调用失败原因)。
发布、定价与分成
- 提交:发布页粘贴代码 → 自动解析元数据 → 填分类、截图与详细说明 → 提交审核。
- 审核:系统先做静态扫描(Cookie 读取、凭据外传、动态执行等),命中高风险特征转人工。通常 24 小时内出结果。
- 更新:在「我的脚本」点该脚本那一行的 更新 进入编辑页,版本号和代码头部的
@version会自动递增(如 1.0.0 → 1.0.1),改完代码写一句更新说明,提交审核。审核期间线上继续提供上一个已通过的版本,市场排名、安装量和已装用户都不受影响;通过后新版自动替换,已装用户下一次检查更新(Pro 每 12 小时自动,免费版点「检查更新」)即可拿到。 - 通知:审核有结果时会往作者邮箱发一封信 —— 通过给出脚本链接,驳回或下架写明原因;更新被驳回时线上旧版本照常提供。站内「我的脚本」里同样能看到状态与原因,邮件只是提醒,没收到可以查一下垃圾箱。
- 定价:可设为免费,或按月 / 年 / 永久定价。付费脚本在未购买用户那里只展示说明与权限,不返回源码。
- 分成:售出后按平台设定的分成比例计入作者余额,达到最低提现额可申请提现,打款账号在账号中心绑定。
云同步怎么工作
云同步不是「后台自动上传」,而是点一次、对齐一次:把本机已安装的脚本清单和云端的上一份备份做双向合并 —— 云端有而本机没有的下载下来,本机有而云端没有的上传上去,两边都有的以本机为准(不会用云端旧代码覆盖你本机改过的版本)。
| 对象 | 同步什么 |
|---|---|
| 市场脚本 | 只备份「装了哪些 + 开关状态」,源码本来就在云端;换设备时按脚本重新拉取当前已过审的版本 |
| 本地导入脚本 | 连代码一起备份,换设备能原样还原;单次请求超过 500KB 时降级为只传清单,面板会提示 |
GM 存储值(GM_setValue) | 不同步,各设备各自独立(避免把登录态、临时数据带出去) |
| 设置 | 只同步「自动更新」开关 |
什么时候同步:登录一次会自动同步一次(换新设备时登录完脚本自己就下来了);安装 / 导入 / 删除脚本之后也会自动同步一次,不用再手动点。此外随时可以在扩展里手动点「云同步」(管理面板「已安装」页顶部与「设置与同步」页各有一个入口,双向合并)。自动同步只在已登录且开通云同步时执行,几秒内的多次操作会合并成一次;没有定时后台轮询,也不会上传你没装过的脚本。
换账号不会自动合并:如果本机还装着上一个账号的脚本,登录另一个账号时会跳过自动同步,避免把前一个账号的脚本和删除标记带进新账号。确认要合并时,手动点一次「云同步」即可。
网页端(我的脚本)只显示「上次备份时间 / 脚本数 / 设备数」这类统计,不展示也不写入脚本代码 —— 网页拿不到你本机装了哪些脚本,写进去只会覆盖掉备份。
设备数:每次同步带一个设备标识,服务端按设备去重计数。免费会员不含云同步,Pro 3 台、Team 10 台;达到上限后新设备同步会被拒绝并提示,可到账号设置 → 云同步设备移除不再用的设备(只删该设备的云端备份,不影响本机已装脚本)。
备份保留:每个账号保留最近 5 份快照,用于换机恢复;旧备份没有代码时(早期版本只存清单)会被原样保留而不会被覆盖丢失。
删除会一起同步:删掉一个脚本时并记一条删除标记,下次同步会把云端那条一起删掉——不会出现「删了再点同步又回来」的往复。其他设备同步时也会跟着删掉;想留下就在那台设备上重新安装,装回来会撤销标记。删除标记保留 60 天后自动过期。
常见问题
能用 React / Vue / TypeScript 写吗?
可以,但要先编译成纯 JavaScript 再粘贴。运行时不提供打包与模块解析,import 语句不会被处理;依赖库可用 @require 引入 CDN 版本。
能操作 iframe 里的内容吗?
同源 iframe 可以直接访问;跨域 iframe 受浏览器同源策略限制,需要目标页面自身配合。平台会在所有 frame 中执行匹配到的脚本。
脚本数量有上限吗?
有。免费账号可安装的脚本数量由站点设置决定,达到上限时会提示登录或升级会员;具体额度见会员方案。
云同步是自动的吗?
部分是。登录后会自动同步一次,之后每次安装、导入、删除脚本也会自动同步;其余时刻想立刻对齐(比如刚在另一台设备上改过),在扩展管理面板点「云同步」即可。它做的是双向合并:两边都补充,冲突以本机为准。详见云同步怎么工作。
删掉的脚本,同步后为什么没有回来?
删除会被记成一条标记一起同步:云端对应条目被删掉,其他设备同步时也会跟着删,所以不会再被拉回来。这是 1.9.18 起的行为;更早的版本只做并集、不记删除,才会出现「删了又回来」的往复。要恢复就在扩展里重新安装,装了会撤销这条标记。
能读取页面的登录状态吗?
能读到自己页面上的公开 DOM,但不得读取、外发 Cookie、令牌或密码框内容。此类脚本一律不过审,已上架的会被下架。
脚本被下架了怎么办?
审核记录里会给出原因,按提示修改后重新提交。对处理结果有异议,可在投诉页提交申诉并补充材料。
脚本在别家管理器能用,装到这里没反应?
先按安装页的「三步排查」走一遍:确认网址命中 @match、刷新页面、检查扩展开关。仍然不行,多半是脚本用到了尚未支持的 GM API,可对照API 清单确认;缺的能力欢迎在投诉/反馈入口提给我们。
GitHub、X 这类站点注入不进去?
这些站点启用了严格的 CSP。Chrome 138+ 请在 chrome://extensions → 狂暴鲨 → 详情里打开「允许用户脚本」,扩展会改用浏览器官方脚本接口注入;不开启时会自动回退到普通注入,普通站点不受影响。