
油猴初体验
插件编写要求
js
// ==UserScript==
// @name 油猴脚本的名字
// @namespace 这个是命名空间,用来区分名称相同但是作者不同的用户脚本,一般都是写作者的个人网址,没有也可以写你的博客地址
// @version 0.1.0 这个是版本号
// @description 这个是功能描述
// @author 这个是作者的名字
// @match 这个是该脚本匹配的网址,支持通配符匹配
// @include 这个也是该脚本匹配的网址,支持通配符匹配
// @exclude 这个和 iclude 配合使用,排除匹配到的网址,优先于 include
// @grant none
// @license MIT 证书。最好直接写上,不然发布脚本的时候会出现提醒
// ==/UserScript==
@run-at运行时机
作用:指定脚本的执行时机,不同场景需要不同的时间点。取值包括:
- document-start:文档开始加载时执行。
- document-end:文档加载完成时执行(默认)。
- document-idle:文档空闲时执行。
- context-menu:右键菜单点击时执行。
用法示例:
js
// @run-at document-start
@grant权限
没有 @grant
如果脚本中未使用任何 GM_* API,等同于 @grant none。
如果脚本中使用了 GM_* API,Tampermonkey 会自动启用沙箱模式,并隐式添加 @grant 对应的权限
不推荐依赖隐式行为,建议显式声明 @grant
1、@grant none
: 禁用沙箱环境,脚本直接在页面上下文中运行。
特点:
无法使用 GM_* API(如 GM_setValue, GM_xmlhttpRequest 等)。
可以直接访问和修改页面中的全局变量(如 window)。
脚本与页面共享同一个作用域,可能被页面代码干扰。
js
// ==UserScript==
// @name No Grant Example
// @namespace http://tampermonkey.net/
// @version 1.0
// @grant none
// ==/UserScript==
// 直接操作页面中的变量
document.querySelector('button').click();
console.log('直接访问页面变量:', window.pageVariable);
2、@grant unsafeWindow
: 启用沙箱环境,但允许通过 unsafeWindow 访问原始页面的 window 对象。
特点:
可以使用 GM_* API。
通过 unsafeWindow 间接访问页面变量(需要手动操作)。
脚本主体在沙箱中运行,安全性更高。
js
// ==UserScript==
// @name UnsafeWindow Example
// @namespace http://tampermonkey.net/
// @version 1.0
// @grant unsafeWindow
// @grant GM_setValue
// ==/UserScript==
// 使用 GM_* API
GM_setValue('key', 'value');
// 通过 unsafeWindow 操作页面变量
unsafeWindow.pageVariable = 'modified';
console.log('页面变量已修改:', unsafeWindow.pageVariable);
第一个油猴插件
js
// ==UserScript==
// @name 测试脚本
// @namespace https://blog.share888.top
// @version 2025-04-29
// @description 测试一下
// @author 敲代码的卡卡罗特
// @match http://*/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant none
// ==/UserScript==
(function() {
'use strict';
alert('HelloWorld')
// Your code here...
})();
引入css和js
引用外部JS文件
使用 @require 元数据指令直接引入JS文件,脚本会在用户脚本运行前加载这些资源。
js
// ==UserScript==
// @name My Script
// @namespace http://tampermonkey.net/
// @version 1.0
// @match https://example.com/*
// @require https://code.jquery.com/jquery-3.6.0.min.js
// @grant none
// ==/UserScript==
// 引入后可直接使用jQuery
console.log('jQuery版本:', $.fn.jquery);
引用外部CSS文件
方法1:通过 @resource 和 GM_addStyle(推荐)
使用 @resource 声明CSS资源。
通过 GM_getResourceText 获取内容,再用 GM_addStyle 插入样式。
js
// ==UserScript==
// @name My Styled Script
// @namespace http://tampermonkey.net/
// @version 1.0
// @match https://example.com/*
// @grant GM_addStyle
// @grant GM_getResourceText
// @resource customCSS https://example.com/path/to/style.css
// ==/UserScript==
(function() {
'use strict';
// 获取并插入CSS
const css = GM_getResourceText('customCSS');
GM_addStyle(css);
})();
方法2:动态创建 标签
直接通过DOM操作插入外部CSS链接。
js
// ==UserScript==
// @name Dynamic CSS Example
// @namespace http://tampermonkey.net/
// @version 1.0
// @match https://example.com/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://example.com/path/to/style.css';
document.head.appendChild(link);
})();
总结:如何确保资源加载完成?
方法 | 加载类型 | 确保加载完成的方案 |
---|---|---|
@require | 同步 | 天然阻塞,无需额外处理 |
动态 <script> | 异步 | 监听 script.onload |
动态 <link> | 异步 | 监听 link.onload(部分浏览器支持) |
@resource + CSS | 同步 | 直接使用,无需等待 |
GM_xmlhttpRequest
当我们调用别人网站的接口的时候,大部分会出现跨域问题,为了解决这个问题,我们可以利用油猴的GM函数来处理。
js
// ==UserScript==
// @name 跨域问题解决
// @namespace http://tampermonkey.net/
// @version 2025-05-10
// @description 跨域问题解决
// @author You
// @match http://localhost:63342/demo/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function () {
"use strict";
function getFollowingCount(vmid) {
const url = `https://api.bilibili.com/x/relation/stat?vmid=${vmid}`;
GM_xmlhttpRequest({
method: "GET",
url: url,
responseType: "json",
onload: function (response) {
if (response.status == 200) {
const data = response.response;
if (data.code === 0) {
console.log(`用户 ${vmid} 的粉丝数为: ${data.data.follower}`);
console.log(`用户 ${vmid} 的关注数为: ${data.data.following}`);
} else {
console.error(`API error! Message: ${data.message}`);
}
} else {
console.error(`HTTP error! Status: ${response.status}`);
}
},
onerror: function (error) {
console.error("Error fetching following count:", error);
},
});
}
const vmid = 27589677;
getFollowingCount(vmid);
})();
fetch拦截
我们可以利用fetch拦截来实现抓包
js
//使用URLSearchParams构建查询参数
const params = new URLSearchParams();
params.append('url', 'https://blog.share888.top');
fetch('https://my-api.share888.top/my-api/seo/getWebsiteInfo?' + params.toString(), {
method: 'GET',
}).then(response => response.json())
.then(data => {
console.log('上传成功', data);
}).catch(error => {
console.error('上传失败', error);
});
脚本
js
// ==UserScript==
// @name fetch 拦截
// @namespace http://tampermonkey.net/
// @version 2025-05-10
// @description 个人博客:https://blog.share888.top
// @author You
// @match http://localhost:63342/demo/*
// @icon https://blog.share888.top/favicon.ico
// @grant unsafeWindow
// @run-at document-start
// ==/UserScript==
(function () {
//注意:在油猴脚本中,window是指的油猴的window,并不是页面的,如果要访问页面的window,则需要window.unsafeWindow来访问
//得到是页面的fetch
const originFetch = window.unsafeWindow.fetch;
//修改页面的fetch
window.unsafeWindow.fetch = (url, options) => {
return originFetch(url, options).then(async (response) => {
console.log(url);
if (url.startsWith("https://my-api.share888.top") ){
const responseClone = response.clone();
let res = await responseClone.json();
res.data.xx="油猴脚本修改数据";
const responseNew = new Response(JSON.stringify(res), response);
return responseNew;
} else {
return response;
}
});
};
})();
xhr拦截
该xhr拦截脚本非常的完美,博主辛辛苦苦调试出来的。
- 支持get,post请求参数自定义
- 支持自改返回值
页面
js
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://my-api.share888.top/my-api/seo/getWebsiteInfo?url=https://blog.share888.top");
//post请求才有用
let data = {
key1: "value1",
key2: "value2"
};
let jsonData = JSON.stringify(data);
xhr.send(jsonData);
xhr.onload = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}
};
拦截脚本
js
// ==UserScript==
// @name xhr 拦截
// @namespace http://tampermonkey.net/
// @version 2025-05-10
// @description 个人博客:https://blog.share888.top
// @author You
// @match http://localhost:63342/demo/*
// @icon https://blog.share888.top/favicon.ico
// @grant unsafeWindow
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// 保存原始XMLHttpRequest对象
const originalXHR = unsafeWindow.XMLHttpRequest;
// 重写XMLHttpRequest
unsafeWindow.XMLHttpRequest = function() {
const xhr = new originalXHR();
// 劫持open方法
const originalOpen = xhr.open;
xhr.open = function(method, url) {
this._method = method;
this._url = url;
console.log(`拦截请求: ${method} ${url}`);
// 只处理GET参数修改
if (method === 'GET') {
const modifiedUrl = modifyGetParams(url);
console.log('[GET修改] 原始URL:', url, '修改后URL:', modifiedUrl);
url = modifiedUrl; // 使用修改后的URL
}
return originalXHR.prototype.open.call(this, method, url, true);
};
// 劫持send方法
const originalSend = xhr.send;
xhr.send = function(data) {
// POST参数修改
if (this._method === 'POST' && data) {
try {
const modifiedData = modifyPostData(data);
return originalSend.call(this, modifiedData);
} catch (e) {
console.error('POST数据处理失败:', e);
}
}
return originalSend.apply(this, arguments);
//修改返回值
this.addEventListener('load', function() {
console.log(`响应状态: ${this.status}`);
console.log('响应数据:', this.responseText);
// 这里可以修改响应数据
// 保存原始响应
const originalResponse = this.responseText;
try {
// 通过重新定义属性来绕过只读限制
Object.defineProperty(this, 'responseText', {
value: "123", // 修改为你需要的内容
writable: true // 设置为可写
});
// 同时修改 response 属性
Object.defineProperty(this, 'response', {
value: "123",
writable: true
});
} catch (e) {
console.error('修改响应失败:', e);
}
});
};
return xhr;
};
// GET参数修改器
function modifyGetParams(url) {
const urlObj = new URL(url);
// 示例:添加时间戳参数
urlObj.searchParams.set('_t', Date.now());
// 示例:修改现有参数
if (urlObj.searchParams.has('page')) {
urlObj.searchParams.set('page', '666');
}
return urlObj.toString();
}
// POST数据修改器
function modifyPostData(data) {
const obj = JSON.parse(data);
obj.xx = 'xxx'; // 注入数据
return JSON.stringify(obj);
}
})();