// ==UserScript==
// @name 雨课堂刷课助手
// @namespace http://tampermonkey.net/
// @version 2.1.5
// @description 针对雨课堂视频进行自动播放
// @author 风之子
// @license MIT
// @match *://*.yuketang.cn/*
// @match *://rain.gdufemooc.cn/*
// @icon http://niuwh.cn/favicon.ico
// @grant GM_addStyle
// @require https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.js
// @downloadURL none
// ==/UserScript==
// 雨课堂刷课脚本
/*
已适配雨课堂学校及网址:
学校:中原工学院,河南大学研究院,广东财经大学,辽宁大学,河北大学,中南大学,电子科技大学,华北电力大学,上海理工大学研究生院及其他院校...
网址:changjiang.yuketang.cn,yuketang.cn ...
*/
const version = '2.1.5';
// 视频播放速率,可选值 [1,1.25,1.5,2],默认为二倍速
const rate = 2;
// 添加用户交互窗口
function addWindow() {
// 插入的交互HTML窗口
const outerHTML = `
放大
`
$('body').append(outerHTML);
$('body').append(icon);
// 添加css样式
function addStyle() {
let css = `
ul,
li,
p {
margin: 0;
padding: 0;
}
body {
width: 100vw;
height: 100vh;
}
.n_outer {
margin: 0;
padding: 0;
position: fixed;
top: 0;
left: 0;
min-width: 500px;
height: 250px;
background-color: #fff;
z-index: 99999;
box-shadow: 6px 4px 17px 2px #000000;
border-radius: 10px;
border: 1px solid #a3a3a3;
font-family: Avenir, Helvetica, Arial, sans-serif;
color: #636363;
}
.hide{
display:none;
}
.n_header {
text-align: center;
height: 40px;
background-color: #f7f7f7;
color: #000;
font-size: 18px;
line-height: 40px;
cursor: move;
border-radius: 10px 10px 0 0;
border-bottom: 2px solid #eee;
}
.n_header .tools{
position:absolute;
right:0;
top:0;
}
.n_header .tools ul li{
position:relative;
display:inline-block;
padding:0 5px;
cursor:pointer;
}
.n_header .minimality::after{
content:'最小化';
display:none;
position:absolute;
left:0;
bottom:-30px;
height:32px;
width:50px;
font-size:12px;
background:#ffffe1;
color:#000;
border-radius:3px;
}
.n_header .minimality:hover::after{
display:block;
}
.n_header .question::after{
content:'有问题';
display:none;
position:absolute;
left:0;
bottom:-30px;
height:32px;
width:50px;
font-size:12px;
background:#ffffe1;
color:#000;
border-radius:3px;
}
.n_header .question:hover::after{
display:block;
}
.n_body {
font-weight: bold;
font-size: 13px;
line-height: 26px;
height: 183px;
}
.n_body .n_infoAlert {
overflow-y: scroll;
height: 100%;
}
/* 滚动条整体 */
.n_body .n_infoAlert::-webkit-scrollbar {
height: 20px;
width: 7px;
}
/* 滚动条轨道 */
.n_body .n_infoAlert::-webkit-scrollbar-track {
--webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
border-radius: 10px;
background: #ffffff;
}
/* 滚动条滑块 */
.n_body .n_infoAlert::-webkit-scrollbar-thumb {
border-radius: 10px;
--webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
background: rgb(20, 19, 19, 0.6);
}
.n_footer {
position: absolute;
bottom: 0;
left: 0;
text-align: right;
height: 25px;
width: 100%;
background-color: #f7f7f7;
color: #c5c5c5;
font-size: 13px;
line-height: 25px;
border-radius: 0 0 10px 10px;
border-bottom: 2px solid #eee;
display: flex;
justify-content: space-between;
}
.n_footer button {
border-radius: 6px;
border: 0;
background-color: blue;
color: #fff;
cursor: pointer;
}
.n_footer button:hover {
background-color: yellow;
color: #000;
}
.n_footer #n_zanshang {
cursor: pointer;
position: relative;
color: red;
}
.n_footer #n_zanshang img {
position: absolute;
top: 30px;
left: -130px;
display: none;
width: 300px;
}
.n_footer #n_zanshang:hover img {
display: block;
}
.n_icon{
background:#f5f5f5;
border:1px solid #000;
position:fixed;
top:0;
left:0;
height:50px;
width:50px;
border-radius:6px;
z-index:-9999;
text-align:center;
line-height:50px;
}
`;
GM_addStyle(css);
}
addStyle();
// 窗口的拽拖逻辑
$('.n_header').mousedown(function (e) {
let innerLeft = e.offsetX,
innerTop = e.offsetY
$('body').mousemove(function (e) {
let left = e.clientX - innerLeft,
top = e.clientY - innerTop;
//获取body的页面可视宽高
var clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
var clientWidth = document.documentElement.clientWidth || document.body.clientWidth;
// 通过判断是否溢出屏幕
if (left <= 0) {
left = 0;
} else if (left >= clientWidth - $('.n_outer')[0].offsetWidth) {
left = clientWidth - $('.n_outer')[0].offsetWidth
}
if (top <= 0) {
top = 0
} else if (top >= clientHeight - $('.n_outer')[0].offsetHeight) {
top = clientHeight - $('.n_outer')[0].offsetHeight
}
$('.n_outer').css({
left: () => {
return left + 'px';
},
top: () => {
return top + 'px';
}
})
})
})
$('.n_header').mouseup(function (e) {
$('body').off();
})
$('#n_button').click(function () {
main();
$('#n_button').text('刷课中~');
})
$('#n_clear').click(function () {
localStorage.removeItem(location.href);
localStorage.removeItem('userCount');
localStorage.removeItem('pro_lms_classCount');
})
// 工具类
$('.minimality').click(function (e) {
let leftPx = e.clientX + 'px', topPx = e.clientY + 'px';
$('.n_outer').css('z-index', '-9999');
$('.n_icon').css({ 'top': topPx, 'left': leftPx, 'z-index': '9999' });
// 点击事件
document.querySelector('.n_icon').addEventListener('click', () => {
console.log(1212);
$('.n_icon').css('z-index', '-9999');
$('.n_outer').css({ 'top': topPx, 'left': leftPx, 'z-index': '9999' });
})
})
$('.question').click(function () {
alert('作者网站:niuwh.cn');
})
}
// 脚本运行核心逻辑
function main() {
start();
// 向弹窗里追加信息
function alertMessage(message) {
$('.n_infoAlert').append(`
${message}`);
}
setInterval(function () {
document.querySelector('.n_infoAlert').lastElementChild.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
}, 500)
// 视频自动加速逻辑
function speed() {
let keyt = '';
if (rate === 2 || rate === 1) {
keyt = "[keyt='" + rate + ".00']"
} else {
keyt = "[keyt='" + rate + "']"
}
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);
console.log(document.getElementsByClassName(className)[0]);
document.getElementsByClassName(className)[0].dispatchEvent(mousemove);
document.querySelector(selector).click();
alertMessage('已开始两倍速播放');
}
fun('xt_video_player_speed', keyt)
}
// 判断页面类型执行不同的操作
function start() {
const url = location.host;
const pathName = location.pathname.split('/');
const matchURL = url + pathName[0] + '/' + pathName[1] + '/' + pathName[2];
console.log(matchURL);
const changjiangv2 = ['changjiang.yuketang.cn/v2/web', 'yuketang.cn/v2/web', 'www.yuketang.cn/v2/web', 'xxxxx.yuketang.cn/v2/web', 'rain.gdufemooc.cn/v2/web'];
const pro_lms = ['bksycsu.yuketang.cn/pro/lms', 'henuyjs.yuketang.cn/pro/lms'];
alertMessage(`正在为您匹配${url}的处理逻辑...`);
if (changjiangv2.includes(matchURL)) {
yuketang_v2();
} else if (pro_lms.includes(matchURL) || matchURL.includes('yuketang.cn/pro/lms')) { // 没有匹配到但网址含有 pro/lms 就优先匹配
yukerang_pro_lms();
}
}
// yuketang.cn/v2/web页面的处理逻辑
function yuketang_v2() {
alertMessage('已匹配到www.yuketang.cn,正在处理...');
// 用于判断不同的课程
let baseUrl = location.href;
// 根据客户端记录的URL判别刷到那一集了,不影响第一批用户的刷课进度。
if (localStorage.getItem('classIndex')) {
localStorage.setItem(baseUrl, +localStorage.getItem('classIndex'));
localStorage.removeItem('classIndex');
}
let count = +localStorage.getItem(baseUrl) || 0;
alertMessage(`检测到已经播放到${count}集...`);
let classList = [];
// 用于标记视频是否播放完毕
let play = true;
// 主函数
function main() {
autoSlide(count).then(() => {
let list = document.querySelector('.logs-list').childNodes;
alertMessage('刷课状态:第' + (count + 1) + '个/' + list.length + '个');
classList[count] = list[count]?.querySelector('.content-box')?.querySelector('section');
let classInfo = classList[count]?.querySelector('.tag')?.querySelector('use')?.getAttribute('xlink:href');
if (classInfo?.includes('shipin') && play === true) { // 视频处理
play = false;
classList[count].click();
setTimeout(() => {
alertMessage('第' + (count + 1) + '个:进入了视频区');
speed();
let progress = document.querySelector('.progress-wrap').querySelector('.text');
let timer1 = setInterval(() => {
if (progress.innerHTML.includes('100%') || progress.innerHTML.includes('99%') || progress.innerHTML.includes('98%')) {
count++;
localStorage.setItem(baseUrl, count);
play = true;
history.back();
main();
clearInterval(timer1);
}
}, 10000);
}, 3000)
// 批量处理
} else if (classInfo?.includes('piliang') && play === true) { // 批量处理
let zhankai = classList[count].querySelector('.sub-info').querySelector('.gray').querySelector('span');
sync();
async function sync() {
await zhankai.click();
setTimeout(() => {
alertMessage('第' + (count + 1) + '个:进入了批量区');
localStorage.getItem('userCount') ? localStorage.getItem('userCount') : localStorage.setItem('userCount', 0);
// 保存所有视频
let a = list[count].querySelector('.leaf_list__wrap').querySelectorAll('.activity__wrap');
let count1 = localStorage.getItem('userCount');
bofang();
function bofang() {
let classInfo1 = a[count1]?.querySelector('.tag').querySelector('use').getAttribute('xlink:href');
let play = true;
if (classInfo1?.includes('shipin') && play === true) {
play = false;
a[count1].click();
alertMessage(`开始播放视频`);
// 延迟3秒后加速
setTimeout(() => {
speed();
}, 3000);
let timer = setInterval(() => {
let progress = document.querySelector('.progress-wrap').querySelector('.text');
if (progress.innerHTML.includes('100%') || progress.innerHTML.includes('99%') || progress.innerHTML.includes('98%')) {
count1++;
localStorage.setItem('userCount', count1);
clearInterval(timer);
alertMessage(`视频播放完毕`);
history.back();
setTimeout(() => {
bofang();
}, 2000);
}
}, 3000)
} else if (classInfo1 && !classInfo1.includes('shipin') && play === true) {
alertMessage('不是视频');
count1++;
localStorage.setItem('userCount', count1);
bofang();
} else if (count1 === a.length && play === true) {
alertMessage('合集播放完毕');
count++;
count1 = 0;
localStorage.setItem('userCount', count1);
localStorage.setItem(baseUrl, count);
main();
}
}
}, 2000)
}
} else if (classInfo?.includes('ketang') && play === true) { // 课堂处理
alertMessage('第' + (count + 1) + '个:进入了课堂区');
play = false;
classList[count].click();
setTimeout(() => {
let playBack = document.querySelector('.playback');
if (playBack) { // 存在回放按钮时进入详情页
playBack.click();
setTimeout(() => {
// 内容为视频的逻辑
if (document.querySelector('video')) {
function isComplate() {
let videoTime = document.querySelector('.video__time').innerHTML.toString();
let currentTime = videoTime.split('/')[0];
let totalTime = videoTime.split('/')[1];
if (currentTime == totalTime || currentTime == '00:00' || currentTime == '00:00:00') {
count++;
localStorage.setItem(baseUrl, count);
play = true;
history.go(-2);
main();
clearInterval(timer);
}
}
let timer = setInterval(() => {
isComplate();
}, 10000)
}
// 内容为音频的逻辑
if (document.querySelector('audio')) {
function isComplate() {
let mainArea = document.querySelector('.mainArea');
let currentTime = mainArea.querySelectorAll('span')[0].innerHTML.toString();
let totalTime = mainArea.querySelectorAll('span')[1].innerHTML.toString();
if (currentTime == totalTime || currentTime == '00:00' || currentTime == '00:00:00') {
count++;
localStorage.setItem(baseUrl, count);
play = true;
history.go(-2);
main();
clearInterval(timer);
}
}
let timer = setInterval(() => {
isComplate();
}, 10000)
}
}, 3000)
} else { // 不存在回放按钮时退出
count++;
localStorage.setItem(baseUrl, count);
play = true;
history.go(-1);
main();
}
}, 3000)
} else if (classInfo?.includes('kejian') && play === true) { // 课件处理
alertMessage('根据ycj用户的反馈修改新增课件处理,且赞助支持,表示感谢') // 8.8元
alertMessage('第' + (count + 1) + '个:进入了课件区');
play = false;
classList[count].click();
let classType;
(async function () {
await new Promise(function (resolve) {
setTimeout(function () {
classType = $('.el-card__header')[0].innerText;
console.log(classType);
$('.check').click();
resolve();
}, 3000)
}) // 3秒后执行点击事件
let className = $('.dialog-header')[0].firstElementChild.innerText;
console.log(className);
if (classType == '课件PPT') { // 课件为ppt
let allPPT = $('.swiper-wrapper')[0].children;
alertMessage(`开始播放${className}`)
for (let i = 0; i < allPPT.length; i++) {
await new Promise(function (resolve) {
setTimeout(function () {
allPPT[i].click();
alertMessage(`${className}:第${i + 1}个ppt已经播放`);
resolve();
}, 500)
})
}
if ($('.video-box')) { // 回头检测如果ppt里面有视频
let pptVideo = $('.video-box');
alertMessage('检测到ppt里面有视频,将继续播放视频');
for (let i = 0; i < pptVideo.length; i++) {
if ($('.video-box')[i].innerText != '已完成') { // 判断视频是否已播放
pptVideo[i].click();
alertMessage(`开始播放:${className}里面的第${i + 1}个视频`)
await new Promise(function (resolve) {
setTimeout(function () {
speed(); // 加速
$('.xt_video_player_common_icon').click(); // 静音
resolve();
}, 3000)
})
alertMessage('已开启二倍速,且自动静音');
await new Promise(function (resolve) {
let timer = setInterval(function () {
let allTime = $('.xt_video_player_current_time_display')[0].innerText;
nowTime = allTime.split(' / ')[0];
totalTime = allTime.split(' / ')[1]
console.log(nowTime + totalTime);
if (nowTime == totalTime) {
clearInterval(timer);
resolve();
}
}, 200);
}) // 等待视频结束
} else { // 视频已完成
alertMessage(`检测到${className}里面的第${i + 1}个视频已经播放完毕`);
}
}
}
alertMessage(`${className} 已经播放完毕`)
} else { // 课件为视频
$('.video-box').click();
alertMessage(`开始播放视频:${className}`);
await new Promise(function (resolve) {
setTimeout(function () {
speed();
$('.xt_video_player_common_icon').click();
resolve();
}, 3000)
}) // 3秒后加速,静音
alertMessage('已开启二倍速,且自动静音');
await new Promise(function (resolve) {
let timer = setInterval(function () {
let allTime = $('.xt_video_player_current_time_display')[0].innerText;
nowTime = allTime.split(' / ')[0];
totalTime = allTime.split(' / ')[1]
console.log(nowTime + totalTime);
if (nowTime == totalTime) {
clearInterval(timer);
resolve();
}
}, 200);
}) // 等待视频结束
alertMessage(`${className} 视频播放完毕`)
}
count++;
localStorage.setItem(baseUrl, count);
play = true;
history.back();
main();
})()
} else if (count === list.length && play === true) { // 结束
alertMessage('课程刷完了');
$('#n_buttoni').text('刷完了~');
localStorage.setItem(baseUrl, 0);
return;
} else if (!(classInfo.includes('shipin') || classInfo.includes('piliang') || classInfo.includes('kejian')) && play === true) { // 视频,批量,课件都不是的时候跳过,此处可以优化
alertMessage('第' + (count + 1) + '个:不是视频,已跳过');
count++;
localStorage.setItem(baseUrl, count);
main();
}
})
}
// 根据视频集数,自动下拉刷新集数
async function autoSlide(count) {
let frequency = parseInt((count + 1) / 20) + 1;
for (let i = 0; i < frequency; i++) {
await new Promise((resolve, reject) => {
setTimeout(() => {
document.querySelector('.viewContainer').scrollTop = document.querySelector('.el-tab-pane').scrollHeight;
resolve();
}, 1000)
})
}
}
main();
}
// yuketang.cn/pro/lms页面的处理逻辑
function yukerang_pro_lms() {
localStorage.setItem('n_type', true);
alertMessage('正准备打开新标签页...');
localStorage.getItem('pro_lms_classCount') ? null : localStorage.setItem('pro_lms_classCount', 1); // 初始化集数
let classCount = localStorage.getItem('pro_lms_classCount') - 1;
$('.leaf-detail')[classCount].click(); // 进入第一个课程,启动脚本
}
// 其他不同类型的DOM页面的处理逻辑,待完善...
}
function yukerang_pro_lms_new() {
function alertMessage(message) {
$('.n_infoAlert').append(`
${message}`);
}
function speed() {
let keyt = '';
if (rate === 2 || rate === 1) {
keyt = "[keyt='" + rate + ".00']"
} else {
keyt = "[keyt='" + rate + "']"
}
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);
console.log(document.getElementsByClassName(className)[0]);
document.getElementsByClassName(className)[0].dispatchEvent(mousemove);
document.querySelector(selector).click();
alertMessage('已开始两倍速播放');
}
fun('xt_video_player_speed', keyt)
}
function claim() {
$(
"#video-box > div > xt-wrap > xt-controls > xt-inner > xt-volumebutton > xt-icon"
).click();
alertMessage('已开启静音')
}
// function playOut() {
// let nowTime = document.querySelector('.xt_video_player_current_time_display').firstElementChild.innerText;
// let totalTime = document.querySelector('.xt_video_player_current_time_display').lastElementChild.innerText;
// console.log(nowTime, totalTime);
// if (nowTime == totalTime) {
// return true;
// } else {
// return false;
// }
// }
function nextCount(classCount) {
event1 = new Event('mousemove', { bubbles: true });
event1.clientX = 9999;
event1.clientY = 9999;
if ($('.btn-next')[0]) {
localStorage.setItem('pro_lms_classCount', classCount);
$('.btn-next')[0].dispatchEvent(event1);
$('.btn-next')[0].dispatchEvent(new Event('click'));
main();
} else {
localStorage.removeItem('pro_lms_classCount');
alertMessage('课程播放完毕了');
}
}
function controllScroll() {
let scrollTimer;
scrollTimer = setInterval(function () {
document.querySelector('.n_infoAlert').lastElementChild.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
}, 500)
document.querySelector('.n_infoAlert').addEventListener('mouseenter', () => {
clearInterval(scrollTimer);
console.log('enter');
})
document.querySelector('.n_infoAlert').addEventListener('mouseleave', () => {
scrollTimer = setInterval(function () {
document.querySelector('.n_infoAlert').lastElementChild.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
}, 500)
console.log('leave');
})
}
alertMessage('已就绪,开始刷课,请尽量保持页面不动。');
let classCount = localStorage.getItem('pro_lms_classCount');
controllScroll();
async function main() {
alertMessage(`准备播放第${classCount}集...`);
await new Promise(function (resolve) {
setTimeout(function () {
let className = $('.header-bar')[0].firstElementChild.innerText;
let classType = $('.header-bar')[0].firstElementChild.firstElementChild.getAttribute('class');
let classStatus = $('#app > div.app_index-wrapper > div.wrap > div.viewContainer.heightAbsolutely > div > div > div > div > section.title')[0]?.lastElementChild?.innerText;
if (classType.includes('tuwen') && classStatus != '已读') {
alertMessage(`正在废寝忘食地看:${className}中...`);
setTimeout(() => {
resolve();
}, 2000)
} else if (classType.includes('taolun')) {
alertMessage(`只是看看,目前没有自动发表讨论功能,欢迎反馈...`);
setTimeout(() => {
resolve();
}, 2000)
} else if (classType.includes('shipin') && !classStatus.includes('100%')) {
alertMessage(`正在播放:${className}`);
let playover = false; // 代表视频没播放完毕
var observer;
setTimeout(() => {
// 监测视频播放状态
let timer = setInterval(() => {
let classStatus = $('#app > div.app_index-wrapper > div.wrap > div.viewContainer.heightAbsolutely > div > div > div > div > section.title')[0]?.lastElementChild?.innerText;
if (classStatus.includes('100%') || classStatus.includes('99%') || classStatus.includes('98%')) {
alertMessage(`${className}播放完毕...`);
clearInterval(timer);
if (!!observer) { // 防止新的视频已经播放完了,还未来得及赋值observer的问题
observer.disconnect(); // 停止监听
}
resolve();
}
}, 200)
// 根据video是否加载出来判断加速时机
let nowTime = Date.now();
let videoTimer = setInterval(() => {
let video = document.querySelector('video');
if (video) {
setTimeout(() => { // 防止视频刚加载出来,就加速,出现无法获取到元素地bug
speed();
claim();
observe();
clearInterval(videoTimer);
}, 2000)
} else if (!video && Date.now() - nowTime > 20000) { // 如果20s内仍未加载出video
localStorage.setItem('n_type', true);
location.reload();
}
}, 5000)
}, 2000)
//防止切出窗口自动暂停 duck123ducker贡献
function observe() {
if (document.getElementsByClassName('play-btn-tip').length === 0) setTimeout(observe, 100);
else {
var targetElement = document.getElementsByClassName('play-btn-tip')[0];
observer = new MutationObserver(function (mutationsList) {
for (var mutation of mutationsList) {
if (mutation.type === 'childList' && mutation.target === targetElement && targetElement.innerText === '播放') {
const classStatus = $('#app > div.app_index-wrapper > div.wrap > div.viewContainer.heightAbsolutely > div > div > div > div > section.title')[0]?.lastElementChild?.innerText;
if (classStatus.includes('100%') || classStatus.includes('99%') || classStatus.includes('98%')) playover = true;
if (!playover) document.querySelector('.xt_video_bit_play_btn').click(); // 视频放完了就不模拟点击播放
}
}
});
var config = { childList: true };
observer.observe(targetElement, config);
document.querySelector('.xt_video_bit_play_btn').click(); //防止进入下一章时由于鼠标离开窗口而在视频开始时就暂停导致永远无法触发监听器
}
}
} else if (classType.includes('zuoye')) {
alertMessage(`进入:${className},目前没有自动作答功能,敬请期待...`);
setTimeout(() => {
resolve();
}, 2000)
} else {
alertMessage(`您已经看过${className}...`);
setTimeout(() => {
resolve();
}, 2000)
}
}, 2000);
})
alertMessage(`第${classCount}集播放完了...`);
classCount++;
nextCount(classCount);
}
main();
}
// 油猴执行文件
(function () {
'use strict';
addWindow();
if (localStorage.getItem('n_type') === 'true') {
$('#n_button').text('刷课中~');
localStorage.setItem('n_type', false);
yukerang_pro_lms_new();
}
})();