【高教在线刷课助手】|| 自动执行,移除防止暂停,自动跳转下一节
// ==UserScript==
// @name 【高教在线刷课助手】|| 自动执行,移除防止暂停,自动跳转下一节
// @namespace http://tampermonkey.net/
// @version 0.0.1
// @description 高教在线课程自动挂机,当前脚本支持课程视频播放完成,自动跳转下一小节,章节测试自动跳过,后台播放防止视频暂停。
// @author Sweek
// @match *://*.cqooc.com/*
// @license GPLv3
// @icon https://www.sweek.top/api/preview/avatar.jpg
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @require https://code.jquery.com/jquery-2.1.4.min.js
// @grant unsafeWindow
// @grant GM_getResourceText
// ==/UserScript==
/* 窗口初始化 */
/* 窗口初始化 */
/* 窗口初始化 */
function initPopup() {
// 创建 CSS 样式
const popCSs = `
#my-window {
position: fixed;
top: 10px;
left: 20px;
width: 400px;
height: 600px;
background: rgb(255, 255, 255);
color: white;
font-size: 14px;
border-radius: 5px;
z-index: 9999;
display: flex;
flex-direction: column;
user-select: none;
font-family: 'fangsong';
border: 1px solid rgb(71, 158, 130);
overflow: hidden;
}
#window-header {
height: 50px;
padding: 0 10px;
background: rgb(71, 158, 130);
cursor: grab;
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 5px 5px 0 0;
.window-header-title {
font-size: 16px;
font-weight: bold;
font-family: 'fangsong';
}
}
#restore-btn {
cursor: pointer;
font-size: 16px;
background: none;
border: none;
color: #444;
height:20px;
width:30px;
text-align: center;
line-height: 20px;
background: #fff;
border-radius: 5px;
padding: 0 5px;
}
#restore-btn:hover {
background: rgb(71, 158, 130);
border: 1px solid #fff;
color: #fff;
}
.tab {
display: inline-block;
padding: 5px 10px;
cursor: pointer;
background: #888;
text-align: center;
font-weight: bold;
}
.tab.active {
background: #444;
}
#window-content {
flex: 1;
padding: 8px;
overflow: auto;
}
#my-window.minimized {
height: 50px;
overflow: hidden;
}
#tab-container {
display: block;
width: 100%;
background: #ddd;
display: grid;
grid-template-columns: repeat(2, 1fr);
padding: 6px 8px;
}
#window-content {
display: block;
background: #fff;
}
#my-window.minimized #window-content, #my-window.minimized #tab-container {
display: none;
}
#taskCountContent {
background: #fff;
border: 1px solid #ccc;
border-radius: 5px;
height: 100%;
overflow-y: auto;
color: #333;
line-height: 20px;
padding: 5px;
}
#taskLogsContent {
background: #222;
border: 1px solid #ccc;
border-radius: 2px;
height: 100%;
overflow-y: auto;
color: #fff;
line-height: 20px;
padding: 5px;
}
`;
// 添加 CSS 样式
const style = document.createElement("style");
style.innerHTML = popCSs;
document.head.appendChild(style);
// 创建窗口元素
const popHtml = `
<div id="window-header">
<span class="window-header-title">Sweek高教在线刷课助手[0.0.1]</span>
<div id="restore-btn">⏷</div>
</div>
<div id="tab-container">
<span class="tab active" data-tab="taskCount">页面任务</span>
<span class="tab" data-tab="taskLogs">执行日志</span>
</div>
<div id="window-content">
<div id="taskCountContent">当前任务数: <span id="taskCount">0</span></div>
<div id="taskLogsContent" style="display: none;"></div>
</div>
`;
const myWindow = document.createElement("div");
myWindow.id = "my-window";
myWindow.innerHTML = popHtml;
document.body.appendChild(myWindow);
// 绑定最小化按钮事件
document.getElementById("restore-btn").addEventListener("click", () => {
myWindow.classList.toggle("minimized");
});
// 处理拖动窗口
let isDragging = false;
let offsetX = 0;
let offsetY = 0;
document.getElementById("window-header").addEventListener("mousedown", (e) => {
isDragging = true;
offsetX = e.clientX - myWindow.offsetLeft;
offsetY = e.clientY - myWindow.offsetTop;
});
document.addEventListener("mousemove", (e) => {
if (isDragging) {
let x = e.clientX - offsetX;
let y = e.clientY - offsetY;
// 限制窗口不能拖出屏幕
let maxX = window.innerWidth - myWindow.offsetWidth;
let maxY = window.innerHeight - myWindow.offsetHeight;
x = Math.max(0, Math.min(x, maxX));
y = Math.max(0, Math.min(y, maxY));
myWindow.style.left = `${x}px`;
myWindow.style.top = `${y}px`;
}
});
document.addEventListener("mouseup", () => {
isDragging = false;
});
// 绑定 Tab 切换事件
document.querySelectorAll(".tab").forEach(tab => {
tab.addEventListener("click", () => {
document.querySelectorAll(".tab").forEach(el => el.classList.remove("active"));
tab.classList.add("active");
document.getElementById("taskCountContent").style.display = "none";
document.getElementById("taskLogsContent").style.display = "none";
if (tab.dataset.tab === "taskCount") {
document.getElementById("taskCountContent").style.display = "block";
} else {
document.getElementById("taskLogsContent").style.display = "block";
}
});
});
}
function destroyPopup() {
const myWindow = document.getElementById("my-window");
if (myWindow) {
myWindow.remove(); // 移除窗口
}
const styleTags = document.querySelectorAll("style");
styleTags.forEach(style => {
if (style.innerHTML.includes("#my-window")) {
style.remove(); // 移除添加的 CSS 样式
}
});
document.removeEventListener("mousemove", moveHandler);
document.removeEventListener("mouseup", upHandler);
// console.log("窗口已销毁");
}
/* 课程处理方法 */
/* 课程处理方法 */
/* 课程处理方法 */
let taskQueue = []; // 任务队列
const testDealEvent = new Event("testRedeal", { bubbles: false, cancelable: false });
// 处理视频
function handleVideo(Dom) {
return new Promise((resolve) => {
const playButton = Dom.querySelector(".dplayer-mobile-play"); // 获取播放按钮
const video = Dom.querySelector("video"); // 获取视频元素
if (!video) {
addLog("未找到视频元素");
return resolve();
}
let playAttempted = false; // 是否尝试过播放
// 监听视频播放时的进度
video.addEventListener("timeupdate", () => {
const currentTime = video.currentTime; // 当前播放时间
const duration = video.duration; // 视频总时长
const playbackRate = video.playbackRate; // 播放倍速
// 生成显示进度的内容
const val1 = currentTime; // 当前播放时间
const val2 = duration; // 视频总时长
setProgress(val1, val2, playbackRate, 'video'); // 设置显示播放进度
});
function preventPause() {
video.onpause = () => {
// addLog("视频暂停,1秒后重新播放");
setTimeout(() => {
if (video.paused) {
video.play().catch((error) => {
addLog(`重新播放失败: ${error}`);
});
}
}, 500); // 0.5秒后重新播放
};
}
function checkPlayback() {
setTimeout(() => {
if (!video.paused) {
addLog("视频已成功播放");
return;
}
addLog("播放按钮点击无效,尝试直接调用 video.play()");
video.muted = true; // 静音播放,防止浏览器限制
video.play().catch((error) => {
addLog(`直接播放失败: ${error}`);
});
}, 500); // 延迟检查,确保点击后有时间触发播放
}
if (playButton) {
addLog("找到播放按钮,尝试点击播放");
playButton.click();
playAttempted = true;
checkPlayback(); // 延迟检查播放情况
}
if (!playAttempted) {
addLog("未找到播放按钮,尝试直接调用 video.play()");
video.muted = true;
video.play().catch((error) => {
addLog(`直接播放失败: ${error}`);
});
}
// 阻止视频暂停
preventPause();
// 监听视频播放完成事件
video.onended = () => {
addLog("视频播放完成");
// 清理事件监听和状态
video.onended = null; // 移除事件监听
video.onpause = null; // 移除暂停监听
resolve(); // 完成当前视频任务
};
});
}
// 处理PDF
function handlePDF(Dom) {
return new Promise((resolve) => {
const interval = setInterval(() => {
const headerBox = document.querySelector(".header-box");
if (headerBox) {
const text = headerBox.innerText;
if (!text.includes("完成倒计时")) {
clearInterval(interval); // 停止轮询
resolve(); // 任务完成,调用 resolve
}
}
}, 1000); // 每秒检查一次
});
}
// 获取课程内容类型
function getCourseContentType(Dom) {
// 获取DOM元素的HTML内容
const htmlContent = Dom.innerHTML || Dom.outerHTML;
// 判断Dom中是否包含<video>标签
if (htmlContent.includes('video')) {
return 'video';
}
// 判断Dom中是否包含<iframe>标签
if (htmlContent.includes('iframe')) {
return 'PDF';
}
// 如果都不包含,返回null或者其他默认值
return null;
}
// 处理课程接口
function handleCourseContent(Dom) {
return new Promise((resolve) => {
const courseContentType = getCourseContentType(Dom)
switch (courseContentType) {
case 'video':
addLog("开始处理视频任务...");
handleVideo(Dom).then(() => {
addLog("视频任务处理完成");
resolve();
});
break;
case 'PDF':
addLog("开始处理PDF/PPT内容...");
handlePDF(Dom).then(() => {
addLog("PDF/PPT任务处理完成");
resolve();
});
break;
default:
addLog("未知课程类型,跳过该任务");
resolve();
}
});
}
// 监听任务完成状态的函数
function waitForContentToLoad() {
return new Promise((resolve) => {
let contentContainer = document.querySelector(".video-box");
if (!contentContainer) {
addLog("未找到课程内容模块,直接继续下一个任务");
resolve();
return
}
let observer = new MutationObserver((mutations, obs) => {
if (contentContainer.innerText.trim() !== "") {
obs.disconnect(); // 停止监听
// 调用 handleCourseContent,并等待其完成
handleCourseContent(contentContainer).then(() => {
resolve();
});
}
});
observer.observe(contentContainer, { childList: true, subtree: true });
// 设置超时防止卡死
// setTimeout(() => {
// addLog("课程内容加载超时,继续下一个任务");
// observer.disconnect();
// resolve();
// }, 5000);
});
}
// 任务执行器
async function processNextTask() {
if (taskQueue.length === 0) {
addLog("所有任务已执行完毕");
return;
}
let element = taskQueue.shift(); // 取出任务
// console.log(`点击元素:`, element);
element.click(); // 触发点击
await waitForContentToLoad(); // 等待课程内容加载处理完成
// 触发下一个任务
setTimeout(() => {
document.dispatchEvent(testDealEvent);
}, 2000);
}
// 监听事件,触发下一个任务
document.addEventListener("testRedeal", processNextTask);
// 初始化任务执行
function startTaskFlow() {
let allTasks = Array.from(document.querySelectorAll(".third-level-box"))
.filter(el => !/作业|测验/.test(el.innerText)); // 筛选掉包含"作业"或"测验"的任务
let activeIndex = allTasks.findIndex(el => el.classList.contains("active"));
addLog(`本课程共 ${allTasks.length} 个有效任务`); // 仅计算有效任务
if (activeIndex === -1) {
console.log("未找到 active 状态的元素,从头开始执行任务流");
taskQueue = allTasks;
} else {
console.log(`找到 active 元素,从索引 ${activeIndex} 开始执行任务流`);
taskQueue = [...allTasks.slice(activeIndex)];
}
addLog(`共 ${taskQueue.length} 个任务即将执行`);
if (taskQueue.length > 0) {
document.dispatchEvent(testDealEvent);
}
}
/* 工具方法 */
/* 工具方法 */
/* 工具方法 */
// 格式化时间函数
function formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.floor(seconds % 60);
return `${minutes}:${remainingSeconds < 10 ? "0" + remainingSeconds : remainingSeconds}`;
}
// 添加日志方法
function addLog(message) {
let logEl = document.getElementById("taskLogsContent");
let logItem = document.createElement("div");
logItem.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
logEl.appendChild(logItem);
};
// 设置播放进度
function setProgress(val1, val2, val3, type) {
let newContent = "";
switch (type) {
case "video":
newContent += `<p>播放进度:${(val1/val2 * 100).toFixed(2)}%</p><hr>`;
newContent += `<p>播放倍速:${val3}</p><hr>`;
newContent += `<p>视频长度:${formatTime(val1)}/${formatTime(val2)}</p>`;
break;
case "PDF":
newContent += `<p>滚动进度:${(val1/val2 * 100).toFixed(2)}%</p><hr>`;
newContent += `<p>滚动倍速:${val3}</p><hr>`;
newContent += `<p>PDF高度:${formatTime(val1)}/${formatTime(val2)}</p>`;
break;
default:
break;
}
const taskCountContent = document.getElementById("taskCountContent"); // 获取显示进度的元素
// 更新 taskCountContent 中的内容
taskCountContent.innerHTML = newContent;
}
// 页面通知提示
function notify(text, time) {
// 创建通知元素
var notification = document.createElement('div');
notification.className = 'notification';
// 设置通知内容
notification.innerHTML = '<div class="notification-content"><h2 style="font-size: 16px;font-weight: bold;color: #307dff;font-family: fangsong;">' + 'Sweek高教在线刷课助手提示' + '</h2><p style="font-family: fangsong;font-size: 13px;font-weight: bold;">' + text + '</p></div>';
// 将通知添加到页面中
document.body.appendChild(notification);
// 设置通知样式
notification.style.position = 'fixed';
notification.style.top = '50px';
notification.style.left = '-400px'; // 从左边弹出
notification.style.transform = 'translateY(-50%)'; // 垂直居中
notification.style.transition = 'left 0.5s ease-in-out'; // 添加过渡效果
notification.style.zIndex = '999999';
notification.style.backgroundColor = '#fff';
notification.style.border = '1px solid #ccc';
notification.style.padding = '10px';
notification.style.borderRadius = '5px';
notification.style.lineHeight = '25px';
// 等待一小段时间后,移动通知到左边
setTimeout(function() {
notification.style.left = '20px'; // 移动到左边
}, 100);
// 设置定时器,在指定时间后移除通知
setTimeout(function() {
// 移动通知到左边以外
notification.style.left = '-400px';
// 等待过渡效果完成后,移除通知元素
setTimeout(function() {
notification.remove();
}, 500);
}, time);
}
/* 处理页面路由跳转 */
/* 处理页面路由跳转 */
/* 处理页面路由跳转 */
let lastPath = location.pathname;
function handlePathChange() {
if (lastPath !== location.pathname) {
lastPath = location.pathname;
handleInPageNotify()
}
}
// 进入页面提示逻辑
function handleInPageNotify() {
// console.log('location.pathname:::+ ', location.pathname)
switch (location.pathname) {
case '/index/home':
notify('高教在线刷课助手已成功启动,已进入高教在线首页,请鼠标悬浮右上角头像点击在学课程', 2500)
destroyPopup()
break;
case '/account/course':
notify('已进入在学课程页面', 2500)
destroyPopup()
break;
case '/course/detail/courseStudy':
notify('已进入课程学习页面', 2500)
// 初始化窗口
initPopup()
setTimeout(() => {
// 执行任务流
startTaskFlow();
}, 2500);
break;
default:
break;
}
}
// 劫持 history.pushState 和 replaceState
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;
history.pushState = function (...args) {
originalPushState.apply(this, args);
handlePathChange();
};
history.replaceState = function (...args) {
originalReplaceState.apply(this, args);
handlePathChange();
};
window.addEventListener("popstate", handlePathChange);
// 方法执行入口
// 方法执行入口
// 方法执行入口
(async function () {
// 引入Bootstrap Icons
let link = document.createElement("link");
link.rel = "stylesheet";
link.href = "https://cdn.bootcdn.net/ajax/libs/bootstrap-icons/1.10.0/font/bootstrap-icons.css";
document.head.appendChild(link);
handleInPageNotify()
})();