// ==UserScript== // @name 江西职培在线网课助手 // @namespace jiangxizhipeizaixian // @version 1.0.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 // @downloadURL none // ==/UserScript== (function () { 'use strict'; var isDebug = GM_getValue('isDebug', false); var isCompleted = false; // 全局邮件地址 var localEmail = GM_getValue('localEmail', null); var isVerify = false; var isMoal = false; // 获取视频控件 var checkVideoInterval; // 全局悬浮窗 var shadow; // 监控视频播放 var monitorVideoInterval; var updateDebugInfoInterval; function print(message) { if (isDebug) { console.log(message); } } function Main() { print("职培在线网课助手脚本开始运行..."); // 创建信息悬浮窗 createFloatingWindow(); if (isCompleted) { print("当前课程已完成,脚本停止执行..."); clearInterval(monitorVideoInterval); return; } // 监控视频播放 monitorVideo(); } checkVideoInterval = setInterval(function () { if (getVideo()) { print("视频控件已加载,开始运行..."); Main(); clearInterval(checkVideoInterval); } else { print("视频控件未加载,继续轮询..."); } }, 500); function getMoal() { var moal = document.querySelector('.zhipei-modal-content') if (moal) { print("获取到人机验证弹窗,请手动完成人机验证!"); return moal; } return null; } function checkMoalType(moal) { let num = moal.querySelector('.code_box___32BrH'); if (num) { print("获取到人机验证弹窗,类型为数字验证码..."); print(num.querySelector('img').src); } } function captureAndSendEmail(item = null) { captureScreenshot(item).then(dataURL => { let subject = '职培在线网课助手人机验证弹窗通知'; let lvideo = getVideo(); let message = `

职培在线网课助手 - 人机验证弹窗通知

课程信息:

课程标题: ${getTitle()}
视频时长: ${lvideo.duration.toFixed(0)} 秒
已看时长: ${lvideo.currentTime.toFixed(0)} 秒

人机验证弹窗截图:

人机验证弹窗截图

请尽快处理人机验证弹窗,以免影响课程进度!

此邮件由职培在线网课助手自动发送,请勿直接回复!。

`; //print(dataURL); sendEmail(localEmail, subject, message); }); } function getNextButton() { var nextButton = document.querySelector('.next_button___YGZWZ'); if (nextButton) { print("成功获取下一页按钮"); nextButton.click(); } } function getTitle() { var title = document.querySelector('.header_box_title___1INxv'); if (title) { return title.innerText; } return null; } /** * 获取页面中的视频元素 * @returns {HTMLVideoElement|null} 返回视频元素,如果未找到视频元素,则返回 null。 */ function getVideo() { const videoContainer = document.getElementById('J_prismPlayer'); if (!videoContainer) return null; const video = videoContainer.querySelector('video'); return video || null; } function monitorVideo() { monitorVideoInterval = setInterval(function () { checkCurrentCourceIsCompleted(true); let moal = getMoal(); if (isMoal) { print("等待人机验证..."); if (!moal) { isMoal = false; print("人机验证完成,继续播放视频..."); window.location.reload(); } return; } // 如果不是人机验证阶段,且检测到moal if (moal) { if (!localEmail) { print("邮件地址未设置,不发送邮件通知!"); return; } isMoal = true; // 捕获并发送邮件 setTimeout(() => { captureAndSendEmail('.zhipei-modal-content'); }, 3000); checkMoalType(moal); } let video = getVideo(); if (video.currentTime >= video.duration) { print("视频播放完毕,尝试自动播放下一节..."); getNextButton(); return; } if (video.paused) { print("视频暂停,尝试继续播放..."); video.volume = 0; video.play(); } }, 1000); } function createFloatingWindow() { var floatingWindow = document.createElement('div'); shadow = floatingWindow.attachShadow({ mode: 'open' }); // 添加样式 var style = document.createElement('style'); style.textContent = ` #contentContainer { max-height: 50vh; /* 容器高度,设置为视口高度的80% */ 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; } .highlight { margin: 8px 0; font-size: 16px; font-weight: bold; line-height: 1.5; color: red; } input[type="text"] { width: calc(100% - 100px); padding: 5px; border: 1px solid #ccc; border-radius: 5px; } button { margin-top: 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; /* 默认隐藏调试信息内容 */ } `; shadow.appendChild(style); // 添加内容 var content = document.createElement('div'); content.id = 'debugWindow'; content.innerHTML = `

职培在线网课助手

功能列表:

1.自动静音后台播放 ☑

2.自动播放下一节课程☑

3.自动过人机验证☐

3.邮件提醒人机验证☑

