我的第一个油猴脚本

我的第一个油猴脚本

Tampermonkey 中文名俗称油猴,是一款免费的浏览器插件,目前最为流行的用户脚本管理器,用户可以通过油猴添加和使用脚本,而脚本是一种可以修改网页 JavaScript 的程序。通过这些脚本,可以实现视频解析、音乐下载、网盘直连解析、豆瓣资源下载、百度文库增强、屏蔽网站广告等功能。总之,通过油猴,我们可以实现很多想象不到的强大功能,而这些功能的背后就是依托庞大的脚本市场,如 greasyfork:https://greasyfork.org/zh-CN,我编写的脚本也是发布在这上面。

1、契机

从我接触到浏览器插件就用上了油猴,也算是打开了新世界的大门一样,接触了各式各样的脚本,比如:网页 vip 视频解析、百度网盘直链下载、百度文库复制、csdn 免登录复制、网页去广告、刷网课脚本等等,给我的上网冲浪生活带来了很多的便利,greasyfork 上的脚本基本上都能满足我的需求,而最近学校开了网课,网课每次看完之后就暂停了,要手动点下一节,这就产生了需求,而恰好 greasyfork 上没有这样的脚本,于是萌生了自己实现这样一个脚本的想法。

2、初步构思

  1. 在课程列表界面,自动检测没有完成的课程,点进去观看
  2. 课程视频自动播放且静音,开启视频倍速播放
  3. 检测视频的完成度,当完成度大于 95 则跳转至下一单元
  4. 直到所有课程都观看完毕

3、技术

需求实现实质就是一些逻辑判断,获取到指定的元素的值,然后做 if 判断以及页面跳转

  1. 原生 js 的 document 对象,获取指定的 dom 元素
  2. jQuery 方便获取 dom 节点
  3. 油猴提供的 API,GM_getValue 根据 key 获取 value、GM_setValue 设置 key-value 对、unsafeWindow 是油猴提供的沙盒环境,在 unsafeWindow 环境下,可以使用油猴提供的强大函数

4、脚本开发

在油猴插件的脚本管理界面创建新的脚本即可。我是用在 vscode 中写 js 代码,再复制到油猴中保存,然后在浏览器调试。这样挺不方便的,也没有去研究更高效的办法。

因为我对 js 也只是一知半解,再加上也没有那么多时间去研究,只以实现功能为准,没有去追求代码的优雅。

代码写的比较暴力,用了很多计时器。

5、脚本代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// ==UserScript==
// @name scnu华南师范大学网课脚本
// @namespace http://tampermonkey.net/
// @version 1.2
// @description scnu 华南师范大学 长江雨课堂 网课自动化脚本
// @author hqzqaq
// @icon https://statics.scnu.edu.cn/statics/images/favicon.ico
// @grant GM_getValue
// @grant GM_setValue
// @grant unsafeWindow
// @match https://scnuyjs.yuketang.cn/pro/*
// @run-at document-end
// @license MIT
// @require https://cdn.bootcss.com/jquery/1.10.2/jquery.min.js
// ==/UserScript==

(function () {
"use strict";
// 多长时间刷新一下页面,单位 分钟
const reloadTime = 10;
// 视频播放速率,可选值 [1,1.25,1.5,2],默认为二倍速
const rate = 2;

window.onload = function () {
// 网课页面跳转
function getElTooltipItemList() {
return document.getElementsByClassName("el-tooltip leaf-detail");
}

function getElTooltipList() {
return document.getElementsByClassName("el-tooltip f12 item");
}

// 静音
function claim() {
$(
"#video-box > div > xt-wrap > xt-controls > xt-inner > xt-volumebutton > xt-icon"
).click();
}

function fun(className, selector)
{
var mousemove = document.createEvent("MouseEvent");
mousemove.initMouseEvent("mousemove", true, true, unsafeWindow, 0, 10, 10, 10, 10, 0, 0, 0, 0, 0, null);
document.getElementsByClassName(className)[0].dispatchEvent(mousemove);
document.querySelector(selector).click();
}

// 加速
function speed() {
let keyt = '';
if(rate === 2 || rate === 1){
keyt = "[keyt='" + rate + ".00']"
}else{
keyt = "[keyt='" + rate + "']"
}
fun("xt_video_player_speed", keyt);
}

const getElementInterval = setInterval(function () {
const elTooltipList = getElTooltipList();
const elTooltipItemList = getElTooltipItemList();
if (elTooltipList) {
for (let index = 0; index < elTooltipList.length; index++) {
const element = elTooltipList[index];
const textContent = element.textContent;
//const textContent = ''
if (textContent === "未开始" || textContent === "未读") {
// 判断是否是习题
if(elTooltipItemList[index].innerText.indexOf('习题')!= -1){
continue;
}
// 判断是否已过学习时间
if (elTooltipItemList[index].children[1].children[0].innerText.indexOf("已过") != -1) {
continue;
}
window.clearInterval(getElementInterval);
GM_setValue("rowUrl", window.location.href.toString());
// 网课页面跳转
elTooltipItemList[index].click();
window.close();
break;
}
}
}
}, 1000);

let video;
const videoPlay = setInterval(function () {
// 获取播放器
video = document.getElementsByClassName("xt_video_player")[0];
if (!video) {
return;
}
setTimeout(function () {
// 视频开始5s之后再开启倍速
speed()
},5000);
claim();
window.clearInterval(videoPlay);
}, 500);

// 是否播放完成的检测
const playTimeOut = setInterval(function () {
if (!video) {
return;
}
video.play();

// 没有静音
if (video.volume != 0) {
claim();
}
const completeness = $(
"#app > div.app-wrapper > div.wrap > div.viewContainer.heightAbsolutely > div > div.video-wrap > div > div > section.title > div.title-fr > div > div > span"
);
if (!completeness) {
return;
}
if (typeof completeness[0] == "undefined") {
return;
}
const videoText = completeness[0].innerHTML
if (videoText) {
let str = videoText.toString();
const succ = str.substring(4, str.length - 1);
const succNum = parseInt(succ);
if (succ >= 95) {
const url = GM_getValue("rowUrl");
if(url){
window.clearInterval(playTimeOut);
window.location.replace(url);
}
}
}

}, 1000);

// 是否为阅读类型
const readInterval = setInterval(function () {
const read = $(
"#app > div.app-wrapper > div.wrap > div.viewContainer.heightAbsolutely > div > div.graph-wrap > div > div > section.title > div.title-fr > div > div"
);
if(!read){
return
}
if (typeof read[0] == "undefined") {
return;
}
const readText = read[0].innerHTML
if(readText){
if(readText.toString() === '已读'){
window.clearInterval(readInterval);
window.location.replace(GM_getValue("rowUrl"));
}
}
}, 1000);

// 为了防止页面假死,定时刷新一下页面
setTimeout(function () {
// 如果保存了课程列表路径就回退的课程列表页面
if(GM_getValue("rowUrl")){
window.location.replace(GM_getValue("rowUrl"));
}
location.reload()
},reloadTime * 60 * 1000);
};
})();

