// ==UserScript==
// @name 🔥🔥🔥华医助手(修复版2025.9.6)
// @namespace http://tampermonkey.net/
// @version 1.3.1
// @description ✅视频助手✅屏蔽或者跳过课堂签到、提醒、疲劳✅考试助手(试错算法仅面向可多次提交的考试)✅全自动学习所有课程
// @author 二创作者:大成路旁 原创作者:Dr.S
// @license AGPL License
// @match *://*.91huayi.com/course_ware/course_ware_polyv.aspx?*
// @match *://*.91huayi.com/course_ware/course_ware_cc.aspx?*
// @match *://*.91huayi.com/pages/exam.aspx?*
// @match *://*.91huayi.com/pages/exam_result.aspx?*
// @match *://*.91huayi.com/pages/course.aspx?*
// @match *://*.91huayi.com/*
// @grant none
// @require https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==
(function () {
'use strict';
// ========================
// 🔧 配置参数
// ========================
const config = {
submitTime: 6100,
reTryTime: 2100,
examTime: 10000,
randomX: 5000,
autoSkip: false,
checkInterval: 20000, // 检测间隔时间(毫秒),
videoCompletionThreshold: 0.95 // 视频完成阈值(95%视为完成)
};
// ========================
// 🗃️ 存储键名
// ========================
const STORAGE_KEYS = {
PLAY_RATE: "JJ_Playrate",
TEST: "JJ_Test",
RESULT: "JJ_Result",
THIS_TITLE: "JJ_ThisTitle",
TEST_ANSWER: "JJ_TestAnswer",
RIGHT_ANSWER: "JJ_RightAnswer",
ALL_ANSWER: "JJ_AllAnswer",
CURRENT_COURSE: "JJ_CurrentCourse",
VIDEO_COMPLETED: "JJ_VideoCompleted" // 新增:记录视频完成状态
};
// ========================
// 🎨 按钮样式
// ========================
const BTN_STYLES = {
A: "font-size:16px;font-weight:300;text-decoration:none;text-align:center;line-height:40px;height:40px;padding:0 40px;display:inline-block;appearance:none;cursor:pointer;border:none;box-sizing:border-box;transition:all .3s;background:#4cb0f9;border-color:#4cb0f9;border-radius:4px;margin:5px;color:#FFF;",
B: "font-size:12px;font-weight:300;text-decoration:none;text-align:center;line-height:20px;height:20px;padding:0 5px;display:inline-block;appearance:none;cursor:pointer;border:none;box-sizing:border-box;transition:all .3s;background:#4cb0f9;border-color:#4cb0f9;border-radius:4px;margin:5px;color:#FFF;",
C: "font-size:12px;font-weight:300;text-decoration:none;text-align:center;line-height:20px;height:20px;padding:0 5px;display:inline-block;appearance:none;cursor:pointer;border:none;box-sizing:border-box;transition:all .3s;background:#f15854;border-color:#f15854;border-radius:4px;margin:5px;color:#FFF;"
};
// ========================
// 🌐 URL 解析
// ========================
const urlParts = window.location.href.split('/');
const pageName = urlParts[urlParts.length - 1].split('?')[0];
// ========================
// 🧠 主逻辑分发
// ========================
const huayi = getHuayi();
const actions = {
'course_ware_polyv.aspx': () => huayi.seeVideo(1),
'course_ware_cc.aspx': () => huayi.seeVideo(2),
'exam.aspx': () => huayi.doTest(),
'course.aspx': () => huayi.courseList(),
'cme.aspx': () => huayi.courseList(),
'exam_result.aspx': () => huayi.doResult()
};
const action = actions[pageName];
if (action) {
console.log(`当前任务: ${{
'course_ware_polyv.aspx': '华医看视频',
'course_ware_cc.aspx': '华医看视频',
'exam.aspx': '华医考试',
'course.aspx': '课程列表',
'cme.aspx': '课程列表',
'exam_result.aspx': '华医考试结果审核'
}[pageName]}`);
action();
} else {
console.log("其它页面,无需处理");
}
// ========================
// 🧩 核心功能对象
// ========================
function getHuayi() {
return {
courseList: () => {
addAnswerCopyBtn();
addClearAnswerBtn();
goToNextCourse(); // 自动进入下一课
},
seeVideo: (playerType) => {
cleanStorage();
asynckillSendQuestion();
killSendQuestion2();
killSendQuestion3();
addModeIndicator();
changeLayout();
// 重置视频完成状态
localStorage.removeItem(STORAGE_KEYS.VIDEO_COMPLETED);
window.onload = () => {
localStorage.setItem(STORAGE_KEYS.THIS_TITLE, JSON.stringify(document.title));
if (config.autoSkip) {
setTimeout(skipVideo, config.submitTime + Math.ceil(Math.random() * config.randomX));
console.log("秒过了!");
}
// 设置视频完成监听
setTimeout(setupVideoCompletionListener, 3000);
// 定期检查状态 - 使用配置的间隔时间
const clock = setInterval(examherftest, config.checkInterval);
// 初始化播放器
const initPlayer = {
1: () => {
window.s2j_onPlayerInitOver = () => {
const video = document.querySelector('video');
if (video) video.defaultMuted = true;
const playerInstance = window.player;
playerInstance?.j2s_setVolume?.(0);
setTimeout(() => {
try {
playerInstance?.j2s_resumeVideo?.();
examherftest();
} catch (e) {
console.warn("保利威播放器初始化错误:", e);
}
}, 2000);
};
},
2: () => {
window.on_CCH5player_ready = () => {
const video = document.querySelector('video');
if (video) video.defaultMuted = true;
const playerInstance = window.cc_js_Player;
playerInstance?.setVolume?.(0);
setTimeout(() => {
try {
playerInstance?.play?.();
examherftest();
} catch (e) {
console.warn("CC播放器初始化错误:", e);
}
}, 2000);
};
}
}[playerType];
initPlayer?.();
};
},
doTest: () => {
const questions = JSON.parse(localStorage.getItem(STORAGE_KEYS.TEST)) || {};
let qRightAnswer = JSON.parse(localStorage.getItem(STORAGE_KEYS.RIGHT_ANSWER)) || {};
if (Object.keys(qRightAnswer).length === 0) {
qRightAnswer = loadRightAnswer();
}
const qTestAnswer = {};
let index = 0;
let questionEl;
while ((questionEl = document.querySelectorAll("table.tablestyle")[index])) {
const questionText = questionEl.querySelector(".q_name")?.innerText.substring(2).replace(/\s/g, '');
if (!questionText) {
index++;
continue;
}
const tbody = questionEl.querySelector("tbody");
if (!tbody) {
index++;
continue;
}
// 优先使用正确答案
if (qRightAnswer.hasOwnProperty(questionText)) {
const rightOption = findAnswer(tbody, qRightAnswer[questionText]);
rightOption?.click();
} else {
// 否则试错机制
const current = questions[questionText] || "A";
const next = String.fromCharCode(current.charCodeAt(0) + 1);
questions[questionText] = next;
const optionIndex = current.charCodeAt(0) - "A".charCodeAt(0);
const labels = tbody.getElementsByTagName("label");
const element = labels[optionIndex] || labels[0]; // fallback to A
try {
qTestAnswer[questionText] = element.innerText.substring(3);
} catch (e) {
console.warn("答案文本获取失败:", e);
}
element.click();
}
index++;
}
localStorage.setItem(STORAGE_KEYS.TEST, JSON.stringify(questions));
localStorage.setItem(STORAGE_KEYS.TEST_ANSWER, JSON.stringify(qTestAnswer));
setTimeout(() => {
document.querySelector("#btn_submit")?.click();
}, config.submitTime + Math.ceil(Math.random() * config.randomX));
},
doResult: () => {
const resultText = $(".tips_text")[0]?.innerText || "";
const dds = $(".state_cour_lis");
localStorage.removeItem(STORAGE_KEYS.RESULT);
if (/考试通过|完成项目学习/.test(resultText)) {
console.log("✅ 考试通过,正在查找下一门课程...");
saveRightAnswer();
saveAllAnswers();
cleanStorage();
// 保存当前课程信息,用于后续跳转
const currentCourse = {
url: window.location.href,
title: document.title,
timestamp: new Date().getTime()
};
localStorage.setItem(STORAGE_KEYS.CURRENT_COURSE, JSON.stringify(currentCourse));
// 延迟执行,确保页面稳定
setTimeout(() => {
// 首先尝试查找页面上的"立即学习"按钮
const learnBtn = $('input[value="立即学习"].state_lis_btn')[0];
if (learnBtn) {
const title = $(learnBtn).siblings('.state_lis_text').attr('title') || "未知课程";
console.log(`▶️ 发现待学课程: ${title},正在跳转...`);
learnBtn.click();
} else {
console.log("❌ 未找到立即学习按钮,尝试自动进入下一课程");
// 如果没有找到按钮,尝试自动跳转到课程列表
// setTimeout(() => {
// window.location.href = "/pages/course.aspx";
// }, 2000);
}
}, 1000);
} else {
console.log("❌ 考试未通过,准备重考");
document.querySelector("p.tips_text").innerText =
"本次未通过,正在尝试更换答案\r(此为正常现象,脚本几秒后刷新,请勿操作)";
const wrongMap = {};
dds.each((i, el) => {
const img = el.querySelector("img");
const p = el.querySelector("p");
if (img && p && !img.src.includes("bar_img")) {
wrongMap[p.title.replace(/\s/g, "")] = i;
}
});
if (Object.keys(wrongMap).length > 0) {
localStorage.setItem(STORAGE_KEYS.RESULT, JSON.stringify(wrongMap));
saveRightAnswer();
}
setTimeout(() => {
$("input[type=button][value='重新考试']").click();
}, config.reTryTime + Math.ceil(Math.random() * config.randomX));
}
}
};
}
// ========================
// 🚀 功能函数(优化版)
// ========================
function goToNextCourse() {
// 获取当前课程信息
const currentCourse = JSON.parse(localStorage.getItem(STORAGE_KEYS.CURRENT_COURSE) || "{}");
const currentUrl = currentCourse.url || "";
console.log("正在寻找下一门课程...");
setTimeout(() => {
const lessons = document.querySelectorAll(".lis-inside-content");
let foundNext = false;
for (const lesson of lessons) {
const status = lesson.querySelector("button")?.innerText.trim();
const h2 = lesson.querySelector("h2[onclick]");
const onclick = h2?.getAttribute("onclick");
if ((status === "学习中" || status === "未学习") && onclick) {
const match = onclick.match(/window\.location\.href='([^']+)'/);
if (match) {
const courseUrl = match[1];
console.log("➡️ 正在进入下一门课程:", courseUrl);
// 确保URL是完整的
const fullUrl = courseUrl.startsWith("http") ? courseUrl :
`https://${window.location.hostname}${courseUrl.startsWith("/") ? "" : "/"}${courseUrl}`;
window.location.href = fullUrl;
foundNext = true;
return;
}
}
}
if (!foundNext) {
console.log("❌ 未找到可用的下一门课程");
// 尝试其他方法查找课程
tryAlternativeCourseFinding();
}
}, 3000);
}
function tryAlternativeCourseFinding() {
// 方法1: 查找所有可能的课程链接
const courseLinks = document.querySelectorAll('a[href*="course_ware"], a[href*="exam"], h2[onclick*="course_ware"], h2[onclick*="exam"]');
for (const link of courseLinks) {
let url = null;
if (link.tagName === 'A') {
url = link.href;
} else if (link.hasAttribute('onclick')) {
const onclick = link.getAttribute('onclick');
const match = onclick.match(/window\.location\.href='([^']+)'/);
if (match) {
url = match[1];
}
}
if (url && (url.includes("course_ware") || url.includes("exam"))) {
console.log("🔍 发现备选课程链接:", url);
// 确保URL是完整的
const fullUrl = url.startsWith("http") ? url :
`https://${window.location.hostname}${url.startsWith("/") ? "" : "/"}${url}`;
setTimeout(() => {
window.location.href = fullUrl;
}, 2000);
return;
}
}
console.log("🎉 所有课程已完成或未找到更多课程!");
}
function saveAllAnswers() {
const allAnswers = JSON.parse(localStorage.getItem(STORAGE_KEYS.ALL_ANSWER)) || {};
const rightAnswers = JSON.parse(localStorage.getItem(STORAGE_KEYS.RIGHT_ANSWER)) || {};
const title = JSON.parse(localStorage.getItem(STORAGE_KEYS.THIS_TITLE)) || "没有记录到章节名称";
if (title !== "没有记录到章节名称") {
const chapterAnswers = allAnswers[title] || {};
Object.assign(chapterAnswers, rightAnswers);
allAnswers[title] = chapterAnswers;
localStorage.setItem(STORAGE_KEYS.ALL_ANSWER, JSON.stringify(allAnswers));
}
}
function loadRightAnswer() {
const allAnswers = JSON.parse(localStorage.getItem(STORAGE_KEYS.ALL_ANSWER)) || {};
const title = JSON.parse(localStorage.getItem(STORAGE_KEYS.THIS_TITLE)) || "没有记录到章节名称";
return title !== "没有记录到章节名称" ? allAnswers[title] || {} : {};
}
function saveRightAnswer() {
const rightAnswers = JSON.parse(localStorage.getItem(STORAGE_KEYS.RIGHT_ANSWER)) || {};
const testAnswers = JSON.parse(localStorage.getItem(STORAGE_KEYS.TEST_ANSWER)) || {};
const wrongs = JSON.parse(localStorage.getItem(STORAGE_KEYS.RESULT)) || {};
for (const q in testAnswers) {
if (!wrongs.hasOwnProperty(q)) {
console.log("正确的题目:" + q + ",答案:" + testAnswers[q]);
rightAnswers[q] = testAnswers[q];
} else {
console.log("错误的题目:" + q + ",答案:" + testAnswers[q]);
}
}
localStorage.setItem(STORAGE_KEYS.RIGHT_ANSWER, JSON.stringify(rightAnswers));
localStorage.removeItem(STORAGE_KEYS.TEST_ANSWER);
}
function addAnswerCopyBtn() {
const btn = document.createElement("a");
btn.innerHTML = '显示已记录答案';
btn.style.cssText = BTN_STYLES.B;
btn.onclick = () => {
const allAnswers = JSON.parse(localStorage.getItem(STORAGE_KEYS.ALL_ANSWER)) || {};
const output = JSON.stringify(allAnswers, null, "\t");
const textarea = document.getElementById("AnwserOut") || (() => {
const el = document.createElement("textarea");
el.id = "AnwserOut";
el.rows = 20;
el.cols = 30;
document.getElementById("main_div").parentNode.appendChild(el);
return el;
})();
textarea.value = output;
};
document.getElementById("main_div")?.parentNode.appendChild(btn);
}
function addClearAnswerBtn() {
const btn = document.createElement("a");
btn.innerHTML = '清除已记录答案';
btn.style.cssText = BTN_STYLES.B;
btn.onclick = () => {
if (confirm("确定清除历史答案?!")) {
localStorage.removeItem(STORAGE_KEYS.ALL_ANSWER);
}
};
document.getElementById("main_div")?.parentNode.appendChild(btn);
}
function skipVideo() {
const video = document.querySelector('video');
if (video) video.currentTime = video.duration - 1;
}
function examherftest() {
const jrks = document.getElementById("jrks");
const ckjy = document.getElementById("ckjy");
if (!ckjy || !jrks) {
console.log("❌ 未找到考试相关元素");
return;
}
const ckjyHref = ckjy.href || "";
const cwidMatch = ckjyHref.match(/[?&]cwid=([^&]+)/i);
const cwid = cwidMatch ? cwidMatch[1] : null;
// 检测按钮状态
const isDisabled = jrks.hasAttribute("disabled");
const hasDisabledClass = jrks.classList.contains("inputstyle2_2");
// 检测视频状态 - 通过保利威播放器API
let isVideoCompleted = false;
try {
if (window.player && typeof window.player.j2s_getCurrentTime === 'function') {
const currentTime = window.player.j2s_getCurrentTime();
const duration = window.player.j2s_getDuration();
// 如果视频播放接近完成(达到阈值)
const completionRatio = currentTime / duration;
isVideoCompleted = completionRatio >= config.videoCompletionThreshold;
// 检查是否已经标记为完成
const alreadyCompleted = localStorage.getItem(STORAGE_KEYS.VIDEO_COMPLETED) === "true";
if (isVideoCompleted && !alreadyCompleted) {
console.log("✅ 视频已达到完成阈值,标记为已完成");
localStorage.setItem(STORAGE_KEYS.VIDEO_COMPLETED, "true");
}
// 如果已经标记为完成,则始终认为视频已完成
if (alreadyCompleted) {
isVideoCompleted = true;
}
}
} catch (e) {
console.log("无法获取视频状态:", e);
}
// 检测页面中的状态指示器
const statusButtons = document.querySelectorAll(".lis-inside-content button");
let currentVideoCompleted = false;
statusButtons.forEach(button => {
if (button.textContent === "已完成" || button.textContent === "待考试") {
currentVideoCompleted = true;
}
});
console.log(`📊 状态检测: cwid=${cwid}, 按钮禁用=${isDisabled}, 禁用类=${hasDisabledClass}, 视频完成=${isVideoCompleted}, 页面状态=${currentVideoCompleted}`);
// 主要判断条件:按钮可用且有CWID
if (cwid && !isDisabled && !hasDisabledClass) {
console.log("✅ 满足所有条件,正在跳转考试...");
// 直接使用URL跳转(更可靠)
const examUrl = `/pages/exam.aspx?cwid=${cwid}`;
window.location.href = examUrl;
} else {
// if (!cwid) console.log("❌ 缺少cwid参数");
// if (isDisabled) console.log("❌ 考试按钮仍被禁用");
// if (hasDisabledClass) console.log("❌ 考试按钮仍有禁用样式");
// if (!isVideoCompleted) console.log("❌ 视频未完成");
}
}
// 添加视频完成事件监听
function setupVideoCompletionListener() {
if (window.player && typeof window.player.j2s_onPlayOver === 'function') {
// 保存原始回调
const originalOnPlayOver = window.player.j2s_onPlayOver;
// 重写回调
window.player.j2s_onPlayOver = function() {
console.log("🎬 视频播放完成,准备跳转考试");
// 标记视频为已完成
localStorage.setItem(STORAGE_KEYS.VIDEO_COMPLETED, "true");
// 调用原始回调
if (typeof originalOnPlayOver === 'function') {
originalOnPlayOver();
}
// 检查并跳转
setTimeout(examherftest, 2000);
};
}
}
function asynckillSendQuestion() {
(async () => {
while (!window.player || !window.player.sendQuestion) await new Promise(r => setTimeout(r, 20));
window.player.sendQuestion = () => {};
})();
}
function killSendQuestion2() {
// 修复:安全地检查并设置 isInteraction 变量
try {
// 首先检查页面上是否已定义 isInteraction 变量
if (typeof window.isInteraction !== 'undefined') {
window.isInteraction = "off";
console.log("✅ 已设置 isInteraction = 'off'");
}
} catch (e) {
console.log("❌ 设置 isInteraction 时出错:", e);
}
}
function killSendQuestion3() {
setInterval(() => {
try {
// 跳过各种弹窗
const selectors = [
'.pv-ask-skip', // 问题对话框
'.signBtn', // 签到
"button[onclick='closeProcessbarTip()']", // 旧提示
'#div_processbar_tip .rig_btn', // 新版"知道了"
'button.btn_sign' // 疲劳提醒
];
selectors.forEach(sel => {
const el = document.querySelector(sel);
if (el && isVisible(el)) {
console.log(`检测到弹窗,尝试跳过: ${sel}`);
el.click();
}
});
// 视频播放状态监控
const video = document.querySelector('video');
const playIcon = document.querySelector("i#top_play");
const stateText = document.querySelector('.rig_text')?.innerText;
if (video && playIcon && video.paused && stateText !== "已完成") {
video.muted = true;
video.volume = 0;
video.play().catch(err => console.warn("自动播放被阻止:", err));
} else if (stateText === "已完成") {
video?.pause();
}
} catch (err) {
console.warn("弹窗处理出错:", err);
}
}, 2000);
}
// 辅助函数:判断元素是否可见
function isVisible(el) {
return el && el.offsetParent !== null && el.style.display !== 'none' && el.style.visibility !== 'hidden';
}
function findAnswer(tbody, answerText) {
const labels = tbody.getElementsByTagName("label");
for (let i = 0; i < labels.length; i++) {
if (labels[i].innerText.substring(3) === answerText) {
return labels[i];
}
}
return null;
}
function cleanStorage() {
[
STORAGE_KEYS.TEST,
STORAGE_KEYS.RESULT,
STORAGE_KEYS.TEST_ANSWER,
STORAGE_KEYS.RIGHT_ANSWER
].forEach(key => localStorage.removeItem(key));
}
function addModeIndicator() {
const mode = localStorage.getItem("华医mode") === "2" ? "视频+考试" : "单刷视频";
const icon = localStorage.getItem("华医mode") === "2" ? "🎬📝" : "🎬";
const color = localStorage.getItem("华医mode") === "2" ? "#28a745" : "#007bff";
const el = document.createElement("div");
el.innerHTML = `${icon} ${mode} (点击切换)`;
el.title = "点击切换模式";
// 修复样式设置,使用正确的CSS属性名
el.style.position = "fixed";
el.style.top = "90%";
el.style.left = "50%";
el.style.transform = "translateX(-50%)";
el.style.background = color;
el.style.color = "white";
el.style.padding = "12px 20px";
el.style.borderRadius = "8px";
el.style.fontSize = "24px";
el.style.fontWeight = "bold";
el.style.boxShadow = "0 4px 15px rgba(0,0,0,0.2)";
el.style.zIndex = "9999";
el.style.cursor = "pointer";
el.style.whiteSpace = "nowrap";
el.onclick = () => {
const m = localStorage.getItem("华医mode") !== "2" ? "2" : "1";
localStorage.setItem("华医mode", m);
const i = m === "2" ? "🎬📝" : "🎬";
const c = m === "2" ? "#007bff" : "#28a745";
el.innerHTML = `${i} ${m === "2" ? "视频+考试" : "单刷视频"} (点击切换)`;
el.style.background = c;
};
document.body.appendChild(el);
}
function changeLayout() {
// 这里可以添加布局修改代码
console.log("修改页面布局...");
}
})();