// ==UserScript==
// @name UOOC assistant
// @namespace http://tampermonkey.net/
// @version 1.0.7
// @description 【使用前先看介绍/有问题可反馈】UOOC 优课联盟助手 (UOOC assistant):可选是否倍速 (若取消勾选则一倍速播放),可选是否静音 (若取消勾选则恢复原音量),可选是否播放 (若取消勾选则暂停播放),可选是否连播 (若取消勾选则循环播放),离开页面保持视频状态,自动回答视频中途弹出问题,可复制已提交测验题目及答案,键盘左右方向键可以控制视频快进/快退,上下方向键可以控制音量增大/减小,空格键可以控制播放/暂停,停止连播支持提醒,如果视频标题下面出现 `倍速/静音/播放/连播` 选项说明脚本正常启动运行。
// @author cc
// @include http://www.uooc.net.cn/home/learn/index*
// @include http://www.uooconline.com/home/learn/index*
// @include https://www.uooc.net.cn/home/learn/index*
// @include https://www.uooconline.com/home/learn/index*
// @grant none
// @require https://greasyfork.org/scripts/418193-coder-utils.js
// ==/UserScript==
(function () {
'use strict';
const RECURSION_DURATION = 500;
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
Notification.requestPermission().then((permission) => {
if (permission === 'granted') {
let text = '已停止连播,可能遇到测试章节';
new Notification('UOOC Assistant', { body: text });
};
});
};
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 (err) {
console.error(err);
};
};
};
} catch (err) {
console.error(err);
};
}; // end recursion
let wait = () => {
if (document.readyState == 'complete') {
// get permission
Notification.requestPermission().then((permission) => {
if (permission === 'granted') {
console.log('UOOC Assistant: 已获得通知权限');
} else {
console.log('UOOC Assistant: 无法获得通知权限');
};
});
// define element creating functions
let getCheckbox = (name, text) => {
let p = HTMLElement.$mkel('p', {}, {}, {
'color': '#cccccc',
'padding-left': '10px',
});
let checkbox = HTMLElement.$mkel('input', {
id: name,
type: 'checkbox',
name: name,
value: name,
}, {
checked: true,
}, {
'margin-left': '15px',
'width': '12px',
'height': '12px',
});
p.append(checkbox);
let label = HTMLElement.$mkel('label', {
for: name,
}, {
innerText: text,
}, {
'margin-left': '13px',
'font-size': '12px',
});
p.append(label);
return p;
};
let getContainer = (_id) => {
return HTMLElement.$mkel('div', {id: _id}, {}, {
'display': 'flex',
'flex-direction': 'row',
'align-items': 'center',
});
};
// 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 copyCheckbox = HTMLElement.$mkel('p', {}, {}, {
'color': '#cccccc',
'padding-left': '10px',
});
let btn = HTMLElement.$mkel('button', {}, {innerHTML: '复制题目答案'}, {
'margin-left': '13px',
'padding': '0 5px 0',
'font-size': '12px',
'cursor': 'pointer',
}, {
click: function(event) {
let testPaperTop = frames[0] ? frames[0].document.querySelector('.testPaper-Top') : document.querySelector('.testPaper-Top');
if (!testPaperTop) {
alert('该页面不是测验页面,无法复制内容');
} else {
if (testPaperTop.querySelector('.fl_right')) {
let queItems = frames[0] ? Array.from(frames[0].document.querySelectorAll('.queItems')) : Array.from(document.querySelectorAll('.queItems'));
let content = queItems.map(queType => {
let res = '';
if (queType.querySelector('.queItems-type').innerText.indexOf('选') >= 0) {
let questions = queType.querySelectorAll('.queContainer');
res += Array.from(questions).map(question => {
let que = question.querySelector('.queBox').innerText.replace(/\n{2,}/g, '\n').replace(/(\w\.)\n/g, '$1 ');
let ans = question.querySelector('.answerBox div:first-child').innerText.replace(/\n/g, '');
let right = question.querySelector('.scores').innerText.match(/\d+\.?\d+/g).map(score => eval(score));
right = right[0] === right[1];
return `${que}\n${ans}\n是否正确:${right}\n`;
}).join('\n');
};
return res;
}).join('\n');
content.$copyToClipboard();
alert('题目及答案已复制到剪切板');
} else {
alert('该测验可能还没提交,无法复制');
};
};
},
});
copyCheckbox.appendChild(btn);
let head = document.querySelector('.learn-head');
if (!head) {
setTimeout(wait, RECURSION_DURATION);
return;
};
checkboxContainer.appendChild(rateCheckbox);
checkboxContainer.appendChild(volumeCheckbox);
checkboxContainer.appendChild(playCheckbox);
checkboxContainer.appendChild(continueCheckbox);
checkboxContainer.appendChild(copyCheckbox);
// set prompt container
let promptContainer = getContainer('prompt-container');
let div = HTMLElement.$mkel('div', {}, {
innerHTML: `提示:<u><a href="https://greasyfork.org/zh-CN/scripts/425837-uooc-assistant-beta" target="_blank" style="color: yellow;">更新内测版本,点此尝鲜试用</a></u>,键盘的 \u2190 和 \u2192 可以控制快进/快退,\u2191 和 \u2193 可以控制音量增大/减小,空格键可以控制播放/暂停`,
}, {
'color': '#cccccc',
'height': 'min-height',
'margin': '0 20px 0',
'padding': '0 5px',
'border-radius': '5px',
'font-size': '12px',
});
promptContainer.appendChild(div);
let appreciationCodeContainer = getContainer('appreciation-code-container');
let a = HTMLElement.$mkel('a', {
href: 'https://s1.ax1x.com/2020/11/08/BTeRqe.png',
target: '_blank',
}, {
innerHTML: '<u>本脚本使用完全免费😉,脚本代码编写维护不易,俊男👦靓女👧们有心的话可以点这儿支持一下作者呀❤️~</u>',
}, {
'color': '#cccccc',
'font-weight': 'bold',
'height': 'min-height',
'margin': '0 20px 0',
'padding': '0 5px',
'border-radius': '5px',
'font-size': '11px',
});
appreciationCodeContainer.appendChild(a);
// set head
head.appendChild(checkboxContainer);
head.appendChild(promptContainer);
head.appendChild(appreciationCodeContainer);
head.style.height = `${head.offsetHeight + 30}px`;
// bind key down events
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;
};
};
};
};
// information
console.info('UOOC assistant init ok.');
recursion();
} else {
setTimeout(wait, RECURSION_DURATION);
};
}; // end wait
wait();
})();