我的第一个油猴脚本

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、脚本代码

// ==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 上其他人实现这种功能的代码,没想到还真让我找到了,就是下面这一段珍贵的代码:

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​​,安装油猴插件

SCNU长江雨课堂网课脚本_油猴脚本

8.4、安装脚本

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

SCNU长江雨课堂网课脚本_油猴脚本_02

SCNU长江雨课堂网课脚本_jquery_03

8.5、脚本运行

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

SCNU长江雨课堂网课脚本_js_04

8.6、参数调整

总共有两个参数可以调整

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

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

SCNU长江雨课堂网课脚本_jquery_05