// ==UserScript== // @name U助手-自用本地版 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 解决U校园的刷题烦恼(使用硅基流动API) // @author Gemini // @match *://ucontent.unipus.cn/* // @grant GM_xmlhttpRequest // @connect api.siliconflow.cn // @connect * // ==/UserScript== // --- (1) 用户需要修改的配置 --- const SILICONFLOW_API_KEY = "sk-vmkgibbrlnmyfgofxilejrulypmephmidvcyfwgijabxqyny"; // <--- 在这里填入你的硅基流动API Key const SILICONFLOW_API_ENDPOINT = "https://api.siliconflow.cn/v1/chat/completions"; // <--- 确认这是正确的API端点 const SILICONFLOW_MODEL_NAME = "deepseek-ai/DeepSeek-V3.2-Exp"; // <--- 在这里填入你想使用的模型名称 // --- 结束配置 --- let loadedQuestionBank = null; let currentTopicUsedAnswers = new Set(); let lastActiveTopicName = ''; // 移除所有付费和积分相关的功能,默认返回true以通过校验 async function checkAndDeductPoints() { console.log("付费功能已移除,积分检查已跳过。"); return true; } let useKimiAI = localStorage.getItem('useKimiAI') === 'true'; // 变量名保留,避免用户设置丢失 const SYSTEM_PROMPT = `你是一个专业的英语教学助手,擅长分析英语题目。请注意: 1. 阅读整个页面的所有题目和选项 2. 给出每道题的答案,格式为:1.A, 2.B 这样的形式 3. 只使用实际存在的选项字母 4. 理解题目类型,特别是"not collocate"类型表示选择不正确的搭配 5. 确保答案数量与题目数量一致 6. 如果是填空题,直接给出单词或短语答案 7. 如果遇到不确定的答案,说明原因并给出最可能的选项`; async function askAI(question, retryCount = 3, retryDelay = 1000) { if (!useKimiAI) { alert('请先开启AI功能'); return null; } if (SILICONFLOW_API_KEY === "YOUR_API_KEY_HERE") { alert('错误:请先在脚本代码中填入您的硅基流动API Key!'); return null; } for (let attempt = 1; attempt <= retryCount; attempt++) { try { console.log(`正在调用硅基流动 API... (第${attempt}次尝试)`); const messages = [ { role: "system", content: SYSTEM_PROMPT }, { role: "user", content: question } ]; const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: SILICONFLOW_API_ENDPOINT, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${SILICONFLOW_API_KEY}` }, data: JSON.stringify({ model: SILICONFLOW_MODEL_NAME, messages: messages, temperature: 0.6 }), timeout: 30000, onload: function(response) { console.log('硅基流动 API响应状态:', response.status); console.log('硅基流动 API响应体:', response.responseText); resolve(response); }, onerror: function(error) { console.error('硅基流动 API请求错误:', error); reject(error); }, ontimeout: function() { console.error('硅基流动 API请求超时'); reject(new Error('请求超时')); } }); }); if (response.status >= 200 && response.status < 300 && response.responseText) { try { const data = JSON.parse(response.responseText); console.log('硅基流动 API响应:', data); if (data.choices && data.choices[0] && data.choices[0].message && data.choices[0].message.content) { return data.choices[0].message.content; } else { throw new Error('API响应格式不正确'); } } catch (error) { console.error('解析API响应失败:', error); if (attempt === retryCount) throw error; } } else { let errorMessage = `API请求失败 (HTTP ${response.status})`; try { if (response.responseText) { const errorData = JSON.parse(response.responseText); errorMessage += `: ${JSON.stringify(errorData)}`; } } catch (e) { // Ignore parsing error } console.error(errorMessage); if (attempt === retryCount) { console.log('所有重试都失败,使用本地分析替代API响应'); return await analyzeQuestionLocally(question); } } if (attempt < retryCount) { const delay = retryDelay * Math.pow(2, attempt - 1); console.log(`等待 ${delay}ms 后重试...`); await new Promise(resolve => setTimeout(resolve, delay)); } } catch (error) { console.error(`第${attempt}次尝试失败:`, error); if (attempt === retryCount) { console.log('所有重试都失败,使用本地分析替代API响应'); return await analyzeQuestionLocally(question); } const delay = retryDelay * Math.pow(2, attempt - 1); console.log(`等待 ${delay}ms 后重试...`); await new Promise(resolve => setTimeout(resolve, delay)); } } return null; } async function analyzeQuestionLocally(question) { console.log('开始本地分析题目'); const questionLines = question.split('\n'); const questionText = questionLines.find(line => line.includes('Identify') || line.includes('Choose') || line.includes('Select') || line.includes('which') ) || questionLines[0]; console.log('题目文本:', questionText); const options = []; let optionLetters = ''; questionLines.forEach(line => { const optionMatch = line.match(/^([A-D])[\.:\)\s]\s*(.+)$/); if (optionMatch) { const [, letter, text] = optionMatch; options.push({ letter, text }); console.log(`选项 ${letter}: ${text}`); } }); let simulatedAnswer = ''; if (questionText.includes('not collocate') || questionText.includes('do not') || questionText.includes('incorrect') || questionText.includes('wrong') || questionText.includes('false')) { console.log('识别为"查找不正确"类型题目'); simulatedAnswer = 'B'; optionLetters = 'B'; } else if (questionText.includes('collocate') || questionText.includes('match') || questionText.includes('pair') || questionText.includes('correct') || questionText.includes('true')) { console.log('识别为"查找正确"类型题目'); simulatedAnswer = 'A, C, D'; optionLetters = 'ACD'; } else { console.log('无法确定题目类型,返回所有选项'); optionLetters = options.map(opt => opt.letter).join(', '); simulatedAnswer = optionLetters; } console.log('本地分析生成的答案:', simulatedAnswer); return simulatedAnswer; } function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function createCollapsibleSection(title, storageKey) { const section = document.createElement('div'); section.className = 'u-helper-section u-collapsible'; const header = document.createElement('div'); header.className = 'u-helper-section-header'; const titleEl = document.createElement('div'); titleEl.className = 'u-helper-section-title'; titleEl.textContent = title; const icon = document.createElement('div'); icon.className = 'u-collapse-icon'; icon.innerHTML = ''; const content = document.createElement('div'); content.className = 'u-helper-section-content'; header.appendChild(titleEl); header.appendChild(icon); section.appendChild(header); section.appendChild(content); const isCollapsed = localStorage.getItem(storageKey) === 'true'; if (isCollapsed) { section.classList.add('u-collapsed'); } header.addEventListener('click', () => { const collapsed = section.classList.toggle('u-collapsed'); localStorage.setItem(storageKey, collapsed); }); return { section, content }; } function createFloatingButton() { const styles = ` :root { --u-bg-color: rgba(255, 255, 255, 0.75); --u-blur-bg-color: rgba(255, 255, 255, 0.5); --u-border-color: rgba(0, 0, 0, 0.08); --u-text-color-primary: #1a202c; --u-text-color-secondary: #4a5568; --u-text-color-tertiary: #718096; --u-accent-color: #4299e1; --u-accent-color-dark: #3182ce; --u-success-color: #38a169; --u-danger-color: #e53e3e; --u-warning-color: #dd6b20; --u-font-family: 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif; --u-border-radius-lg: 16px; --u-border-radius-md: 12px; --u-border-radius-sm: 8px; --u-shadow-lg: 0 10px 25px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); --u-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); } .u-helper-container { position: fixed; top: 20px; right: 20px; z-index: 10000; display: flex; flex-direction: column; font-family: var(--u-font-family); background: var(--u-bg-color); backdrop-filter: blur(16px) saturate(180%); -webkit-backdrop-filter: blur(16px) saturate(180%); border-radius: var(--u-border-radius-lg); border: 1px solid rgba(255, 255, 255, 0.6); box-shadow: var(--u-shadow-lg); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); overflow: hidden; width: 320px; opacity: 0; transform: translateY(-20px) scale(0.95); color: var(--u-text-color-primary); } .u-helper-container.u-visible { opacity: 1; transform: translateY(0) scale(1); } .u-helper-title-bar { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; background-color: var(--u-blur-bg-color); font-weight: 600; cursor: move; user-select: none; font-size: 15px; border-bottom: 1px solid var(--u-border-color); flex-shrink: 0; } .u-helper-title { display: flex; align-items: center; gap: 10px; } .u-helper-control-btn { background: transparent; border: none; color: var(--u-text-color-secondary); font-size: 20px; cursor: pointer; padding: 0; display: flex; align-items: center; justify-content: center; height: 28px; width: 28px; border-radius: var(--u-border-radius-sm); transition: all 0.2s ease-in-out; } .u-helper-control-btn:hover { background-color: rgba(0, 0, 0, 0.08); transform: scale(1.1); } .u-helper-content { display: flex; flex-direction: column; gap: 16px; padding: 16px; width: 100%; box-sizing: border-box; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); overflow-y: auto; max-height: calc(100vh - 150px); } .u-helper-section { display: flex; flex-direction: column; padding: 12px; background: rgba(255, 255, 255, 0.6); border-radius: var(--u-border-radius-md); } .u-helper-section-header { display: flex; justify-content: space-between; align-items: center; font-weight: 600; } .u-helper-section.u-collapsible > .u-helper-section-header { cursor: pointer; margin: -12px; padding: 12px; } .u-helper-section-content { padding-top: 12px; display: flex; flex-direction: column; gap: 12px; } .u-collapse-icon { transition: transform 0.2s ease-in-out; color: var(--u-text-color-tertiary); } .u-collapsible.u-collapsed .u-collapse-icon { transform: rotate(-90deg); } .u-collapsible.u-collapsed .u-helper-section-content { display: none; } .u-helper-section-title { font-size: 13px; color: var(--u-text-color-primary); font-weight: 600; } .u-helper-input-row { display: flex; align-items: center; justify-content: space-between; } .u-helper-label { font-size: 13px; color: var(--u-text-color-secondary); } .u-helper-input { width: 75px; padding: 6px 8px; border-radius: var(--u-border-radius-sm); border: 1px solid var(--u-border-color); text-align: center; background-color: #fff; font-size: 13px; transition: all 0.2s; box-shadow: var(--u-shadow-md); } .u-helper-input:focus { outline: none; border-color: var(--u-accent-color); box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.2); } .u-helper-file-upload-btn { padding: 10px 16px; border: 1px solid transparent; border-radius: var(--u-border-radius-md); font-size: 14px; font-weight: 500; color: var(--u-accent-color-dark); background: rgba(66, 153, 225, 0.1); cursor: pointer; text-align: center; transition: all 0.3s ease; width: 100%; box-sizing: border-box; } .u-helper-file-upload-btn:hover { background: rgba(66, 153, 225, 0.2); border-color: rgba(66, 153, 225, 0.3); transform: translateY(-2px); box-shadow: var(--u-shadow-md); } .u-helper-select-group { display: flex; align-items: center; gap: 8px; } .u-helper-select { flex-grow: 1; padding: 8px 12px; border: 1px solid var(--u-border-color); border-radius: var(--u-border-radius-md); font-size: 13px; background-color: #fff; cursor: pointer; width: 100%; min-width: 0; box-shadow: var(--u-shadow-md); transition: all 0.2s; } .u-helper-select:focus { outline: none; border-color: var(--u-accent-color); box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.2); } .u-helper-delete-btn { background: rgba(0, 0, 0, 0.05); color: var(--u-text-color-tertiary); border: none; border-radius: 50%; width: 28px; height: 28px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); flex-shrink: 0; display: none; } .u-helper-delete-btn:hover { background-color: var(--u-danger-color); color: white; transform: scale(1.1) rotate(90deg); } .u-helper-btn { padding: 10px 16px; border: none; border-radius: var(--u-border-radius-md); font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease-in-out; display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; box-sizing: border-box; box-shadow: var(--u-shadow-md); } .u-helper-btn:hover { transform: translateY(-2px); box-shadow: var(--u-shadow-lg); } .u-helper-btn:active { transform: translateY(0) scale(0.98); box-shadow: var(--u-shadow-md); } .u-helper-btn-primary { background: var(--u-accent-color); color: white; } .u-helper-btn-primary:hover { background: var(--u-accent-color-dark); } .u-helper-btn-success { background: var(--u-success-color); color: white; } .u-helper-btn-warning { background: var(--u-warning-color); color: white; } .u-helper-btn-danger { background: var(--u-danger-color); color: white; } .u-helper-btn-secondary { background: #fff; color: var(--u-text-color-secondary); border: 1px solid var(--u-border-color); } .u-helper-btn-secondary:hover { background: #f7fafc; } .u-helper-info-display { padding: 12px; background: var(--u-blur-bg-color); border-radius: var(--u-border-radius-md); font-size: 13px; color: var(--u-text-color-primary); width: 100%; max-height: 250px; overflow-y: auto; overflow-x: hidden; overflow-wrap: break-word; white-space: pre-wrap; line-height: 1.4; display: none; border: 1px solid rgba(255, 255, 255, 0.8); box-sizing: border-box; opacity: 0; transform: translateY(-10px) scale(0.98); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .u-helper-info-display.u-visible { display: block; opacity: 1; transform: translateY(0) scale(1); } .u-helper-info-display::-webkit-scrollbar { width: 6px; } .u-helper-info-display::-webkit-scrollbar-track { background: transparent; } .u-helper-info-display::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.2); border-radius: 3px; } .u-helper-info-display::-webkit-scrollbar-thumb:hover { background: rgba(0, 0, 0, 0.3); } .u-helper-footer { width: 100%; padding: 8px 0; margin-top: 8px; border-top: 1px solid var(--u-border-color); font-size: 12px; color: var(--u-text-color-tertiary); text-align: center; font-weight: 500; cursor: default; user-select: none; } .u-ripple { position: absolute; border-radius: 50%; background: rgba(255, 255, 255, 0.4); transform: scale(0); animation: u-ripple-anim 0.6s linear; pointer-events: none; } @keyframes u-ripple-anim { to { transform: scale(4); opacity: 0; } } `; const styleSheet = document.createElement("style"); styleSheet.type = "text/css"; styleSheet.innerText = styles; document.head.appendChild(styleSheet); const container = document.createElement('div'); container.className = 'u-helper-container'; setTimeout(() => { container.classList.add('u-visible'); }, 100); const titleBar = document.createElement('div'); titleBar.className = 'u-helper-title-bar'; titleBar.innerHTML = `
U-Helper Local
`; const buttonContainer = document.createElement('div'); const minimizeButton = document.createElement('button'); minimizeButton.innerHTML = '⚊'; minimizeButton.className = 'u-helper-control-btn'; const contentContainer = document.createElement('div'); contentContainer.className = 'u-helper-content'; const { section: aiConfigSection, content: aiConfigContent } = createCollapsibleSection('AI配置', 'u-collapse-ai'); const aiToggleContainer = document.createElement('div'); aiToggleContainer.className = 'u-helper-setting-item'; const aiToggleLabel = document.createElement('label'); aiToggleLabel.className = 'u-helper-checkbox-label'; aiToggleLabel.style.display = 'flex'; aiToggleLabel.style.alignItems = 'center'; aiToggleLabel.style.gap = '8px'; const aiToggle = document.createElement('input'); aiToggle.type = 'checkbox'; aiToggle.checked = useKimiAI; aiToggle.onchange = (e) => { useKimiAI = e.target.checked; localStorage.setItem('useKimiAI', useKimiAI.toString()); }; aiToggleLabel.appendChild(aiToggle); aiToggleLabel.appendChild(document.createTextNode('AI答题 (硅基流动)')); aiToggleContainer.appendChild(aiToggleLabel); const aiDescription = document.createElement('div'); aiDescription.className = 'u-helper-setting-item'; aiDescription.style.marginTop = '8px'; aiDescription.style.padding = '8px'; aiDescription.style.backgroundColor = 'rgba(0,0,0,0.03)'; aiDescription.style.borderRadius = '4px'; aiDescription.style.fontSize = '12px'; aiDescription.innerHTML = `AI模型: ${SILICONFLOW_MODEL_NAME}`; aiConfigContent.appendChild(aiToggleContainer); aiConfigContent.appendChild(aiDescription); contentContainer.appendChild(aiConfigSection); const { section: fileManagementSection, content: fileManagementContent } = createCollapsibleSection('题库管理', 'u-collapse-file'); const fileInputButton = document.createElement('label'); fileInputButton.htmlFor = 'question-bank-uploader'; fileInputButton.textContent = '上传新题库 (.json)'; fileInputButton.className = 'u-helper-file-upload-btn'; const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.id = 'question-bank-uploader'; fileInput.accept = '.json'; fileInput.style.display = 'none'; const bankSelectorGroup = document.createElement('div'); bankSelectorGroup.className = 'u-helper-select-group'; const bankSelector = document.createElement('select'); bankSelector.className = 'u-helper-select'; const clearSelectedBankButton = document.createElement('button'); clearSelectedBankButton.innerHTML = ''; clearSelectedBankButton.title = '删除选中的题库'; clearSelectedBankButton.className = 'u-helper-delete-btn'; bankSelectorGroup.appendChild(bankSelector); bankSelectorGroup.appendChild(clearSelectedBankButton); fileManagementContent.appendChild(fileInputButton); fileManagementContent.appendChild(fileInput); fileManagementContent.appendChild(bankSelectorGroup); const { section: delaySettingsContainer, content: delaySettingsContent } = createCollapsibleSection('自动化延迟 (毫秒)', 'u-collapse-delay'); const answerDelayContainer = document.createElement('div'); answerDelayContainer.className = 'u-helper-input-row'; const answerDelayLabel = document.createElement('label'); answerDelayLabel.textContent = '答案填入延迟'; answerDelayLabel.className = 'u-helper-label'; const answerDelayInput = document.createElement('input'); answerDelayInput.type = 'number'; answerDelayInput.min = '100'; answerDelayInput.max = '5000'; answerDelayInput.value = localStorage.getItem('u-answer-delay') || '800'; answerDelayInput.className = 'u-helper-input'; answerDelayInput.addEventListener('change', () => { localStorage.setItem('u-answer-delay', answerDelayInput.value); }); const pageDelayContainer = document.createElement('div'); pageDelayContainer.className = 'u-helper-input-row'; const pageDelayLabel = document.createElement('label'); pageDelayLabel.textContent = '页面切换延迟'; pageDelayLabel.className = 'u-helper-label'; const pageDelayInput = document.createElement('input'); pageDelayInput.type = 'number'; pageDelayInput.min = '1000'; pageDelayInput.max = '10000'; pageDelayInput.value = localStorage.getItem('u-page-delay') || '3500'; pageDelayInput.className = 'u-helper-input'; pageDelayInput.addEventListener('change', () => { localStorage.setItem('u-page-delay', pageDelayInput.value); }); answerDelayContainer.appendChild(answerDelayLabel); answerDelayContainer.appendChild(answerDelayInput); pageDelayContainer.appendChild(pageDelayLabel); pageDelayContainer.appendChild(pageDelayInput); delaySettingsContent.appendChild(answerDelayContainer); delaySettingsContent.appendChild(pageDelayContainer); let isDragging = false; let currentX; let currentY; let initialX; let initialY; let xOffset = 0; let yOffset = 0; function dragStart(e) { if (e.type === "mousedown") { initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; } isDragging = true; } function dragEnd(e) { initialX = currentX; initialY = currentY; isDragging = false; } function drag(e) { if (isDragging) { e.preventDefault(); if (e.clientX) { currentX = e.clientX - initialX; currentY = e.clientY - initialY; xOffset = currentX; yOffset = currentY; container.style.transform = `translate(${currentX}px, ${currentY}px)`; } } } titleBar.addEventListener('mousedown', dragStart); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', dragEnd); let isMinimized = false; minimizeButton.addEventListener('click', () => { isMinimized = !isMinimized; contentContainer.style.display = isMinimized ? 'none' : 'flex'; minimizeButton.innerHTML = isMinimized ? '+' : '⚊'; container.style.width = isMinimized ? 'auto' : '320px'; }); fileInput.addEventListener('change', (event) => { const file = event.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (e) => { try { const fileContent = e.target.result; JSON.parse(fileContent); const collection = getBankCollection(); collection[file.name] = fileContent; saveBankCollection(collection); localStorage.setItem('u-question-bank-selected', file.name); console.log(`题库 "${file.name}" 已成功添加并保存。`); updateBankSelectorUI(); fileInput.value = ''; } catch (error) { console.error('加载题库文件失败:', error); alert('加载失败,请检查文件是否为有效的JSON格式。'); } }; reader.readAsText(file, 'UTF-8'); } }); bankSelector.addEventListener('change', () => { const selectedFile = bankSelector.value; if (selectedFile) { const collection = getBankCollection(); const fileContent = collection[selectedFile]; if(fileContent) { try { loadedQuestionBank = JSON.parse(fileContent); localStorage.setItem('u-question-bank-selected', selectedFile); console.log(`已切换到题库: ${selectedFile}`); } catch(e) { console.error(`解析缓存的题库 ${selectedFile} 失败:`, e); loadedQuestionBank = null; } } } }); clearSelectedBankButton.addEventListener('click', () => { const selectedFile = bankSelector.value; if (selectedFile && confirm(`确定要删除题库 "${selectedFile}" 吗?此操作不可撤销。`)) { const collection = getBankCollection(); delete collection[selectedFile]; saveBankCollection(collection); const lastSelected = localStorage.getItem('u-question-bank-selected'); if (lastSelected === selectedFile) { localStorage.removeItem('u-question-bank-selected'); loadedQuestionBank = null; } console.log(`题库 "${selectedFile}" 已被删除。`); updateBankSelectorUI(); } }); function getBankCollection() { const stored = localStorage.getItem('u-question-bank-collection'); return stored ? JSON.parse(stored) : {}; } function saveBankCollection(collection) { localStorage.setItem('u-question-bank-collection', JSON.stringify(collection)); } function updateBankSelectorUI() { const collection = getBankCollection(); const lastSelected = localStorage.getItem('u-question-bank-selected'); const bankNames = Object.keys(collection); bankSelector.innerHTML = ''; loadedQuestionBank = null; if (bankNames.length === 0) { const option = document.createElement('option'); option.textContent = '暂无题库'; option.disabled = true; bankSelector.appendChild(option); clearSelectedBankButton.style.display = 'none'; } else { bankNames.forEach(name => { const option = document.createElement('option'); option.value = name; option.textContent = name; bankSelector.appendChild(option); }); const selectedFile = bankNames.includes(lastSelected) ? lastSelected : bankNames[0]; bankSelector.value = selectedFile; try { loadedQuestionBank = JSON.parse(collection[selectedFile]); localStorage.setItem('u-question-bank-selected', selectedFile); console.log(`已自动加载题库: ${selectedFile}`); } catch(e) { console.error(`解析缓存的题库 ${selectedFile} 失败:`, e); } clearSelectedBankButton.style.display = 'flex'; } } updateBankSelectorUI(); const infoDisplay = document.createElement('div'); infoDisplay.className = 'u-helper-info-display'; const toggleInfoButton = document.createElement('button'); toggleInfoButton.className = 'u-helper-btn u-helper-btn-secondary'; toggleInfoButton.innerHTML = '显示信息'; let isInfoVisible = false; toggleInfoButton.addEventListener('click', () => { isInfoVisible = !isInfoVisible; infoDisplay.classList.toggle('u-visible', isInfoVisible); toggleInfoButton.innerHTML = isInfoVisible ? '收起信息' : '显示信息'; }); const infoButton = document.createElement('button'); infoButton.className = 'u-helper-btn u-helper-btn-primary'; infoButton.innerHTML = '获取章节信息'; const autoSelectButton = document.createElement('button'); autoSelectButton.className = 'u-helper-btn u-helper-btn-primary'; autoSelectButton.innerHTML = '自动选择答案'; const autoRunButton = document.createElement('button'); autoRunButton.id = 'auto-run-btn'; autoRunButton.className = 'u-helper-btn u-helper-btn-success'; autoRunButton.innerHTML = '开始挂机'; autoSelectButton.addEventListener('click', () => { autoSelectAnswers(); }); autoRunButton.addEventListener('click', () => { toggleAutoRun(); }); const addRippleEffect = (button) => { button.addEventListener('mousedown', (e) => { const rect = button.getBoundingClientRect(); const ripple = document.createElement('span'); ripple.className = 'u-ripple'; ripple.style.left = `${e.clientX - rect.left}px`; ripple.style.top = `${e.clientY - rect.top}px`; button.appendChild(ripple); setTimeout(() => ripple.remove(), 600); }); }; [infoButton, autoSelectButton, autoRunButton, toggleInfoButton].forEach(addRippleEffect); const footerBar = document.createElement('div'); footerBar.className = 'u-helper-footer'; footerBar.innerHTML = 'By 恶搞之家'; buttonContainer.appendChild(minimizeButton); titleBar.appendChild(buttonContainer); contentContainer.appendChild(aiConfigSection); contentContainer.appendChild(fileManagementSection); contentContainer.appendChild(delaySettingsContainer); const { section: videoSettingsSection, content: videoSettingsContent } = createCollapsibleSection('视频设置', 'u-collapse-video'); const videoSpeedContainer = document.createElement('div'); videoSpeedContainer.className = 'u-helper-input-row'; const videoSpeedLabel = document.createElement('label'); videoSpeedLabel.className = 'u-helper-label'; videoSpeedLabel.textContent = '播放速度'; const videoSpeedSelector = document.createElement('select'); videoSpeedSelector.className = 'u-helper-select'; videoSpeedSelector.style.width = '120px'; ['1.0', '1.25', '1.5', '2.0', '2.5', '3.0'].forEach(speed => { const option = document.createElement('option'); option.value = speed; option.textContent = `${speed}x`; videoSpeedSelector.appendChild(option); }); videoSpeedSelector.value = localStorage.getItem('u-video-speed') || '2.0'; videoSpeedSelector.addEventListener('change', () => { const newSpeed = parseFloat(videoSpeedSelector.value); localStorage.setItem('u-video-speed', newSpeed); document.querySelectorAll('video[data-handled-by-script="true"]').forEach(v => { v.playbackRate = newSpeed; console.log(`[视频助手] 已将视频速度调整为 ${newSpeed}x`); }); }); videoSpeedContainer.appendChild(videoSpeedLabel); videoSpeedContainer.appendChild(videoSpeedSelector); videoSettingsContent.appendChild(videoSpeedContainer); contentContainer.appendChild(videoSettingsSection); const buttonGrid = document.createElement('div'); buttonGrid.style.cssText = 'display: grid; grid-template-columns: 1fr 1fr; gap: 10px;'; buttonGrid.appendChild(infoButton); buttonGrid.appendChild(toggleInfoButton); contentContainer.appendChild(buttonGrid); contentContainer.appendChild(autoSelectButton); contentContainer.appendChild(autoRunButton); contentContainer.appendChild(infoDisplay); contentContainer.appendChild(footerBar); container.appendChild(titleBar); container.appendChild(contentContainer); document.body.appendChild(container); } function processSpecialNumbering(answers) { const result = []; for (let i = 0; i < answers.length; i++) { let answer = answers[i]; let lines = answer.split(/\n/).map(line => line.trim()).filter(Boolean); let processedLines = []; for (let j = 0; j < lines.length; j++) { let currentLine = lines[j]; let nextLine = j + 1 < lines.length ? lines[j + 1] : ''; if (/^\d+$/.test(currentLine) && nextLine.match(/^[00][\.\、\)]/)) { const prefix = currentLine; const match = nextLine.match(/^[00]([\.\、\)].*)/); if (match) { processedLines.push(`${prefix}${match[1]}`); j++; } else { processedLines.push(currentLine); } } else { processedLines.push(currentLine); } } result.push(processedLines.join('\n')); } return result; } function searchAnswersInJson(jsonContent, chapterContent, topicContent) { try { function formatJsonAnswer(answerObj) { try { let lines = []; if (Array.isArray(answerObj)) { lines = answerObj.map((item, index) => { const trimmedItem = item.toString().trim(); return /^\d+[\.\))]/.test(trimmedItem) ? trimmedItem : `${index + 1}) ${trimmedItem}`; }); } else if (typeof answerObj === 'object' && answerObj !== null) { const allKeys = Object.keys(answerObj).sort((a, b) => parseInt(a) - parseInt(b)); allKeys.forEach(key => { let value = answerObj[key]; if (typeof value === 'object' && value !== null) value = Object.values(value).join(' '); const formattedKey = key.trim().replace(/\.$/, ')'); lines.push(`${formattedKey} ${value}`); }); } else { return []; } if (lines.some(line => line.includes('|||'))) { const fillInAnswers = [], textAnswers = []; lines.forEach(line => { const parts = line.split('|||'); const prefix = (line.match(/^(\d+[\.\))]\s*)/) || [''])[0]; if (parts.length > 1) { fillInAnswers.push(prefix + parts[0].replace(/^(\d+[\.\))]\s*)/, '').trim()); textAnswers.push(prefix + parts[1].trim()); } else { fillInAnswers.push(line); } }); const groups = []; if (fillInAnswers.length > 0) groups.push(fillInAnswers.join('\n')); if (textAnswers.length > 0) groups.push(textAnswers.join('\n')); return groups; } const numberCounts = lines.reduce((acc, line) => { const match = line.match(/^(\d+)/); if (match) acc[match[1]] = (acc[match[1]] || 0) + 1; return acc; }, {}); const hasDuplicates = Object.values(numberCounts).some(count => count > 1); if (hasDuplicates) { console.log("检测到重复题号,启用特殊分组逻辑。"); const group1 = []; const group2 = []; const spares = []; const seenNumbers = new Set(); lines.forEach(line => { const numMatch = line.match(/^(\d+)/); if (numMatch) { const num = numMatch[1]; if (numberCounts[num] > 1) { if (seenNumbers.has(num)) { group1.push(line); } else { group2.push(line); seenNumbers.add(num); } } else { spares.push(line); } } }); const finalGroup1 = [...group1, ...spares].join('\n'); const finalGroup2 = [...group2, ...spares].join('\n'); console.log("分组完成,生成两组包含多余项的答案。"); return [finalGroup2, finalGroup1]; } return [lines.join('\n')]; } catch (error) { console.error('格式化答案时出错:', error); return []; } } console.log('--- 开始在JSON题库中搜索答案 ---'); const breadcrumbs = document.querySelectorAll('.ant-breadcrumb-link span'); const navigationPath = Array.from(breadcrumbs).map(span => span.textContent.trim()); const unitName = navigationPath.length > 1 ? navigationPath[1] : ''; const sectionName = navigationPath.length > 2 ? navigationPath[2] : ''; const activeTopicElement = document.querySelector('.pc-header-tab-activity'); const activeTopicName = activeTopicElement ? activeTopicElement.textContent.trim() : ''; const subTopicElement = document.querySelector('.pc-task.pc-header-task-activity'); const subTopicName = subTopicElement ? subTopicElement.textContent.trim() : ''; console.log(`导航路径: ${navigationPath.join(' > ')}`); console.log(`搜索范围: 单元='${unitName}', 区域='${sectionName}'`); console.log(`搜索关键字: 主题='${activeTopicName}', 子主题='${subTopicName}'`); let targetUnit = null; let targetUnitKey = null; if (unitName) { const normalizedUnitName = unitName.toLowerCase().replace(/['\s-]/g, ''); for (const [key, value] of Object.entries(jsonContent)) { const normalizedKey = key.toLowerCase().replace(/['\s-]/g, ''); if (normalizedKey.includes(normalizedUnitName)) { targetUnit = value; targetUnitKey = key; console.log(`成功匹配到单元: "${key}"`); break; } } } if (!targetUnit) { console.log('在题库中未能匹配到当前单元。'); return []; } let searchScope = targetUnit; let scopeName = targetUnitKey; console.log(`搜索范围已设置为整个单元: "${scopeName}",以提高定位准确性。`); const searchKeywords = [...new Set([activeTopicName, subTopicName])].filter(Boolean).map(s => s.toLowerCase()); const foundAnswers = []; function searchRecursive(obj, path = []) { if (!obj || typeof obj !== 'object') return; const isAnswerObject = Array.isArray(obj) || Object.keys(obj).some(k => /^\d+[\.\))]?$/.test(k)); if (isAnswerObject) { const currentPathStr = path.join(' ').toLowerCase(); const allKeywordsMatch = searchKeywords.every(keyword => currentPathStr.includes(keyword)); if (allKeywordsMatch) { const formattedAnswers = formatJsonAnswer(obj); if (formattedAnswers.length > 0) { foundAnswers.push(...formattedAnswers); console.log(`在路径 [${path.join(' -> ')}] 下找到 ${formattedAnswers.length} 组匹配答案`); } } return; } for (const [key, value] of Object.entries(obj)) { searchRecursive(value, [...path, key]); } } console.log(`开始在 "${scopeName}" 中搜索, 关键字: ${searchKeywords.join(', ')}`); searchRecursive(searchScope, []); console.log(`--- 搜索完成, 找到 ${foundAnswers.length} 组答案 ---`); const uniqueAnswers = [...new Set(foundAnswers)]; if (uniqueAnswers.length > 0) { console.log('找到的唯一答案:', uniqueAnswers); } return uniqueAnswers; } catch (error) { console.error('JSON题库搜索错误:', error); return []; } } function formatAnswer(answer) { if (!answer) return ''; const lines = answer.split(/\n/).map(line => line.trim()).filter(Boolean); return '\n' + lines.join('\n') + '\n'; } function deduplicateAnswers(answers) { const uniqueAnswers = new Set(); const result = []; for (const answer of answers) { const lowerAnswer = answer.toLowerCase(); if (!uniqueAnswers.has(lowerAnswer)) { uniqueAnswers.add(lowerAnswer); result.push(answer); } } return result; } function getAnswerType(answerGroup) { if (!answerGroup || typeof answerGroup !== 'string') return 'unknown'; const lines = answerGroup.split('\n').map(l => l.trim()).filter(Boolean); if (lines.length === 0) return 'unknown'; const allLinesAreChoiceFormat = lines.every(line => { const cleanedAnswer = line.replace(/^\d+[\.\、\)]+\s*/, '').trim(); return /^[A-ZА-Я](?:\s*,\s*[A-ZА-Я])*$/.test(cleanedAnswer); }); if (allLinesAreChoiceFormat) { return 'choice'; } return 'fill-in'; } function getAnswerDelay() { const baseDelay = parseInt(localStorage.getItem('u-answer-delay') || '800'); const randomFactor = 0.8 + Math.random() * 0.4; return Math.floor(baseDelay * randomFactor); } function getPageDelay() { return parseInt(localStorage.getItem('u-page-delay') || '3500'); } function simulateHumanBehavior(element) { if (!element) return; const mouseEvents = [ new MouseEvent('mouseover', { bubbles: true }), new MouseEvent('mouseenter', { bubbles: true }), ]; const focusEvents = [ new Event('focus', { bubbles: true }), new Event('focusin', { bubbles: true }), ]; setTimeout(() => { mouseEvents.forEach(event => element.dispatchEvent(event)); }, Math.random() * 200); setTimeout(() => { focusEvents.forEach(event => element.dispatchEvent(event)); element.focus(); }, Math.random() * 200 + 100); } async function handleSpecialFillInQuestions() { try { console.log('检查特殊填空题结构...'); const scoopContainers = document.querySelectorAll('.fe-scoop'); if (!scoopContainers || scoopContainers.length === 0) { console.log('未找到特殊填空题结构'); return false; } console.log(`找到 ${scoopContainers.length} 个特殊填空题`); const directions = document.querySelector(".layout-direction-container, .abs-direction"); const directionsText = directions ? directions.textContent.trim() : ''; const availableWords = []; const optionPlaceholders = document.querySelectorAll('.option-placeholder'); optionPlaceholders.forEach(placeholder => { const word = placeholder.textContent.trim(); if (word) { availableWords.push(word); console.log('找到可用填空词:', word); } }); if (availableWords.length === 0) { const wordsList = document.querySelectorAll('.words-color'); wordsList.forEach(word => { const wordText = word.textContent.trim(); if (wordText) { availableWords.push(wordText); console.log('从words-color找到可用填空词:', wordText); } }); } const questions = []; scoopContainers.forEach((container, index) => { const numberElement = container.querySelector('.question-number'); const number = numberElement ? numberElement.textContent.trim() : (index + 1).toString(); const paragraphElement = container.closest('p'); const contextText = paragraphElement ? paragraphElement.textContent.trim() : ''; questions.push({ number: number, context: contextText, type: 'special-fill-in', container: container }); }); if (questions.length === 0) { console.log('未能提取填空题信息'); return false; } let prompt = `请帮我完成以下填空题。\n指示:${directionsText}\n\n`; if (availableWords.length > 0) { prompt += "可用词汇(答案必须从以下词汇中选择):\n"; availableWords.forEach(word => { prompt += `- ${word}\n`; }); prompt += "\n"; } questions.forEach(q => { prompt += `${q.number}. ${q.context}\n`; }); prompt += "\n请按照以下格式回答每个题目:\n"; prompt += "1. [填空答案]\n2. [填空答案] ...\n"; if (availableWords.length > 0) { prompt += "注意:答案必须从上面列出的可用词汇中选择。\n"; } prompt += "注意:只需提供填空的单词或短语,无需解释。\n"; console.log('生成的AI提示:', prompt); console.log('正在请求AI回答...'); const aiAnswer = await askAI(prompt); if (!aiAnswer) { console.log('未能获取AI答案'); return false; } const answers = aiAnswer.split('\n').filter(line => /^\d+\./.test(line)); console.log('AI答案:', answers); for (let i = 0; i < questions.length; i++) { const question = questions[i]; const answerLine = answers[i]; if (!answerLine) continue; const answer = answerLine.replace(/^\d+\.\s*/, '').trim(); console.log(`准备填写题目 ${question.number} 的答案:`, answer); const inputSelectors = [ '.scoop-input-wrapper input', 'input', '.comp-abs-input input', '.input-user-answer input' ]; let input = null; for (const selector of inputSelectors) { input = question.container.querySelector(selector); if (input) { console.log(`找到填空输入框,使用选择器: ${selector}`); break; } } if (input) { await simulateHumanBehavior(input); input.value = answer + '\n'; console.log(`已添加换行符: ${answer}\\n`); input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); input.dispatchEvent(new Event('blur', { bubbles: true })); } else { console.log('未找到填空题输入框'); } await new Promise(resolve => setTimeout(resolve, getAnswerDelay())); } return true; } catch (error) { console.error('处理特殊填空题时发生错误:', error); return false; } } async function parseConsoleQuestions() { try { await new Promise(resolve => setTimeout(resolve, 1000)); const directions = document.querySelector(".layout-direction-container, .abs-direction"); const directionsText = directions ? directions.textContent.trim() : ''; console.log('题目指示:', directionsText); console.log('尝试从控制台输出获取题目信息'); const questions = []; const questionContainers = [ '.question-common-abs-question-container', '.question-wrap', '.question-basic', '.layoutBody-container.has-reply', '.question-material-banked-cloze.question-abs-question' ]; let mainContainer = null; for (const selector of questionContainers) { const container = document.querySelector(selector); if (container) { mainContainer = container; console.log(`找到题目容器: ${selector}`); break; } } if (!mainContainer) { console.log('未找到题目容器,使用默认查询'); mainContainer = document; } const availableWords = []; const optionPlaceholders = mainContainer.querySelectorAll('.option-placeholder'); optionPlaceholders.forEach(placeholder => { const word = placeholder.textContent.trim(); if (word) { availableWords.push(word); console.log('找到可用填空词:', word); } }); const questionSelectors = [ '.question-common-abs-reply', '.question-inputbox', '.question', '[class*="question"]', '[class*="stem"]' ]; let questionElements = []; for (const selector of questionSelectors) { const elements = mainContainer.querySelectorAll(selector); if (elements && elements.length > 0) { questionElements = Array.from(elements); console.log(`使用选择器 ${selector} 找到题目数量: ${elements.length}`); break; } } questionElements.forEach((element, index) => { let questionText = ''; if (element.querySelector('.fe-scoop, .scoop-input-wrapper')) { const paragraphs = element.querySelectorAll('p'); if (paragraphs.length > 0) { questionText = Array.from(paragraphs) .map(p => p.textContent.trim()) .join(' '); console.log(`从段落中提取填空题文本: ${questionText.substring(0, 50)}${questionText.length > 50 ? '...' : ''}`); } } if (!questionText) { const contentSelectors = [ '.question-inputbox-header', '.component-htmlview', 'p', '.title', '[class*="content"]', 'strong + span', '.words-color' ]; for (const selector of contentSelectors) { const contentElements = element.querySelectorAll(selector); if (contentElements && contentElements.length > 0) { questionText = Array.from(contentElements) .map(el => el.textContent.trim()) .join(' '); console.log(`使用选择器 ${selector} 找到题目文本`); break; } } } if (!questionText) { questionText = element.textContent.trim(); console.log('使用元素自身文本作为题目内容'); } questionText = questionText .replace(/\s+/g, ' ') .replace(/^\d+\.\s*/, '') .trim(); let number = (index + 1).toString(); const numberElement = element.querySelector('strong, [class*="number"], [class*="index"]'); if (numberElement) { const numberMatch = numberElement.textContent.match(/\d+/); if (numberMatch) { number = numberMatch[0]; } } let type = 'unknown'; if (element.querySelector('.fe-scoop, .scoop-input-wrapper, .comp-abs-input, .input-user-answer') || element.classList.contains('fill-blank-reply') || element.querySelector('span.question-number')) { type = 'fill-in'; console.log(`题目 ${number} 是填空题 (通过特殊类识别)`); } else if (element.querySelector('textarea')) { type = 'text'; console.log(`题目 ${number} 是文本题`); } else if (element.querySelector('input[type="text"]')) { type = 'fill-in'; console.log(`题目 ${number} 是填空题`); } else if (element.querySelector('input[type="radio"]')) { type = 'single-choice'; console.log(`题目 ${number} 是单选题`); } else if (element.querySelector('input[type="checkbox"]')) { type = 'multiple-choice'; console.log(`题目 ${number} 是多选题`); } else if (element.querySelector('.option-wrap, .option.isNotReview, .caption')) { const options = element.querySelectorAll('.option-wrap, .option.isNotReview'); if (options.length > 0) { const titleElement = element.querySelector('.ques-title, .component-htmlview.ques-title'); const titleText = titleElement ? titleElement.textContent : ''; if (titleText.includes('多选') || titleText.includes('所有') || titleText.includes('多个') || titleText.includes('multiple')) { type = 'multiple-choice'; console.log(`题目 ${number} 是多选题 (通过选项和标题识别)`); } else { type = 'single-choice'; console.log(`题目 ${number} 是单选题 (通过选项识别)`); } } } else { const parentElement = element.parentElement; if (parentElement && ( parentElement.querySelector('.fe-scoop') || parentElement.querySelector('.scoop-input-wrapper') || parentElement.querySelector('.comp-abs-input') )) { type = 'fill-in'; console.log(`题目 ${number} 是填空题 (通过父元素识别)`); } else if (parentElement && parentElement.querySelector('.option-wrap, .option.isNotReview, .caption')) { type = 'single-choice'; console.log(`题目 ${number} 是选择题 (通过父元素识别)`); } else { type = 'text'; console.log(`题目 ${number} 类型未知,默认为文本题`); } } questions.push({ number: number, text: questionText, type: type, element: element }); }); let prompt = `请帮我完成以下题目。\n指示:${directionsText}\n\n`; questions.forEach(q => { let typeDesc = ''; switch (q.type) { case 'single-choice': typeDesc = '【单选题】'; break; case 'multiple-choice': typeDesc = '【多选题】'; break; case 'fill-in': typeDesc = '【填空题】'; break; case 'text': typeDesc = '【文本题】'; break; default: typeDesc = ''; } prompt += `${q.number}. ${typeDesc}${q.text}\n`; }); prompt += "\n请按照以下格式回答每个题目:\n"; prompt += "1. [答案]\n2. [答案] ...\n\n"; prompt += "注意事项:\n"; prompt += "- 只需提供答案,无需解释\n"; prompt += "- 单选题请直接回答选项内容,例如 'energy' 或 'future'\n"; prompt += "- 多选题请直接回答选项内容,用逗号分隔,例如 'energy, future'\n"; prompt += "- 填空题直接提供单词或短语\n"; prompt += "- 文本题提供完整句子或段落\n"; console.log('生成的AI提示:', prompt); return { prompt: prompt, questions: questions }; } catch (error) { console.error('解析题目时发生错误:', error); return null; } } async function autoSelectAnswers() { try { console.log('开始自动选择答案'); console.log('AI状态:', { useKimiAI }); if (useKimiAI) { const specialFillInResult = await handleSpecialFillInQuestions(); if (specialFillInResult) { console.log('已成功处理特殊填空题'); return true; } } if (useKimiAI) { console.log('使用AI模式答题'); const questionInfo = await parseConsoleQuestions(); if (!questionInfo) { console.log('无法获取题目信息'); return false; } console.log('正在请求AI回答...'); const aiAnswer = await askAI(questionInfo.prompt); if (!aiAnswer) { console.log('未能获取AI答案'); return false; } const answers = aiAnswer.split('\n').filter(line => /^\d+\./.test(line)); console.log('AI答案:', answers); for (let i = 0; i < questionInfo.questions.length; i++) { const question = questionInfo.questions[i]; const answerLine = answers[i]; if (!answerLine) continue; const answer = answerLine.replace(/^\d+\.\s*/, '').trim(); console.log(`准备填写题目 ${question.number} 的答案:`, answer); const questionElement = question.element; if (!questionElement) { console.log(`题目 ${question.number} 没有找到对应的DOM元素`); continue; } if (question.type === 'fill-in') { const inputSelectors = [ 'input[type="text"]', '.comp-abs-input input', '.input-user-answer input', '.scoop-input-wrapper input', '.fe-scoop input', 'input' ]; let input = null; for (const selector of inputSelectors) { input = questionElement.querySelector(selector); if (input) { console.log(`找到填空输入框,使用选择器: ${selector}`); break; } } if (input) { console.log(`填写填空题答案: ${answer}`); await simulateHumanBehavior(input); input.value = answer + '\n'; console.log(`已添加换行符: ${answer}\\n`); input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); input.dispatchEvent(new Event('blur', { bubbles: true })); const submitButton = questionElement.querySelector('button[type="submit"], [class*="submit"], [class*="confirm"]'); if (submitButton) { console.log('找到提交按钮,点击提交'); await new Promise(resolve => setTimeout(resolve, 300)); submitButton.click(); } } else { console.log('未找到填空题输入框'); } } else if (question.type === 'text') { const textareaSelectors = [ 'textarea.question-inputbox-input', 'textarea', '.question-inputbox-input-container textarea', '.question-inputbox-body textarea' ]; let textarea = null; for (const selector of textareaSelectors) { textarea = questionElement.querySelector(selector); if (textarea) break; } if (textarea) { console.log(`找到文本框,填写答案: ${answer.substring(0, 20)}${answer.length > 20 ? '...' : ''}`); await simulateHumanBehavior(textarea); const typeText = async (text, element) => { const delay = () => Math.floor(Math.random() * 30) + 10; element.value = ''; element.dispatchEvent(new Event('input', { bubbles: true })); let currentText = ''; for (let i = 0; i < text.length; i++) { await new Promise(resolve => setTimeout(resolve, delay())); currentText += text[i]; element.value = currentText; element.dispatchEvent(new Event('input', { bubbles: true })); } currentText += '\n'; element.value = currentText; console.log(`已添加换行符: ${text}\\n`); element.dispatchEvent(new Event('input', { bubbles: true })); element.dispatchEvent(new Event('change', { bubbles: true })); }; await typeText(answer, textarea); } else { console.log('未找到文本框元素'); } } else if (question.type === 'single-choice') { console.log('处理单选题,答案:', answer); const cleanAnswer = answer.trim().toLowerCase(); console.log('清理后的答案:', cleanAnswer); let targetOption = null; let targetLetter = null; let foundByText = false; const options = questionElement.querySelectorAll('.option.isNotReview'); const optionsArray = Array.from(options); for (let i = 0; i < optionsArray.length; i++) { const option = optionsArray[i]; const content = option.querySelector('.component-htmlview.content'); const caption = option.querySelector('.caption'); if (content && caption) { const contentText = content.textContent.trim().toLowerCase(); const letter = caption.textContent.trim(); console.log(`选项 ${letter}: "${contentText}"`); if (contentText === cleanAnswer || contentText.includes(cleanAnswer) || cleanAnswer.includes(contentText)) { targetOption = option; targetLetter = letter; foundByText = true; console.log(`找到匹配的选项 ${letter}: "${contentText}"`); break; } } } if (!targetOption && cleanAnswer.length === 1 && /^[A-Z]$/i.test(cleanAnswer)) { const letter = cleanAnswer.toUpperCase(); for (const option of optionsArray) { const caption = option.querySelector('.caption'); if (caption && caption.textContent.trim() === letter) { targetOption = option; targetLetter = letter; console.log(`通过选项字母找到选项 ${letter}`); break; } } } if (targetOption && targetLetter) { console.log(`准备点击选项 ${targetLetter} (${foundByText ? '通过文本匹配' : '通过字母匹配'})`); await simulateHumanBehavior(targetOption); targetOption.click(); console.log(`已点击选项 ${targetLetter}`); const submitButton = questionElement.querySelector('button[type="submit"], [class*="submit"], [class*="confirm"]'); if (submitButton) { console.log('找到提交按钮,点击提交'); await new Promise(resolve => setTimeout(resolve, 300)); submitButton.click(); } } else { console.log(`未找到匹配的选项: ${cleanAnswer}`); } if (targetOption) { console.log(`选择选项: ${foundByText ? '通过文本内容匹配' : '通过选项字母'}`); await simulateHumanBehavior(targetOption); if (targetOption.tagName === 'INPUT' && targetOption.type === 'radio') { targetOption.checked = true; targetOption.dispatchEvent(new Event('change', { bubbles: true })); } targetOption.click(); console.log(`已点击选项`); const submitButton = questionElement.querySelector('button[type="submit"], [class*="submit"], [class*="confirm"]'); if (submitButton) { console.log('找到提交按钮,点击提交'); await new Promise(resolve => setTimeout(resolve, 300)); submitButton.click(); } } else { console.log(`未找到匹配的选项: ${answer}`); } } else if (question.type === 'multiple-choice') { console.log('处理多选题,答案:', answer); const options = questionElement.querySelectorAll('.option.isNotReview'); const optionsArray = Array.from(options); const foundOptions = []; const letterMatches = answer.match(/[A-Z]/gi); if (letterMatches && letterMatches.length > 0) { console.log('检测到选项字母答案:', letterMatches); for (const option of optionsArray) { const caption = option.querySelector('.caption'); if (caption) { const letter = caption.textContent.trim(); if (letterMatches.includes(letter.toUpperCase())) { foundOptions.push({ option: option, letter: letter, text: option.querySelector('.component-htmlview.content')?.textContent.trim() || '' }); console.log(`找到字母匹配的选项 ${letter}`); } } } } if (foundOptions.length === 0) { const answerParts = answer.split(/[,,、;;\s]+/).filter(part => part.trim().length > 0); console.log('答案拆分为多个部分:', answerParts); for (let i = 0; i < optionsArray.length; i++) { const option = optionsArray[i]; const content = option.querySelector('.component-htmlview.content'); const caption = option.querySelector('.caption'); if (content && caption) { const contentText = content.textContent.trim().toLowerCase(); const letter = caption.textContent.trim(); console.log(`选项 ${letter}: "${contentText}"`); for (const part of answerParts) { const cleanPart = part.trim().toLowerCase(); if (contentText === cleanPart || contentText.includes(cleanPart) || cleanPart.includes(contentText)) { foundOptions.push({ option: option, letter: letter, text: contentText }); console.log(`找到文本匹配的选项 ${letter}: "${contentText}"`); break; } } } } } if (foundOptions.length > 0) { console.log(`找到 ${foundOptions.length} 个匹配的选项`); for (const option of optionsArray) { if (option.classList.contains('selected')) { option.click(); await new Promise(resolve => setTimeout(resolve, 100)); } } for (const found of foundOptions) { console.log(`准备点击选项 ${found.letter}: "${found.text}"`); await simulateHumanBehavior(found.option); found.option.click(); console.log(`已点击选项 ${found.letter}`); await new Promise(resolve => setTimeout(resolve, Math.random() * 300 + 100)); } const submitButton = questionElement.querySelector('button[type="submit"], [class*="submit"], [class*="confirm"]'); if (submitButton) { console.log('找到提交按钮,点击提交'); await new Promise(resolve => setTimeout(resolve, 300)); submitButton.click(); } } else { console.log('未找到匹配的选项:', answer); } } await new Promise(resolve => setTimeout(resolve, getAnswerDelay())); } return true; } const contentInfo = await getContentInfo(); if (!contentInfo || !contentInfo.answers || contentInfo.answers.length === 0) { console.log('%c未找到匹配的答案,可能为主观题,将自动跳过。', 'color: #f44336; font-weight: bold;'); return false; } if (contentInfo.activeTopicName !== lastActiveTopicName) { currentTopicUsedAnswers.clear(); lastActiveTopicName = contentInfo.activeTopicName; console.log('%c检测到主题切换,已重置答案使用记录', 'color: #2196F3;'); } const textareas = document.querySelectorAll('textarea.question-inputbox-input'); if (textareas && textareas.length > 0) { console.log('%c检测到文本框题,开始自动填写答案', 'color: #4CAF50; font-weight: bold;'); let selectedAnswer = null; for (const answer of contentInfo.answers) { if (getAnswerType(answer) === 'fill-in' && !currentTopicUsedAnswers.has(answer)) { selectedAnswer = answer; currentTopicUsedAnswers.add(answer); console.log('%c使用新答案组 (文本题型)', 'color: #2196F3;', answer); break; } } if (!selectedAnswer) { for (const answer of contentInfo.answers) { if (getAnswerType(answer) === 'fill-in') { selectedAnswer = answer; console.log('%c所有文本题答案组都已使用,将重用第一组匹配的答案', 'color: #FFA500;'); break; } } } if (!selectedAnswer) { console.log('%c未找到适用于当前文本题的答案组。', 'color: #f44336;'); return false; } const answerMatches = selectedAnswer.match(/\d+[\.\、\) ][\s\S]*?(?=\d+[\.\、\) ]|$)/g); if (!answerMatches) { console.log('%c无法解析答案格式', 'color: #f44336;'); return false; } for (let index = 0; index < textareas.length; index++) { const textarea = textareas[index]; try { if (answerMatches[index]) { const rawAnswer = answerMatches[index] .replace(/^\d+[\.\、\) ]\s*/, '') .trim(); const parts = rawAnswer.split('|||'); let answer = ''; let answerType = ''; if (parts.length > 1) { answer = parts[0].trim(); answerType = '填空题'; } else { answer = parts[0].trim(); answerType = '文本题'; } await new Promise(resolve => { setTimeout(() => { simulateHumanBehavior(textarea); let currentText = ''; const answerText = answer + '\n'; let charIndex = 0; const typeNextChar = () => { if (charIndex < answerText.length) { currentText += answerText.charAt(charIndex); textarea.value = currentText; textarea.dispatchEvent(new Event('input', { bubbles: true })); const typingDelay = 30 + Math.random() * 80; charIndex++; setTimeout(typeNextChar, typingDelay); } else { textarea.dispatchEvent(new Event('change', { bubbles: true })); textarea.dispatchEvent(new Event('blur', { bubbles: true })); resolve(); } }; typeNextChar(); }, getAnswerDelay() + index * 500); }); console.log(`%c第 ${index + 1} 题 (${answerType}) 已填写答案: ${answer}`, 'color: #2196F3;'); } else { console.log(`%c第 ${index + 1} 题未找到对应答案`, 'color: #f44336;'); } } catch (error) { console.error(`填写第 ${index + 1} 题时发生错误:`, error); } } return true; } const fillInBlanks = document.querySelectorAll('.fe-scoop'); if (fillInBlanks && fillInBlanks.length > 0) { console.log('%c检测到填空题,开始自动填写答案', 'color: #4CAF50; font-weight: bold;'); let selectedAnswer = null; for (const answer of contentInfo.answers) { if (getAnswerType(answer) === 'fill-in' && !currentTopicUsedAnswers.has(answer)) { selectedAnswer = answer; currentTopicUsedAnswers.add(answer); console.log('%c使用新答案组 (填空题型)', 'color: #2196F3;', answer); break; } } if (!selectedAnswer) { for (const answer of contentInfo.answers) { if (getAnswerType(answer) === 'fill-in') { selectedAnswer = answer; console.log('%c所有填空题答案组都已使用,将重用第一组匹配的答案', 'color: #FFA500;'); break; } } } if (!selectedAnswer) { console.log('%c未找到适用于当前填空题的答案组。', 'color: #f44336;'); return false; } const answerLines = selectedAnswer.split('\n').map(line => line.trim()).filter(Boolean); if (!answerLines || answerLines.length === 0) { console.log('%c无法解析答案格式', 'color: #f44336;'); return false; } const fillInAnswers = answerLines.map(line => { return line.replace(/^\d+[\.\、\) ]\s*/, '').trim(); }).filter(answer => { return !/^[A-Z]$/.test(answer); }); if (fillInAnswers.length < fillInBlanks.length) { console.log(`%c警告:找到的填空答案数量 (${fillInAnswers.length}) 少于页面空格数量 (${fillInBlanks.length})。`, 'color: #FFA500;'); } for (let index = 0; index < fillInBlanks.length; index++) { const blank = fillInBlanks[index]; try { const inputContainer = blank.querySelector('.comp-abs-input'); const input = inputContainer ? inputContainer.querySelector('input') : null; if (input && fillInAnswers[index]) { const rawAnswer = fillInAnswers[index]; const answer = rawAnswer.split('|||')[0].trim(); await new Promise(resolve => { setTimeout(() => { simulateHumanBehavior(input); let currentText = ''; const answerText = answer + '\n'; let charIndex = 0; const typeNextChar = () => { if (charIndex < answerText.length) { currentText += answerText.charAt(charIndex); input.value = currentText; input.dispatchEvent(new Event('input', { bubbles: true })); const typingDelay = 30 + Math.random() * 80; charIndex++; setTimeout(typeNextChar, typingDelay); } else { input.dispatchEvent(new Event('change', { bubbles: true })); input.dispatchEvent(new Event('blur', { bubbles: true })); resolve(); } }; typeNextChar(); }, getAnswerDelay() + index * 500); }); console.log(`%c第 ${index + 1} 题已填写答案: ${answer}`, 'color: #2196F3;'); } else { console.log(`%c第 ${index + 1} 题未找到输入框或有效答案`, 'color: #f44336;'); } } catch (error) { console.error(`填写第 ${index + 1} 题时发生错误:`, error); } } return true; } const matchingWrapper = document.querySelector('#sortableListWrapper'); if (matchingWrapper) { console.log('%c检测到匹配/排序题,开始自动填写答案', 'color: #4CAF50; font-weight: bold;'); const iframe = document.querySelector('iframe#pc-sequence-iframe'); const doc = iframe ? (iframe.contentDocument || iframe.contentWindow.document) : document; const inputs = doc.querySelectorAll('input[type="text"], input.answer-item-input'); if (!inputs || inputs.length === 0) { console.log('%c在此类匹配题中未找到输入框。', 'color: #f44336;'); return false; } let selectedAnswer = null; for (const answer of contentInfo.answers) { if (!currentTopicUsedAnswers.has(answer)) { selectedAnswer = answer; currentTopicUsedAnswers.add(answer); console.log('%c使用新答案组', 'color: #2196F3;', answer); break; } } if (!selectedAnswer) { console.log('%c所有答案组都已使用,将重用第一组答案', 'color: #FFA500;'); selectedAnswer = contentInfo.answers[0]; } const answerMatches = selectedAnswer.match(/\d+[\.\、\)]\s*[A-Z]/g); if (!answerMatches) { console.log('%c无法解析匹配题答案格式 (e.g., "1) D 2) C ...")', 'color: #f44336;'); return false; } const answers = answerMatches.map(ans => ans.replace(/\d+[\.\、\)]\s*/, '').trim()); for (let index = 0; index < inputs.length; index++) { const input = inputs[index]; if (answers[index]) { await new Promise(resolve => { setTimeout(() => { simulateHumanBehavior(input); input.value = answers[index]; input.dispatchEvent(new Event('input', { bubbles: true })); setTimeout(() => { input.dispatchEvent(new Event('blur', { bubbles: true })); resolve(); }, 100 + Math.random() * 200); }, getAnswerDelay() + index * 400); }); console.log(`%c匹配题 ${index + 1} 已填写答案: ${answers[index]}`, 'color: #2196F3;'); } } return true; } const choiceContainer = document.querySelector('.question-common-abs-choice'); const optionDivs = document.querySelectorAll('div.option'); if (!choiceContainer && (!optionDivs || optionDivs.length === 0)) { console.log('%c当前页面既不是选择题也不是填空题也不是文本框题', 'color: #f44336; font-weight: bold;'); return false; } const questions = choiceContainer ? document.querySelectorAll('.question-common-abs-reply') : document.querySelectorAll('.question-common-abs-banked-cloze'); if (!questions || questions.length === 0) { console.log('%c未找到题目', 'color: #f44336; font-weight: bold;'); return false; } console.log('%c开始自动选择答案', 'color: #4CAF50; font-weight: bold;'); let selectedAnswer = null; for (const answer of contentInfo.answers) { if (getAnswerType(answer) === 'choice' && !currentTopicUsedAnswers.has(answer)) { selectedAnswer = answer; currentTopicUsedAnswers.add(answer); console.log('%c使用新答案组 (选择题型)', 'color: #2196F3;', answer); break; } } if (!selectedAnswer) { for (const answer of contentInfo.answers) { if (getAnswerType(answer) === 'choice') { selectedAnswer = answer; console.log('%c所有选择题答案组都已使用,将重用第一组匹配的答案', 'color: #FFA500;'); break; } } } if (!selectedAnswer) { console.log('%c未找到适用于当前选择题的答案组。', 'color: #f44336;'); return false; } for (let questionIndex = 0; questionIndex < questions.length; questionIndex++) { const question = questions[questionIndex]; await new Promise(resolve => setTimeout(resolve, getAnswerDelay() * (1 + questionIndex * 0.5) + Math.random() * 300)); const options = choiceContainer ? question.querySelectorAll('.option.isNotReview') : question.querySelectorAll('div.option'); if (!options || options.length === 0) { console.log(`%c第 ${questionIndex + 1} 题未找到选项`, 'color: #f44336;'); continue; } const answerPattern = /(\d+)[\.\、\)]\s*([A-K](?:\s*,\s*[A-K])*)/g; const answers = []; let match; answerPattern.lastIndex = 0; while ((match = answerPattern.exec(selectedAnswer)) !== null) { const questionNum = parseInt(match[1]); const answerChoices = match[2].split(/\s*,\s*/); answers[questionNum - 1] = answerChoices; } if (answers.length > 0 && questionIndex < answers.length) { const answerChoices = answers[questionIndex]; if (answerChoices && answerChoices.length > 0) { console.log(`%c第 ${questionIndex + 1} 题检测到答案: ${answerChoices.join(', ')}`, 'color: #2196F3;'); for (let letterIndex = 0; letterIndex < answerChoices.length; letterIndex++) { const letter = answerChoices[letterIndex]; let targetOption = null; for (let i = 0; i < options.length; i++) { const caption = options[i].querySelector('.caption'); if (caption && caption.textContent.trim() === letter) { targetOption = options[i]; break; } } if (targetOption) { await new Promise(resolve => { setTimeout(() => { const isSelected = targetOption.classList.contains('selected'); if (!isSelected) { targetOption.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); setTimeout(() => { targetOption.click(); console.log(`%c第 ${questionIndex + 1} 题已选择选项 ${letter}`, 'color: #2196F3;'); setTimeout(resolve, 100 + Math.random() * 200); }, 100 + Math.random() * 300); } else { console.log(`%c第 ${questionIndex + 1} 题选项 ${letter} 已经被选中`, 'color: #FFA500;'); resolve(); } }, getAnswerDelay() + letterIndex * 200); }); } else { console.log(`%c第 ${questionIndex + 1} 题未找到选项 ${letter}`, 'color: #f44336;'); } } } else { console.log(`%c第 ${questionIndex + 1} 题未找到答案`, 'color: #f44336;'); } } else { console.log(`%c第 ${questionIndex + 1} 题未找到答案`, 'color: #f44336;'); } } return true; } catch (error) { console.error('自动选择答案时发生错误:', error); return false; } } async function getContentInfo() { try { const breadcrumbs = document.querySelectorAll('.ant-breadcrumb-link span'); const navigationPath = Array.from(breadcrumbs).map(span => span.textContent.trim()); const chapterContent = navigationPath.length >= 2 ? navigationPath[1] : '未找到章节内容'; const topicElement = document.querySelector(".pc-header-tasks-container .pc-task.pc-header-task-activity"); const topicElementSecond = document.querySelector(".pc-header-tasks-container .pc-task.pc-header-task-activity.pc-task-last"); const topicFirstPart = topicElement ? topicElement.textContent.trim() : ''; const topicSecondPart = topicElementSecond ? topicElementSecond.textContent.trim() : ''; const topicContent = [topicFirstPart, topicSecondPart].filter(Boolean).join(' : '); const finalTopicContent = topicContent || '未找到主题内容'; const tabContainer = document.querySelector('.pc-tab-container'); const allTopics = tabContainer ? tabContainer.querySelectorAll('.ant-col') : []; const totalTopics = allTopics.length; const allTopicNames = []; allTopics.forEach((topic, index) => { const topicDiv = topic.querySelector('.pc-tab-view-container'); if (topicDiv) { const isActive = topic.classList.contains('pc-header-tab-activity'); allTopicNames.push({ index: index + 1, name: topicDiv.textContent.trim(), isActive: isActive }); } }); let activeTopicName = ''; let nextTopicName = ''; let foundActive = false; for (let i = 0; i < allTopicNames.length; i++) { if (foundActive) { nextTopicName = allTopicNames[i].name; break; } if (allTopicNames[i].isActive) { activeTopicName = allTopicNames[i].name; foundActive = true; } } console.log('%c当前页面信息', 'color: #2196F3; font-weight: bold; font-size: 14px;'); console.log('导航路径:', navigationPath); console.log('章节内容:', chapterContent); console.log('主题内容:', finalTopicContent); console.log('主题总数:', totalTopics); console.log('当前选中的主题:', activeTopicName || '未找到选中的主题'); console.log('下一个主题:', nextTopicName || '没有下一个主题'); console.log('\n%c所有主题列表', 'color: #2196F3; font-weight: bold; font-size: 14px;'); allTopicNames.forEach(topic => { console.log(`${topic.index}. ${topic.name} ${topic.isActive ? '(当前选中)' : ''}`); }); let answers = []; try { console.log('\n%c正在从题库搜索答案...', 'color: #4CAF50; font-weight: bold; font-size: 14px;'); if (!loadedQuestionBank) { console.log('%c请先上传题库文件。', 'color: #f44336; font-weight: bold;'); return { chapter: chapterContent, topic: finalTopicContent, totalTopics: totalTopics, activeTopicName: activeTopicName, nextTopicName: nextTopicName, allTopics: allTopicNames, answers: [] }; } const questionBankData = { type: 'json', content: loadedQuestionBank }; answers = await searchAnswers(questionBankData, chapterContent, finalTopicContent, nextTopicName); console.log('\n%c匹配到的答案', 'color: #4CAF50; font-weight: bold; font-size: 14px;'); if (answers.length === 0) { console.log('%c未找到匹配的答案', 'color: #f44336;'); } else { answers.forEach((answer, index) => { console.log(`\n%c答案 ${index + 1}:`, 'color: #4CAF50; font-weight: bold;'); console.log(formatAnswer(answer)); }); } } catch (error) { console.error('获取或搜索题库时发生错误:', error); } return { chapter: chapterContent, topic: finalTopicContent, totalTopics: totalTopics, activeTopicName: activeTopicName, nextTopicName: nextTopicName, allTopics: allTopicNames, answers: answers }; } catch (error) { console.error('获取内容时发生错误:', error); return null; } } async function searchAnswers(questionBankData, chapterContent, topicContent, nextTopicName) { try { chapterContent = chapterContent.trim(); topicContent = topicContent.trim(); if (!questionBankData || questionBankData.type !== 'json' || !questionBankData.content) { console.log('题库数据无效或不是JSON格式'); return []; } console.log('使用JSON格式题库进行搜索'); return searchAnswersInJson(questionBankData.content, chapterContent, topicContent); } catch (error) { console.error('搜索答案时发生错误:', error); return []; } } let isAutoRunning = false; let autoRunTimeoutId = null; const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); function updateAutoRunButtonUI() { const btn = document.getElementById('auto-run-btn'); if (!btn) return; if (isAutoRunning) { btn.innerHTML = '停止挂机'; btn.className = 'u-helper-btn u-helper-btn-danger'; } else { btn.innerHTML = '开始挂机'; btn.className = 'u-helper-btn u-helper-btn-success'; } } function findFooterButtonByText(text) { const footerButtons = document.querySelectorAll('#pc-foot a, #pc-foot button'); for (const btn of footerButtons) { if (btn.textContent.replace(/\s/g, '').includes(text)) { return btn; } } return null; } async function handleVocabularyCards() { console.log('[挂机] 检测到词汇卡片学习页面,开始自动点击下一个...'); let cardCount = 0; while (cardCount < 300) { const nextButton = document.querySelector("#main-content > div > div > div > div.layoutBody-container > div > div > div.vocContainer > div.vocActions > div.action.next"); if (!nextButton || nextButton.classList.contains('disabled')) { console.log(`[挂机] 词汇卡片处理完成,共处理 ${cardCount} 个卡片。准备跳转到下一任务。`); return; } await sleep(500 + Math.random() * 1000); nextButton.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); await sleep(100 + Math.random() * 200); console.log(`[挂机] 点击第 ${cardCount + 1} 个词汇卡片的"下一个"按钮`); nextButton.click(); cardCount++; await sleep(800 + Math.random() * 500); } console.log(`[挂机] 已达到最大处理数量限制 (${cardCount})`); } async function waitForVideosToEnd() { const videos = Array.from(document.querySelectorAll('video')).filter(v => !v.ended && v.duration > 0); if (videos.length === 0) { console.log('[挂机] 页面上没有需要等待的视频,继续执行。'); return; } console.log(`[挂机] 检测到 ${videos.length} 个视频,等待播放完毕...`); const videoPromises = videos.map(video => { return new Promise(resolve => { if (video.ended) { resolve(); return; } const onEnded = () => { console.log('[挂机] 一个视频已播放完毕。'); video.removeEventListener('ended', onEnded); resolve(); }; video.addEventListener('ended', onEnded); const observer = new MutationObserver(() => { if (!document.body.contains(video)) { console.log('[挂机] 一个视频已从页面移除,视为播放结束。'); observer.disconnect(); video.removeEventListener('ended', onEnded); resolve(); } }); observer.observe(document.body, { childList: true, subtree: true }); }); }); await Promise.all(videoPromises); console.log('[挂机] 所有视频均已播放完毕。'); } async function runNextStep() { if (!isAutoRunning) return; try { const vocContainer = document.querySelector("#main-content > div > div > div > div.layoutBody-container > div > div > div.vocContainer"); const discussionTextarea = document.querySelector("#bottom > div > div > div.discussion-cloud-bottom > div.discussion-cloud-bottom-textArea-container > div.ant-input-textarea.ant-input-textarea-show-count.discussion-cloud-bottom-textArea > textarea"); if (vocContainer) { await handleVocabularyCards(); } else if (discussionTextarea) { await handleDiscussionPage(); } else { console.log('[挂机] 尝试自动选择答案...'); const foundAnswers = await autoSelectAnswers(); if (foundAnswers) { console.log('[挂机] 已尝试填充答案。'); await sleep(getAnswerDelay() * 2); const submitButton = findFooterButtonByText('提交'); if (submitButton) { console.log('[挂机] 题目作答完毕,点击 "提交"'); await sleep(800 + Math.random() * 1000); submitButton.click(); await sleep(2500); } } else { console.log('[挂机] 未找到答案,可能为主观题,将直接尝试导航。'); await sleep(500); } } await waitForVideosToEnd(); console.log('[挂机] 内容处理/提交完毕,查找下一步操作进行导航...'); const nextQuestionButton = findFooterButtonByText('下一题'); if (nextQuestionButton) { console.log('[挂机] 操作: 点击 "下一题"'); await sleep(500 + Math.random() * 800); nextQuestionButton.click(); autoRunTimeoutId = setTimeout(runNextStep, getPageDelay()); return; } const navigatedToSubTopic = await navigateToNextSubTopic(); if (navigatedToSubTopic) { autoRunTimeoutId = setTimeout(runNextStep, getPageDelay()); return; } const navigatedByToc = await navigateToNextTocItem(); if (navigatedByToc) { autoRunTimeoutId = setTimeout(runNextStep, getPageDelay()); return; } console.log('[挂机] 结束: 未找到任何可执行的导航操作。'); isAutoRunning = false; updateAutoRunButtonUI(); } catch (error) { console.error('[挂机] 执行步骤时发生错误:', error); isAutoRunning = false; updateAutoRunButtonUI(); } } async function navigateToNextSubTopic() { console.log('[挂机] 尝试导航到下一个子主题 (横向Tab)...'); const tabSystems = [ { name: "子任务Tabs", containerSelector: '.pc-header-tasks-container', tabSelector: '.pc-task', activeClass: 'pc-header-task-activity' }, { name: "主任务Tabs", containerSelector: '.pc-tab-container', tabSelector: '.ant-col', activeClass: 'pc-header-tab-activity' } ]; for (const system of tabSystems) { const container = document.querySelector(system.containerSelector); if (!container) { continue; } const tabs = Array.from(container.querySelectorAll(system.tabSelector)); if (tabs.length <= 1) { continue; } const activeIndex = tabs.findIndex(tab => tab.classList.contains(system.activeClass)); if (activeIndex === -1) { continue; } if (activeIndex + 1 < tabs.length) { const nextTab = tabs[activeIndex + 1]; const clickable = nextTab.querySelector('.pc-tab-view-container') || nextTab; const nextTabName = (clickable.textContent || nextTab.title || '未知').trim(); console.log(`[挂机] 导航到下一个子主题: "${nextTabName}"`); await sleep(600 + Math.random() * 800); clickable.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); await sleep(200 + Math.random() * 300); clickable.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); await sleep(50 + Math.random() * 50); clickable.dispatchEvent(new MouseEvent('mouseup', { bubbles: true })); clickable.click(); return true; } else { console.log(`[挂机] 在 ${system.name} 中已是最后一个Tab。`); } } console.log('[挂机] 未找到可切换的下一个子主题。'); return false; } async function navigateToNextTocItem() { console.log('[挂机] 未找到操作按钮,尝试导航到目录中的下一项...'); const tocContainer = document.querySelector('.pc-slier-menu-container.show, .pc-slider-menu-container.show'); if (!tocContainer) { console.log('[挂机] 结束: 无法找到目录容器。'); return false; } const tocItems = tocContainer.querySelectorAll('div[data-role="micro"]'); if (tocItems.length === 0) { console.log('[挂机] 结束: 无法找到目录中的具体条目 (micro items)。'); return false; } let activeIndex = -1; for (let i = 0; i < tocItems.length; i++) { if (tocItems[i].classList.contains('pc-menu-activity')) { activeIndex = i; break; } } if (activeIndex === -1) { console.log('[挂机] 结束: 无法在目录中定位到当前活动项。'); return false; } if (activeIndex + 1 < tocItems.length) { const nextItem = tocItems[activeIndex + 1]; const nextItemName = nextItem.querySelector('.pc-menu-node-name')?.textContent.trim() || '未知项'; console.log(`[挂机] 导航到下一项: "${nextItemName}"`); await sleep(700 + Math.random() * 1000); nextItem.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); await sleep(300 + Math.random() * 400); nextItem.click(); return true; } else { console.log('[挂机] 结束: 已是目录中的最后一项。'); return false; } } function toggleAutoRun() { isAutoRunning = !isAutoRunning; updateAutoRunButtonUI(); if (isAutoRunning) { console.log('自动挂机已启动...'); runNextStep(); } else { if (autoRunTimeoutId) { clearTimeout(autoRunTimeoutId); autoRunTimeoutId = null; } console.log('自动挂机已手动停止。'); } } function handleVideo(video) { if (video.dataset.handledByScript) return; video.dataset.handledByScript = 'true'; console.log('[视频助手] 发现视频,开始处理:', video); const setPlaybackRate = () => { const targetSpeed = parseFloat(localStorage.getItem('u-video-speed') || '2.0'); if (video.playbackRate !== targetSpeed) { video.playbackRate = targetSpeed; console.log(`[视频助手] 视频倍速已设置为 ${targetSpeed}x`); } }; const attemptPlay = () => { video.muted = true; const playPromise = video.play(); if (playPromise !== undefined) { playPromise.then(() => { console.log('[视频助手] 视频已自动播放。'); setPlaybackRate(); }).catch(error => { console.warn('[视频助手] 自动播放失败,可能是浏览器策略限制。等待用户交互后再次尝试。'); const playOnInteraction = () => { video.play(); setPlaybackRate(); document.body.removeEventListener('click', playOnInteraction, true); }; document.body.addEventListener('click', playOnInteraction, { once: true, capture: true }); }); } }; if (video.readyState >= 3) { attemptPlay(); } else { video.addEventListener('canplay', attemptPlay, { once: true }); } video.addEventListener('ratechange', () => { setTimeout(setPlaybackRate, 100); }); video.addEventListener('playing', setPlaybackRate); video.addEventListener('pause', () => { setTimeout(() => { const popup = document.querySelector('.question-video-popup'); if (popup && popup.offsetParent !== null) { console.log('[视频助手] 视频暂停,检测到弹窗问题,开始处理...'); handleVideoPopupQuestions(popup); } }, 200); }); } function setupVideoHandler() { document.querySelectorAll('video').forEach(handleVideo); const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { if (node.tagName === 'VIDEO') { handleVideo(node); } else if (node.querySelectorAll) { node.querySelectorAll('video').forEach(handleVideo); } } }); } }); observer.observe(document.body, { childList: true, subtree: true }); console.log('已启动视频自动播放和倍速调整功能。'); } async function handleVideoPopupQuestions(popupElement) { if (popupElement.dataset.handledByRandomSelect) return; popupElement.dataset.handledByRandomSelect = 'true'; console.log('[视频弹题助手] 检测到视频中的弹窗问题,准备随机选择...'); await sleep(500); const options = popupElement.querySelectorAll('.option.isNotReview'); if (options.length > 0) { await sleep(1000 + Math.random() * 1500); const randomIndex = Math.floor(Math.random() * options.length); const randomOption = options[randomIndex]; const optionCaption = randomOption.querySelector('.caption')?.textContent.trim() || `选项 ${randomIndex + 1}`; console.log(`[视频弹题助手] 随机选择选项: ${optionCaption}`); randomOption.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); await sleep(150 + Math.random() * 200); randomOption.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); await sleep(50 + Math.random() * 50); randomOption.dispatchEvent(new MouseEvent('mouseup', { bubbles: true })); randomOption.click(); console.log('[视频弹题助手] 已成功点击选项。'); await sleep(500 + Math.random() * 300); const confirmButton = popupElement.querySelector('button.submit-btn'); if (confirmButton && !confirmButton.disabled) { console.log('[视频弹题助手] 找到"确定"按钮,正在点击...'); confirmButton.click(); console.log('[视频弹题助手] 已点击"确定"按钮。'); } else { console.log('[视频弹题助手] 未找到或"确定"按钮不可用。'); } } else { console.log('[视频弹题助手] 未在弹窗中找到可选选项。'); } } function setupVideoPopupObserver() { const observer = new MutationObserver(() => { const popup = document.querySelector('.question-video-popup'); if (popup && popup.offsetParent === null) { if (popup.dataset.handledByRandomSelect) { console.log('[视频弹题助手] 弹窗已隐藏,重置处理标记以便下次使用。'); delete popup.dataset.handledByRandomSelect; } } }); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class', 'hidden'] }); console.log('已启动视频内弹窗问题状态监视器。'); } async function handleDiscussionPage() { const textarea = document.querySelector("#bottom > div > div > div.discussion-cloud-bottom > div.discussion-cloud-bottom-textArea-container > div.ant-input-textarea.ant-input-textarea-show-count.discussion-cloud-bottom-textArea > textarea"); const submitButton = document.querySelector("#bottom > div > div > div.discussion-cloud-bottom > div.discussion-cloud-bottom-btns > div > div.btns-submit.student-btns-submit > button"); if (submitButton && !submitButton.disabled) { console.log('[挂机] 检测到讨论区,准备自动发送 "Hello"。'); await sleep(1000 + Math.random() * 500); textarea.dispatchEvent(new Event('focus', { bubbles: true })); textarea.value = "Hello"; textarea.dispatchEvent(new Event('input', { bubbles: true })); await sleep(200); textarea.dispatchEvent(new Event('blur', { bubbles: true })); console.log('[挂机] 已输入 "Hello"'); await sleep(500 + Math.random() * 500); if (!submitButton.disabled) { submitButton.click(); console.log('[挂机] 已点击发布按钮。'); await sleep(2500); } } else { console.log('[挂机] 讨论区发布按钮不可用或未找到,跳过。'); } } function setupPopupHandler() { setInterval(() => { document.querySelectorAll('.ant-modal-wrap:not([style*="display: none"])').forEach(modal => { const confirmButton = modal.querySelector('.ant-btn-primary'); if (confirmButton && confirmButton.offsetParent !== null) { const buttonText = confirmButton.textContent || confirmButton.innerText; if (buttonText.includes('确')) { console.log(`[自动确认] 发现弹窗,点击 "${buttonText.trim()}"`); confirmButton.click(); } } }); }, 1000); console.log('已启动全局弹窗自动确认处理器。'); } window.addEventListener('load', () => { createFloatingButton(); setupPopupHandler(); setupVideoHandler(); setupVideoPopupObserver(); });