// ==UserScript== // @name 宝武学习助手 // @namespace http://tampermonkey.net/ // @version 1.3.3 // @description 1.自动静音、自动播放、自动下一节;2.自动完成学习时长;3.学习进度状态显示 // @author jinxhcn // @match *://learn.baowugroup.com/* // @grant GM_info // @license MIT // @run-at document-idle // ==/UserScript== (function () { "use strict"; console.log(`✅ ${GM_info.script.name} v${GM_info.script.version}`); function n() { const n = new Date; return `${String(n.getHours()).padStart(2, "0")}:${String(n.getMinutes()).padStart(2, "0")}:${String(n.getSeconds()).padStart(2, "0")}`; } const e = { o: 2e3, t: 1e3, i: 3e3, l: "bw-learn-status-bar", u: "bw-learn-status-styles", p: 500, m: 18e4 }; const o = { h: false, $: "", v: false, k: null, T: false, _: false, S: false, I: false, A: null, C: false, M: null, L: null, P: false, E: false, D: true, F: null }; function t() { return document.querySelector("#xg-video video"); } function s(n) { if (!n) return false; if (!n.muted) { n.muted = true; console.log("🔇 已静音"); return true; } return false; } function r() { const n = document.querySelector(".el-tree-node.is-current"); if (!n) return false; let e = n; while (e) { let n = e.nextElementSibling; while (n) { if (n.classList.contains("el-tree-node")) return true; n = n.nextElementSibling; } e = e.parentElement?.closest?.(".el-tree-node"); } return false; } function i() { const n = ["播放下一节", "下一节"]; for (const e of n) { const n = document.querySelectorAll("button"); for (const o of n) if (o.offsetParent !== null && (o.innerText.trim() === e || o.innerText.includes(e))) { o.click(); console.log('✅ 已点击"下一节"'); return true; } } const e = document.querySelectorAll("*"); for (const n of e) if (n.offsetParent !== null && n.innerText?.trim() === "本节播放结束, 是否继续播放?") { const e = n.closest(".play-chapter") || n.parentElement; if (e) { const n = e.querySelector("button"); if (n && n.offsetParent !== null) { n.click(); console.log('✅ 已点击"下一节"(通过结束提示)'); return true; } } break; } const o = document.querySelectorAll(".el-button--primary"); for (const n of o) if (n.offsetParent !== null && n.innerText.includes("下一节")) { n.click(); console.log('✅ 已点击"下一节"(Primary 按钮)'); return true; } console.warn('⚠️ 未找到"下一节"按钮'); return false; } function l() { const n = document.querySelectorAll(".play-chapter"); for (const e of n) if (e.offsetParent !== null) { const n = e.querySelector("button"); if (n && n.offsetParent !== null && n.innerText.includes("重新播放")) { n.click(); console.log('🔄 已点击"重新播放"按钮(时长未达标)'); return true; } } const e = document.querySelectorAll("button"); for (const n of e) if (n.offsetParent !== null && n.innerText.trim() === "重新播放") { n.click(); console.log('🔄 已点击"重新播放"按钮(备用查找)'); return true; } console.warn('⚠️ 未找到"重新播放"按钮'); return false; } function a(n) { if (!n) return false; s(n); if (n.ended || !n.paused) return false; const e = document.querySelector(".xgplayer-start"); if (e && e.offsetParent !== null) { e.click(); console.log("▶️ 点击中央播放按钮"); return true; } const o = document.querySelector(".xgplayer-play .xgplayer-icon"); if (o && o.offsetParent !== null) { o.click(); console.log("▶️ 点击控制栏播放图标"); return true; } const t = document.querySelector("#xg-video, .xgplayer"); if (t) { t.click(); console.log("▶️ 点击视频画面区域"); return true; } n.play().catch(() => { }); console.log("▶️ 调用原生 video.play()"); return true; } function c() { clearTimeout(o.k); if (o.A) { clearTimeout(o.A); o.A = null; } o.I = false; o.h = false; o.v = true; o.T = false; o.k = setTimeout(() => { o.v = false; if (!document.hidden) { const n = t(); if (n) { s(n); if (!o.I) { o.I = true; a(n); o.A = setTimeout(() => { o.I = false; o.A = null; }, 1500); } } } }, 300); } function f(n) { const e = n.querySelectorAll("tr.vxe-body--row"); let o = 0, t = 0; for (const n of e) { const e = n.querySelectorAll("td.vxe-body--column"); if (e.length < 3) continue; const s = e[0].textContent.trim(); if (s.includes("学习时长")) { let n = s.match(/[((]\s*(\d+\.?\d*)\s*分钟[))]/); if (!n) n = s.match(/(\d+\.?\d*)\s*分钟/); if (n) o = parseFloat(n[1]); const r = e[2].textContent.trim(); const i = r.match(/(\d+\.?\d*)\s*分钟/); if (i) t = parseFloat(i[1]); break; } } return { total: o, learned: t }; } function d(n) { const e = n.querySelector(".el-dialog__headerbtn"); if (e) e.click(); else { const e = n.closest(".el-overlay"); if (e) { const n = e.querySelector(".el-overlay-dialog"); if (n) n.click(); } } } function u(n) { if (o.C) return Promise.resolve(null); o.C = true; return new Promise(e => { const t = document.querySelectorAll("button"); let s = null; for (const n of t) if (n.offsetParent !== null && n.innerText.trim() === "完成情况") { s = n; break; } if (!s) { if (!n) v('📊 未找到"完成情况"按钮', "warn"); o.C = false; e(null); return; } s.click(); console.log("📊 正在获取学习进度..."); let r = 0; const i = 10; const l = setInterval(() => { r++; let t = null; const s = document.querySelectorAll(".el-dialog"); for (const n of s) { const e = n.querySelector(".el-dialog__header .text"); if (e && e.textContent.trim() === "完成情况") { t = n; break; } } if (!t) { const n = document.querySelectorAll('[role="dialog"]'); for (const e of n) if (e.getAttribute("aria-label") === "完成情况") { t = e; break; } } if (t) { clearInterval(l); setTimeout(() => { let n = f(t); if (n && n.total === 0) setTimeout(() => { n = f(t); if (n && n.total === 0) setTimeout(() => { n = f(t); d(t); if (n && n.total > 0) { y(n); console.log(`📊 进度:${n.learned}/${n.total}分钟`); } o.C = false; e(n); }, 1500); else { d(t); if (n && n.total > 0) { y(n); console.log(`📊 进度:${n.learned}/${n.total}分钟`); } o.C = false; e(n); } }, 1500); else { d(t); if (n && n.total > 0) { y(n); console.log(`📊 进度:${n.learned}/${n.total}分钟`); } o.C = false; e(n); } }, 2000); return; } if (r >= i) { clearInterval(l); if (!n) v("📊 等待进度弹窗超时", "warn"); o.C = false; e(null); } }, 200); }); } async function b() { if (o.C) return; const n = await u(true); if (!n || n.total === 0) { o.S = false; return; } if (n.learned >= n.total) { console.log(`✅ 学习时长已达标(${n.learned}/${n.total}),停止自动操作`); o._ = true; k(); return; } const e = t(); if (!e) return; if (e.ended && !r()) { console.log(`⏰ 学习时长不足(${n.learned}/${n.total}),尝试重新播放`); if (l()) { c(); o.h = false; o.S = false; o._ = false; } else o.S = false; } } function p() { if (document.getElementById(e.u)) return; const n = document.createElement("style"); n.id = e.u; n.textContent = `#${e.l}{position:fixed;bottom:20px;right:16px;width:360px;max-height:320px;background:rgba(20,20,30,0.92);color:#e0e0e0;border-radius:10px;padding:12px 14px;z-index:99999;font-family:'Microsoft YaHei','PingFang SC',Arial,sans-serif;font-size:13px;box-shadow:0 6px 28px rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.08);display:none;overflow:hidden;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);transition:all 0.35s cubic-bezier(0.4,0,0.2,1)}#${e.l}.visible{display:block}#${e.l}.collapsed{width:auto;min-width:30px;height:30px;border-radius:15px;padding:0;cursor:pointer;overflow:hidden;background:rgba(20,20,30,0.88);border:1px solid rgba(79,195,247,0.3);box-shadow:0 2px 16px rgba(79,195,247,0.15);display:flex!important;align-items:center;justify-content:center;right:14px;bottom:14px;backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);transition:all 0.3s cubic-bezier(0.4,0,0.2,1)}#${e.l}.collapsed:hover{border-color:rgba(79,195,247,0.6);box-shadow:0 4px 24px rgba(79,195,247,0.25);transform:scale(1.05)}#${e.l}.collapsed .sb-header{border:none;margin:0;padding:0;width:auto;gap:0;white-space:nowrap;justify-content:center}#${e.l}.collapsed .sb-header .sb-badge{display:none}#${e.l}.collapsed .sb-header .sb-title{font-size:0;color:transparent;gap:0}#${e.l}.collapsed .sb-header .sb-title .sb-video-dot{display:inline-block!important;width:8px;height:8px;margin:0;animation:bw-pulse 2s ease-in-out infinite}@keyframes bw-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:0.5;transform:scale(0.85)}}#${e.l}.collapsed .sb-progress-row,#${e.l}.collapsed .sb-log-box,#${e.l}.collapsed .sb-footer,#${e.l}.collapsed .sb-version,#${e.l}.collapsed .sb-badge{display:none!important}#${e.l}.collapsed .sb-collapse-btn{display:none!important}#${e.l} .sb-collapse-btn{cursor:pointer;background:none;border:none;color:rgba(255,255,255,0.35);font-size:16px;padding:0 2px;transition:color 0.2s;line-height:1;margin-left:4px}#${e.l} .sb-collapse-btn:hover{color:rgba(255,255,255,0.7)}#${e.l} .sb-header{display:flex;align-items:center;justify-content:space-between;padding-bottom:6px;border-bottom:1px solid rgba(255,255,255,0.1);margin-bottom:4px}#${e.l} .sb-title{font-weight:600;font-size:14px;color:#4fc3f7;display:flex;align-items:center;gap:6px}#${e.l} .sb-version{font-size:10px;color:rgba(255,255,255,0.3);font-weight:400;margin-left:-2px}#${e.l} .sb-badge{padding:2px 12px;border-radius:14px;font-size:12px;font-weight:600;transition:all 0.3s ease}#${e.l} .sb-badge.learning{background:#e65100;color:#ffcc80}#${e.l} .sb-badge.completed{background:#2e7d32;color:#a5d6a7}#${e.l} .sb-badge.waiting{background:#1565c0;color:#90caf9}#${e.l} .sb-badge.paused{background:#37474f;color:#90a4ae}#${e.l} .sb-progress-row{display:flex;align-items:center;gap:8px;padding:8px 4px 10px 6px;margin-bottom:6px;font-size:12px;background:rgba(255,255,255,0.03);border-radius:6px}#${e.l} .sb-progress-info{color:#b0bec5;white-space:nowrap;min-width:70px;font-size:12px}#${e.l} .sb-progress-bar-wrap{flex:1;height:8px;background:rgba(255,255,255,0.08);border-radius:4px;overflow:hidden}#${e.l} .sb-progress-bar{height:100%;width:0%;background:linear-gradient(90deg,#4fc3f7,#29b6f6);border-radius:4px;transition:width 0.6s cubic-bezier(0.4,0,0.2,1)}#${e.l} .sb-toggle-wrap{display:flex;align-items:center;gap:4px;cursor:pointer;user-select:none;flex-shrink:0}#${e.l} .sb-toggle-track{position:relative;width:32px;height:18px;background:rgba(255,255,255,0.15);border-radius:9px;transition:background 0.25s ease}#${e.l} .sb-toggle-track.on{background:#4fc3f7}#${e.l} .sb-toggle-thumb{position:absolute;top:2px;left:2px;width:14px;height:14px;background:#fff;border-radius:50%;transition:transform 0.25s cubic-bezier(0.4,0,0.2,1);box-shadow:0 1px 3px rgba(0,0,0,0.3)}#${e.l} .sb-toggle-track.on .sb-toggle-thumb{transform:translateX(14px)}#${e.l} .sb-log-box{max-height:170px;border:1px solid rgba(255,255,255,0.04);border-radius:6px;background:rgba(0,0,0,0.15);padding:4px 6px;overflow-y:auto;font-size:12px;line-height:1.7}#${e.l} .sb-log-box::-webkit-scrollbar{width:4px}#${e.l} .sb-log-box::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.2);border-radius:4px}#${e.l} .sb-log-box::-webkit-scrollbar-track{background:transparent}#${e.l} .sb-log-entry{padding:2px 4px;border-bottom:1px solid rgba(255,255,255,0.03);word-break:break-all;font-family:'Consolas','Courier New',monospace;font-size:11.5px}#${e.l} .sb-log-entry.info{color:#b0bec5}#${e.l} .sb-log-entry.success{color:#81c784}#${e.l} .sb-log-entry.warn{color:#ffb74d}#${e.l} .sb-log-entry.error{color:#ef5350}#${e.l} .sb-log-entry.action{color:#64b5f6}#${e.l} .sb-footer{display:flex;justify-content:space-between;align-items:center;padding-top:6px;border-top:1px solid rgba(255,255,255,0.06);margin-top:4px;font-size:11px;color:rgba(255,255,255,0.35)}#${e.l} .sb-clear-btn{cursor:pointer;color:rgba(255,255,255,0.35);transition:color 0.2s;background:none;border:none;font-size:11px;padding:2px 6px}#${e.l} .sb-clear-btn:hover{color:rgba(255,255,255,0.7)}#${e.l} .sb-video-dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:4px;transition:all 0.3s ease}#${e.l} .sb-video-dot.learning{background:#e65100;box-shadow:0 0 8px rgba(230,81,0,0.6)}#${e.l} .sb-video-dot.completed{background:#2e7d32;box-shadow:0 0 8px rgba(46,125,50,0.6)}#${e.l} .sb-video-dot.waiting{background:#1565c0;box-shadow:0 0 8px rgba(21,101,192,0.6)}#${e.l} .sb-video-dot.paused{background:#90a4ae;box-shadow:0 0 8px rgba(144,164,174,0.6)}#${e.l} .sb-video-dot.off{background:#ef5350}`; document.head.appendChild(n); } function g() { if (!o.E && o.P) { o.E = true; o.M.classList.add("collapsed"); } } function x() { if (o.E) { o.E = false; o.M.classList.remove("collapsed"); } } function m() { if (document.getElementById(e.l)) return; const n = document.createElement("div"); n.id = e.l; n.innerHTML = `
${GM_info.script.name}v${GM_info.script.version}⏳ 等待中
📊 -- / --
📋 状态栏已加载,等待视频...
`; document.body.appendChild(n); o.M = n; o.L = document.getElementById("bw-log-box"); document.getElementById("bw-collapse-btn").addEventListener("click", function (n) { n.stopPropagation(); g(); }); n.addEventListener("click", function () { if (o.E) x(); }); document.getElementById("bw-clear-log").addEventListener("click", function () { if (o.L) { o.L.innerHTML = ""; $(); } }); document.getElementById("bw-progress-toggle").addEventListener("click", function (n) { n.stopPropagation(); o.D = !o.D; const e = document.getElementById("bw-toggle-track"); if (e) e.classList.toggle("on", o.D); if (o.D) { v("📊 进度追踪已开启(每3分钟获取一次)", "success"); if (!o._) u(); T(); } else { v("📊 进度追踪已关闭", "info"); _(); y(null); } }); } function w(n, e) { const o = document.getElementById("bw-status-badge"); if (!o) return; o.textContent = n; o.className = "sb-badge " + e; } function h(n) { const e = document.getElementById("bw-video-dot"); if (!e) return; e.className = "sb-video-dot " + (n || "off"); } function $() { const n = document.getElementById("bw-log-count"); if (!n || !o.L) return; n.textContent = o.L.children.length + " 条日志"; } function v(n, t) { if (!o.L) return; const s = document.createElement("div"); s.className = "sb-log-entry " + (t || "info"); s.textContent = n; o.L.appendChild(s); while (o.L.children.length > e.p) o.L.removeChild(o.L.firstChild); $(); o.L.scrollTop = o.L.scrollHeight; } function y(n) { const e = document.getElementById("bw-progress-info"), o = document.getElementById("bw-progress-bar"); if (!e || !o) return; if (!n || n.total === 0) { e.textContent = "📊 -- / --"; o.style.width = "0%"; return; } const t = Math.min(100, Math.round(n.learned / n.total * 100)); e.textContent = `📊 ${n.learned}/${n.total}分钟`; o.style.width = t + "%"; if (n.learned >= n.total) { o.style.background = "linear-gradient(90deg, #66bb6a, #43a047)"; w("✅ 学习完成", "completed"); } else o.style.background = "linear-gradient(90deg, #4fc3f7, #29b6f6)"; } function k() { v("✅ 课程学习进度已达标,停止进度获取", "success"); w("✅ 学习完成", "completed"); _(); } function T() { _(); o.F = setInterval(() => { if (o._) return; if (o.D && o.P) u(); }, e.m); } function _() { if (o.F) { clearInterval(o.F); o.F = null; } } function S() { const e = console.log.bind(console), o = console.warn.bind(console), t = console.error.bind(console); console.log = function (...o) { e(`[${n()}]`, ...o); const t = o.map(n => typeof n === "object" ? JSON.stringify(n) : String(n)).join(" "); let s = "info"; if (/✅|完成|已达标/.test(t)) s = "success"; else if (/▶️|播放|点击/.test(t)) s = "action"; else if (/⚠️|未找到/.test(t)) s = "warn"; else if (/❌|出错/.test(t)) s = "error"; v(t, s); }; console.warn = function (...e) { o(`[${n()}]`, ...e); v(e.map(n => String(n)).join(" "), "warn"); }; console.error = function (...e) { t(`[${n()}]`, ...e); v(e.map(n => String(n)).join(" "), "error"); }; } function z() { const n = document.querySelector("#xg-video video"), e = !!n; if (e !== o.P) { o.P = e; h(e ? "waiting" : "off"); if (e) { o.M.classList.add("visible"); v("🎬 检测到视频播放器", "success"); if (o.D && !o._) setTimeout(() => u(), 2e3); } else { o.M.classList.remove("visible"); o.M.classList.remove("collapsed"); o.E = false; y(null); v("🚫 视频播放器已移除", "warn"); } return; } if (e && n) { if (n.ended) { w("✅ 学习完成", "completed"); h("completed"); } else if (!n.paused) { w("📖 学习中", "learning"); h("learning"); } else { w("⏸️ 已暂停", "paused"); h("paused"); } } } function I() { const n = document.querySelectorAll(".el-overlay"); for (const e of n) { if (getComputedStyle(e).display === "none") continue; const n = e.querySelector(".el-dialog"); if (!n) continue; const o = n.querySelector(".el-dialog__header"); if (o && o.textContent.includes("温馨提示")) { const e = n.querySelector(".el-button--primary"); if (e && e.innerText.trim() === "继续") { e.click(); console.log('✅ 检测到专注提醒弹窗,点击"继续"'); return; } } } } function A() { I(); if (o._) return; const n = t(); if (n) { const e = n.currentSrc || n.src; if (e && e !== o.$) { o.$ = e; c(); } } if (n && !n.paused) { s(n); if (o.I) { clearTimeout(o.A); o.A = null; o.I = false; } if (o.v) { clearTimeout(o.k); o.v = false; } if (!o.T) { const n = document.querySelector(".xgplayer-start"); if (n && n.offsetParent !== null) { n.classList.add("hide"); console.log("🎯 隐藏中央按钮"); } const e = document.querySelector(".video-overlay"); if (e && e.offsetParent !== null) { e.style.display = "none"; console.log("🎯 隐藏视频覆盖层"); } o.T = true; } return; } if (n && n.ended) { if (r() && !o.h) { if (i()) o.h = true; } else if (!r()) if (!o.S) { o.S = true; console.log("🏁 所有课程已播放完毕,检查学习时长..."); b(); } return; } if (n && n.paused && !o.v) { if (o.I) { console.log("⏳ 播放操作进行中,跳过本次"); return; } const e = document.querySelector(".video-overlay"); if (e) { e.style.display = ""; console.log("🔄 恢复视频覆盖层显示"); } o.T = false; o.I = true; if (o.A) { clearTimeout(o.A); o.A = null; } if (a(n)) o.A = setTimeout(() => { o.I = false; o.A = null; }, 1500); else o.I = false; return; } if (!o.h) { const n = document.querySelectorAll("*"); for (const e of n) if (e.offsetParent !== null && e.innerText?.trim() === "本节播放结束, 是否继续播放?") { if (r()) { if (i()) o.h = true; } else { console.log("🏁 检测到结束提示,但无下一节,触发检查"); if (!o.S) { o.S = true; b(); } } break; } } } let C = null; function E() { if (C) { clearTimeout(C); C = null; } function n() { A(); C = setTimeout(n, e.o); } C = setTimeout(n, e.o); } document.addEventListener("visibilitychange", () => { if (!document.hidden) { if (o.v) { clearTimeout(o.k); o.v = false; const n = t(); if (n) { s(n); if (!o.I) { o.I = true; if (o.A) { clearTimeout(o.A); o.A = null; } a(n); o.A = setTimeout(() => { o.I = false; o.A = null; }, 1500); } } } setTimeout(A, 500); } }); function M() { p(); m(); S(); setTimeout(() => { z(); }, 1500); setInterval(z, 1e3); E(); T(); } if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", M); else M(); })();