脚本头部配置,定义基本信息、权限和运行规则。
// ==UserScript==
// @name Script Name
// @namespace https://example.com
// @version 1.0.0
// @description Description
// @author Author
// @match *://google.com/*
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @require https://cdn.js/jquery.min.js
// @run-at document-end
// @connect api.example.com
// ==/UserScript==
| @name | 脚本名称 |
| @namespace | 唯一标识符 |
| @match | 匹配规则 (推荐) |
| @grant | 申请权限 |
| @require | 引入JS库 |
| @run-at | 执行时机 |
| @connect | 允许跨域域名 |
| document-start | 文档开始加载 |
| document-body | <body>出现时 |
| document-end | DOM完成 (默认) |
| document-idle | 页面完全加载 |
// @match *://*/* // 所有 http/https
// @match https://google.com/*
// @include /https:\/\/.*\.com\/.*/ // 正则
// @exclude https://*/login
需 @grant GM_setValue 等。
// 存储(支持任意类型)
GM_setValue('config', { theme: 'dark' });
// 读取(支持默认值)
const conf = GM_getValue('config', {});
// 删除
GM_deleteValue('config');
// 列出所有键
const keys = GM_listValues();
// 监听变化(跨标签页)
GM_addValueChangeListener('key', (n, o, v, r) => {
console.log('Changed:', v);
});
跨域请求需 @grant GM_xmlhttpRequest 和 @connect。
GM_xmlhttpRequest({
method: 'GET',
url: 'https://api.example.com/data',
headers: {
'Authorization': 'Bearer token'
},
onload: (res) => {
if (res.status === 200) {
console.log(res.responseText);
}
},
onerror: (err) => console.error(err),
ontimeout: () => console.error('超时')
});
GM_addStyle(`
#my-panel {
position: fixed;
top: 10px; right: 10px;
z-index: 99999;
}
`);
// @resource myCSS https://site.com/s.css
const css = GM_getResourceText('myCSS');
GM_addStyle(css);
// @resource icon https://site.com/icon.png
const iconUrl = GM_getResourceURL('icon');
// 返回 base64 数据 URL
GM_registerMenuCommand('⚙️ 设置', () => {
toggleSettings();
}, 's'); // 快捷键
// 桌面通知
GM_notification({
text: '任务完成',
title: '提示',
onclick: () => console.log('点击')
});
// 写入剪贴板
GM_setClipboard('文本内容');
// 新标签页打开
GM_openInTab(url, { active: false });
SPA 必备,监听动态加载元素。
const obs = new MutationObserver(muts => {
for (const m of muts) {
for (const node of m.addedNodes) {
if (node.nodeType === 1 &&
node.matches('.ad-banner')) {
node.remove();
}
}
}
});
obs.observe(document.body, {
childList: true,
subtree: true
});
function waitFor(selector, timeout = 10000) {
return new Promise((resolve, reject) => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector));
}
const observer = new MutationObserver(() => {
const el = document.querySelector(selector);
if (el) {
observer.disconnect();
resolve(el);
}
});
observer.observe(document.body, {
childList: true, subtree: true
});
setTimeout(() => {
observer.disconnect();
reject(new Error('Timeout'));
}, timeout);
});
}
// await waitFor('.btn');
function debounce(fn, delay = 300) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
function throttle(fn, interval = 300) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
Greasemonkey 4+ 风格的异步 API。
(async () => {
// 数据存储
await GM.setValue('key', 123);
const val = await GM.getValue('key');
await GM.deleteValue('key');
const keys = await GM.listValues();
// 网络请求(回调方式不变)
GM.xmlHttpRequest({
url: '...',
onload: res => console.log(res)
});
})();
| GM.setValue | 异步存储 |
| GM.getValue | 异步读取 |
| GM.xmlHttpRequest | 网络请求 |
// 访问页面原生 window
unsafeWindow.nativeFunction();
// 暴露调试变量
unsafeWindow.myDebug = { config, data };
// 全局错误
window.addEventListener('error', (e) => {
console.error('[Script]', e.error);
});
// Promise 错误
window.addEventListener('unhandledrejection', (e) => {
console.error('[Script]', e.reason);
});
自动暗黑模式脚本
// ==UserScript==
// @name 暗黑模式
// @match https://example.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_addStyle
// ==/UserScript==
(function() {
const KEY = 'darkEnabled';
let enabled = GM_getValue(KEY, true);
function apply() {
if (enabled) {
GM_addStyle(`body {
background: #1a1a1a !important;
color: #e0e0e0 !important;
}`);
}
}
GM_registerMenuCommand('🌙 切换', () => {
enabled = !enabled;
GM_setValue(KEY, enabled);
location.reload();
});
apply();
})();