// ==UserScript==
// @name 联大学堂 自主计时70%+OCR答题(原生强制提交)【播放才计时+停止不重置+立刻显示总长】
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 播放才计时 + 停止不重置 + 页面加载立刻显示视频总长 + OCR图片识别答题(适配答案解析图)
// @match *://*.jxjypt.cn/*
// @require https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1.min.js
// @require https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js
// @grant GM_xmlhttpRequest
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
// ====================== OCR 识别函数 ======================
async function recognizeImage(file) {
const { data } = await Tesseract.recognize(file, 'chi_sim+eng');
let text = data.text;
// 修复常见识别错误
text = text.replace(/4/g, 'A');
text = text.replace(/©/g, 'C');
text = text.replace(/×/g, 'x');
return text;
}
// ====================== 面板UI ======================
function createPanel() {
let panel = document.createElement('div');
panel.id = 'autoStudyPanel';
panel.style.cssText = `
position:fixed;top:140px;right:20px;z-index:999999;
background:#fff;padding:12px;border-radius:10px;box-shadow:0 0 10px rgba(0,0,0,0.2);
width:220px;max-height:80vh;overflow-y:auto;
`;
panel.innerHTML = `
🎯 自主计时70%+OCR答题
计时:00:00
总长:00:00
进度:0%
状态:等待启动
OCR状态:未获取
`;
document.body.appendChild(panel);
}
let isRunning = false;
let timer = 0;
let total = 0;
let interval = null;
let jumped = false;
let aiAlreadyCalled = false;
let autoAnswered = false;
function fmt(s) {
let m = Math.floor(s / 60);
let sec = Math.floor(s % 60);
return `${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;
}
function closePopups() {
try {
$("button:contains('确定'),button:contains('关闭'),.close,.layui-layer-close").click();
$(".layui-layer,.modal,.popup").hide();
} catch (e) { }
}
// 立刻获取视频总时长
function loadDurationNow() {
let checkVideo = setInterval(() => {
let video = document.querySelector('video');
if (video && video.duration > 0) {
total = video.duration;
updateInfo();
clearInterval(checkVideo);
}
}, 100);
}
// 更新信息
function updateInfo() {
let percent = total > 0 ? (timer / total) * 100 : 0;
$('#videoInfo').html(`计时:${fmt(timer)}
总长:${fmt(total)}
进度:${percent.toFixed(1)}%`);
}
function loop() {
if (!isRunning || jumped) return;
closePopups();
let video = document.querySelector('video');
if (!video) return;
// 自动播放
if (video.paused) video.play().catch(() => {});
// 播放才计时
if (!video.paused && !video.ended && video.readyState >= 4) {
timer++;
}
updateInfo();
let percent = total > 0 ? (timer / total) * 100 : 0;
if (percent >= 70 && !autoAnswered) {
autoAnswered = true;
$('#status').text('✅ 达到70%,OCR答题中...');
setTimeout(() => getAnswerOnce(), 1200);
}
}
function goNext() {
aiAlreadyCalled = false;
autoAnswered = false;
// 切换下一个视频时才清空
$('#questionDisplay').text('');
$('#answerStatus').text('OCR状态:未获取');
let cur = $('dd.z-color.z-class-icon').first();
if (!cur.length) return;
let next = cur.next('dd');
if (next.length) next.click();
else {
let nextChap = cur.closest('.course-list-txt').next().find('dd').first();
if (nextChap.length) nextChap.click();
}
setTimeout(() => {
timer = 0;
total = 0;
jumped = false;
loadDurationNow();
$('#status').text('状态:运行中');
// 切换新视频后,自动显示题目
setTimeout(showQuestions, 1500);
}, 4000);
}
function start() {
isRunning = true;
$('#status').text('状态:运行中');
clearInterval(interval);
interval = setInterval(loop, 1000);
}
function stop() {
isRunning = false;
clearInterval(interval);
$('#status').text('状态:已暂停(计时保留)');
let video = document.querySelector('video');
if (video) video.pause();
}
// ====================== 提取题目 ======================
function getExamQuestions() {
const result = [];
document.querySelectorAll('.m-question').forEach(el => {
const q = {};
q.title = el.querySelector('.sub-dotitle pre')?.innerText.trim() || '无题目';
q.options = {};
el.querySelectorAll('.m-question-option').forEach(opt => {
const k = opt.getAttribute('data-value');
const v = opt.innerText.trim().replace(/^[A-Z]\./, '').trim();
if (k && v && !v.includes('null')) q.options[k] = v;
});
result.push(q);
});
return result;
}
function showQuestions() {
const qs = getExamQuestions();
let txt = '';
if (!qs.length) txt = '未检测到题目';
else qs.forEach((q, i) => {
txt += `第${i+1}题:${q.title}\n`;
Object.entries(q.options).forEach(([k, v]) => txt += `${k}. ${v}\n`);
txt += `——————————\n`; // 分隔线
});
$('#questionDisplay').text(txt);
}
// ====================== 核心:识别答案解析图 ======================
async function getImageAnswer() {
try {
// 先自动展开解析(显示答案图)
$('.zkjx').click();
// 精准找到你网站的答案图片(.solution 下的 img)
const img = document.querySelector('.solution img');
if (!img) {
$('#answerStatus').text('OCR:未找到答案图');
return null;
}
console.log('识别答案图片:', img.src);
// 转成 blob 供 OCR 使用
const response = await fetch(img.src);
const blob = await response.blob();
// OCR 识别
const text = await recognizeImage(blob);
// 🔥 关键:答案追加在题目后面,不覆盖
let oldText = $('#questionDisplay').text();
$('#questionDisplay').text(oldText + `\n✅ 识别答案:${text}\n`);
// 提取答案 A/B/C/D
const match = text.match(/[A-D]/);
if (match) return match[0];
} catch (e) {
console.error('OCR识别失败', e);
$('#answerStatus').text('OCR:识别失败');
}
return null;
}
function realSubmit() {
setTimeout(() => {
try {
let submitBtn = document.getElementById('submitSelfBtn');
if (submitBtn) {
$('#status').text('✅ 答案已提交,正在跳转');
submitBtn.click();
submitBtn.dispatchEvent(new Event('click', { bubbles: true }));
}
} catch (e) {}
setTimeout(() => { jumped = true; goNext(); }, 1500);
}, 1500);
}
async function getAnswerOnce() {
if (aiAlreadyCalled) return;
aiAlreadyCalled = true;
$('#answerStatus').text('OCR状态:识别中...');
// 使用 OCR 获取答案
const ans = await getImageAnswer();
if (ans) {
$('#answerStatus').text(`OCR状态:答案 ${ans}`);
let opt = document.querySelector(`.m-question-option[data-value="${ans}"]`);
if (opt) {
opt.click();
opt.dispatchEvent(new Event('click', { bubbles: true }));
}
} else {
$('#answerStatus').text('OCR状态:识别失败,跳过');
}
realSubmit();
}
// ====================== 初始化 ======================
$(function () {
createPanel();
loadDurationNow();
// 🔥 页面加载完成自动显示题目
setTimeout(showQuestions, 800);
$('#startBtn').click(start);
$('#stopBtn').click(stop);
$('#showQBtn').click(showQuestions);
$('#getAnswerBtn').click(getAnswerOnce);
});
})();