某天拿到了一个刷课的网站,需要帮忙刷一下课,但是呢这个网站
- 打开之后不自动播放
- 播放完一个视频不会自动播放下一个
- 不能直接倍速
- 不能拖动(这个解决不了QAQ)
- 检测出倍速需要点击弹出框来关闭,从而继续刷课
- 切换一个课程后有时候会弹出一个alert点击来播放视频
嗯……就这么多破事儿
实现
实现方法
油猴(篡改猴)扩展程序
自动播放视频
一开始的思路是直接
1
| document.querySelector('video').play
|
让视频播放。
但是在网页的控制台中这个方法可以,但是写到油猴脚本里没法运行,还是不能播放视频。因此使用浏览器模拟点击的方法。
在点击之前,首先要找到video视频标签,这个网页的video元素使用的应该是用js动态加载出来的,所以监听页面加载完毕的方法判断video元素加载到了页面上不太好用(实际上确实不咋好用),于是使用递归查找video元素
查找页面中的video元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function hasVideo() { if (!document.querySelector('video')) { setTimeout(hasVideo, 500); console.log('再次检查'); } else { const currentVideo = document.querySelector('video'); if (currentVideo.paused) { console.log('当前视频未播放,开始自动播放...'); setTimeout(() => { checkVideoPlaying(); }, 500); } } }
|
如果视频未开始播放,那就调用checkVideoPlaying();
函数
检查视频是否未播放
1 2 3 4 5 6 7 8 9 10 11 12 13
| function checkVideoPlaying() { setTimeout(() => { simulateClickOnVideo(); console.log('当前视频开始播放'); }, 200); setTimeout(() => { checkPlaybackRate(); }, 500); }
|
模拟点击一下video元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function simulateClickOnVideo() { const video = document.querySelector('video'); if (video) { const clickEvent = new MouseEvent('click', { view: window, bubbles: true, cancelable: true }); video.dispatchEvent(clickEvent); console.log('模拟点击 video 元素'); } else { console.log('未找到 video 元素'); } }
|
倍速
在检查视频是否未播放的时候,里面写了一个函数,checkPlaybackRate();
检测视频是否为2倍速播放
先让视频两倍速播放,如果不是两倍速重新检查
网站支持两倍速,但是有时候他还是会检测出倍速然后暂停…………解决方法见6. 浏览器模拟点击弹出的按钮
1 2 3 4 5 6 7 8 9 10 11
| function checkPlaybackRate() { document.querySelector('video').playbackRate = 2; const currentVideo = document.querySelector('video'); if (currentVideo.playbackRate == 2) { console.log('2倍速播放成功') } else { setTimeout(checkPlaybackRate, 500); } checkCourseLearned(); }
|
学完自动播放下一个视频
检测是否学完
这里就要按照这个网站的特征来分析了

把鼠标放在这个对钩上面发现显示出了“已学完”

这样的是“进行中”

这样的是“未开始”
所以从这里入手分析

