您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
江西职培在线自动化助手
// ==UserScript== // @name 江西职培在线网课助手 // @namespace jiangxizhipeizaixian // @version 1.1 // @description 江西职培在线自动化助手 // @author Nanako660 // @match https://jiangxi.zhipeizaixian.com/study/video* // @icon https://www.google.com/s2/favicons?sz=64&domain=zhipeizaixian.com // @require https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js // @grant GM_xmlhttpRequest // @grant GM_log // @grant GM_notification // @grant GM_setValue // @grant GM_getValue // @connect furina.one // @license MIT // ==/UserScript== (function () { 'use strict'; // #region 全局配置 // 自动播放视频 var isAutoPlay = GM_getValue('isAutoPlay', true); // 自动下一节 var isAutoNext = GM_getValue('isAutoNext', true); // 自动跳转最新课程 var isAutoJump = GM_getValue('isAutoJump', true); // 自动过人机验证 var isAutoVerify = GM_getValue('isAutoVerify', false); // 自动发送邮件通知 var isEmailNotice = GM_getValue('isEmailNotice', true); // 主循环间隔 var mainLoopInterval = GM_getValue('mainLoopInterval', 1000); // 调试模式 var isDebug = GM_getValue('isDebug', false); // 邮件地址 var localEmail = GM_getValue('localEmail', null); // #endregion // #region 常量 // 人机验证弹窗class const POP_WINDOWS_CLASS = '.zhipei-modal-content'; // 验证码弹窗特征class const CODE_CLASS = '.code_box___32BrH'; // 人脸弹窗特征class const FACE_CLASS = '.video_box___2zomT'; // 标题class const TITLE_CLASS = '.header_box_title___1INxv'; // 视频控件class const VIDEO_ID = 'J_prismPlayer'; // 目录class const CONTENTS_CLASS = '.content_box___1fOQp'; // 目录子项class const CONTENTS_ITEM_CLASS = '.content_box_wrap___ZdoU3'; // 目录子项标题class const CONTENTS_ITEM_TITLE_CLASS = '.units_title___1Js-7'; // 目录子项时长class const CONTENTS_ITEM_DURATION_CLASS = '.time_box___1PlPI'; // 目录子项链接class const CONTENTS_ITEM_LINK_CLASS = 'a.units_wrap_box___1ncip'; // 目录子项完成状态class const CONTENTS_ITEM_STATUS_CLASS = '.anticon-check-circle'; // 课程下一节按钮class const NEXT_BUTTON_CLASS = '.next_button___YGZWZ'; // 邮箱发送API const EMAIL_API_URL = 'https://furina.one/api/email.php'; // #endregion // #region 标志位 var isPopWindows = false; const FinishType = { NOT_FINISH: 0, FINISHED: 1, UPPER_LIMIT: 2 }; var finishType = FinishType.NOT_FINISH; // 验证类型 const VerifyType = { NONE: 0, CODE: 1, FACE: 2 }; var verifyType = VerifyType.NONE; // #endregion // #region 全局变量 // 主循环 var MainLoop; // 获取视频控件 var checkVideoInterval; // 全局悬浮窗 var shadowWindows; var updateDebugInfoInterval; // #endregion /** * 打印调试信息 * @param {string} message - 要打印的消息 */ function print(message) { if (isDebug) { console.log(message); } } // 等待视频加载完成 checkVideoInterval = setInterval(function () { if (getVideo()) { Main(); clearInterval(checkVideoInterval); } }, 1000); function Main() { print("职培在线网课助手脚本开始运行..."); if (isAutoJump) { autoJumpToLatestCourse(); } // 创建信息悬浮窗 createFloatingWindow(); // 主循环 mainLoop(); } function mainLoop() { MainLoop = setInterval(function () { if (finishType === FinishType.FINISHED || finishType === FinishType.UPPER_LIMIT) { alert("当前课程已完成或者每日学习时长已达8小时上限,脚本停止执行..."); clearInterval(MainLoop); return; } // 更新调试信息 if (isDebug) { updateDebugInfo(); } // 检测弹窗 let popWindows = getPopWindows(); if (isPopWindows) { print("等待人机验证..."); if (!popWindows) { isPopWindows = false; print("人机验证完成,继续播放视频..."); window.location.reload(); } return; } if (popWindows) { isPopWindows = true; checkPopWindowsType(popWindows); // 截取弹窗并发送邮件 if (isEmailNotice) { setTimeout(() => { sendNoticeEmail(POP_WINDOWS_CLASS); }, 3000); } } // 视频播放完毕,自动播放下一节 if (isAutoNext) { let video = getVideo(); if (video.currentTime >= video.duration) { getNextButtonAndClick(); return; } } // 自动播放视频 if (isAutoPlay && !isPopWindows) { let video = getVideo(); if (video.paused) { print("视频暂停,尝试继续播放..."); video.volume = 0; video.play(); } } }, mainLoopInterval); } function getPopWindows() { var popWindows = document.querySelector(POP_WINDOWS_CLASS) if (popWindows) { return popWindows; } return null; } function checkPopWindowsType(popWindows) { let code = popWindows.querySelector(CODE_CLASS); let face = popWindows.querySelector(FACE_CLASS); if (code) { verifyType = VerifyType.CODE; print("获取到人机验证弹窗,类型为验证码验证..."); print(code.querySelector('img').src); } else if (face) { verifyType = VerifyType.FACE; print("获取到人机验证弹窗,类型为人脸验证..."); } else { finishType = FinishType.UPPER_LIMIT; print("今日学习时长已达8小时上限..."); } } function sendNoticeEmail(item = null) { let title; if (verifyType === VerifyType.CODE) { title = "验证码验证"; } else if (verifyType === VerifyType.FACE) { title = "人脸验证"; } else { print("无弹窗,不发送邮件通知。"); return; } captureScreenshot(item).then(dataURL => { let subject = `江西职培在线网课助手${title}弹窗通知`; let lvideo = getVideo(); let message = ` <div style="font-family: Arial, sans-serif; color: #333; line-height: 1.6;"> <h2 style="color: #4CAF50; border-bottom: 2px solid #4CAF50; padding-bottom: 5px;"> 江西职培在线网课助手 - ${title}弹窗通知 </h2> <p>课程信息:</p> <table style="width: 100%; border-collapse: collapse; margin-top: 15px;"> <tr> <td style="padding: 8px; border: 1px solid #ddd; background-color: #f9f9f9; width: 30%;"> <strong>课程标题:</strong> </td> <td style="padding: 8px; border: 1px solid #ddd;">${getTitle()}</td> </tr> <tr> <td style="padding: 8px; border: 1px solid #ddd; background-color: #f9f9f9;"> <strong>视频时长:</strong> </td> <td style="padding: 8px; border: 1px solid #ddd;">${lvideo.duration.toFixed(0)} 秒</td> </tr> <tr> <td style="padding: 8px; border: 1px solid #ddd; background-color: #f9f9f9;"> <strong>已看时长:</strong> </td> <td style="padding: 8px; border: 1px solid #ddd;">${lvideo.currentTime.toFixed(0)} 秒</td> </tr> </table> <p style="margin-top: 20px;">${title}弹窗截图:</p> <div style="text-align: center; margin: 20px 0;"> <img src="${dataURL}" alt="人${title}弹窗截图" style="max-width: 100%; border: 1px solid #ddd; padding: 5px; background-color: #f9f9f9;" /> </div> <p>请尽快处理${title}弹窗,以免影响课程进度!</p> <p style="color: #777; font-size: 12px;"> 此邮件由江西职培在线网课助手自动发送,如有反馈可直接回复邮件,请勿泄露个人信息。 </p> </div> `; //print(dataURL); sendEmail(localEmail, subject, message); }); } function getNextButtonAndClick() { var nextButton = document.querySelector(NEXT_BUTTON_CLASS); if (nextButton) { print("当前视频播放完毕,尝试自动播放下一节..."); nextButton.click(); } } function autoJumpToLatestCourse() { var checkCourse = setInterval(() => { print("检测当前课程是否完成..."); var contents = getContents(); if (contents) { checkCurrentCourceIsCompleted(getContentsData(contents), true); clearInterval(checkCourse); } }, 1000); } function getTitle() { var title = document.querySelector(TITLE_CLASS); if (title) { return title.innerText; } return null; } /** * 获取页面中的视频元素 * @returns {HTMLVideoElement|null} 返回视频元素,如果未找到视频元素,则返回 null。 */ function getVideo() { const videoContainer = document.getElementById(VIDEO_ID); if (!videoContainer) return null; const video = videoContainer.querySelector('video'); return video || null; } function createFloatingWindow() { var floatingWindow = document.createElement('div'); shadowWindows = floatingWindow.attachShadow({ mode: 'open' }); // 添加样式 var style = document.createElement('style'); style.textContent = ` #contentContainer { max-height: 50vh; overflow-y: auto; #padding: 10px; #border: 1px solid #ccc; } #debugWindow { position: fixed; top: 80px; right: 10px; background-color: #f0f0f0; color: #333; padding: 20px; border-radius: 15px; box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.3); z-index: 9999; #cursor: move; width: 320px; font-family: Arial, sans-serif; user-select: none; } h4 { margin-top: 0; margin-bottom: 10px; font-size: 18px; font-weight: bold; color: #444; } h3 { margin-top: 0; margin-bottom: 10px; font-size: 16px; font-weight: bold; color: #444; } p { margin: 8px 0; font-size: 14px; line-height: 1.5; } .func { margin: 8px 0; font-size: 15px; font-weight: bold; line-height: 1.2; color: #66ccff; } .highlight { margin: 8px 0; font-size: 16px; font-weight: bold; line-height: 1.5; color: red; } input[type="email"] { width: calc(100% - 100px); padding: 5px; border: 1px solid #ccc; border-radius: 5px; } button { margin-top: 10px; margin-bottom: 10px; padding: 5px 10px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; } button:hover { background-color: #0056b3; } label { display: flex; align-items: center; #margin-bottom: 10px; font-size: 14px; } label input[type="checkbox"] { margin-right: 10px; } #debugContent { display: none; /* 默认隐藏调试信息内容 */ } `; shadowWindows.appendChild(style); // 添加内容 var content = document.createElement('div'); content.id = 'debugWindow'; content.innerHTML = ` <div id="contentContainer"> <h3 id='title'>职培在线网课助手</h4> <h4>功能列表:</h3> <label> <p class="func">🔇 静音后台自动播放</p> <input type="checkbox" id="autoPlay" /> </label> <label> <p class="func">⏭️ 自动播放下一节</p> <input type="checkbox" id="autoNext" /> </label> <label> <p class="func">🛫 自动跳转最新节</p> <input type="checkbox" id="autoJump" /> </label> <label"> <p class="func"> <del>🤖 自动过人机验证</del> <input type="checkbox" id="autoVerify" disabled /> </p> </label> <label> <p class="func">📧 邮件提醒人机验证</p> <input type="checkbox" id="emailNotice" /> </label> <h4>使用说明:</h4> <p class="highlight">填写邮箱用于接收人机验证通知邮件</p1> <p class="highlight">推荐使用QQ邮箱,手机下载QQ邮箱App设置通知优先级为高,以免错过通知</p> <p class="highlight"></p> <h4>配置信息:</h4> <p>邮箱地址:</p> <div></div> <input type="email" id="emailInput" placeholder="输入邮箱地址" /> <div></div> <button id="saveEmail">保存邮箱</button> <div></div> <label> DebugMode <input type="checkbox" id="debugMode" /> </label> <div id="debugContent"> <h4>调试信息</h4> <div id="debugInfo">初始化调试信息...</div> <h4>调试按钮</h4> <p class="highlight">调试用,没事别乱点</p> <button id="backMyClass">返回我的班级</button> <div></div> <button id="testButton1">测试按钮1</button> </div> </div> `; shadowWindows.appendChild(content); // 将浮动窗口添加到文档中 document.body.appendChild(floatingWindow); // 获取页面元素 let autoPlay = shadowWindows.querySelector('#autoPlay'); let autoNext = shadowWindows.querySelector('#autoNext'); let autoJump = shadowWindows.querySelector('#autoJump'); let autoVerify = shadowWindows.querySelector('#autoVerify'); let emailNotice = shadowWindows.querySelector('#emailNotice'); let emailInput = shadowWindows.querySelector('#emailInput'); let saveEmailButton = shadowWindows.querySelector('#saveEmail'); let debugMode = shadowWindows.querySelector('#debugMode'); let debugContent = shadowWindows.querySelector('#debugContent'); let backMyClassButton = shadowWindows.querySelector('#backMyClass'); let testButton1 = shadowWindows.querySelector('#testButton1'); // 初始化状态 autoPlay.checked = isAutoPlay; autoNext.checked = isAutoNext; autoJump.checked = isAutoJump; autoVerify.checked = isAutoVerify; // 自动过人机验证功能暂时关闭 emailNotice.checked = isEmailNotice; shadowWindows.querySelector('#debugMode').checked = isDebug; let savedEmail = localEmail; emailInput.value = savedEmail; debugContent.style.display = isDebug ? 'block' : 'none'; // 监听复选框状态变化 autoPlay.addEventListener('change', function () { isAutoPlay = this.checked; GM_setValue('isAutoPlay', isAutoPlay); }); autoNext.addEventListener('change', function () { isAutoNext = this.checked; GM_setValue('isAutoNext', isAutoNext); }); autoJump.addEventListener('change', function () { isAutoJump = this.checked; GM_setValue('isAutoJump', isAutoJump); if (this.checked) { autoJumpToLatestCourse(); } }); autoVerify.addEventListener('change', function () { isAutoVerify = this.checked; GM_setValue('isAutoVerify', isAutoVerify); }); emailNotice.addEventListener('change', function () { isEmailNotice = this.checked; GM_setValue('isEmailNotice', isEmailNotice); }); debugMode.addEventListener('change', function () { isDebug = this.checked; GM_setValue('isDebug', isDebug); shadowWindows.querySelector('#debugContent').style.display = isDebug ? 'block' : 'none'; }); saveEmailButton.addEventListener('click', function () { var email = emailInput.value.trim(); if (email) { GM_setValue('localEmail', email); alert('邮箱地址已保存!'); } else { alert('请输入有效的邮箱地址!'); } }); backMyClassButton.addEventListener('click', function () { window.location.href = 'https://admin-jiangxi.zhipeizaixian.com/train-center/mine/student/subPages/student/class'; }); testButton1.addEventListener('click', function () { //checkCurrentCourceIsCompleted(true); sendNoticeEmail(); }); } function makeDraggable(element) { var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; element.onmousedown = dragMouseDown; function dragMouseDown(e) { e = e || window.event; e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; // 计算新的位置 var newTop = element.offsetTop - pos2; var newLeft = element.offsetLeft - pos1; // 限制拖动范围,避免拖出屏幕 var minLeft = 0; var minTop = 0; var maxLeft = window.innerWidth - element.offsetWidth; var maxTop = window.innerHeight - element.offsetHeight; // 限制 left 和 top 的最小最大值 newLeft = Math.max(minLeft, Math.min(newLeft, maxLeft)); newTop = Math.max(minTop, Math.min(newTop, maxTop)); // 设置窗口的新位置,并保持固定宽度 element.style.top = newTop + "px"; element.style.left = newLeft + "px"; element.style.width = '300px'; // 强制宽度固定,避免拖动时缩小 } function closeDragElement() { document.onmouseup = null; document.onmousemove = null; } } function updateDebugInfo() { var video = getVideo(); if (video) { var debugInfo = shadowWindows.getElementById('debugInfo'); var info = ` <strong>视频标题:</strong> ${getTitle()} <br> <strong>视频时长:</strong> ${video.duration.toFixed(0)} 秒 <br> <strong>播放时间:</strong> ${video.currentTime.toFixed(0)} 秒 <br> <strong>播放倍速:</strong> ${video.playbackRate}x <br> <strong>播放状态:</strong> ${video.paused ? '暂停' : '播放中'} <br> <strong>音量:</strong> ${Math.round(video.volume * 100)} % <br> `; debugInfo.innerHTML = info; } } function getContents() { var contents = document.querySelector(CONTENTS_CLASS); return contents; } function getContentsData(contents) { var result = {}; if (contents) { print("成功获取目录父级元素"); // 获取所有子元素 var childElements = contents.querySelectorAll(CONTENTS_ITEM_CLASS); if (childElements.length > 0) { print('开始解析目录子集元素...'); var parsedData = []; // 存储解析后的数据 childElements.forEach(function (childElement, index) { // 解析每个子元素的信息 var title = childElement.querySelector(CONTENTS_ITEM_TITLE_CLASS)?.textContent || '未找到标题'; var time = childElement.querySelector(CONTENTS_ITEM_DURATION_CLASS)?.textContent || '未找到时长'; var link = childElement.querySelector(CONTENTS_ITEM_LINK_CLASS)?.href || '未找到链接'; var completed = childElement.querySelector(CONTENTS_ITEM_STATUS_CLASS) ? '已完成' : '未完成'; // 将解析的信息存储在对象中 var item = { index: index + 1, title: title, time: time, link: link, completed: completed }; // 将对象添加到数组中 parsedData.push(item); }); // 将解析后的数据存储在结果对象中 result.data = parsedData; } else { print("未找到目录子元素"); result.data = []; } } else { print("未找到目录父级元素"); result.data = []; } return result.data; } // 查找第一个未完成的课程 function findFirstIncompleteCourse(contents) { for (let element of contents) { if (element.completed === '未完成') { return element; // 返回第一个未完成的课程对象 } } return null; // 如果没有未完成的课程,返回 null } /** * 检查当前课程是否已完成 * @param {Array} contents - 课程目录数组 * @param {boolean} autoNext - 如果为 true,且课程已完成,则自动跳转到下一个未完成的课程 * @returns {boolean} - 返回当前课程是否已完成 */ function checkCurrentCourceIsCompleted(contents, autoNext = false) { // 获取当前页面的标题 let currentTitle = getTitle(); // 查找与当前标题匹配的课程 let currentCourse = contents.find(element => element.title === currentTitle); if (currentCourse) { print(`检查当前课程是否已完成:${currentCourse.title}:${currentCourse.completed}`); if (currentCourse.completed === '已完成' && autoNext) { let nextCourse = findFirstIncompleteCourse(contents); if (nextCourse) { print(`跳转到未完成的课程:${nextCourse.title}`); window.location.href = nextCourse.link; // 跳转到第一个未完成课程的链接 } else { print("所有课程已完成!"); finishType = FinishType.FINISHED; } } return currentCourse.completed === '已完成'; } return false; // 如果未找到当前课程,返回 false } /** * 发送邮件 * @param {string} recipient - 收件人邮箱地址 * @param {string} subject - 邮件主题 * @param {string} message - 邮件内容 * @param {string} image - 图片路径、URL或base64编码,可选 */ function sendEmail(recipient, subject, message, image = null) { // 检查收件人邮箱地址是否为空 if (!recipient) { print("邮件通知发送失败,邮箱地址不正确!"); alert("邮件通知发送失败,邮箱地址不正确!"); return; } // 如果图片存在 if (image) { // 检查是否是base64编码 if (image.startsWith('data:image/')) { // 图片是base64编码 message += `<br><img src="${image}" alt="邮件图片" />`; } else if (image.startsWith('http://') || image.startsWith('https://')) { // 图片是URL message += `<br><img src="${image}" alt="邮件图片" />`; } else { print("图片路径无效!"); return; } } // 邮件发送数据 const emailData = { recipient: recipient, subject: subject || "默认主题", // 如果没有提供主题,使用默认主题 message: message || "默认内容" // 如果没有提供内容,使用默认内容 }; // 将emailData转换为JSON字符串 const jsonData = JSON.stringify(emailData); // 调用PHP接口的URL const apiUrl = EMAIL_API_URL; // 使用GM_xmlhttpRequest发送POST请求 GM_xmlhttpRequest({ method: "POST", url: apiUrl, data: jsonData, headers: { "Content-Type": "application/json" }, onload: function (response) { if (response.status === 200) { // 处理成功响应 print("邮件发送成功: " + response.responseText); } else { // 处理失败响应 print("邮件通知发送失败: " + response.status + " - " + response.responseText); alert("邮件通知发送失败: " + response.status + " - " + response.responseText); } }, onerror: function (error) { // 处理错误 print("邮件通知发送失败,请求错误: " + error); alert("邮件通知发送失败,请求错误: " + error); } }); } /** * 截取网页内容并返回base64编码的图片 * @param {string} [selector] - 要截取的元素选择器,若为空则截取整个网页 * @returns {Promise<string>} - 返回base64编码的图片数据URL */ function captureScreenshot(selector = null) { return new Promise((resolve, reject) => { let element = document.body; // 默认截取整个网页 // 如果提供了选择器,尝试查找元素 if (selector) { element = document.querySelector(selector); if (!element) { print("指定的元素未找到!"); reject("指定的元素未找到!"); return; } } // 使用 html2canvas 捕获截图 html2canvas(element).then(canvas => { // 将 canvas 转换为 base64 编码的图像 const dataURL = canvas.toDataURL('image/png'); resolve(dataURL); }).catch(error => { print("截图失败: " + error); reject(error); }); }); } })();