// ==UserScript== // @name 市场监管教育在线 自动学习脚本【公益版】 // @namespace 一心向善 // @version 2.1 // @description 自动播放完成mreln.com课程视频,倍速观看,自动续播 // @match https://www.mreln.com/* // @match https://mreln.com/* // @grant none // @run-at document-idle // ==/UserScript== (function () { 'use strict'; // ============ 配置 ============ const CONFIG = { speed: 2, // 播放倍速(服务器最大接受2x) checkInterval: 3000, // 播放状态检查间隔(毫秒) keepAliveInterval: 5000 // 后台保活间隔(毫秒) }; const STORAGE_KEY = 'mreln_auto_learning'; // ============ 全局状态 ============ let token = ''; let courseId = ''; let unitId = ''; let contentId = ''; let videoDuration = 0; let keepAliveTimer = null; let checkTimer = null; function log(msg, type) { type = type || 'info'; const colors = { info: '#2196F3', success: '#4CAF50', warn: '#FF9800', error: '#F44336' }; console.log('%c[自动学习] ' + msg, 'color:' + colors[type] + ';font-weight:bold'); } function sleep(ms) { return new Promise(function (r) { setTimeout(r, ms); }); } // ============ 持久化状态 ============ function getState() { try { return JSON.parse(localStorage.getItem(STORAGE_KEY)) || { active: false }; } catch (e) { return { active: false }; } } function saveState(state) { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } function clearState() { localStorage.removeItem(STORAGE_KEY); // 清除API尝试记录 var keys = Object.keys(localStorage).filter(function(k) { return k.indexOf('mreln_api_attempt_') === 0; }); keys.forEach(function(k) { localStorage.removeItem(k); }); } // ============ 页面信息提取 ============ function extractPageInfo() { if (window.$CONFIG && window.$CONFIG.token) { token = window.$CONFIG.token; } var courseInput = document.querySelector('input[name="course_id"]'); if (courseInput) courseId = courseInput.value; var unitInput = document.querySelector('input[name="unit_id"]'); if (unitInput) unitId = unitInput.value; var videoBlock = document.querySelector('.block-content.block-video'); if (videoBlock) contentId = videoBlock.dataset.id; var v = document.querySelector('video'); if (v && v.duration && !isNaN(v.duration)) videoDuration = Math.round(v.duration); } // ============ 进度检查 ============ function getCurrentProgress() { var progs = document.querySelectorAll('[data-progress]'); var max = 0; for (var i = 0; i < progs.length; i++) { var p = parseFloat(progs[i].dataset.progress); if (p > max) max = p; } return max; } function isLessonComplete() { return getCurrentProgress() >= 99; } // ============ 获取Video.js播放器 ============ function getPlayer() { var playerEl = document.querySelector('.video-js'); if (!playerEl || !window.videojs) return null; try { return window.videojs.getPlayer(playerEl.id); } catch (e) { return null; } } // ============ 核心:播放视频 ============ function startPlayback() { var v = document.querySelector('video'); var player = getPlayer(); if (!v || !player) { log('未找到视频播放器,等待重试...', 'warn'); return false; } log('启动' + CONFIG.speed + '倍速播放...'); // 从上次进度位置继续播放(跳过已看部分) var progress = getCurrentProgress(); if (progress > 5 && progress < 99) { var skipTo = Math.round(videoDuration * progress / 100); log('从进度' + progress + '%处继续播放 (跳转到' + skipTo + 's)'); try { player.currentTime(skipTo); } catch (e) { } } v.muted = true; try { player.playbackRate(CONFIG.speed); } catch (e) { } player.play().catch(function (e) { log('播放失败: ' + e.message + ',3秒后重试...', 'warn'); setTimeout(startPlayback, 3000); }); return true; } // ============ 后台保活 + 自动恢复播放 ============ function startKeepAlive() { if (keepAliveTimer) clearInterval(keepAliveTimer); keepAliveTimer = setInterval(function () { var state = getState(); if (!state.active) { clearInterval(keepAliveTimer); return; } var v = document.querySelector('video'); var player = getPlayer(); if (!v || !player) return; // 如果视频暂停了但没结束,自动恢复播放 if (v.paused && !v.ended && !isLessonComplete()) { log('检测到视频暂停,自动恢复播放...'); v.muted = true; try { player.playbackRate(CONFIG.speed); } catch (e) { } player.play().catch(function () { // 如果play()被浏览器策略阻止,尝试先mute再play v.muted = true; setTimeout(function () { player.play().catch(function () { }); }, 500); }); } // 确保倍速保持 if (player.playbackRate() !== CONFIG.speed) { try { player.playbackRate(CONFIG.speed); } catch (e) { } log('恢复倍速为' + CONFIG.speed + 'x'); } }, CONFIG.keepAliveInterval); } // ============ 监听播放完成 ============ function startCompletionChecker() { if (checkTimer) clearInterval(checkTimer); checkTimer = setInterval(function () { var state = getState(); if (!state.active) { clearInterval(checkTimer); return; } var v = document.querySelector('video'); if (!v) return; // 视频播放结束 或 进度达到100% if (v.ended || isLessonComplete()) { clearInterval(checkTimer); if (keepAliveTimer) clearInterval(keepAliveTimer); log('当前课程播放完成!', 'success'); navigateToNextLesson(); } else { // 定期输出进度 var progress = getCurrentProgress(); var timeStr = Math.round(v.currentTime) + '/' + videoDuration + 's'; log('播放中: ' + timeStr + ' | 进度: ' + progress + '%'); } }, CONFIG.checkInterval); } // ============ 课程导航 ============ function getAllLessons() { var menuItems = document.querySelectorAll('.menu-list li, [class*=menu] li'); var lessons = []; for (var i = 0; i < menuItems.length; i++) { var link = menuItems[i].querySelector('a'); if (link && link.href.indexOf('/lesson/') > -1) { var progressEl = menuItems[i].querySelector('[data-progress]'); var p = progressEl ? parseFloat(progressEl.dataset.progress) : 0; lessons.push({ url: link.href, progress: p, title: menuItems[i].textContent.trim().substring(0, 50), active: menuItems[i].classList.contains('active') || menuItems[i].classList.contains('is-active') }); } } return lessons; } function findNextLesson() { var lessons = getAllLessons(); var currentUrl = window.location.href; // 找当前课程位置 var currentIdx = -1; for (var i = 0; i < lessons.length; i++) { if (lessons[i].url === currentUrl || lessons[i].active) { currentIdx = i; break; } } // 找下一个未完成的课程 if (currentIdx >= 0) { for (var j = currentIdx + 1; j < lessons.length; j++) { if (lessons[j].progress < 99) return lessons[j]; } } // 从头找未完成的 for (var k = 0; k < lessons.length; k++) { if (lessons[k].progress < 99) return lessons[k]; } return null; } function navigateToNextLesson() { var next = findNextLesson(); if (next) { log('导航到下一节: ' + next.title, 'success'); window.location.href = next.url; } else { log('=== 所有课程已完成! ===', 'success'); clearState(); } } // ============ 主流程 ============ function processCurrentLesson() { log('========== 处理当前课程 =========='); extractPageInfo(); if (isLessonComplete()) { log('当前课程已完成,跳过', 'success'); navigateToNextLesson(); return; } log('视频时长: ' + videoDuration + 's | 当前进度: ' + getCurrentProgress() + '%'); // 等待播放器就绪后启动播放 setTimeout(function () { if (startPlayback()) { startKeepAlive(); startCompletionChecker(); } }, 2000); } function continueLearning() { var state = getState(); if (!state.active) return; log('检测到自动学习任务,继续...'); sleep(3000).then(function () { processCurrentLesson(); }); } function start() { var state = getState(); if (state.active) { log('已有任务在运行中,继续执行...'); continueLearning(); return; } log('=== 市场监管教育在线 自动学习脚本启动 ===', 'success'); log('配置: 倍速=' + CONFIG.speed + 'x | 后台保活=' + (CONFIG.keepAliveInterval / 1000) + '秒间隔'); saveState({ active: true, startTime: Date.now() }); continueLearning(); } function stop() { clearState(); if (keepAliveTimer) clearInterval(keepAliveTimer); if (checkTimer) clearInterval(checkTimer); log('脚本已停止', 'warn'); } // ============ 自动续跑 ============ var state = getState(); if (state.active) { log('检测到自动学习任务,自动续跑...'); setTimeout(continueLearning, 2000); } // ============ 页面可见性监听(回到前台时恢复播放) ============ document.addEventListener('visibilitychange', function () { if (!document.hidden) { var state = getState(); if (state.active) { log('页面回到前台,检查播放状态...'); var v = document.querySelector('video'); var player = getPlayer(); if (v && player && v.paused && !v.ended && !isLessonComplete()) { v.muted = true; try { player.playbackRate(CONFIG.speed); } catch (e) { } player.play().catch(function () { }); log('已恢复播放'); } } } }); // ============ 浮动控制面板 ============ var panel = null; var panelTimer = null; function createPanel() { if (panel) return; panel = document.createElement('div'); panel.id = 'mreln-auto-panel'; panel.innerHTML = '
' + '
' + '📚 天府市场监管学习助手' + '' + '
' + '
' + '
' + '状态' + '未启动' + '
' + '
' + '进度' + '--' + '
' + '
' + '倍速' + '2.5x' + '
' + '
' + '' + '' + '
' + '
' + '' + '
' + '
' + '
'; document.body.appendChild(panel); // 按钮事件 document.getElementById('mreln-start-btn').addEventListener('click', function () { start(); }); document.getElementById('mreln-stop-btn').addEventListener('click', function () { stop(); }); document.getElementById('mreln-hide-btn').addEventListener('click', function () { var content = panel.querySelector('div:nth-child(2)'); var btn = document.getElementById('mreln-hide-btn'); if (content.style.display === 'none') { content.style.display = ''; btn.textContent = '收起 ▾'; } else { content.style.display = 'none'; btn.textContent = '展开 ▴'; } }); // 拖动功能 var dragEl = document.getElementById('mreln-drag'); var isDragging = false, offsetX = 0, offsetY = 0; dragEl.addEventListener('mousedown', function (e) { isDragging = true; offsetX = e.clientX - panel.offsetLeft; offsetY = e.clientY - panel.offsetTop; panel.style.transition = 'none'; }); document.addEventListener('mousemove', function (e) { if (!isDragging) return; panel.style.left = (e.clientX - offsetX) + 'px'; panel.style.top = (e.clientY - offsetY) + 'px'; panel.style.right = 'auto'; }); document.addEventListener('mouseup', function () { isDragging = false; panel.style.transition = ''; }); // 状态更新定时器 updatePanel(); panelTimer = setInterval(updatePanel, 2000); } function updatePanel() { if (!panel) return; var state = getState(); var v = document.querySelector('video'); var dot = document.getElementById('mreln-status-dot'); var statusText = document.getElementById('mreln-status-text'); var progressText = document.getElementById('mreln-progress-text'); var speedText = document.getElementById('mreln-speed-text'); if (state.active) { dot.style.background = '#4CAF50'; dot.style.boxShadow = '0 0 6px #4CAF50'; statusText.textContent = v ? (v.paused ? '暂停中(恢复中)' : '播放中') : '运行中'; statusText.style.color = '#81C784'; } else { dot.style.background = '#f44336'; dot.style.boxShadow = 'none'; statusText.textContent = '未启动'; statusText.style.color = '#fff'; } // 显示视频实际播放进度(实时变化)+ 服务端记录进度 if (v && v.duration) { var playPct = (v.currentTime / v.duration * 100).toFixed(1); var cur = Math.round(v.currentTime); var dur = Math.round(v.duration); var mm = Math.floor(cur / 60), ss = cur % 60; var dm = Math.floor(dur / 60), ds = dur % 60; var timeStr = mm + ':' + (ss < 10 ? '0' + ss : ss) + '/' + dm + ':' + (ds < 10 ? '0' + ds : ds); var serverPct = getCurrentProgress(); progressText.textContent = playPct + '% | ' + timeStr + ' (记录' + serverPct + '%)'; } else { progressText.textContent = '--'; } speedText.textContent = v ? v.playbackRate + 'x' : CONFIG.speed + 'x'; } // 创建面板(确保DOM就绪) if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', createPanel); } else { createPanel(); } // ============ 暴露接口 ============ window.mrelnAuto = { start: start, stop: stop, status: function () { var s = getState(); var v = document.querySelector('video'); return { active: s.active, progress: getCurrentProgress(), videoTime: v ? Math.round(v.currentTime) + '/' + videoDuration + 's' : 'no video', playing: v ? !v.paused : false, speed: v ? v.playbackRate : 0 }; } }; log('脚本已加载!点击右上角控制面板的"开始"按钮启动'); })();