选中这个元素发现里面有一个<i>
标签,里面的title
是进行中,所以就判断这个title
是不是已学完就可以了
同时发现正在播放的视频这里有一个active,没有播放的视频没有这个带active的类,所以就寻找这个带active的类,看这个类下面的<i>
标签中的title是否是“已学完”。


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function checkCourseLearned() { console.log('checkCourseLearned调用'); const items = document.querySelectorAll('.resource-item.resource-item-train.resource-item-active');
items.forEach(item => { const iElement = item.querySelector('i');
if (iElement && iElement.title === '已学完') { console.log('找到一个已学完的资源项:', item); playNextVideo(); } else { listenVideoEnd(); } }); }
|
监听视频是否播放完
1 2 3 4 5 6 7 8 9 10
| function listenVideoEnd() { const currentVideo = document.querySelector('video'); console.log('listenVideoEnd调用'); currentVideo.addEventListener('ended', function () { console.log('当前视频播放结束,正在尝试播放下一个视频...'); playNextVideo(); }); }
|
播放下一个视频
首先要找到当前激活的项const activeItem = document.querySelector('.resource-item.resource-item-train.resource-item-active');
,然后找到下一个没有激活的项,也就是.resource-item.resource-item-train
,找到之后就点击这个项
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
| function playNextVideo() { console.log('playNextVideo调用'); const activeItem = document.querySelector('.resource-item.resource-item-train.resource-item-active'); if (!activeItem) { console.log('没有找到当前激活的资源项'); return; } else { console.log(activeItem) }
let nextItem = activeItem.nextElementSibling; while (nextItem && (!nextItem.classList.contains('resource-item') || !nextItem.classList.contains('resource-item-train'))) { nextItem = nextItem.nextElementSibling; } if (!nextItem) { nextItem = activeItem.parentNode.querySelector('.resource-item.resource-item-train'); }
if (nextItem) { simulateClick(nextItem, function () { setTimeout(handleAlertAndPlayVideo, 3000); }); } else { console.log('没有找到下一个资源项或资源项不符合条件'); }
hasVideo(); }
|
模拟点击
1 2 3 4 5 6 7 8 9 10 11
| function simulateClick(element, callback) { const clickEvent = new MouseEvent('click', { 'view': window, 'bubbles': true, 'cancelable': true }); element.dispatchEvent(clickEvent); setTimeout(callback, 0); }
|
处理alert
这个可能不会生效……所以后面又写了一个直接浏览器模拟点击按钮的
1 2 3 4 5 6 7 8 9 10
| function handleAlertAndPlayVideo() { if (window.alert) { window.alert = function (message) { console.log('模拟点击alert确定按钮'); }; } }
|
浏览器模拟点击弹出的按钮

经分析,所有的弹出按钮都有这个类
但是运行的时候发现,一打开脚本就跳转到搜索界面,所以肯定是某个搜索的地方也有这个类

