您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
【使用前先看介绍/有问题可反馈】UOOC 优课联盟助手 (内测版) (UOOC assistant beta):可选是否倍速 (若取消勾选则一倍速播放),可选是否静音 (若取消勾选则恢复原音量),可选是否播放 (若取消勾选则暂停播放),可选是否连播 (若取消勾选则循环播放),离开页面保持视频状态,自动回答视频中途弹出问题,可复制已提交测验题目及答案;键盘左右方向键可以控制视频快进/快退,上下方向键可以控制音量增大/减小,空格键可以控制播放/暂停;如果视频标题下面出现 `倍速/静音/播放/连播` 选项说明脚本正常启动运行。
// ==UserScript== // @name UOOC assistant beta // @name UOOC 优课联盟助手 (内测版) // @namespace http://tampermonkey.net/ // @version 1.0.6 // @description 【使用前先看介绍/有问题可反馈】UOOC 优课联盟助手 (内测版) (UOOC assistant beta):可选是否倍速 (若取消勾选则一倍速播放),可选是否静音 (若取消勾选则恢复原音量),可选是否播放 (若取消勾选则暂停播放),可选是否连播 (若取消勾选则循环播放),离开页面保持视频状态,自动回答视频中途弹出问题,可复制已提交测验题目及答案;键盘左右方向键可以控制视频快进/快退,上下方向键可以控制音量增大/减小,空格键可以控制播放/暂停;如果视频标题下面出现 `倍速/静音/播放/连播` 选项说明脚本正常启动运行。 // @author cc // @include http://www.uooc.net.cn/home/learn/index* // @grant none // ==/UserScript== function ckeckTestIgnorable () { var cid = location.href.match(/index#\/(\d+)\//)[1] $.ajax({ url: 'http://www.uooc.net.cn/home/learn/getCourseLearn', type: 'GET', data: { cid: cid } }).then(res => { window.canIgnoreTest = Boolean(res.data.course_learn_mode === '20') }) } 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 = `提示:<u>该版本为内测版,使用时请先关闭正式版</u>,<u><a href="https://gf.qytechs.cn/zh-CN/scripts/425837-uooc-assistant-beta/feedback" target="_blank" style="color: yellow;">若出现 BUG 点此反馈</a></u>,键盘的 \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 = '<u>本脚本使用完全免费,您的打赏是作者维护下去的最大动力!点此打赏作者😊</u>' 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: if (isExistsVideo()) { toFirstVideo() next_video.click() start() } else if (isExistsNextListAfterFile()) { search(mode.LAST_LIST) } else if (window.canIgnoreTest) { next_part = next_part.lastElementChild search(mode.OUTER_LIST) } break case mode.NEXT_VIDEO: if (isExistsNextVideo()) { toNextVideo() next_video.click() start() } else if (isExistsNextListAfterFile()) { search(mode.LAST_LIST) } else { search(mode.OUTER_ITEM) } break case mode.LAST_LIST: toNextListAfterFile() toInnerList() search(mode.INNER_LIST) break case mode.NEXT_LIST: toNextListAfterList() search(mode.INNER_LIST) break case mode.INNER_LIST: 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: 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: 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 () { ckeckTestIgnorable() 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) } } waitHead() }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址