// ==UserScript== // @name UOOC assistant beta // @name UOOC 优课联盟助手 (内测版) // @namespace http://tampermonkey.net/ // @version 1.0.5 // @description 【使用前先看介绍/有问题可反馈】UOOC 优课联盟助手 (内测版) (UOOC assistant beta):可选是否倍速 (若取消勾选则一倍速播放),可选是否静音 (若取消勾选则恢复原音量),可选是否播放 (若取消勾选则暂停播放),可选是否连播 (若取消勾选则循环播放),离开页面保持视频状态,自动回答视频中途弹出问题,可复制已提交测验题目及答案;键盘左右方向键可以控制视频快进/快退,上下方向键可以控制音量增大/减小,空格键可以控制播放/暂停;如果视频标题下面出现 `倍速/静音/播放/连播` 选项说明脚本正常启动运行。 // @author cc // @include http://www.uooc.net.cn/home/learn/index* // @grant none // @downloadURL none // ==/UserScript== function bindChapterChange () { function bindSubChapterChange () { var sourceView = document.querySelector('[source-view]') var sObserver = new MutationObserver(function (mutations) { if (document.querySelector('[source-view] [uooc-video] video')) setTimeout(start, 250) }) sObserver.observe(sourceView, { childList: true }) } var mainLeft = document.querySelector('.learn-main-left') var mObserver = new MutationObserver(function (mutations) { bindSubChapterChange() }) mObserver.observe(mainLeft, { childList: true }) bindSubChapterChange() } function autoQuiz () { function autoQuizAnswer () { let quizLayer = document.getElementById('quizLayer') let source = JSON.parse(document.querySelector('div[uooc-video]').getAttribute('source')) let quizQuestion = document.querySelector('.smallTest-view .ti-q-c').innerHTML let quizAnswer = source.quiz.find(q => q.question === quizQuestion).answer let quizOptions = quizLayer.querySelector('div.ti-alist') for (let ans of eval(quizAnswer)) quizOptions.children[ans.charCodeAt() - 'A'.charCodeAt()].click() quizLayer.querySelector('button').click() } var learnView = document.querySelector('.lean_view') var observer = new MutationObserver(function (mutations) { for (let mutation of mutations) { let node = mutation.addedNodes[0] if (node && node.id && node.id.includes('layui-layer')) { autoQuizAnswer() break } } }) observer.observe(learnView, { childList: true }) } function bindVideoEvents () { var video = document.querySelector('#player_html5_api') video.onpause = function () { if (document.querySelector('#play').checked) this.play() } video.onended = findNextVideo } function start () { bindKeyboardEvents() bindVideoEvents() autoQuiz() var video = document.getElementById('player_html5_api') var volume = document.getElementById('volume') var play = document.getElementById('play') var rate = document.getElementById('rate') if (volume.checked) video.muted = true if (play.checked && video.paused) video.play() if (rate.checked) video.playbackRate = 2.0 } function placeComponents () { function copyToClipboard (content) { var t = document.createElement('textarea') t.value = content document.body.appendChild(t) t.select() document.execCommand('copy') document.body.removeChild(t) } function getCheckbox (name, text) { var p = document.createElement('p') p.style = 'color: #ccc; padding-left: 10px;' var checkbox = document.createElement('input') checkbox.id = checkbox.name = checkbox.value = name checkbox.type = 'checkbox' checkbox.checked = true checkbox.style = 'margin-left: 15px; width: 12px; height: 12px;' p.append(checkbox) var label = document.createElement('label') label.for = name label.innerText = text label.style = 'margin-left: 13px; font-size: 12px;' p.append(label) return p } function getContainer (_id) { var container = document.createElement('div') container.id = _id container.style = 'display: flex; flex-direction: row; align-items: center;' return container } function getCopyButton () { var copyButton = document.createElement('p') copyButton.style = 'color: #ccc; padding-left: 10px;' var btn = document.createElement('button') btn.innerText = '复制题目答案' btn.style = 'margin-left: 13px; padding: 0 5px 0; font-size: 12px; cursor: pointer;' btn.onclick = function () { var testPaperTop = frames[0] ? frames[0].document.querySelector('.testPaper-Top') : document.querySelector('.testPaper-Top'); if (!testPaperTop) { alert('该页面不是测验页面,无法复制内容') } else { if (testPaperTop.querySelector('.fl_right')) { var queItems = frames[0] ? Array.from(frames[0].document.querySelectorAll('.queItems')) : Array.from(document.querySelectorAll('.queItems')); var content = queItems.map(queType => { var res = '' if (queType.querySelector('.queItems-type').innerText.indexOf('选') >= 0) { var questions = queType.querySelectorAll('.queContainer') res += Array.from(questions).map((question) => { var que = question.querySelector('.queBox').innerText.replace(/\n{2,}/g, '\n').replace(/(\w\.)\n/g, '$1 ') var ans = question.querySelector('.answerBox div:first-child').innerText.replace(/\n/g, '') var 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') copyToClipboard(content) alert('题目及答案已复制到剪切板') } else { alert('该测验可能还没提交,无法复制') } } } copyButton.appendChild(btn) return copyButton } function setCheckboxes (container) { var rateCheckbox = getCheckbox('rate', '倍速') var volumeCheckbox = getCheckbox('volume', '静音') var playCheckbox = getCheckbox('play', '播放') var continueCheckbox = getCheckbox('continue', '连播') var copyButton = getCopyButton() var video = document.getElementById('player_html5_api') rateCheckbox.firstElementChild.checked rateCheckbox.firstElementChild.onchange = function (event) { if (event.target.checked) video.playbackRate = 2 } volumeCheckbox.firstElementChild.onchange = function (event) { if (event.target.checked) video.muted = true else video.muted = false } playCheckbox.firstElementChild.onchange = function (event) { if (event.target.checked) video.play() else video.pause() } container.appendChild(rateCheckbox) container.appendChild(volumeCheckbox) container.appendChild(playCheckbox) container.appendChild(continueCheckbox) container.appendChild(copyButton) } function setPrompt (container) { var div = document.createElement('div') div.innerHTML = `提示:该版本为内测版,使用时请先关闭正式版若出现 BUG 点此反馈,键盘的 \u2190 和 \u2192 可以控制快进/快退,\u2191 和 \u2193 可以控制音量增大/减小,空格键可以控制播放/暂停` div.style = 'color: #cccccc; height: min-height; margin: 0 20px 0; padding: 0 5px; border-radius: 5px; font-size: 12px;' container.appendChild(div) } function setAppreciationCode (container) { var a = document.createElement('a') a.href = 'https://s1.ax1x.com/2020/11/08/BTeRqe.png' a.target = '_blank' a.innerHTML = '本脚本使用完全免费,您的打赏是作者维护下去的最大动力!点此打赏作者😊' a.style = 'color: #cccccc; font-weight: bold; height: min-height; margin: 0 20px 0; padding: 0 5px; border-radius: 5px; font-size: 11px;' container.appendChild(a) } var head = document.querySelector('.learn-head') var checkboxContainer = getContainer('checkbox-container') setCheckboxes(checkboxContainer) var promptContainer = getContainer('prompt-container') setPrompt(promptContainer) var appreciationCodeContainer = getContainer('appreciation-code-container') setAppreciationCode(appreciationCodeContainer) head.appendChild(checkboxContainer) head.appendChild(promptContainer) head.appendChild(appreciationCodeContainer) head.style.height = `${head.offsetHeight + 30}px` } function bindKeyboardEvents () { document.onkeydown = function (event) { var complete = false var basicActiveDiv = document.querySelector('div.basic.active') var video = document.getElementById('player_html5_api') if (basicActiveDiv && basicActiveDiv.classList.contains('complete')) complete = true switch (event.key) { 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.click() break } } } } function findNextVideo () { var video = document.getElementById('player_html5_api') if (video) { if (!document.getElementById('continue').checked) { video.currentTime = 0 } else { let current_video = document.querySelector('.basic.active') let next_part = current_video.parentNode let next_video = current_video // 定义判断是否视频的函数 let isVideo = (node) => { return 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() start() } 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() start() } 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) { if (next_part.firstElementChild.nextElementSibling.childElementCount === 0) { search(mode.OUTER_LIST) } else { toExpandListFirstElement() if (isList()) { toInnerList() search(mode.INNER_LIST) } else { search(mode.FIRST_VIDEO) } } } else { setTimeout(waitForExpand, 250) } } waitForExpand() 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) } } } } window.onload = function () { (function waitHead () { if (document.querySelector('.learn-head')) { placeComponents() bindChapterChange() function ready () { start() console.log('UOOC assistant beta has initialized.') } if (document.getElementById('player_html5_api')) { ready() } else { var iconVideo = document.querySelector('.icon-video') if (iconVideo) { iconVideo.click() setTimeout(ready, 250) } } } else { setTimeout(waitHead, 250) } })() }