// ==UserScript== // @name 英华网课课助手 Plus // @description 自动连播网课 // @author little3022 // @namespace little3022.TM // @homepageURL https://greasyfork.org/zh-CN/users/782903-little3022 // @version 1.0.0 // @license GNU GPL v2.0 // @icon data:image/gif;base64,R0lGODlhQABAALMAAAAAABCWm/vaCPoFE3t+fu3t7AOi6+C8rXV0dJHR7leHifRPVpCQkUa48PTiUQAAACH5BAkAAA8ALAAAAABAAEAAAAT+8MlJq7046827/2B4LWJpVsNwrmVKsjCXpnGNLTNt71Ku8zXfDBgUqogs4xBpwikHL2bouZR+qLOoVYb9bTXd3FfjzC7OZ984MzscLoVDeW1JvTkFHL2yKIDkexJ3IXmBJ36GiYqLjB4FiBaIkIaPD4+TE5WVjH6YkZ9rl5uRohWDQAYYnRqSFAcORA2ppJ6Zlp4FAkQGsxSatRKXFwKwO7y9oBnAArrGvMoby8Q2x8i2wLbDzDWyxwk7DszbMNUGDeDizSvlzzbp4yfs5jXh78Ul8u0w7+Im3fLnWPATdw9EvmMBTdQbqO7DwWoJQyxkWLDDQ4giJjJsyOFiuYiSHDRu5Jjhn0eEIUcOjNGgW8uXDb5dEKmyXyMpASTkxLDTws6fPR/81GkigNGjSCcMJUohaNCmQp+KWNoU6VGlVpNGzWqUhVSrOntKZZozq9CiZqFSxaoVqtIVS8Vy7epzbNoSc51GPXuBLl+3Mfz2/dpVMFuwJwpXyGtXw9gQgq8udmx4K1e8NzNr3sy5s+cMEQAAOw== // @match *://*.yinghuaonline.com/* // @grant GM_setValue // @grant GM_getValue // @downloadURL none // ==/UserScript== class YHAssistant { /** * 类名: YHAssistant * 说明: 主类, 用于存放程序所有数据. **/ constructor() { this.HTML = null; this.setting = {}; this.regexs = { courseId: /(?<=courseId=)\d+/, nodeId: /(?<=nodeId=)\d+/ } this.pathnames = [ '/user/node' ]; this.beepURLs = [ 'https://cdn2.ear0.com:3321/preview?soundid=34591&type=mp3', 'https://downsc.chinaz.net/Files/DownLoad/sound1/202106/14428.mp3', 'https://downsc.chinaz.net/Files/DownLoad/sound1/202103/14039.mp3' ]; this.finishBeep = 'https://ppt-mp3cdn.hrxz.com/d/file/filemp3/hrxz.com-bjxdrlfq5o143304.mp3'; this.videoData = { // 本地视频总时长, 已提交时长, 本次播放有效时长 totalTime: 0, submitTime: 0, validTime: 0 }; this.courseData = { page: 0, pageCount: 0, index: 0, recordsCount: 0, nextURL: '' }; this.timer = { mainTimer: { id: null, value: 0 } }; this._elTree = {}; this._locker = { refresh: { value: false, timeout: 3000 }, button: { value: false, timeout: 300 }, beep: { value: false, timeout: 1000 }, request: { value: false, timeout:500 } }; this._elBeep = document.createElement('audio'); this._init(); } _init() { Function.prototype.getMultiLine = function() { var lines = new String(this); lines = lines.substring(lines.indexOf("/*") + 3, lines.lastIndexOf("*/")); return lines; } function divText() { /*
英华网课助手 Plus 控制台
作者: little3022版本: 1.0.0
课程信息 设置
null
观看次数 null
视频时长 null
剩余时长 null
状态 null
计时 null
预计完成 null
进度 null

视频设置

音量

音效设置