使用说明:

填写邮箱用于接收人机验证通知邮件

推荐使用QQ邮箱,手机下载QQ邮箱App设置通知优先级为高,以免错过通知

配置信息

邮箱地址:

调试信息

等待视频信息...

不要乱点

`; shadow.appendChild(content); // 将浮动窗口添加到文档中 document.body.appendChild(floatingWindow); // 初始化复选框状态 shadow.querySelector('#debugMode').checked = isDebug; // 监听复选框状态变化 shadow.querySelector('#debugMode').addEventListener('change', function () { isDebug = this.checked; GM_setValue('isDebug', isDebug); shadow.querySelector('#debugContent').style.display = isDebug ? 'block' : 'none'; }); // 测试按钮功能 shadow.querySelector('#testButton1').addEventListener('click', function () { //checkCurrentCourceIsCompleted(true); captureAndSendEmail(); }); // 获取页面上的元素 var emailInput = shadow.querySelector('#emailInput'); var saveEmailButton = shadow.querySelector('#saveEmail'); // 初始化输入框的值为保存的邮箱 var savedEmail = GM_getValue('localEmail', ''); emailInput.value = savedEmail; // 点击保存邮箱按钮的事件处理 saveEmailButton.addEventListener('click', function () { var email = emailInput.value.trim(); if (email) { GM_setValue('localEmail', email); alert('邮箱地址已保存!请手动刷新网页生效!'); } else { alert('请输入有效的邮箱地址。'); } }); // 初始化调试信息的显示状态 shadow.querySelector('#debugContent').style.display = isDebug ? 'block' : 'none'; // 更新调试信息 setInterval(function () { updateDebugInfo(); }, 1000); } 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 = shadow.getElementById('debugInfo'); var info = ` 视频标题: ${getTitle()}
视频时长: ${video.duration.toFixed(0)} 秒
播放时间: ${video.currentTime.toFixed(0)} 秒
播放倍速: ${video.playbackRate}x
播放状态: ${video.paused ? '暂停' : '播放中'}
音量: ${Math.round(video.volume * 100)} %
`; debugInfo.innerHTML = info; } } function getContents() { var parentElement = document.querySelector('.content_box___1fOQp'); var result = {}; if (parentElement) { print("成功获取目录父级元素"); // 获取所有子元素 var childElements = parentElement.querySelectorAll('.content_box_wrap___ZdoU3'); if (childElements.length > 0) { print('开始解析目录子集元素...'); var parsedData = []; // 存储解析后的数据 childElements.forEach(function (childElement, index) { // 解析每个子元素的信息 var title = childElement.querySelector('.units_title___1Js-7')?.textContent || '未找到标题'; var time = childElement.querySelector('.time_box___1PlPI')?.textContent || '未找到时长'; var link = childElement.querySelector('a.units_wrap_box___1ncip')?.href || '未找到链接'; var completed = childElement.querySelector('.anticon-check-circle') ? '已完成' : '未完成'; // 将解析的信息存储在对象中 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; } // 查找第一个未完成的课程 function findFirstIncompleteCourse(contents) { for (let element of contents) { if (element.completed === '未完成') { return element; // 返回第一个未完成的课程对象 } } return null; // 如果没有未完成的课程,返回 null } /** * 检查当前课程是否已完成 * @param {boolean} autoNext - 如果为 true,且课程已完成,则自动跳转到下一个未完成的课程 * @returns {boolean} - 返回当前课程是否已完成 */ function checkCurrentCourceIsCompleted(autoNext = false) { // 获取当前页面的标题 let currentTitle = getTitle(); // 获取内容列表 let contents = getContents().data; // 查找与当前标题匹配的课程 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("所有课程已完成!"); alert("所有课程已完成!"); isCompleted = true; } } 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 += `
邮件图片`; } else if (image.startsWith('http://') || image.startsWith('https://')) { // 图片是URL message += `
邮件图片`; } else { print("图片路径无效!"); return; } } // 邮件发送数据 const emailData = { recipient: recipient, subject: subject || "默认主题", // 如果没有提供主题,使用默认主题 message: message || "默认内容" // 如果没有提供内容,使用默认内容 }; // 将emailData转换为JSON字符串 const jsonData = JSON.stringify(emailData); // 调用PHP接口的URL const apiUrl = "https://furina.one/api/email.php"; // 替换为实际PHP接口地址 // 使用GM_xmlhttpRequest发送POST请求 GM_xmlhttpRequest({ method: "POST", url: apiUrl, data: jsonData, headers: { "Content-Type": "application/json" // 告知服务器我们发送的是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} - 返回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); }); }); } })();