// ==UserScript== // @name 学起Plus-增强版,重庆理工继续教育,含后台播放+防挂机 // @namespace http://scriptcat.net/ // @version 0.8 // @description 修复自动播放拦截,强制静音属性注入+2倍速+防挂机 // @match *://*.chinaedu.net/* // @match *://*.sccchina.net/* // @run-at document-start // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; /* ========== 1. 可见性检测禁用 ========== */ function disableVisibilityCheck() { const origAddEventListener = EventTarget.prototype.addEventListener; EventTarget.prototype.addEventListener = function (type, listener, options) { if (type === 'visibilitychange') return; return origAddEventListener.call(this, type, listener, options); }; ['hidden', 'visibilityState'].forEach(prop => { Object.defineProperty(document, prop, { configurable: true, get: () => prop === 'hidden' ? false : 'visible' }); }); console.log('[学起Plus] 页面可见性检测已禁用'); } disableVisibilityCheck(); /* ========== 2. 强制视频静音属性注入 (核心修复) ========== */ // 在视频元素创建的瞬间就加上 muted 属性,欺骗浏览器安全策略 const originalCreate = Document.prototype.createElement; Document.prototype.createElement = function (tagName) { const element = originalCreate.call(this, tagName); if (tagName.toLowerCase() === 'video') { element.setAttribute('muted', ''); // 强制添加 HTML 属性 element.muted = true; // 强制同步 JS 属性 } return element; }; /* ========== 3. 等待 jQuery 工具函数 ========== */ function waitForJQuery(callback, timeout = 10000) { if (window.jQuery) { callback(window.jQuery); return; } const start = Date.now(); const interval = setInterval(() => { if (window.jQuery) { clearInterval(interval); callback(window.jQuery); } else if (Date.now() - start > timeout) { clearInterval(interval); console.error('[学起Plus] jQuery 加载超时'); } }, 50); } /* ========== 4. 模拟鼠标移动 (防挂机) ========== */ function startIdleMouseSimulator(options) { const defaults = { idleThreshold: 60000, minInterval: 30000, maxInterval: 120000, moveRange: 5 }; const config = Object.assign({}, defaults, options); let lastActivity = Date.now(); let simulateTimer = null; let lastMouseX = window.innerWidth / 2; let lastMouseY = window.innerHeight / 2; function recordActivity() { lastActivity = Date.now(); if (simulateTimer) clearTimeout(simulateTimer); } ['mousemove', 'keydown', 'scroll', 'click', 'touchstart'].forEach(evt => { document.addEventListener(evt, recordActivity, { passive: true }); }); document.addEventListener('mousemove', e => { lastMouseX = e.clientX; lastMouseY = e.clientY; }); function simulateMouseMove() { const dx = (Math.random() - 0.5) * config.moveRange * 2; const dy = (Math.random() - 0.5) * config.moveRange * 2; const newX = Math.min(Math.max(lastMouseX + dx, 0), window.innerWidth); const newY = Math.min(Math.max(lastMouseY + dy, 0), window.innerHeight); const event = new MouseEvent('mousemove', { clientX: newX, clientY: newY, bubbles: true }); document.dispatchEvent(event); lastMouseX = newX; lastMouseY = newY; } function scheduleNext() { if (Date.now() - lastActivity >= config.idleThreshold) simulateMouseMove(); const delay = config.minInterval + Math.random() * (config.maxInterval - config.minInterval); simulateTimer = setTimeout(scheduleNext, delay); } scheduleNext(); console.log('[学起Plus] 空闲鼠标模拟器已启动'); } /* ========== 5. 主业务逻辑 ========== */ waitForJQuery(function($) { startIdleMouseSimulator({ idleThreshold: 60000 }); // 消息处理逻辑(保持不变) function handleMessage(data) { if (typeof data === 'object' && data.type === 'studyNext') { console.log('[学起Plus] 收到 studyNext 消息'); dispatchSectionNext(); return; } try { if (typeof data === 'string') { const parsed = JSON.parse(data); if (parsed.type === 'studyNext') dispatchSectionNext(); } } catch (e) { if (err instanceof SyntaxError) { // 仅忽略JSON解析错误 console.debug('[学起Plus] 无效JSON消息已忽略', data); } else { // 保留其他错误(如内存溢出等关键错误) console.error('[学起Plus] 消息处理意外错误', err); } } } window.addEventListener('message', (e) => { handleMessage(e.data); }); // --- 视频页逻辑 --- if (location.href.includes('video.html')) { console.log('[学起Plus] 视频页脚本启动'); const observer = new MutationObserver((mutations, obs) => { const v = document.querySelector('video'); if (v) { setupVideo(v); obs.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); const existingVideo = document.querySelector('video'); if (existingVideo) { setupVideo(existingVideo); observer.disconnect(); } function setupVideo(v) { // 确保双重保险 v.setAttribute('muted', ''); v.muted = true; v.autoplay = true; v.playbackRate = 2; // --- 核心修改:增强播放逻辑 --- function tryPlay() { v.play().then(() => { console.log('[学起Plus] 视频播放成功'); }).catch((err) => { console.log('[学起Plus] 播放被阻止:', err.message); // 即使失败,也强制设置静音并重试 v.muted = true; setTimeout(tryPlay, 1000); }); } // 立即尝试,不要等 canplay,因为 canplay 可能不触发 tryPlay(); // 兜底:如果2秒后还没播放,强制重试 setTimeout(() => { if (v.paused) tryPlay(); }, 2000); v.addEventListener('ended', () => { console.log('[学起Plus] 视频结束'); window.parent.postMessage({ type: 'studyNext' }, '*'); }); } return; } // --- 大纲页逻辑 (保持不变) --- console.log('[学起Plus] 大纲页脚本启动'); const cleanMask = () => document.querySelector('#pop, #cover')?.remove(); setInterval(cleanMask, 2000); const strategies = [ { match: () => document.querySelector('#ztree_online li'), handler: zTreeNext }, { match: () => document.querySelector('ul.sub-menu'), handler: nestedMenuNext } ]; function dispatchSectionNext() { for (const strategy of strategies) { if (strategy.match()) { console.log('[学起Plus] 匹配到策略:', strategy.handler.name); strategy.handler(); return; } } console.error('[学起Plus] 未找到匹配的课程目录结构'); } function zTreeNext() { const $items = $('#ztree_online li'); if (!$items.length) return; let $active = $items.find('a.curSelectedNode').closest('li'); if (!$active.length && window.currOutlineId) $active = $items.filter('#' + window.currOutlineId); if (!$active.length) $active = $items.first(); const $next = $items.eq($items.index($active) + 1); if ($next.length) { $items.find('a').removeClass('curSelectedNode'); $next.find('a').addClass('curSelectedNode'); $next.find('a')[0].click(); $next[0].scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } } function nestedMenuNext() { const $li = $('li').filter((_, li) => !$(li).find('ul.sub-menu').length); let $active = $li.filter('[class*="activeState"]'); if (!$active.length && window.currOutlineId) $active = $li.filter(`[nid="${window.currOutlineId}"]`); const $next = $li.eq($li.index($active) + 1); if ($next.length) { $('ul.sub-menu').hide(); $next.find('a')[0].click(); } } }); })();