您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
课程思政教学能力提升专题网络培训倍速和自动播放
// ==UserScript== // @name 课程思政教学能力提升专题网络培训(改良) // @namespace http://tampermonkey.net/ // @version 0.1.0 // @description 课程思政教学能力提升专题网络培训倍速和自动播放 // @author You // @match https://study.enaea.edu.cn/* // @icon https://www.google.com/s2/favicons?sz=64&domain=enaea.edu.cn // @grant none // @license MIT // ==/UserScript== (async function () { "use strict"; async function waitElementLoaded(ele, max = 50, n = 1, m = "querySelector") { return new Promise((resolve, reject) => { let i = 0; let timer = setInterval(() => { let element = document[m](ele); console.log("find element: ", ele); if (element) { console.log("已找到指定元素: ", element); clearInterval(timer); // 停止重复计时 resolve(element); } else { i++; console.log(`检测次数: ${i}`); if (i >= max) { console.log(`已检测超过 ${i} 次, 无法获取指定元素. `); clearInterval(timer); // 停止重复计时 reject(null); } } }, n * 1000); }); } async function courseDetail() { let speed = 2; async function waitTime(s = 3) { return new Promise((resolve, reject) => { setTimeout(() => { return resolve(); }, s * 1000); }); } let currUrl = window.location.href; let url = currUrl.split("?")[0]; function initialize() { const html = ` <div id="acme-box"> <button id="show" class="btn btn-outline-danger"> <css-icon class="icon-arrow-up"> </button> <div class="acme-card card"> <div id="acme-card-header" title="可移动" class="acme-card-header d-flex flex-row align-items-center card-header bg-danger text-white"> <css-icon class="icon-apps"></css-icon> <span class="mx-2">控制面板</span> <div class="flex-grow-1"></div> <span id="restore" class="restore"> <css-icon class="icon-arrow-down"></css-icon> </span> </div> <div class="acme-card-body card-body"> <span>速度: </span> <select id="speedx" class="acme-speed"> <option value="8.0">8.0x</option> <option value="6.0">6.0x</option> <option value="4.0">4.0x</option> <option value="3.0">3.0x</option> <option selected="selected" value="2.0">2.0x</option> <option value="1.0">1.0x</option> <option value="0.5">0.5x</option> </select> <button id="set-speedx" class="btn btn-outline-danger"> 设置播放速度 </button> </div> </div> </div>`; const cssStyle = ` @charset "UTF-8"; :root { --bs-blue: #0d6efd; --bs-indigo: #6610f2; --bs-purple: #6f42c1; --bs-pink: #d63384; --bs-red: #dc3545; --bs-orange: #fd7e14; --bs-yellow: #ffc107; --bs-green: #198754; --bs-teal: #20c997; --bs-cyan: #0dcaf0; --bs-white: #fff; --bs-gray: #6c757d; --bs-gray-dark: #343a40; --bs-gray-100: #f8f9fa; --bs-gray-200: #e9ecef; --bs-gray-300: #dee2e6; --bs-gray-400: #ced4da; --bs-gray-500: #adb5bd; --bs-gray-600: #6c757d; --bs-gray-700: #495057; --bs-gray-800: #343a40; --bs-gray-900: #212529; --bs-primary: #0d6efd; --bs-secondary: #6c757d; --bs-success: #198754; --bs-info: #0dcaf0; --bs-warning: #ffc107; --bs-danger: #dc3545; --bs-light: #f8f9fa; --bs-dark: #212529; --bs-primary-rgb: 13, 110, 253; --bs-secondary-rgb: 108, 117, 125; --bs-success-rgb: 25, 135, 84; --bs-info-rgb: 13, 202, 240; --bs-warning-rgb: 255, 193, 7; --bs-danger-rgb: 220, 53, 69; --bs-light-rgb: 248, 249, 250; --bs-dark-rgb: 33, 37, 41; --bs-white-rgb: 255, 255, 255; --bs-black-rgb: 0, 0, 0; --bs-body-color-rgb: 33, 37, 41; --bs-body-bg-rgb: 255, 255, 255; --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); --bs-body-font-family: var(--bs-font-sans-serif); --bs-body-font-size: 1rem; --bs-body-font-weight: 400; --bs-body-line-height: 1.5; --bs-body-color: #212529; --bs-body-bg: #fff; } *, *::before, *::after { box-sizing: border-box; } #acme-box { position: absolute; z-index: 2147483647; } #acme-box>#show { position: fixed; bottom: 20px; right: 20px; width: 60px; height: 60px; text-align: center; border-radius: 50%; } #acme-box>.acme-card { display: none; width: 320px; height: 180px; position: fixed; bottom: 20px; right: 20px; font-size: 14px; } #acme-box>div>#acme-card-header:hover { cursor: grab; } #acme-box>div>#acme-card-header:active { cursor: grabbing; } #acme-box>div>div>#restore:hover { cursor: pointer; } /* 图标公共样式 */ #acme-box css-icon { display: inline-block; height: 1em; width: 1em; font-size: 20px; box-sizing: border-box; text-indent: -9999px; vertical-align: middle; position: relative; } #acme-box css-icon::before, #acme-box css-icon::after { content: ''; box-sizing: inherit; position: absolute; left: 50%; top: 50%; -ms-transform: translate(-50%, -50%); transform: translate(-50%, -50%); } /* 图标公共样式线束 */ /* 向上图标 */ #acme-box .icon-arrow-up::before { height: .65em; width: .65em; border-style: solid; border-width: 2px 0 0 2px; -ms-transform: translate(-50%, -25%) rotate(45deg); transform: translate(-50%, -25%) rotate(45deg); } /* 向下图标 */ #acme-box .icon-arrow-down::before { height: .65em; width: .65em; border-style: solid; border-width: 2px 0 0 2px; -ms-transform: translate(-50%, -75%) rotate(225deg); transform: translate(-50%, -75%) rotate(225deg); } /* 标题栏图标 */ #acme-box .icon-apps::before { height: .15em; width: .15em; background: currentColor; box-shadow: -.35em -.35em, -.35em 0, -.35em .35em, 0 -.35em, 0 .35em, .35em -.35em, .35em 0, .35em .35em; } #acme-box .d-flex { display: flex !important; } #acme-box .flex-row { flex-direction: row !important; } #acme-box .flex-grow-1 { flex-grow: 1 !important; } #acme-box .align-items-center { align-items: center !important; } #acme-box .bg-danger { --bs-bg-opacity: 1; background-color: rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important; } #acme-box .text-white { --bs-text-opacity: 1; color: rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important; } #acme-box .btn { display: inline-block; font-weight: 400; line-height: 1.5; color: #212529; text-align: center; text-decoration: none; vertical-align: middle; cursor: pointer; -webkit-user-select: none; -moz-user-select: none; user-select: none; background-color: transparent; border: 1px solid transparent; padding: 0.375rem 0.75rem; font-size: 1rem; border-radius: 0.25rem; transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } #acme-box .btn-outline-danger { color: #dc3545; border-color: #dc3545; } #acme-box .btn-outline-danger:hover { color: #fff; background-color: #dc3545; border-color: #dc3545; } #acme-box .btn-check:focus+.btn-outline-danger, #acme-box .btn-outline-danger:focus { box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.5); } #acme-box .btn-check:checked+.btn-outline-danger, #acme-box .btn-check:active+.btn-outline-danger, #acme-box .btn-outline-danger:active, #acme-box .btn-outline-danger.active, #acme-box .btn-outline-danger.dropdown-toggle.show { color: #fff; background-color: #dc3545; border-color: #dc3545; } #acme-box .btn-check:checked+.btn-outline-danger:focus, #acme-box .btn-check:active+.btn-outline-danger:focus, #acme-box .btn-outline-danger:active:focus, #acme-box .btn-outline-danger.active:focus, #acme-box .btn-outline-danger.dropdown-toggle.show:focus { box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.5); } #acme-box .btn-outline-danger:disabled, #acme-box .btn-outline-danger.disabled { color: #dc3545; background-color: transparent; } #acme-box .card { /* position: relative; display: flex; */ flex-direction: column; min-width: 0; word-wrap: break-word; background-color: #fff; background-clip: border-box; border: 1px solid rgba(0, 0, 0, 0.125); border-radius: 0.75rem; } #acme-box .card-body { flex: 1 1 auto; padding: 1rem 1rem; } #acme-box .card-title { margin-bottom: 0.5rem; } #acme-box .card-header { padding: 0.5rem 1rem; margin-bottom: 0; background-color: rgba(0, 0, 0, 0.03); border-bottom: 1px solid rgba(0, 0, 0, 0.125); /* border-radius: 0.75rem; */ border-top-left-radius: 0.75rem; border-top-right-radius: 0.75rem; } #acme-box .mx-2 { margin-right: 0.5rem !important; margin-left: 0.5rem !important; } `; function addStyleCss(styleCss) { let style = document.createElement("style"); style.type = "text/css"; let text = document.createTextNode(styleCss); style.appendChild(text); document.querySelector("head").appendChild(style); } // 在 </body>标签前添加 html function addHtml(html) { document.body.insertAdjacentHTML("beforeend", html); } addStyleCss(cssStyle); addHtml(html); } initialize(); (() => { // 实现界面的显示切换及拖动,这部分要放在最后面 let btnShow = document.querySelector("#show"); let acmeCard = document.querySelector("#acme-box > .acme-card"); let acmeCardheader = document.querySelector("#acme-card-header"); let restore = document.querySelector("#restore"); let btnSetSpeedx = document.querySelector("#set-speedx"); let speedSelect = document.querySelector("#speedx"); speedSelect.addEventListener("change", async (e) => { console.log(speedSelect.value); speed = speedSelect.value; await setPlaySpeed(speed); }); window.addEventListener("hashchange", async function (event) { console.log("url changed", event); await setPlaySpeed(speed); }); btnShow.addEventListener("click", (e) => { acmeCard.style.display = "block"; btnShow.style.display = "none"; }); restore.addEventListener("click", (e) => { acmeCard.style.display = "none"; btnShow.style.display = "block"; }); btnSetSpeedx.addEventListener("click", (e) => { setPlaySpeed(speed); }); // 鼠标指向标题栏时拖动 acmeCard acmeCardheader.addEventListener("mousedown", (e) => { let event = e || window.event; // 获取鼠标按下时的位置 let pageX = event.pageX || event.clientX + document.documentElement.scrollLeft; let pageY = event.pageY || event.clientY + document.documentElement.scrollTop; // 计算鼠标按下时距例子的位置 let spaceX = pageX - acmeCard.offsetLeft; let spaceY = pageY - acmeCard.offsetTop; // 实现拖动的函数 function moveacmeCard(e) { let event = e || window.event; // 获取鼠标的位置 let pageX = event.pageX || event.clientX + document.documentElement.scrollLeft; let pageY = event.pageY || event.clientY + document.documentElement.scrollTop; // 计算并设置盒子移动后的位置 acmeCard.style.left = `${pageX - spaceX}px`; acmeCard.style.top = `${pageY - spaceY}px`; // 取消选中标题栏的文字 if (window.getSelection) { window.getSelection().removeAllRanges(); } else { document.selection.empty(); } } // 移动例子 document.addEventListener("mousemove", moveacmeCard); // 释放鼠标时停止移动 document.addEventListener("mouseup", () => { document.removeEventListener("mousemove", moveacmeCard); }); }); })(); const vd = "#J_CC_videoPlayerDiv > video "; let video = null; async function findVideoEl() { const el = await waitElementLoaded(vd); video = el; let timer; video.addEventListener("ended", async function (e) { console.log("播放已经结束"); clearTimeout(timer); await waitTime(1); const xgReplay = document.querySelector(".xgplayer-replay"); const styles = getComputedStyle(xgReplay); if (styles.display !== "none") { const query = new URLSearchParams(location.search); window.opener.postMessage({ courseId: query.get("courseId"), }); } }); // video.addEventListener("durationchange", async function (e) { // console.log("播放时长改变"); // await waitTime(1); // setPlaySpeed(speed); // }); video.addEventListener("pause", async function (e) { console.log("播放已暂停"); timer = setTimeout(showPause, 1000); }); setPlaySpeed(speed); video.play(); video.muted = true; video.addEventListener("DOMNodeRemovedFromDocument", findVideoEl); } async function setPlaySpeed(speed = 4) { video.playbackRate = parseInt(speed); console.log("speed: ", video.playbackRate); } async function showPause() { try { let pauseBtn = await waitElementLoaded(".dialog-box button", 2); if (pauseBtn) { console.log("触发离机检测,已恢复"); pauseBtn.click(); } else { video?.play(); } } catch (e) { console.log("no dialog button."); } } findVideoEl(); // setInterval(async function () { // console.log("show pause dialog."); // await showPause(); // }, 1000 * 60); } async function courseList() { let lists = []; let btn; let courseNum; const insertBtn = () => { const btnHtml = ` <button id="cus_btn" style="position:absolute;right:20px;bottom:20px;">收集课程<div id="course_num">?</div></button> `; document.body.insertAdjacentHTML("beforeend", btnHtml); btn = document.getElementById("cus_btn"); courseNum = document.getElementById("course_num"); btn.addEventListener("click", initLists); }; async function initLists() { lists = Array.from( await waitElementLoaded("a[title=开始学习]", 50, 1, "querySelectorAll") ); courseNum && (courseNum.innerText = lists.length); } insertBtn(); initLists(); window.addEventListener("message", (data) => { console.log("messgae", data); if (!data.data.courseId) { return; } const curIndex = lists.findIndex((item) => { console.log(item.getAttribute("courseid-id"), data.data.courseId); return item.getAttribute("courseid-id") === data.data.courseId; }); if (curIndex === -1) { console.error("错误的courseId"); return; } const nextCourse = lists[curIndex + 1]; if (nextCourse) { nextCourse.click(); } else { console.log("当前页课程已学完"); } }); } const path = location.pathname; if (path === "/circleIndexRedirect.do") { console.log("课程列表页启动"); courseList(); } if (path === "/viewerforccvideo.do") { console.log("课程详情页启动"); courseDetail(); } // Your code here... })();