本文在参考深入浅出 Greasemonkey一文后,结合自己实践总结而成,文中大部分内容来源于网上原文,只在实践部分加入自己内容。原文是在Firefox里调试的,我用chrome,所以有些地方不太一样。
有几个相关网站不错:
- GreaseSpot is community documentation for user scripting with Greasemonkey.
第 2 章 编写第一个用户脚本
2.1. Hello World
我们步入 Greasemonkey 美妙世界的万里长征将从第一步开始,所有读过技术手册的读者都会很熟悉这一步:让您的电脑打出“Hello world”。
例 2.1. helloworld.user.js
// ==UserScript==
// @name Hello World
// @namespace http://tampermonkey.net/
// @version 0.1
// @description 示例脚本:在所有网页上弹出提示“Hello World!”
// @author You
// @match http://write.blog.csdn.net/mdeditor
// @grant none
// @include *
// @exclude http://diveintogreasemonkey.org/*
// @exclude http://www.diveintogreasemonkey.org/*
// ==/UserScript==
alert('Hello world!');
正如你所见,这个脚本的大部分都是注释。有些注释没什么特殊含义;只是对初学者的一些指导。但是,有一节注释确实有特殊含义,下一节会有详细的解释。
要看到脚本的效果,您首先要安装,然后访问一个网站(例如,Google)。这个页面将会像平时一样显示出来,还会弹出一个对话框:“Hello world!”
2.2. 用元数据描述您的用户脚本
每个用户脚本都含有一段元数据,用来向 Greasemonkey 描述这个脚本自身的信息:作者,执行规则等等。
例 2.2. Hello World 元数据
// ==UserScript==
// @name Hello World
// @namespace http://tampermonkey.net/
// @version 0.1
// @description 示例脚本:在所有网页上弹出提示“Hello World!”
// @author You
// @match http://write.blog.csdn.net/mdeditor
// @grant none
// @include *
// @exclude http://diveintogreasemonkey.org/*
// @exclude http://www.diveintogreasemonkey.org/*
// ==/UserScript==
这里有几条独立的元数据信息,作为一个整体包含在注释中。现在让我们按顺序逐条解释。
首先讲最外面的这层封装。
// ==UserScript==
//
// ==/UserScript==
上述标记很重要,必须完全吻合。用它们来标记用户脚本的元数据段。这段注释可以放在用户脚本的任何部位,但经常会放在靠近顶部的地方。
在元数据段内,第一项是名字。
// @name Hello World
这是用户脚本的名字。它将会在用户第一次安装脚本时在安装对话框(install dialog)中显示出来。之后会显示在“管理用户脚本”对话框中。这个名字应该言简意赅。
@name是可选的。如果存在,它只能被定义一次。如果不存在,将会默认显示用户脚本的去掉扩展名.user.js的文件名。
下一个是命名空间(namespace)。
// @namespace http://diveintogreasemonkey.org/download/
这是一个 URL,用它来区分名称相同但是作者不同的用户脚本。如果您有一个域名,您可以使用它作命名空间。
@namespace是可选的。如果存在,它只能被定义一次。如果不存在,将会默认使用下载用户脚本的网站域名。
下一项是描述。
// @description example script to alert “Hello world!” on every page
1
这是关于用户脚本功能的描述。在您第一次安装脚本时,它将会在安装对话框中显示,之后会在“管理用户脚本”对话框中显示。描述不应多于两句。
@description 是可选的。如果使用它,那么它只能被定义一次。如果不使用,默认会显示为空白。
下面三行是最重要的:@include 和 @exclude URL。
// @include *
// @exclude http://diveintogreasemonkey.org/*
// @exclude http://www.diveintogreasemonkey.org/*
@include 是可选的,表示在指定的网站上执行您的用户脚本。可以明确的指定一个 URL,或者用通配符 * 来代替域名或路径中的部分字符。
@exclude 是可选的,表示在指定的网站上不执行用户脚本。
排除(Excludes)优先于包含(includes),但必须每条规则各占一行。如果您没有任何定义, Greasemonkey 将会对所有的网站执行您的用户脚本。(等同于 @include *)。
元数据可以以任意次序排列。推荐使用@name,@namespace,@description,@include,最后是@exclude,但是其它的顺序也没关系。
2.3. 编写用户脚本代码
我们的第一个用户脚本是在执行时简单地显示一条提示信息:“Hello world!”。
例 2.3. 显示“Hello world!”提示信息
alert('Hello world!');
尽管这段代码仿佛够用了,而且也达到了目的。Greasemonkey 实际上在幕后做了很多的事情来确保用户脚本不会与页面所包含的原有脚本发生严重的冲突。特别是它会自动的把您的用户脚本封装在一个匿名的函数包里。一般情况下,您可以忽视,但是终究有一天会让您遇到麻烦。所以最好现在就了解一下。
最经常遇到的麻烦之一是在用户脚本里定义的变量和函数不能被别的脚本访问。事实上,只要用户脚本运行完了,所有的变量和函数就都不能使用了。如果您期望使用 window.setTimeout 函数,或者在链接挂上字符串式的 onclick 属性然后期望 Javascript 稍后调用您的函数,那么您会遇到问题。
例如,下面这个用户脚本中定义了一个函数helloworld, 然后尝试设置一个计数器来在一秒后调用这个函数。
例 2.4. 延迟调用函数的错误方法
function helloworld() {
alert('Hello world!');
}
window.setTimeout("helloworld()", 60);
这段代码没有起任何作用;不会弹出提示窗口。如果您打开错误控制台,会看到一个异常:Error: helloworld is not defined.这是因为当延迟结束,开始调用helloworld()时,helloworld函数已经不存在了。
如果您需要引用用户脚本中的变量或者函数,应该显式的把它们定义为window对象的属性,它是始终存在的。
例 2.5. 延迟调用函数的更好方法
window.helloworld = function() {
alert('Hello world!');
}
window.setTimeout("helloworld()", 60);
目的达到了!页面完成加载一秒后,一个提示框骄傲的弹了出来,写着:“Hello world!”
然而,在 window上设置属性依然不太理想;这有点像用全局变量来做局部变量该做的事。(事实上,就是那么回事,window是全局的,可以被页面中的所有脚本访问。更实际的讲,它可能会与页面自身的脚本,甚至是其它的用户脚本相互干扰。
最佳的解决方案是定义匿名函数,把它作为第一个参数传递给 window.setTimeout。
例 2.6. 延迟调用函数的最好方法
window.setTimeout(function() { alert('Hello world!') }, 60);
我在这里所做的是建立一个没有名字的函数(一个“匿名函数”),然后直接把它传递给 window.setTimeout。这样可以完成与上个例子相同的事,而不会留下痕迹。例如不会被其它的脚本检测到。
我发现我在写用户脚本时经常使用匿名函数。它们很适合创建“一次性”函数,然后当作参数传递给类似window.setTimeout,document.addEventListener 或者赋值给事件句柄像 click 或 submit。
第 3 章 修改网页操作
在chrome上编辑油猴脚本的时候,可以按F12键,调出chrome的控制台帮助调试,一些错误命令会在控制台上显示出来,比较方便。
油猴脚本库中,有一个下载小说的脚本,我觉得对我这种重度小说依赖症来说,是个福音。因此,我练手的时候,打算以此为蓝本,练习自己的代码。计划是这样:在小说网站的网页上添加一个下载小说的按钮,如果想下载小说,可以按此按钮一键下载。
首先,先看一下,如何在页面上添加一个按钮。
3.1 给网页添加一个按钮
这个脚本用作练习,功能是在网页上指定位置添加一个按钮。
脚本如下:
// ==UserScript==
// @name Hello World_cjn2
// @namespace
// @description
// @include https://www.bixia.org/*
// @include https://bixia.org/*
// ==/UserScript==
(function() {
'use strict';
var button = document.createElement("input"); //创建一个input对象(提示框按钮)
button.setAttribute("type", "button");
button.setAttribute("value", "下载");
button.style.width = "60px";
button.style.align = "center";
button.style.marginLeft = "250px";
button.style.marginBottom = "10px";
button.style.background = "#b46300";
button.style.border = "1px solid " + "#b46300";//52
button.style.color = "white";
var x = document.getElementById("maininfo");
x.appendChild(button);
// Your code here...
})();
效果如下,加载完页面后,会在画圈的位置添加一个按钮:
代码的前半部分,都是用来给按钮添加各种属性。后半部分指明了添加在网页中的位置,这里给元素节点 maininfo 追加一个子节点 button
var x = document.getElementById("maininfo");
x.appendChild(button);
有时候,调试不成功要注意查看控制台输出错误,看看哪里不对。