果不其然,这个搜索图标也使用了这个类,所以再排除这个fish-input-search-button
类就好了
这里是怎么发现的呢…………本来这个课是放在虚拟机里刷的,因为不能离开这个网页,所以网页比例就没调,但是在某一瞬间缩小了一下比例,本来是只有一个搜索按钮的地方突然变出来一个搜索框,由于之前比例太大这个搜索框没显示,但是现在显示了,所以多了这么一个类,于是就需要排除这个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function simulateClickOnPrimaryButton() { const button = document.querySelector('.fish-btn.fish-btn-primary'); if (button) { const classList = button.classList; if (classList.contains('fish-input-search-button')) { console.log('按钮包含 fish-input-search-button 类,不进行点击'); } else { const clickEvent = new MouseEvent('click', { view: window, bubbles: true, cancelable: true }); button.dispatchEvent(clickEvent); console.log('模拟点击 fish-btn fish-btn-primary 按钮'); } } else { console.log('未找到 fish-btn fish-btn-primary 按钮'); } }
|
播放完的Bug
有时候视频播放完会出来一个Bug,就是视频不自动暂停,这时候如果用鼠标点一下暂停键,脚本才会检测到video.ended,所以再加一个判断,判断视频已播放时长是否等于总时长
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function isVideoDurationEqual() { const video = document.querySelector('video'); if (video) { const currentTime = video.currentTime; const duration = video.duration; const tolerance = 1;
if (Math.abs(currentTime - duration) <= tolerance) { console.log('视频播放时长等于总时长'); setTimeout(() => { simulateClickOnVideo(); }, 3000); } } }
|
持续执行的函数
1 2 3
| setInterval(simulateClickOnPrimaryButton, 3000); setInterval(checkPlaybackRate, 3000); setInterval(isVideoDurationEqual, 3000);
|
这三个函数让他每隔三秒重复执行就好了。其他的整个代码逻辑使用递归的方法让整个脚本持续运行
总代码
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 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
|
(function () { 'use strict'; console.log('Autoplay js start'); hasVideo();
function playv() { document.querySelector('video').play() }
setInterval(playv, 1000); setInterval(simulateClickOnPrimaryButton, 3000); setInterval(checkPlaybackRate, 3000); setInterval(isVideoDurationEqual, 3000);
function simulateClickOnPrimaryButton() { const button = document.querySelector('.fish-btn.fish-btn-primary'); if (button) { const classList = button.classList; if (classList.contains('fish-input-search-button')) { console.log('按钮包含 fish-input-search-button 类,不进行点击'); } else { const clickEvent = new MouseEvent('click', { view: window, bubbles: true, cancelable: true }); button.dispatchEvent(clickEvent); console.log('模拟点击 fish-btn fish-btn-primary 按钮'); } } else { console.log('未找到 fish-btn fish-btn-primary 按钮'); } }
function hasVideo() { if (!document.querySelector('video')) { setTimeout(hasVideo, 500); console.log('再次检查'); } else { const currentVideo = document.querySelector('video'); if (currentVideo.paused) { console.log('当前视频未播放,开始自动播放...'); setTimeout(() => { checkVideoPlaying(); }, 500); } } }
function checkVideoPlaying() { console.log('checkVideoPlaying调用'); setTimeout(() => { simulateClickOnVideo(); console.log('当前视频开始播放'); }, 200); setTimeout(() => { checkPlaybackRate(); }, 500); }
function simulateClickOnVideo() { const video = document.querySelector('video'); if (video) { const clickEvent = new MouseEvent('click', { view: window, bubbles: true, cancelable: true }); video.dispatchEvent(clickEvent); console.log('模拟点击 video 元素'); } else { console.log('未找到 video 元素'); } }
function checkPlaybackRate() { document.querySelector('video').playbackRate = 2; const currentVideo = document.querySelector('video'); if (currentVideo.playbackRate == 2) { console.log('2倍速播放成功') } else { setTimeout(checkPlaybackRate, 500); } checkCourseLearned(); }
function checkCourseLearned() { console.log('checkCourseLearned调用'); const items = document.querySelectorAll('.resource-item.resource-item-train.resource-item-active');
items.forEach(item => { const iElement = item.querySelector('i');
if (iElement && iElement.title === '已学完') { console.log('找到一个已学完的资源项:', item); playNextVideo(); } else { listenVideoEnd(); } }); }
function listenVideoEnd() { const currentVideo = document.querySelector('video'); console.log('listenVideoEnd调用'); currentVideo.addEventListener('ended', function () { console.log('当前视频播放结束,正在尝试播放下一个视频...'); playNextVideo(); }); }
function playNextVideo() { console.log('playNextVideo调用'); const activeItem = document.querySelector('.resource-item.resource-item-train.resource-item-active'); if (!activeItem) { console.log('没有找到当前激活的资源项'); return; } else { console.log(activeItem) }
let nextItem = activeItem.nextElementSibling; while (nextItem && (!nextItem.classList.contains('resource-item') || !nextItem.classList.contains('resource-item-train'))) { nextItem = nextItem.nextElementSibling; } if (!nextItem) { nextItem = activeItem.parentNode.querySelector('.resource-item.resource-item-train'); }
if (nextItem) { simulateClick(nextItem, function () { setTimeout(handleAlertAndPlayVideo, 3000); }); } else { console.log('没有找到下一个资源项或资源项不符合条件'); }
hasVideo(); }
function simulateClick(element, callback) { const clickEvent = new MouseEvent('click', { 'view': window, 'bubbles': true, 'cancelable': true }); element.dispatchEvent(clickEvent); setTimeout(callback, 0); }
function handleAlertAndPlayVideo() { if (window.alert) { window.alert = function (message) { console.log('模拟点击alert确定按钮'); }; } }
function isVideoDurationEqual() { const video = document.querySelector('video'); if (video) { const currentTime = video.currentTime; const duration = video.duration; const tolerance = 1;
if (Math.abs(currentTime - duration) <= tolerance) { console.log('视频播放时长等于总时长'); setTimeout(() => { simulateClickOnVideo(); }, 3000); } } } })();
|
使用
安装篡改猴插件

打开管理面板

添加脚本

将代码扔进去,ctrl + s保存


启用

脚本成功运行
Bug
其实是懒得写还没实现的功能
比如:当最后一个课程学完后,需要打开下一个折叠的层


才能自动播放下一个折叠层中的视频
所以播放完一个视频应该先遍历一下所有.resource-item.resource-item-train
的类中的所有<i>
标签的title是否都为“已学完”,然后再找到折叠层的类,使用浏览器模拟点击让他折叠打开,然后再点击课程…………因为这里没必要所以没写
折叠层的类:
判断点击这里就可以实现自动展开了