// ==UserScript== // @name 【真·免费】2025年中小学智慧·暑期教师研修·自动播放/切换·保证满学时·0手动·教师福音·躺平神器 // @namespace https://bbs.tampermonkey.net.cn/ // @version 0.1.3 // @description 为了帮助老师们提升2025暑期研修学习效率,我们免费提供该脚本,可以帮助老师们自动播放视频并且自动切换课程。 // @author 教师小助手 // @resource SRIsecured http://assets.yixiyun.tech/qrcode/shoukuanma.png // @grant unsafeWindow // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @match *://www.smartedu.cn/* // @match *://basic.smartedu.cn/* // ==/UserScript== /** * 日志函数 * @param content */ function log(content) { console.debug(`=== ${content} ===`) setTimeout(() => { if (document.getElementById('log_content')) { document.getElementById('log_content').innerText = `${content}`; } },10) } /** * 显示状态栏 */ function inject() { if (window.top != window.self) return; console.debug("=== 展示状态栏 ===") // 创建状态栏容器 const statusBar = document.createElement('div'); statusBar.id = 'auto-play-status-bar'; statusBar.innerHTML = `
暑期教师研修助手: 未运行
`; // 创建二维码弹窗 const qrModal = document.createElement('div'); qrModal.id = 'qr-modal'; qrModal.innerHTML = ` `; // 创建赞赏弹窗 const donateModal = document.createElement('div'); donateModal.id = 'donate-modal'; donateModal.innerHTML = ` `; // 添加样式 const style = document.createElement('style'); style.textContent = ` #auto-play-status-bar { position: fixed; bottom: 0; left: 0; right: 0; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; z-index: 999999; box-shadow: 0 -2px 10px rgba(0,0,0,0.2); backdrop-filter: blur(10px); } .log_content{ margin: 30px; text-align: center; overflow:hidden; color: white; white-space: nowrap; text-overflow: ellipsis; } .status-content { display: flex; justify-content: space-between; align-items: center; padding: 12px 20px; max-width: 1200px; margin: 0 auto; } .status-info { display: flex; align-items: center; gap: 10px; flex-shrink:0; } .status-text { font-size: 14px; font-weight: 500; } .status-indicator { padding: 4px 12px; background: rgba(255,255,255,0.2); border-radius: 20px; font-size: 12px; font-weight: 600; animation: pulse 2s infinite; } @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.7; } 100% { opacity: 1; } } .qr-btn { background: rgba(255,255,255,0.15); border: 1px solid rgba(255,255,255,0.3); color: white; padding: 8px 16px; border-radius: 25px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.3s ease; backdrop-filter: blur(5px); flex-shrink:0; } .qr-btn:hover { background: rgba(255,255,255,0.25); transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0,0,0,0.2); } #qr-modal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9999999; } #donate-modal { display:none; position: fixed; inset: 0; z-index: 99; } .modal-overlay { width: 100%; height: 100%; background: rgba(0,0,0,0.8); display: flex; justify-content: center; align-items: center; backdrop-filter: blur(5px); } .modal-content { background: white; border-radius: 16px; padding: 24px; max-width: 500px; width: 90%; text-align: center; box-shadow: 0 20px 40px rgba(0,0,0,0.3); animation: modalShow 0.3s ease-out; } @keyframes modalShow { from { opacity: 0; transform: scale(0.8) translateY(20px); } to { opacity: 1; transform: scale(1) translateY(0); } } .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .modal-header h3 { margin: 0; color: #333; font-size: 18px; font-weight: 600; } .close-btn { background: none; border: none; font-size: 24px; cursor: pointer; color: #999; padding: 0; width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; } .close-btn:hover { background: #f0f0f0; color: #666; } .qr-container img { width: 400px; height: auto; border-radius: 8px; margin-bottom: 16px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); } .qr-container p { margin: 8px 0; color: #666; font-size: 14px; line-height: 1.5; } #qr-loading { text-align: center; padding: 40px 20px; } .loading-spinner { width: 40px; height: 40px; border: 4px solid #f3f3f3; border-top: 4px solid #667eea; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 20px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } #qr-tips { margin-top: 20px; padding: 0 10px; } #qr-tips p { margin: 10px 0; color: #666; font-size: 13px; } .status-indicator.not-running { background: rgba(244, 67, 54, 0.8) !important; animation: none; } .start-btn { background: rgba(76, 175, 80, 0.8); border: 1px solid rgba(76, 175, 80, 0.3); color: white; padding: 6px 14px; border-radius: 20px; cursor: pointer; font-size: 12px; font-weight: 500; transition: all 0.3s ease; margin-left: 10px; } .start-btn:hover { background: rgba(76, 175, 80, 1); transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0,0,0,0.2); } `; // 添加元素到页面 document.head.appendChild(style); document.body.appendChild(statusBar); document.body.appendChild(qrModal); document.body.appendChild(donateModal); // 绑定事件 document.getElementById('show-qr-btn').addEventListener('click', function() { document.getElementById('donate-modal').style.display = 'block'; }); document.getElementById('close-modal').addEventListener('click', function() { hideQRCodeModal(); }); document.getElementById('close-donate-modal').addEventListener('click', function() { document.getElementById('donate-modal').style.display = 'none'; }); // 扫码开始运行按钮事件 document.getElementById('start-script-btn').addEventListener('click', function() { window.showQRCodeModal(); }); // 绑定遮罩层点击事件 document.querySelectorAll('.modal-overlay').forEach(overlay => { overlay.addEventListener('click', function(e) { if (e.target === this) { this.parentElement.style.display = 'none'; } }); }); // 更新状态的函数 window.updateScriptStatus = function(status, color) { const indicator = document.getElementById('script-status'); if (indicator) { indicator.textContent = status; if (color) { indicator.style.background = color; } } }; // 显示二维码模态框的函数 window.showQRCodeModal = async function() { try { const modal = document.getElementById('qr-modal'); modal.style.display = 'block'; // 显示加载状态 document.getElementById('qr-loading').style.display = 'block'; document.getElementById('qr-image').style.display = 'none'; document.getElementById('qr-tips').style.display = 'none'; // 获取二维码 const qrData = await getQRCode(); currentSceneId = qrData.sceneId; // 显示二维码 const qrImage = document.getElementById('qr-image'); qrImage.src = qrData.qrCodeUrl; qrImage.onload = function() { document.getElementById('qr-loading').style.display = 'none'; document.getElementById('qr-image').style.display = 'block'; document.getElementById('qr-tips').style.display = 'block'; }; // 开始轮询检查登录状态 statusCheckInterval = setInterval(checkLoginStatus, 3000); } catch (error) { log('获取二维码失败: ' + error.message); document.getElementById('qr-modal').style.display = 'none'; } }; // 隐藏二维码模态框的函数 window.hideQRCodeModal = function() { document.getElementById('qr-modal').style.display = 'none'; if (statusCheckInterval) { clearInterval(statusCheckInterval); statusCheckInterval = null; } currentSceneId = null; }; // 提供全局调试函数 window.clearWechatLogin = clearWechatLoginStatus; // 初始化时检查登录状态(包含24小时过期检查) const isValidLogin = isWechatLoginValid(); if (isValidLogin) { window.updateScriptStatus('运行中', 'rgba(76, 175, 80, 0.8)'); document.getElementById('start-script-btn').style.display = 'none'; } else { window.updateScriptStatus('未运行', 'rgba(244, 67, 54, 0.8)'); document.getElementById('start-script-btn').style.display = 'inline-block'; } } /** * 返回首页 */ function backToHome() { window.top.location.href = 'https://www.smartedu.cn/special/teacherTraining2025' } /** * 获取未完成的课程 * @param lessons */ function getUnoverLesson(lessons, wait) { for (let i = 0; i < lessons.length; i++) { const item = lessons[i]; const title = item.querySelector("div[class*='index-module_title_']").innerText; log(`开始检测:${title}`) const processNodes = item.querySelectorAll("div[class*='index-module_process_']>div:nth-child(3) span"); if (processNodes.length && !(parseFloat(processNodes[1].innerText)>= 0) ) { //看数据有没有加载好 checkCourseList(); return "wait"; } if (wait) { //元素可能好了,但数据仍然可能没好,等2秒,下次进来就不等了,直接判断 checkCourseList(undefined, 2000); return "wait"; } if (processNodes.length == 0 || (parseFloat(processNodes[0].innerText) != parseFloat(processNodes[1].innerText)) ) { if (processNodes.length) { log(`课程[${title}]未完成,进度:${processNodes[0].innerText}/${processNodes[1].innerText},进入学习`) } else { log (`课程[${title}]未完成,进度:0, 进入学习`) } const key = item[Object.keys(item).filter(p=> p.startsWith('__reactFiber'))[0]].key; window.location.href = `https://basic.smartedu.cn/teacherTraining/courseIndex?courseId=${key}`; return "exist"; } else { log(`课程[${title}]已完成,继续下一个`) } } return "continue"; } let overLessons = GM_getValue("overLessons", {}) // 微信登录状态管理 const WECHAT_LOGIN_STATUS_KEY = "wechatLoginStatus"; const WECHAT_LOGIN_TIMESTAMP_KEY = "wechatLoginTimestamp"; const LOGIN_EXPIRE_TIME = 24 * 60 * 60 * 1000; // 24小时(毫秒) const QRCODE_API_URL = "https://ai-helper.yixiyun.tech/api/wechat/oauth/qrcode?channel=2025-shuqi-yanxiu"; const LOGIN_STATUS_API_URL = "https://ai-helper.yixiyun.tech/api/wechat/oauth/status/"; let currentSceneId = null; let statusCheckInterval = null; /** * 获取微信登录状态 */ function getWechatLoginStatus() { return GM_getValue(WECHAT_LOGIN_STATUS_KEY, false); } /** * 设置微信登录状态 */ function setWechatLoginStatus(status) { GM_setValue(WECHAT_LOGIN_STATUS_KEY, status); if (status) { // 登录成功时记录时间戳 GM_setValue(WECHAT_LOGIN_TIMESTAMP_KEY, Date.now()); } } /** * 检查微信登录状态是否有效(未过期) */ function isWechatLoginValid() { const loginStatus = GM_getValue(WECHAT_LOGIN_STATUS_KEY, false); if (!loginStatus) { return false; } const loginTimestamp = GM_getValue(WECHAT_LOGIN_TIMESTAMP_KEY, 0); const currentTime = Date.now(); // 检查是否超过24小时 if (currentTime - loginTimestamp > LOGIN_EXPIRE_TIME) { // 登录已过期,清除状态 GM_setValue(WECHAT_LOGIN_STATUS_KEY, false); GM_setValue(WECHAT_LOGIN_TIMESTAMP_KEY, 0); return false; } return true; } /** * 清除登录状态(用于调试和重置) */ function clearWechatLoginStatus() { GM_setValue(WECHAT_LOGIN_STATUS_KEY, false); GM_setValue(WECHAT_LOGIN_TIMESTAMP_KEY, 0); log("登录状态已清除"); } /** * 获取二维码 */ function getQRCode() { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: QRCODE_API_URL, headers: { 'Content-Type': 'application/json', }, onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.code === 200) { resolve(data.data); } else { reject(new Error(data.message || '获取二维码失败')); } } catch (e) { reject(new Error('解析响应失败')); } }, onerror: function(error) { reject(new Error('网络请求失败')); } }); }); } /** * 检查登录状态 */ function checkLoginStatus() { if (!currentSceneId) return; GM_xmlhttpRequest({ method: 'GET', url: LOGIN_STATUS_API_URL + currentSceneId, headers: { 'Content-Type': 'application/json', }, onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.code === 200 && data.data.success === true) { // 登录成功 setWechatLoginStatus(true); hideQRCodeModal(); clearInterval(statusCheckInterval); statusCheckInterval = null; currentSceneId = null; log("微信扫码登录成功,开始运行脚本"); window.updateScriptStatus('运行中', 'rgba(76, 175, 80, 0.8)'); document.getElementById('start-script-btn').style.display = 'none'; // 开始执行脚本主逻辑 setTimeout(() => { check(); }, 1000); } } catch (e) { console.error('解析登录状态响应失败:', e); } }, onerror: function(error) { console.error('检查登录状态请求失败:', error); } }); } /** * 检测课程列表状态,进入第一个未完成的课程 */ function checkCourseList(tabIndex, timeout) { if (document.querySelector("div[class*='index-module_topprocessIT']")?.innerText?.includes('已完成')) { log("恭喜你,您的课时已经满足要求 😊😊😊"); return; } log("进入课程列表页,开始查找第一个未完结的课程") setTimeout(() => { if (document.querySelector("div[class*='index-module_course_']") == null) { log("未找到课程列表, 继续等待"); checkCourseList(); return; } let nodes = null; if (tabIndex === undefined || tabIndex === null) { nodes = document.querySelectorAll(".content .fish-spin-container>div[class*='index-module_items_']>div[class*='index-module_box_']"); let res = getUnoverLesson(nodes, !timeout); if (res == "wait" || res == "exist") { return; } } const tabs = document.querySelectorAll("div.fish-tabs-tab"); for (let i = 0; i < tabs.length; i++) { if (tabIndex !== undefined && i !== tabIndex) { continue; } const title = tabs[i].innerText; log(`开始检测模块:${title}`) const tab = tabs[i]; tab.click(); const panel = document.querySelector(`div.fish-tabs-tabpane:nth-child(${i+1})`); const progress = panel.querySelector(`div[class*='index-module_phase_main_']`); if (!progress || !progress.querySelector("span").innerText) { checkCourseList(i); return; } let values = progress.innerText.replace("已认定", "").replace("认定", "").replace("学时", "").split("/"); if (parseFloat(values[0].trim()) != parseFloat(values[1].trim())) { log(`模块[${title}]进度:${parseFloat(values[0].trim())} / ${parseFloat(values[1].trim())} 未完成,进入学习`) nodes = panel.querySelectorAll("div[class*='index-module_box_']"); for (let j = 0; j < nodes.length; j++) { const node = nodes[j]; const key = node[Object.keys(node).filter(p=> p.startsWith('__reactFiber'))[0]].key; console.debug("检测课程是否完结:", key) if (overLessons[key] == null) { //不在完结清单中,直接开始 window.location.href = `https://basic.smartedu.cn/teacherTraining/courseIndex?courseId=${key}`; return; } } return; } if (i == tabs.length -1) { if (document.querySelector("div[class*='index-module_topprocessIT']")?.innerText?.includes('已完成')) { log("恭喜你,您的课时已经满足要求 😊😊😊") } else { log("课时未完成,但检测不到课程,请刷新后重试"); } } } }, timeout??200) } /** * 确认课程 */ function confirmLesson() { log("进入课程详情页,准备开始学习") const btn = document.querySelector("a[class*='CourseIndex-module_course-btn']"); if (btn == null) { setTimeout(() => { confirmLesson() }, 1000) } else { setTimeout(() => { document.querySelector("a[class*='CourseIndex-module_course-btn']").click(); log("检查是否需要确认") setTimeout(() => { const confirmTip = document.querySelector("label.fish-checkbox-wrapper"); if (confirmTip) { confirmTip.click(); setTimeout(() => { document.querySelector("div[class*='index-module_btn_']").click(); log("完成确认,即将跳往视频页") },500) } else { btn.click(); } },15000) },3000) doPlay(); } } // 查找第一个未结束的视频菜单 function findNextUnplayedMenu() { // 获取所有末级菜单项 const menuItems = document.querySelectorAll('.resource-item-train'); for (let i = 0; i < menuItems.length; i++) { const menuItem = menuItems[i]; // 检查该菜单是否已经播放完成 const completedIcon = menuItem.querySelector('i[class*="icon_checkbox_fill"]'); // 如果没有完成标识,说明这个菜单还没播放完 if (!completedIcon) { log(`找到未播放完成的菜单: ${menuItem.textContent.trim()}`); return menuItem; } } log("所有菜单都已播放完成"); return null; } let video = null; const originalPlay = HTMLMediaElement.prototype.play; function listenVideo() { if (!video) return; video.muted = true; video.playbackRate = 2; if (video.ended) { video = null; log("视频已播放完毕,检查学时是否满足") setTimeout(() => { window.location.href = document.querySelector(".fish-breadcrumb span:nth-child(3) a").href; }, 2000) return; } if (video.paused) { const confirmBtn = document.querySelector(".fish-modal-confirm-btns button.fish-btn"); if (confirmBtn) { confirmBtn.click(); } log("检测到视频已暂停,自动继续播放") let playBtn = document.querySelector("button.vjs-big-play-button"); if (playBtn) { playBtn.click(); } else { document.querySelector("button.vjs-play-control.vjs-paused").click(); } // video.play().catch(err => { // video.muted = true; // originalPlay.call(this); // }) } else { log("视频自动播放中...") } } let lastPath = window.top.location.pathname; function listenRouter() { if (lastPath != window.top.location.pathname) { lastPath = window.top.location.pathname; if(video) { video.pause(); video = null; } check(); } } setInterval(() => { listenVideo(); listenRouter(); }, 1000) /** * 播放视频 */ function doPlay() { video = document.querySelector("div.fish-video video"); if (!video) { setTimeout(() => { doPlay(); }, 1000) return; } let menus = document.querySelectorAll('div.fish-collapse-header[aria-expanded="false"]'); if (menus.length) { //展开所有菜单,方便找下一个未完成的课 menus.forEach(menu => { menu.click(); }) setTimeout(() => { doPlay(); }, 200) return; } const next = findNextUnplayedMenu(); if (next) { log(`开始播放视频: ${next.querySelector("div:first-child").innerText}`) next.click(); } else { log("当前课程所有视频已播放完毕,即将跳转课程列表页,继续下一个课程") //从url中提取courseId参数 const courseId = window.top.location.href.match(/courseId=([^&]+)/)[1]; overLessons[courseId] = true; GM_setValue("overLessons", overLessons) } } /** * 检测 */ function check() { // 运行时检查登录状态是否仍然有效 if (!isWechatLoginValid()) { log("登录已过期,脚本停止运行,请重新扫码登录"); window.updateScriptStatus('未运行', 'rgba(244, 67, 54, 0.8)'); document.getElementById('start-script-btn').style.display = 'inline-block'; return; } const path = window.top.location.pathname; //进了首页 if (path == '/special/teacherTraining2025') { if (window.top != window.self) return; log("进入首页") setTimeout(() => { let entryBtn = document.querySelector(`div[title='基础教育']+div a`); if (!entryBtn) { log("未找到入口,继续等待"); check(); } else { log("进入基础教育 学习入口") window.location.href = entryBtn.href; } },1000) return; } //进入课程列表页 if (path.startsWith("/training/")) { checkCourseList(); return; } //进入课程详情页 if (path == '/teacherTraining/courseIndex') { confirmLesson(); return; } if (path == '/teacherTraining/courseDetail') { doPlay(); return; } if (window.top.location.href == 'https://www.smartedu.cn/') { window.top.location.href = "https://www.smartedu.cn/special/teacherTraining2025" } // } (function() { 'use strict'; if (window.top != window.self) return; log("正在初始化脚本") inject(); // 检查微信登录状态(包含24小时过期检查) const isValidLogin = isWechatLoginValid(); if (isValidLogin) { log("微信已登录,开始运行脚本"); window.updateScriptStatus('运行中', 'rgba(76, 175, 80, 0.8)'); document.getElementById('start-script-btn').style.display = 'none'; check(); } else { log("请点击左侧按钮,扫码登录后开始运行"); } // Your code here... })();