// ==UserScript== // @name 甘肃省水利水电施工企业三类人员安全生产继续教育网络培训助手 // @namespace local.video.helper // @version 2.0 // @description 防止页面因 visibilitychange / blur 导致视频误暂停,并在防挂机弹窗出现时进行明显提醒 // @match *://*/* // @run-at document-start // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; console.log('[Video Helper] 启动'); // ============================================================ // 1. 保存浏览器原始 API // ============================================================ const rawDocumentAddEventListener = Document.prototype.addEventListener; const rawWindowAddEventListener = Window.prototype.addEventListener; // ============================================================ // 2. 页面始终报告 visible // ============================================================ try { Object.defineProperty(document, 'hidden', { configurable: true, get() { return false; } }); Object.defineProperty(document, 'webkitHidden', { configurable: true, get() { return false; } }); Object.defineProperty(document, 'visibilityState', { configurable: true, get() { return 'visible'; } }); Object.defineProperty(document, 'webkitVisibilityState', { configurable: true, get() { return 'visible'; } }); } catch (e) { console.warn( '[Video Helper] visibility 属性覆盖失败', e ); } // ============================================================ // 3. 阻止网站后续监听 visibilitychange // ============================================================ const blockDocumentEvents = new Set([ 'visibilitychange', 'webkitvisibilitychange' ]); Document.prototype.addEventListener = function ( type, listener, options ) { if (blockDocumentEvents.has(type)) { console.log( '[Video Helper] 拦截事件:', type ); return; } return rawDocumentAddEventListener.call( this, type, listener, options ); }; // ============================================================ // 4. 阻止 blur / focus 检测 // ============================================================ const blockWindowEvents = new Set([ 'blur', 'focus' ]); Window.prototype.addEventListener = function ( type, listener, options ) { if (blockWindowEvents.has(type)) { console.log( '[Video Helper] 拦截窗口事件:', type ); return; } return rawWindowAddEventListener.call( this, type, listener, options ); }; // ============================================================ // 5. 捕获 visibilitychange,阻止继续传播 // ============================================================ rawDocumentAddEventListener.call( document, 'visibilitychange', function (event) { event.stopImmediatePropagation(); event.stopPropagation(); }, true ); // ============================================================ // 6. 阻止 onvisibilitychange // ============================================================ try { Object.defineProperty( document, 'onvisibilitychange', { configurable: true, get() { return null; }, set() {} } ); } catch (e) {} // ============================================================ // 7. 防挂机弹窗检测 // ============================================================ let antiIdleVisible = false; let originalTitle = ''; let titleTimer = null; let audioContext = null; function findAntiIdleDialog() { const dialogs = document.querySelectorAll('.el-dialog'); for (const dialog of dialogs) { const text = dialog.innerText || ''; if ( text.includes('防挂机提醒') && text.includes('是否继续学习') ) { return dialog; } } return null; } // ============================================================ // 8. 找“继续”按钮 // ============================================================ function findContinueButton(dialog) { if (!dialog) { return null; } const buttons = dialog.querySelectorAll('.dialog-btn'); for (const button of buttons) { if ( button.textContent .trim() === '继续' ) { return button; } } return null; } // ============================================================ // 9. 浏览器通知 // ============================================================ async function sendNotification() { if (!('Notification' in window)) { return; } try { if ( Notification.permission === 'default' ) { await Notification .requestPermission(); } if ( Notification.permission === 'granted' ) { new Notification( '防挂机提醒', { body: '学习页面正在等待你的确认,请返回页面点击“继续”。' } ); } } catch (e) { console.warn( '[Video Helper] 通知失败', e ); } } // ============================================================ // 10. 声音提醒 // ============================================================ function beep() { try { audioContext = audioContext || new ( window.AudioContext || window.webkitAudioContext )(); const oscillator = audioContext.createOscillator(); const gain = audioContext.createGain(); oscillator.connect(gain); gain.connect( audioContext.destination ); oscillator.frequency.value = 800; gain.gain.value = 0.08; oscillator.start(); oscillator.stop( audioContext.currentTime + 0.3 ); } catch (e) { console.warn( '[Video Helper] 声音提醒失败', e ); } } // ============================================================ // 11. 标题闪烁 // ============================================================ function startTitleFlash() { if (titleTimer) { return; } originalTitle = document.title; let flag = false; titleTimer = setInterval(() => { document.title = flag ? '⚠ 防挂机提醒!' : originalTitle; flag = !flag; }, 700); } function stopTitleFlash() { if (!titleTimer) { return; } clearInterval(titleTimer); titleTimer = null; document.title = originalTitle; } // ============================================================ // 12. 弹窗出现处理 // ============================================================ function onAntiIdleAppeared( dialog ) { console.warn( '[Video Helper] 检测到防挂机提醒' ); sendNotification(); beep(); startTitleFlash(); const continueButton = findContinueButton(dialog); if (continueButton) { // 让按钮更容易看到 continueButton.style.outline = '4px solid #ff9800'; continueButton.style .outlineOffset = '3px'; console.log( '[Video Helper] 已找到“继续”按钮:', continueButton ); } } // ============================================================ // 13. MutationObserver // ============================================================ function startDialogObserver() { const observer = new MutationObserver(() => { const dialog = findAntiIdleDialog(); if ( dialog && !antiIdleVisible ) { antiIdleVisible = true; onAntiIdleAppeared( dialog ); } if ( !dialog && antiIdleVisible ) { antiIdleVisible = false; stopTitleFlash(); console.log( '[Video Helper] 防挂机弹窗关闭' ); } }); observer.observe( document.documentElement, { childList: true, subtree: true } ); } // ============================================================ // 14. 页面加载后启动 // ============================================================ rawDocumentAddEventListener.call( document, 'DOMContentLoaded', function () { startDialogObserver(); console.log( '[Video Helper] 防挂机提醒监听已启动' ); } ); })();