音量
测试 注意: Edge 浏览器“标签睡眠”后程序将暂停运行!!! 播放声音可阻止睡眠.
*/ } function css1() { /* #YHAssistant { --yha-width: 300px; --yha-height: 380px; --yha-color: gray; z-index: 999; position: fixed; top: calc(50vh - var(--yha-height) / 2); left: 8px; margin: 0; padding: 0; border: 0; border-radius: 8px; box-shadow: 3px 3px 8px #3338; width: var(--yha-width); max-height: var(--yha-height); font-size: 16px; color: var(--yha-color); background-color: #F5F5DCDD; transition: all 0.5s ease; overflow: hidden; -webkit-user-select: none; user-select: none; } [data-yha="title"] { margin: 1em auto; font-size: 1em; font-weight: bold; text-align: center; } [data-yha="title"] span { font-size: 0.6em; font-weight: normal; vertical-align: super; } [data-yha="info"] { border-bottom: 1px solid var(--yha-color); padding-bottom: 8px; font-size: 0.6em; text-align: center; } [data-yha="info"] span { padding: 0 5px 0 5px; border-right: 1px solid var(--yha-color); } [data-yha="info"] span:last-child { padding: 0 0 0 5px; border-right: none; } [data-yha="toolbar"] { z-index: 10; margin: 8px 12px; } [data-yha="toolbar"] [data-yha="icon"] { float: left; margin: 0 8px; } [data-yha="toolbar"] [data-yha="button"] { float: right; margin-left: 8px; } [data-yha="icon"] svg, [data-yha="button"] svg { width: 20px; height: 20px; margin: auto; pointer-events: none; } [data-yha="button"] { opacity: 0.6; height: 30px; width: 30px; display: block; cursor: pointer; line-height: 36px; text-align: center; border-radius: 8px; transition: opacity 0.1s ease; fill: #333; } [data-yha="button"]:hover { opacity: 1; background-color: #3331; } [data-yha="button"]::after { display: none; content: attr(yha-tooltip); position: relative; top: -15px; font-size: 0.8em; } [data-yha="button"]:hover::after { display: block; } @keyframes refreshing{ from{transform: rotate(0deg);} to{transform: rotate(-360deg);} } [yha-action="refresh"].action svg { animation: refreshing 3s linear infinite; animation-fill-mode: forwards; } [data-yha="container"] { display: flex; flex-direction: row; position: relative; left: 0; width: 200%; min-height: 200px; transition: left 0.5s ease; } [data-yha="progress"], [data-yha="setting"] { padding: 5px 20px 0 20px; width: 50%; } [data-yha="table"] { clear: both; margin: 0 auto 18px; width: 80%; line-height: 1.5em; text-align: center; } [data-yha="table"] caption { margin: 8px auto; width: calc(var(--yha-width) * 0.8); font-size: 1.1em; font-weight: bold; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } [data-yha="table"] th, td { border-bottom: 1px solid #ccc; } [data-yha="table"] th { font-size: 0.9em; } [data-yha="setting"] { font-size: 0.85em; } [data-yha="setting-value"] input[type="range"] { position: relative; top: -2px; width: calc(100% - 75px); } [data-yha="setting-value"] input[type="range"]::after { content: attr(value); float: right; width: 0; } [data-yha="setting-value"] input[type="checkbox"] { position: relative; top: -1.5px; margin: 0 8px 0 0; } [data-yha="setting-value"] input[type="radio"] { position: relative; top: 1.5px; margin: 0 8px 0 0; } [data-yha="text"] { margin: 5px auto; padding-top: 5px; border-top: 1px var(--yha-color) dashed; font-size: 1em; font-weight: bold; } [data-yha="setting-item"] { margin: 5px auto; } [data-yha="setting-key"] { position: relative; top: -3px; } [data-yha="test"] { float: right; position: relative; top: -32px; left: -18px; display: inline-block; border-radius: 8px; width: 52px; height: 28px; color: #fff; line-height: 28px; text-align: center; background-color: #0005; box-shadow: 3px 3px 3px #3333; cursor: pointer; } [data-yha="test"]:hover { background-color: #0008; } [data-yha="tip"] { display: inline-block; position: relative; top: -20px; line-height: 20px; color: red; font-weight: bold; text-indent: 1.5em; } [data-yha="msgbox"] { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; display: flex; flex-direction: column; justify-content: center; pointer-events: none; } [data-yha="msgbox"] > span { z-index: 999; margin: 5px auto; padding: 8px 20px; border: 2px dashed royalblue; border-radius: 8px; text-align: center; color: white; transition: opacity 0.8s linear; } .msgbox-info { background-color: #ef950399; } .msgbox-error { background-color: #ff1e1e99; } .yha-input { opacity: 0; position: absolute; left: 0; cursor: pointer; } .yha-icon-muting { display: inline-block; width: 18px; height: 18px; background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSJncmF5IiB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxwYXRoIGQ9Ik03NjIuNyA0NjkuN2MwLTkzLjUtNjkuMy0xNzAuMS0xNTkuMi0xODN2NTMuMWM2MC41IDEyLjMgMTA2LjEgNjUuOCAxMDYuMSAxMzBzLTQ1LjYgMTE3LjctMTA2LjEgMTMwdjUzLjFjODkuOS0xMy4yIDE1OS4yLTg5LjggMTU5LjItMTgzLjJ6TTk5LjQgMzYzLjV2MjEyLjJjMCAyOS4zIDIzLjggNTMuMSA1My4xIDUzLjFoNzkuNlYzMTAuNWgtNzkuNmMtMjkuMyAwLTUzLjEgMjMuNy01My4xIDUzeiBtMzcxLjUtMjEyLjJMMjg1LjIgMjc1LjF2Mzg5LjFMNDcwLjkgNzg4YzI5LjMgMCA1My4xLTIzLjggNTMuMS01My4xVjIwNC40Yy0wLjEtMjkuNC0yMy44LTUzLjEtNTMuMS01My4xeiBtMTMyLjYtNTIuN3Y1NC41YzE1NS44IDMwIDI2NS4zIDE1Ny4xIDI2NS4zIDMxNi42IDAgMTU4LjYtMTA2LjEgMjgxLjUtMjY1LjMgMzE2LjZ2NTQuNWMxNzkuOC0yNi4zIDMxOC40LTE4MS42IDMxOC40LTM3MS4xIDAtMTg5LjUtMTM4LjYtMzQ0LjgtMzE4LjQtMzcxLjF6Ij48L3BhdGg+PC9zdmc+); cursor: pointer; } .yha-icon-muting.muted { background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSJncmF5IiB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxwYXRoIGQ9Ik0xMDAuMSAzOTl2MjIxYzAgMzAuNSAyNC43IDU1LjMgNTUuMyA1NS4zaDgyLjlWMzQzLjdoLTgyLjljLTMwLjUgMC01NS4zIDI0LjgtNTUuMyA1NS4zek04MDMgNTA5LjVsMTE1LjMtMTE1LjNjMTIuNC0xMi40IDEzLjYtMzEuNSAyLjYtNDIuNXMtMzAtOS45LTQyLjUgMi42TDc2My4xIDQ2OS42IDY0Ny45IDM1NC4zYy0xMi40LTEyLjUtMzEuNS0xMy42LTQyLjUtMi42cy05LjkgMzAgMi42IDQyLjVsMTE1LjMgMTE1LjNMNjA4IDYyNC44Yy0xMi40IDEyLjQtMTMuNiAzMS41LTIuNiA0Mi41czMwIDkuOSA0Mi41LTIuNmwxMTUuMy0xMTUuMyAxMTUuMyAxMTUuM2MxMi40IDEyLjQgMzEuNSAxMy42IDQyLjUgMi42czkuOS0zMC0yLjYtNDIuNUw4MDMgNTA5LjV6TTQ4Ni45IDE3OEwyOTMuNSAzMDYuOXY0MDUuMkw0ODYuOSA4NDFjMzAuNSAwIDU1LjMtMjQuNyA1NS4zLTU1LjNWMjMzLjJjLTAuMS0zMC41LTI0LjgtNTUuMi01NS4zLTU1LjJ6Ij48L3BhdGg+PC9zdmc+); } */ } function css2() { /* #YHAssistant:hover { left: 0 !important; } */ } this.divText = divText; this.css1 = css1; this.css2 = css2; } showGUI() { var _el = document.createElement('div'); var _css = document.createElement('style'); _el.id = 'YHAssistant'; _el.innerHTML = this.divText.getMultiLine(); _css.type = 'text/css'; _css.innerHTML = this.css1.getMultiLine(); document.documentElement.insertBefore(_el, document.body); document.head.appendChild(_css); this.HTML = _el; setTimeout(() => { _el.style.left = 20 - _el.offsetWidth + 'px'; _css.innerHTML += this.css2.getMultiLine(); }, 3000); } initElementTree() { this.HTML = document.getElementById('YHAssistant'); let table = this.HTML.querySelector('table[data-yha="table"]'); this._elTree = { msgbox: this.HTML.querySelector('[data-yha="msgbox"]'), container: this.HTML.querySelector('[data-yha="container"]'), labelInfo: this.HTML.querySelector('[data-yha="label-info"]'), labelSet: this.HTML.querySelector('[data-yha="label-set"]'), btRefresh: this.HTML.querySelector('[yha-action="refresh"]'), btSet: this.HTML.querySelector('[yha-action="setting"]'), btBack: this.HTML.querySelector('[yha-action="back"]'), btReset: this.HTML.querySelector('[yha-action="reset"]'), progress: { table: { caption: table.caption, cells: [ table.rows[0].cells[1], table.rows[1].cells[1], table.rows[2].cells[1], table.rows[3].cells[1], table.rows[4].cells[1], table.rows[5].cells[1], table.rows[6].cells[1] ] } }, setting: { video: { muting: this.HTML.querySelector('span[yha-setting="video-muting"]'), volume: this.HTML.querySelector('input[yha-setting="video-volume"]'), autoplay: this.HTML.querySelector('input[yha-setting="video-autoplay"]') }, beep:{ muting: this.HTML.querySelector('[yha-setting="beep-muting"]'), volume: this.HTML.querySelector('input[yha-setting="beep-volume"]'), beep1: this.HTML.querySelector('input[yha-setting="beep-beep1"]'), beep2: this.HTML.querySelector('input[yha-setting="beep-beep2"]'), beep3: this.HTML.querySelector('input[yha-setting="beep-beep3"]'), beep4: this.HTML.querySelector('input[yha-setting="beep-beep4"]'), beepURL: this.HTML.querySelector('input[yha-setting="beep-beepURL"]'), test: this.HTML.querySelector('[yha-action="test"]') } } } } timerLock(locker) { if(!locker.value) { locker.value = true; setTimeout(() => {locker.value = false;}, locker.timeout); return false; } return true; } bindEvent() { let elContainer = this._elTree.container; let labelInfo = this._elTree.labelInfo; let labelSet = this._elTree.labelSet; let btRefresh = this._elTree.btRefresh; let btSet = this._elTree.btSet; let btBack = this._elTree.btBack; let btReset = this._elTree.btReset; let vMute = this._elTree.setting.video.muting; let bMute = this._elTree.setting.beep.muting; let vVolume = this._elTree.setting.video.volume; let bVolume = this._elTree.setting.beep.volume; let vAutoplay = this._elTree.setting.video.autoplay; let beep4 = this._elTree.setting.beep.beep4; let txtURL = this._elTree.setting.beep.beepURL; let btTest = this._elTree.setting.beep.test; let radioArr = [ this._elTree.setting.beep.beep1, this._elTree.setting.beep.beep2, this._elTree.setting.beep.beep3, this._elTree.setting.beep.beep4, ]; let arr = []; btRefresh.addEventListener('click', () => { if(this.timerLock(this._locker.refresh)) return; let tStr = btRefresh.getAttribute('yha-tooltip'); let tTime = this._locker.refresh.timeout; btRefresh.classList.toggle('action'); this.refreshClick(); let tTimer = setInterval(() => { btRefresh.setAttribute('yha-tooltip', `${(tTime -= 100) / 1000}s`); if(tTime <= 0) { btRefresh.setAttribute('yha-tooltip', tStr); btRefresh.classList.toggle('action'); tTimer && clearInterval(tTimer); } }, 100); }); btSet.addEventListener('click', () => { if(this.timerLock(this._locker.button)) return; elContainer.style.left = '-100%'; labelInfo.style.display = 'none'; labelSet.style.display = 'inline-block'; btRefresh.style.display = 'none'; btSet.style.display = 'none'; btBack.style.display = 'block'; btReset.style.display = 'block'; }); btBack.addEventListener('click', () => { if(this.timerLock(this._locker.button)) return; elContainer.style.left = '0'; labelInfo.style.display = 'inline-block'; labelSet.style.display = 'none'; btRefresh.style.display = 'block'; btSet.style.display = 'block'; btBack.style.display = 'none'; btReset.style.display = 'none'; }); btReset.addEventListener('click', () => { if(this.timerLock(this._locker.button)) return; this.recoverSetting(); }); arr = [ this._elTree.progress.table.cells[1], this._elTree.progress.table.cells[2], this._elTree.progress.table.cells[4] ]; arr.forEach(item => { item.set = function(value) { if(value < 0) value = 0; item.innerText = value + ' s'; }; }); vMute.addEventListener('click', () => { if(this.timerLock(this._locker.button)) return; if(!this.setting.video.muted) { vMute.classList.add('muted'); vVolume.value = 0; } else { vMute.classList.remove('muted'); vVolume.value = this.setting.video.volume; } this.setting.video.muted = !this.setting.video.muted; if(this.elVideo) { this.elVideo.muted = this.setting.video.muted; } this.saveSetting(); }); bMute.addEventListener('click', () => { if(this.timerLock(this._locker.button)) return; if(!this.setting.beep.muted) { bMute.classList.add('muted'); bVolume.value = 0; } else { bMute.classList.remove('muted'); bVolume.value = this.setting.beep.volume; } this.setting.beep.muted = !this.setting.beep.muted; this.saveSetting(); }); vVolume.addEventListener('input', () => { vVolume.setAttribute('value', vVolume.value); if(vVolume.value == 0) vMute.classList.add('muted'); vMute.classList.remove('muted'); if(this.elVideo) { this.elVideo.muted = false; this.elVideo.volume = vVolume.value / 100; } }); bVolume.addEventListener('input', () => { bVolume.setAttribute('value', bVolume.value); if(bVolume.value == 0) bMute.classList.add('muted'); bMute.classList.remove('muted'); }); vVolume.addEventListener('change', () => { if(this.timerLock(this._locker.button)) return; if(vVolume.value == 0) { vMute.classList.add('muted'); vVolume.setAttribute('value', this.setting.video.volume); this.setting.video.muted = true; } else { vMute.classList.remove('muted'); this.setting.video.volume = parseInt(vVolume.value); this.setting.video.muted = false; } if(this.elVideo) { this.elVideo.muted = this.setting.video.muted; this.elVideo.volume = this.setting.video.volume / 100; // 同步视频标签操作, 防止音量被重置 } this.saveSetting(); }); bVolume.addEventListener('change', () => { if(this.timerLock(this._locker.button)) return; if(bVolume.value == 0) { bMute.classList.add('muted'); bVolume.setAttribute('value', this.setting.beep.volume); this.setting.beep.muted = true; } else { bMute.classList.remove('muted'); this.setting.beep.volume = parseInt(bVolume.value); this.setting.beep.muted = false; this.beep(); this.saveSetting(); } }); vAutoplay.addEventListener('click', () => { if(this.timerLock(this._locker.button)) return; if(!this.setting.video.autoplay) { vAutoplay.checked = true; } else { vAutoplay.checked = false; } this.setting.video.autoplay = !this.setting.video.autoplay; this.saveSetting(); }); arr = [ this._elTree.setting.beep.beep1, this._elTree.setting.beep.beep2, this._elTree.setting.beep.beep3 ] arr.forEach(item => { item.addEventListener('click', () => { if(this.timerLock(this._locker.beep)) { radioArr[this.setting.beep.effect].checked = true; return; } txtURL.disabled = true; this.setting.beep.effect= parseInt(item.value); this.beep(); this.saveSetting(); }); }); beep4.addEventListener('click', () => { if(this.timerLock(this._locker.button)) { radioArr[this.setting.beep.effect].checked = true; return; } this.setting.beep.effect = 3; txtURL.disabled = false; }); txtURL.addEventListener('input', () => { if(this.setting.beep.customURL.search(/https:\S+/) != 0) return; this.setting.beep.customURL = txtURL.value; }); txtURL.addEventListener('change', () => { if(this.timerLock(this._locker.button)) return; if(this.setting.beep.customURL.search(/https:\S+/) != 0) return this.showInfo('无效的 URL', 5000, 1); this.saveSetting(); }); btTest.addEventListener('click', () => { if(this.timerLock(this._locker.beep)) return; if(this.setting.beep.effect == 3 && (this.setting.beep.customURL.search(/https:\S+/) != 0)) return this.showInfo('无效的 URL', 5000, 1); this.beep(); }); } loadSetting() { this.setting = GM_getValue('YHAssistant', null); if(!this.setting) this.recoverSetting(); if(this.setting.beep.effect == 3 && (this.setting.beep.customURL.search(/https:\S+/) != 0)) { this.setting.beep.effect = 0; this.setting.beep.customURL = ''; } this.showSetting(); } saveSetting() { if(this.setting.beep.effect == 3 && (this.setting.beep.customURL.search(/https:\S+/) != 0)) { this.setting.beep.effect = 0; this.setting.beep.customURL = ''; } GM_setValue('YHAssistant', this.setting); } recoverSetting() { /** * 函数名: recoverSetting * 说明: 重置设置. **/ let tURL = ''; if(this.setting.beep.customURL && (this.setting.beep.customURL.search(/https:\S+/) === 0)) tURL = this.setting.beep.customURL; this.setting = { video: { muted: false, volume: 1, autoplay: true }, beep: { muted: false, volume: 80, effect: 0, customURL: '' }, }; this.setting.beep.customURL = tURL; this.showSetting(); this.saveSetting(); } _setRange(el, value) { el.value = value; el.setAttribute('value', value); } showSetting() { let vMute = this._elTree.setting.video.muting; let bMute = this._elTree.setting.beep.muting; let vVolume = this._elTree.setting.video.volume; let bVolume = this._elTree.setting.beep.volume; let vAutoplay = this._elTree.setting.video.autoplay; let txtURL = this._elTree.setting.beep.beepURL; if(this.setting.video.muted) { vMute.classList.add('muted'); vVolume.setAttribute('value', this.setting.video.volume); vVolume.value = 0; } else { this._setRange(vVolume, this.setting.video.volume); } if(this.setting.video.autoplay) vAutoplay.checked = true; if(this.setting.beep.muted) { bMute.classList.add('muted'); vVolume.setAttribute('value', this.setting.beep.volume); bVolume.value = 0; } else { this._setRange(bVolume, this.setting.beep.volume); if(this.setting.beep.volume < 50) this.showInfo('提示音音量过低', 6000); } switch(this.setting.beep.effect) { case 0: this._elTree.setting.beep.beep1.checked = true; txtURL.disabled = true; break; case 1: this._elTree.setting.beep.beep2.checked = true; txtURL.disabled = true; break; case 2: this._elTree.setting.beep.beep3.checked = true; txtURL.disabled = true; break; case 3: this._elTree.setting.beep.beep4.checked = true; txtURL.disabled = false; } txtURL.value = this.setting.beep.customURL; } beep(src='') { if(this.setting.beep.muted) return; if(src) { this._elBeep.src = src; } else if(this.setting.beep.effect == 3) { this._elBeep.src = this.setting.beep.customURL; } else { this._elBeep.src = this.beepURLs[this.setting.beep.effect]; } this._elBeep.volume = this.setting.beep.volume / 100; this._elBeep.play(); } loopBeep(times=3, interval=1000) { let i = 0; if(times <= 1) { this.beep(); return; } let val = setInterval(() => { if(i++ < times) this.beep(); else val && clearInterval(val); }, interval); return val; } beepTip() { // 组合提示音 this.timer.a = this.loopBeep(3, 2000); this.timer.c = setTimeout(() => { this.timer.b = this.loopBeep(5, 10000); }, 30000); } clearBeepTip() { this.timer.a && clearInterval(this.timer.a); this.timer.b && clearInterval(this.timer.b); this.timer.c && clearTimeout(this.timer.c); } showInfo(msg, timeout=3000, level=0) { let elTip = document.createElement('span'); switch(level) { case 0: elTip.className = 'msgbox-info'; elTip.innerText = 'Ⓘ ' + msg; break; case 1: elTip.className = 'msgbox-error'; elTip.innerText = '⚠ ' + msg; this._elTree.progress.table.caption.innerText = `⚠ ${msg} ⚠`; this._elTree.progress.table.caption.style.color = 'red'; break; } this._elTree.msgbox.appendChild(elTip); setTimeout(() => { elTip.style.opacity = 0; }, timeout - 800); setTimeout(() => { elTip.style.display = 'none'; }, timeout); return !level; } simulateMouseMove(element) { } refreshClick() { if(!this.courseData) return this.showInfo('不支持的页面', 5000, 1); this.showInfo('正在检查进度...'); let courseData = this.checkPage(); if(courseData) { this.courseData = courseData; this.showCourseData(courseData); // 刷新计时器 if(this.timer.d) { clearTimeout(this.timer.d); this.timer.d = null; } let surplusTime = this.videoData.totalTime - this.videoData.submitTime - this.videoData.validTime; this.timer.d = setTimeout(() => { }, parseInt(surplusTime > 0? surplusTime + 3: 0) * 1000); } else { this.showInfo('刷新失败'); } } furureTime(sec=0) { /** * 返回当前时间(hh:mm:ss)加 sec 后的时间 */ let date = new Date(); let sTime = date.toJSON().match(/\d\d:\d\d:\d\d/)[0]; let time = this.parseSec(sTime); return this.formatSec(time + sec); } parseSec(sTime="00:00:00") { /** * 函数名: parseSec() * 说明: 把时间格式的字符串转换为秒数.*/ let sec = 0; if(sTime != "" && !isNaN(Date.parse("1970-1-1 " + sTime))) { //判断是否是时间格式 let t = sTime.split(":"); sec += parseInt(t[0]) * 3600 + parseInt(t[1]) * 60 + parseInt(t[2]); } else return -1; return sec; } formatSec(sec=0) { /** * 函数名: formateSec() * 说明: 把秒数转换为时间格式的字符串.*/ return (new Date(sec * 1000).toTimeString().slice(0, 8)); } getCourseID() { /** * 爬取网页源码中的课程 ID, 用于请求课程数据 */ let courseId = ''; try { courseId = document.querySelector('#wrapper > div.curPlace > div.center > a:last-child').href.match(/(?<=courseId=)\d+/)[0]; } catch(err) { // this.showInfo('courseId 获取失败', 5000, 1); } return courseId; } request(courseId, page=1) { /** * 描述: 从服务器获取课程数据(JSON) * 请求模式: 同步 */ let xhttp = new XMLHttpRequest(); let url = `/user/study_record.json?courseId=${courseId}&page=${page}&_=${(new Date()).valueOf()}`; let result = null; xhttp.onreadystatechange = () => { if(xhttp.readyState == 4 && xhttp.status == 200) { result = JSON.parse(xhttp.responseText); } }; xhttp.open('get', url, false); xhttp.send(); return result; } checkPage(page=1, pageCount=1) { /** * 跳转未完成页面, 返回对应课程数据 */ let courseId = this.getCourseID(); if(!courseId) return this.showInfo('courseId 获取失败', 5000, 1); if(this.setting.pageData && this.setting.pageData.courseId == courseId) page = this.setting.pageData.page; for(; page <= pageCount; page++) { this.showInfo(`请求数据, courseId: ${courseId}, page: ${page}`); let json = this.request(courseId, page); if(json && json.status) { this.showInfo('数据请求成功'); pageCount = json.pageInfo.pageCount; for(let courseData of json.list) { // 查找未完成页面 if(courseData.state.match(/(未学完|未学)/)) { let currentNodeID = document.location.href.match(this.regexs.nodeId)[0]; let newNodeID = courseData.url.match(this.regexs.nodeId)[0]; let index = json.list.indexOf(courseData); let nextCourse = json.list[index + 1]; // 跳转未完成页面 if(currentNodeID != newNodeID) window.open(courseData.url, '_self'); // 当前页面为未完成, 添加页面信息方便后续处理 courseData.page = json.pageInfo.page; courseData.pageCount = json.pageInfo.pageCount; courseData.index = json.pageInfo.page * 20 - 20 + index; courseData.recordsCount = json.pageInfo.recordsCount; if(nextCourse) courseData.nextURL = nextCourse.url; // 保存页码减少重复请求 this.setting.pageData = { courseId: courseId, page: page }; this.saveSetting(); // 立即退出循环, 防止 window.open() 重复请求 return courseData; } } } } if(page >= pageCount) { // 课程进度 100% this.HTML.style.color = 'black'; this.HTML.style.backgroundColor = 'mediumseagreen'; this.showInfo('该课程观看进度 100%'); this._elTree.progress.table.caption.innerText = '(已完成) ' + this._elTree.progress.table.caption.innerText; this._elTree.progress.table.caption.title = this._elTree.progress.table.caption.innerText; this.beep(this.finishBeep); } return false; } showCourseData() { this._elTree.progress.table.caption.innerText = this.courseData.name; this._elTree.progress.table.caption.title = this.courseData.name; this._elTree.progress.table.cells[0].innerText = parseInt(this.courseData.viewCount) + 1; this._elTree.progress.table.cells[1].set(this.parseSec(this.courseData.videoDuration)); this._elTree.progress.table.cells[2].set(this.parseSec(this.courseData.videoDuration) - this.courseData.duration); this._elTree.progress.table.cells[3].innerText = this.courseData.state.match(/(未学完|未学|已学)/)[0]; this._elTree.progress.table.cells[4].set(0); this._elTree.progress.table.cells[5].innerText = this.furureTime(this.parseSec(this.courseData.videoDuration) - this.courseData.duration); this._elTree.progress.table.cells[6].innerText = `${this.courseData.index}/${this.courseData.recordsCount} (${(this.courseData.index / this.courseData.recordsCount * 100).toFixed(2)}%)`; } listeningVideo() { let elVideo = document.querySelector('video'); if(!elVideo) return this.showInfo('未获取视频对象', 5000, 1); if(elVideo.src != this.courseData.localFile) return this.showInfo('视频 URL 不匹配', 5000, 1); this.showInfo('开始监听视频对象'); // 初始化数据 this.videoData.totalTime = this.parseSec(this.courseData.videoDuration); this.videoData.submitTime = parseInt(this.courseData.duration); this.timer.d = null; // 超时查询进度 // 绑定事件 let pos1 = parseInt(elVideo.currentTime), pos2 = 0; elVideo.addEventListener('timeupdate', () => { let pos2 = parseInt(elVideo.currentTime); if(pos2 >= pos1 + 1) { // 经过 1s pos1 = pos2; this._elTree.progress.table.cells[2].set(this.videoData.totalTime - this.videoData.submitTime - this.videoData.validTime++); } }); elVideo.addEventListener('play', () => { if(!this.timer.d) { // 超时查询进度 let surplusTime = this.videoData.totalTime - this.videoData.submitTime - this.videoData.validTime; this.timer.d = setTimeout(() => { this.showInfo('到达设定时间, 正在检查进度...'); this.checkPage(); }, parseInt(surplusTime + 3) * 1000); // 刷新预计完成时间 this._elTree.progress.table.cells[5].innerText = this.furureTime(surplusTime + 3); } this.clearBeepTip(); }); elVideo.addEventListener('pause', () => { if(this.timer.d) { clearTimeout(this.timer.d); this.timer.d = null; } if(elVideo.ended) { if(this.courseData.nextURL) { window.open(this.courseData.nextURL, '_self'); return this.showInfo('正在跳转新页面...'); } else if(this.courseData.page == this.courseData.pageCount) { // 课程进度 100% this.HTML.style.color = 'black'; this.HTML.style.backgroundColor = 'mediumseagreen'; this.showInfo('该课程观看进度 100%'); this._elTree.progress.table.caption.innerText = '(已完成) ' + this._elTree.progress.table.caption.innerText; this._elTree.progress.table.caption.title = this._elTree.progress.table.caption.innerText; this.beep(this.finishBeep); } } this.beepTip(); }); // 监听鼠标移动事件 document.body.onmousemove = () => { this.clearBeepTip(); }; // 应用设置 elVideo.muted = this.setting.video.muted; elVideo.volume = this.setting.video.volume / 100; if(this.setting.video.autoplay) elVideo.play(); elVideo.playbackRate = 1; this.elVideo = elVideo; this.showCourseData(); } exec() { this.showGUI(); this.initElementTree(); this.bindEvent(); this.loadSetting(); if(this.pathnames.indexOf(document.location.pathname) > -1) { let courseData = this.checkPage(); if(courseData) { this.courseData = courseData; // 设置计时器 this.timer.mainTimer.id = setInterval(() => { this._elTree.progress.table.cells[4].set(++this.timer.mainTimer.value); }, 1000); this.listeningVideo(); } } else { this.showInfo('不支持的页面', 5000, 1); } } } (function() { 'use strict'; window.onload = function() { var app = new YHAssistant(); app.exec(); }; })();