Skip to content
鼓励作者:欢迎打赏犒劳

油猴初体验

插件编写要求

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);
    }
})();

如有转载或 CV 的请标注本站原文地址