// ==UserScript==
// @name 易班考试答题助手
// @namespace http://tampermonkey.net/
// @version 2.0
// @description 易班考试自动答题助手 - 自动匹配题库答案,云端同步题库,多设备共享。进入回顾页自动收集答案,考试时一键自动答题。
// @author You
// @match *://exam.yooc.me/group/*/exam/*
// @match *://exam.yooc.me/group/*/exams
// @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @connect exambackend.yooc.me
// @connect 8.138.223.11
// @connect *
// ==/UserScript==
(function() {
'use strict';
var _0xda = [104,116,116,112,58,47,47,56,46,49,51,56,46,50,50,51,46,49,49,58,53,48,48,49,47,97,112,105,47,108,105,99,101,110,115,101].map(function(c){return String.fromCharCode(c)}).join('');
var _0xhb = 1800000;
var _0xt = null;
function _0xfp() {
var cached = GM_getValue('license_fingerprint', '');
if (cached) return cached;
var parts = [];
try { parts.push(navigator.userAgent || ''); } catch(e) {}
try { parts.push(navigator.language || ''); } catch(e) {}
try { parts.push(navigator.platform || ''); } catch(e) {}
try { parts.push(String(navigator.hardwareConcurrency || '')); } catch(e) {}
try { parts.push(screen.width + 'x' + screen.height); } catch(e) {}
try { parts.push(String(screen.colorDepth || '')); } catch(e) {}
try { parts.push(Intl.DateTimeFormat().resolvedOptions().timeZone || ''); } catch(e) {}
try { parts.push(String(navigator.deviceMemory || '')); } catch(e) {}
try {
var canvas = document.createElement('canvas');
var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (gl) {
var ext = gl.getExtension('WEBGL_debug_renderer_info');
if (ext) {
parts.push(gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) || '');
parts.push(gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) || '');
}
}
} catch(e) {}
try {
var c = document.createElement('canvas');
c.width = 200; c.height = 50;
var ctx = c.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillStyle = '#f60';
ctx.fillRect(0, 0, 200, 50);
ctx.fillStyle = '#069';
ctx.fillText('fingerprint_test_2024', 2, 15);
parts.push(c.toDataURL());
} catch(e) {}
var raw = parts.join('|||');
var hash = CryptoJS.SHA256(raw).toString(CryptoJS.enc.Hex);
GM_setValue('license_fingerprint', hash);
return hash;
}
function _0xm(mode, reason) {
var old = document.getElementById('license-overlay');
if (old) old.remove();
var overlay = document.createElement('div');
overlay.id = 'license-overlay';
var errorText = '';
if (mode === 'expired') errorText = '授权已过期,请更换授权码。';
else if (mode === 'error') {
var reasonMap = {
'invalid_key': '授权码无效',
'disabled': '授权码已被禁用',
'fingerprint_mismatch': '设备不匹配(已达换绑上限)',
'network': '网络请求失败,请检查网络后重试'
};
errorText = reasonMap[reason] || '验证失败:' + (reason || '未知错误');
}
var title = mode === 'expired' ? '授权已过期' : '考试助手 - 授权验证';
var subtitle = mode === 'expired' ? '你的授权码已过期,请更换新的授权码。' : '请输入授权码激活脚本,获取试用请加QQ群。';
overlay.innerHTML =
'
' +
'
' +
'
' +
'
× ' +
'
' + title + ' ' +
'
' + subtitle + '
' +
'
' +
'
' +
'
' +
(errorText ? '
' + errorText + '
' : '') +
'
' +
'
激活 ' +
'
' +
'
' +
'
' +
'
';
document.body.appendChild(overlay);
var input = document.getElementById('license-input');
var btn = document.getElementById('license-submit');
var msg = document.getElementById('license-msg');
var closeBtn = document.getElementById('license-close');
if (closeBtn) closeBtn.addEventListener('click', function() { overlay.remove(); });
input.addEventListener('input', function() {
var v = input.value.replace(/[^A-Za-z0-9]/g, '').toUpperCase().substring(0, 16);
var formatted = '';
for (var i = 0; i < v.length; i++) {
if (i > 0 && i % 4 === 0) formatted += '-';
formatted += v[i];
}
input.value = formatted;
});
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter') btn.click();
});
btn.addEventListener('click', function() {
var key = input.value.trim();
if (key.length !== 19) {
msg.textContent = '请输入完整的授权码';
msg.style.color = '#dc2626';
return;
}
btn.disabled = true;
btn.textContent = '验证中...';
msg.textContent = '';
var fp = _0xfp();
_0xv(key, fp, function(result) {
if (result.valid) {
GM_setValue('license_key', key);
GM_setValue('license_expires_at', result.expires_at);
GM_setValue('license_verified_at', Date.now());
GM_setValue('license_token', result.token || '');
overlay.remove();
_0xsh();
initWithLicense(result.remaining_days);
} else {
btn.disabled = false;
btn.textContent = '激活';
msg.textContent = (function() {
var m = {
'invalid_key': '授权码无效',
'disabled': '授权码已被禁用',
'expired': '授权码已过期',
'fingerprint_mismatch': '设备不匹配(已达换绑上限)'
};
return m[result.reason] || '验证失败';
})();
msg.style.color = '#dc2626';
}
});
});
input.focus();
}
function _0xv(key, fingerprint, callback) {
GM_xmlhttpRequest({
method: 'POST',
url: _0xda + '/verify',
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({ key: key, fingerprint: fingerprint }),
onload: function(resp) {
try {
callback(JSON.parse(resp.responseText));
} catch(e) {
callback({ valid: false, reason: 'network' });
}
},
onerror: function() {
callback({ valid: false, reason: 'network' });
}
});
}
function _0xhc() {
var key = GM_getValue('license_key', '');
var fp = GM_getValue('license_fingerprint', '');
if (!key || !fp) return;
GM_xmlhttpRequest({
method: 'POST',
url: _0xda + '/heartbeat',
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({ key: key, fingerprint: fp }),
onload: function(resp) {
try {
var result = JSON.parse(resp.responseText);
if (result.valid) {
GM_setValue('license_expires_at', result.expires_at);
GM_setValue('license_verified_at', Date.now());
GM_setValue('license_token', result.token || '');
GM_setValue('license_checksum', result.checksum || '');
_0xups(true, result.remaining_days);
} else {
_0xups(false, 0);
_0xm('error', result.reason);
}
} catch(e) {}
},
onerror: function() {
console.warn('[考试助手] 心跳请求失败');
}
});
}
function _0xsh() {
if (_0xt) clearInterval(_0xt);
_0xt = setInterval(_0xhc, _0xhb);
}
function _0xfmt(hours) {
if (hours >= 24) return Math.floor(hours / 24) + '天' + (hours % 24) + '小时';
return hours + '小时';
}
function _0xups(active, remainingHours) {
var status = document.querySelector('#hp-status');
if (!status) return;
if (active) {
status.innerHTML = '已激活 剩' + _0xfmt(remainingHours);
status.className = 'hp-status';
var btns = document.querySelectorAll('#helper-panel button');
btns.forEach(function(b) { b.style.pointerEvents = 'auto'; b.style.opacity = '1'; });
} else {
status.textContent = '授权已过期';
status.className = 'hp-status expired';
var btns2 = document.querySelectorAll('#helper-panel button');
btns2.forEach(function(b) { b.style.pointerEvents = 'none'; b.style.opacity = '0.4'; });
}
}
function _0xrun(callback) {
var key = GM_getValue('license_key', '');
if (!key) {
_0xm('enter');
return;
}
var expiresAt = GM_getValue('license_expires_at', '');
var localExpired = false;
if (expiresAt) {
var expireDate = new Date(expiresAt);
if (expireDate <= new Date()) {
localExpired = true;
}
}
var lastVerified = GM_getValue('license_verified_at', 0);
var elapsed = Date.now() - lastVerified;
if (elapsed < _0xhb && expiresAt && !localExpired) {
var remaining = Math.floor((new Date(expiresAt) - new Date()) / 3600000);
callback(remaining);
_0xsh();
return;
}
var remaining = expiresAt ? Math.floor((new Date(expiresAt) - new Date()) / 3600000) : 0;
if (remaining < 0) remaining = 0;
callback(remaining); // 立即创建面板
var fp = _0xfp();
var token = GM_getValue('license_token', '');
function onVerifySuccess(result) {
GM_setValue('license_expires_at', result.expires_at);
GM_setValue('license_verified_at', Date.now());
GM_setValue('license_token', result.token || '');
_0xups(true, result.remaining_days);
_0xsh();
}
function onVerifyFail(reason) {
if (reason === 'network') {
if (expiresAt && elapsed < 2 * 3600 * 1000) {
_0xsh(); // 继续尝试心跳
} else {
_0xups(false, 0);
_0xm('error', 'network');
}
} else {
_0xups(false, 0);
_0xm('error', reason);
}
}
if (token && !localExpired) {
GM_xmlhttpRequest({
method: 'POST',
url: _0xda + '/heartbeat',
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({ key: key, fingerprint: fp, token: token }),
onload: function(resp) {
try {
var result = JSON.parse(resp.responseText);
if (result.valid) {
onVerifySuccess(result);
} else {
_0xdv(key, fp, onVerifySuccess, onVerifyFail);
}
} catch(e) {
_0xdv(key, fp, onVerifySuccess, onVerifyFail);
}
},
onerror: function() {
onVerifyFail('network');
}
});
} else {
_0xdv(key, fp, onVerifySuccess, onVerifyFail);
}
}
function _0xdv(key, fp, onSuccess, onFail) {
_0xv(key, fp, function(result) {
if (result.valid) {
onSuccess(result);
} else {
onFail(result.reason);
}
});
}
function initWithLicense(remainingDays) {
console.log('[考试助手] 授权验证通过,剩余 ' + _0xfmt(remainingDays));
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
return null;
}
console.log("脚本开始初始化...");
const userId = getCookie('user_id');
const userToken = getCookie('user_token');
const yibanId = getCookie('yiban_id');
console.log("用户信息:", { userId, userToken, yibanId });
if (!userToken) {
console.warn("未能从Cookie中获取 user_token,部分功能(如API请求)可能受限。");
}
if (document.readyState === 'complete') {
createControlPanel(remainingDays);
if (window.location.href.match(/group\/\d+\/exams/)) {
injectExamListStats();
}
autoCollectOnReview();
} else {
window.addEventListener('load', function() {
createControlPanel(remainingDays);
if (window.location.href.match(/group\/\d+\/exams/)) {
injectExamListStats();
}
autoCollectOnReview();
});
}
function isLicenseValid() {
var expiresAt = GM_getValue('license_expires_at', '');
if (!expiresAt) return false;
return new Date(expiresAt).getTime() > Date.now();
}
function checkLicenseWithServer(key, cb) {
GM_xmlhttpRequest({
method: 'POST',
url: _0xda + '/check',
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({ key: key }),
onload: function(resp) {
try {
var result = JSON.parse(resp.responseText);
cb(result);
} catch(e) {
cb({ valid: true });
}
},
onerror: function() {
cb({ valid: true });
}
});
}
function withLicenseCheck(fn, btnEl) {
return function() {
if (!isLicenseValid()) {
_0xm('expired');
return;
}
var origText = btnEl ? btnEl.textContent : '';
if (btnEl) { btnEl.disabled = true; btnEl.textContent = '验证中...'; }
var key = GM_getValue('license_key', '');
checkLicenseWithServer(key, function(result) {
if (btnEl) { btnEl.disabled = false; btnEl.textContent = origText; }
if (!result.valid) {
GM_deleteValue('license_expires_at');
GM_deleteValue('license_token');
GM_deleteValue('license_verified_at');
_0xm('error', result.reason);
return;
}
fn();
});
};
}
function createControlPanel(remainingDays) {
GM_addStyle(`
#helper-panel {
position: fixed;
bottom: 20px;
left: 20px;
background-color: #fff;
border: 1px solid #e5e7eb;
border-radius: 10px;
padding: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 9999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
min-width: 140px;
}
#helper-panel .hp-title {
margin: 0;
font-size: 13px;
font-weight: 600;
color: #333;
text-align: center;
line-height: 1.4;
}
#helper-panel .hp-status {
margin: 0 0 10px 0;
font-size: 11px;
color: #059669;
text-align: center;
}
#helper-panel .hp-status.expired { color: #dc2626; }
#helper-panel button {
display: block;
width: 100%;
padding: 6px 10px;
margin-top: 6px;
border: none;
border-radius: 6px;
background-color: #007bff;
color: white;
font-size: 12px;
cursor: pointer;
transition: background-color 0.2s;
}
#helper-panel button:hover { background-color: #0056b3; }
#helper-panel button:active { background-color: #004a99; }
`);
const panel = document.createElement('div');
panel.id = 'helper-panel';
panel.innerHTML = '考试助手
已激活 剩' + _0xfmt(remainingDays) + '
';
const collectButton = document.createElement('button');
collectButton.innerText = '更新题库';
collectButton.addEventListener('click', withLicenseCheck(runReviewMode, collectButton));
panel.appendChild(collectButton);
const examButton = document.createElement('button');
examButton.innerText = '自动答题';
examButton.addEventListener('click', withLicenseCheck(runExamMode, examButton));
panel.appendChild(examButton);
const importButton = document.createElement('button');
importButton.innerText = '导入题库';
importButton.addEventListener('click', importQuestionBank);
panel.appendChild(importButton);
const forceviewButton = document.createElement('button');
forceviewButton.innerText = '强制看题';
forceviewButton.addEventListener('click', forcevieExam);
panel.appendChild(forceviewButton);
const licenseButton = document.createElement('button');
licenseButton.innerText = '验证卡密';
licenseButton.addEventListener('click', function() { _0xm('enter'); });
panel.appendChild(licenseButton);
document.body.appendChild(panel);
console.log("操作面板已注入。");
}
function injectExamListStats() {
setTimeout(() => {
var root = document.getElementById('root');
if (!root) { console.log('[助手] 未找到 #root'); return; }
var reactKey = Object.keys(root).find(k => k.startsWith('__reactContainer') || k.startsWith('__reactFiber'));
if (!reactKey) { console.log('[助手] 未找到 React fiber key'); return; }
var exams = [];
function walk(fiber, depth) {
if (!fiber || depth > 60) return;
if (fiber.memoizedProps && fiber.memoizedProps.exam) {
var exam = fiber.memoizedProps.exam;
if (!exams.find(function(e) { return e.examId === exam.examId; })) {
exams.push(exam);
}
}
if (fiber.child) walk(fiber.child, depth + 1);
if (fiber.sibling) walk(fiber.sibling, depth + 1);
}
walk(root[reactKey], 0);
console.log('[助手] 找到考试数量:', exams.length);
exams.forEach(function(exam) {
fetchQuestionCount(exam);
});
}, 2000);
}
function fetchQuestionCount(exam) {
GM_xmlhttpRequest({
method: "GET",
url: `${SERVER_BASE}/api/question-bank/download?examId=${exam.examId}`,
onload: function(response) {
let count = 0;
if (response.status >= 200 && response.status < 300) {
try {
const result = JSON.parse(response.responseText);
if (result.questionBank) {
for (const sectionId of Object.keys(result.questionBank)) {
count += Object.keys(result.questionBank[sectionId]).length;
}
}
} catch(e) {}
}
injectStatsToCard(exam, count);
},
onerror: function() {
injectStatsToCard(exam, 0);
}
});
}
function injectStatsToCard(exam, serverCount) {
const articles = document.querySelectorAll('article');
for (const article of articles) {
if (article.querySelector('.exam-helper-stats')) continue;
const h3 = article.querySelector('h3');
if (h3 && h3.innerText.includes(exam.name)) {
const statsEl = document.createElement('span');
statsEl.className = 'exam-helper-stats';
statsEl.style.cssText = 'font-size:12px;color:#4338CA;margin-left:10px;font-weight:normal;';
statsEl.innerHTML = `(题库 ${serverCount} 题)`;
h3.parentElement.appendChild(statsEl);
break;
}
}
}
function autoCollectOnReview() {
var currentUrl = window.location.href;
var reviewMatch = currentUrl.match(/group\/(\d+)\/exam\/(\d+)\/review\/(\d+)/);
if (!reviewMatch) return;
var examId = reviewMatch[2];
var examuserId = reviewMatch[3];
if (!userToken || !examuserId) return;
console.log('[考试助手] 回顾页检测到,自动收集题库...');
GM_xmlhttpRequest({
method: "GET",
url: `https://exambackend.yooc.me/api/exam/answer/get?examuserId=${examuserId}&token=${userToken}&yibanId=${yibanId}`,
headers: { "Cache-Control": "no-cache", "Pragma": "no-cache" },
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
var data = JSON.parse(response.responseText);
handleApiResponseQuiet(data, examId);
}
}
});
}
function handleApiResponseQuiet(data, examId) {
var oldQuestionBank = JSON.parse(localStorage.getItem('question_bank') || '{}');
var newQuestions = {};
var question_count = 0;
if (data.result) {
var question_data = data.data;
for (var i = 0; i < question_data.length; i++) {
var sectionId = question_data[i].sectionId;
newQuestions[sectionId.toString()] = {};
for (var j = 0; j < question_data[i].subjects.length; j++) {
var subjectId = question_data[i].subjects[j].subjectId;
var title = question_data[i].subjects[j].title[0];
var option = question_data[i].subjects[j].option;
var answer = JSON.parse(decrypt(question_data[i].subjects[j].answer, yibanId));
newQuestions[sectionId.toString()][subjectId.toString()] = {"title": title, "option": option, "answer": answer};
}
question_count += Object.keys(newQuestions[sectionId.toString()]).length;
}
}
if (question_count === 0) return;
var mergedQuestionBank = _.merge({}, oldQuestionBank, newQuestions);
localStorage.setItem('question_bank', JSON.stringify(mergedQuestionBank));
console.log('[考试助手] 自动收集完成,新增/更新 ' + question_count + ' 题');
uploadQuestionBank(examId);
var toast = document.createElement('div');
toast.textContent = '题库已自动更新 +' + question_count + ' 题';
toast.style.cssText = 'position:fixed;top:20px;right:20px;background:#059669;color:#fff;padding:10px 20px;border-radius:8px;z-index:99999;font-size:14px;box-shadow:0 4px 12px rgba(0,0,0,0.2);transition:opacity 0.5s;';
document.body.appendChild(toast);
setTimeout(function() { toast.style.opacity = '0'; }, 2000);
setTimeout(function() { toast.remove(); }, 2500);
}
function forcevieExam(){
const currentUrl = window.location.href;
const reviewMatch = currentUrl.match(/group\/(\d+)\/exam\/(\d+)\/review\/(\d+)/);
if (reviewMatch) {
alert("当前已在回顾页,可直接点击「更新题库」收集答案。");
return;
}
const resultMatch = currentUrl.match(/group\/(\d+)\/exam\/(\d+)/);
if (!resultMatch) {
alert("请先打开一场考试的结果页或回顾页,再点击「强制看题」。");
return;
}
const groupId = resultMatch[1];
const examId = resultMatch[2];
console.log("考试ID:", examId);
GM_xmlhttpRequest({
method: "GET",
url: `https://exambackend.yooc.me/api/exam/result/get?userId=${userId}&token=${userToken}&yibanId=${yibanId}&examId=${examId}`,
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
const responseData = JSON.parse(response.responseText);
if(responseData.result){
const examuserId = responseData.data.examuserId;
location.href = `https://exam.yooc.me/group/${groupId}/exam/${examId}/review/${examuserId}`
}
else{
alert("未能成功获取 examuserId,可能是还未参加过这场考试。");
}
}
else{
alert("接口请求失败: " + response.status);
console.error("接口请求失败:", response.status, response.statusText, response.responseText);
}
}
})
}
function importQuestionBank() {
importJson(function(error, data) {
if (error) {
console.error("导入题库失败:", error);
alert("导入失败: " + error.message);
return;
}
const oldQuestionBank = JSON.parse(localStorage.getItem('question_bank') || '{}');
const mergedQuestionBank = _.merge({}, oldQuestionBank, data);
localStorage.setItem('question_bank', JSON.stringify(mergedQuestionBank));
const message = `题库导入成功!\n导入题库数: ${Object.keys(data).length}\n题库总数: ${Object.keys(mergedQuestionBank).length}`;
console.log(message.replace(/\n/g, ' '));
alert(message);
});
}
const SERVER_BASE = 'http://8.138.223.11:8088';
function uploadQuestionBank(examId) {
const questionBank = JSON.parse(localStorage.getItem('question_bank') || '{}');
if (Object.keys(questionBank).length === 0) {
console.log("题库为空,跳过上传。");
return;
}
GM_xmlhttpRequest({
method: "POST",
url: `${SERVER_BASE}/api/question-bank/upload`,
headers: { "Content-Type": "application/json" },
data: JSON.stringify({ examId: examId, questionBank: questionBank }),
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
const result = JSON.parse(response.responseText);
console.log("题库上传成功:", result.message);
} else {
console.error("题库上传失败:", response.status, response.responseText);
}
},
onerror: function(error) {
console.error("题库上传请求出错:", error);
}
});
}
function downloadQuestionBank(examId, callback) {
GM_xmlhttpRequest({
method: "GET",
url: `${SERVER_BASE}/api/question-bank/download?examId=${examId}`,
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
const result = JSON.parse(response.responseText);
if (result.success && result.questionBank) {
const oldQuestionBank = JSON.parse(localStorage.getItem('question_bank') || '{}');
const mergedQuestionBank = _.merge({}, oldQuestionBank, result.questionBank);
localStorage.setItem('question_bank', JSON.stringify(mergedQuestionBank));
console.log(`题库下载成功,已合并。服务器题库键数: ${Object.keys(result.questionBank).length},合并后总数: ${Object.keys(mergedQuestionBank).length}`);
} else {
console.log("服务器未找到该考试的题库,将使用本地题库。");
}
} else if (response.status === 404) {
console.log("服务器未找到该考试的题库,将使用本地题库。");
} else {
console.error("题库下载失败:", response.status, response.responseText);
}
if (callback) callback();
},
onerror: function(error) {
console.error("题库下载请求出错:", error);
if (callback) callback();
}
});
}
function importJson(callback) {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.style.display = 'none';
document.body.appendChild(input);
input.addEventListener('change', function(event) {
const file = event.target.files[0];
if (!file) {
callback(new Error('未选择文件'));
return;
}
const reader = new FileReader();
reader.onload = function(e) {
try {
const data = JSON.parse(e.target.result);
callback(null, data);
} catch (error) {
callback(error);
}
};
reader.onerror = function() {
callback(new Error('文件读取失败'));
};
reader.readAsText(file);
});
input.click();
document.body.removeChild(input);
}
function runExamMode() {
try {
const currentUrl = window.location.href;
const examIdMatch = currentUrl.match(/exam\/(\d+)/);
const examId = examIdMatch ? examIdMatch[1] : null;
console.log("考试ID:", examId);
if (!examId) {
alert("未能从URL中解析出 examId");
return;
}
console.log("正在从服务器下载题库...");
downloadQuestionBank(examId, function() {
console.log("题库下载完成,开始自动答题...");
GM_xmlhttpRequest({
method: "GET",
url: `https://exambackend.yooc.me/api/exam/setting/get?examId=${examId}&userId=${userId}&token=${userToken}&yibanId=${yibanId}`,
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
const responseData = JSON.parse(response.responseText);
if(responseData.result){
const examuserId = responseData.data.examuserId;
if (Object.hasOwn(localStorage, `exam-paper-${examuserId}`)){
handleApiResponse2write(JSON.parse(localStorage.getItem(`exam-paper-${examuserId}`)).value.paper);
}
else{
GM_xmlhttpRequest({
method: "GET",
url: `https://exambackend.yooc.me/api/exam/paper/get?examuserId=${examuserId}&token=${userToken}&yibanId=${yibanId}`,
headers: { "Cache-Control": "no-cache", "Pragma": "no-cache" },
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
const responseData = JSON.parse(response.responseText);
if (responseData.result){
handleApiResponse2write(responseData.data);
} else {
alert("题目获取失败!");
}
} else {
console.error("接口请求失败:", response.status);
}
},
onerror: function(error) { console.error(error); }
});
}
} else {
alert("获取 examuserId 失败");
}
} else {
console.error("接口请求失败:", response.status);
}
},
onerror: function(error) { console.error(error); }
});
});
} catch (error) {
console.error("自动答题脚本出错:", error);
}
}
function runReviewMode() {
try {
const currentUrl = window.location.href;
const examIdMatch = currentUrl.match(/exam\/(\d+)/);
const examId = examIdMatch ? examIdMatch[1] : null;
console.log("考试ID:", examId);
if (!examId) {
console.error("未能从URL中解析出 examId,无法执行操作。");
alert("未能从URL中解析出 examId,无法执行操作。");
return;
}
const examuserIdMatch = currentUrl.match(/review\/(\d+)$/);
const examuserId = examuserIdMatch ? examuserIdMatch[1] : null;
console.log("考试用户ID:", examuserId);
const apiUrl = `https://exambackend.yooc.me/api/exam/answer/get?examuserId=${examuserId}&token=${userToken}&yibanId=${yibanId}`;
if (!apiUrl || !examId) {
console.error("未能构造有效的接口URL,请检查脚本。");
return;
}
console.log("正在请求接口:", apiUrl);
GM_xmlhttpRequest({
method: "GET",
url: apiUrl,
headers: {
"Cache-Control": "no-cache",
"Pragma": "no-cache"
},
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
const responseData = JSON.parse(response.responseText);
handleApiResponse(responseData, examId);
} else {
console.error("接口请求失败:", response.status, response.statusText, response.responseText);
}
},
onerror: function(error) {
console.error("接口请求发生错误:", error);
}
});
} catch (error) {
console.error("收集题库脚本出错:", error);
}
}
function handleApiResponse2write(data) {
const questionBank = JSON.parse(localStorage.getItem('question_bank') || '{}');
const bottomEls = document.getElementsByClassName("jsx-3527395752 __ pa-xs flex items-center fs-l");
const bottom = bottomEls[0];
const last = bottom.getElementsByClassName("jsx-3527395752 p")[0];
let span = bottom.getElementsByTagName("span");
let count_list = span[0].innerText.split("/");
for (let a = 0; a < count_list[0]; a++) last.click();
for (let i=0;i当前版本 v' + _0xverCurrent + ',需要更新后才能使用
立即更新 ';
document.body.appendChild(overlay);
document.getElementById('exam-helper-update-close').onclick = function() { overlay.remove(); };
}
function _0xver() {
GM_xmlhttpRequest({
method: 'GET',
url: _0xverUrl,
onload: function(resp) {
if (resp.status !== 200) { _0xrun(initWithLicense); return; }
var match = resp.responseText.match(/@version\s+([\d.]+)/);
if (!match) { _0xrun(initWithLicense); return; }
var serverVersion = match[1];
var cur = _0xverCurrent.split('.').map(Number);
var srv = serverVersion.split('.').map(Number);
for (var i = 0; i < Math.max(cur.length, srv.length); i++) {
var c = cur[i] || 0, s = srv[i] || 0;
if (s > c) {
_0xshowBlockOverlay(serverVersion);
return;
}
if (s < c) break;
}
_0xrun(initWithLicense);
},
onerror: function() { _0xrun(initWithLicense); }
});
}
_0xver();
})();