代码比较简单,简单的逻辑判断,最难的一个是视频倍速播放,这个 video 倍速播放是锁住的,调整倍速播放的属性是无效的,我就想直接触发它提供的二倍速也行,没想到这个也很困难,视频倍速播放的按钮,首先需要触发鼠标悬浮才有效,这就无法实现自动化的效果了。因为我对 js 的不熟悉,在网上也没有找到解决办法,尝试了各种办法都没有效果,本来一天就能弄完,为了这一个功能,又搭进去好几天~~~,最后是想到去读 greasfork 上其他人实现这种功能的代码,没想到还真让我找到了,就是下面这一段珍贵的代码:

1
2
3
4
5
6
7
function fun(className, selector)
{
var mousemove = document.createEvent("MouseEvent");
mousemove.initMouseEvent("mousemove", true, true, unsafeWindow, 0, 10, 10, 10, 10, 0, 0, 0, 0, 0, null);
document.getElementsByClassName(className)[0].dispatchEvent(mousemove);
document.querySelector(selector).click();
}

先触发 dom 元素的 mousemove 事件

之前在网上也有看到过类似的描述,只是我不知道这个 mousemove.initMouseEvent()怎么传参,再加上控制台报错,我就觉得这种方法不行,没想到也是解决办法是远在天边,近在眼前,不过好歹绕回来了。

6、注解介绍

代码的前面被 \==UserScript\== 包裹的注释,包含了一些注解,定义了脚本的一些元信息,接下来介绍几个常用的注解,更多的注解信息可以访问官方文档:https://www.tampermonkey.net/documentation.php。

  1. @name 定义脚本的名字
  2. @version 定义脚本的版本,在有代码更新的时候,需要改变版本的值,这样才能将代码推送给用户
  3. @description 脚本的描述
  4. @author 作者名字
  5. @icon 脚本的图标,可以是 url 地址,也可以是图片的 base64 编码
  6. @grant 获取权限,使用它可以获取油猴提供的 API
  7. @match 脚本生效的网址匹配,只有符合匹配规则的网址,才会执行脚本
  8. @run-at 脚本代码执行的时间,有好几个值,这里我用的是 document-end,即当所有的 dom 元素加载完毕之后再执行代码
  9. @license 开源许可证,这里我用的是 MIT,即所有人都可查看和修改代码
  10. @require 引入外部的 js,这里我引入了 jQuery

7、脚本的发布

前往 https://greasyfork.org/zh-CN 网址,登录,点击自己的头像即可发布自己编写的脚本。

填入脚本源码和脚本使用说明即可。

8、脚本使用

8.1、功能实现

目前已实现:

  1. 当进入课程界面,查找未开始和已经有完成度的课程和未读的材料,自动看视频,直到所有视频看完为止
  2. 视频界面,自动播放视频、静音、5秒之后默认开启二倍速

未实现:

  1. 答题,所有习题都会跳过,需要手动答题

只是做了些自动化的处理,理论上是不会有风险的(当然只是理论上,自行考虑 ^_^)

8.2、使用

本脚本只适用于华南师范大学长江雨课堂

8.3、安装油猴

以edge浏览器为例,在扩展商店搜索tampermonkey,安装油猴插件

image-20221003232246888

8.4、安装脚本

在 greasyfork(https://greasyfork.org/zh-CN) 网站,搜索scnu华南师范大学网课脚本关键字,安装脚本

image-20221003234101801 image-20221003234133621

8.5、脚本运行

前往 scnu 华南师范大学 长江雨课堂(https://scnuyjs.yuketang.cn/pro/portal/home/),登录,进入课程界面,即可开始刷课。
注意:edge 浏览器需要允许网站弹窗(注意地址栏的提示)脚本才能执行跳转,这个很关键,不然脚本无法正常执行。

image-20221003232615741

8.6、参数调整

总共有两个参数可以调整

  • 一个是页面刷新的时间,防止网络不好或者其它原因造成页面假死,默认为10分钟。
  • 一个是视频倍速的速率,有四个值,1、1.25、1.5、2;默认为 2

可自己在代码编辑进行修改

image-20221008084124867

9、总结

可以解决面临的问题,这就是开发的快乐吧。

借用赛博丁真的一句话:不觉得很酷吗?作为一个理科生,我觉得太酷了(手动狗头)

  • Copyrights © 2022-2023 hqz

请我喝杯咖啡吧~

支付宝
微信