前言
本文全程使用原生JavaScript进行编程,不使用像jQuery这样的第三方库
视频链接
零基础油猴脚本编写指南【上】 (对应0x01~0x05)
0x00 前置知识
本节简单的介绍一下一些名词的概念,了解为主,直接跳过也是可以的
-
HTML、CSS和JS
HTML即超文本标记语言,它用来描述各种网页元素,类似与一份清单,浏览器读取以后根据它来加载对应的资源
CSS即层叠样式表,它只用来描述网页元素的外观,还可以添加少许动画,但是不影响功能
JS即JavaScript,它是一种网页脚本语言,用来让静态的网页能够响应各种动作,
还可以挖矿如果把HTML比作木偶,那么CSS就是木偶的衣服,JS就是木偶的提线,用来操控木偶
-
油猴和浏览器
当你访问链接的时候,浏览器会先下载链接对应的文件(通常是一个HTML文件),然后根据HTML描述的对象列表,来下载图片、视频等资源,有的CSS和JS内嵌在HTML文件里,有的则是单独的文件,等必要的资源加载完成以后,浏览器才能生成可以交互的网页
油猴实际上是一个加载器,它把用户脚本安插在最开始获取到的HTML文件中,这样浏览器加载网页的时候,也会加载特定的用户脚本,这样我们就能够通过编写用户脚本来修改网页了
-
HTML5和DOM
HTML5是现行的HTML标准,DOM即文档对象模型,是一用对象表示HTML标签的方法,通过DOM,我们可以简单的修改文档对象的属性,来轻松修改HTML标签的内容
0x01 初识开发者工具
工欲善其事必先利其器,本节简单的介绍一下开发者工具的使用。
本节以我的博客作为演示,地址:chrxw.com
个人比较喜欢Firefox的开发者工具,以下介绍均使用Firefox进行演示,如果使用Chrome的话可能布局会有细微差异,不过功能是类似的。
Chrome自带的开发者工具貌似没有汉化,建议使用别的Chromium内核的浏览器(比如新版Edge),功能都差不多。
-
在浏览器中按
F12
即可打开开发者工具 -
查看器,左侧可以查看页面的HTML内容,右侧显示的是选中的DOM元素的CSS
-
控制台,可以运行自己编写的JS代码。
也可以在设置里打开“分离式控制台”,这样就可以在所有TAB里都显示控制台了。
-
调试器,可以用来单步调试JS
-
网络,可以查看浏览器的网络请求
-
储存,在这个TAB中可以查看网站在本地保存的内容,最常用的是Cookie,其次是LocalStorge(本地储存),还有个由浏览器自动管理的Session(会话储存),JS可以访问的只有Cookie和LocalStorge。
0x02 初识DOM
本节通过使用开发者工具,简单的演示一下如何操作DOM元素,以我的博客作为演示,地址:chrxw.com
-
首先打开开发者工具,然后激活选择器,然后点击“标题”,这样在右边的查看器中就会显示我们点击的对象
-
右键高亮的元素,然后点击“在控制台使用”(如果是Chromium或者Chrome,点击“复制”/“Copy”->“复制JS路径”/“Copy JS path”,然后把复制结果粘贴到控制台中)
-
Firefox自动创建了一个
temp0
变量,按一下回车,可以看到控制台输出了一个DOM元素,点一下左边的箭头展开,可以看到这个对象的各种属性。 -
接下来我们尝试修改一下它的内容
temp0.textContent = 'hello world';
按回车执行,然后可以看到,网页的标题已经被修改了
-
接着我们修改一下CSS,让它居中显示
temp0.style.textAlign='center';
-
接下来我们创建一个新的DOM元素,然后插入到网页中
let btn = document.createElement('button'); btn.textContent = '按钮'; btn.addEventListener('click',() => { alert('hello world'); }); temp0.appendChild(btn);
点一下新出现的按钮,可以看到消息弹窗
代码讲解:
PS: document 也是DOM元素,它代表整个网页,其他DOM元素都是它的子集。
-
let btn = document.createElement('button'); btn.textContent = '按钮';
这两行代码创建了一个
button
对象,它内部的文本为按钮
,由浏览器自动生成的HTML表达式为<button>按钮</button>
-
btn.addEventListener('click',() => { alert('hello world'); });
这三行代码为
btn
对象添加了事件监听器,addEventListener
的第一个参数是事件监听器的名称,第二个参数是接收到指定事件后调用的函数,()=>{ ... }
是匿名函数的缩略写法,匿名函数还有另一种等效写法function(){ ... }
,或者写成普通函数,也是可以的。//匿名函数写法2 btn.addEventListener('click',function(){ alert('hello world'); });
//普通函数的写法 btn.addEventListener('click',foo);//foo后面不加() function foo(){ alert('hello world'); }
添加好事件监听器以后,点一下按钮,就会触发
click
事件,然后由浏览器调用我们绑定的方法alert('hello world');
,弹出消息框 -
temp0.appendChild(btn);
A.appendChild(B)
的作用是把B元素插入到A元素的子元素的末尾,也就是把btn
对象插到temp0
中,由浏览器自动帮我们修改了HTML文本//修改前 <h1 class="m-n font-thin text-black l-h">Chr_小屋</h1> //修改后 <h1 class="m-n font-thin text-black l-h">Chr_小屋 <button>按钮</button> //这是我们新创建的DOM元素 </h1>
-
-
通过操作DOM元素的属性,就可以很方便的修改网页的显示效果,添加各种功能。
0x03 DOM进阶之获取DOM元素
通过修改DOM元素的属性,可以很方便地修改网页,但是首先我们得获取我们想要修改的DOM元素
本节将会介绍如何获通过代码获取现存的DOM元素,以我的博客作为演示,地址:about.html
1. 使用ID进行获取
WEB开发者设计网页的时候,会给一些经常需要修改的元素添加
id
属性,原则上每个元素的id
都是不同的,所以如果某个元素具有id
属性,我们就可以使用getElementById
来获取需要注意的是,如果元素没有
id
属性的话这个方法就行不通了
随便打开一篇博文,最下面的评论框是带有id
属性的,使用开发者工具我们可以看到,它的id
为comment
我们可以用document.getElementById( id名称 )
的方式获取DOM元素
let box = document.getElementById('comment');
box.value = '通过ID获取DOM';
代码非常好理解,先调用getElementById
方法获取DOM,然后修改它的value
属性,效果就是文本框的内容被修改了
PS: textContent 属性修改的是HTML文本,而 value 属性修改的是文本框中的内容,不影响HTML文本
2. 使用CSS选择器进行获取
在一个网页中,具有
id
属性的元素不会太多,大多数时候都是没有这个属性的,这时候可以根据元素的class
属性,使用querySelector(获取符合条件的第一个元素)
或者querySelectorAll(获取符合条件的全部元素数组)
进行获取本节只介绍常用的方法,更详细的介绍可以参考这个:CSS 选择器参考手册
-
根据
class
进行选择标题元素的
class
为entry-title m-n font-thin text-black l-h
,class
选择器的语法为.
+class名称
,如果有多个class
名称,则用.
分隔所以标题元素的选择器写法为
.entry-title.m-n.font-thin.text-black.l-h
在CSS选择器语法中,空格代表子元素的意思,所以不能随意添加空格。
通过搜索,我们发现
entry-title
在HTML文档只出现了一次,因此选择器也可以简化写作.entry-title
,代码如下//两种写法等效 document.querySelector('.entry-title.m-n.font-thin.text-black.l-h'); document.querySelector('.entry-title');
在实际运用中,没必要给出完整的
class
,只需要写独一无二的部分即可 -
根据
id
进行选择(可以用,但还是建议用getElementById
)id
选择器的语法为#
+id名称
,评论框的id
是comment
,所以选择器写法为#comment
,代码如下//两种写法等效,推荐第一种 document.getElementById('comment'); document.querySelector('#comment');
-
根据元素类型选择
还可以根据DOM元素的类型进行选择,如果网页中只有一个特定种类的元素,那么直接写元素类型即可
使用开发者工具,我们可以知道,评论框的类型为
textarea
,而且整个网页只有一个textarea
,因此选择器可以直接写成textarea
,代码如下document.querySelector('textarea');
-
根据相对关系进行选择
有的时候,想要操作的元素可能没有独一无二的属性,那么就可以根据相对关系进行选择
A>B
表示在A
的一级子元素中查找B
A B
表示在A
的内部查找B
(查找范围不仅包括子元素,还包括孙元素等)A+B
表示与A
相邻的B
元素(平级关系,非父子关系)这里的 A 和 B 代表的是选择器,选择器可以嵌套使用。
A.B
,不表示相对关系,它表示类型是A
,class
名是B
的元素,这个要注意。还是用标题元素举例子
先找到容易定位的元素,然后再根据相对关系寻找想要找的元素,方法有很多,下面举几个例子
//以下语句都是等效的 document.querySelector('main>div>div>header>h1');//一级子元素 document.querySelector('main header>h1');//所有子元素 document.querySelector('main h1');//这样也可以 document.querySelector('header>h1');//多个方法定位header对象 document.querySelector('#small_widgets>h1'); document.querySelector('.bg-light.lter.wrapper-md>h1'); document.querySelector('header.bg-light>h1');
写成这个样子主要是为了举例子,实际使用中选择器越简洁越好
-
根据其他属性名称进行选择
[xxx]
选择带有xxx
属性的元素[xxx=sss]
选择xxx
属性等于sss
的元素[xxx~=sss]
选择xxx
属性包含sss
的元素(sss
必须是整个单词)[xxx|=sss]
选择xxx
属性以sss
开头的元素(sss
必须是整个单词)[xxx^=sss]
选择xxx
属性以sss
开头的元素(sss
可以是单词的一部分)[xxx$=sss]
选择xxx
属性以sss
结尾的元素(sss
可以是单词的一部分)[xxx*=sss]
选择xxx
属性包含sss
的元素(sss
可以是单词的一部分)通过开发者工具可以看出高亮的DOM元素具有
for
属性,属性值为comment
,代码如下,感受一下几种用法的区别document.querySelector('[for=comment]');//完整匹配 document.querySelector('[for|=co]');//匹配开头,匹配失败,for属性中没有co这个单词 document.querySelector('[for~=co]');//匹配任意位置,匹配失败,for属性中没有co这个单词 document.querySelector('[for^=co]');//匹配开头 document.querySelector('[for*=co]');//匹配任意位置 document.querySelector('[for*=mm]');//匹配任意位置 document.querySelector('[for$=nt]');//匹配结尾
-
组合选择器
上面介绍过的选择器可以组合使用,比如选择表情按钮中的矢量图片
代码如下,自己体会一下各个部分是什么意思,以及哪些部分是没有必要的
//两种写法效果一样 document.querySelector('textarea.form-control[name=text]+div.OwO>.OwO-logo svg'); document.querySelector('.OwO-logo svg');
0x04 DOM进阶之修改DOM元素
拿到了DOM对象,接下来就可以进行各种操♂作了
本节将会介绍DOM元素的常用方法,以我的博客作为演示,地址:about.html
以标题元素为例,首先我们先获取DOM对象
let h1 = document.querySelector('h1');
console.log(h1);
使用开发者工具可以非常方便的看到DOM元素的方法和属性
枚举子元素
DOM.children
返回值为DOM元素的所有子元素数组,如果不含有子元素,返回的是空数组。
可以通过DOM.children[i]
的方式获取某一个子元素
获取/修改文本内容
DOM.textContent
返回值为当前DOM元素内的显示字符(没有被 < > 包起来的所有字符),不仅包括自身,还包括所有子元素
DOM.innerText
也有类似的效果,区别是它会去掉多余的换行符
也可以修改DOM.textContent
和`DOM.innerText
不过要注意的是,这两个方法会直接替换掉元素内部的内容,如果元素内部有其他子元素的话,子元素会消失。
获取/修改元素CSS
只能修改DOM元素的内联CSS,全局CSS得修改Style元素才行。
CSS相关内容不做过多介绍,贴个简单的教程:菜鸟CSS教程
DOM.style
,获取DOM元素的内联CSS样式
DOM.style.[css名称]
,获取DOM元素的特定CSS属性,例如textAlign
DOM.style.cssText
,获取DOM元素的内联CSS样式,纯文本格式
同样的,也可以用类似的方法修改CSS样式
DOM.style.[css名称] = 'xxx'
,修改DOM元素的特定CSS属性为xxx
DOM.style.cssText = 'aaa: xxx;'
,直接修改DOM元素的内联CSS文本,可以一次设置多个CSS
如果想隐藏某些元素的话,可以把它的CSS的
display
属性设置成none
,就会被隐藏起来了
新增子元素
DOM.appendChild( [要插入的DOM对象] )
appendChild
会将新插入的对象放在所有子元素的末尾
DOM.insertBefore( [要插入的DOM对象] , [DOM对象,将要插在这个对象之前])
insertBefore
可以指定新插入的对象的位置
let h1 = document.querySelector('h1');
let b1 = document.createElement('button');
b1.textContent='按钮1';
let b2 = document.createElement('button');
b2.textContent='按钮2';
h1.appendChild(b1);
h1.insertBefore(b2,h1.children[0]);
删除对象
DOM.remove()
这是个方法,所以要加()
,调用后会在HTML文本中删除对象对应的内容
添加/删除事件监听器
DOM.addEventListener( [事件名称] , [回调函数])
添加事件监听器
DOM.removeEventListener( [事件名称] , [回调函数])
移除事件监听器
也可以用以前的写法(不推荐):
DOM.on[事件名称] = [回调函数]
添加事件监听器
DOM.on[事件名称] = null
移除事件监听器
比如要让h1
对象响应click
事件,代码如下
let h1 = document.querySelector('h1');
//比较推荐这种写法
h1.addEventListener('click',() => {
alert('响应click事件');
});
//也可以用旧风格的写法
h1.onclick = () => {
alert('响应click事件');
};
//一些解释:
//() => { ... } 定义了一个匿名函数,相当于 function(){ ... }
//也可以使用实名函数,比如定义了function foo(){ ... }以后,也可以这么绑定:
//h1.addEventListener('click',foo);
//h1.onclick = foo;
删除绑定的方法也是类似的,不再赘述
模拟鼠标点击
DOM.click()
相当于鼠标点了一下DOM对象,触发它的click
事件(如果有的话)
0x05 第一个油猴脚本
讲了这么久,都是在介绍前端的基础知识,接下来开始正文
首先让我们新建一个用户脚本
按Ctrl
+S
即可保存
元信息
元信息是给油猴插件使用的,用来记录脚本的属性
新建脚本时给出的默认元信息如下:
// ==UserScript==
// @name New Userscript //名称
// @namespace http://tampermonkey.net/ //命名空间
// 说明:@namespace相同,@name也相同的脚本会被油猴当做是同一个脚本,这两个属性起到区分不同脚本的作用
// @version 0.1 //版本号
// @description try to take over the world! //说明
// @author You //作者
// @match http://*/* //在什么网址下启用该脚本
// @grant none //特殊权限(调用GM_开头的函数需要先申请权限)
// ==/UserScript==
还有部分比较常见的元信息:
// @include /https://example.com/.*/ //和@match差不多,但是支持正则表达式,我更喜欢用这个
// @connect example.com //如果脚本需要访问跨域资源,需要提前申明
// @license AGPL-3.0 //脚本的授权许可信息
// @icon https://blog.chrxw.com/favicon.ico //脚本的图标(显示在脚本列表里)
代码区域
(function() {
'use strict';
// Your code here...
})();
首先定义了一个匿名函数function() { ... }
,也可以写成我常用的格式,都是等效的:
(() => {
'use strict';
// Your code here...
})();
用()
把匿名函数包起来,再在后面加一个()
,表示直接执行这个匿名函数
其实完全可以把代码写到匿名函数的外部,都可以执行,只是定义域不同而已
'use strict';
是伪代码,它告诉解释器,开启严格模式
开启严格模式以后,会进行更严格的语法检查,建议开启,有利于养成良好的编码习惯
// 非严格模式,允许访问未定义变量
(() => {
y = 3.14; // 不报错
})();
// 严格模式,允许访问已定义变量
(() => {
"use strict"; // 启用严格模式
let y = 3.14; // 不报错(y已定义)
})();
// 严格模式,不允许访问未定义变量
(() => {
"use strict"; // 启用严格模式
y = 3.14; // 报错(y未定义)
})();
实现功能
接下来我们为脚本编写一些功能,以我的博客作为演示,地址:about.html
-
首先我们需要修改元信息,让脚步只在特定的网页生效
然后添加作者名称和脚本名称(除了@match都可以随便写)
// ==UserScript== // @name 第一个油猴脚本 // @namespace http://blog.chrxw.com/ // @version 0.1 // @description hello world // @author Chr_ // @match https://blog.chrxw.com/about.html // @grant none // ==/UserScript==
-
我们先不编写脚本,我们先使用开发者工具测试一下代码
首先我们找一个对象下手(如图)
它没有
id
属性,但是它的class
名称独一无二,所以我们可以直接用document.querySelector('.item-thumb')
获取到DOM元素let a = document.querySelector('.item-thumb');
-
修改元素的背景图
a.style.backgroundImage='url(/usr/customize/logo.png)';
-
添加点击事件
a.addEventListener('click',() => { a.style.background='#f0f'; });
点一下以后背景变成了品红色
-
最后我们编写油猴脚本,让这一切自动完成
// ==UserScript== // @name 第一个油猴脚本 // @namespace http://blog.chrxw.com/ // @version 0.1 // @description hello world // @author Chr_ // @match https://blog.chrxw.com/about.html // @grant none // ==/UserScript== (() => { 'use strict'; let a = document.querySelector('.item-thumb'); a.style.backgroundImage='url(/usr/customize/logo.png)'; a.addEventListener('click',() => { a.style.background='#f0f'; }); // Your code here... })();
保存脚本以后,刷新页面,可以看到元素的背景图片已经改掉了,说明脚本运行成功了。
0x06 脚本实战,Auto_Sub3
脚本获取链接:Auto_Sub3
脚本功能
安装脚本以后,首页可以打开一个面板,主要功能是一键-3
元信息
// ==UserScript==
// @name Auto_Sub3
// @namespace https://blog.chrxw.com
// @version 2.1
// @description 一键快乐-3
// @author Chr_
// @include https://keylol.com/*
// @license AGPL-3.0
// @icon https://blog.chrxw.com/favicon.ico
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// ==/UserScript==
元信息提供了这个脚本的基本信息,我们还可以知道,它申请了GM_setValue
,GM_getValue
和GM_xmlhttpRequest
三个特殊权限
代码总览
// 上次-3时间
let VLast = 0;
// 今天还能不能-3
let VCan3 = true;
// 自动展开
let VShow = false;
// 自动-3
let VAuto = false;
// 音效载入
let sound = new Audio("https://blog.chrxw.com/usr/keylol/gas.mp3");
(function () { // 每次加载以后从这里运行
'use strict';
loadCFG();
addBtns();
if (VShow) {
switchPanel();
}
if (VCan3 && VAuto) {
autoRoll();
}
})();
// 添加GUI
function addBtns() { ... }
// 显示小轮盘
function showLiteRoll() { ... }
// 自动打开面板
function fBtnShow() { ... }
// 自动-3
function fBtnAuto() { ... }
// 显示布尔
function bool2txt(bool) { ... }
// 隐藏面板
function switchPanel() { ... }
// 读取设置
function loadCFG() { ... }
// 保存设置
function saveCFG() { ... }
// 检查能否-3
function checkZP() { ... }
// 禁止-3
function disableS3() { ... }
// 自动-3
function autoRoll() { ... }
先不用太纠结代码,建议先用调试模式看一下代码流程
脚本调试模式
在设置里可以打开调试模式,开启以后在加载完脚本以后会自动暂停,可以用来动态调试脚本
打开调试模式以后,先打开开发者工具,然后刷新页面,可以看到暂停在debugger
语句上了
给我们感兴趣的函数添加断点,然后点“继续运行(F8)”
可以自己尝试跟踪一下每个函数的运行过程,不再赘述
代码详解
脚本入口
// 上次-3时间
let VLast = 0;
// 今天还能不能-3
let VCan3 = true;
// 自动展开
let VShow = false;
// 自动-3
let VAuto = false;
// 音效载入
let sound = new Audio("https://blog.chrxw.com/usr/keylol/gas.mp3");
(function () {
'use strict';
loadCFG();
addBtns();
if (VShow) {
switchPanel();
}
if (VCan3 && VAuto) {
autoRoll();
}
})();
首先定义了5个全局变量,然后运行了匿名函数,在这个匿名函数里完成了脚本的初始化工作
在匿名函数内部调用了这么几个函数:loadCFG
,addBtns
,switchPanel
,autoRoll
loadCFG/saveCFG - 读写配置
// 读取设置
function loadCFG() {
let t = null;
t = GM_getValue('VLast');
t = Number(t);
if (t != t) { t = 0; }
VLast = t;
t = GM_getValue('VCan3');
VCan3 = Boolean(t);
t = GM_getValue('VShow');
VShow = Boolean(t);
t = GM_getValue('VAuto');
VAuto = Boolean(t);
// 日期变更,重置VCan3
let d = new Date();
let day = d.getDate();
let hour = d.getHours();
if (day != VLast && hour >= 8) {
VCan3 = true;
VLast = day;
}
saveCFG();
}
// 保存设置
function saveCFG() {
GM_setValue('VLast', VLast);
GM_setValue('VCan3', VCan3);
GM_setValue('VShow', VShow);
GM_setValue('VAuto', VAuto);
}
脚本保存数据的方法有很多种,可以利用原生的Cookie
,也可以利用HTML5标准中的LocalStorge
,但是这两个地方只能保存文本,我们还需要自行完成类型转换,非常不方便,所以我用的是油猴自带的储存空间
// 需要在元信息里提前申明,才可以使用
// @grant GM_setValue
// @grant GM_getValue
GM_setValue( 键名 , 键值)
,保存某个值
GM_getValue( 键名 )
,如果键名不存在,返回null
可以在脚本编辑器的储存
TAB看到当前脚本的储存空间
saveCFG
非常简单,就是把4个全局变量保存起来
loadCFG
也很简单,就是从储存空间读取4个变量,然后获取现在的时间和日期,判断日期有没有变更,如果日期变了,并且当前小时数大于8
,就把Vcan3
赋值成true
,然后把VLast
改成今天的日期,最后调用saveCFG
保存4个全局变量
addBtns - 添加GUI
// 添加GUI
function addBtns() {
function genButton(text, foo, id) {
let b = document.createElement('button');//创建类型为button的DOM对象
b.textContent = text; //修改内部文本为text
b.style.cssText = 'margin: 3px 5px;' //添加样式(margin可以让元素间隔开一定距离)
b.addEventListener('click', foo); //绑定click的事件的监听器
if (id) { b.id = id; } //如果传入了id,就修改DOM对象的id
return b; //返回修改好的DOM对象
}
function genDiv(cls) {
let d = document.createElement('div'); //创建类型为div的DOM对象
if (cls) { d.className = cls }; //如果传入了cls,就修改DOM对象的class
return d; //返回修改好的DOM对象
}
function genPanel(name, visiable) {
let p = genDiv(name); //创建类型为div,class为name的DOM对象
p.id = name; //修改DOM对象的id为name
p.style.cssText = 'width: 100%;height: 100%;';//修改DOM对象的样式(占满父容器)
if (!visiable) { p.style.display = 'none'; }//修改DOM对象的样式(不可见)
return p; //返回修改好的DOM对象
}
//使用CSS选择器获取对象
let btnSwitch = document.querySelector('.index_left_title>div');
if (btnSwitch == null) { return; }
btnSwitch.id = 'btnSwitch1';
btnSwitch.title = '点这里开启/隐藏控制面板';
btnSwitch.style.cssText = 'width: auto;padding: 0 5px;cursor: pointer;';
btnSwitch.addEventListener('click', switchPanel);
let panelArea = document.querySelector('.index_navi_left');
let panelOri = document.querySelector('.index_left_detail');
panelOri.id = 'panelOri'
let panel54 = genPanel('panel54');
let aLP = document.createElement('a');
aLP.href = 'https://keylol.com/t571093-1-1';
let img54 = document.createElement('img');
img54.src = 'https://gitee.com/chr_a1/gm_scripts/raw/master/index.png';
img54.alt = '总之这里是54的名言';
img54.style.cssText = 'float: right;margin-top: -28px;height: 100%;'
aLP.appendChild(img54);
let btnArea = genDiv('btnArea');
btnArea.style.cssText = 'width: 210px;text-align: center;margin-top: -10px;';
let btnS3 = genButton('【一键-3】', autoRoll, 'btnS3');
if (!VCan3) {
btnS3.style.textDecoration = 'line-through';
btnS3.textContent = '今天已经不能-3了';
}
let btnShow = genButton(bool2txt(VShow) + '自动打开面板', fBtnShow, 'btnShow');
let btnAuto = genButton(bool2txt(VAuto) + '自动每日-3', fBtnAuto, 'btnAuto');
btnArea.appendChild(btnS3);
btnArea.appendChild(btnShow);
btnArea.appendChild(btnAuto);
panel54.appendChild(aLP);
panel54.appendChild(btnArea);
panelArea.insertBefore(panel54, panelArea.children[1]);
}
看着很长,实际上很简单,首先定义了3和内部函数genButton
,genDiv
,genPanel
,用来生成我们需要的DOM元素
然后尝试用选择器获取DOM对象
let btnSwitch = document.querySelector('.index_left_title>div');//使用CSS选择器获取DOM对象
if (btnSwitch == null) { return; } //如果页面中不存在这个对象,就直接返回,结束运行
btnSwitch.id = 'btnSwitch1'; //添加id属性
btnSwitch.title = '点这里开启/隐藏控制面板'; //添加title属性(鼠标悬停的时候显示的文本)
btnSwitch.style.cssText = 'width: auto;padding: 0 5px;cursor: pointer;';//添加CSS样式
btnSwitch.addEventListener('click', switchPanel);//绑定click事件的回调函数为switchPanel
可以利用开发者工具看一下是哪个对象
剩下的部分也很好理解,创建3个button
对象,1个img
对象,依次添加到名为panel54
的div
对象里,最后再把panel54
添加到panelArea
中
let panelArea = document.querySelector('.index_navi_left');
let panelOri = document.querySelector('.index_left_detail');
panelOri.id = 'panelOri'
let panel54 = genPanel('panel54');
let aLP = document.createElement('a');
aLP.href = 'https://keylol.com/t571093-1-1';
let img54 = document.createElement('img');
img54.src = 'https://gitee.com/chr_a1/gm_scripts/raw/master/index.png';
img54.alt = '总之这里是54的名言';
img54.style.cssText = 'float: right;margin-top: -28px;height: 100%;'
aLP.appendChild(img54);
let btnArea = genDiv('btnArea');
btnArea.style.cssText = 'width: 210px;text-align: center;margin-top: -10px;';
let btnS3 = genButton('【一键-3】', autoRoll, 'btnS3');
if (!VCan3) {
btnS3.style.textDecoration = 'line-through';
btnS3.textContent = '今天已经不能-3了';
}
let btnShow = genButton(bool2txt(VShow) + '自动打开面板', fBtnShow, 'btnShow');
let btnAuto = genButton(bool2txt(VAuto) + '自动每日-3', fBtnAuto, 'btnAuto');
btnArea.appendChild(btnS3);
btnArea.appendChild(btnShow);
btnArea.appendChild(btnAuto);
panel54.appendChild(aLP);
panel54.appendChild(btnArea);
panelArea.insertBefore(panel54, panelArea.children[1]);
通过switchPanel
,可以切换显示原来的元素和脚本的面板
// 隐藏面板
function switchPanel() {
let panel1 = document.getElementById('panel54');
let panel2 = document.getElementById('panelOri');
if (panel1.style.display == 'none') {
btnSwitch1.textContent = '今天你【-3】了吗 - By Chr_';
panel1.style.display = 'block';
panel2.style.display = 'none';
} else {
btnSwitch1.textContent = '关注重点';
panel1.style.display = 'none';
panel2.style.display = 'block';
}
}
运行效果:
autoRoll - 自动-3
最后我们看一下核心功能,自动-3
// 自动-3
function autoRoll() {
try {
sound.play();
} catch (e) {
console.error(e);
}
let v = 0;
gethash();
function gethash() {
GM_xmlhttpRequest({
method: "GET",
url: 'https://keylol.com/plugin.php?id=steamcn_lottery:view&lottery_id=43',
onload: function (response) {
if (response.status == 200) {
let m = response.responseText.match(/plugin\.php\?id=steamcn_lottery:view&lottery_id=43&hash=(.+)&roll/);
let hash = m ? m[1] : null;
console.log(hash);
if (hash != null) {
roll(hash);
roll(hash);
roll(hash);
} else {
disableS3();
saveCFG();
}
} else {
console.error('出错');
}
}
});
}
function roll(hash) {
GM_xmlhttpRequest({
method: "GET",
url: 'https://keylol.com/plugin.php?id=steamcn_lottery:view&lottery_id=43&hash=' + hash + '&roll',
onload: function (response) {
if (response.status == 200) {
console.log(response.responseText);
} else {
console.error('出错')
}
if (++v == 3) {
disableS3();
saveCFG();
}
}
});
}
}
乍一看非常复杂,实际上也很好理解
-
try { sound.play(); } catch (e) { console.error(e); } let v = 0; gethash();
首先调用
sound.play()
,播放蒸汽泄漏的音效,sound
是全局变量,会在网页载入的时候自动加载音频资源,如果遇到错误就忽略错误(比如网络问题加载音频失败的话就会报错)然后调用了
gethash
方法 -
function gethash() { GM_xmlhttpRequest({ method: "GET", url: 'https://keylol.com/plugin.php?id=steamcn_lottery:view&lottery_id=43', onload: function (response) { if (response.status == 200) { let m = response.responseText.match(/plugin\.php\?id=steamcn_lottery:view&lottery_id=43&hash=(.+)&roll/); let hash = m ? m[1] : null; console.log(hash); if (hash != null) { roll(hash); roll(hash); roll(hash); } else { disableS3(); saveCFG(); } } else { console.error('出错'); } } }); }
-
这个函数使用了
GM_xmlhttpRequest
,这个东西是一个网络请求器,深入了解可以参考官方文档(英语),GM_xmlhttpRequest
类似于xmlHttpRequest
,两者用法几乎一样,只是GM_xmlhttpRequest
不存在跨域的问题// 需要先在元信息里申明,才可以使用 // @grant GM_xmlhttpRequest
简单的用法如下
GM_xmlhttpRequest({ method: '', //请求方法,可以是 GET POST HEAD 等 url: '', //请求路径,简单的说就是url onload: (response) => { //请求被响应时执行,response是接受到的响应的对象 //response.status,获取响应代码(200代表请求成功,4xx和5xx代表请求出错) //response.responseText,获取响应文本,本例中获取到的是HTML文本 //response.responseXML,获取XML格式的响应文本 } });
-
看到
onload
的回调函数部分onload: (response) => { if (response.status == 200) { let m = response.responseText.match(/plugin\.php\?id=steamcn_lottery:view&lottery_id=43&hash=(.+)&roll/); let hash = m ? m[1] : null; console.log(hash); if (hash != null) { roll(hash); roll(hash); roll(hash); } else { disableS3(); saveCFG(); } } else { console.error('出错'); } }
首先判断状态码是不是200(代表请求成功),请求成功才会处理响应的内容
可以在浏览器中打开链接,用开发者工具模拟一下处理过程
我们把响应的内容当做纯文本处理,使用正则表达式提取想要的内容
正则表达式深入学习可以参考这个:正则表达式 - 语法
let a = document.body.innerHTML; console.log(a.match(/plugin\.php\?id=steamcn_lottery:view&lottery_id=43&hash=(.+)&roll/));
简单的讲一下上面的正则表达式是什么意思
/plugin\.php\?id=steamcn_lottery:view&lottery_id=43&hash=(.+)&roll/ // 两边的 / 就是告诉解释器里面是正则表达式,没别的意思 // 按照不同元素拆分,如下: // plugin - 匹配“plugin”这个字符串 // \. - 匹配“.”,“.”原来有别的意思,在前面加“\”转义以后就只有字面意思了 // id=steamcn_lottery:view&lottery_id=43&hash= - 匹配一整串字符串 // (.+) - “()”是子匹配的意思,“.+”意思是匹配至少一个字符 // - “(.+)”的意思是不仅要匹配至少一个字符,而且匹配到的文本需要返回 // &roll - 匹配“&roll”这个字符串
简单点说就是先找到
plugin.php?id=steamcn_lottery:view&lottery_id=43&hash=[XXXXXXX]&roll
这一串字符串,然后提取中间的XXXXXXX
执行效果如下,可以看到返回了一个数组,第一个元素是匹配到的字符串,第二个元素是子匹配项,我们只需要子匹配项,也就是
8b51a2d3
(这个值不同用户是不一样的,所以需要动态获取)然后就是赋值操作,用到了三目运算符号
let hash = m ? m[1] : null; // D = A ? B : C // 等价于: if(A){ D = B; }else{ D = C; } // 当A的逻辑意义为true时,返回值为B // 当A的逻辑意义为false时,返回值为C // 类似这样 let a = true ? 1 : 2; // a = 1 let a = false ? 1 : 2; // a = 2
如果正则表达式没有匹配到合适的字符串,返回的内容是
null
,逻辑意义等于false
,这时候hash
的值就取null
如果正则表达式匹配到了合适的字符串,返回的内容是数组,逻辑意义等于
true
,这时候hash
的值就取数组的第2个元素然后根据
hash
决定是不是要继续运行if (hash != null) { roll(hash); roll(hash); roll(hash); } else { disableS3(); saveCFG(); }
-
-
最后是
roll
函数,核心也是一个网络请求器function roll(hash) { GM_xmlhttpRequest({ method: "GET", url: 'https://keylol.com/plugin.php?id=steamcn_lottery:view&lottery_id=43&hash=' + hash + '&roll', onload: function (response) { if (response.status == 200) { console.log(response.responseText); } else { console.error('出错') } if (++v == 3) { disableS3(); saveCFG(); } } }); }
实际上就是访问
https://keylol.com/plugin.php?id=steamcn_lottery:view&lottery_id=43&hash=[XXXXXXX]&roll
这个网站,然后把响应结果打印出来可以根据上一步得到的
hash
在浏览器中试一下调用以后,会自增
v
,如果v = 3
,就调用disableS3
和saveCFG
,修改了按钮的名称,然后保存全局变量
本文链接:https://blog.chrxw.com/archives/2021/02/08/1449.html
转载请保留本文链接,谢谢
15 条评论
|´・ω・)ノ
|´・ω・)ノ
非常感谢!入门小白拜读。
搜索答案半天,发现无法在匿名函数上调用removeEventListener
目前只有将移除事件写在添加事件当中,满足触发条件之后移除事件;
在F12调试中无法实现添加完之后再进行移除。
前辈辛苦了!拜读中
"GM_setValue( 键名 ),如果键名不存在,返回null",这句写错了:"GM_getValue( 键名 ),如果键名不存在,返回null"
修正了,感谢反馈
你好!我下载了你文中实战的脚本(Auto_Sub3),在脚本编辑器里没有看到"储存"TAB,请问要怎样设置才能看到!我的Tampermonkey版本为 V4.11
脚本要调用过 GM_setValue 才会有“存储”TAB,你可以访问一次 keylol.com,应该就会有“存储”TAB了。
谢谢!
收藏了
添加/删除事件监听器 中 “DOM.addEventLiremoveEventListenerstener( [事件名称] , [回调函数])移除事件监听器” 写错了 ,应该是removeEventListener
感谢指出,已经修正了
好耶 讲的很详细 入门足矣
可以可以,收藏了