// ==UserScript==
// @name Welearn助手
// @namespace https://bbs.tampermonkey.net.cn/
// @version 3.0.0
// @author Wei
// @description 自动选择Welearn平台的选择题、判断题、填空题和下拉框答案
// @match *://welearn.sflep.com/Student/StudyCourse.aspx*
// @match *://welearn.sflep.com/student/StudyCourse.aspx*
// @match *://welearn.sflep.com/student/studyCourse.aspx*
// @match *://welearn.sflep.com/student/Studycourse.aspx*
// @match *://welearn.sflep.com/student/studycourse.aspx*
// @match *://welearn.sflep.com/course/trycourse.aspx*
// @match *://welearn.sflep.com/course/Trycourse.aspx*
// @match *://welearn.sflep.com/course/TryCourse.aspx*
// @match *://welearn.sflep.com/*/TryCourse.aspx*
// @grant GM_getValue
// @grant GM_setValue
// @grant unsafeWindow
// ==/UserScript==
(function () {
'use strict';
// ==================== 版权弹窗 ====================
function showCopyrightDialog() {
if (document.getElementById('welearnCopyrightOverlay')) return;
const overlay = document.createElement('div');
overlay.id = 'welearnCopyrightOverlay';
overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.55);z-index:2147483647;display:flex;align-items:center;justify-content:center;';
const dialog = document.createElement('div');
dialog.style.cssText = 'background:rgba(28,32,34,0.97);padding:30px;border-radius:16px;box-shadow:0 16px 48px rgba(0,0,0,0.5);color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;max-width:420px;text-align:center;border:1px solid rgba(255,255,255,0.12);';
dialog.innerHTML =
'
🎯
' +
'Welearn助手 v3.0
' +
'Copyright © 2026 Wei
' +
'' +
'
📌 自动完成选择题、判断题、填空题、下拉框、连线题等
' +
'
🔄 支持自动挂机循环答题模式
' +
'
🎨 多款UI主题皮肤
' +
'
⏭️ 自动跳转下一章节
' +
'
' +
'' +
'⚠️ 本工具仅供学习交流使用,请勿用于违规行为。
使用者需自行承担所有风险与责任。' +
'
' +
'';
overlay.appendChild(dialog);
document.body.appendChild(overlay);
function closeDialog() {
overlay.style.opacity = '0';
overlay.style.transition = 'opacity 0.3s';
setTimeout(function () { if (overlay.parentNode) overlay.remove(); }, 300);
}
document.getElementById('welearnCopyrightBtn').addEventListener('click', closeDialog);
overlay.addEventListener('click', function (e) { if (e.target === overlay) closeDialog(); });
}
// 确保 body 存在后立即弹出
(function waitForBody() {
if (document.body && document.body.children.length > 0) {
showCopyrightDialog();
} else {
setTimeout(waitForBody, 100);
}
})();
const themePresets = {
default: {
name: '默认绿色',
primaryColor: '#4CAF50',
secondaryColor: '#45a049',
backgroundColor: 'rgba(28, 32, 34, 0.92)',
textColor: '#e0e0e0',
buttonColor: '#26a69a',
buttonHoverColor: '#2ab7a9',
accentColor: '#5c6bc0',
borderColor: 'rgba(255, 255, 255, 0.12)'
},
pink: {
name: '少女粉',
primaryColor: '#FF69B4',
secondaryColor: '#FF1493',
backgroundColor: 'rgba(28, 32, 34, 0.92)',
textColor: '#FFB6C1',
buttonColor: '#FF69B4',
buttonHoverColor: '#FF1493',
accentColor: '#DDA0DD',
borderColor: 'rgba(255, 192, 203, 0.3)'
},
sakura: {
name: '樱花粉',
primaryColor: '#FFB7C5',
secondaryColor: '#FF9EB5',
backgroundColor: 'rgba(28, 32, 34, 0.92)',
textColor: '#FFE4E1',
buttonColor: '#FF8DA1',
buttonHoverColor: '#FF7B93',
accentColor: '#FFB5C5',
borderColor: 'rgba(255, 228, 225, 0.3)'
},
coral: {
name: '珊瑚橙',
primaryColor: '#FF7F50',
secondaryColor: '#FF6347',
backgroundColor: 'rgba(28, 32, 34, 0.92)',
textColor: '#FFA07A',
buttonColor: '#FF8C69',
buttonHoverColor: '#FF7256',
accentColor: '#FA8072',
borderColor: 'rgba(255, 160, 122, 0.3)'
},
blue: {
name: '深邃蓝',
primaryColor: '#1976D2',
secondaryColor: '#1565C0',
backgroundColor: 'rgba(28, 32, 34, 0.92)',
textColor: '#90CAF9',
buttonColor: '#2196F3',
buttonHoverColor: '#1976D2',
accentColor: '#64B5F6',
borderColor: 'rgba(144, 202, 249, 0.3)'
},
ocean: {
name: '海洋蓝',
primaryColor: '#00CED1',
secondaryColor: '#20B2AA',
backgroundColor: 'rgba(28, 32, 34, 0.92)',
textColor: '#E0FFFF',
buttonColor: '#48D1CC',
buttonHoverColor: '#40E0D0',
accentColor: '#00CED1',
borderColor: 'rgba(224, 255, 255, 0.3)'
},
purple: {
name: '神秘紫',
primaryColor: '#9C27B0',
secondaryColor: '#7B1FA2',
backgroundColor: 'rgba(28, 32, 34, 0.92)',
textColor: '#E1BEE7',
buttonColor: '#BA68C8',
buttonHoverColor: '#AB47BC',
accentColor: '#9575CD',
borderColor: 'rgba(225, 190, 231, 0.3)'
},
lavender: {
name: '薰衣草',
primaryColor: '#9B59B6',
secondaryColor: '#8E44AD',
backgroundColor: 'rgba(28, 32, 34, 0.92)',
textColor: '#D7BDE2',
buttonColor: '#AF7AC5',
buttonHoverColor: '#A569BD',
accentColor: '#BB8FCE',
borderColor: 'rgba(215, 189, 226, 0.3)'
},
mint: {
name: '薄荷绿',
primaryColor: '#00B894',
secondaryColor: '#00A885',
backgroundColor: 'rgba(28, 32, 34, 0.92)',
textColor: '#E0F2F1',
buttonColor: '#1ABC9C',
buttonHoverColor: '#16A085',
accentColor: '#4DB6AC',
borderColor: 'rgba(224, 242, 241, 0.3)'
},
gold: {
name: '金色',
primaryColor: '#F1C40F',
secondaryColor: '#F39C12',
backgroundColor: 'rgba(28, 32, 34, 0.92)',
textColor: '#FFF3E0',
buttonColor: '#FFB300',
buttonHoverColor: '#FFA000',
accentColor: '#FFD700',
borderColor: 'rgba(255, 243, 224, 0.3)'
},
dark: {
name: '暗夜黑',
primaryColor: '#424242',
secondaryColor: '#212121',
backgroundColor: 'rgba(18, 18, 18, 0.95)',
textColor: '#BDBDBD',
buttonColor: '#616161',
buttonHoverColor: '#757575',
accentColor: '#9E9E9E',
borderColor: 'rgba(189, 189, 189, 0.2)'
}
};
let currentTheme = GM_getValue('welearnTheme', themePresets.default);
function createThemePanel() {
const themePanel = document.createElement('div');
themePanel.id = 'welearnThemePanel';
themePanel.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: ${currentTheme.backgroundColor};
padding: 20px;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
z-index: 10001;
display: none;
width: 300px;
color: ${currentTheme.textColor};
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
`;
const title = document.createElement('h3');
title.textContent = '主题选择';
title.style.cssText = `
margin: 0 0 20px 0;
color: ${currentTheme.textColor};
text-align: center;
font-size: 18px;
`;
const themesContainer = document.createElement('div');
themesContainer.style.cssText = `
display: grid;
gap: 10px;
grid-template-columns: repeat(2, 1fr);
`;
Object.entries(themePresets).forEach(([key, theme]) => {
const themeButton = document.createElement('button');
themeButton.textContent = theme.name;
themeButton.style.cssText = `
padding: 15px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
background: linear-gradient(135deg, ${theme.primaryColor}, ${theme.secondaryColor});
color: ${theme.textColor};
position: relative;
overflow: hidden;
`;
themeButton.addEventListener('mouseover', () => {
themeButton.style.transform = 'scale(1.05)';
themeButton.style.boxShadow = `0 4px 15px ${theme.primaryColor}40`;
});
themeButton.addEventListener('mouseout', () => {
themeButton.style.transform = 'scale(1)';
themeButton.style.boxShadow = 'none';
});
themeButton.addEventListener('click', () => {
currentTheme = { ...theme };
GM_setValue('welearnTheme', currentTheme);
updateUIColors();
themePanel.style.display = 'none';
});
themesContainer.appendChild(themeButton);
});
const closeButton = document.createElement('button');
closeButton.textContent = '关闭';
closeButton.style.cssText = `
padding: 8px 16px;
background: #666;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 20px;
width: 100%;
`;
closeButton.addEventListener('click', () => {
themePanel.style.display = 'none';
});
themePanel.appendChild(title);
themePanel.appendChild(themesContainer);
themePanel.appendChild(closeButton);
return themePanel;
}
function updateUIColors() {
const panel = document.getElementById('welearnHelperPanel');
if (!panel) return;
panel.style.background = currentTheme.backgroundColor;
panel.style.color = currentTheme.textColor;
panel.style.borderColor = currentTheme.borderColor;
const titleBar = panel.querySelector('.welearn-title-bar');
if (titleBar) {
titleBar.style.background = `linear-gradient(135deg,
${currentTheme.primaryColor}15,
${currentTheme.secondaryColor}10,
${currentTheme.primaryColor}15)`;
}
const buttons = panel.querySelectorAll('.welearn-button');
buttons.forEach(button => {
if (button.classList.contains('welearn-auto-button')) {
button.style.background = `linear-gradient(135deg, ${currentTheme.buttonColor} 0%, ${currentTheme.buttonHoverColor} 100%)`;
} else if (button.classList.contains('welearn-loop-button')) {
button.style.background = `linear-gradient(135deg, ${currentTheme.accentColor} 0%, ${adjustColor(currentTheme.accentColor, -20)} 100%)`;
}
});
const containers = panel.querySelectorAll('.welearn-timer-container, .welearn-accuracy-container');
containers.forEach(container => {
container.style.borderColor = currentTheme.borderColor;
});
const inputs = panel.querySelectorAll('.welearn-time-input, .welearn-accuracy-input');
inputs.forEach(input => {
input.style.borderColor = currentTheme.borderColor;
input.style.color = currentTheme.textColor;
});
}
function adjustColor(color, amount) {
return color.replace(/^#/, '').match(/.{2}/g).map(hex => {
const num = Math.min(255, Math.max(0, parseInt(hex, 16) + amount));
return num.toString(16).padStart(2, '0');
}).join('');
}
function addThemeButton() {
const panel = document.getElementById('welearnHelperPanel');
if (!panel) return;
const themeButton = document.createElement('button');
themeButton.className = 'welearn-theme-button';
themeButton.innerHTML = '🎨';
themeButton.style.cssText = `
position: absolute;
top: 16px;
right: 50px;
background: none;
border: none;
color: ${currentTheme.textColor};
font-size: 18px;
cursor: pointer;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 50%;
transition: all 0.3s ease;
`;
themeButton.addEventListener('mouseover', () => {
themeButton.style.transform = 'rotate(180deg) scale(1.1)';
});
themeButton.addEventListener('mouseout', () => {
themeButton.style.transform = 'none';
});
themeButton.addEventListener('click', () => {
let themePanel = document.getElementById('welearnThemePanel');
if (!themePanel) {
themePanel = createThemePanel();
document.body.appendChild(themePanel);
}
themePanel.style.display = themePanel.style.display === 'none' ? 'block' : 'none';
});
const titleBar = panel.querySelector('.welearn-title-bar');
titleBar.appendChild(themeButton);
}
const originalCreateIntegratedUI = createIntegratedUI;
createIntegratedUI = function () {
const panel = originalCreateIntegratedUI();
updateUIColors();
return panel;
};
const originalAlert = window.alert;
const originalConfirm = window.confirm;
const originalPrompt = window.prompt;
window.alert = function (message) {
console.log('拦截到alert弹窗:', message);
return true;
};
window.confirm = function (message) {
console.log('拦截到confirm弹窗:', message);
return true;
};
window.prompt = function (message, defaultValue) {
console.log('拦截到prompt弹窗:', message);
return defaultValue || '';
};
function handleIframeDialogs(iframe) {
try {
if (iframe.contentWindow) {
iframe.contentWindow.alert = window.alert;
iframe.contentWindow.confirm = window.confirm;
iframe.contentWindow.prompt = window.prompt;
}
} catch (e) {
console.log('处理iframe弹窗失败:', e);
}
}
function setupIframeDialogHandlers() {
const iframes = document.getElementsByTagName('iframe');
for (const iframe of iframes) {
iframe.addEventListener('load', () => {
try {
handleIframeDialogs(iframe);
} catch (error) {
if (error.name === 'SecurityError') {
console.log('允许iframe跨域错误:', error.message);
} else {
throw error;
}
}
});
try {
handleIframeDialogs(iframe);
} catch (error) {
if (error.name === 'SecurityError') {
console.log('允许iframe跨域错误:', error.message);
} else {
throw error;
}
}
}
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.tagName === 'IFRAME') {
node.addEventListener('load', () => {
try {
handleIframeDialogs(node);
} catch (error) {
if (error.name === 'SecurityError') {
console.log('允许iframe跨域错误:', error.message);
} else {
throw error;
}
}
});
try {
handleIframeDialogs(node);
} catch (error) {
if (error.name === 'SecurityError') {
console.log('允许iframe跨域错误:', error.message);
} else {
throw error;
}
}
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
setupIframeDialogHandlers();
const originalWindow = window;
setInterval(() => {
if (window.alert !== originalWindow.alert) {
window.alert = originalWindow.alert;
}
if (window.confirm !== originalWindow.confirm) {
window.confirm = originalWindow.confirm;
}
if (window.prompt !== originalWindow.prompt) {
window.prompt = originalWindow.prompt;
}
const iframes = document.getElementsByTagName('iframe');
for (const iframe of iframes) {
handleIframeDialogs(iframe);
}
}, 1000);
window.isAutoLoopActive = false;
window.autoLoopTimeout = null;
window.waitTimerActive = false;
window.waitTimerInterval = null;
window.defaultWaitTime = 30;
window.defaultAccuracyRate = 100;
window.autoNextEnabled = true;
window.autoSubmitEnabled = true;
const createEvent = (type, options = {}) => new Event(type, { bubbles: true, cancelable: true, ...options });
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
async function autoFillAnswers() {
console.log('开始自动填空');
let filled = false;
const answerElements = document.querySelectorAll('span.key, div.key');
const inputBoxes = document.querySelectorAll('span[autocapitalize="none"], textarea.blank');
const dataInputs = document.querySelectorAll('input[data-itemtype="input"], textarea[data-itemtype="input"]');
const textareaInputs = document.querySelectorAll('textarea[data-itemtype="textarea"]');
const editableSpans = document.querySelectorAll('span[contenteditable][ng-class*="blank.isOverflowed"]');
const totalInputs = inputBoxes.length + dataInputs.length + textareaInputs.length + editableSpans.length;
console.log('找到填空题数量:', totalInputs);
let correctCount, wrongCount;
let wrongIndices = new Set();
if (totalInputs > 5) {
correctCount = Math.ceil((totalInputs * window.defaultAccuracyRate) / 100);
wrongCount = totalInputs - correctCount;
console.log(`总题目: ${totalInputs}, 正确数: ${correctCount}, 错误数: ${wrongCount}`);
while (wrongIndices.size < wrongCount) {
const randomIndex = Math.floor(Math.random() * totalInputs);
wrongIndices.add(randomIndex);
}
}
let currentIndex = 0;
if (inputBoxes.length > 0) {
for (let index = 0; index < answerElements.length; index++) {
try {
const answer = answerElements[index].textContent;
const inputBox = inputBoxes[index];
if (!inputBox) continue;
let finalAnswer;
if (totalInputs > 5 && wrongIndices.has(currentIndex)) {
finalAnswer = answer + String.fromCharCode(65 + Math.floor(Math.random() * 26));
console.log(`准备填写第${currentIndex + 1}题错误答案:`, finalAnswer);
} else {
finalAnswer = answer;
console.log(`准备填写第${currentIndex + 1}题正确答案:`, finalAnswer);
}
if (inputBox.tagName === 'SPAN') {
inputBox.textContent = finalAnswer;
} else {
inputBox.value = finalAnswer;
}
['click', 'focus', 'input', 'change', 'blur'].forEach(evt =>
inputBox.dispatchEvent(createEvent(evt))
);
filled = true;
await sleep(200);
currentIndex++;
} catch (error) {
console.log(`填写第${currentIndex + 1}题出错:`, error);
}
}
}
if (dataInputs.length > 0) {
for (let i = 0; i < dataInputs.length; i++) {
try {
const input = dataInputs[i];
let answer = input.getAttribute('data-solution');
if (!answer) {
const parentDiv = input.closest('div');
if (parentDiv) {
const resultDiv = parentDiv.querySelector('div[data-itemtype="result"]');
if (resultDiv) {
answer = resultDiv.textContent.trim();
}
}
}
if (!answer) continue;
let finalAnswer;
if (totalInputs > 5 && wrongIndices.has(currentIndex)) {
finalAnswer = answer + String.fromCharCode(65 + Math.floor(Math.random() * 26));
console.log(`准备填写第${currentIndex + 1}题错误答案:`, finalAnswer);
} else {
finalAnswer = answer;
console.log(`准备填写第${currentIndex + 1}题正确答案:`, finalAnswer);
}
if (input.tagName === 'INPUT') {
input.value = finalAnswer;
} else {
input.textContent = finalAnswer;
}
['click', 'focus', 'input', 'change', 'blur'].forEach(evt =>
input.dispatchEvent(createEvent(evt))
);
filled = true;
await sleep(200);
currentIndex++;
} catch (error) {
console.log(`处理data-solution题目出错:`, error);
}
}
}
if (textareaInputs.length > 0) {
for (let i = 0; i < textareaInputs.length; i++) {
try {
const textarea = textareaInputs[i];
let answer = textarea.getAttribute('data-solution');
if (!answer) {
const parentDiv = textarea.closest('div');
if (parentDiv) {
const resultDiv = parentDiv.querySelector('div[data-itemtype="result"]');
if (resultDiv) {
answer = resultDiv.textContent.trim();
}
}
}
if (!answer) continue;
let finalAnswer;
if (totalInputs > 5 && wrongIndices.has(currentIndex)) {
finalAnswer = answer + String.fromCharCode(65 + Math.floor(Math.random() * 26));
console.log(`准备填写第${currentIndex + 1}题错误答案:`, finalAnswer);
} else {
finalAnswer = answer;
console.log(`准备填写第${currentIndex + 1}题正确答案:`, finalAnswer);
}
textarea.value = finalAnswer;
['click', 'focus', 'input', 'change', 'blur'].forEach(evt =>
textarea.dispatchEvent(createEvent(evt))
);
if (textarea.style.height) {
const computedStyle = window.getComputedStyle(textarea);
const height = computedStyle.height;
textarea.style.height = height;
}
filled = true;
await sleep(200);
currentIndex++;
} catch (error) {
console.log(`处理textarea题目出错:`, error);
}
}
}
if (editableSpans.length > 0) {
for (let i = 0; i < editableSpans.length; i++) {
try {
const span = editableSpans[i];
const answerSpan = span.nextElementSibling?.querySelector('span.key[ng-if="blank.hasKey"]') ||
span.parentElement?.querySelector('span.key[ng-if="blank.hasKey"]');
if (!answerSpan) continue;
let answer = answerSpan.textContent.trim();
if (!answer) continue;
let finalAnswer;
if (totalInputs > 5 && wrongIndices.has(currentIndex)) {
finalAnswer = answer + String.fromCharCode(65 + Math.floor(Math.random() * 26));
console.log(`准备填写第${currentIndex + 1}题错误答案:`, finalAnswer);
} else {
finalAnswer = answer;
console.log(`准备填写第${currentIndex + 1}题正确答案:`, finalAnswer);
}
span.textContent = finalAnswer;
['click', 'focus', 'input', 'change', 'blur'].forEach(evt =>
span.dispatchEvent(createEvent(evt))
);
if (typeof angular !== 'undefined') {
try {
const scope = angular.element(span).scope();
if (scope && scope.blank) {
scope.blank.value = finalAnswer;
scope.$apply();
}
} catch (e) {
console.log('Angular数据绑定更新失败:', e);
}
}
filled = true;
await sleep(200);
currentIndex++;
} catch (error) {
console.log(`处理contenteditable span题目出错:`, error);
}
}
}
await sleep(500);
console.log('开始对每个填空框进行点击操作');
const allInputs = [...inputBoxes, ...dataInputs, ...textareaInputs, ...editableSpans];
allInputs.forEach((inputBox, index) => {
try {
if (!document.body.contains(inputBox)) return;
console.log(`对第${index + 1}个填空框进行点击操作`);
inputBox.dispatchEvent(createEvent('click'));
inputBox.dispatchEvent(createEvent('focus'));
setTimeout(() => {
inputBox.dispatchEvent(createEvent('blur'));
}, 50);
} catch (error) {
console.log(`额外点击第${index + 1}个填空框出错:`, error);
}
});
console.log('填空题处理完成');
return filled;
}
function isElementInViewport(el) {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
async function handleUnitTestAnswers() {
console.log('开始处理单元测试题目');
let answered = false;
try {
const resultSpans = document.querySelectorAll('span.unittestresult');
if (resultSpans.length > 0) {
console.log('找到单元测试题目数量:', resultSpans.length);
const totalQuestions = resultSpans.length;
const correctCount = Math.ceil((totalQuestions * window.defaultAccuracyRate) / 100);
const wrongCount = totalQuestions - correctCount;
console.log(`总题目: ${totalQuestions}, 正确数: ${correctCount}, 错误数: ${wrongCount}`);
const wrongIndices = new Set();
while (wrongIndices.size < wrongCount) {
const randomIndex = Math.floor(Math.random() * totalQuestions);
wrongIndices.add(randomIndex);
}
let currentIndex = 0;
for (const span of resultSpans) {
try {
const resultDiv = span.querySelector('div[data-itemtype="result"]');
if (!resultDiv) continue;
const answer = resultDiv.textContent.trim();
console.log('找到答案:', answer);
const questionText = span.closest('div[data-controltype="filling"]')?.querySelector('span[data-itemtype="sn"]')?.textContent.trim();
if (!questionText) continue;
const answerSheets = document.querySelectorAll('div.AnswerSheet');
for (const sheet of answerSheets) {
const sheetTitle = sheet.querySelector('h6.unit_test_answersheet_tit')?.textContent.trim();
if (sheetTitle && sheetTitle.includes(questionText)) {
const options = Array.from(sheet.querySelectorAll('ul.AnswerSheet_chooseBox li h5'));
if (wrongIndices.has(currentIndex)) {
const wrongOptions = options.filter(opt => opt.textContent.trim() !== answer);
if (wrongOptions.length > 0) {
const randomWrongOption = wrongOptions[Math.floor(Math.random() * wrongOptions.length)];
randomWrongOption.click();
console.log(`第${currentIndex + 1}题选择错误答案: ${randomWrongOption.textContent.trim()}`);
} else {
const correctOption = options.find(opt => opt.textContent.trim() === answer);
if (correctOption) {
correctOption.click();
console.log(`第${currentIndex + 1}题未找到错误选项,选择正确答案: ${answer}`);
}
}
} else {
const correctOption = options.find(opt => opt.textContent.trim() === answer);
if (correctOption) {
correctOption.click();
console.log(`第${currentIndex + 1}题选择正确答案: ${answer}`);
}
}
answered = true;
await sleep(300);
break;
}
}
currentIndex++;
} catch (error) {
console.log('处理单个题目时出错:', error);
}
}
}
} catch (error) {
console.log('处理单元测试题目出错:', error);
}
return answered;
}
async function autoSelectCorrectAnswers() {
console.log('开始自动选择答案');
let allQuestions = [];
let answered = false;
const choiceQuestions = document.querySelectorAll('et-choice[et-index]');
const tfQuestions = document.querySelectorAll('et-tof[et-index]');
const newChoiceQuestions = document.querySelectorAll('div[data-controltype="choice"]');
allQuestions = [...choiceQuestions, ...tfQuestions, ...newChoiceQuestions];
const totalQuestions = allQuestions.length;
console.log('找到选择/判断题数量:', totalQuestions);
if (totalQuestions <= 5) {
for (const question of allQuestions) {
await handleQuestion(question, true);
}
} else {
const correctCount = Math.ceil((totalQuestions * window.defaultAccuracyRate) / 100);
const wrongCount = totalQuestions - correctCount;
console.log(`总题目: ${totalQuestions}, 正确数: ${correctCount}, 错误数: ${wrongCount}`);
const wrongIndices = new Set();
while (wrongIndices.size < wrongCount) {
const randomIndex = Math.floor(Math.random() * totalQuestions);
wrongIndices.add(randomIndex);
}
for (let i = 0; i < allQuestions.length; i++) {
const question = allQuestions[i];
const shouldBeCorrect = !wrongIndices.has(i);
await handleQuestion(question, shouldBeCorrect);
await sleep(300);
}
}
console.log('选择/判断题处理完成');
}
async function handleQuestion(question, shouldBeCorrect) {
const questionType = question.tagName.toLowerCase() === 'et-tof' ? 'tof' :
question.hasAttribute('data-controltype') ? 'new-choice' : 'choice';
let options = [];
if (questionType === 'tof') {
options = question.querySelectorAll('span[ng-class*="chosen:tof.value"], div.wrapper span');
} else if (questionType === 'new-choice') {
const optionsList = question.querySelector('ul[data-itemtype="options"]');
if (optionsList) {
options = Array.from(optionsList.querySelectorAll('li'));
const correctOption = optionsList.querySelector('li[data-solution]');
if (correctOption && shouldBeCorrect) {
try {
correctOption.click();
console.log('找到并选择了正确答案:', correctOption.textContent.trim());
await sleep(300);
return;
} catch (error) {
console.log('点击正确选项出错:', error);
}
} else if (!shouldBeCorrect && options.length > 0) {
const wrongOptions = Array.from(options).filter(opt => opt !== correctOption);
if (wrongOptions.length > 0) {
const randomWrongOption = wrongOptions[Math.floor(Math.random() * wrongOptions.length)];
try {
randomWrongOption.click();
console.log('已选择错误答案:', randomWrongOption.textContent.trim());
await sleep(300);
return;
} catch (error) {
console.log('选择错误答案时出错:', error);
}
}
}
}
} else {
options = question.querySelectorAll('ol > li, div.wrapper li, span[ng-click*="choice.select"], li[class]');
}
if (options.length === 0) {
console.log('未找到选项,跳过');
return;
}
if (shouldBeCorrect) {
for (const option of options) {
try {
option.click();
await sleep(300);
const wrapper = questionType === 'tof'
? option.closest('div[ng-class*="isKeyVisible"]')
: option.closest('div.wrapper');
if (wrapper?.classList.contains('correct')) {
console.log('找到并选择了正确答案:', option.textContent.trim());
break;
}
} catch (error) {
console.log('点击选项出错:', error);
}
}
} else {
let correctOption = null;
for (const option of options) {
try {
option.click();
await sleep(300);
const wrapper = questionType === 'tof'
? option.closest('div[ng-class*="isKeyVisible"]')
: option.closest('div.wrapper');
if (wrapper?.classList.contains('correct')) {
correctOption = option;
break;
}
} catch (error) {
console.log('查找正确答案时出错:', error);
}
}
if (correctOption) {
const wrongOptions = Array.from(options).filter(opt => opt !== correctOption);
if (wrongOptions.length > 0) {
const randomWrongOption = wrongOptions[Math.floor(Math.random() * wrongOptions.length)];
try {
randomWrongOption.click();
console.log('已选择错误答案:', randomWrongOption.textContent.trim());
} catch (error) {
console.log('选择错误答案时出错:', error);
}
}
}
}
await sleep(300);
}
function autoSelectDropdownAnswers() {
console.log('开始处理下拉框选择题');
const dropdowns = document.querySelectorAll('select[ng-model]');
console.log('找到下拉框数量:', dropdowns.length);
if (dropdowns.length === 0) return;
dropdowns.forEach((dropdown, index) => {
try {
const correctOption = dropdown.querySelector('option.key');
if (!correctOption) return;
dropdown.value = correctOption.value;
dropdown.dispatchEvent(createEvent('change'));
console.log(`已选择第${index + 1}个下拉框答案:`, correctOption.textContent.trim());
} catch (error) {
console.log(`处理第${index + 1}个下拉框出错:`, error);
}
});
}
async function autoMatchLines() {
console.log('开始处理连线题');
const matchingElements = document.querySelectorAll('et-matching[et-index]');
if (matchingElements.length === 0) return;
console.log(`找到连线题数量: ${matchingElements.length}`);
for (const matchingElement of matchingElements) {
const matchingKey = matchingElement.getAttribute('key');
if (!matchingKey) continue;
console.log(`处理连线题,答案key: ${matchingKey}`);
try {
const pairs = matchingKey.split(',').map(pair => {
const [left, right] = pair.split('-');
return {
leftIndex: parseInt(left) - 1,
rightIndex: parseInt(right) - 1
};
});
let angularSuccess = false;
if (typeof angular !== 'undefined') {
try {
const scope = angular.element(matchingElement).scope();
if (scope?.matching) {
if (Array.isArray(scope.matching.lines)) {
scope.matching.lines = [];
}
for (const { leftIndex, rightIndex } of pairs) {
const leftCircle = scope.matching.circles?.A?.[leftIndex];
const rightCircle = scope.matching.circles?.B?.[rightIndex];
if (!leftCircle || !rightCircle) continue;
if (typeof leftCircle.select === 'function') {
leftCircle.select();
await sleep(200);
rightCircle.select?.();
} else if (typeof scope.matching.connect === 'function') {
scope.matching.connect(leftCircle, rightCircle);
} else if (Array.isArray(scope.matching.lines)) {
scope.matching.lines.push({
x1: leftCircle.x,
y1: leftCircle.y,
x2: rightCircle.x,
y2: rightCircle.y,
circleA: leftCircle,
circleB: rightCircle
});
}
await sleep(200);
}
scope.$apply?.();
await sleep(500);
if (Array.isArray(scope.matching.lines) && scope.matching.lines.length > 0) {
angularSuccess = true;
}
}
} catch (e) {
console.error('Angular模型操作失败:', e);
}
}
if (angularSuccess) continue;
console.log('尝试DOM操作方式');
let leftCircles = [];
let rightCircles = [];
leftCircles = Array.from(matchingElement.querySelectorAll('circle[data-circle="A"]'));
rightCircles = Array.from(matchingElement.querySelectorAll('circle[data-circle="B"]'));
if (leftCircles.length === 0 || rightCircles.length === 0) {
const allCircles = matchingElement.querySelectorAll('circle[ng-repeat]');
leftCircles = [];
rightCircles = [];
for (const circle of allCircles) {
const ngRepeat = circle.getAttribute('ng-repeat');
if (ngRepeat?.includes('matching.circles.A')) {
leftCircles.push(circle);
} else if (ngRepeat?.includes('matching.circles.B')) {
rightCircles.push(circle);
}
}
}
if (leftCircles.length === 0 || rightCircles.length === 0) {
const allCircles = matchingElement.querySelectorAll('circle');
if (allCircles.length > 1) {
const circlesWithCoords = Array.from(allCircles)
.map(circle => ({
element: circle,
x: parseFloat(circle.getAttribute('cx')),
y: parseFloat(circle.getAttribute('cy'))
}))
.filter(c => !isNaN(c.x) && !isNaN(c.y));
if (circlesWithCoords.length > 1) {
circlesWithCoords.sort((a, b) => a.x - b.x);
const midX = (circlesWithCoords[0].x + circlesWithCoords[circlesWithCoords.length - 1].x) / 2;
leftCircles = circlesWithCoords.filter(c => c.x < midX).map(c => c.element);
rightCircles = circlesWithCoords.filter(c => c.x >= midX).map(c => c.element);
}
}
}
if (leftCircles.length === 0 || rightCircles.length === 0) continue;
for (const { leftIndex, rightIndex } of pairs) {
if (leftIndex < 0 || leftIndex >= leftCircles.length ||
rightIndex < 0 || rightIndex >= rightCircles.length) continue;
const leftCircle = leftCircles[leftIndex];
const rightCircle = rightCircles[rightIndex];
const leftX = parseFloat(leftCircle.getAttribute('cx'));
const leftY = parseFloat(leftCircle.getAttribute('cy'));
const rightX = parseFloat(rightCircle.getAttribute('cx'));
const rightY = parseFloat(rightCircle.getAttribute('cy'));
try {
const createMouseEvent = (type, x, y) => new MouseEvent(type, {
bubbles: true, cancelable: true, view: window,
clientX: x, clientY: y
});
['mousedown', 'click'].forEach(type =>
leftCircle.dispatchEvent(createMouseEvent(type, leftX, leftY)));
await sleep(300);
['mousedown', 'click'].forEach(type =>
rightCircle.dispatchEvent(createMouseEvent(type, rightX, rightY)));
await sleep(300);
if (typeof angular !== 'undefined') {
const scope = angular.element(leftCircle).scope();
scope?.$apply?.();
if (element.tagName === 'circle' && element.hasAttribute('data-index')) {
const dataIndex = element.getAttribute('data-index');
const dataCircle = element.getAttribute('data-circle');
const matchingElement = element.closest('et-matching[et-index]');
if (matchingElement && dataCircle) {
const matchingScope = angular.element(matchingElement).scope();
const circles = matchingScope?.matching?.circles?.[dataCircle];
const circle = circles?.[dataIndex];
if (circle?.select) {
circle.select();
matchingScope.$apply?.();
}
}
}
}
} catch (e) {
console.error('点击圆圈出错:', e);
}
}
} catch (error) {
console.error(`处理连线题出错:`, error);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log('所有连线题处理完成');
}
function isElementVisible(element) {
if (!element) return false;
try {
const style = window.getComputedStyle(element);
return style.display !== 'none' &&
style.visibility !== 'hidden' &&
element.offsetParent !== null;
} catch (e) {
return true;
}
}
async function clickElement(element) {
if (!element) return;
try {
element.click();
await sleep(100);
const cx = parseFloat(element.getAttribute('cx')) || 0;
const cy = parseFloat(element.getAttribute('cy')) || 0;
const mouseEventOptions = {
view: window,
bubbles: true,
cancelable: true,
clientX: cx,
clientY: cy
};
['mousedown', 'mouseup', 'click'].forEach(type =>
element.dispatchEvent(new MouseEvent(type, mouseEventOptions)));
if (typeof angular !== 'undefined') {
const scope = angular.element(element).scope();
scope?.$apply?.();
if (element.tagName === 'circle' && element.hasAttribute('data-index')) {
const dataIndex = element.getAttribute('data-index');
const dataCircle = element.getAttribute('data-circle');
const matchingElement = element.closest('et-matching[et-index]');
if (matchingElement && dataCircle) {
const matchingScope = angular.element(matchingElement).scope();
const circles = matchingScope?.matching?.circles?.[dataCircle];
const circle = circles?.[dataIndex];
if (circle?.select) {
circle.select();
matchingScope.$apply?.();
}
}
}
}
} catch (error) {
console.error('点击元素出错:', error);
}
}
async function submitAnswers() {
if (!window.autoSubmitEnabled) {
console.log('自动提交已禁用,跳过提交');
return false;
}
console.log('准备提交答案');
let submitted = false;
let maxAttempts = 3;
let attemptCount = 0;
await sleep(2000);
while (!submitted && attemptCount < maxAttempts) {
attemptCount++;
console.log(`尝试提交答案 (第${attemptCount}次)`);
try {
let submitButton = null;
submitButton = Array.from(document.querySelectorAll('button')).find(btn =>
(btn.textContent.trim() === 'Submit' ||
btn.querySelector('span')?.textContent.trim() === 'Submit') &&
!btn.disabled && btn.offsetParent !== null
);
if (!submitButton) {
const etButtons = document.querySelectorAll('et-button[right][action="item.submit()"]');
for (const etBtn of etButtons) {
const btn = etBtn.querySelector('button[ng-click="btn.doAction()"]');
if (btn && !btn.disabled && btn.offsetParent !== null) {
submitButton = btn;
break;
}
}
}
if (!submitButton) {
submitButton = document.querySelector('button[ng-click*="submit"], button[ng-click*="btn.doAction()"]');
}
if (submitButton) {
console.log('找到Submit按钮,尝试点击');
submitButton.scrollIntoView({ behavior: 'smooth', block: 'center' });
await sleep(500);
submitButton.click();
await sleep(500);
if (typeof angular !== 'undefined') {
const scope = angular.element(submitButton).scope();
if (scope?.item?.submit) {
scope.item.submit();
scope.$apply();
}
}
submitted = true;
await sleep(2000);
} else {
console.log('未找到Submit按钮,尝试其他方式');
const submitLink = document.querySelector('a[data-controltype="submit"]');
if (submitLink) {
submitLink.click();
await sleep(2000);
submitted = true;
}
}
if (submitted) {
console.log('提交成功,检查确认框');
for (let i = 0; i < 5; i++) {
const confirmButton = document.querySelector('a.layui-layer-btn0');
if (confirmButton) {
console.log('找到确认按钮,点击确认');
confirmButton.click();
await sleep(500);
break;
}
await sleep(500);
}
}
} catch (error) {
console.error(`第${attemptCount}次提交尝试失败:`, error);
}
if (!submitted) {
await sleep(1000);
}
}
if (submitted) {
console.log('提交流程完成');
return true;
}
console.log('所有提交尝试均失败');
return false;
}
async function autoNextSection() {
if (!window.autoNextEnabled) {
console.log('自动跳转已禁用,跳过跳转');
return false;
}
if (window.isTransitioning) {
console.log('跳转已在进行中,跳过重复跳转');
return true;
}
console.log('准备自动跳转到下一章节');
const isInIframe = window !== window.top;
console.log(`跳转环境: ${isInIframe ? 'iframe内' : '主窗口'}`);
await sleep(3000);
let nextSectionAttempts = 0;
const maxNextSectionAttempts = 3;
let transitionStarted = false;
while (nextSectionAttempts < maxNextSectionAttempts && !transitionStarted) {
nextSectionAttempts++;
console.log(`尝试跳转到下一章节 (第${nextSectionAttempts}次)`);
try {
const navLinks = document.querySelectorAll('a[href*="javascript:NextSCO()"], a[href*="javascript:PrevSCO()"]');
if (navLinks.length > 0) {
const nextLink = Array.from(navLinks).find(link =>
link.href.includes('NextSCO') && !link.href.includes('PrevSCO')
);
if (nextLink) {
console.log('找到NextSCO链接,尝试点击');
nextLink.click();
await sleep(3000);
transitionStarted = true;
break;
}
}
} catch (e) {
console.log('点击NextSCO链接失败:', e);
}
if (transitionStarted) break;
try {
console.log('尝试直接执行NextSCO函数');
const scriptEl = document.createElement('script');
scriptEl.textContent = `
try {
if (typeof NextSCO === 'function') {
NextSCO();
console.log("成功执行NextSCO函数");
}
} catch(e) {
console.error("执行NextSCO出错:", e);
}
`;
document.body.appendChild(scriptEl);
document.body.removeChild(scriptEl);
await sleep(2000);
transitionStarted = true;
break;
} catch (e) {
console.log('执行NextSCO函数失败:', e);
}
if (transitionStarted) break;
try {
window.location.href = 'javascript:void(0); try { NextSCO(); } catch(e) {}';
await sleep(2000);
transitionStarted = true;
break;
} catch (e) {
console.log('通过location.href跳转失败:', e);
}
if (!transitionStarted && nextSectionAttempts < maxNextSectionAttempts) {
console.log('本次跳转尝试失败,等待后重试');
await sleep(2000);
}
}
if (transitionStarted) {
console.log('已成功触发跳转,等待新页面加载');
window.isTransitioning = false;
return true;
}
console.log('所有跳转尝试都失败');
window.isTransitioning = false;
return false;
}
async function ensureCorrectContext() {
const isInIframe = window !== window.top;
console.log(`当前环境: ${isInIframe ? 'iframe内' : '主窗口'}`);
if (isInIframe && window.name === 'contentFrame') {
console.log('已在答题的iframe中,可以直接答题');
return;
}
if (!isInIframe) {
console.log('在主窗口中,尝试切换到contentFrame');
let contentFrame = document.getElementById('contentFrame');
if (!contentFrame) {
const frames = document.getElementsByTagName('iframe');
for (let i = 0; i < frames.length; i++) {
if (frames[i].name === 'contentFrame') {
contentFrame = frames[i];
break;
}
}
}
if (contentFrame) {
console.log('找到contentFrame,准备在其中执行答题');
try {
return executeInContentFrame(contentFrame);
} catch (e) {
console.error('向contentFrame执行答题失败:', e);
}
} else {
const iframes = document.querySelectorAll('iframe');
console.log(`共找到${iframes.length}个iframe`);
for (let i = 0; i < iframes.length; i++) {
try {
const iframe = iframes[i];
if (!iframe.contentDocument) continue;
const hasQuizElements =
iframe.contentDocument.querySelector('et-choice') ||
iframe.contentDocument.querySelector('et-tof') ||
iframe.contentDocument.querySelector('span.key') ||
iframe.contentDocument.querySelector('div.key') ||
iframe.contentDocument.querySelector('select[ng-model]') ||
iframe.contentDocument.querySelector('button');
if (hasQuizElements) {
console.log(`找到可能的答题iframe: ${iframe.name || iframe.id || i}`);
return executeInContentFrame(iframe);
}
} catch (e) {
console.log(`检查iframe ${i}失败:`, e);
}
}
console.log('未找到答题iframe,直接在当前窗口执行');
}
}
}
function executeInContentFrame(frame) {
console.log('准备在iframe中执行答题');
try {
const scriptContent = `
if (!window.weLearnHelperInjected) {
window.weLearnHelperInjected = true;
window.defaultAccuracyRate = ${window.defaultAccuracyRate};
window.isAutoLoopActive = ${window.isAutoLoopActive};
window.autoLoopTimeout = null;
window.waitTimerActive = ${window.waitTimerActive};
window.waitTimerInterval = null;
window.defaultWaitTime = ${window.defaultWaitTime};
window.autoNextEnabled = ${window.autoNextEnabled};
window.autoSubmitEnabled = ${window.autoSubmitEnabled};
try {
window.parent.postMessage({
action: 'iframeReady',
frameId: '${frame.id || frame.name || 'unknown'}'
}, '*');
} catch (e) {
console.log('iframe通信错误,但继续执行:', e.message);
}
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const createEvent = (type, options = {}) => new Event(type, {bubbles: true, cancelable: true, ...options});
${autoFillAnswers.toString()}
${autoSelectCorrectAnswers.toString()}
${autoSelectDropdownAnswers.toString()}
${autoMatchLines.toString()}
${submitAnswers.toString()}
${handleQuestion.toString()}
${handlePickerQuestions.toString()}
window.addEventListener('message', function(event) {
try {
if (event.data && event.data.action === 'runQuizFunctions') {
console.log('iframe收到执行答题请求:', event.data.step);
const executeAsync = async () => {
try {
switch(event.data.step) {
case 'selectAnswers':
await autoSelectCorrectAnswers();
break;
case 'fillAnswers':
await autoFillAnswers();
break;
case 'selectDropdowns':
await autoSelectDropdownAnswers();
break;
case 'matchLines':
await autoMatchLines();
break;
case 'submitAnswers':
await submitAnswers();
break;
case 'pickerQuestions':
await handlePickerQuestions();
break;
}
window.parent.postMessage({
action: 'stepComplete',
step: event.data.step
}, '*');
} catch (error) {
console.error('执行答题操作失败:', error);
try {
window.parent.postMessage({
action: 'stepError',
step: event.data.step,
error: error.message
}, '*');
} catch (e) {
console.log('发送错误消息失败:', e.message);
}
}
};
executeAsync();
}
} catch (e) {
console.log('处理iframe消息时出错,但继续执行:', e.message);
}
});
}
`;
const script = document.createElement('script');
script.textContent = scriptContent;
try {
frame.contentDocument.body.appendChild(script);
frame.contentDocument.body.removeChild(script);
console.log('已将答题脚本注入到iframe中');
} catch (e) {
console.log('注入脚本到iframe失败,但继续执行:', e.message);
}
return {
autoSelectCorrectAnswers: () => sendCommandToIframe(frame, 'selectAnswers'),
autoFillAnswers: () => sendCommandToIframe(frame, 'fillAnswers'),
autoSelectDropdownAnswers: () => sendCommandToIframe(frame, 'selectDropdowns'),
autoMatchLines: () => sendCommandToIframe(frame, 'matchLines'),
submitAnswers: () => sendCommandToIframe(frame, 'submitAnswers'),
handlePickerQuestions: () => sendCommandToIframe(frame, 'pickerQuestions')
};
} catch (e) {
console.log('iframe操作失败,但继续执行:', e.message);
return null;
}
}
function handleIframeMessage(event) {
if (event.data && event.data.action) {
if (event.data.action === 'iframeReady') {
console.log(`iframe已就绪: ${event.data.frameId}`);
} else if (event.data.action === 'stepComplete') {
console.log(`iframe完成步骤: ${event.data.step}`);
} else if (event.data.action === 'stepError') {
console.error(`iframe执行出错: ${event.data.error}`);
}
}
}
async function sendCommandToIframe(frame, step) {
return new Promise((resolve, reject) => {
let timeout;
try {
const frameDoc = frame.contentDocument || frame.contentWindow.document;
const questionCount = frameDoc.querySelectorAll('et-choice[et-index], et-tof[et-index], span[autocapitalize="none"], textarea.blank, span.key, div.key, select[ng-model], et-matching[et-index]').length;
const baseTimeout = 30000;
const additionalTimePerTenQuestions = 30000;
timeout = baseTimeout + Math.ceil(questionCount / 10) * additionalTimePerTenQuestions;
console.log(`题目数量: ${questionCount}, 设置超时时间: ${timeout / 1000}秒`);
} catch (e) {
console.log('计算题目数量失败,使用默认超时时间60秒:', e.message);
timeout = 60000;
}
let timeoutHandle;
let isCompleted = false;
const messageHandler = (event) => {
try {
if (!event.data) return;
if (event.data.action === 'stepComplete' && event.data.step === step) {
isCompleted = true;
cleanup();
resolve();
} else if (event.data.action === 'stepError' && event.data.step === step) {
cleanup();
reject(new Error(event.data.error || '未知iframe错误'));
}
} catch (e) {
console.log('处理iframe消息时出错,但继续执行:', e.message);
}
};
const cleanup = () => {
if (timeoutHandle) {
clearTimeout(timeoutHandle);
timeoutHandle = null;
}
try {
window.removeEventListener('message', messageHandler);
} catch (e) {
console.log('清理事件监听器失败:', e.message);
}
};
timeoutHandle = setTimeout(() => {
if (!isCompleted) {
cleanup();
console.log(`向iframe发送${step}命令超时,但继续执行`);
resolve();
}
}, timeout);
try {
window.addEventListener('message', messageHandler);
} catch (e) {
console.log('添加消息监听器失败,但继续执行:', e.message);
}
try {
frame.contentWindow.postMessage({
action: 'runQuizFunctions',
step: step
}, '*');
console.log(`已向iframe发送${step}命令`);
} catch (e) {
console.log('向iframe发送命令失败,但继续执行:', e.message);
cleanup();
resolve();
}
});
}
function createIntegratedUI() {
const globalStyle = document.createElement('style');
globalStyle.id = 'welearn-helper-style';
globalStyle.textContent = `
@keyframes slideIn {
from {
transform: translateX(-120%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes pulse {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.03); opacity: 0.8; }
100% { transform: scale(1); opacity: 1; }
}
@keyframes subtlePopIn {
0% { opacity: 0; transform: scale(0.95) translateY(5px); }
100% { opacity: 1; transform: scale(1) translateY(0); }
}
@keyframes shine {
0% { background-position: -150% 50%; }
100% { background-position: 250% 50%; }
}
@keyframes gradientFlow {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
@keyframes titleGlow {
0% { text-shadow: 0 0 5px rgba(76, 175, 80, 0.5); }
50% { text-shadow: 0 0 15px rgba(76, 175, 80, 0.8), 0 0 25px rgba(76, 175, 80, 0.4); }
100% { text-shadow: 0 0 5px rgba(76, 175, 80, 0.5); }
}
@keyframes borderPulse {
0% { border-color: rgba(255, 255, 255, 0.15); }
50% { border-color: rgba(76, 175, 80, 0.3); }
100% { border-color: rgba(255, 255, 255, 0.15); }
}
#welearnHelperPanel {
position: fixed;
top: 15px;
left: 15px;
z-index: 10000;
background: rgba(28, 32, 34, 0.75);
backdrop-filter: blur(10px) saturate(180%);
-webkit-backdrop-filter: blur(10px) saturate(180%);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35),
0 2px 8px rgba(76, 175, 80, 0.15),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.15);
padding: 18px;
color: #e0e0e0;
font-size: 14px;
width: 290px;
display: flex;
flex-direction: column;
gap: 14px;
border: 1px solid rgba(255, 255, 255, 0.12);
animation: slideIn 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
#welearnHelperPanel:hover {
transform: translateY(-3px) scale(1.01);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4),
0 4px 12px rgba(76, 175, 80, 0.2),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.2);
}
.welearn-title-bar {
display: flex;
justify-content: space-between;
align-items: center;
background: linear-gradient(135deg,
rgba(76, 175, 80, 0.15),
rgba(56, 142, 60, 0.1),
rgba(76, 175, 80, 0.15));
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
background-size: 200% 200%;
animation: gradientFlow 8s ease infinite;
margin: -18px -18px 6px -18px;
padding: 16px 18px;
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 12px 12px 0 0;
position: relative;
overflow: hidden;
}
.welearn-title-bar::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg,
transparent,
rgba(255, 255, 255, 0.1),
transparent);
transform: translateX(-100%);
animation: shine 8s infinite;
}
.welearn-title-bar::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg,
transparent,
rgba(76, 175, 80, 0.5),
transparent);
animation: borderPulse 4s infinite;
}
.welearn-title {
font-weight: 600;
font-size: 20px;
background: linear-gradient(135deg, #ffffff 30%, #4CAF50);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
animation: titleGlow 3s ease-in-out infinite;
letter-spacing: 0.5px;
position: relative;
padding-left: 24px;
}
.welearn-title::before {
content: '🎯';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
font-size: 16px;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
animation: pulse 2s ease-in-out infinite;
}
.welearn-minimize-btn {
background: rgba(255, 255, 255, 0.1);
border: none;
color: #c0c0c0;
font-size: 18px;
font-weight: bold;
cursor: pointer;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55);
backdrop-filter: blur(4px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.welearn-minimize-btn:hover {
background-color: rgba(76, 175, 80, 0.25);
color: #ffffff;
transform: rotate(180deg) scale(1.1);
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
}
.welearn-minimize-btn.minimized:hover {
transform: rotate(-180deg) scale(1.1);
}
.welearn-minimize-btn::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
background: radial-gradient(circle at center,
rgba(255, 255, 255, 0.1) 0%,
transparent 70%);
opacity: 0;
transition: opacity 0.3s ease;
}
.welearn-minimize-btn:hover::before {
opacity: 1;
}
.welearn-button-container {
display: flex;
gap: 10px;
animation: fadeIn 0.5s ease-out 0.2s both;
}
.welearn-button {
position: relative;
flex: 1;
padding: 10px 14px;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 500;
font-size: 13.5px;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
color: white;
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
box-shadow: inset 0 0 0 0.5px rgba(255, 255, 255, 0.2);
}
.welearn-button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 200%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.15),
transparent
);
transition: 0.5s;
}
.welearn-button:hover::before {
animation: shine 1.2s infinite linear;
}
.welearn-button:hover {
transform: translateY(-2.5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.2),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.3);
}
.welearn-button:active {
transform: scale(0.96) translateY(-1px);
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.welearn-auto-button {
background: linear-gradient(135deg,
rgba(38, 166, 154, 0.95) 0%,
rgba(0, 121, 107, 0.95) 100%);
}
.welearn-auto-button:hover {
background: linear-gradient(135deg,
rgba(42, 183, 169, 0.95) 0%,
rgba(0, 137, 123, 0.95) 100%);
box-shadow: 0 5px 18px rgba(38, 166, 154, 0.35),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.3);
}
.welearn-loop-button {
background: linear-gradient(135deg,
rgba(92, 107, 192, 0.95) 0%,
rgba(57, 73, 171, 0.95) 100%);
}
.welearn-loop-button:hover {
background: linear-gradient(135deg,
rgba(111, 125, 214, 0.95) 0%,
rgba(71, 88, 184, 0.95) 100%);
box-shadow: 0 5px 18px rgba(92, 107, 192, 0.35),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.3);
}
.welearn-loop-active {
background: linear-gradient(135deg, #ef5350 0%, #d32f2f 100%);
animation: pulse 1.8s infinite ease-in-out;
}
.welearn-loop-active:hover {
background: linear-gradient(135deg, #fa6e6b 0%, #e53935 100%);
box-shadow: 0 5px 18px rgba(239, 83, 80, 0.45);
}
.welearn-timer-container, .welearn-accuracy-container {
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
padding: 10px;
border-radius: 8px;
display: flex;
align-items: center;
gap: 8px;
animation: fadeIn 0.5s ease-out 0.3s both;
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
box-shadow: inset 0 0 0 0.5px rgba(255, 255, 255, 0.05);
}
.welearn-timer-container:hover, .welearn-accuracy-container:hover {
background: rgba(255, 255, 255, 0.12);
border-color: rgba(255, 255, 255, 0.2);
box-shadow: inset 0 0 0 0.5px rgba(255, 255, 255, 0.1);
}
.welearn-accuracy-container {
animation-delay: 0.2s;
}
.welearn-time-input, .welearn-accuracy-input {
width: 45px;
padding: 7px;
border-radius: 6px;
border: 1px solid rgba(255, 255, 255, 0.2);
background-color: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
text-align: center;
font-weight: 500;
color: #e0e0e0;
transition: all 0.25s ease;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
}
.welearn-time-input:hover, .welearn-accuracy-input:hover {
transform: scale(1.05);
border-color: rgba(255, 255, 255, 0.3);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2);
}
.welearn-time-input:focus, .welearn-accuracy-input:focus {
outline: none;
border-color: #26a69a;
background-color: rgba(0, 0, 0, 0.3);
box-shadow: 0 0 10px rgba(38, 166, 154, 0.5),
inset 0 1px 3px rgba(0, 0, 0, 0.2);
transform: scale(1.08);
}
.welearn-countdown {
margin-left: auto;
font-weight: 500;
min-width: 65px;
text-align: right;
color: #80cbc4;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
transition: color 0.3s ease, transform 0.3s ease;
}
.welearn-countdown.status-update {
animation: subtlePopIn 0.4s ease-out;
}
.welearn-status {
font-size: 11.5px;
color: #9e9e9e;
text-align: center;
padding: 7px;
background-color: rgba(0, 0, 0, 0.15);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
border-radius: 6px;
animation: fadeIn 0.5s ease-out 0.4s both;
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: inset 0 0 0 0.5px rgba(255, 255, 255, 0.05);
}
.welearn-loop-status {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
background-color: rgba(239, 83, 80, 0.15);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
padding: 7px;
border-radius: 6px;
margin-top: 2px;
font-weight: 500;
font-size: 12.5px;
border: 1px solid rgba(239, 83, 80, 0.25);
color: #f5c8c7;
animation: fadeIn 0.5s ease-out;
box-shadow: inset 0 0 0 0.5px rgba(255, 255, 255, 0.1);
}
@media (max-width: 768px) {
#welearnHelperPanel {
width: calc(100% - 30px);
left: 15px;
top: 15px;
}
}
`;
document.head.appendChild(globalStyle);
const mainPanel = document.createElement('div');
mainPanel.id = 'welearnHelperPanel';
const titleBar = document.createElement('div');
titleBar.className = 'welearn-title-bar';
const title = document.createElement('div');
title.className = 'welearn-title';
title.textContent = 'Welearn助手';
const themeButton = document.createElement('button');
themeButton.className = 'welearn-theme-button';
themeButton.innerHTML = '🎨';
themeButton.style.cssText = `
background: none;
border: none;
color: ${currentTheme?.textColor || '#e0e0e0'};
font-size: 18px;
cursor: pointer;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 50%;
transition: all 0.3s ease;
margin-right: 10px;
`;
themeButton.addEventListener('mouseover', () => {
themeButton.style.transform = 'rotate(180deg) scale(1.1)';
});
themeButton.addEventListener('mouseout', () => {
themeButton.style.transform = 'none';
});
themeButton.addEventListener('click', () => {
let themePanel = document.getElementById('welearnThemePanel');
if (!themePanel) {
themePanel = createThemePanel();
document.body.appendChild(themePanel);
}
themePanel.style.display = themePanel.style.display === 'none' ? 'block' : 'none';
});
const minimizeBtn = document.createElement('button');
minimizeBtn.className = 'welearn-minimize-btn';
minimizeBtn.textContent = '-';
minimizeBtn.onclick = () => {
const content = document.getElementById('welearnHelperContent');
if (content.style.display === 'none') {
content.style.display = 'flex';
minimizeBtn.textContent = '-';
} else {
content.style.display = 'none';
minimizeBtn.textContent = '+';
}
};
titleBar.appendChild(title);
titleBar.appendChild(themeButton);
titleBar.appendChild(minimizeBtn);
mainPanel.appendChild(titleBar);
const content = document.createElement('div');
content.id = 'welearnHelperContent';
content.style.cssText = 'display: flex; flex-direction: column; gap: 12px;';
const accuracyContainer = document.createElement('div');
accuracyContainer.className = 'welearn-accuracy-container';
const accuracyLabel = document.createElement('span');
accuracyLabel.textContent = '正确率:';
accuracyLabel.style.fontWeight = '500';
const accuracyInput = document.createElement('input');
accuracyInput.id = 'accuracyInput';
accuracyInput.className = 'welearn-accuracy-input';
accuracyInput.type = 'number';
accuracyInput.min = '0';
accuracyInput.max = '100';
accuracyInput.value = defaultAccuracyRate.toString();
accuracyInput.style.cssText = `
width: 60px;
padding: 7px;
border-radius: 6px;
border: 1px solid rgba(255, 255, 255, 0.2);
background-color: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
text-align: center;
font-weight: 500;
color: #e0e0e0;
transition: all 0.25s ease;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
font-size: 14px;
`;
const percentLabel = document.createElement('span');
percentLabel.textContent = '%';
percentLabel.style.fontWeight = '500';
accuracyInput.addEventListener('change', function () {
const newRate = parseInt(this.value, 10);
if (!isNaN(newRate) && newRate >= 0 && newRate <= 100) {
defaultAccuracyRate = newRate;
} else {
this.value = defaultAccuracyRate.toString();
}
});
accuracyContainer.appendChild(accuracyLabel);
accuracyContainer.appendChild(accuracyInput);
accuracyContainer.appendChild(percentLabel);
content.appendChild(accuracyContainer);
const autoNextContainer = document.createElement('div');
autoNextContainer.className = 'welearn-accuracy-container';
autoNextContainer.style.justifyContent = 'space-between';
const autoNextLabel = document.createElement('span');
autoNextLabel.textContent = '自动跳转下一章:';
autoNextLabel.style.fontWeight = '500';
const autoNextCheckbox = document.createElement('input');
autoNextCheckbox.type = 'checkbox';
autoNextCheckbox.id = 'autoNextCheckbox';
autoNextCheckbox.className = 'welearn-checkbox';
autoNextCheckbox.checked = window.autoNextEnabled;
autoNextCheckbox.style.cssText = `
width: 18px;
height: 18px;
cursor: pointer;
accent-color: #4CAF50;
transition: all 0.3s ease;
`;
const style = document.createElement('style');
style.textContent = `
.welearn-checkbox:hover {
transform: scale(1.1);
box-shadow: 0 0 5px rgba(76, 175, 80, 0.5);
}
.welearn-checkbox:checked {
animation: checkmark 0.2s ease-in-out;
}
@keyframes checkmark {
0% { transform: scale(1); }
50% { transform: scale(0.9); }
100% { transform: scale(1); }
}
`;
document.head.appendChild(style);
autoNextCheckbox.addEventListener('change', function () {
window.autoNextEnabled = this.checked;
const container = this.closest('.welearn-accuracy-container');
container.style.backgroundColor = 'rgba(76, 175, 80, 0.1)';
setTimeout(() => {
container.style.backgroundColor = '';
}, 300);
});
autoNextContainer.appendChild(autoNextLabel);
autoNextContainer.appendChild(autoNextCheckbox);
content.insertBefore(autoNextContainer, accuracyContainer.nextSibling);
const autoSubmitContainer = document.createElement('div');
autoSubmitContainer.className = 'welearn-accuracy-container';
autoSubmitContainer.style.justifyContent = 'space-between';
const autoSubmitLabel = document.createElement('span');
autoSubmitLabel.textContent = '自动提交答案:';
autoSubmitLabel.style.fontWeight = '500';
const autoSubmitCheckbox = document.createElement('input');
autoSubmitCheckbox.type = 'checkbox';
autoSubmitCheckbox.id = 'autoSubmitCheckbox';
autoSubmitCheckbox.className = 'welearn-checkbox';
autoSubmitCheckbox.checked = window.autoSubmitEnabled;
autoSubmitCheckbox.style.cssText = `
width: 18px;
height: 18px;
cursor: pointer;
accent-color: #4CAF50;
transition: all 0.3s ease;
`;
autoSubmitCheckbox.addEventListener('change', function () {
window.autoSubmitEnabled = this.checked;
const container = this.closest('.welearn-accuracy-container');
container.style.backgroundColor = 'rgba(76, 175, 80, 0.1)';
setTimeout(() => {
container.style.backgroundColor = '';
}, 300);
const frames = document.getElementsByTagName('iframe');
for (const frame of frames) {
try {
frame.contentWindow.postMessage({
action: 'updateAutoSubmit',
enabled: this.checked
}, '*');
} catch (e) {
console.log('向iframe发送自动提交状态更新失败:', e);
}
}
});
autoSubmitContainer.appendChild(autoSubmitLabel);
autoSubmitContainer.appendChild(autoSubmitCheckbox);
content.insertBefore(autoSubmitContainer, autoNextContainer.nextSibling);
const buttonContainer = document.createElement('div');
buttonContainer.className = 'welearn-button-container';
const autoAnswerButton = document.createElement('button');
autoAnswerButton.id = 'autoAnswerButton';
autoAnswerButton.textContent = '一键全自动';
autoAnswerButton.className = 'welearn-button welearn-auto-button';
const autoLoopButton = document.createElement('button');
autoLoopButton.id = 'autoLoopButton';
autoLoopButton.textContent = '开始自动挂机';
autoLoopButton.className = 'welearn-button welearn-loop-button';
autoAnswerButton.onclick = async function () {
await runAutoAnswerProcess(false);
};
autoLoopButton.onclick = async function () {
await toggleAutoLoop();
};
buttonContainer.appendChild(autoAnswerButton);
buttonContainer.appendChild(autoLoopButton);
content.appendChild(buttonContainer);
const timerContainer = document.createElement('div');
timerContainer.id = 'waitTimerContainer';
timerContainer.className = 'welearn-timer-container';
const label = document.createElement('span');
label.textContent = '刷时长:';
label.style.fontWeight = '500';
const timeInput = document.createElement('input');
timeInput.id = 'waitTimeInput';
timeInput.className = 'welearn-time-input';
timeInput.type = 'number';
timeInput.min = '1';
timeInput.max = '3600';
timeInput.value = defaultWaitTime.toString();
timeInput.style.cssText = `
width: 60px;
padding: 7px;
border-radius: 6px;
border: 1px solid rgba(255, 255, 255, 0.2);
background-color: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
text-align: center;
font-weight: 500;
color: #e0e0e0;
transition: all 0.25s ease;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
font-size: 14px;
`;
timeInput.addEventListener('change', function () {
const newTime = parseInt(this.value, 10);
if (!isNaN(newTime) && newTime > 0) {
defaultWaitTime = newTime;
const countdownDisplay = document.getElementById('countdownDisplay');
if (countdownDisplay) {
const originalText = countdownDisplay.textContent;
const originalColor = countdownDisplay.style.color;
countdownDisplay.textContent = '已保存';
countdownDisplay.style.color = '#4CAF50';
countdownDisplay.classList.add('status-update');
setTimeout(() => {
if (countdownDisplay) {
countdownDisplay.textContent = originalText;
countdownDisplay.style.color = originalColor;
countdownDisplay.classList.remove('status-update');
}
}, 1000);
}
}
});
const secondsLabel = document.createElement('span');
secondsLabel.textContent = '秒';
secondsLabel.style.fontWeight = '500';
const statusDisplay = document.createElement('div');
statusDisplay.id = 'countdownDisplay';
statusDisplay.className = 'welearn-countdown';
statusDisplay.textContent = '未开始';
timerContainer.appendChild(label);
timerContainer.appendChild(timeInput);
timerContainer.appendChild(secondsLabel);
timerContainer.appendChild(statusDisplay);
content.appendChild(timerContainer);
const statusContainer = document.createElement('div');
statusContainer.className = 'welearn-status';
statusContainer.textContent = 'by Wei';
content.appendChild(statusContainer);
const loopStatus = document.createElement('div');
loopStatus.id = 'loopStatusIndicator';
loopStatus.className = 'welearn-loop-status';
loopStatus.style.display = 'none';
loopStatus.textContent = '自动挂机中';
content.appendChild(loopStatus);
mainPanel.appendChild(content);
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
let xOffset = 0;
let yOffset = 0;
titleBar.addEventListener('mousedown', dragStart);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', dragEnd);
function dragStart(e) {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
if (e.target === titleBar || e.target === title) {
isDragging = true;
titleBar.style.cursor = 'grabbing';
}
}
function drag(e) {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, mainPanel);
}
}
function setTranslate(xPos, yPos, el) {
el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
}
function dragEnd() {
initialX = currentX;
initialY = currentY;
isDragging = false;
titleBar.style.cursor = 'grab';
}
return mainPanel;
}
async function executeWaitTimer(callback) {
const timeInput = document.getElementById('waitTimeInput');
const countdownDisplay = document.getElementById('countdownDisplay');
let waitTime = parseInt(timeInput?.value || defaultWaitTime);
if (isNaN(waitTime) || waitTime < 1) {
waitTime = defaultWaitTime;
if (timeInput) timeInput.value = defaultWaitTime.toString();
} else {
defaultWaitTime = waitTime;
}
if (waitTime <= 0) {
if (typeof callback === 'function') callback();
return;
}
if (countdownDisplay) {
countdownDisplay.textContent = waitTime + ' 秒';
countdownDisplay.style.color = '#00ffcc';
countdownDisplay.style.display = 'block';
}
if (waitTimerInterval) {
clearInterval(waitTimerInterval);
}
waitTimerActive = true;
let remainingTime = waitTime;
return new Promise(resolve => {
waitTimerInterval = setInterval(() => {
remainingTime--;
if (countdownDisplay) {
countdownDisplay.textContent = remainingTime + ' 秒';
if (remainingTime <= 10 && remainingTime > 0) {
countdownDisplay.style.color = '#ff6b6b';
countdownDisplay.style.transform = 'scale(1.05)';
} else {
countdownDisplay.style.transform = 'scale(1)';
}
}
if (remainingTime <= 0) {
clearInterval(waitTimerInterval);
waitTimerInterval = null;
waitTimerActive = false;
if (countdownDisplay) {
countdownDisplay.textContent = '完成!';
countdownDisplay.style.color = '#4CAF50';
countdownDisplay.classList.add('status-update');
setTimeout(() => {
if (countdownDisplay) {
countdownDisplay.textContent = '未开始';
countdownDisplay.style.color = '#80cbc4';
countdownDisplay.classList.remove('status-update');
}
}, 3000);
}
if (typeof callback === 'function') callback();
resolve();
}
}, 1000);
});
}
function cancelWaitTimer() {
if (waitTimerInterval) {
clearInterval(waitTimerInterval);
waitTimerInterval = null;
}
waitTimerActive = false;
const countdownDisplay = document.getElementById('countdownDisplay');
if (countdownDisplay) {
countdownDisplay.textContent = '已取消';
countdownDisplay.style.color = '#ff9800';
countdownDisplay.classList.add('status-update');
setTimeout(() => {
if (countdownDisplay) {
countdownDisplay.textContent = '未开始';
countdownDisplay.style.color = '#80cbc4';
countdownDisplay.classList.remove('status-update');
}
}, 3000);
}
}
function addButton() {
if (window !== window.top) {
console.log('当前在iframe中,不添加按钮');
return;
}
removeExistingButtons();
const uiPanel = createIntegratedUI();
document.body.appendChild(uiPanel);
updateButtonsState();
}
function updateButtonsState() {
const loopButton = document.getElementById('autoLoopButton');
if (loopButton) {
if (isAutoLoopActive) {
loopButton.textContent = '停止自动挂机';
loopButton.className = 'welearn-button welearn-loop-active';
showLoopStatus(true);
} else {
loopButton.textContent = '开始自动挂机';
loopButton.className = 'welearn-button welearn-loop-button';
showLoopStatus(false);
}
}
}
function showLoopStatus(isActive) {
let statusIndicator = document.getElementById('loopStatusIndicator');
if (!statusIndicator) return;
if (isActive) {
statusIndicator.innerHTML = ' 自动挂机中';
statusIndicator.style.display = 'flex';
const style = document.createElement('style');
style.id = 'loopStatusStyle';
style.textContent = `
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0.3; }
100% { opacity: 1; }
}
`;
if (!document.getElementById('loopStatusStyle')) {
document.head.appendChild(style);
}
} else {
statusIndicator.style.display = 'none';
}
}
async function toggleAutoLoop() {
if (!isAutoLoopActive) {
isAutoLoopActive = true;
updateButtonsState();
showLoopStatus(true);
await runAutoAnswerProcess(true);
} else {
stopAutoLoop();
}
}
function stopAutoLoop() {
console.log('停止自动挂机');
isAutoLoopActive = false;
if (autoLoopTimeout) {
clearTimeout(autoLoopTimeout);
autoLoopTimeout = null;
}
cancelWaitTimer();
updateButtonsState();
showLoopStatus(false);
}
async function runAutoAnswerProcess(isLoop) {
if (!isLoop && isAutoLoopActive) {
console.log('自动挂机已激活,单次执行被忽略');
return;
}
try {
const answerButton = document.getElementById('autoAnswerButton');
if (isLoop) {
if (answerButton) {
answerButton.textContent = '等待中...';
answerButton.style.backgroundColor = 'orange';
}
await executeWaitTimer(() => {
if (answerButton) {
answerButton.textContent = '开始答题...';
}
});
if (!isAutoLoopActive) return;
}
if (answerButton) {
answerButton.textContent = '检测环境...';
answerButton.style.backgroundColor = 'orange';
}
const iframeFunctions = await ensureCorrectContext();
const selectAnswersFn = iframeFunctions ? iframeFunctions.autoSelectCorrectAnswers : autoSelectCorrectAnswers;
const fillAnswersFn = iframeFunctions ? iframeFunctions.autoFillAnswers : autoFillAnswers;
const selectDropdownsFn = iframeFunctions ? iframeFunctions.autoSelectDropdownAnswers : autoSelectDropdownAnswers;
const matchLinesFn = iframeFunctions ? iframeFunctions.autoMatchLines : autoMatchLines;
const submitAnswersFn = iframeFunctions ? iframeFunctions.submitAnswers : submitAnswers;
const handlePickerQuestionsFn = iframeFunctions ? iframeFunctions.handlePickerQuestions : handlePickerQuestions;
let hasQuestions = false;
let doc = document;
const contentFrame = document.getElementById('contentFrame');
if (contentFrame && contentFrame.contentDocument) {
doc = contentFrame.contentDocument;
}
const questionSelectors = [
'et-choice[et-index]',
'et-tof[et-index]',
'div[data-controltype="choice"]',
'div.white_frame_normal_choice',
'div[data-score]',
'ul[data-itemtype="options"]',
'span[autocapitalize="none"]',
'textarea.blank',
'span.key',
'div.key',
'input[data-itemtype="input"]',
'textarea[data-itemtype="input"]',
'textarea[data-itemtype="textarea"]',
'select[ng-model]',
'et-matching[et-index]',
'span.blank[ng-attr-tabindex]',
];
const checkForQuestions = (document) => {
for (const selector of questionSelectors) {
const elements = document.querySelectorAll(selector);
if (elements.length > 0) {
console.log(`找到题目类型 ${selector}:`, elements.length);
return true;
}
}
return false;
};
hasQuestions = checkForQuestions(doc);
if (!hasQuestions) {
const iframes = document.getElementsByTagName('iframe');
for (const iframe of iframes) {
try {
if (iframe.contentDocument) {
hasQuestions = checkForQuestions(iframe.contentDocument);
if (hasQuestions) {
doc = iframe.contentDocument;
break;
}
}
} catch (e) {
console.log('检查iframe失败:', e);
}
}
}
if (answerButton) {
answerButton.textContent = '检测题目...';
}
const hasChoiceQuestions = (
doc.querySelectorAll('et-choice[et-index], et-tof[et-index], div[data-controltype="choice"], div.white_frame_normal_choice, div[data-score], ul[data-itemtype="options"]').length > 0
) || (
window.frames['contentFrame']?.document.querySelectorAll('et-choice[et-index], et-tof[et-index], div[data-controltype="choice"], div.white_frame_normal_choice, div[data-score], ul[data-itemtype="options"]').length > 0
);
if (hasChoiceQuestions) {
hasQuestions = true;
await selectAnswersFn();
console.log('选择题和判断题处理完成');
await sleep(1000);
}
if (answerButton) {
answerButton.textContent = '检测填空题...';
}
const hasBlankQuestions = (doc.querySelectorAll('span[autocapitalize="none"], textarea.blank, span.key, div.key, input[data-solution], textarea[data-solution]').length > 0) ||
(window.frames['contentFrame']?.document.querySelectorAll('span[autocapitalize="none"], textarea.blank, span.key, div.key, input[data-solution], textarea[data-solution]').length > 0);
if (hasBlankQuestions) {
hasQuestions = true;
await fillAnswersFn();
console.log('填空题处理完成');
await sleep(1000);
}
if (answerButton) {
answerButton.textContent = '检测下拉框...';
}
const hasDropdowns = (doc.querySelectorAll('select[ng-model]').length > 0) ||
(window.frames['contentFrame']?.document.querySelectorAll('select[ng-model]').length > 0);
if (hasDropdowns) {
hasQuestions = true;
await selectDropdownsFn();
console.log('下拉框处理完成');
await sleep(1000);
}
if (answerButton) {
answerButton.textContent = '检测连线题...';
}
const hasMatchingQuestions = (doc.querySelectorAll('et-matching[et-index]').length > 0) ||
(window.frames['contentFrame']?.document.querySelectorAll('et-matching[et-index]').length > 0);
if (hasMatchingQuestions) {
hasQuestions = true;
await matchLinesFn();
console.log('连线题处理完成');
await sleep(1000);
}
if (answerButton) {
answerButton.textContent = '检测单元测试...';
}
const hasUnitTest = (doc.querySelectorAll('span.unittestresult').length > 0) ||
(window.frames['contentFrame']?.document.querySelectorAll('span.unittestresult').length > 0);
if (hasUnitTest) {
hasQuestions = true;
await handleUnitTestAnswers();
console.log('单元测试题处理完成');
await sleep(1000);
}
const hasTabExercise = (doc.querySelectorAll('li[ng-click*="tab.go"]').length > 0) ||
(window.frames['contentFrame']?.document.querySelectorAll('li[ng-click*="tab.go"]').length > 0);
if (hasTabExercise) {
hasQuestions = true;
if (answerButton) {
answerButton.textContent = '处理标签页练习...';
}
await handleTabExercises();
console.log('标签页练习处理完成');
await sleep(1000);
}
if (answerButton) {
answerButton.textContent = '检测选择框题目...';
}
const hasPickerQuestions = (doc.querySelectorAll('span.blank[ng-attr-tabindex]').length > 0) ||
(window.frames['contentFrame']?.document.querySelectorAll('span.blank[ng-attr-tabindex]').length > 0);
if (hasPickerQuestions) {
hasQuestions = true;
await handlePickerQuestions();
console.log('选择框题目处理完成');
await sleep(1000);
}
if (!hasQuestions) {
console.log('未找到需要处理的题目,准备跳转到下一章节...');
if (answerButton) {
answerButton.textContent = window.autoNextEnabled ? '跳转中...' : '已完成';
answerButton.style.backgroundColor = window.autoNextEnabled ? 'blue' : 'green';
}
if (window.autoNextEnabled) {
await autoNextSection();
}
} else {
await sleep(2000);
if (answerButton) {
answerButton.textContent = window.autoSubmitEnabled ? '提交中...' : '已完成';
}
let submitResult = false;
if (window.autoSubmitEnabled) {
submitResult = await submitAnswersFn();
} else {
console.log('自动提交已禁用,跳过提交');
}
if (!submitResult) {
if (answerButton) {
answerButton.textContent = window.autoNextEnabled ? '跳转中...' : '已完成';
answerButton.style.backgroundColor = window.autoNextEnabled ? 'blue' : 'green';
}
if (window.autoNextEnabled) {
await autoNextSection();
}
}
}
if (isLoop && isAutoLoopActive) {
if (answerButton) {
answerButton.textContent = '准备下一轮...';
answerButton.style.backgroundColor = 'purple';
}
console.log('等待页面加载,准备下一轮答题...');
autoLoopTimeout = setTimeout(async () => {
console.log('开始下一轮答题');
if (isAutoLoopActive) {
await runAutoAnswerProcess(true);
}
}, 3000);
} else {
if (answerButton) {
answerButton.textContent = '已完成';
answerButton.style.backgroundColor = 'green';
setTimeout(() => {
if (answerButton && !isAutoLoopActive) {
answerButton.textContent = '一键全自动';
answerButton.style.backgroundColor = 'rgb(0, 255, 127)';
}
}, 3000);
}
}
} catch (error) {
console.error('自动化流程出错:', error);
const answerButton = document.getElementById('autoAnswerButton');
if (answerButton) {
answerButton.textContent = '出错了!';
answerButton.style.backgroundColor = 'red';
}
if (isLoop && isAutoLoopActive) {
console.log('自动挂机出错,10秒后重试...');
autoLoopTimeout = setTimeout(async () => {
console.log('重新开始挂机');
if (isAutoLoopActive) {
await runAutoAnswerProcess(true);
}
}, 10000);
} else {
setTimeout(() => {
if (answerButton && !isAutoLoopActive) {
answerButton.textContent = '一键全自动';
answerButton.style.backgroundColor = 'rgb(0, 255, 127)';
}
}, 5000);
}
}
}
function removeExistingButtons() {
const welearnPanel = document.getElementById('welearnHelperPanel');
if (welearnPanel && welearnPanel.parentNode) {
welearnPanel.parentNode.removeChild(welearnPanel);
}
const oldElements = [
document.getElementById('autoAnswerButton'),
document.getElementById('autoLoopButton'),
document.getElementById('autoAnswerButtonContainer'),
document.getElementById('autoAnswerInfoText'),
document.getElementById('loopStatusIndicator'),
document.getElementById('waitTimerContainer')
];
oldElements.forEach(element => {
if (element && element.parentNode) {
element.parentNode.removeChild(element);
}
});
const allFixedElements = document.querySelectorAll('*[style*="position: fixed"], *[style*="position:fixed"]');
for (const element of allFixedElements) {
const computedStyle = window.getComputedStyle(element);
const top = parseInt(computedStyle.top);
const left = parseInt(computedStyle.left);
if (top <= 50 && left <= 300 && !element.closest('#welearnHelperPanel') &&
(element.tagName === 'BUTTON' ||
element.tagName === 'DIV' ||
element.id.includes('auto') ||
element.id.includes('button') ||
(element.textContent && (
element.textContent.includes('一键') ||
element.textContent.includes('全自动') ||
element.textContent.includes('自动') ||
element.textContent.includes('挂机') ||
element.textContent.includes('答题') ||
element.textContent.includes('提交')
))
)) {
console.log('强制移除外部元素:', element.tagName, element.id || element.className, element.textContent?.substring(0, 20));
if (element.parentNode) {
element.parentNode.removeChild(element);
}
}
}
const loopStyle = document.getElementById('loopStatusStyle');
if (loopStyle && loopStyle.parentNode) {
loopStyle.parentNode.removeChild(loopStyle);
}
cancelWaitTimer();
console.log('移除已存在的UI元素');
}
function clearExternalButtons() {
const allButtons = document.querySelectorAll('button');
allButtons.forEach(button => {
if (button.closest('#welearnHelperPanel')) {
return;
}
if (button.textContent && (
button.textContent.includes('一键') ||
button.textContent.includes('全自动') ||
button.textContent.includes('自动') ||
button.textContent.includes('挂机') ||
button.textContent === '一键全自动' ||
button.textContent === '开始自动挂机' ||
button.textContent === '停止自动挂机'
)) {
console.log('强制移除外部按钮:', button.textContent);
if (button.parentNode) {
button.parentNode.removeChild(button);
}
}
});
const containers = document.querySelectorAll('div[style*="position: fixed"], div[style*="position:fixed"]');
containers.forEach(container => {
if (container.id === 'welearnHelperPanel' || container.closest('#welearnHelperPanel')) {
return;
}
const style = window.getComputedStyle(container);
const top = parseInt(style.top);
const left = parseInt(style.left);
if (top <= 50 && left <= 50) {
const innerButtons = container.querySelectorAll('button');
if (innerButtons.length > 0) {
console.log('移除外部按钮容器:', container.id || container.className);
if (container.parentNode) {
container.parentNode.removeChild(container);
}
}
}
});
const buttonContainer = document.getElementById('autoAnswerButtonContainer');
if (buttonContainer) {
console.log('移除autoAnswerButtonContainer');
buttonContainer.parentNode.removeChild(buttonContainer);
}
}
window.addEventListener('beforeunload', () => {
stopAutoLoop();
});
let observer;
function setupObserver() {
if (observer) {
observer.disconnect();
}
observer = new MutationObserver((mutations) => {
clearExternalButtons();
const hasPanel = document.getElementById('welearnHelperPanel');
const hasButton = document.getElementById('autoAnswerButton');
if ((!hasPanel || !hasButton) && window === window.top) {
console.log('DOM变化检测到面板或按钮不存在,添加面板');
removeExistingButtons();
addButton();
}
setTimeout(clearExternalButtons, 100);
});
if (window === window.top) {
observer.observe(document.body, {
childList: true,
subtree: true
});
clearExternalButtons();
}
}
function setupPageListeners() {
window.removeEventListener('load', handlePageLoad);
window.addEventListener('load', handlePageLoad);
if (window === window.top) {
let lastUrl = window.location.href;
const checkURLChange = () => {
if (lastUrl !== window.location.href) {
console.log('URL已变化,重新添加按钮');
lastUrl = window.location.href;
setTimeout(() => {
removeExistingButtons();
addButton();
}, 1000);
}
};
setInterval(checkURLChange, 1000);
}
}
function handlePageLoad() {
console.log('页面加载完成,检查按钮');
if (window === window.top) {
removeExistingButtons();
addButton();
}
}
function initialize() {
if (window.weLearnHelperInitialized) {
console.log('助手已初始化,跳过');
return;
}
window.weLearnHelperInitialized = true;
window.isTransitioning = false;
console.log('[WeLearn助手] 初始化 v3.0 - by Wei');
// ==================== 禁止页面自动更新 ====================
// 拦截自动更新跳转
const _origReplaceState = history.replaceState.bind(history);
history.replaceState = function () { return _origReplaceState.apply(this, arguments); };
// 阻止定时器触发的自动刷新
const _origSetTimeout = window.setTimeout;
window.setTimeout = function (fn, delay, ...args) {
const fnStr = String(fn);
if (delay < 2000 && (fnStr.includes('update') || fnStr.includes('version') || fnStr.includes('reload') || fnStr.includes('location.reload'))) {
console.log('[WeLearn助手] 拦截自动更新定时器');
return 0;
}
return _origSetTimeout.call(this, fn, delay, ...args);
};
window.setTimeout.toString = function () { return _origSetTimeout.toString(); };
if (window === window.top) {
let lastTransitionTime = 0;
const TRANSITION_COOLDOWN = 5000;
window.addEventListener('message', function (event) {
if (event.data && event.data.action === 'nextSection') {
const currentTime = Date.now();
if (currentTime - lastTransitionTime < TRANSITION_COOLDOWN) {
console.log('跳转冷却中,忽略重复请求');
return;
}
if (!window.autoNextEnabled) {
console.log('自动跳转已禁用,忽略跳转请求');
return;
}
console.log('收到iframe的跳转请求');
lastTransitionTime = currentTime;
try {
if (typeof NextSCO === 'function' && !window.isTransitioning) {
window.isTransitioning = true;
NextSCO();
console.log('成功执行NextSCO');
setTimeout(() => {
window.isTransitioning = false;
}, 5000);
}
} catch (e) {
console.error('执行NextSCO失败:', e);
window.isTransitioning = false;
}
}
});
}
if (window === window.top) {
removeExistingButtons();
addButton();
setupObserver();
setupPageListeners();
}
}
initialize();
async function simulateHTML5DragDrop(sourceElement, targetElement) {
if (!sourceElement || !targetElement) return;
console.log('使用HTML5拖放API模拟拖拽');
try {
const dragStartEvent = new DragEvent('dragstart', {
bubbles: true,
cancelable: true,
dataTransfer: new DataTransfer()
});
dragStartEvent.dataTransfer.setData('text/plain', 'dragged');
sourceElement.dispatchEvent(dragStartEvent);
await new Promise(resolve => setTimeout(resolve, 200));
const dragOverEvent = new DragEvent('dragover', {
bubbles: true,
cancelable: true,
dataTransfer: dragStartEvent.dataTransfer
});
targetElement.dispatchEvent(dragOverEvent);
await new Promise(resolve => setTimeout(resolve, 200));
const dropEvent = new DragEvent('drop', {
bubbles: true,
cancelable: true,
dataTransfer: dragStartEvent.dataTransfer
});
targetElement.dispatchEvent(dropEvent);
const dragEndEvent = new DragEvent('dragend', {
bubbles: true,
cancelable: true,
dataTransfer: dragStartEvent.dataTransfer
});
sourceElement.dispatchEvent(dragEndEvent);
console.log('HTML5拖放事件序列完成');
} catch (e) {
console.error('HTML5拖放模拟失败:', e);
}
}
async function simulateMouseDrag(sourceElement, targetElement, coords) {
if (!sourceElement || !targetElement) return;
console.log('使用增强的鼠标事件模拟拖拽');
const { fromX, fromY, toX, toY } = coords;
const dragVisual = document.createElement('div');
dragVisual.style.cssText = `
position: fixed;
pointer-events: none;
z-index: 10000;
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
opacity: 0;
background: rgba(255, 255, 255, 0.95);
border: 2px solid #4CAF50;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.2),
0 8px 24px rgba(0, 0, 0, 0.15);
padding: 8px;
transform-origin: center center;
backdrop-filter: blur(4px);
will-change: transform, opacity;
`;
if (sourceElement.tagName.toLowerCase() === 'circle') {
dragVisual.style.width = '24px';
dragVisual.style.height = '24px';
dragVisual.style.borderRadius = '50%';
dragVisual.style.background = 'linear-gradient(135deg, #4CAF50, #45a049)';
dragVisual.style.border = '2px solid rgba(255, 255, 255, 0.8)';
dragVisual.style.boxShadow = '0 2px 8px rgba(76, 175, 80, 0.3), inset 0 2px 4px rgba(255, 255, 255, 0.3)';
} else {
dragVisual.innerHTML = sourceElement.innerHTML;
dragVisual.style.width = sourceElement.offsetWidth + 'px';
dragVisual.style.height = sourceElement.offsetHeight + 'px';
dragVisual.style.background = 'linear-gradient(135deg, rgba(255, 255, 255, 0.98), rgba(255, 255, 255, 0.92))';
}
document.body.appendChild(dragVisual);
try {
requestAnimationFrame(() => {
dragVisual.style.opacity = '1';
dragVisual.style.transform = 'scale(1.05) translateY(-2px)';
});
const mouseDownEvent = new MouseEvent('mousedown', {
bubbles: true,
cancelable: true,
view: window,
clientX: fromX,
clientY: fromY,
button: 0
});
sourceElement.dispatchEvent(mouseDownEvent);
dragVisual.style.left = `${fromX}px`;
dragVisual.style.top = `${fromY}px`;
const dx = toX - fromX;
const dy = toY - fromY;
const distance = Math.sqrt(dx * dx + dy * dy);
const curvatureHeight = -Math.min(distance * 0.3, 100);
const controlX = (fromX + toX) / 2;
const controlY = Math.min(fromY, toY) + curvatureHeight;
const steps = 30;
const stepDuration = 12;
for (let i = 1; i <= steps; i++) {
const progress = i / steps;
const ease = easeInOutCubic(progress);
const t = ease;
const moveX = Math.pow(1 - t, 2) * fromX + 2 * (1 - t) * t * controlX + Math.pow(t, 2) * toX;
const moveY = Math.pow(1 - t, 2) * fromY + 2 * (1 - t) * t * controlY + Math.pow(t, 2) * toY;
const rotation = Math.sin(progress * Math.PI) * 2;
const scale = 1.05 - Math.abs(progress - 0.5) * 0.1;
dragVisual.style.transform = `
translate3d(${moveX - dragVisual.offsetWidth / 2}px,
${moveY - dragVisual.offsetHeight / 2}px,
0)
scale(${scale})
rotate(${rotation}deg)
`;
const shadowBlur = 12 - Math.abs(progress - 0.5) * 8;
const shadowOpacity = 0.2 + Math.abs(progress - 0.5) * 0.1;
dragVisual.style.boxShadow = `
0 ${shadowBlur}px ${shadowBlur * 2}px rgba(0, 0, 0, ${shadowOpacity}),
0 ${shadowBlur / 2}px ${shadowBlur}px rgba(76, 175, 80, 0.2)
`;
const mouseMoveEvent = new MouseEvent('mousemove', {
bubbles: true,
cancelable: true,
view: window,
clientX: moveX,
clientY: moveY,
button: 0
});
document.elementFromPoint(moveX, moveY)?.dispatchEvent(mouseMoveEvent);
await new Promise(resolve => setTimeout(resolve, stepDuration));
}
const mouseUpEvent = new MouseEvent('mouseup', {
bubbles: true,
cancelable: true,
view: window,
clientX: toX,
clientY: toY,
button: 0
});
dragVisual.style.transition = 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)';
dragVisual.style.transform = 'scale(0.8) translateY(10px)';
dragVisual.style.opacity = '0';
targetElement.dispatchEvent(mouseUpEvent);
await new Promise(resolve => setTimeout(resolve, 200));
document.body.removeChild(dragVisual);
targetElement.style.transition = 'transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
targetElement.style.transform = 'scale(1.1)';
await new Promise(resolve => setTimeout(resolve, 100));
targetElement.style.transform = '';
console.log('增强的鼠标拖拽事件序列完成');
} catch (e) {
console.error('增强的鼠标拖拽模拟失败:', e);
if (document.body.contains(dragVisual)) {
document.body.removeChild(dragVisual);
}
}
}
function easeInOutCubic(t) {
return t < 0.5
? 4 * t * t * t
: 1 - Math.pow(-2 * t + 2, 3) / 2;
}
function bezierPoint(t, p0, p1, p2) {
const oneMinusT = 1 - t;
return Math.pow(oneMinusT, 2) * p0 + 2 * oneMinusT * t * p1 + Math.pow(t, 2) * p2;
}
async function simulateDragDrop(sourceElement, targetElement, coords) {
if (!sourceElement || !targetElement) return;
console.log('使用增强的组合事件模拟拖拽');
const { fromX, fromY, toX, toY } = coords;
try {
sourceElement.style.transition = 'transform 0.2s ease-out';
sourceElement.style.transform = 'scale(1.05)';
sourceElement.dispatchEvent(new MouseEvent('mousedown', {
bubbles: true,
cancelable: true,
view: window,
clientX: fromX,
clientY: fromY
}));
await new Promise(resolve => setTimeout(resolve, 100));
const dragStartEvent = new Event('dragstart', { bubbles: true, cancelable: true });
sourceElement.dispatchEvent(dragStartEvent);
const steps = 20;
const controlX = (fromX + toX) / 2;
const controlY = Math.min(fromY, toY) - 40;
for (let i = 1; i <= steps; i++) {
const t = i / steps;
const moveX = bezierPoint(t, fromX, controlX, toX);
const moveY = bezierPoint(t, fromY, controlY, toY);
document.dispatchEvent(new MouseEvent('mousemove', {
bubbles: true,
cancelable: true,
view: window,
clientX: moveX,
clientY: moveY
}));
await new Promise(resolve => setTimeout(resolve, 15));
}
targetElement.style.transition = 'transform 0.2s ease-out';
targetElement.style.transform = 'scale(1.1)';
targetElement.dispatchEvent(new Event('dragover', { bubbles: true, cancelable: true }));
await new Promise(resolve => setTimeout(resolve, 100));
targetElement.dispatchEvent(new Event('drop', { bubbles: true, cancelable: true }));
sourceElement.dispatchEvent(new Event('dragend', { bubbles: true, cancelable: true }));
targetElement.dispatchEvent(new MouseEvent('mouseup', {
bubbles: true,
cancelable: true,
view: window,
clientX: toX,
clientY: toY
}));
sourceElement.style.transform = '';
targetElement.style.transform = '';
console.log('增强的组合拖拽事件序列完成');
} catch (e) {
console.error('增强的组合拖拽模拟失败:', e);
sourceElement.style.transform = '';
targetElement.style.transform = '';
}
}
async function handleTabNavigation(retryCount = 3) {
for (let attempt = 1; attempt <= retryCount; attempt++) {
try {
let doc = document;
if (window !== window.top) {
doc = document;
} else {
const contentFrame = document.getElementById('contentFrame');
if (contentFrame && contentFrame.contentDocument) {
doc = contentFrame.contentDocument;
}
}
if (attempt > 1) {
console.log(`第${attempt}次尝试查找标签页...`);
await sleep(1000 * attempt);
}
const headerTabSelectors = [
'ul.headerTab.center',
'ul[header][class*="headerTab"][class*="center"]',
'ul[class*="headerTab"][class*="center"]',
'et-tab ul[header]'
];
let headerTab = null;
for (const selector of headerTabSelectors) {
headerTab = doc.querySelector(selector);
if (headerTab) {
console.log(`找到headerTab,使用选择器: ${selector}`);
break;
}
}
if (!headerTab) {
console.log('未找到headerTab center标签');
return false;
}
const tabSelectors = [
'li[ng-click*="tab.go"]',
'li[count]',
'li[ng-class*="tab.active"]'
];
let tabElements = [];
for (const selector of tabSelectors) {
const elements = headerTab.querySelectorAll(selector);
if (elements.length > 0) {
tabElements = Array.from(elements);
console.log(`找到标签页元素,使用选择器: ${selector}`);
break;
}
}
if (tabElements.length === 0) {
console.log('未找到标签页元素');
return false;
}
const tabInfo = tabElements.map(tab => {
const info = {
element: tab,
goNumber: -1,
activeNumber: -1,
count: -1
};
const ngClickAttr = tab.getAttribute('ng-click');
if (ngClickAttr) {
const goMatch = ngClickAttr.match(/tab\.go\((\d+)\)/);
if (goMatch) {
info.goNumber = parseInt(goMatch[1]);
}
}
const ngClassAttr = tab.getAttribute('ng-class');
if (ngClassAttr) {
const activeMatch = ngClassAttr.match(/tab\.active === (\d+)/);
if (activeMatch) {
info.activeNumber = parseInt(activeMatch[1]);
}
}
const countAttr = tab.getAttribute('count');
if (countAttr) {
info.count = parseInt(countAttr);
}
return info;
});
const lastTab = tabInfo.reduce((prev, current) => {
if (current.goNumber > prev.goNumber) return current;
if (current.goNumber < prev.goNumber) return prev;
if (current.activeNumber > prev.activeNumber) return current;
if (current.activeNumber < prev.activeNumber) return prev;
if (current.count > prev.count) return current;
return prev;
});
if (lastTab.element) {
console.log(`找到最后的标签页:`, {
goNumber: lastTab.goNumber,
activeNumber: lastTab.activeNumber,
count: lastTab.count
});
const isCurrentTab = lastTab.element.classList.contains('active');
if (!isCurrentTab) {
console.log('尝试点击最后的标签页');
const triggerClick = async () => {
if (typeof angular !== 'undefined') {
try {
const scope = angular.element(lastTab.element).scope();
if (scope && scope.tab && typeof scope.tab.go === 'function') {
await new Promise(resolve => {
scope.$apply(() => {
scope.tab.go(lastTab.goNumber);
resolve();
});
});
return true;
}
} catch (e) {
console.log('Angular触发失败:', e);
}
}
try {
const events = [
new MouseEvent('mousedown', { bubbles: true, cancelable: true }),
new MouseEvent('mouseup', { bubbles: true, cancelable: true }),
new MouseEvent('click', { bubbles: true, cancelable: true }),
new Event('focus', { bubbles: true, cancelable: true })
];
for (const event of events) {
lastTab.element.dispatchEvent(event);
await sleep(100);
}
return true;
} catch (e) {
console.log('事件触发失败:', e);
}
try {
lastTab.element.click();
return true;
} catch (e) {
console.log('直接点击失败:', e);
return false;
}
};
const clickResult = await triggerClick();
if (clickResult) {
await sleep(1000);
return true;
}
} else {
console.log('已经在最后的标签页');
return true;
}
}
return false;
} catch (error) {
console.error(`第${attempt}次尝试出错:`, error);
if (attempt < retryCount) {
continue;
}
}
}
console.error(`在${retryCount}次尝试后仍未能成功处理标签页`);
return false;
}
async function handleTabExercises() {
console.log('开始处理带标签页的练习题目');
try {
const headerTabResult = await handleTabNavigation();
if (headerTabResult) {
console.log('成功处理headerTab navigation');
await sleep(1000);
}
const tabElements = document.querySelectorAll('li[ng-click*="tab.go"]');
if (tabElements.length === 0) {
console.log('未找到标签页元素');
return false;
}
console.log(`找到${tabElements.length}个标签页`);
for (let i = 0; i < tabElements.length; i++) {
const tab = tabElements[i];
console.log(`切换到第${i + 1}个标签页`);
try {
tab.click();
await sleep(1000);
if (typeof angular !== 'undefined') {
const scope = angular.element(tab).scope();
if (scope) {
scope.$apply();
}
}
await sleep(2000);
await autoSelectCorrectAnswers();
await autoFillAnswers();
await autoSelectDropdownAnswers();
await autoMatchLines();
await handleUnitTestAnswers();
if (i === tabElements.length - 1) {
console.log('到达最后一个标签页,准备提交答案');
await sleep(1000);
if (window.autoSubmitEnabled) {
console.log('准备提交答案');
const submitResult = await submitAnswers();
if (submitResult && !window.autoNextEnabled) {
return true;
}
} else {
console.log('自动提交已禁用,跳过提交');
}
} else {
await sleep(1000);
}
} catch (error) {
console.error(`处理第${i + 1}个标签页时出错:`, error);
}
}
return true;
} catch (error) {
console.error('处理标签页练习题目时出错:', error);
return false;
}
}
async function handlePickerQuestions() {
console.log('开始处理选择框题目');
let answered = false;
try {
let doc = document;
if (window !== window.top) {
doc = document;
} else {
const contentFrame = document.getElementById('contentFrame');
if (contentFrame && contentFrame.contentDocument) {
doc = contentFrame.contentDocument;
}
}
const blankElements = Array.from(doc.querySelectorAll('span.blank[ng-attr-tabindex]'));
const totalQuestions = blankElements.length;
console.log('找到空白框数量:', totalQuestions);
if (totalQuestions === 0) return false;
const correctCount = Math.ceil((totalQuestions * window.defaultAccuracyRate) / 100);
const wrongCount = totalQuestions - correctCount;
console.log(`总题目: ${totalQuestions}, 正确数: ${correctCount}, 错误数: ${wrongCount}`);
let allAnswers = [];
if (wrongCount > 0) {
for (const blank of blankElements) {
const keySpan = blank.closest('et-blank')?.querySelector('span[ng-if="blank.hasKey"]');
if (keySpan) {
allAnswers.push({
blank: blank,
answer: keySpan.textContent.trim()
});
}
}
if (allAnswers.length < 2) {
console.log('答案数量不足,无法实现错误率要求');
return false;
}
if (wrongCount >= 2) {
for (let i = allAnswers.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[allAnswers[i], allAnswers[j]] = [allAnswers[j], allAnswers[i]];
}
const swapCount = Math.min(wrongCount, Math.floor(allAnswers.length / 2));
for (let i = 0; i < swapCount * 2; i += 2) {
const temp = allAnswers[i].answer;
allAnswers[i].answer = allAnswers[i + 1].answer;
allAnswers[i + 1].answer = temp;
}
}
}
for (let i = 0; i < blankElements.length; i++) {
const blankElement = blankElements[i];
try {
const existingPicker = doc.querySelector('div.optionsPicker.visible');
if (existingPicker) {
const closeButton = existingPicker.querySelector('a.close');
if (closeButton) {
closeButton.click();
await sleep(500);
}
}
console.log(`准备处理第${i + 1}个空白框`);
if (!isElementVisible(blankElement)) {
console.log('空白框不可见,跳过');
continue;
}
async function enhancedClick(element) {
try {
element.click();
await sleep(200);
const rect = element.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const eventTypes = ['mouseenter', 'mouseover', 'mousedown', 'mouseup', 'click'];
for (const eventType of eventTypes) {
const event = new Event(eventType, {
bubbles: true,
cancelable: true
});
Object.defineProperties(event, {
clientX: { value: centerX },
clientY: { value: centerY },
screenX: { value: centerX },
screenY: { value: centerY }
});
element.dispatchEvent(event);
await sleep(50);
}
['focus', 'focusin'].forEach(eventType => {
const event = new Event(eventType, {
bubbles: true,
cancelable: true
});
element.dispatchEvent(event);
});
await sleep(100);
if (typeof angular !== 'undefined') {
const scope = angular.element(element).scope();
if (scope && scope.blank) {
if (typeof scope.blank.click === 'function') {
scope.blank.click();
}
if (typeof scope.blank.focus === 'function') {
scope.blank.focus();
}
scope.$apply();
}
}
const parentWrapper = element.closest('span.wrapper');
if (parentWrapper) {
parentWrapper.click();
await sleep(100);
}
const etBlank = element.closest('et-blank');
if (etBlank) {
etBlank.click();
await sleep(100);
}
} catch (e) {
console.error('增强点击失败:', e);
}
}
await enhancedClick(blankElement);
await sleep(500);
let optionsPicker = null;
let optionsWrapper = null;
let attempts = 0;
const maxAttempts = 5;
while (!optionsWrapper && !optionsPicker && attempts < maxAttempts) {
optionsPicker = doc.querySelector('div.optionsPicker.visible');
optionsWrapper = doc.querySelector('div.optionsWrapper')
if (!optionsPicker && !optionsWrapper) {
console.log(`等待选择框出现,尝试 ${attempts + 1}/${maxAttempts}`);
attempts++;
if (attempts > 2) {
await enhancedClick(blankElement);
}
await sleep(500);
}
}
if (!optionsPicker && !optionsWrapper) {
console.log('选择框未出现,跳过当前空白框');
continue;
}
let targetAnswer;
if (wrongCount > 0 && allAnswers.length > 0) {
const answerObj = allAnswers.find(a => a.blank === blankElement);
if (answerObj) {
targetAnswer = answerObj.answer;
console.log('使用交换后的答案:', targetAnswer);
} else {
const keySpan = blankElement.closest('et-blank')?.querySelector('span[ng-if="blank.hasKey"]');
targetAnswer = keySpan ? keySpan.textContent.trim() : null;
console.log('使用原始答案:', targetAnswer);
}
} else {
const keySpan = blankElement.closest('et-blank')?.querySelector('span[ng-if="blank.hasKey"]');
targetAnswer = keySpan ? keySpan.textContent.trim() : null;
console.log('使用原始答案:', targetAnswer);
}
if (!targetAnswer) {
console.log('未找到答案,跳过当前空白框');
continue;
}
let optionsList = null;
let options = [];
try {
if (!doc) {
console.log('文档对象不存在,跳过当前空白框');
continue;
}
optionsPicker = doc.querySelector('div.optionsPicker.visible');
optionsWrapper = doc.querySelector('div.optionsWrapper');
console.log('选择框状态:', {
hasOptionsPicker: !!optionsPicker,
hasOptionsWrapper: !!optionsWrapper
});
if (optionsPicker && optionsPicker.querySelector) {
console.log('从optionsPicker中查找选项列表');
const pickerList = optionsPicker.querySelector('ul');
if (pickerList && pickerList.querySelectorAll) {
const pickerOptions = pickerList.querySelectorAll('li[ng-click*="options.toggle"]');
if (pickerOptions && pickerOptions.length > 0) {
optionsList = pickerList;
options = Array.from(pickerOptions);
console.log('在optionsPicker中找到', options.length, '个选项');
}
}
}
if ((!optionsList || options.length === 0) && optionsWrapper && optionsWrapper.querySelector) {
console.log('从optionsWrapper中查找选项列表');
const wrapperList = optionsWrapper.querySelector('ul');
if (wrapperList && wrapperList.querySelectorAll) {
const wrapperOptions = wrapperList.querySelectorAll('li[ng-click*="options.toggle"]');
if (wrapperOptions && wrapperOptions.length > 0) {
optionsList = wrapperList;
options = Array.from(wrapperOptions);
console.log('在optionsWrapper中找到', options.length, '个选项');
}
}
}
if (!optionsList || options.length === 0) {
console.log('尝试从整个文档中查找选项列表');
try {
const lists = doc.querySelectorAll('.optionsPicker.visible ul, .optionsWrapper ul');
if (lists && lists.length > 0) {
for (const list of lists) {
if (list && list.querySelectorAll) {
const docOptions = list.querySelectorAll('li[ng-click*="options.toggle"]');
if (docOptions && docOptions.length > 0) {
optionsList = list;
options = Array.from(docOptions);
console.log('在文档中找到', options.length, '个选项');
break;
}
}
}
}
} catch (e) {
console.error('从文档查找选项时出错:', e);
}
}
if (!optionsList || options.length === 0) {
console.log('未找到有效的选项列表或选项为空,跳过当前空白框');
continue;
}
console.log(`最终找到 ${options.length} 个选项`);
let optionFound = false;
for (const option of options) {
if (!option || !option.getAttribute) continue;
try {
const ngClickAttr = option.getAttribute('ng-click');
const toggleMatch = ngClickAttr.match(/toggle\('([^']+)'\)/);
if (!toggleMatch) continue;
const optionValue = toggleMatch[1];
console.log('检查选项:', optionValue);
if (optionValue === targetAnswer) {
console.log('找到匹配选项:', optionValue);
option.click();
await sleep(300);
if (typeof angular !== 'undefined') {
const scope = angular.element(option).scope();
if (scope?.options?.toggle) {
scope.options.toggle(optionValue);
scope.$apply();
}
}
optionFound = true;
answered = true;
await sleep(500);
break;
}
} catch (e) {
console.error('处理选项时出错:', e);
}
}
if (!optionFound) {
console.log('未找到匹配选项');
}
if (optionsPicker) {
const closeButton = optionsPicker.querySelector('a.close');
if (closeButton) {
closeButton.click();
await sleep(800);
}
}
} catch (error) {
console.error('处理空白框时出错:', error);
}
} catch (error) {
console.error('处理空白框时出错:', error);
}
}
} catch (error) {
console.error('处理选择框题目时出错:', error);
}
return answered;
}
// 暴露点击工具函数到全局
window.clickElement = clickElement;
})();