// ==UserScript==
// @name UOOC assistant
// @name:en UOOC assistant
// @namespace http://tampermonkey.net/
// @version 0.9.8
// @description 【使用前先看介绍/有问题可反馈】【欢迎一键三连(好评+打赏+收藏),你的支持是作者维护下去的最大动力!】UOOC优课联盟助手(UOOC assistant):可选是否倍速(若取消勾选则一倍速播放),可选是否静音(若取消勾选则恢复原音量),可选是否播放(若取消勾选则暂停播放),可选是否连播(若取消勾选则循环播放),离开页面保持状态,自动回答视频中途弹出问题;键盘左右方向键可以控制视频快进/快退,上下方向键可以控制音量增大/减小,空格键可以控制播放/暂停;自动签到慕课;如果视频标题下面出现 倍速/静音/播放/连播 选项说明脚本正常启动运行。
// @description:en 【使用前先看介绍/有问题可反馈】【欢迎一键三连(好评+打赏+收藏),你的支持是作者维护下去的最大动力!】UOOC优课联盟助手(UOOC assistant):可选是否倍速(若取消勾选则一倍速播放),可选是否静音(若取消勾选则恢复原音量),可选是否播放(若取消勾选则暂停播放),可选是否连播(若取消勾选则循环播放),离开页面保持状态,自动回答视频中途弹出问题;键盘左右方向键可以控制视频快进/快退,上下方向键可以控制音量增大/减小,空格键可以控制播放/暂停;自动签到慕课;如果视频标题下面出现 倍速/静音/播放/连播 选项说明脚本正常启动运行。
// @author cc
// @include http://www.uooc.net.cn/*
// @grant none
// @downloadURL none
// ==/UserScript==
(function () {
'use strict';
const jsName = 'UOOC-assistant.js';
const RECURSION_DURATION = 500;
if (location.host == 'www.uooc.net.cn') {
console.log(`excute ${jsName}`);
let recursion = () => {
let extraTime = 0;
try {
let done = false;
let video = document.querySelector('#player_html5_api');
if (video) {
if (document.getElementById('rate').checked)
video.playbackRate = 2;
else
video.playbackRate = 1;
if (document.getElementById('volume').checked)
video.muted = true;
else
video.muted = false;
if (document.getElementById('play').checked && !video.ended)
video.play();
else
video.pause();
if (video.ended) {
done = true;
};
let quizLayer = document.querySelector('#quizLayer');
if (quizLayer && quizLayer.style.display != 'none') {
if (done) {
setTimeout(() => {
document.querySelectorAll('.layui-layer-shade').forEach(e => e.style.display = 'none');
}, RECURSION_DURATION << 1);
};
let source = JSON.parse(document.querySelector('div[uooc-video]').getAttribute('source'));
let quizList = source.quiz;
let quizIndex = 0;
let quizQuestion = document.querySelector('.smallTest-view .ti-q-c').innerHTML;
for (let i = 0; i < quizList.length; i++) {
if (quizList[i].question == quizQuestion) {
quizIndex = i;
break;
};
};
let quizAnswer = eval(quizList[quizIndex].answer);
let quizOptions = quizLayer.querySelector('div.ti-alist');
for (let ans of quizAnswer) {
let labelIndex = ans.charCodeAt() - 'A'.charCodeAt();
quizOptions.children[labelIndex].click();
}; // end for
quizLayer.querySelector('button').click();
extraTime = 1000;
}; // end if
if (!done) {
if (video.paused && document.getElementById('play').checked) {
video.play();
} else {
document.querySelectorAll('.layui-layer-shade, #quizLayer').forEach(e => e.style.display = 'none');
};
};
}; // end if (video)
if (!done) {
setTimeout(recursion, RECURSION_DURATION + extraTime);
} else if (video) {
if (!document.getElementById('continue').checked) {
video.currentTime = 0;
// video.ended = false;
setTimeout(recursion, RECURSION_DURATION + extraTime);
} else {
let current_video = document.querySelector('.basic.active');
let next_part = current_video.parentNode;
let next_video = current_video;
// 定义判断是否视频的函数
let isVideo = node => Boolean(node.querySelector('span.icon-video'));
// 定义是否可返回上一级目录的函数
let canBack = () => {
return Boolean(next_part.parentNode.parentNode.tagName == 'LI');
};
// 定义更新至后续视频的函数
let toNextVideo = () => {
next_video = next_video.nextElementSibling;
while (next_video && !isVideo(next_video)) {
next_video = next_video.nextElementSibling;
};
};
// 定义判断是否存在视频的函数
let isExistsVideo = () => {
let _video = next_part.firstElementChild;
while (_video && !isVideo(_video)) {
_video = _video.nextElementSibling;
};
return Boolean(_video && isVideo(_video));
};
// 定义判断是否存在后续视频的函数
let isExistsNextVideo = () => {
let _video = current_video.nextElementSibling;
while (_video && !isVideo(_video)) {
_video = _video.nextElementSibling;
};
return Boolean(_video && isVideo(_video));
};
// 定义检查文件后是否存在后续目录的函数
let isExistsNextListAfterFile = () => {
let part = next_part.nextElementSibling;
return Boolean(part && part.childElementCount > 0);
};
// 定义更新文件后的后续目录的函数
let toNextListAfterFile = () => {
next_part = next_part.nextElementSibling;
};
// 定义返回上一级的函数
let toOuterList = () => {
next_part = next_part.parentNode.parentNode;
};
// 定义返回主条目的函数
let toOuterItem = () => {
next_part = next_part.parentNode;
};
// 定义检查列表后是否存在后续目录的函数
let isExistsNextListAfterList = () => {
return Boolean(next_part.nextElementSibling);
};
// 定义进入列表后的后续目录的函数
let toNextListAfterList = () => {
next_part = next_part.nextElementSibling;
};
// 定义展开目录的函数
let expandList = () => {
next_part.firstElementChild.click();
};
// 定义进入展开目录的第一个块级元素的函数
let toExpandListFirstElement = () => {
next_part = next_part.firstElementChild.nextElementSibling;
if (next_part.classList.contains('unfoldInfo')) {
next_part = next_part.nextElementSibling;
};
};
// 定义判断块级元素是否目录列表的函数
let isList = () => {
return Boolean(next_part.tagName == 'UL');
};
// 定义目录列表的第一个目录的函数
let toInnerList = () => {
next_part = next_part.firstElementChild;
};
// 定义进入文件列表的第一个视频的函数
let toFirstVideo = () => {
next_video = next_part.firstElementChild;
while (next_video && !isVideo(next_video)) {
next_video = next_video.nextElementSibling;
};
};
// 定义模式
let mode = {
FIRST_VIDEO: 'FIRST_VIDEO',
NEXT_VIDEO: 'NEXT_VIDEO',
LAST_LIST: 'LAST_LIST',
NEXT_LIST: 'NEXT_LIST',
INNER_LIST: 'INNER_LIST',
OUTER_LIST: 'OUTER_LIST',
OUTER_ITEM: 'OUTER_ITEM',
}
// 定义搜索函数
let search = (_mode) => {
switch (_mode) {
case mode.FIRST_VIDEO: // mode = 0
if (isExistsVideo()) {
toFirstVideo();
next_video.click();
setTimeout(recursion, RECURSION_DURATION);
} else if (isExistsNextListAfterFile()) {
search(mode.LAST_LIST);
} else {
// perhaps there is an exam, end recursion
};
break;
case mode.NEXT_VIDEO: // mode == 1
if (isExistsNextVideo()) {
toNextVideo();
next_video.click();
setTimeout(recursion, RECURSION_DURATION);
} else if (isExistsNextListAfterFile()) {
search(mode.LAST_LIST);
} else {
search(mode.OUTER_ITEM);
};
break;
case mode.LAST_LIST: // mode == 2
toNextListAfterFile();
toInnerList();
search(mode.INNER_LIST);
break;
case mode.NEXT_LIST: // mode == 3
toNextListAfterList();
search(mode.INNER_LIST);
break;
case mode.INNER_LIST: // mode == 4
expandList();
(function waitForExpand () {
if (next_part.firstElementChild.nextElementSibling) {
toExpandListFirstElement();
if (isList()) {
toInnerList();
search(mode.INNER_LIST);
} else {
search(mode.FIRST_VIDEO);
};
} else {
setTimeout(waitForExpand, RECURSION_DURATION);
};
})();
break;
case mode.OUTER_LIST: // mode == 5
toOuterList();
if (isExistsNextListAfterList()) {
search(mode.NEXT_LIST);
} else if (canBack()) {
search(mode.OUTER_LIST);
} else {
// perhaps there is no next list
};
break;
case mode.OUTER_ITEM: // mode == 6
toOuterItem();
if (isExistsNextListAfterList()) {
toNextListAfterList();
search(mode.INNER_LIST);
} else if (canBack()){
search(mode.OUTER_LIST);
} else {
// perhaps there is no list
};
break;
default:
break;
};
};
try {
search(mode.NEXT_VIDEO);
} catch (e) {
console.log(e);
};
};
};
} catch (e) {
console.log(e);
};
}; // end recursion
let wait = () => {
if (document.readyState == 'complete') {
console.log('ready to set checkboxes.');
let getCheckbox = (name, text) => {
let p = document.createElement('p');
p.style.color = '#cccccc';
p.style.paddingLeft = '10px';
let checkbox = document.createElement('input');
checkbox.id = name;
checkbox.type = 'checkbox';
checkbox.checked = true;
checkbox.name = name;
checkbox.value = name;
checkbox.style.marginLeft = '15px';
checkbox.style.width = '12px';
checkbox.style.height = '12px';
p.append(checkbox);
let label = document.createElement('label');
label.setAttribute('for', name);
label.innerText = text;
label.style.marginLeft = '13px';
label.style.fontSize = '12px';
p.append(label);
return p;
};
let getContainer = (_id) => {
let container = document.createElement('div');
container.id = _id;
container.style.display = 'flex';
container.style.flexDirection = 'row';
container.style.alignItems = 'center';
return container;
};
// set checkbox container
let checkboxContainer = getContainer('checkbox-container');
let rateCheckbox = getCheckbox('rate', '倍速');
let volumeCheckbox = getCheckbox('volume', '静音');
let playCheckbox = getCheckbox('play', '播放');
let continueCheckbox = getCheckbox('continue', '连播');
let head = document.querySelector('.learn-head');
checkboxContainer.appendChild(rateCheckbox);
checkboxContainer.appendChild(volumeCheckbox);
checkboxContainer.appendChild(playCheckbox);
checkboxContainer.appendChild(continueCheckbox);
// set prompt container
let promptContainer = getContainer('prompt-container');
let div = document.createElement('div');
div.innerHTML = '提示:键盘的 \u2190 和 \u2192 可以控制快进/快退,\u2191 和 \u2193 可以控制音量增大/减小,空格键可以控制播放/暂停';
div.style.color = '#cccccc';
div.style.height = 'min-height';
div.style.margin = '0 20px 0';
div.style.padding = '0 5px';
div.style.bordRadius = '5px';
div.style.fontSize = '12px';
promptContainer.appendChild(div);
// set appreciation container
let elemeCodeContainer = getContainer('eleme-code-container');
let a = document.createElement('a');
a.href = 'https://s3.ax1x.com/2020/11/25/Da6MTO.jpg';
a.target = '_blank';
a.innerHTML = '反正都看到这条信息了,领个红包🧧吃外卖吧!';
a.style.color = '#cccccc';
a.style.fontWeight = 'bold';
a.style.height = 'min-height';
a.style.margin = '0 20px 0';
a.style.padding = '0 5px';
a.style.bordRadius = '5px';
a.style.fontSize = '11px';
elemeCodeContainer.appendChild(a);
let appreciationCodeContainer = getContainer('appreciation-code-container');
a = document.createElement('a');
a.href = 'https://s1.ax1x.com/2020/11/08/BTeRqe.png';
a.target = '_blank';
a.innerHTML = '本脚本使用完全免费,您的打赏是作者维护下去的最大动力!点此打赏作者😊';
a.style.color = '#cccccc';
a.style.fontWeight = 'bold';
a.style.height = 'min-height';
a.style.margin = '0 20px 0';
a.style.padding = '0 5px';
a.style.bordRadius = '5px';
a.style.fontSize = '11px';
appreciationCodeContainer.appendChild(a);
// set head
head.appendChild(checkboxContainer);
head.appendChild(promptContainer);
head.appendChild(elemeCodeContainer);
head.appendChild(appreciationCodeContainer);
head.style.height = `${head.offsetHeight + 45}px`;
// information
console.log('checkboxes have been set.');
// auto login
let cid = location.href.match(/\d+/g)[0];
let httpRequest = new XMLHttpRequest();
httpRequest.open('GET', `http://www.uooc.net.cn/home/course/info?cid=${cid}`, true);
httpRequest.send();
httpRequest.onreadystatechange = function () {
if (httpRequest.readyState == 4 && httpRequest.status == 200) {
console.log('Automatic sign-in succeeded.');
};
};
document.onkeydown = (event) => {
let k = event.key;
let complete = false;
let div = document.querySelector('div.basic.active');
if (div && div.classList.contains('complete'))
complete = true;
let video = document.getElementById('player_html5_api');
if (video) {
switch (k) {
case 'ArrowLeft': {
video.currentTime -= 10;
break;
};
case 'ArrowRight': {
if (complete)
video.currentTime += 10;
break;
};
case 'ArrowUp': {
if (video.volume + 0.1 <= 1.0)
video.volume += 0.1;
else
video.volume = 1.0;
break;
}
case 'ArrowDown': {
if (video.volume - 0.1 >= 0.0)
video.volume -= 0.1;
else
video.volume = 0.0;
break;
};
case ' ': {
let continueCheckbox = document.getElementById('play');
continueCheckbox.checked = !continueCheckbox.checked;
break;
};
};
};
};
recursion();
} else {
setTimeout(wait, RECURSION_DURATION);
};
}; // end wait
wait();
}
})();