// ==UserScript==
// @name 智慧树共享课全自动刷课(可拖动+精准识别)
// @namespace zhihuishu-share-drag
// @version 10.0.0
// @description 可拖动面板+精准识别共享课+25分钟自动切课+自动答题+弹窗处理
// @author 适配你的页面
// @match *://*.zhihuishu.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_xmlhttpRequest
// @connect tk.enncy.cn
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
// ===================== 基础配置 =====================
const DEFAULT_CONFIG = {
watchTime: 25, // 单课时长(分钟)
playbackRate: 1.25, // 播放倍速
autoSwitch: true, // 自动切课
autoPlay: true, // 自动播放
autoClosePopup: true, // 自动关弹窗
autoAnswer: true, // 自动答题
reviewCompleted: true, // 复看已完成课程
useOnlineBankFirst: true, // 优先言溪题库
};
// 言溪题库配置
const YANXI_BANK_CONFIG = {
url: "https://tk.enncy.cn/query",
token: "0abc866e094c4714936e88a84ae3cb93",
};
// 全局变量
let state = {
currentCourseIndex: 0,
currentTime: 0,
isRunning: false,
totalCourses: 0,
};
let config = { ...DEFAULT_CONFIG };
let allCourseList = []; // 所有课程列表
let selectedCourseList = []; // 选中的课程
let localQuestionBank = {}; // 本地题库
let timer = null;
let popupTimer = null;
let answerTimer = null;
let panel = null;
let isDragging = false; // 拖动状态
let dragOffset = { x: 0, y: 0 }; // 拖动偏移量
// ===================== 存储相关 =====================
function loadAllData() {
try { config = { ...DEFAULT_CONFIG, ...JSON.parse(GM_getValue("zhs_full_config")) } } catch (e) { }
try { state = { ...state, ...JSON.parse(GM_getValue("zhs_full_state")) } } catch (e) { }
try { selectedCourseList = JSON.parse(GM_getValue("zhs_selected_courses")) || [] } catch (e) { }
try { localQuestionBank = JSON.parse(GM_getValue("zhs_local_bank")) || {} } catch (e) { }
}
function saveAllData() {
GM_setValue("zhs_full_config", JSON.stringify(config));
GM_setValue("zhs_full_state", JSON.stringify(state));
GM_setValue("zhs_selected_courses", JSON.stringify(selectedCourseList));
GM_setValue("zhs_local_bank", JSON.stringify(localQuestionBank));
}
// ===================== 【核心修复】精准识别你的共享课 =====================
function getAllCourseCards() {
let courseList = [];
// ----------------===== 【你的页面专属】精准匹配共享课 =====----------------
// 完全适配你截图里的DOM结构:.datalist dl 就是每一个课程卡片
const shareCourseSelectors = [
".datalist dl", // 你的共享课核心容器
".sharing-new-style .datalist dl",
".course-list dl",
".shared-course-wrap dl",
];
// 遍历所有选择器,抓取课程
for (const selector of shareCourseSelectors) {
const elements = document.querySelectorAll(selector);
elements.forEach((el, index) => {
// 提取课程标题(过滤掉老师、进度、无关文字)
const titleEl = el.querySelector("h3, .course-title, .course-name, dt");
let title = "";
if (titleEl) {
title = titleEl.textContent.trim().replace(/\s+/g, " ");
} else {
// 兜底:提取纯课程名,过滤进度、老师信息
const fullText = el.innerText.trim().replace(/\s+/g, " ");
title = fullText.split("在学:")[0].split("进度:")[0].trim();
}
// 过滤无效内容
if (!title || title.length < 4 || /登录|注册|客服|帮助/.test(title)) return;
// 去重
if (courseList.some(item => item.title === title)) return;
// 加入课程列表
courseList.push({
id: btoa(title + index), // 唯一ID
index: index,
title: title,
element: el // 课程DOM元素,用于点击进入
});
});
}
allCourseList = courseList;
return courseList;
}
// ===================== 【核心新增】面板拖动功能 =====================
function initDrag() {
const header = panel.querySelector(".panel-header");
if (!header) return;
// 鼠标按下:开始拖动
header.addEventListener("mousedown", (e) => {
isDragging = true;
// 计算鼠标和面板左上角的偏移量
const rect = panel.getBoundingClientRect();
dragOffset.x = e.clientX - rect.left;
dragOffset.y = e.clientY - rect.top;
// 选中文字不影响拖动
e.preventDefault();
});
// 鼠标移动:拖动面板
document.addEventListener("mousemove", (e) => {
if (!isDragging) return;
// 计算新位置
let newLeft = e.clientX - dragOffset.x;
let newTop = e.clientY - dragOffset.y;
// 边界限制:不能拖出屏幕外
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
const panelWidth = panel.offsetWidth;
const panelHeight = panel.offsetHeight;
// 左右边界
if (newLeft < 0) newLeft = 0;
if (newLeft + panelWidth > windowWidth) newLeft = windowWidth - panelWidth;
// 上下边界
if (newTop < 0) newTop = 0;
if (newTop + panelHeight > windowHeight) newTop = windowHeight - panelHeight;
// 设置新位置
panel.style.left = newLeft + "px";
panel.style.top = newTop + "px";
panel.style.right = "unset"; // 取消原来的right定位
});
// 鼠标松开:结束拖动
document.addEventListener("mouseup", () => {
isDragging = false;
});
// 鼠标离开窗口:结束拖动
document.addEventListener("mouseleave", () => {
isDragging = false;
});
}
// ===================== 面板创建 =====================
function createPanel() {
// 移除已存在的面板
const existingPanel = document.getElementById("zhs-auto-panel");
if (existingPanel) existingPanel.remove();
panel = document.createElement("div");
panel.id = "zhs-auto-panel";
// 初始定位
panel.style.position = "fixed";
panel.style.top = "20px";
panel.style.right = "20px";
panel.style.zIndex = "999999";
panel.innerHTML = `
运行状态:
未运行
当前课程:
0/0
计时:
00:00 / ${config.watchTime}分钟
`;
document.body.appendChild(panel);
// 初始化拖动功能
initDrag();
}
// ===================== 刷新课程列表到面板 =====================
function refreshCourseList() {
const courseList = getAllCourseCards();
const listBox = document.getElementById("courseList");
if (courseList.length === 0) {
listBox.innerHTML = `未找到课程,请确保在共享课列表页
`;
return;
}
// 生成勾选列表
let html = "";
courseList.forEach(course => {
const isChecked = selectedCourseList.some(item => item.id === course.id);
html += `
`;
});
listBox.innerHTML = html;
// 绑定勾选事件
bindCourseCheckEvent();
// 更新状态
state.totalCourses = selectedCourseList.length;
updateUI();
addLog(`成功加载${courseList.length}门共享课`);
}
// 绑定课程勾选事件
function bindCourseCheckEvent() {
document.querySelectorAll("#courseList input[type=checkbox]").forEach(checkbox => {
checkbox.onchange = () => {
const courseId = checkbox.dataset.id;
const course = allCourseList.find(item => item.id === courseId);
if (!course) return;
if (checkbox.checked) {
// 选中,加入列表
if (!selectedCourseList.some(item => item.id === courseId)) {
selectedCourseList.push(course);
}
} else {
// 取消选中,移除
selectedCourseList = selectedCourseList.filter(item => item.id !== courseId);
}
saveAllData();
state.totalCourses = selectedCourseList.length;
updateUI();
addLog(`${checkbox.checked ? "选中" : "取消选中"}:${course.title}`);
};
});
}
// ===================== 刷课核心逻辑 =====================
// 开始刷课
function startWatch() {
if (selectedCourseList.length === 0) {
alert("请先勾选要刷的课程!");
addLog("请先勾选要刷的课程", "error");
return;
}
if (state.isRunning) return;
state.isRunning = true;
state.currentTime = 0;
state.currentCourseIndex = 0;
saveAllData();
updateUI();
addLog("开始自动刷课", "success");
// 打开第一门课
openTargetCourse();
}
// 暂停刷课
function stopWatch() {
state.isRunning = false;
saveAllData();
updateUI();
clearAllTimer();
addLog("已暂停自动刷课");
}
// 打开目标课程
function openTargetCourse() {
if (!state.isRunning) return;
if (state.currentCourseIndex >= selectedCourseList.length) {
addLog("全部课程刷完!", "success");
alert("✅ 全部选中的课程已刷完!");
stopWatch();
return;
}
const targetCourse = selectedCourseList[state.currentCourseIndex];
if (!targetCourse || !targetCourse.element) {
addLog("未找到目标课程", "error");
stopWatch();
return;
}
// 点击进入课程
targetCourse.element.click();
addLog(`已打开课程:${targetCourse.title}`, "success");
updateUI();
// 进入课程后,启动计时
setTimeout(() => {
startTimer();
setupVideo();
}, 2000);
}
// 计时核心
function startTimer() {
clearInterval(timer);
timer = setInterval(() => {
if (!state.isRunning) return;
state.currentTime++;
saveAllData();
updateUI();
// 时长到了,切课
if (state.currentTime >= config.watchTime * 60) {
clearInterval(timer);
addLog(`【${selectedCourseList[state.currentCourseIndex].title}】时长已满`, "success");
state.currentTime = 0;
state.currentCourseIndex++;
saveAllData();
// 返回课程列表,打开下一门
backToCourseList();
setTimeout(() => {
openTargetCourse();
}, 3000 + Math.random() * 1000);
}
}, 1000);
}
// 返回课程列表
function backToCourseList() {
const backBtn = document.querySelector('a:has-text("返回学堂"), .back-btn, a[href*="onlinestuh5"], .header-left a');
if (backBtn && backBtn.offsetParent !== null) {
backBtn.click();
addLog("已点击「返回学堂」,回到课程列表", "success");
} else {
addLog("未找到返回按钮,手动跳转", "error");
window.location.href = "https://onlineweb.zhihuishu.com/onlinestuh5";
}
}
// 视频设置
function setupVideo() {
let attempts = 0;
const detectTimer = setInterval(() => {
const video = document.querySelector("video");
if (video) {
clearInterval(detectTimer);
video.playbackRate = config.playbackRate;
if (config.autoPlay) {
video.play().catch(() => {
addLog("自动播放失败,请手动点击播放", "error");
});
}
addLog("视频设置完成,已自动播放", "success");
// 视频结束自动续播下一节
video.addEventListener("ended", () => {
addLog("当前小节播放结束,自动播放下一节", "success");
setTimeout(() => {
const nextBtn = document.querySelector(".next-btn, .nav-next, .catalog-item.active + .catalog-item");
if (nextBtn) nextBtn.click();
}, 1500);
});
} else if (attempts > 30) {
clearInterval(detectTimer);
addLog("未找到视频元素", "error");
}
attempts++;
}, 500);
}
// ===================== 自动答题&弹窗处理 =====================
// 弹窗监控
function startPopupMonitor() {
if (popupTimer) clearInterval(popupTimer);
popupTimer = setInterval(() => {
if (!config.autoClosePopup) return;
// 挂机验证弹窗
const continueBtns = document.querySelectorAll('.dialog-footer .btn, .vjs-modal-dialog .vjs-close-button, .continue-btn, .confirm-btn, button[title="继续播放"]');
continueBtns.forEach(btn => {
if (btn.offsetParent !== null) {
btn.click();
addLog("已自动关闭挂机验证弹窗", "success");
}
});
// 普通关闭弹窗
const closeBtns = document.querySelectorAll('.close, .btn-close, .modal-close, .dialog-close, [class*="close"]');
closeBtns.forEach(btn => {
if (btn.offsetParent !== null && !btn.closest(".video-js")) {
btn.click();
addLog("已自动关闭弹窗", "success");
}
});
window.onbeforeunload = null;
}, 2000);
}
// 自动答题监控
function startAnswerMonitor() {
if (answerTimer) clearInterval(answerTimer);
answerTimer = setInterval(() => {
if (!config.autoAnswer) return;
const popupSelectors = [
'.question-modal', '.test-popup', '.dialog-box:has(.question-stem)',
'.el-dialog:has(.question-title)', '.popupsbox:has(.option-item)'
];
let answerPopup = null;
for (const sel of popupSelectors) {
const el = document.querySelector(sel);
if (el && el.offsetParent !== null) {
answerPopup = el;
break;
}
}
if (answerPopup) handleAnswerPopup(answerPopup);
}, 1000);
}
// 处理答题
async function handleAnswerPopup(popup) {
if (popup.dataset.processed === "true") return;
popup.dataset.processed = "true";
// 提取题干
const stemEl = popup.querySelector('.question-stem, .question-title, .stem, .title h3');
const stem = stemEl ? stemEl.textContent.trim().replace(/\s+/g, "") : "";
if (!stem) {
popup.dataset.processed = "false";
return;
}
addLog(`识别到题目:${stem}`);
// 提取选项
const optionEls = Array.from(popup.querySelectorAll('.option-item, .answer-option, .option, .el-radio, .el-checkbox')).filter(el => el.offsetParent !== null);
if (optionEls.length === 0) {
popup.dataset.processed = "false";
return;
}
const optionList = optionEls.map(el => {
const textEl = el.querySelector('.option-text, .label, span') || el;
return { el: el, text: textEl.textContent.trim().replace(/\s+/g, "") };
});
const optionTextList = optionList.map(opt => opt.text);
const isMulti = popup.querySelector('.el-checkbox') !== null;
const questionType = isMulti ? "multi" : "single";
// 真人延迟
await new Promise(r => setTimeout(r, 1000 + Math.random() * 1000));
// 在线搜题
let correctAnswers = [];
if (config.useOnlineBankFirst) {
correctAnswers = await searchYanxiBank(stem, optionTextList, questionType);
}
// 本地题库兜底
if (correctAnswers.length === 0) {
for (const key in localQuestionBank) {
const cleanKey = key.replace(/\s+/g, "");
if (stem.includes(cleanKey) || cleanKey.includes(stem)) {
correctAnswers = localQuestionBank[key];
addLog(`本地题库匹配成功`, "success");
break;
}
}
}
// 随机选择
let selected = [];
if (correctAnswers.length > 0) {
correctAnswers.forEach(answer => {
const cleanAnswer = answer.replace(/\s+/g, "");
const target = optionList.find(opt => opt.text.includes(cleanAnswer) || cleanAnswer.includes(opt.text));
if (target) {
target.el.click();
selected.push(target.text);
}
});
} else {
addLog("无匹配答案,随机选择", "info");
const randomCount = isMulti ? Math.floor(Math.random() * optionList.length) + 1 : 1;
const randomOptions = [...optionList].sort(() => 0.5 - Math.random()).slice(0, randomCount);
randomOptions.forEach(opt => {
opt.el.click();
selected.push(opt.text);
});
}
addLog(`已选择:${selected.join("、")}`);
// 提交答案
await new Promise(r => setTimeout(r, 800 + Math.random() * 500));
const submitBtns = popup.querySelectorAll('.submit-btn, .confirm-btn, .el-button--primary, button:has-text("确定"), button:has-text("提交")');
submitBtns.forEach(btn => {
if (btn.offsetParent !== null) btn.click();
});
addLog("已提交答案,关闭弹窗", "success");
setTimeout(() => {
popup.dataset.processed = "false";
}, 2000);
}
// 言溪题库搜题
async function searchYanxiBank(title, options, type) {
return new Promise((resolve) => {
const queryParams = new URLSearchParams({
token: YANXI_BANK_CONFIG.token,
title: title.trim(),
options: options.join("\n"),
type: type
});
GM_xmlhttpRequest({
method: "GET",
url: `${YANXI_BANK_CONFIG.url}?${queryParams.toString()}`,
timeout: 5000,
onload: (res) => {
try {
const result = JSON.parse(res.responseText);
if (result.code === 0 && result.data?.answer) {
let answer = result.data.answer;
answer = Array.isArray(answer) ? answer : [answer.toString().trim()];
addLog(`【言溪题库】搜题成功`, "success");
resolve(answer);
} else {
resolve([]);
}
} catch (e) {
resolve([]);
}
},
onerror: () => resolve([]),
ontimeout: () => resolve([])
});
});
}
// ===================== 工具函数 =====================
// 清空所有定时器
function clearAllTimer() {
if (timer) clearInterval(timer);
if (popupTimer) clearInterval(popupTimer);
if (answerTimer) clearInterval(answerTimer);
}
// 更新UI
function updateUI() {
const statusText = document.getElementById("statusText");
const courseText = document.getElementById("courseText");
const timeText = document.getElementById("timeText");
const progressFill = document.getElementById("progressFill");
if (statusText) statusText.textContent = state.isRunning ? "运行中" : "已停止";
if (courseText) courseText.textContent = `${state.currentCourseIndex + 1}/${state.totalCourses}`;
if (timeText) timeText.textContent = `${formatTime(state.currentTime)} / ${config.watchTime}分钟`;
if (progressFill) progressFill.style.width = `${Math.min((state.currentTime / (config.watchTime * 60)) * 100, 100)}%`;
}
// 格式化时间
function formatTime(seconds) {
const m = Math.floor(seconds / 60).toString().padStart(2, "0");
const s = (seconds % 60).toString().padStart(2, "0");
return `${m}:${s}`;
}
// 日志
function addLog(msg, type = "info") {
console.log(`[智慧树助手] ${msg}`);
}
// 防检测真人模拟
function startHumanSimulate() {
setInterval(() => {
if (!state.isRunning) return;
document.dispatchEvent(new MouseEvent("mousemove", {
clientX: Math.random() * 1000 + 200,
clientY: Math.random() * 600 + 100
}));
window.scrollBy(0, Math.random() > 0.5 ? 10 : -10);
}, 30000 + Math.random() * 20000);
}
// ===================== 事件绑定 =====================
function bindEvents() {
// 刷新课程按钮
document.getElementById("refreshBtn").onclick = refreshCourseList;
// 开始按钮
document.getElementById("startBtn").onclick = startWatch;
// 暂停按钮
document.getElementById("stopBtn").onclick = stopWatch;
}
// ===================== 初始化启动 =====================
function init() {
loadAllData();
createPanel();
bindEvents();
startPopupMonitor();
startAnswerMonitor();
startHumanSimulate();
// 延迟加载课程,等待页面渲染完成
setTimeout(() => {
refreshCourseList();
}, 1500);
}
// 页面加载完成后启动
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();