// ==UserScript==
// @name 智慧树刷课助手
// @namespace https://github.com/CXRunfree/Autovisor
// @version 1.1.0
// @description 智慧树网课自动播放、自动切换下一集、跳过弹窗和弹题、自动静音、倍速播放、检测暂停续播、刷习惯分、支持智慧共享课和翻转课、考试自动答题
// @author 叶屿
// @license GPL3
// @antifeature payment 题库答题功能需要激活码(付费),视频播放等基础功能完全免费
// @match *://passport.zhihuishu.com/*
// @match *://studyh5.zhihuishu.com/*
// @match *://studyvideoh5.zhihuishu.com/*
// @match *://hike.zhihuishu.com/*
// @match *://www.zhihuishu.com/*
// @match *://studyservice-api.zhihuishu.com/*
// @match *://examh5.zhihuishu.com/*
// @match *://onlineexamh5new.zhihuishu.com/*
// @match *://studentexambaseh5.zhihuishu.com/*
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @connect qsy.iano.cn
// @connect lyck6.cn
// @connect *
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
/*
* ==========================================
* 免责声明 / Disclaimer
* ==========================================
*
* 本脚本仅供个人学习和技术研究使用,请勿用于任何违反
* 学校规章制度、考试纪律或法律法规的行为。
*
* 使用本脚本即表示您同意以下条款:
* 1. 本脚本不鼓励、不支持任何形式的考试作弊行为
* 2. 使用者应自行承担使用本脚本产生的一切后果
* 3. 作者不对因使用本脚本而导致的任何直接或间接
* 损失(包括但不限于学业处分、账号封禁等)承担责任
* 4. 本脚本不保证题库答案的准确性和完整性
* 5. 请遵守《中华人民共和国教育法》及相关法律法规
* 6. 请遵守所在学校的学术诚信规范
*
* 如您不同意以上条款,请立即停止使用并删除本脚本。
*
* This script is for personal learning and technical
* research purposes only. The author assumes no
* responsibility for any consequences.
* ==========================================
*/
// ===================== 配置区 =====================
const DEFAULT_CONFIG = {
playbackSpeed: 1.5, // 播放倍速 (0.5 ~ 1.8)
soundOff: true, // 是否静音
autoNext: true, // 自动切换下一集
autoAnswer: true, // 自动跳过弹题
autoPlay: true, // 自动播放 & 暂停检测
habitScore: true, // 刷习惯分(模拟鼠标移动)
autoExam: false, // 考试自动答题(默认关闭,手动点击搜题)
examAutoFill: false, // 考试自动填入答案(默认关闭,需手动确认)
showPanel: true, // 显示悬浮面板
};
// 从存储读取或使用默认配置
function loadConfig() {
const saved = GM_getValue('autovisor_config', null);
if (saved) {
return Object.assign({}, DEFAULT_CONFIG, saved);
}
return Object.assign({}, DEFAULT_CONFIG);
}
function saveConfig(cfg) {
GM_setValue('autovisor_config', cfg);
}
let config = loadConfig();
// ===================== 日志系统 =====================
const LOG_PREFIX = '[智慧树助手]';
const log = {
info: (msg) => console.log(`%c${LOG_PREFIX} ${msg}`, 'color:#409EFF;font-weight:bold;'),
warn: (msg) => console.warn(`%c${LOG_PREFIX} ${msg}`, 'color:#E6A23C;font-weight:bold;'),
error: (msg) => console.error(`%c${LOG_PREFIX} ${msg}`, 'color:#F56C6C;font-weight:bold;'),
success: (msg) => console.log(`%c${LOG_PREFIX} ${msg}`, 'color:#67C23A;font-weight:bold;'),
};
// ===================== 工具函数 =====================
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function $(selector) {
return document.querySelector(selector);
}
function $$(selector) {
return document.querySelectorAll(selector);
}
function safeClick(selector) {
const el = typeof selector === 'string' ? $(selector) : selector;
if (el) {
el.click();
return true;
}
return false;
}
function safeRemove(selector) {
const el = $(selector);
if (el) {
el.remove();
return true;
}
return false;
}
// 判断页面类型
function isNewVersion() {
return location.href.includes('fusioncourseh5');
}
function isHikeClass() {
return location.href.includes('hike.zhihuishu.com');
}
function isStudyPage() {
return location.href.includes('studyh5.zhihuishu.com') ||
location.href.includes('studyvideoh5.zhihuishu.com') ||
location.href.includes('hike.zhihuishu.com');
}
function isExamPage() {
const url = location.href;
// 只匹配实际做题页面,不匹配作业/考试列表页
return url.includes('examh5.zhihuishu.com') ||
url.includes('onlineexamh5new.zhihuishu.com') ||
url.includes('studentexambaseh5.zhihuishu.com') ||
url.includes('/stuExamWeb.html') ||
url.includes('/webExamList/dohomework/') ||
url.includes('/webExamList/doexamination/') ||
// 通用: 页面内存在题目容器(延迟检测)
(url.includes('zhihuishu.com') && !!document.querySelector('.examPaper_subject, .questionBox, .ques-detail'));
}
// ===================== 反检测模块 =====================
// 温和策略:只拦截弹窗DOM,不修改原型链,不拦截XHR
function hookAntiCheat() {
const KEYWORDS = ['检测到异常脚本', '关闭插件', '异常行为'];
// 1. MutationObserver 监听弹窗插入
const target = document.body || document.documentElement;
if (!target) return;
const observer = new MutationObserver((mutations) => {
for (const m of mutations) {
for (const node of m.addedNodes) {
if (node.nodeType !== 1) continue;
const text = (node.textContent || '').slice(0, 200);
if (KEYWORDS.some(kw => text.includes(kw))) {
node.style.display = 'none';
node.style.visibility = 'hidden';
node.style.opacity = '0';
node.style.pointerEvents = 'none';
try { node.remove(); } catch {}
log.info('[反检测] 已拦截异常脚本弹窗');
}
}
}
});
observer.observe(target, { childList: true, subtree: true });
// 2. 定时扫描关闭已有弹窗(只针对特定关键词的弹窗)
setInterval(() => {
document.querySelectorAll('.el-dialog, .el-message-box, [class*="modal"]').forEach(el => {
const t = (el.textContent || '').slice(0, 300);
if (KEYWORDS.some(kw => t.includes(kw))) {
el.style.display = 'none';
try { el.remove(); } catch {}
// 同时移除对应遮罩
const overlay = el.previousElementSibling;
if (overlay && (overlay.classList.contains('el-overlay') || overlay.classList.contains('v-modal'))) {
overlay.style.display = 'none';
try { overlay.remove(); } catch {}
}
log.info('[反检测] 已清除异常检测弹窗');
}
});
}, 3000);
log.info('[反检测] 反检测模块已激活(温和模式)');
}
// ===================== 核心功能模块 =====================
// --- 获取视频元素 ---
function getVideo() {
return $('video');
}
// --- 页面优化:关闭弹窗、移除多余元素 ---
async function optimizePage() {
await sleep(2000);
// 关闭学习时长弹窗
safeClick('.iconfont.iconguanbi');
if (!isNewVersion() && !isHikeClass()) {
// 自动夜间模式 (18:00 ~ 7:00)
const hour = new Date().getHours();
if (hour >= 18 || hour < 7) {
const darkBtn = $('.Patternbtn-div');
if (darkBtn) darkBtn.click();
}
// 移除无关元素
safeRemove('.exploreTip');
safeRemove('.ai-helper-Index2');
safeRemove('.aiMsg.once');
}
log.info('页面优化完成');
}
// --- 覆盖视频pause方法,防止被网页暂停 ---
function overridePause() {
const video = getVideo();
if (video && !video._pauseOverridden) {
const origPause = video.pause.bind(video);
video.pause = () => {
// 只允许用户主动暂停(如点击暂停按钮),忽略脚本检测触发的暂停
};
video._pauseOverridden = true;
log.info('已覆盖视频 pause 方法');
}
}
// --- 静音控制 ---
function setMute() {
if (!config.soundOff) return;
const video = getVideo();
if (video && video.volume !== 0) {
video.volume = 0;
const volumeBox = $('.volumeBox');
if (volumeBox) volumeBox.classList.add('volumeNone');
log.info('已静音');
}
}
// --- 倍速控制 ---
function setSpeed() {
const video = getVideo();
if (!video) return;
const speed = Math.max(0.5, Math.min(config.playbackSpeed, 1.8));
if (video.playbackRate !== speed) {
video.playbackRate = speed;
const speedLabel = $('.speedBox span');
if (speedLabel) speedLabel.innerText = `X ${speed}`;
log.info(`已设置倍速: ${speed}x`);
}
}
// --- 检测暂停并续播 ---
async function checkAndPlay() {
if (!config.autoPlay) return;
const video = getVideo();
if (video && video.paused) {
try {
await video.play();
log.info('检测到视频暂停,已恢复播放');
} catch (e) {
// 自动播放可能被浏览器策略阻止
log.warn('自动播放被阻止,请手动点击播放');
}
}
}
// --- 跳过弹题 ---
async function skipQuestions() {
if (!config.autoAnswer) return;
if (isHikeClass()) return; // 翻转课不支持自动答题
try {
const quesContainer = $('.el-scrollbar__view');
if (!quesContainer) return;
const questions = quesContainer.querySelectorAll('.number');
if (questions.length === 0) return;
log.info(`检测到 ${questions.length} 道弹题`);
for (const ques of questions) {
ques.click();
await sleep(300);
// 如果没有已选答案,随机选前两个
if (!$('.answer')) {
const choices = $$('.topic-item');
for (let i = 0; i < Math.min(2, choices.length); i++) {
choices[i].click();
await sleep(150);
}
}
}
// 按 ESC 关闭弹题对话框
await sleep(200);
const dialog = $('.el-dialog');
if (dialog) {
document.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, keyCode: 27 }));
log.success('弹题已跳过');
}
} catch (e) {
log.warn('跳过弹题时出错: ' + e.message);
}
}
// --- 关闭其他弹窗 ---
function closePopups() {
// 新版共享课弹窗
if (isNewVersion()) {
const dialog = $('.el-dialog');
if (dialog) {
document.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, keyCode: 27 }));
}
} else if (!isHikeClass()) {
// 旧版弹窗
safeClick('.el-message-box__headerbtn');
}
// 通用弹窗关闭
safeClick('.iconfont.iconguanbi');
}
// --- 检测人机验证 ---
function checkVerify() {
const verifyModal = $('.yidun_modal__title');
if (verifyModal) {
log.warn('⚠️ 检测到安全验证,请手动完成验证!');
updatePanelStatus('⚠️ 需要手动验证!');
return true;
}
return false;
}
// --- 习惯分:模拟鼠标在视频区域移动 ---
function simulateMouseMove() {
if (!config.habitScore) return;
const videoArea = $('.videoArea') || getVideo();
if (!videoArea) return;
const rect = videoArea.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return;
const x = rect.left + rect.width / 2 + (Math.random() - 0.5) * 20;
const y = rect.top + rect.height / 2 + (Math.random() - 0.5) * 20;
videoArea.dispatchEvent(new MouseEvent('mousemove', {
bubbles: true,
clientX: x,
clientY: y,
}));
}
// --- 获取当前学习进度 ---
function getCourseProgress() {
if (isHikeClass()) {
const active = $('.file-item.active');
if (!active) return '0%';
const rate = active.querySelector('.rate');
if (rate) return rate.textContent || '0%';
const finish = active.querySelector('.icon-finish');
return finish ? '100%' : '0%';
} else {
const curPlay = $('.current_play');
if (!curPlay) return '0%';
const progress = curPlay.querySelector('.progress-num');
if (progress) return progress.textContent || '0%';
if (isNewVersion()) {
const pn = curPlay.querySelector('.progress-num');
if (pn && pn.textContent === '100%') return '100%';
} else {
const finish = curPlay.querySelector('.time_icofinish');
if (finish) return '100%';
}
return '0%';
}
}
// --- 获取未完成的课程列表 ---
function getUnfinishedLessons() {
if (isHikeClass()) {
const all = $$('.file-item');
const unfinished = [];
all.forEach(item => {
const done = item.querySelector('.icon-finish');
if (!done) unfinished.push(item);
});
return unfinished.length > 0 ? unfinished : Array.from(all);
} else {
const all = $$('.clearfix.video');
const unfinished = [];
all.forEach(item => {
if (isNewVersion()) {
const pn = item.querySelector('.progress-num');
if (!pn || pn.textContent !== '100%') unfinished.push(item);
} else {
const done = item.querySelector('.time_icofinish');
if (!done) unfinished.push(item);
}
});
return unfinished.length > 0 ? unfinished : Array.from(all);
}
}
// --- 切换到下一集 ---
async function switchToNext() {
if (!config.autoNext) return;
const progress = getCourseProgress();
if (progress !== '100%') return;
log.success('当前课程已完成,正在切换下一集...');
updatePanelStatus('🔄 正在切换下一集...');
const lessons = getUnfinishedLessons();
if (lessons.length === 0) {
log.success('🎉 所有课程已学习完毕!');
updatePanelStatus('🎉 全部完成!');
stopAllTasks();
return;
}
// 找到当前课程的下一个
if (isHikeClass()) {
const active = $('.file-item.active');
const allItems = Array.from($$('.file-item'));
const curIdx = allItems.indexOf(active);
const nextIdx = curIdx + 1;
if (nextIdx < allItems.length) {
allItems[nextIdx].click();
log.info('已切换到下一集');
await sleep(2000);
afterLessonSwitch();
} else {
log.success('🎉 所有课程已学习完毕!');
updatePanelStatus('🎉 全部完成!');
stopAllTasks();
}
} else {
const curPlay = $('.current_play');
const allItems = Array.from($$('.clearfix.video'));
const curIdx = allItems.indexOf(curPlay);
const nextIdx = curIdx + 1;
if (nextIdx < allItems.length) {
allItems[nextIdx].click();
log.info('已切换到下一集');
await sleep(2000);
afterLessonSwitch();
} else {
log.success('🎉 所有课程已学习完毕!');
updatePanelStatus('🎉 全部完成!');
stopAllTasks();
}
}
}
// 切换课程后重新初始化
function afterLessonSwitch() {
overridePause();
setMute();
setSpeed();
const title = getLessonTitle();
log.info(`正在学习: ${title}`);
updatePanelStatus(`▶️ ${title}`);
}
// 获取当前课程名称
function getLessonTitle() {
if (isHikeClass()) {
const span = $('.file-item.active span[title]');
return span ? span.getAttribute('title') : '未知课程';
} else {
const el = $('#lessonOrder');
return el ? el.getAttribute('title') : '未知课程';
}
}
// ===================== 定时任务管理 =====================
let timers = [];
function addTimer(fn, interval) {
const id = setInterval(fn, interval);
timers.push(id);
return id;
}
function stopAllTasks() {
timers.forEach(id => clearInterval(id));
timers = [];
log.info('所有任务已停止');
}
// ===================== 悬浮控制面板 =====================
let panelStatusEl = null;
let panelProgressEl = null;
function createPanel() {
if (!config.showPanel) return;
if ($('#autovisor-panel')) return;
const panel = document.createElement('div');
panel.id = 'autovisor-panel';
panel.innerHTML = `
⚡ 智慧树刷课助手
▼
倍速
${config.playbackSpeed}x
静音
自动播放
自动下一集
跳过弹题
习惯分
进度
--
🚀 脚本运行中...
`;
document.body.appendChild(panel);
panelStatusEl = $('#av-status');
panelProgressEl = $('#av-progress');
// 收起/展开
$('#av-collapse').addEventListener('click', () => {
panel.classList.toggle('collapsed');
$('#av-collapse').textContent = panel.classList.contains('collapsed') ? '▶' : '▼';
});
// 倍速控制
$('#av-speed-down').addEventListener('click', () => {
config.playbackSpeed = Math.max(0.5, +(config.playbackSpeed - 0.25).toFixed(2));
saveConfig(config);
setSpeed();
$('#av-speed-val').textContent = config.playbackSpeed + 'x';
});
$('#av-speed-up').addEventListener('click', () => {
config.playbackSpeed = Math.min(1.8, +(config.playbackSpeed + 0.25).toFixed(2));
saveConfig(config);
setSpeed();
$('#av-speed-val').textContent = config.playbackSpeed + 'x';
});
// 静音切换
$('#av-mute-btn').addEventListener('click', () => {
config.soundOff = !config.soundOff;
saveConfig(config);
const video = getVideo();
if (video) {
video.volume = config.soundOff ? 0 : 0.5;
const volumeBox = $('.volumeBox');
if (volumeBox) {
if (config.soundOff) volumeBox.classList.add('volumeNone');
else volumeBox.classList.remove('volumeNone');
}
}
$('#av-mute-btn').textContent = config.soundOff ? '🔇 已静音' : '🔊 有声音';
});
// 开关切换
function toggleBtn(key, btnId, label) {
$(btnId).addEventListener('click', () => {
config[key] = !config[key];
saveConfig(config);
$(btnId).textContent = config[key] ? '✅ 开启' : '❌ 关闭';
});
}
toggleBtn('autoPlay', '#av-autoplay-btn');
toggleBtn('autoNext', '#av-autonext-btn');
toggleBtn('autoAnswer', '#av-autoanswer-btn');
toggleBtn('habitScore', '#av-habit-btn');
// 拖拽
makeDraggable(panel, $('#av-drag-handle'));
}
function makeDraggable(el, handle) {
let offsetX = 0, offsetY = 0, isDragging = false;
handle.addEventListener('mousedown', (e) => {
isDragging = true;
offsetX = e.clientX - el.getBoundingClientRect().left;
offsetY = e.clientY - el.getBoundingClientRect().top;
e.preventDefault();
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
el.style.left = (e.clientX - offsetX) + 'px';
el.style.top = (e.clientY - offsetY) + 'px';
el.style.right = 'auto';
});
document.addEventListener('mouseup', () => { isDragging = false; });
}
function updatePanelStatus(text) {
if (panelStatusEl) panelStatusEl.textContent = text;
}
function updatePanelProgress(text) {
if (panelProgressEl) panelProgressEl.textContent = text;
}
// ===================== 考试答题模块 =====================
// 对接 qsy.iano.cn 中转API + lyck6.cn 免费题库
// 借鉴万能脚本的选择器与答案匹配逻辑
// --- 设备ID ---
function getDeviceId() {
let id = GM_getValue('av_device_id', '');
if (!id) {
id = 'av_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
GM_setValue('av_device_id', id);
}
return id;
}
// --- 文本格式化 (借鉴万能脚本 formatString) ---
function formatString(src) {
src = String(src);
// HTML 实体解码
if (!src.includes('img') && !src.includes('iframe')) {
const tmp = document.createElement('div');
tmp.innerHTML = src;
src = tmp.textContent || tmp.innerText || '';
}
// 全角转半角
src = src.replace(/[\uff01-\uff5e]/g, s => String.fromCharCode(s.charCodeAt(0) - 65248));
return src.replace(/\s+/g, ' ').replace(/[""]/g, '"').replace(/['']/g, "'")
.replace(/。/g, '.').replace(/[,.?:!;]$/, '').trim();
}
// 判断题匹配 (借鉴万能脚本)
function isTrue(str) {
return Boolean(String(str).match(/(^|,)(正确|是|对|√|T|ri|true|A)(,|$)/));
}
function isFalse(str) {
return Boolean(String(str).match(/(^|,)(错误|否|错|×|F|不是|wr|false|B)(,|$)/));
}
function isPlainAnswer(answer) {
if (!answer || answer.length > 8 || !/[A-Z]/.test(answer)) return false;
let min = 0;
for (let i = 0; i < answer.length; i++) {
if (answer.charCodeAt(i) < min) return false;
min = answer.charCodeAt(i);
}
return true;
}
// 答案文本相似度 (简化版)
function answerSimilar(answer, options) {
return options.map(opt => {
const o = opt.replace(/\s/g, '');
const a = answer.replace(/\s/g, '');
if (o === a) return 100;
if (o.includes(a) || a.includes(o)) return 85;
let match = 0;
const shorter = a.length < o.length ? a : o;
const longer = a.length < o.length ? o : a;
for (let i = 0; i < shorter.length; i++) {
if (longer.includes(shorter[i])) match++;
}
return Math.round(match / longer.length * 100);
});
}
// --- 题型检测 (万能脚本 getQuestionType) ---
function getQuestionType(typeText) {
const t = String(typeText).trim();
if (/单选|单项选择|radio/i.test(t)) return 0;
if (/多选|多项选择|checkbox|不定项/i.test(t)) return 1;
if (/填空/i.test(t)) return 2;
if (/判断|是非|对错/i.test(t)) return 3;
if (/简答|问答|主观/i.test(t)) return 4;
if (/名词解释/i.test(t)) return 5;
if (/论述/i.test(t)) return 6;
if (/计算/i.test(t)) return 7;
if (/阅读理解|完型填空|听力/i.test(t)) return 8;
return 4; // 默认
}
const TYPE_NAMES = { 0: '单选', 1: '多选', 2: '填空', 3: '判断', 4: '简答', 5: '名词解释', 6: '论述', 7: '计算', 8: '其他' };
// --- 题目提取 (借鉴万能脚本的智慧树选择器) ---
function extractExamQuestions() {
const questions = [];
// 按优先级尝试不同页面版本的选择器
const layouts = [
{ // 智慧树作业/考试 (examPaper)
root: '.examPaper_subject',
question: '.subject_describe div, .smallStem_describe p',
options: '.subject_node .nodeLab .node_detail',
$options: '.subject_node .nodeLab .node_detail',
type: '.subject_type span:first-child',
checked: '.onChecked',
},
{ // 智慧树学分课作业
root: '.questionBox',
question: '.questionContent',
options: '.optionUl label .el-radio__label, .optionUl label .el-checkbox__label',
$options: '.optionUl label',
type: '.questionTit',
checked: '.is-checked',
},
{ // 智慧树学分课考试
root: '.ques-detail',
question: '.questionName .centent-pre',
options: '.radio-view li .preStyle, .checkbox-views label .preStyle',
$options: '.radio-view li, .checkbox-views label',
type: '.letterSortNum',
checked: '.is-checked',
},
{ // 通用回退
root: 'div[class*="subject"], div[class*="question"], div[class*="exam"]',
question: '.subject_describe, .question-title, .ques-title, .topic-title, .stem, h3',
options: '.subject_node, .option-item, .topic-item, label',
$options: '.subject_node, .option-item, .topic-item, label',
type: '.subject_type, .questionTit, .letterSortNum',
checked: '.onChecked, .is-checked',
}
];
let activeLayout = null;
let containers = [];
for (const layout of layouts) {
containers = $$(layout.root);
if (containers.length > 0) { activeLayout = layout; break; }
}
if (!activeLayout || containers.length === 0) {
log.warn('[考试] 未检测到题目容器');
return [];
}
log.info(`[考试] 使用选择器: ${activeLayout.root}, 检测到 ${containers.length} 题`);
// 跳过题型/分值标题的正则
const SKIP_HEADER = /^[\d\s\.\))、]*[【\[((]*[单多判填简论计][选断空答述算]|^\d+[\s\.、)\)]*\(?\d+分\)?$|^第?\d+题|^【.*题】/;
containers.forEach((container, index) => {
// 题目文本 — 跳过题型/分值标题,找到真正的问题
let questionText = '';
const qEls = container.querySelectorAll(activeLayout.question);
for (const qEl of qEls) {
const t = qEl.textContent.trim();
// 跳过:太短、题型标记如"1.【单选题】(2分)"、纯选项字母
if (t.length <= 5) continue;
if (SKIP_HEADER.test(t) && t.length < 30) continue;
if (/^[A-G][\..\s、]/.test(t)) continue;
questionText = formatString(t);
break;
}
// 备用:直接在container里找长文本段落
if (!questionText) {
const ps = container.querySelectorAll('p, span, div');
for (const p of ps) {
const t = p.textContent.trim();
if (t.length <= 8) continue;
if (SKIP_HEADER.test(t) && t.length < 30) continue;
if (/^[A-G][\..\s、]/.test(t)) continue;
questionText = formatString(t);
break;
}
}
// 清理残留的题号和分值
if (questionText) {
questionText = questionText
.replace(/^[\d\s\.\))、]+/, '')
.replace(/\(\s*\d+分\s*\)/g, '')
.replace(/^[【\[((]*[单多判填简论计][选断空答述算][题】\]))]*\s*/g, '')
.trim();
}
if (!questionText || questionText.length < 4) return;
// 题型
const typeEl = container.querySelector(activeLayout.type);
const typeCode = typeEl ? getQuestionType(typeEl.textContent) : 4;
// 选项文本 & 可点击元素
const optTexts = [];
const $optEls = [];
const optTextEls = container.querySelectorAll(activeLayout.options);
const optClickEls = container.querySelectorAll(activeLayout.$options);
const useClickEls = optClickEls.length >= optTextEls.length ? optClickEls : optTextEls;
for (let i = 0; i < useClickEls.length; i++) {
const textSrc = optTextEls[i] || useClickEls[i];
const raw = formatString(textSrc.textContent).replace(/^[A-Za-z][.\s、.]+/, '').trim();
optTexts.push(raw);
$optEls.push(useClickEls[i]);
}
// 已选检测
const isChecked = (el) => {
if (!activeLayout.checked) return false;
return activeLayout.checked.split(',').some(sel => el.matches(sel.trim()) || el.querySelector(sel.trim()));
};
questions.push({
index, el: container, text: questionText,
type: typeCode, options: optTexts,
optionEls: $optEls, isChecked,
});
});
return questions;
}
// --- API调用 ---
function gmRequest(opts) {
return new Promise(resolve => {
const timer = setTimeout(() => resolve(null), opts.timeout || 12000);
try {
GM_xmlhttpRequest({
...opts,
onload: (res) => { clearTimeout(timer); resolve(res); },
onerror: () => { clearTimeout(timer); resolve(null); },
ontimeout: () => { clearTimeout(timer); resolve(null); },
});
} catch { clearTimeout(timer); resolve(null); }
});
}
// 通用答案数组解析
function parseAnswersFromResult(resultObj, options) {
if (!resultObj) return [];
const raw = resultObj.answers || resultObj.answer || [];
const success = resultObj.success !== false;
let answers = [];
if (typeof raw === 'string' && raw.trim()) {
// 单个字符串答案
answers = [raw.trim()];
} else if (Array.isArray(raw) && raw.length > 0) {
if (Array.isArray(raw[0])) {
// 嵌套数组 [[答案1, 答案2]]
answers = raw[0].map(a => String(a));
} else if (typeof raw[0] === 'number') {
// 索引数组 [0, 2] => 对应选项
answers = raw.map(idx => options[idx] || String.fromCharCode(65 + idx));
} else {
answers = raw.map(a => String(a));
}
}
return answers.filter(a => a && a.trim());
}
// 你的中转API (qsy.iano.cn)
async function askPaidAPI(question, options, type) {
const deviceId = getDeviceId();
log.info(`[考试] 发送题目: "${question.slice(0, 40)}..." 选项:${options.length}个`);
const res = await gmRequest({
method: 'POST',
url: 'https://qsy.iano.cn/index.php?s=/api/question_bank/answer',
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({
device_id: deviceId,
question: question,
options: options,
type: type,
location: '智慧树'
}),
timeout: 12000,
});
if (!res) { log.warn('[考试] 付费API无响应'); return null; }
if (res.status !== 200) { log.warn(`[考试] 付费API HTTP ${res.status}`); return null; }
try {
const json = JSON.parse(res.responseText);
log.info(`[考试] 付费API响应: code=${json.code}, msg=${json.msg || ''}`);
if (json.code === 1 && json.data) {
const result = json.data.result || json.data;
const remaining = json.data.remaining_credits || 0;
const answers = parseAnswersFromResult(result, options);
if (answers.length > 0) {
return { answers, remaining, source: '付费题库' };
}
log.info('[考试] 付费API返回结果但无有效答案');
}
if (json.code === 0) {
log.warn(`[考试] 付费API: ${json.msg || '查询失败'}`);
}
} catch (e) { log.warn('[考试] 付费API解析错误: ' + e.message); }
return null;
}
// 免费题库 (lyck6.cn, 与万能脚本同源)
async function askFreeAPI(question, options, type) {
const res = await gmRequest({
method: 'POST',
url: 'http://lyck6.cn/scriptService/api/autoFreeAnswer',
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({
question: question,
options: options,
type: type,
location: location.href
}),
timeout: 15000,
});
if (!res) { log.warn('[考试] 免费API无响应'); return null; }
if (res.status !== 200) { log.warn(`[考试] 免费API HTTP ${res.status}`); return null; }
try {
const json = JSON.parse(res.responseText);
const result = json.result || json.data || json;
const answers = parseAnswersFromResult(result, options);
if (answers.length > 0) {
return { answers, remaining: -1, source: '免费题库' };
}
} catch (e) { log.warn('[考试] 免费API解析错误: ' + e.message); }
return null;
}
// --- 当前题库模式: 'free' 或 'paid' ---
let examMode = GM_getValue('av_exam_mode', 'free');
function setExamMode(mode) {
examMode = mode;
GM_setValue('av_exam_mode', mode);
}
// --- 免费验证码相关 ---
function getVerifyState() {
return GM_getValue('av_verify_state', { validUntil: 0 });
}
function setVerifyState(state) {
GM_setValue('av_verify_state', state);
}
function isVerifyValid() {
const state = getVerifyState();
return state.validUntil && state.validUntil > Date.now() / 1000;
}
async function verifyCode(code) {
const res = await gmRequest({
method: 'POST',
url: 'https://qsy.iano.cn/index.php?s=/api/code/verify',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: `code=${encodeURIComponent(code)}`,
timeout: 10000,
});
if (!res || res.status !== 200) return { ok: false, msg: '网络错误' };
try {
const json = JSON.parse(res.responseText);
if (json.code === 1 && json.data) {
const validUntil = json.data.valid_until || (Date.now() / 1000 + 86400);
const validStr = json.data.valid_until_str || new Date(validUntil * 1000).toLocaleString();
setVerifyState({ validUntil });
return { ok: true, msg: `验证成功,有效期至 ${validStr}` };
}
return { ok: false, msg: json.msg || '验证码无效或已过期' };
} catch { return { ok: false, msg: '解析错误' }; }
}
// 综合搜索: 根据模式选择题库
async function searchAnswer(question, options, type) {
const cleanQ = formatString(question);
if (!cleanQ || cleanQ.length < 4) return null;
const cleanOpts = (options || []).map(o => formatString(o));
if (examMode === 'paid') {
// 付费模式
log.info('[考试] 正在查询付费题库...');
const paidResult = await askPaidAPI(cleanQ, cleanOpts, type);
if (paidResult && paidResult.answers.length > 0) {
log.success(`[考试] 付费题库找到答案 (余${paidResult.remaining}积分)`);
updateCreditsDisplay(paidResult.remaining);
return paidResult;
}
} else {
// 免费模式
if (!isVerifyValid()) {
log.warn('[考试] 免费验证码未验证或已过期,请先验证');
return null;
}
log.info('[考试] 正在查询免费题库...');
const freeResult = await askFreeAPI(cleanQ, cleanOpts, type);
if (freeResult && freeResult.answers.length > 0) {
log.success('[考试] 免费题库找到答案');
return freeResult;
}
}
return null;
}
// --- 智能答案匹配 & 填入 (借鉴万能脚本) ---
function matchAndFill(q, result) {
if (!result || !result.answers || result.answers.length === 0) return false;
const answers = result.answers;
let filled = false;
if (q.type === 3) {
// 判断题
const ansJoined = answers.join(',');
for (let i = 0; i < q.optionEls.length; i++) {
const shouldClick =
(isTrue(ansJoined) && isFalse(q.options[i])) ? false :
(isTrue(ansJoined) && !isFalse(q.options[i])) ? true :
(isFalse(ansJoined) && isTrue(q.options[i])) ? false :
(isFalse(ansJoined) && !isTrue(q.options[i])) ? true : false;
// 更简单的逻辑: 如果答案为"对",点包含"对/正确/√"的选项
const optText = q.options[i] || '';
let match = false;
if (isTrue(ansJoined)) match = isTrue(optText) || /对|正确|√|是|true/i.test(optText);
else if (isFalse(ansJoined)) match = isFalse(optText) || /错|错误|×|否|false/i.test(optText);
if (match && !q.isChecked(q.optionEls[i])) {
q.optionEls[i].click();
filled = true;
}
}
} else if (q.type === 2 || q.type === 4 || q.type === 5 || q.type === 6 || q.type === 7) {
// 填空题/简答/论述等: 填入 textarea/input
const inputs = q.el.querySelectorAll('input[type="text"], textarea');
const editorNames = q.el.querySelectorAll('textarea[name]');
if (editorNames.length > 0) {
// 尝试 UEditor
editorNames.forEach((ta, i) => {
const val = answers[i] || answers[0] || '';
try {
if (typeof UE !== 'undefined' && UE.getEditor) {
UE.getEditor(ta.getAttribute('name')).setContent(val);
filled = true;
}
} catch { }
});
}
if (!filled && inputs.length > 0) {
inputs.forEach((inp, i) => {
const val = answers[i] || answers[0] || '';
inp.value = val.trim();
inp.dispatchEvent(new Event('input', { bubbles: true }));
inp.dispatchEvent(new Event('change', { bubbles: true }));
});
filled = inputs.length > 0;
}
} else {
// 单选(0)/多选(1)/其他选择题
const beautifulOptions = q.options.map(o => formatString(o).toLowerCase().replace(/\s/g, ''));
let targetIndices = new Set();
for (const ans of answers) {
// 1. 字母匹配 (A/B/C...)
if (ans.length === 1 && isPlainAnswer(ans.toUpperCase())) {
targetIndices.add(ans.toUpperCase().charCodeAt(0) - 65);
continue;
}
// 2. 精确文本匹配
const val = formatString(ans).toLowerCase().replace(/\s/g, '');
const exactIdx = beautifulOptions.indexOf(val);
if (exactIdx >= 0) {
targetIndices.add(exactIdx);
continue;
}
// 3. 模糊匹配
if (val.length >= 5) {
const ratings = answerSimilar(val, beautifulOptions);
const maxScore = Math.max(...ratings);
if (maxScore > 65) {
targetIndices.add(ratings.indexOf(maxScore));
}
}
}
for (const idx of targetIndices) {
if (idx >= 0 && idx < q.optionEls.length && !q.isChecked(q.optionEls[idx])) {
q.optionEls[idx].click();
filled = true;
}
}
}
return filled;
}
// --- 积分管理 ---
function updateCreditsDisplay(credits) {
const el = $('#ae-credits');
if (el) el.textContent = credits >= 0 ? credits : '--';
}
async function queryCredits() {
const res = await gmRequest({
method: 'GET',
url: `https://qsy.iano.cn/index.php?s=/api/question_bank/credits&device_id=${encodeURIComponent(getDeviceId())}`,
timeout: 8000,
});
if (!res || res.status !== 200) return 0;
try {
const json = JSON.parse(res.responseText);
if (json.code === 1 && json.data) return json.data.remaining_credits || 0;
} catch { }
return 0;
}
async function activateCode(code) {
const res = await gmRequest({
method: 'POST',
url: 'https://qsy.iano.cn/index.php?s=/api/question_bank/activate',
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({ code, device_id: getDeviceId() }),
timeout: 10000,
});
if (!res || res.status !== 200) return { ok: false, msg: '网络错误' };
try {
const json = JSON.parse(res.responseText);
return { ok: json.code === 1, msg: json.msg || '', data: json.data };
} catch { return { ok: false, msg: '解析错误' }; }
}
// --- 考试面板 UI ---
function createExamPanel() {
if ($('#av-exam-panel')) return;
const panel = document.createElement('div');
panel.id = 'av-exam-panel';
panel.innerHTML = `
📝 考试助手
▼
📱 扫码观看广告获取验证码
验证后免费使用24小时
剩余积分
--
每次答题消耗1积分 · 联系微信: C919irt
⚠️ 仅供学习交流,请勿用于违规行为
使用者自行承担一切后果
`;
document.body.appendChild(panel);
// 收起/展开
$('#ae-collapse').addEventListener('click', () => {
panel.classList.toggle('collapsed');
$('#ae-collapse').textContent = panel.classList.contains('collapsed') ? '▶' : '▼';
});
makeDraggable(panel, $('#ae-drag-handle'));
// --- 模式切换 ---
function switchModeUI(mode) {
const freeTab = $('#ae-mode-free');
const paidTab = $('#ae-mode-paid');
const freeArea = $('#ae-area-free');
const paidArea = $('#ae-area-paid');
if (mode === 'paid') {
freeTab.style.background = 'transparent'; freeTab.style.color = '#94a3b8';
paidTab.style.background = 'rgba(96,165,250,0.15)'; paidTab.style.color = '#60a5fa';
freeArea.style.display = 'none';
paidArea.style.display = '';
setExamMode('paid');
} else {
paidTab.style.background = 'transparent'; paidTab.style.color = '#94a3b8';
freeTab.style.background = 'rgba(52,211,153,0.15)'; freeTab.style.color = '#34d399';
paidArea.style.display = 'none';
freeArea.style.display = '';
setExamMode('free');
}
}
$('#ae-mode-free').addEventListener('click', () => switchModeUI('free'));
$('#ae-mode-paid').addEventListener('click', () => switchModeUI('paid'));
// 恢复上次模式
switchModeUI(examMode);
// --- 免费验证码 ---
// 恢复验证状态
if (isVerifyValid()) {
const state = getVerifyState();
const expStr = new Date(state.validUntil * 1000).toLocaleString();
$('#ae-verify-msg').textContent = `✅ 已验证,有效期至 ${expStr}`;
$('#ae-verify-msg').style.color = '#34d399';
$('#ae-verify-code').disabled = true;
$('#ae-verify-btn').disabled = true;
}
$('#ae-verify-btn').addEventListener('click', async () => {
const code = ($('#ae-verify-code').value || '').trim();
if (!code || code.length !== 4) {
$('#ae-verify-msg').textContent = '请输入4位验证码';
$('#ae-verify-msg').style.color = '#f87171';
return;
}
$('#ae-verify-btn').disabled = true;
$('#ae-verify-btn').textContent = '验证中...';
const result = await verifyCode(code);
$('#ae-verify-msg').textContent = result.ok ? `✅ ${result.msg}` : `❌ ${result.msg}`;
$('#ae-verify-msg').style.color = result.ok ? '#34d399' : '#f87171';
if (result.ok) {
$('#ae-verify-code').disabled = true;
} else {
$('#ae-verify-btn').disabled = false;
}
$('#ae-verify-btn').textContent = '验证';
});
// 回车验证
$('#ae-verify-code').addEventListener('keypress', (e) => {
if (e.key === 'Enter') $('#ae-verify-btn').click();
});
// --- 付费激活码 ---
$('#ae-activate-btn').addEventListener('click', async () => {
const code = ($('#ae-code-input').value || '').trim();
if (!code) return;
$('#ae-activate-msg').textContent = '激活中...';
const result = await activateCode(code);
$('#ae-activate-msg').textContent = result.msg;
$('#ae-activate-msg').style.color = result.ok ? '#34d399' : '#f87171';
if (result.ok) {
const c = await queryCredits();
updateCreditsDisplay(c);
}
});
// --- 搜题/填入/停止 ---
$('#ae-search-all').addEventListener('click', () => searchAllQuestions());
$('#ae-fill-all').addEventListener('click', () => fillAllAnswers());
$('#ae-stop').addEventListener('click', () => {
isSearching = false;
log.info('[考试] 已停止搜题');
});
// 付费模式查询积分
if (examMode === 'paid') {
queryCredits().then(c => updateCreditsDisplay(c));
}
}
// --- 搜题与填入逻辑 ---
let examResults = new Map();
let isSearching = false;
async function searchAllQuestions() {
if (isSearching) { log.warn('[考试] 正在搜题中...'); return; }
isSearching = true;
const questions = extractExamQuestions();
const listEl = $('#ae-question-list');
if (!listEl) { isSearching = false; return; }
listEl.innerHTML = '';
examResults.clear();
$('#ae-total').textContent = questions.length;
$('#ae-found').textContent = '0';
$('#ae-notfound').textContent = '0';
log.info(`[考试] 共 ${questions.length} 道题目,开始搜题...`);
let found = 0, notFound = 0;
for (let i = 0; i < questions.length; i++) {
if (!isSearching) break;
const q = questions[i];
const itemDiv = document.createElement('div');
itemDiv.className = 'ae-q-item searching';
itemDiv.id = `ae-q-${i}`;
itemDiv.innerHTML = `
第${i + 1}题 [${TYPE_NAMES[q.type] || '未知'}]
${q.text}
☕ 搜索中...
`;
listEl.appendChild(itemDiv);
const result = await searchAnswer(q.text, q.options, q.type);
const answerDiv = itemDiv.querySelector('.ae-q-answer');
const sourceDiv = itemDiv.querySelector('.ae-q-source');
if (result && result.answers.length > 0) {
answerDiv.textContent = '✅ ' + result.answers.join(' | ');
sourceDiv.textContent = '来源: ' + result.source;
itemDiv.className = 'ae-q-item found';
found++;
examResults.set(i, { question: q, result });
} else {
answerDiv.textContent = '❌ 未找到答案';
answerDiv.className = 'ae-q-answer empty';
itemDiv.className = 'ae-q-item not-found';
notFound++;
examResults.set(i, { question: q, result: null });
}
$('#ae-found').textContent = found;
$('#ae-notfound').textContent = notFound;
await sleep(600);
}
// 绑定单题按钮
listEl.querySelectorAll('.ae-q-btn').forEach(btn => {
btn.addEventListener('click', async (e) => {
const action = e.target.dataset.action;
const idx = parseInt(e.target.dataset.idx);
if (action === 'retry') {
const q = questions[idx];
const itemDiv = $(`#ae-q-${idx}`);
if (!itemDiv) return;
itemDiv.className = 'ae-q-item searching';
itemDiv.querySelector('.ae-q-answer').textContent = '☕ 重新搜索中...';
const result = await searchAnswer(q.text, q.options, q.type);
const ad = itemDiv.querySelector('.ae-q-answer');
const sd = itemDiv.querySelector('.ae-q-source');
if (result && result.answers.length > 0) {
ad.textContent = '✅ ' + result.answers.join(' | ');
sd.textContent = '来源: ' + result.source;
itemDiv.className = 'ae-q-item found';
examResults.set(idx, { question: q, result });
} else {
ad.textContent = '❌ 未找到答案';
ad.className = 'ae-q-answer empty';
itemDiv.className = 'ae-q-item not-found';
}
} else if (action === 'fill') {
const data = examResults.get(idx);
if (data && data.result) {
const ok = matchAndFill(data.question, data.result);
log.info(`[考试] 第${idx + 1}题 填入${ok ? '成功' : '失败'}`);
}
}
});
});
isSearching = false;
log.success(`[考试] 搜题完成! 找到:${found} 未找到:${notFound}`);
if (config.examAutoFill && found > 0) {
await sleep(1000);
fillAllAnswers();
}
}
async function fillAllAnswers() {
let filled = 0, failed = 0;
for (const [idx, data] of examResults) {
if (data.result) {
const ok = matchAndFill(data.question, data.result);
if (ok) filled++; else failed++;
await sleep(400);
}
}
log.success(`[考试] 填入完成! 成功:${filled} 失败:${failed}`);
}
// --- 考试页入口 ---
async function examMain() {
log.info('考试助手启动!');
await sleep(3000);
createExamPanel();
if (config.autoExam) {
await sleep(2000);
searchAllQuestions();
}
}
// ===================== 主流程 =====================
async function main() {
// 首先激活反检测(必须在所有操作之前)
try { hookAntiCheat(); } catch (e) { log.warn('反检测模块初始化异常: ' + e.message); }
// 考试页走考试流程
if (isExamPage()) {
await examMain();
return;
}
if (!isStudyPage()) {
log.info('当前页面非学习页,脚本待命中');
createPanel();
updatePanelStatus('⏳ 等待进入学习/考试页面...');
return;
}
log.info('智慧树刷课助手启动!');
log.info(`页面类型: ${isHikeClass() ? '翻转课' : isNewVersion() ? '智慧共享课(新版)' : '共享课'}`);
// 等待页面加载
await sleep(3000);
// 创建面板
createPanel();
// 页面优化
await optimizePage();
// 等待视频元素
await waitForVideo();
// 初始化视频设置
overridePause();
setMute();
setSpeed();
const title = getLessonTitle();
log.info(`正在学习: ${title}`);
updatePanelStatus(`▶️ ${title}`);
// 启动定时任务
// 1. 检测暂停并续播 (每3秒)
addTimer(checkAndPlay, 3000);
// 2. 视频设置维持(倍速、静音) (每5秒)
addTimer(() => {
overridePause();
setMute();
setSpeed();
}, 5000);
// 3. 弹题检测 (每3秒)
addTimer(skipQuestions, 3000);
// 4. 弹窗关闭 (每4秒)
addTimer(closePopups, 4000);
// 5. 人机验证检测 (每5秒)
addTimer(checkVerify, 5000);
// 6. 习惯分模拟鼠标 (每8秒)
addTimer(simulateMouseMove, 8000);
// 7. 进度监控 & 自动切换 (每3秒)
addTimer(() => {
const progress = getCourseProgress();
updatePanelProgress(progress);
if (progress === '100%') {
switchToNext();
}
}, 3000);
log.success('所有任务模块已启动');
}
async function waitForVideo() {
let retries = 0;
while (!getVideo() && retries < 30) {
await sleep(1000);
retries++;
}
if (getVideo()) {
log.info('视频元素已就绪');
} else {
log.warn('未找到视频元素,部分功能可能不可用');
}
}
// ===================== 油猴菜单命令 =====================
GM_registerMenuCommand('⚡ 重新启动脚本', () => {
stopAllTasks();
const panel = $('#autovisor-panel');
if (panel) panel.remove();
main();
});
GM_registerMenuCommand('⏹ 停止脚本', () => {
stopAllTasks();
updatePanelStatus('⏹ 脚本已停止');
log.info('脚本已手动停止');
});
GM_registerMenuCommand('🔄 重置配置', () => {
config = Object.assign({}, DEFAULT_CONFIG);
saveConfig(config);
log.info('配置已重置为默认值');
location.reload();
});
// ===================== 启动 =====================
main();
})();