油猴脚本编写指南

以前各种自动化脚本都是用python来写,虽然方便,但是不利于传播,每次还要费力气自己把各种url、cookie和jwt扒出来,比较费事。于是就想学习一下油猴脚本的编写,直接在浏览器上运行,一来希望可以直接利用浏览器中存储的数据(cookie、jwt),二来使用方便,可以很方便地将自己的脚本分享给别人。除了爬虫的作用,油猴脚本还可以通过修改DOM树直接改变页面的显示,直接增强网页的使用体验。

油猴脚本的运行环境可以理解为就是当前运行的tab,脚本可以如同是网站自带的js脚本一样,访问DOM、BOM提供的各种对象(document、window,localStorage等),可以直接修改dom树。除此之外,Tampermonkey作为运行环境,也提供了许多有用的接口,比如可以通过Tampermonkey的接口来存储键值对,作为脚本的数据库。还可以用Tampermonkey的接口来访问其他url,发送请求以及下载内容。

脚本Header

油猴脚本的开头会通过// @xxx的形式指定一系列脚本头,这些脚本头就是脚本的配置,会被Tampermonkey识别,作为脚本的元数据展示,或者用来限制脚本的权限。

全部header可以参考油猴文档。这里给出一些常用的:

元数据(不会影响脚本的运行):

  1. name: 脚本名称
  2. namespace
  3. version: 脚本版本,用来判断是否需要升级,每次升级的时候应该记得递增这里的版本。
  4. description: 描述信息。
  5. author: 脚本作者
  6. homepage: 脚本首页
  7. icon: 脚本图标(缩略图版)
  8. icon64: 脚本图标(64*64版)
  9. updateURL: 检查更新的url
  10. downloadURL: 下载更新的url

会影响脚本运行的Header(如权限和适用的站点):

  1. grant: 限制哪些GM API可用,只有grant的API才能使用。如果是null,只能使用GM_info
  2. match: 限制此脚本在哪个网站上可用。需要指定三部分:protocol、host和path
  3. require:指定其他脚本的地址,运行此脚本之前会先运行require的脚本。可以用来本地开发时使用,require一个local file的地址。
  4. resource: 使用外部资源,例如外部图片。
  5. sandbox: 脚本运行的沙盒环境,默认是raw,即永远运行在和page相同的MAIN_WORLD。
  6. connect: 指定脚本可以和哪些外部网站交互。如果没有通过connect指定,想通过GM_xmlhttpRequest访问该外部网站的资源时会报错。本站资源不受限制。

GM API

GM API是油猴提供给脚本的额外API接口,通过这些接口脚本可以实现一些额外的功能,例如存储键值对。

常用的接口套件有:

DOM套件

该套件下有:

  1. GM_addElement。该函数有两个版本,不带父节点参数的GM_addElement(tag_name, attributes)和带父节点参数的GM_addElement(parent_node, tag_name, attributes)。如果需要直接在tag中添加内容,则可以在attributes对象中指定textContent。不带父节点的版本会返回一个http element,而带有父节点的版本可以直接将元素附加到DOM上。
  2. GM_addStyle(css)。可以附加额外的css样式。

资源类

  1. GM_download,下载内容,会直接下载到用户的计算机上。如果没有下载,可以通过传入onerror回调函数来查看具体的原因。我遇到过一个not whitelisted,是因为该文件的拓展名没有被加入到白名单中,需要在Tampermonkey的设置中找到下载BETA,然后将对应的文件拓展名加入到下面的输入框中。(如果没有看到下载BETA,需要在上面配置模式中调到Advanced(高级)
  2. GM_getResourceText(name), 可以获得@resource中声明的某个resource的text。
  3. GM_getResourceUrl(name), 可以获得@resource中声明的某个resource的url。

通知和日志

  1. GM_log,等同于console.log()
  2. GM_notification,可以弹出系统通知。image-20231111234830226
  3. GM_openInTab,可以在新标签页打开一个链接。

油猴自定义命令按钮

这套API可以在油猴拓展菜单添加和删除命令按钮。

  1. GM_registerMenuCommand(name, callback, options | accessKey) 可以创建一个命令按钮,当点击按钮的时候触发函数。如果加上accessKey就可以为这个按钮指定一个快捷键。
  2. GM_unregisterMenuCommand(menuCmdId),可以删除一个按钮。

Tab对象存储

这套API可以获得当前的Tab对象(默认是一个空的对象,没有任何属性),加入一些属性后存储。可以把当前tab对象当成一个map,存取自定义的属性键值对。

  1. GM_getTab(callback) 获取tab对象,成功获取后会调用callback,callback的第一个参数就是tab对象。浏览器的同一个tab中先后获得的tab对象是同一个,不同tab不共享。
  2. GM_setTab(tab)保存tab对象。
  3. GM_getTabs(callback) 获得当前浏览器所有有该脚本运行的所有tab和其对象。callback的传入参数是一个tabs对象,其中所有key是tabid,对应的value是tab对象。可以如同这样使用:for(const [tabId, tab] of Object.entries(tabs)){...}

Tampermonkey全局键值对存储

这套API可以使用全局键值对存储。存储的值脚本之间可以共享。

  1. GM_setValue(key, value)
  2. GM_getValue(key, defaultValue)
  3. GM_deleteValue(key)
  4. GM_listValues()
  5. GM_addValueChangeListener(key, (key, old_value, new_value, remote)=>void) 后面的回调接受4个参数,分别是键、旧值、新值和是否是其他脚本实例更改的。
  6. GM_removeValueChangeListener(listenerId)

网络请求

这套API可以用于发送网络请求。

  1. GM_xmlhttpRequest(details) details中可以指定以下关键属性:method请求方法、url请求地址、headers请求头、data请求体、onload请求成功后的回调函数。

需要注意的是,如果请求想要成功,请求url必须和当前站点同源,或者在@connect中声明过。

其他API

  1. window.onurlchange,如果url发生变化时,会触发设置在这里的钩子函数。
  2. window.close() 关闭窗口
  3. GM_setClipboard(data, info) 设置剪贴板。info表示data的类型,比如text或者html。

常用技巧

认证

常见的两种认证:

  1. 通过cookie,这种情况直接请求原网站即可,cookie会被自动带上。例如:

    const ndyy_request = {
        method: "GET",
        url: "http://ndyy.nju.edu.cn/NewWeb/Ashx/API_User_Appointment.ashx?action=selectUserRWList&page=1&limit=15&params=302",
        onload: (response) => {
            GM_log(response.responseText);
        }
    }
    GM_xmlhttpRequest(ndyy_request);
    

    另外,如果要获得cookie,可以用document.cookie得到。

  2. 通过jwt。这种时候直接请求并不会带上请求头,可以从localStorage或者sessionStorage中获取请求头,然后手动指定到请求的headers里面。例如:

    const jwt = sessionStorage.getItem('token');
    const paas_request = {
        method: "GET",
        url: "https://paas.seec.seecoder.cn/api/application",
        onload: (response) => {
            GM_log(response.responseText);
        },
        headers: {
            "Authorization": jwt,
        }
    }
    GM_xmlhttpRequest(paas_request);